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.15.2

- 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.7.115021

- Target Minecraft 1.15.2 / Forge 31.2.57 with snapshot 20200514-1.15.1 mappings and MCP 20200515.085601.
Expand Down
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# SteamAdvantage

**Minecraft 1.15.2, version 2.2.7.115021.** See the
[shared test record](https://github.com/MinecraftModDevelopmentMods/PowerAdvantage/blob/master-1.15.2/docs/1.15/CURRENT-QUALIFICATION.md)
[shared test record](https://github.com/MinecraftModDevelopmentMods/PowerAdvantage/blob/master-1.15.2/docs/1.15/TEST-COVERAGE-BACKPORT.md)
for exact jars, automated machine and upgrade results, and remaining manual
checks. Visuals and recipe unlocks were accepted on 27 October 2026. The historical
[1.14 test results](https://github.com/MinecraftModDevelopmentMods/PowerAdvantage/blob/master-1.14.4/docs/1.14/BROADER-QUALIFICATION.md)
Expand Down Expand Up @@ -80,3 +80,32 @@ The combined 1.15 harness must be prepared before an all-Advantage play-test. It
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; it is not a 1.15 migration qualification. 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.15/TEST-COVERAGE-BACKPORT.md#generator-follow-up) records the older `pa115-candidate-b` inputs.
Its 126 operating jobs and 126 cold checks, 39 GUI windows, 351 caption cases,
355 rejected broken controls, thirteen frozen routes, ten connected routes and
paired 30-minute runs passed. This does not extend the earlier fourteen-route
proof. It 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.15/TEST-COVERAGE-BACKPORT.md#final-t-candidate)
records profile `pa115-candidate-d` 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.
Thirteen frozen routes passed. The verified connected aggregate passes all ten
declared routes across both five-route shards.
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 @@ -113,7 +115,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.resultPass(srcItemStack);
if(!hasAmmo(playerEntity, srcItemStack)) return ActionResult.resultFail(srcItemStack);
if(isNotLoaded(srcItemStack) && !hasAmmo(playerEntity, srcItemStack)) return ActionResult.resultFail(srcItemStack);
if(isNotLoaded(srcItemStack) && EnchantmentHelper.getEnchantmentLevel(Enchantments.rapid_reload, srcItemStack) > 0){
// instant reload
decrementAmmo(playerEntity,srcItemStack);
Expand Down Expand Up @@ -366,38 +368,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.getPosX(), source.getPosY() + source.getEyeHeight(), source.getPosZ());
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.getPosX(), source.getPosY(), source.getPosZ());
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.getPosX(), closestEntity.getPosY()+closestEntity.getEyeHeight(), closestEntity.getPosZ());
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 @@ -430,9 +431,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