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
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
/.idea/

# Gradle and Forge run output
**/build/
**/.gradle/
**/logs/
**/.cache/
**/__pycache__/
**/.venv/
/build/
/.gradle/
/eclipse/
Expand All @@ -28,6 +34,10 @@
/.ci-dependencies/

# Machine-specific agent context
**/AGENTS.md
**/AGENT.md
**/agent-notes/
**/.codex/
/AGENTS.md
/AGENT.md
/agent-notes/
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
Unreleased test-coverage repairs for 1.12.2

- Restore the missing carbon-dust recipe name from registered coal and charcoal powders, without replacing existing entries or changing recipe amounts.
- Enable Forge's universal bucket during mod construction so oil buckets work without another mod enabling them first.
- Stop steam, electricity and fluids from loading chunks or crossing an unloaded pipe section. Reconnect the network when that section loads again.
- Keep refused energy available to the rest of the network instead of charging it to the source.
- Check all conveyor destination slots, respect stack limits and preserve different item NBT.
- Route overflow through the same insertion checks and save both inventories after transfers.
- Stop the smeltable filter from accepting items whose furnace result is an empty stack.
- Save changing timers and energy even when a machine has not sent a GUI update.
- Add measured natural-tick jobs, negative controls and exact full-process restart comparisons.

Power Advantage 2.4.1.112021

- Restore conduit networks and machine GUI state after a world reload.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Power Advantage provides shared power, fluid, and item transport systems for its Minecraft 1.12.2 expansion mods.

The [test coverage backport notes](TEST-COVERAGE-BACKPORT.md#final-recorded-candidate) record the final machine, restart, control, partial-network, GUI, endurance, connected-world and reproducibility results alongside exact candidate hashes. The bounded legacy checks pass; the recipe progression graph is still partial. Earlier failures, modern follow-up findings and manual-testing limits are kept in the notes. This is not an all-machines or all-worlds guarantee.

## 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.
Expand Down
229 changes: 229 additions & 0 deletions TEST-COVERAGE-BACKPORT.md

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ plugins {
id 'net.minecraftforge.gradle' version '7.0.34'
}

sourceSets {
guiProbe {
java.srcDir 'test-rig/client-resources/java'
resources.srcDir 'test-rig/client-resources/resources'
compileClasspath += sourceSets.main.compileClasspath + sourceSets.main.output
}
}

tasks.register('clientResourceProbeJar', Jar) {
dependsOn tasks.named('guiProbeClasses')
from sourceSets.guiProbe.output
archiveFileName = 'advantageclientresources-deobf.jar'
destinationDirectory = layout.buildDirectory.dir('client-resource-probe')
}

group = project.mod_group
version = project.mod_version
base.archivesName = 'PowerAdvantage'
Expand Down Expand Up @@ -172,6 +187,7 @@ def rebornCoreCoordinate = usingStagedRebornCore
: "RebornCore:RebornCore-1.12.2:${project.reborncore_version}:universal"

dependencies {
testImplementation 'junit:junit:4.13.2'
implementation minecraft.dependency(
"net.minecraftforge:forge:${project.minecraft_version}-${project.forge_version}")
// Mavenizer's legacy metadata omits this compile-time Minecraft library.
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/mcmoddev/poweradvantage/PowerAdvantage.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
Expand Down Expand Up @@ -216,6 +217,11 @@ compile files(
acceptedMinecraftVersions = "[1.12.2]")
public class PowerAdvantage
{
// Forge registers the bucket during its pre-init, before our fluid setup.
static {
FluidRegistry.enableUniversalBucket();
}

/** The identifier for this mod */
public static final String MODID = "poweradvantage";
/** The display name for this mod */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,25 @@
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.registry.ForgeRegistries;
import net.minecraftforge.oredict.OreDictionary;
import org.apache.logging.log4j.Level;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;

@Mod.EventBusSubscriber(modid = PowerAdvantage.MODID)
public final class BaseMetalsCompat {

private static final String MODID = "basemetals";
Expand All @@ -32,6 +40,26 @@ public static boolean isLoaded() {
return Loader.isModLoaded(MODID);
}

@SubscribeEvent(priority = EventPriority.HIGHEST)
public static void registerRecipeAliases(RegistryEvent.Register<IRecipe> event) {
// Item registration has finished; populate aliases before recipe factories inspect them.
registerCarbonDustFallback();
}

public static void registerCarbonDustFallback() {
registerCarbonDustFallback(OreDictionary::getOres, OreDictionary::registerOre);
}

static void registerCarbonDustFallback(Function<String, List<ItemStack>> ores,
BiConsumer<String, ItemStack> register) {
if (!ores.apply("dustCarbon").isEmpty()) return;
for (String source : new String[]{"dustCoal", "dustCharcoal"}) {
for (ItemStack stack : ores.apply(source)) {
if (stack != null && !stack.isEmpty()) register.accept("dustCarbon", stack.copy());
}
}
}

public static void setStrongHammers(boolean enabled) {
if (!isLoaded()) return;
if (setOldStrongHammers(enabled)) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.mcmoddev.poweradvantage.util.PowerHelper;
import net.minecraft.block.state.IBlockState;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.World;

/**
Expand Down Expand Up @@ -124,18 +125,40 @@ public void invalidateAll() {
}

/**
* Invalidates the cached network at a block location and then rescans the world to make a new
* cached network
* Invalidates networks crossing a changed chunk or touching its boundary.
* Boundary networks must be rescanned when a pipe-only chunk returns.
*
* @param coord The block position that needs revalidating
* @param w The world instance for the same dimension as the coord
* @param type The energy type that is triggering the revalidation (should be the same type as
* this manager)
* @param dimension dimension containing the changed chunk
* @param chunk changed chunk position
*/
public void invalidateChunk(int dimension, ChunkPos chunk) {
lock.writeLock().lock();
try {
int minX = (chunk.x << 4) - 1;
int minZ = (chunk.z << 4) - 1;
for (BlockPos4D coord : new ArrayList<>(networkCache.keySet())) {
if (coord.dimension == dimension && coord.pos.getX() >= minX
&& coord.pos.getX() <= minX + 17 && coord.pos.getZ() >= minZ
&& coord.pos.getZ() <= minZ + 17) invalidate(coord);
}
} finally {
lock.writeLock().unlock();
}
}

/**
* Rebuilds a network using only blocks already loaded in this world.
*
* @param coord origin of the network
* @param w world containing the network
* @param type conduit type being scanned
*/
public void revalidate(BlockPos4D coord, World w, ConduitType type) {
lock.writeLock().lock();
try {
invalidate(coord);
if (!w.isBlockLoaded(coord.pos)) return;
createNewNetwork(coord);
recursiveScan(w, coord, w.getBlockState(coord.pos), type);
} finally {
lock.writeLock().unlock();
Expand Down Expand Up @@ -172,7 +195,7 @@ protected void recursiveScan(World w, BlockPos4D coord, IBlockState src, Conduit
for (int i = 0; i < EnumFacing.values().length; i++) {
EnumFacing face = EnumFacing.values()[i];
BlockPos4D n = coord.offset(face);
if (areInSameNetwork(coord, n)) {
if (!w.isBlockLoaded(n.pos) || areInSameNetwork(coord, n)) {
continue;
}
IBlockState blockstate = w.getBlockState(n.pos);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.ChunkEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;

import java.util.*;
import java.util.concurrent.locks.Lock;
Expand Down Expand Up @@ -45,6 +49,7 @@ public static ConduitRegistry getInstance() {
try {
if (instance == null) {
instance = new ConduitRegistry();
MinecraftForge.EVENT_BUS.register(instance);
}
} finally {
initLock.unlock();
Expand Down Expand Up @@ -87,6 +92,7 @@ public List<PowerRequest> getRequestsForPower(World w, BlockPos coord, ConduitTy
* priority.
*/
public List<PowerRequest> getRequestsForPower(World w, BlockPos coord, ConduitType conduitType, ConduitType energyType) {
if (!w.isBlockLoaded(coord)) return Collections.emptyList();
int dimension = w.provider.getDimension();
clearIfWorldChanged(w, dimension);
ConduitNetworkManager manager = getConduitNetworkManager(conduitType);
Expand All @@ -107,6 +113,7 @@ public List<PowerRequest> getRequestsForPower(World w, BlockPos coord, ConduitTy
private List<PowerRequest> collectRequests(World w, List<BlockPos4D> net, ConduitType conduitType, ConduitType energyType) {
List<PowerRequest> requests = new ArrayList<>();
for (BlockPos4D pos : net) {
if (!w.isBlockLoaded(pos.pos)) continue;
Block b = w.getBlockState(pos.pos).getBlock();
if (b instanceof ITileEntityProvider) {
TileEntity e = w.getTileEntity(pos.pos);
Expand Down Expand Up @@ -184,27 +191,29 @@ public static float transmitPowerToConsumers(
World world, BlockPos srcPos, Object providerInstance
) {
List<PowerRequest> requests = ConduitRegistry.getInstance().getRequestsForPower(world, srcPos, networkType, powerType);
return distributePower(availableEnergy, powerType, minimumPriority, world, providerInstance, requests);
}

static float distributePower(float availableEnergy, ConduitType powerType, byte minimumPriority,
World world, Object providerInstance, List<PowerRequest> requests) {
if (!Float.isFinite(availableEnergy) || availableEnergy <= 0) return 0;
float e = availableEnergy;
for (PowerRequest req : requests) {
if (req.amount <= 0) continue;
if (!Float.isFinite(req.amount) || req.amount <= 0) continue;
if (req.entity == providerInstance) continue;
if (req.priority < minimumPriority) continue;

float offered = Math.min(e, req.amount);
float accepted;
if (req instanceof ExternalPowerRequest) {
e -= LightWeightPowerRegistry.getInstance().addPower(world, ((ExternalPowerRequest) req).pos,
powerType, Math.min(e, req.amount));
if (e <= 0) break;
continue;
}

if (req.entity == null) continue;
if (req.amount < e) {
e -= req.entity.addEnergy(req.amount, powerType);
accepted = LightWeightPowerRegistry.getInstance().addPower(world, ((ExternalPowerRequest) req).pos,
powerType, offered);
} else {
req.entity.addEnergy(e, powerType);
e = 0;
break;
if (req.entity == null) continue;
accepted = req.entity.addEnergy(offered, powerType);
}
if (Float.isFinite(accepted)) e -= Math.max(0, Math.min(offered, accepted));
if (e <= 0) break;
}
return availableEnergy - e;
}
Expand Down Expand Up @@ -297,6 +306,29 @@ public void conduitBlockRemovedEvent(World w, int dimension, BlockPos location,
}
}

/** Clears networks crossing a changed chunk, including adjacent pipe-only boundaries. */
@SubscribeEvent
public void onChunkLoad(ChunkEvent.Load event) {
invalidateChunk(event);
}

/** Clears networks before an unloaded section can be used for another transfer. */
@SubscribeEvent
public void onChunkUnload(ChunkEvent.Unload event) {
invalidateChunk(event);
}

private void invalidateChunk(ChunkEvent event) {
World world = event.getWorld();
if (world.isRemote) return;
int dimension = world.provider.getDimension();
clearIfWorldChanged(world, dimension);
for (ConduitNetworkManager manager : networkManagers.values()) {
manager.invalidateChunk(dimension, new ChunkPos(
event.getChunk().x, event.getChunk().z));
}
}

private ConduitNetworkManager getConduitNetworkManager(ConduitType type) {
if (networkManagers.containsKey(type)) {
return networkManagers.get(type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public static void drawFluidBar(
FluidStack fs, float barHeight, int xPos, int yPos,
ResourceLocation displayImage, GUIContainer guiContainer, int x, int y, float zLevel
) {
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 @@ -45,6 +45,7 @@ public static void drawFluidBar(
FluidStack fs, float barHeight, int xPos, int yPos,
ResourceLocation displayImage, GUIContainer guiContainer, int x, int y, float zLevel
) {
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 @@ -135,8 +135,9 @@ protected static boolean canInsertItemInto(ItemStack item, ISidedInventory dest,
ItemStack destItem = dest.getStackInSlot(slot);
if(destItem == null || destItem.isEmpty()) {
return true;
} else {
return ItemStack.areItemsEqual(item, destItem);
} else if (ItemStack.areItemsEqual(item, destItem) && ItemStack.areItemStackTagsEqual(item, destItem)
&& destItem.getCount() < Math.min(dest.getInventoryStackLimit(), destItem.getMaxStackSize())) {
return true;
}
}
}
Expand All @@ -159,10 +160,13 @@ protected static boolean transferItem(ISidedInventory src, EnumFacing srcFace, I
ItemStack otherItem = dest.getStackInSlot(destValidSlots[j]);
if(otherItem != null && !otherItem.isEmpty()
&& ItemStack.areItemsEqual(item, otherItem)
&& ItemStack.areItemStackTagsEqual(item, otherItem)
&& otherItem.getCount() < dest.getInventoryStackLimit()
&& otherItem.getCount() < otherItem.getItem().getItemStackLimit(otherItem)){
src.decrStackSize(srcValidSlots[i], 1);
otherItem.grow(1);
src.markDirty();
dest.markDirty();
return true;
}
}
Expand All @@ -173,6 +177,8 @@ protected static boolean transferItem(ISidedInventory src, EnumFacing srcFace, I
ItemStack otherItem = dest.getStackInSlot(destValidSlots[j]);
if(otherItem == null || otherItem.isEmpty()){
dest.setInventorySlotContents(destValidSlots[j], src.decrStackSize(srcValidSlots[i], 1));
src.markDirty();
dest.markDirty();
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,7 @@ private boolean canInsert(ItemStack item, EnumFacing f) {
if (target instanceof IInventory) {
EnumFacing side = f.getOpposite();
ISidedInventory dt = InventoryWrapper.wrap((IInventory) target);
int[] slots = dt.getSlotsForFace(side);
for (int i = 0; i < slots.length; i++) {
int slot = slots[i];
if (dt.canInsertItem(slot, item, side)) {
ItemStack targetSlot = dt.getStackInSlot(slot);
if ((targetSlot == null) ||
(ItemStack.areItemsEqual(item, targetSlot)
&& !targetSlot.getItem().isDamageable()
&& targetSlot.getCount() < targetSlot.getMaxStackSize()
&& targetSlot.getCount() < dt.getInventoryStackLimit())) {
return true;
}
}
}
return false;
return canInsertItemInto(item, dt, side);
} else {
return false;
}
Expand Down
Loading
Loading