Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
Unreleased

- Check generator redstone before the first power pulse so a disabled solar panel cannot produce a startup burst.
- Restore the photovoltaic panel's full-block property on Minecraft 1.13.2.
- Keep growth chamber crop stages and unfinished growth through a full restart, including saves before the chamber attaches to its world.

- Stop the electric still consuming oil and power when its full output will not fit.
- Drain waterlogged slabs and stairs without destroying the blocks, and only charge pump power after a successful pickup.
- Keep growth controllers from requesting more electricity than they can store or use.
- Read light above the solar panel so its solid body does not block its own light reading.
- Let turret beams leave their own mounting post while still respecting immediately adjacent cover and their existing range.
- Keep hydroturbines anchored to their generators so flowing water cannot build up invalid movement across a restart.
- Sample a machine's redstone interlock on its first server tick, then retain the normal eight-tick refresh.
- Send machine sounds to nearby connected players without loading surrounding chunks.
- Use an empty or full-scale hydroelectric display without changing the existing power-output reading or electricity generation.
- Add operating and persistence regression tests, including blocked outputs, refused pickups and unchanged waterlogged host blocks.

Electric Advantage 2.2.5.113021

- Keep development archives identical in existing workspaces and fresh CI checkouts.
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,40 @@ The Eclipse resource root is `build/resources/main`, so its builder copies expan

## Compatibility

The [stronger regression-test backport](https://github.com/MinecraftModDevelopmentMods/PowerAdvantage/blob/master-1.13.2/docs/1.13/TEST-COVERAGE-BACKPORT.md)
records measured machine work, restart checks, repaired windows and remaining limits.

For an all-Advantage play-test, run `prepareCombinedEclipseLaunch` in the sibling PowerAdvantage checkout and select its `runClientAllAdvantage.launch`. The [native resource probe](https://github.com/MinecraftModDevelopmentMods/PowerAdvantage/blob/master-1.13.2/test-rig/client-resources/README.md) checks models, textures and names across all three mods. These resource checks do not replace gameplay testing.

The implementation namespace is `zone.moddev.mc.electricadvantage`. Runtime identities, registries, tile/entity IDs, NBT, recipes, tags, OreSpawn provider IDs, and assets remain stable. Electric registers its legacy state, tile, and hydroturbine aliases with PowerAdvantage's shared migration hook.

The RF- and Tech Reborn-labelled converters belong to Power Advantage and now use Forge Energy.
Read the [1.13 migration guide](https://github.com/MinecraftModDevelopmentMods/PowerAdvantage/blob/master-1.13.2/docs/1.13/MIGRATION.md) before upgrading, and always use a backup. The [qualification notes](https://github.com/MinecraftModDevelopmentMods/PowerAdvantage/blob/master-1.13.2/docs/1.13/CURRENT-QUALIFICATION.md) list the tested scenarios and remaining gaps.

## Generator Follow-Up

The [generator follow-up](../../PowerAdvantage/docs/1.13/TEST-COVERAGE-BACKPORT.md#generator-follow-up) records the older `pa113-candidate-g` inputs.
Its 126 operating jobs and 126 cold checks, 39 GUI windows, 351 caption cases,
355 rejected broken controls, eight frozen routes, five connected routes and
paired 30-minute runs passed. This is a pre-final Steam-control checkpoint,
not qualification of the newer SteamAdvantage jars or harness. Those still need
the final sweep. Manual play-testing and release review remain separate.

## Final T Candidate

The [final-t checkpoint](../../PowerAdvantage/docs/1.13/TEST-COVERAGE-BACKPORT.md#final-t-candidate)
records profile `pa113-candidate-i` and its exact jar hashes. All 132 operating
jobs and 132 cold-restart comparisons pass, including an independent audit of
six Steam controls and their nine tile-hold records. GUI checks pass for 39
windows, 351 caption cases and 355 rejected broken controls.
Frozen migration passed on 8 routes and connected-source operation passed on
all five declared routes.
Fresh and migrated-copy 30-minute runs pass. Power and Electric retain their
exact-hash release and reproducibility proof; final-t Steam passes its full suite
and two independent export builds. All 18 mod/version release and reproducibility
gates pass across the six maintained Minecraft lines. Both requested broad
profiles and their independent audit pass; the reporter-only unit/replay
qualification preserves the original native proof. Survival harvest checks pass
before and after restart. The report's extra network/drill scopes are now
qualified; manual acceptance remains separate. Earlier checkpoints keep their original scope;
No release publication is included.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ protected void registerData() {
// do nothing
}

@Override
public boolean isPushedByWater() {
// Anchored to the generator: flow drives rotation, not linear movement.
return false;
}

@Override
public void tick(){
super.tick();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,23 @@ public static void drawIndicatorLight(boolean on,GUIContainer gc, int xOffset, i
}

public static void drawProgressBar(int x, int y, float progress, GUIContainer gc){
int n = (int)(8 * (progress + 0.0625f) );
if(n <= 0) return;
int w = 1 + (n * LED_BAR_W8);
int w = progressBarWidth(progress);
if(w == 0) return;
gc.drawTexturedModalRect(x, y, LED_BAR_X, LED_BAR_Y, w, LED_BAR_H);
}

static int progressBarWidth(float progress) {
progress = Float.isFinite(progress) ? Math.max(0F, Math.min(1F, progress)) : 0F;
int n = (int)(8 * (progress + 0.0625f));
return n <= 0 ? 0 : 1 + n * LED_BAR_W8;
}


public static void drawFluidBar(FluidStack fs, float barHeight, int xPos, int yPos,
GUIContainer guiContainer, int x, int y, float z,
ResourceLocation displayImage){

barHeight = Float.isFinite(barHeight) ? Math.max(0F, Math.min(1F, barHeight)) : 0F;
final int w = 16;
final int barSlotHeight = 60;
final int h = (int)(barSlotHeight * barHeight);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import zone.moddev.mc.electricadvantage.ElectricAdvantage;

import java.util.Arrays;
import java.util.List;

public abstract class ElectricGeneratorTileEntity extends cyano.poweradvantage.api.simple.TileEntitySimplePowerMachine {

Expand Down Expand Up @@ -46,19 +45,26 @@ public ElectricGeneratorTileEntity(TileEntityType<?> tileType, String name,int n


private boolean redstone = false;
private boolean redstoneInitialized = false;
private float oldEnergy = 0f;
@Override
public void powerUpdate(){
super.powerUpdate();

redstone = getWorld().isBlockPowered(getPos());
redstoneInitialized = true;

if(oldEnergy != this.getEnergy(Power.ELECTRIC_POWER)){
this.sync();
}
}

protected boolean hasRedstoneSignal(){
// Generators can check the interlock before their first power-update refresh.
if(!redstoneInitialized && getWorld() != null && !getWorld().isRemote){
redstone = getWorld().isBlockPowered(getPos());
redstoneInitialized = true;
}
return redstone;
}

Expand Down Expand Up @@ -89,10 +95,13 @@ public boolean shouldRefresh(World world, BlockPos pos, IBlockState oldState, IB
protected void playSoundEffect(double x, double y, double z, SoundEvent sound, float volume, float pitch){
if(getWorld().isRemote) return;
final double range = 16;
List<EntityPlayerMP> players = getWorld().getEntitiesWithinAABB(EntityPlayerMP.class, new AxisAlignedBB(
AxisAlignedBB bounds = new AxisAlignedBB(
x - range, y - range, z - range,
x + range, y + range, z + range));
for(EntityPlayerMP player : players){
x + range, y + range, z + range);
// The typed player query scans the existing player list, not surrounding chunks.
for(EntityPlayerMP player : getWorld().getPlayers(EntityPlayerMP.class,
candidate -> !candidate.isSpectator() && candidate.getBoundingBox().intersects(bounds))){
if(player.connection == null) continue;
player.connection.sendPacket(new SPacketCustomSound(sound.getRegistryName(), SoundCategory.BLOCKS,
new net.minecraft.util.math.Vec3d(x, y, z), volume, pitch));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import zone.moddev.mc.electricadvantage.ElectricAdvantage;

import java.util.Arrays;
import java.util.List;

public abstract class ElectricMachineTileEntity extends cyano.poweradvantage.api.simple.TileEntitySimplePowerMachine{

Expand Down Expand Up @@ -71,6 +70,7 @@ public ElectricMachineTileEntity(TileEntityType<?> tileType, String name,int num
}

private boolean redstone = false;
private boolean redstoneInitialized = false;
private float oldEnergy = 0f;
private int[] syncArrayOld = null;
private int[] syncArrayNew = null;
Expand All @@ -79,6 +79,7 @@ public void powerUpdate(){
super.powerUpdate();

redstone = getWorld().isBlockPowered(getPos());
redstoneInitialized = true;

this.setPowerState(this.isPowered());

Expand All @@ -99,6 +100,11 @@ public void powerUpdate(){


protected boolean hasRedstoneSignal(){
// tickUpdate can run before the first scheduled powerUpdate after loading.
if(!redstoneInitialized && getWorld() != null && !getWorld().isRemote){
redstone = getWorld().isBlockPowered(getPos());
redstoneInitialized = true;
}
return redstone;
}

Expand Down Expand Up @@ -366,10 +372,13 @@ public ItemStack insertItemToInputSlots(ItemStack itemStack){
protected void playSoundEffect(double x, double y, double z, SoundEvent sound, float volume, float pitch){
if(getWorld().isRemote) return;
final double range = 16;
List<EntityPlayerMP> players = getWorld().getEntitiesWithinAABB(EntityPlayerMP.class, new AxisAlignedBB(
AxisAlignedBB bounds = new AxisAlignedBB(
x - range, y - range, z - range,
x + range, y + range, z + range));
for(EntityPlayerMP player : players){
x + range, y + range, z + range);
// The typed player query scans the existing player list, not surrounding chunks.
for(EntityPlayerMP player : getWorld().getPlayers(EntityPlayerMP.class,
candidate -> !candidate.isSpectator() && candidate.getBoundingBox().intersects(bounds))){
if(player.connection == null) continue;
player.connection.sendPacket(new SPacketCustomSound(sound.getRegistryName(), SoundCategory.BLOCKS,
new net.minecraft.util.math.Vec3d(x, y, z), volume, pitch));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import zone.moddev.mc.poweradvantage.init.Fluids;
import zone.moddev.mc.poweradvantage.util.FluidIdHelper;
import net.minecraft.block.state.IBlockState;
import net.minecraft.block.IBucketPickupHandler;
import net.minecraft.init.SoundEvents;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
Expand Down Expand Up @@ -83,12 +84,14 @@ public void tickUpdate(boolean isServerWorld) {
float cost = ENERGY_COST_PUMP+ENERGY_COST_VERTICAL*(getPos().getY()-fluidSource.getY());
// Found a fluid to suck-up!
IBlockState blockstate = w.getBlockState(fluidSource);
Fluid f = getFluid(blockstate);
if(f != null && getEnergy() >= cost){
this.getTank().fill(new FluidStack(f, FluidTank.BUCKET_VOLUME), true);
this.subtractEnergy(cost, Power.ELECTRIC_POWER);
w.setBlockState(fluidSource, net.minecraft.init.Blocks.AIR.getDefaultState(), 3);
success = true;
if(blockstate.getBlock() instanceof IBucketPickupHandler && getEnergy() >= cost){
// Native pickup drains waterlogging without deleting the containing block.
Fluid f = ((IBucketPickupHandler)blockstate.getBlock()).pickupFluid(w, fluidSource, blockstate);
if(f != null && f != net.minecraft.init.Fluids.EMPTY){
this.getTank().fill(new FluidStack(f, FluidTank.BUCKET_VOLUME), true);
this.subtractEnergy(cost, Power.ELECTRIC_POWER);
success = true;
}
}
}
timeUntilNextPump = PUMP_INTERVAL;
Expand Down Expand Up @@ -130,12 +133,6 @@ protected boolean isValidInputItem(ItemStack item) {
}


private Fluid getFluid(IBlockState blockstate) {
return blockstate.getFluidState().isEmpty() ? null : blockstate.getFluidState().getFluid();
}



private BlockPos scan(Set<BlockPos> searchSpace, BlockPos coord, int limit) {
if(isFluidBlock(coord) == false) return null;
do{
Expand All @@ -162,7 +159,8 @@ private BlockPos scan(Set<BlockPos> searchSpace, BlockPos coord, int limit) {
}

private boolean isSourceBlock( BlockPos coord){
return getWorld().getFluidState(coord).isSource();
IBlockState state = getWorld().getBlockState(coord);
return state.getBlock() instanceof IBucketPickupHandler && state.getFluidState().isSource();
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public void tickUpdate(boolean isServerWorld) {
if(!this.hasRedstoneSignal()){
if(getInputTank().getFluidAmount() > 0
&& getEnergy() >= electricityPerDistill
&& canDistill(getInputTank().getFluid())){
&& canDistill(getInputTank().getFluid())
&& hasSpaceForDistillation()){
distill();
if(!wasActive){
this.setActiveState(true);
Expand Down Expand Up @@ -93,6 +94,14 @@ private boolean canDistill(FluidStack fs) {
}
}

private boolean hasSpaceForDistillation() {
DistillationRecipe recipe = DistillationRecipeRegistry.getInstance()
.getDistillationRecipeForFluid(getInputTank().getFluid().getFluid());
// applyRecipe consumes its input, so preview a copy before committing either tank.
FluidStack output = recipe.applyRecipe(getInputTank().getFluid().copy(), speed);
return output != null && getOutputTank().fill(output, false) == output.amount;
}

private void distill(){
DistillationRecipe recipe = DistillationRecipeRegistry.getInstance()
.getDistillationRecipeForFluid(getInputTank().getFluid().getFluid());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,10 @@ public PowerRequest getPowerRequest(ConduitType offer) {
float powerWanted = (this.getEnergyCapacity(Power.GROWTHCHAMBER_POWER) - this.getEnergy(Power.GROWTHCHAMBER_POWER));
powerWanted = Math.min(powerWanted, soil / SOIL_PER_UNIT);
powerWanted = Math.min(powerWanted, tank.getFluidAmount() / WATER_PER_UNIT);
return new PowerRequest(PowerRequest.MEDIUM_PRIORITY,ELECTRICITY_PER_UNIT * powerWanted,this);
float stored = getEnergy(Power.ELECTRIC_POWER);
float room = getEnergyCapacity(Power.ELECTRIC_POWER) - stored;
float electricityWanted = Math.max(0, Math.min(room, ELECTRICITY_PER_UNIT * powerWanted - stored));
return new PowerRequest(PowerRequest.MEDIUM_PRIORITY,electricityWanted,this);
} else {
return PowerRequest.REQUEST_NOTHING;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class GrowthChamberTileEntity extends ElectricMachineTileEntity {
private final int[] progression = new int[GROWTH_AREA];
private final int[] progressionMax = new int[GROWTH_AREA];
private final VirtualCrop[] crops = new VirtualCrop[GROWTH_AREA];
private byte[] pendingGrowth;

public GrowthChamberTileEntity() {
super(zone.moddev.mc.electricadvantage.init.Entities.GROWTH_CHAMBER, "tile.electricadvantage.growth_chamber.name",Power.GROWTHCHAMBER_POWER, ENERGY_PER_TICK*64, GROWTH_AREA, 6, 0);
Expand All @@ -30,6 +31,7 @@ public GrowthChamberTileEntity() {
@Override
public void tickUpdate(boolean isServer) {
if(isServer){
restorePendingCrops();
boolean[] recalculate = inventoryChanged();
boolean active = false;
boolean flagSync = false;
Expand Down Expand Up @@ -157,7 +159,8 @@ public float[] getProgress() {

@Override
protected void saveTo(NBTTagCompound tagRoot) {
byte[] growthStages = new byte[GROWTH_AREA];
byte[] growthStages = pendingGrowth == null ? new byte[GROWTH_AREA]
: Arrays.copyOf(pendingGrowth, GROWTH_AREA);
for(int i = 0; i < growthStages.length; i++){
if(crops[i] != null)
growthStages[i] = crops[i].getCurrentGrowth();
Expand All @@ -169,20 +172,34 @@ protected void saveTo(NBTTagCompound tagRoot) {

@Override
protected void loadFrom(NBTTagCompound tagRoot) {
if(tagRoot.contains("growth", 99)){
byte[] growthStages = tagRoot.getByteArray("growth");
for(int i = 0; i < growthStages.length && i < GROWTH_AREA; i++){
if(crops[i] == null){
crops[i] = getVirtualCrop(this.getInputSlot(i));
}
if(crops[i] != null){
crops[i].setCurrentGrowth(growthStages[i]);
}
}
Arrays.fill(crops, null);
Arrays.fill(progression, 0);
Arrays.fill(progressionMax, 0);
oldInventory = null;
pendingGrowth = tagRoot.contains("growth", 7)
? Arrays.copyOf(tagRoot.getByteArray("growth"), GROWTH_AREA) : null;
if(tagRoot.contains("prog", 11)){
int[] saved = tagRoot.getIntArray("prog");
System.arraycopy(saved, 0, progression, 0, Math.min(saved.length, progression.length));
}
if(tagRoot.contains("prog", 99)){
System.arraycopy(tagRoot.getIntArray("prog"), 0, progression, 0, progression.length);
}

private void restorePendingCrops() {
if (pendingGrowth == null) return;
for (int i = 0; i < GROWTH_AREA; i++) {
crops[i] = getVirtualCrop(getInputSlot(i));
if (crops[i] != null) {
crops[i].setCurrentGrowth(pendingGrowth[i]);
progressionMax[i] = crops[i].getMaxGrowth() * TICKS_PER_GROWTH;
} else {
progression[i] = 0;
}
}
// Seed the comparison cache so the first tick does not discard restored work.
ItemStack[] inventory = getInventory();
oldInventory = new ItemStack[inventory.length];
for (int i = 0; i < inventory.length; i++) oldInventory[i] = copyStackOrNull(inventory[i]);
pendingGrowth = null;
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ public float getPowerOutput() {
}
}

private final float[] progress = new float[1];

public float[] getProgress() {
progress[0] = isActive() ? 1F : 0F;
return progress;
}

@Override
protected boolean isValidInputItem(ItemStack item) {
return false;
Expand Down
Loading
Loading