diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c18eb5..188e9fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,9 +80,9 @@ jobs: if-no-files-found: error retention-days: 30 path: | - build/libs/ElectricAdvantage-2.2.1.112021.jar - build/libs/ElectricAdvantage-2.2.1.112021-sources.jar - build/libs/ElectricAdvantage-2.2.1.112021-javadoc.jar + build/libs/ElectricAdvantage-2.2.3.112021.jar + build/libs/ElectricAdvantage-2.2.3.112021-sources.jar + build/libs/ElectricAdvantage-2.2.3.112021-javadoc.jar build/release/SHA256SUMS CHANGELOG.txt diff --git a/.gitignore b/.gitignore index 4502811..4933367 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,25 @@ /replay_pid*.log /secret.json +# Nested local tooling and generated output +AGENTS.md +AGENT.md +CLAUDE.md +GEMINI.md +ARCHITECT.md +agent-notes/ +.codex/ +.agent/ +.agents/ +.claude/ +.gemini/ +.cursor/ +agent-*.md +codex-*.md +*.log +*.class +__pycache__/ +*.pyc /.DS_Store /README.txt /forge-*-changelog.txt diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 7296c18..0bf2b88 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,15 @@ +Electric Advantage 2.2.3.112021 + +- Retain unfinished drill progress when rebuilding the mining target after a world or chunk reload. +- Clear stale deferred target data when loading an untargeted tile; clamp restored progress to current target hardness. +- Preserve registry identities, saved target coordinates, inventories, energy and the existing progress NBT key. + +Electric Advantage 2.2.2.112021 + +- Preserve a loaded hydroturbine's generator link when saved before its first tick, including full restarts and chained world upgrades. +- Clear stale turbine links when reading new entity data; keep existing entity identities and the parentPos NBT key unchanged. +- Add persistence regression tests without changing generated electricity or gameplay balance. + Electric Advantage 2.2.1.112021 - Upgrade the Minecraft 1.12.2 build to ForgeGradle 7 and Gradle 9.6.1. diff --git a/README.md b/README.md index 1a1a07c..932a3d7 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ Electric Advantage adds electric machines, generators, automation, and infrastructure to Power Advantage for Minecraft 1.12.2. +## Drill Reload Fix + +Version `2.2.3.112021` keeps unfinished drill work when a chunk or world reloads. +Existing inventories, energy, orientation and saved-world names are unchanged. +The regression tests cover resumed progress and full server restarts. + ## Building Minecraft 1.12.2 The `master-1.12.2` build uses ForgeGradle 7.0.34 and Gradle 9.6.1. Run Gradle with Java 17; Gradle resolves the Java 8 toolchain used for compilation. @@ -28,3 +34,9 @@ gradlew.bat cleanEclipse verifyEclipseProductionClasspath ``` Power Advantage is required at compile time and runtime but is never bundled. Base Metals remains a required distribution dependency while its integrations stay reflection-based and absent from the compile classpath. + +## Hydroturbine Persistence + +Version `2.2.2.112021` retains the generator coordinate when a loaded turbine is saved before its first update. The existing `parentPos` key, entity identity and generated electricity remain unchanged. This also protects the intermediate 1.12 save during a chained upgrade to 1.13; it cannot recover a coordinate already missing from an old save. + +The normal `check` task includes repeated pre-tick save tests, stale-link clearing and live-generator position checks. The Advantage Works migration harness tests the packaged jar through full 1.12 and 1.13 process restarts. diff --git a/build.gradle b/build.gradle index d4a9af2..104c438 100644 --- a/build.gradle +++ b/build.gradle @@ -169,6 +169,8 @@ dependencies { // PowerAdvantage is required at runtime but remains a separate mod jar. compileOnly files(powerAdvantageDeobfJar) runtimeOnly files(powerAdvantageDeobfJar) + testImplementation 'junit:junit:4.13.2' + testImplementation files(powerAdvantageDeobfJar) } tasks.named('processResources', ProcessResources) { @@ -298,7 +300,7 @@ tasks.register('verifyReleaseConfiguration') { group = 'verification' description = 'Validates release identity, target metadata, and dependency policy.' doLast { - if (project.mod_version != '2.2.1.112021') { + if (project.mod_version != '2.2.3.112021') { throw new GradleException("Unexpected ElectricAdvantage release version: ${project.mod_version}") } if (project.minecraft_version != '1.12.2' @@ -341,7 +343,7 @@ tasks.register('verifyReleaseConfiguration') { String mainSource = file( 'src/main/java/com/mcmoddev/electricadvantage/ElectricAdvantage.java').getText('UTF-8') if (!mainSource.contains('MODID = "electricadvantage"') - || !mainSource.contains('VERSION = "2.2.1.112021"') + || !mainSource.contains('VERSION = "2.2.3.112021"') || !mainSource.contains('dependencies = "required-after:poweradvantage;after:basemetals"')) { throw new GradleException('ElectricAdvantage source identity or dependency policy changed') } diff --git a/gradle.properties b/gradle.properties index bb0c2fc..5cc73ad 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,7 +24,7 @@ curseforge_project_id=240985 mod_id=electricadvantage mod_name=Electric Advantage mod_license=GPL-3.0-only -mod_version=2.2.1.112021 +mod_version=2.2.3.112021 mod_group=com.mcmoddev.electricadvantage mod_authors=DrCyano mod_description=Electric-powered machines and infrastructure for the Power Advantage platform. diff --git a/src/main/java/com/mcmoddev/electricadvantage/ElectricAdvantage.java b/src/main/java/com/mcmoddev/electricadvantage/ElectricAdvantage.java index 87f47c1..63b6bb6 100644 --- a/src/main/java/com/mcmoddev/electricadvantage/ElectricAdvantage.java +++ b/src/main/java/com/mcmoddev/electricadvantage/ElectricAdvantage.java @@ -30,7 +30,7 @@ public class ElectricAdvantage /** The display name for this mod */ public static final String NAME = "Electric Advantage"; /** The version of this mod, in the format major.minor.update */ - public static final String VERSION = "2.2.1.112021"; + public static final String VERSION = "2.2.3.112021"; public static ElectricAdvantage INSTANCE = null; diff --git a/src/main/java/com/mcmoddev/electricadvantage/entities/HydroturbineEntity.java b/src/main/java/com/mcmoddev/electricadvantage/entities/HydroturbineEntity.java index 4b7ddf3..8a129eb 100644 --- a/src/main/java/com/mcmoddev/electricadvantage/entities/HydroturbineEntity.java +++ b/src/main/java/com/mcmoddev/electricadvantage/entities/HydroturbineEntity.java @@ -99,6 +99,8 @@ public void onUpdate(){ @Override protected void readEntityFromNBT(NBTTagCompound root) { + parent = null; + parentCoord = null; if(root.hasKey("parentPos")){ int[] coords = root.getIntArray("parentPos"); if(coords.length >= 3){ @@ -109,9 +111,9 @@ protected void readEntityFromNBT(NBTTagCompound root) { @Override protected void writeEntityToNBT(NBTTagCompound root) { - if(parent != null){ + BlockPos p = parent != null ? parent.getPos() : parentCoord; + if(p != null){ int[] coords = new int[3]; - BlockPos p = parent.getPos(); coords[0] = p.getX(); coords[1] = p.getY(); coords[2] = p.getZ(); diff --git a/src/main/java/com/mcmoddev/electricadvantage/machines/ElectricDrillTileEntity.java b/src/main/java/com/mcmoddev/electricadvantage/machines/ElectricDrillTileEntity.java index a10811d..73e6582 100644 --- a/src/main/java/com/mcmoddev/electricadvantage/machines/ElectricDrillTileEntity.java +++ b/src/main/java/com/mcmoddev/electricadvantage/machines/ElectricDrillTileEntity.java @@ -69,7 +69,7 @@ public float[] getProgress() { public void tickUpdate(boolean isServerWorld) { if(isServerWorld){ if(deferred){ - targetBlock(targetBlockCoord); + restoreTargetBlock(); } updateFacing(); @@ -192,7 +192,7 @@ public void onDataFieldUpdate() { private int oldLength = 0; @Override public void powerUpdate(){ if(deferred){ - targetBlock(targetBlockCoord); + restoreTargetBlock(); } super.powerUpdate(); boolean flagSync = progress != oldProgress || oldEnergy != getEnergy(); @@ -362,12 +362,20 @@ private BlockPos[] getArea(BlockPos center){ return a; } + private void restoreTargetBlock(){ + int savedProgress = progress; + // Rebuild transient drops and hardness without discarding saved work. + targetBlock(targetBlockCoord); + progress = Math.max(0, Math.min(savedProgress, progressGoal)); + } + private void deferredTargetBlock (BlockPos n){ targetBlockCoord = n; deferred = true; } private void untargetBlock(){ + deferred = false; progress = 0; progressGoal = 0; targetBlockCoord = null; @@ -467,6 +475,7 @@ protected void saveTo(NBTTagCompound tagRoot) { @Override protected void loadFrom(NBTTagCompound tagRoot) { + untargetBlock(); if(tagRoot.hasKey("progress")){ progress = tagRoot.getShort("progress"); } @@ -478,7 +487,8 @@ protected void loadFrom(NBTTagCompound tagRoot) { if(getWorld() == null){ this.deferredTargetBlock(new BlockPos(x,y,z)); } else { - this.targetBlock(new BlockPos(x,y,z)); + this.deferredTargetBlock(new BlockPos(x,y,z)); + this.restoreTargetBlock(); } } } diff --git a/src/test/java/com/mcmoddev/electricadvantage/entities/HydroturbinePersistenceTest.java b/src/test/java/com/mcmoddev/electricadvantage/entities/HydroturbinePersistenceTest.java new file mode 100644 index 0000000..331dc3c --- /dev/null +++ b/src/test/java/com/mcmoddev/electricadvantage/entities/HydroturbinePersistenceTest.java @@ -0,0 +1,58 @@ +package com.mcmoddev.electricadvantage.entities; + +import java.lang.reflect.Field; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.BlockPos; +import org.junit.Test; +import sun.misc.Unsafe; + +import static org.junit.Assert.*; + +public class HydroturbinePersistenceTest { + @Test public void preservesPendingGeneratorAcrossRepeatedReadSaveWithoutTick() throws Exception { + NBTTagCompound source = new NBTTagCompound(); + source.setIntArray("parentPos", new int[] {164, 81, 8}); + for (int pass = 0; pass < 3; pass++) { + HydroturbineEntity turbine = withoutWorld(); + turbine.readEntityFromNBT(source); + NBTTagCompound saved = new NBTTagCompound(); + turbine.writeEntityToNBT(saved); + assertArrayEquals(new int[] {164, 81, 8}, saved.getIntArray("parentPos")); + source = saved; + } + } + + @Test public void readingUnlinkedEntityClearsPendingAndLiveLinks() throws Exception { + HydroturbineEntity turbine = withoutWorld(); + NBTTagCompound source = new NBTTagCompound(); + source.setIntArray("parentPos", new int[] {164, 81, 8}); + turbine.readEntityFromNBT(source); + turbine.parent = new TileEntity() {}; + turbine.readEntityFromNBT(new NBTTagCompound()); + NBTTagCompound saved = new NBTTagCompound(); + turbine.writeEntityToNBT(saved); + assertNull(turbine.parent); + assertFalse(saved.hasKey("parentPos", 11)); + } + + @Test public void savesLiveGeneratorPositionRatherThanAnOlderPendingLink() throws Exception { + HydroturbineEntity turbine = withoutWorld(); + NBTTagCompound source = new NBTTagCompound(); + source.setIntArray("parentPos", new int[] {164, 81, 8}); + turbine.readEntityFromNBT(source); + TileEntity parent = new TileEntity() {}; + parent.setPos(new BlockPos(32, 70, -16)); + turbine.parent = parent; + NBTTagCompound saved = new NBTTagCompound(); + turbine.writeEntityToNBT(saved); + assertArrayEquals(new int[] {32, 70, -16}, saved.getIntArray("parentPos")); + } + + // Exercise the custom NBT methods without constructing or ticking a world. + private static HydroturbineEntity withoutWorld() throws Exception { + Field field = Unsafe.class.getDeclaredField("theUnsafe"); + field.setAccessible(true); + return (HydroturbineEntity) ((Unsafe) field.get(null)).allocateInstance(HydroturbineEntity.class); + } +} diff --git a/src/test/java/com/mcmoddev/electricadvantage/machines/ElectricDrillPersistenceTest.java b/src/test/java/com/mcmoddev/electricadvantage/machines/ElectricDrillPersistenceTest.java new file mode 100644 index 0000000..a722d8b --- /dev/null +++ b/src/test/java/com/mcmoddev/electricadvantage/machines/ElectricDrillPersistenceTest.java @@ -0,0 +1,46 @@ +package com.mcmoddev.electricadvantage.machines; + +import net.minecraft.nbt.NBTTagCompound; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ElectricDrillPersistenceTest { + @Test public void retainsLegacyProgressBeforeWorldAttachment() { + ElectricDrillTileEntity drill = new ElectricDrillTileEntity(); + drill.loadFrom(target(512)); + drill.prepareDataFieldsForSync(); + assertEquals(512, drill.getDataFieldArray()[1]); + } + + @Test public void preservesPendingTargetThroughRepeatedSerialization() { + NBTTagCompound source = target(512); + for (int i = 0; i < 3; i++) { + ElectricDrillTileEntity drill = new ElectricDrillTileEntity(); + drill.loadFrom(source); + source = new NBTTagCompound(); + drill.saveTo(source); + assertEquals(512, source.getShort("progress")); + assertEquals(2049, source.getInteger("targetZ")); + } + } + + @Test public void untargetedDataClearsPendingWork() { + ElectricDrillTileEntity drill = new ElectricDrillTileEntity(); + drill.loadFrom(target(512)); + drill.loadFrom(new NBTTagCompound()); + NBTTagCompound saved = new NBTTagCompound(); + drill.saveTo(saved); + assertEquals(0, saved.getShort("progress")); + assertFalse(saved.hasKey("targetX")); + } + + private static NBTTagCompound target(int progress) { + NBTTagCompound tag = new NBTTagCompound(); + tag.setShort("progress", (short) progress); + tag.setInteger("targetX", 2048); + tag.setInteger("targetY", 80); + tag.setInteger("targetZ", 2049); + return tag; + } +}