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

- Keep waterlogged blocks intact when the steam pump collects their water, and only charge for successful pickup.
- Keep fluid in the steam pump when an attached tank refuses it.
- Pause the steam still when its output cannot hold a complete batch, without wasting input or processing steam.
- Let a loaded musket fire its last cartridge without needing spare ammunition in the inventory.
- Stop musket shots at terrain and select nearby targets without loading extra server chunks.
- Send machine sounds to connected nearby players without scanning unloaded chunks; keep the existing sound ranges and spectator exclusion.
- Read live redstone before the crusher and furnace resume after loading, preserving saved work and the first-tick steam heat bonus without changing the normal polling interval.
- Save real coal-boiler tank changes immediately, leave simulations unchanged, and return an empty result for null, empty or mismatched drain requests.
- Honour the water-only input of coal, electric and geothermal boilers even when empty, and safely refuse null, empty or mismatched fluid requests.
- Save accepted external tank changes in electric and geothermal boilers immediately, with the same simulation and client-side safeguards as the coal boiler.

Steam Advantage 2.2.6.114041

- Save the drill's cached facing without reloading a chunk during its unload-save.
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,37 @@ runtime or world-upgrade evidence.

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.14.4/test-rig/client-resources/README.md) checks models, textures and names across all three mods. These resource checks do not replace gameplay testing.

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.steamadvantage`. Runtime identities, registries, tile IDs, saved NBT, recipes, and assets remain stable. Steam registers its legacy state and NBT codecs with PowerAdvantage's shared migration hook so 1.10 and 1.12 worlds can be converted before Minecraft flattens old numeric block states.

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;
nothing has been published by this local test work.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public static float maxDelta(float newValue,float oldValue,float maxChange){

public static void drawNeedle(float pivotX, float pivotY, float zLevel,
float value) {
value = displayLevel(value);


final float pixelSize = 1.0f / 256; // conversion factor
Expand Down Expand Up @@ -85,6 +86,7 @@ public static void drawNeedle(float pivotX, float pivotY, float zLevel,
}

public static void drawFlameProgress(int x, int y, float flameHeight, GUIContainer gc){
flameHeight = displayLevel(flameHeight);
if(flameHeight <= 0) return;
final int flameTexX = 200;
final int flameTexY = 0;
Expand All @@ -100,6 +102,7 @@ public static void drawFlameProgress(int x, int y, float flameHeight, GUIContain
public static void drawFluidBar(FluidStack fs, float barHeight, int xPos, int yPos,
GUIContainer guiContainer, int x, int y, float z,
ResourceLocation displayImage, int texOverlayX, int texOverlayY, int texOverlayW, int texOverlayH){
barHeight = displayLevel(barHeight);
final int texMarginW = (texOverlayW - 16) / 2;
final int texMarginH = (texOverlayH - 60) / 2;
final int w = 16;
Expand All @@ -117,6 +120,7 @@ public static void drawFluidBar(FluidStack fs, float barHeight, int xPos, int yP

public static void drawDownArrowProgress(int x, int y, float progress,
GUIContainer gc) {
progress = displayLevel(progress);
if(progress <= 0) return;
final int arrowTexX = 184;
final int arrowTexY = 0;
Expand All @@ -128,4 +132,8 @@ public static void drawDownArrowProgress(int x, int y, float progress,
gc.blit(x, y, arrowTexX, arrowTexY, width, h);
}
}

static float displayLevel(float value) {
return Float.isFinite(value) ? Math.max(0F, Math.min(1F, value)) : 0F;
}
}
58 changes: 28 additions & 30 deletions src/main/java/zone/moddev/mc/steamadvantage/items/MusketItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import static zone.moddev.mc.steamadvantage.util.SoundHelper.playSoundAtPosition;

import java.util.List;
import java.util.Iterator;
import java.util.Optional;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
Expand Down Expand Up @@ -114,7 +116,7 @@ public UseAction getUseAction(ItemStack stack){
public ActionResult<ItemStack> onItemRightClick(World world, PlayerEntity playerEntity, Hand hand){
ItemStack srcItemStack = playerEntity.getHeldItem(hand);
if(hand != Hand.MAIN_HAND) return ActionResult.newResult(ActionResultType.PASS,srcItemStack);
if(!hasAmmo(playerEntity, srcItemStack)) return ActionResult.newResult(ActionResultType.FAIL,srcItemStack);
if(isNotLoaded(srcItemStack) && !hasAmmo(playerEntity, srcItemStack)) return ActionResult.newResult(ActionResultType.FAIL,srcItemStack);
if(isNotLoaded(srcItemStack) && EnchantmentHelper.getEnchantmentLevel(Enchantments.rapid_reload, srcItemStack) > 0){
// instant reload
decrementAmmo(playerEntity,srcItemStack);
Expand Down Expand Up @@ -367,38 +369,37 @@ public static void decrementAmmo(LivingEntity e, ItemStack musket) {


public static RayTraceResult rayTraceBlocksAndEntities(World w, double maxRange, LivingEntity source){
double rangeSqr = maxRange * maxRange;
Vec3d rayOrigin = new Vec3d(source.posX, source.posY + source.getEyeHeight(), source.posZ);
Vec3d rayDirection = source.getLookVec();
BlockPos srcPos = source.getPosition();
AxisAlignedBB aoi = new AxisAlignedBB(srcPos.getX() - maxRange, srcPos.getY() - maxRange, srcPos.getZ() - maxRange,
srcPos.getX() + maxRange, srcPos.getY() + maxRange, srcPos.getZ() + maxRange);
List<Entity> entities = w.getEntitiesWithinAABBExcludingEntity(source, aoi);
double closestDistSqr = Double.MAX_VALUE;
BlockRayTraceResult terrainHit = w.rayTraceBlocks(new RayTraceContext(rayOrigin, rayOrigin.add(rayDirection.scale(maxRange)),
RayTraceContext.BlockMode.COLLIDER, RayTraceContext.FluidMode.SOURCE_ONLY, source));
Vec3d rayEnd = terrainHit.getHitVec();
AxisAlignedBB aoi = new AxisAlignedBB(rayOrigin, rayEnd).grow(1.0D);
// Select existing server entities without a spatial query that can load distant chunks.
Iterator<Entity> entities = w instanceof ServerWorld ? ((ServerWorld)w).getEntities().iterator()
: w.getEntitiesWithinAABBExcludingEntity(source, aoi).iterator();
double closestDistSqr = rayOrigin.squareDistanceTo(rayEnd);
Entity closestEntity = null;
for(Entity e : entities){
double distSqr = e.getDistanceSq(source.posX, source.posY, source.posZ);
if(distSqr < rangeSqr){
// e is within range
AxisAlignedBB box = e.getBoundingBox();
if(rayIntersectsBoundingBox(rayOrigin,rayDirection, box)){
// e is in cross-hairs
if(distSqr < closestDistSqr){
// e is closest entity in line of fire
closestDistSqr = distSqr;
closestEntity = e;
}
Vec3d closestHit = null;
while(entities.hasNext()){
Entity e = entities.next();
if(e == source || e.removed || e.isSpectator() || !aoi.intersects(e.getBoundingBox())) continue;
Optional<Vec3d> hit = rayHitBeforeTerrain(rayOrigin, rayEnd, e.getBoundingBox());
if(hit.isPresent()){
double distSqr = rayOrigin.squareDistanceTo(hit.get());
if(distSqr < closestDistSqr){
closestDistSqr = distSqr;
closestEntity = e;
closestHit = hit.get();
}
}
}
if(closestEntity == null) {
return w.rayTraceBlocks(new RayTraceContext(rayOrigin, rayOrigin.add(mul(rayDirection, maxRange)),
RayTraceContext.BlockMode.COLLIDER, RayTraceContext.FluidMode.SOURCE_ONLY, source));
} else {
Vec3d pos = new Vec3d(closestEntity.posX, closestEntity.posY+closestEntity.getEyeHeight(), closestEntity.posZ);
RayTraceResult entityCollision = new EntityRayTraceResult(closestEntity, pos);
return entityCollision;
}
return closestEntity == null ? terrainHit : new EntityRayTraceResult(closestEntity, closestHit);
}

static Optional<Vec3d> rayHitBeforeTerrain(Vec3d origin, Vec3d terrainEnd, AxisAlignedBB target){
Optional<Vec3d> hit = target.contains(origin) ? Optional.of(origin) : target.rayTrace(origin, terrainEnd);
return hit.filter(point -> origin.squareDistanceTo(point) < origin.squareDistanceTo(terrainEnd));
}

public static boolean rayIntersectsBoundingBox(Vec3d rayOrigin, Vec3d rayDirection, AxisAlignedBB box){
Expand Down Expand Up @@ -431,9 +432,6 @@ public static boolean rayIntersectsBoundingBox(Vec3d rayOrigin, Vec3d rayDirecti
}


private static Vec3d mul(Vec3d a, double b){
return new Vec3d(a.x * b, a.y * b, a.z * b);
}
private static double max(double a, double b){
return Math.max(a, b);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ public class BlastFurnaceTileEntity extends cyano.poweradvantage.api.simple.Tile
private static final float minSmeltingTemperature = 100f;

private boolean redstone = true;
private boolean redstoneInitialized = false;

@Override
public void onLoad() {
super.onLoad();
redstoneInitialized = false;
}

public BlastFurnaceTileEntity() {
super(zone.moddev.mc.steamadvantage.init.Entities.STEAM_FURNACE, Power.steam_power, 200, "tile.steamadvantage.steam_furnace.name");
Expand Down Expand Up @@ -66,6 +73,10 @@ public float getTemperatureLevel(){
@Override
public void tickUpdate(boolean isServerWorld) {
if(isServerWorld){
if (!redstoneInitialized && getWorld() != null) {
redstone = hasRedstoneSignal();
redstoneInitialized = true;
}
// fual input heat
float e = 0;
if(burnTime > 0){
Expand Down Expand Up @@ -172,6 +183,7 @@ public void powerUpdate(){
this.sync();
}
redstone = hasRedstoneSignal();
redstoneInitialized = true;
}

private boolean hasRedstoneSignal() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,15 @@ public class CoalBoilerTileEntity extends cyano.poweradvantage.api.simple.TileEn

public CoalBoilerTileEntity() {
super(zone.moddev.mc.steamadvantage.init.Entities.STEAM_BOILER_COAL, new ConduitType[]{Power.steam_power,Fluids.fluidConduit_general}, new float[]{1000,1000}, "tile.steamadvantage.steam_boiler_coal.name");
tank = new FluidTank(FluidTank.BUCKET_VOLUME * 4);
tank = new FluidTank(FluidTank.BUCKET_VOLUME * 4) {
@Override
protected void onContentsChanged() {
if (CoalBoilerTileEntity.this.getWorld() != null
&& !CoalBoilerTileEntity.this.getWorld().isRemote) {
CoalBoilerTileEntity.this.markDirty();
}
}
};
inventory = new ItemStack[1];
}

Expand Down Expand Up @@ -349,6 +357,8 @@ && getTank().getFluidAmount() > 0
*/
@Override
public int fill(Direction face, FluidStack fluid, boolean forReal) {
if (fluid == null || fluid.amount <= 0 || !canFill(face, fluid.getFluid())
|| getTank().getFluidAmount() >= getTank().getCapacity()) return 0;
if(getTank().getFluidAmount() <= 0 || getTank().getFluid().getFluid().equals(fluid.getFluid())){
return getTank().fill(fluid, forReal);
} else {
Expand All @@ -363,10 +373,11 @@ public int fill(Direction face, FluidStack fluid, boolean forReal) {
*/
@Override
public FluidStack drain(Direction face, FluidStack fluid, boolean forReal) {
if (fluid == null || fluid.amount <= 0) return null;
if(getTank().getFluidAmount() > 0 && getTank().getFluid().getFluid().equals(fluid.getFluid())){
return getTank().drain(fluid.amount,forReal);
} else {
return new FluidStack(getTank().getFluid().getFluid(),0);
return null;
}
}
/**
Expand All @@ -377,6 +388,7 @@ public FluidStack drain(Direction face, FluidStack fluid, boolean forReal) {
*/
@Override
public FluidStack drain(Direction face, int amount, boolean forReal) {
if (amount <= 0) return null;
if(getTank().getFluidAmount() > 0 ){
return getTank().drain(amount,forReal);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@ public ElectricBoilerTileEntity() {
super(zone.moddev.mc.steamadvantage.init.Entities.STEAM_BOILER_ELECTRIC, new ConduitType[]{Power.steam_power, ELECTRIC_POWER, Fluids.fluidConduit_general},
new float[]{100, MAX_ELECTRICITY, 1000},
"tile.steamadvantage.steam_boiler_electric.name");
tank = new FluidTank(FluidTank.BUCKET_VOLUME * 2);
tank = new FluidTank(FluidTank.BUCKET_VOLUME * 2) {
@Override
protected void onContentsChanged() {
if (ElectricBoilerTileEntity.this.getWorld() != null
&& !ElectricBoilerTileEntity.this.getWorld().isRemote) {
ElectricBoilerTileEntity.this.markDirty();
}
}
};
inventory = new ItemStack[0];
}

Expand Down Expand Up @@ -301,6 +309,8 @@ public PowerRequest getPowerRequest(ConduitType offer) {
*/
@Override
public int fill(Direction face, FluidStack fluid, boolean forReal) {
if (fluid == null || fluid.amount <= 0 || !canFill(face, fluid.getFluid())
|| getTank().getFluidAmount() >= getTank().getCapacity()) return 0;
if(getTank().getFluidAmount() <= 0 || getTank().getFluid().getFluid().equals(fluid.getFluid())){
return getTank().fill(fluid, forReal);
} else {
Expand All @@ -315,10 +325,11 @@ public int fill(Direction face, FluidStack fluid, boolean forReal) {
*/
@Override
public FluidStack drain(Direction face, FluidStack fluid, boolean forReal) {
if (fluid == null || fluid.amount <= 0) return null;
if(getTank().getFluidAmount() > 0 && getTank().getFluid().getFluid().equals(fluid.getFluid())){
return getTank().drain(fluid.amount,forReal);
} else {
return new FluidStack(getTank().getFluid().getFluid(),0);
return null;
}
}
/**
Expand All @@ -329,6 +340,7 @@ public FluidStack drain(Direction face, FluidStack fluid, boolean forReal) {
*/
@Override
public FluidStack drain(Direction face, int amount, boolean forReal) {
if (amount <= 0) return null;
if(getTank().getFluidAmount() > 0 ){
return getTank().drain(amount,forReal);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,15 @@ public class GeothermalBoilerTileEntity extends cyano.poweradvantage.api.simple.

public GeothermalBoilerTileEntity() {
super(zone.moddev.mc.steamadvantage.init.Entities.STEAM_BOILER_GEOTHERMAL, new ConduitType[]{Power.steam_power,Fluids.fluidConduit_general}, new float[]{100,1000}, "tile.steamadvantage.steam_boiler_geothermal.name");
tank = new FluidTank(FluidTank.BUCKET_VOLUME * 2);
tank = new FluidTank(FluidTank.BUCKET_VOLUME * 2) {
@Override
protected void onContentsChanged() {
if (GeothermalBoilerTileEntity.this.getWorld() != null
&& !GeothermalBoilerTileEntity.this.getWorld().isRemote) {
GeothermalBoilerTileEntity.this.markDirty();
}
}
};
inventory = new ItemStack[0];
}

Expand Down Expand Up @@ -337,6 +345,8 @@ public PowerRequest getPowerRequest(ConduitType offer) {
*/
@Override
public int fill(Direction face, FluidStack fluid, boolean forReal) {
if (fluid == null || fluid.amount <= 0 || !canFill(face, fluid.getFluid())
|| getTank().getFluidAmount() >= getTank().getCapacity()) return 0;
if(getTank().getFluidAmount() <= 0 || getTank().getFluid().getFluid().equals(fluid.getFluid())){
return getTank().fill(fluid, forReal);
} else {
Expand All @@ -351,10 +361,11 @@ public int fill(Direction face, FluidStack fluid, boolean forReal) {
*/
@Override
public FluidStack drain(Direction face, FluidStack fluid, boolean forReal) {
if (fluid == null || fluid.amount <= 0) return null;
if(getTank().getFluidAmount() > 0 && getTank().getFluid().getFluid().equals(fluid.getFluid())){
return getTank().drain(fluid.amount,forReal);
} else {
return new FluidStack(getTank().getFluid().getFluid(),0);
return null;
}
}
/**
Expand All @@ -365,6 +376,7 @@ public FluidStack drain(Direction face, FluidStack fluid, boolean forReal) {
*/
@Override
public FluidStack drain(Direction face, int amount, boolean forReal) {
if (amount <= 0) return null;
if(getTank().getFluidAmount() > 0 ){
return getTank().drain(amount,forReal);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ public RockCrusherTileEntity() {
}

private boolean redstone = true;
private boolean redstoneInitialized = false;

@Override
public void onLoad() {
super.onLoad();
redstoneInitialized = false;
}


private int timeSinceLastSteamBurst = 0;
Expand All @@ -41,6 +48,10 @@ public RockCrusherTileEntity() {
@Override
public void tickUpdate(boolean isServerWorld) {
if(isServerWorld){
if (!redstoneInitialized && getWorld() != null) {
redstone = hasRedstoneSignal();
redstoneInitialized = true;
}
// reset progress when item in slot changes
ItemStack input = inventory[0];
if(itemCheck != null && (input == null || input.isEmpty() || ItemStack.areItemsEqual(itemCheck, input) == false)){
Expand Down Expand Up @@ -113,6 +124,7 @@ public void powerUpdate(){
super.powerUpdate();

redstone = hasRedstoneSignal();
redstoneInitialized = true;

if(oldSteam != this.getEnergy(Power.steam_power) || progress != oldProgress){
this.sync();
Expand Down
Loading
Loading