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
14 changes: 14 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
Unreleased

- Check generator redstone before the first power pulse so a disabled solar panel cannot produce a startup burst.
- 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.
- Fix a 1.14 turret crash when checking a target's line of sight. Keep nearby cover and the existing beam range effective without hitting the turret's own mounting post.
- 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.6.114041

- Target Minecraft 1.14.4 and Forge 28.2.26 using native platform classes and Java 8 production bytecode.
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,38 @@ in-world GUI, rendering, sound, control or weapon interaction, multiplayer behav
or compatibility with arbitrary third-party mods. Unit/resource tests alone are not
runtime or world-upgrade evidence.

The local [test-coverage backport](../../PowerAdvantage/docs/1.14/TEST-COVERAGE-BACKPORT.md) records the stronger machine, restart and GUI checks for the repaired candidate.

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.
The historical [1.13 migration guide](https://github.com/MinecraftModDevelopmentMods/PowerAdvantage/blob/master-1.13.2/docs/1.13/MIGRATION.md) explains the legacy conversion and backup process; the 1.14 test results above describe this port's coverage. Always make a whole-world backup before upgrading, keep the complete set of migration backups, and test only disposable copies. Check that every other mod supports the target version; these results do not qualify unrelated mods or an entire existing modpack.

## Generator Follow-Up

The [generator follow-up](../../PowerAdvantage/docs/1.14/TEST-COVERAGE-BACKPORT.md#generator-follow-up) records the older `pa114-candidate-c` inputs.
Its 126 operating jobs and 126 cold checks, 39 GUI windows, 351 caption cases,
355 rejected broken controls, thirteen frozen routes and five connected routes
passed. The corrected paired 30-minute run also passed; the original POI failure
is retained. 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.14/TEST-COVERAGE-BACKPORT.md#final-t-candidate)
records profile `pa114-candidate-e` 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 13 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 @@ -54,6 +54,12 @@ public IPacket<?> createSpawnPacket() {
return NetworkHooks.getEntitySpawningPacket(this);
}

@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 @@ -31,17 +31,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.blit(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 @@ -3,6 +3,7 @@
import zone.moddev.mc.electricadvantage.init.Power;
import cyano.poweradvantage.api.ConduitType;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
Expand All @@ -18,7 +19,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 +46,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 All @@ -74,10 +81,14 @@ protected void setActive(boolean active){
protected void playSoundEffect(double x, double y, double z, SoundEvent sound, float volume, float pitch){
if(getWorld().isRemote) return;
final double range = 16;
List<ServerPlayerEntity> players = getWorld().getEntitiesWithinAABB(ServerPlayerEntity.class, new AxisAlignedBB(
AxisAlignedBB bounds = new AxisAlignedBB(
x - range, y - range, z - range,
x + range, y + range, z + range));
for(ServerPlayerEntity player : players){
x + range, y + range, z + range);
// Broadcast to existing players without loading chunks for a spatial entity query.
for(PlayerEntity candidate : getWorld().getPlayers()){
if(!(candidate instanceof ServerPlayerEntity)) continue;
ServerPlayerEntity player = (ServerPlayerEntity)candidate;
if(player.connection == null || player.isSpectator() || !player.getBoundingBox().intersects(bounds)) continue;
player.connection.sendPacket(new SPlaySoundPacket(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 @@ -3,6 +3,7 @@
import zone.moddev.mc.electricadvantage.init.Power;
import cyano.poweradvantage.api.ConduitType;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
Expand All @@ -22,7 +23,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 +71,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 +80,7 @@ public void powerUpdate(){
super.powerUpdate();

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

this.setPowerState(this.isPowered());

Expand All @@ -99,6 +101,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 @@ -343,10 +350,14 @@ 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<ServerPlayerEntity> players = getWorld().getEntitiesWithinAABB(ServerPlayerEntity.class, new AxisAlignedBB(
AxisAlignedBB bounds = new AxisAlignedBB(
x - range, y - range, z - range,
x + range, y + range, z + range));
for(ServerPlayerEntity player : players){
x + range, y + range, z + range);
// Broadcast to existing players without loading chunks for a spatial entity query.
for(PlayerEntity candidate : getWorld().getPlayers()){
if(!(candidate instanceof ServerPlayerEntity)) continue;
ServerPlayerEntity player = (ServerPlayerEntity)candidate;
if(player.connection == null || player.isSpectator() || !player.getBoundingBox().intersects(bounds)) continue;
player.connection.sendPacket(new SPlaySoundPacket(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.BlockState;
import net.minecraft.block.IBucketPickupHandler;
import net.minecraft.util.SoundEvents;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
Expand Down Expand Up @@ -82,12 +83,14 @@ public void tickUpdate(boolean isServerWorld) {
float cost = ENERGY_COST_PUMP+ENERGY_COST_VERTICAL*(getPos().getY()-fluidSource.getY());
// Found a fluid to suck-up!
BlockState 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.block.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.fluid.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 @@ -129,12 +132,6 @@ protected boolean isValidInputItem(ItemStack item) {
}


private Fluid getFluid(BlockState 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 @@ -161,7 +158,8 @@ private BlockPos scan(Set<BlockPos> searchSpace, BlockPos coord, int limit) {
}

private boolean isSourceBlock( BlockPos coord){
return getWorld().getFluidState(coord).isSource();
BlockState 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 @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import zone.moddev.mc.electricadvantage.ElectricAdvantage;
import zone.moddev.mc.electricadvantage.init.Power;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.EntityClassification;
Expand All @@ -18,6 +19,9 @@
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.*;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
Expand Down Expand Up @@ -293,9 +297,18 @@ private Vec3d calculateLaserTerminus(){
return followRayToSolidBlock(getOpticPosition(),dir,ATTACK_RANGE);
}
private Vec3d followRayToSolidBlock(Vec3d origin, Vec3d dir, double maxRange){
Vec3d max = origin.add(dir).add(maxRange * dir.x, maxRange * dir.y, maxRange * dir.z);
Entity target = getWorld().getEntityByID(targetID);
// A client may receive the lock before its target entity; do not trace a missing beam.
if(target == null) return origin;
Vec3d max = origin.add(maxRange * dir.x, maxRange * dir.y, maxRange * dir.z);
BlockRayTraceResult impact = getWorld().rayTraceBlocks(new RayTraceContext(origin, max,
RayTraceContext.BlockMode.COLLIDER, RayTraceContext.FluidMode.SOURCE_ONLY, null));
RayTraceContext.BlockMode.COLLIDER, RayTraceContext.FluidMode.SOURCE_ONLY, target) {
@Override public VoxelShape getBlockShape(BlockState state, IBlockReader world, BlockPos pos) {
// The optic is inside our post; nearby cover must still block the beam.
return pos.equals(LaserTurretTileEntity.this.getPos()) ? VoxelShapes.empty()
: super.getBlockShape(state, world, pos);
}
});
if(impact.getType() == RayTraceResult.Type.BLOCK){
return impact.getHitVec();
} else {
Expand Down Expand Up @@ -379,7 +392,7 @@ public boolean canSeeEntity(Entity e){
Vec3d offsetOrigin = getOpticPosition().add(e.getPositionVector().subtract(getOpticPosition()).normalize());
BlockRayTraceResult collision = getWorld().rayTraceBlocks(new RayTraceContext(offsetOrigin,
e.getPositionVector().add(0, e.getHeight()*0.5, 0), RayTraceContext.BlockMode.COLLIDER,
RayTraceContext.FluidMode.SOURCE_ONLY, null));
RayTraceContext.FluidMode.SOURCE_ONLY, e));
if(collision.getType() == RayTraceResult.Type.BLOCK){
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@ public void powerUpdate(){
}

public static float getLightIntensityAt(BlockPos pos, World w){
// Sample incident light above the solid panel, not inside its body.
BlockPos lightPos = pos.up();
float light;
if(w.isSkyLightMax(pos) && w.dimension.hasSkyLight()){
if(w.isSkyLightMax(lightPos) && w.dimension.hasSkyLight()){
// outdoor light imposes no penalty
light = MathHelper.sin((float)w.getDayTime() * 2.61799E-4f); // (worldTime * pi / 12000);
// handle weather
Expand All @@ -103,7 +105,7 @@ public static float getLightIntensityAt(BlockPos pos, World w){
light *= 0.35f;
}
} else {
float blockLight = w.getLight(pos) * 6.66666E-2f; // block light / 15
float blockLight = w.getLight(lightPos) * 6.66666E-2f; // block light / 15
light = BLOCKLIGHT_FACTOR * blockLight;
}
return light;
Expand Down
Loading
Loading