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

- Restore missing carbon-dust and steel-block recipe names when equivalent registered materials are available. Keep existing Ore Dictionary entries and recipe amounts unchanged.
- 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.

Test rig updates (no Power Advantage version change)

- Use the repaired Steam and Electric drill builds in the 1.10 test profiles.
Expand Down
5 changes: 4 additions & 1 deletion 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.10.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.

The maintained implementation namespace is `zone.moddev.mc.poweradvantage`. The legacy
`cyano.poweradvantage.api` package remains the supported add-on API.

Expand All @@ -17,7 +19,8 @@ The source-controlled [Advantage Works test rig](test-rig/README.md) turns those
## Building Minecraft 1.10.2

The rig profiles use Steam and Electric Advantage `2.2.5.110021`, which retain unfinished
drill work after a restart. This profile update does not change Power Advantage gameplay or its version.
drill work after a restart. The machine repairs and their verification scope are
described in the test coverage notes above; this work does not bump the version.

The `master-1.10.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
230 changes: 230 additions & 0 deletions TEST-COVERAGE-BACKPORT.md

Large diffs are not rendered by default.

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

// The native GUI adapter is a test mod, not a production or published source set.
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) {
group = 'verification'
description = 'Builds the isolated native GUI capture mod, outside release artifacts.'
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 @@ -829,3 +847,115 @@ tasks.register('verifyEclipseProductionClasspath') {
tasks.named('check') {
dependsOn tasks.named('verifyReleaseConfiguration')
}

tasks.register('prepareGuiProbeLaunch') {
group = 'verification'
description = 'Writes a separate native GUI capture launch with an explicit runtime-only classpath.'
dependsOn tasks.named('genEclipseRuns')
inputs.file(layout.projectDirectory.file('runClient.launch'))
inputs.files(configurations.runtimeClasspath)
def bootstrapOverride = providers.gradleProperty('guiProbeBootstrapJar')
if (bootstrapOverride.isPresent()) {
inputs.file(bootstrapOverride.map { file(it) })
}
outputs.file(layout.buildDirectory.file('client-resource-probe/runClientGuiProbe.launch'))
doLast {
File template = file('runClient.launch')
def launch = new groovy.xml.XmlParser(false, false).parse(template)
def stringAttribute = { String key ->
def attributes = launch.stringAttribute.findAll { it.attribute('key') == key }
if (attributes.size() != 1) {
throw new GradleException("GUI launch needs exactly one ${key} attribute")
}
attributes.first()
}
if (stringAttribute('org.eclipse.jdt.launching.MAIN_TYPE').attribute('value')
!= 'net.minecraftforge.launcher.Main') {
throw new GradleException('GUI capture requires the native ForgeGradle Slime Launcher')
}

def environment = launch.mapAttribute.find {
it.attribute('key') == 'org.eclipse.debug.core.environmentVariables'
}
if (environment == null) {
throw new GradleException('GUI launch is missing its native Forge environment')
}
[MC_VERSION: project.minecraft_version,
FORGE_VERSION: project.forge_version,
MCP_MAPPINGS: "${project.mapping_channel}_${project.mapping_version}"].each { key, value ->
def entry = environment.mapEntry.find { it.attribute('key') == key }
if (entry == null || entry.attribute('value') != value.toString()) {
throw new GradleException("GUI launch has an unexpected ${key}")
}
}

def program = stringAttribute('org.eclipse.jdt.launching.PROGRAM_ARGUMENTS')
String arguments = program.attribute('value')
if (!arguments.contains('--main net.minecraftforge.legacydev.MainClient ')
|| arguments.find(/--(?:server|port|gameDir|world)\b/) != null) {
throw new GradleException('GUI capture requires a client launch without a world or server target')
}
['--cache', '--metadata', '--to-srg', '--to-obf'].each { String option ->
int start = arguments.indexOf(option + ' ')
int end = start < 0 ? -1 : arguments.indexOf(' --', start + option.length() + 1)
if (start < 0 || end < 0) {
throw new GradleException("GUI launch is missing a complete ${option} argument")
}
start += option.length() + 1
String value = arguments.substring(start, end)
if (value.find(/\s/) != null && !value.startsWith('"')) {
arguments = arguments.substring(0, start) + '"' + value + '"' + arguments.substring(end)
}
}
program.attributes().put('value', arguments)

def classpathLists = launch.listAttribute.findAll {
it.attribute('key') == 'org.eclipse.jdt.launching.CLASSPATH'
}
if (classpathLists.size() != 1) {
throw new GradleException('GUI launch needs exactly one generated classpath')
}
// Never use Buildship/project outputs here: the runner supplies hash-bound
// Advantage and probe jars in its disposable mods directory.
Set<File> runtime = new LinkedHashSet<>(configurations.runtimeClasspath.files.collect {
it.canonicalFile
})
classpathLists.first().listEntry.each { entry ->
def parsed = new groovy.xml.XmlParser(false, false).parseText(entry.attribute('value'))
String archive = parsed.attribute('externalArchive')
if (archive != null) runtime.add(file(archive).canonicalFile)
}
// Older cached 1.10 Mavenizer metadata may omit legacydev. An explicit
// test-launch override avoids changing any production dependency/pin.
if (bootstrapOverride.isPresent()) {
runtime.add(file(bootstrapOverride.get()).canonicalFile)
}
String mappedForge = "forge-${project.minecraft_version}-${project.forge_version}-" \
+ "${project.mapping_channel}-${project.mapping_version}.jar"
if (!runtime.any { it.name == mappedForge }
|| !runtime.any { it.name.startsWith('legacydev-') && it.name.endsWith('.jar') }) {
throw new GradleException('GUI launch is missing its native mapped Forge/legacydev runtime. '
+ 'For old metadata, pass -PguiProbeBootstrapJar=<existing-legacydev-fatjar>.')
}
if (runtime.any { !it.isFile() || it.path.replace('\\', '/').find(/\/(?:bin|classes)\/test(?:\/|$)/) != null }) {
throw new GradleException('GUI runtime must contain existing dependency archives, not test output')
}
launch.remove(classpathLists.first())
def classpath = launch.appendNode('listAttribute', [key: 'org.eclipse.jdt.launching.CLASSPATH'])
runtime.each { File archive ->
StringWriter entry = new StringWriter()
new groovy.xml.MarkupBuilder(entry).runtimeClasspathEntry(
externalArchive: archive.canonicalPath, type: '2', path: '5')
classpath.appendNode('listEntry', [value: entry.toString()])
}
['org.eclipse.jdt.launching.DEFAULT_CLASSPATH': 'false',
'org.eclipse.jdt.launching.ATTR_EXCLUDE_TEST_CODE': 'true'].each { key, value ->
launch.booleanAttribute.findAll { it.attribute('key') == key }.each { launch.remove(it) }
launch.appendNode('booleanAttribute', [key: key, value: value])
}
File output = layout.buildDirectory.file('client-resource-probe/runClientGuiProbe.launch').get().asFile
output.parentFile.mkdirs()
output.setText(groovy.xml.XmlUtil.serialize(launch).replace('\r\n', '\n'), 'UTF-8')
logger.lifecycle("Native GUI capture launch: ${output}")
}
}
4 changes: 4 additions & 0 deletions src/main/java/cyano/poweradvantage/api/PoweredEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ public final void update() {
if (this.isServer() && ((this.getWorld().getTotalWorldTime() + this.getTickOffset()) % powerUpdateInterval == 0)) {
this.powerUpdate();
}
if (this.isServer()) {
// Timers and energy may change without a GUI or neighbour update.
this.getWorld().markChunkDirty(this.getPos(), this);
}
}

private void refreshConduitNetworkAfterLoad() {
Expand Down
62 changes: 52 additions & 10 deletions src/main/java/cyano/poweradvantage/api/simple/SimpleMachineGUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@
import zone.moddev.mc.poweradvantage.PowerAdvantage;
import com.mcmoddev.poweradvantage.math.Integer2D;
import com.mcmoddev.poweradvantage.registry.ITileEntityGUI;
import zone.moddev.mc.poweradvantage.registry.MachineGUIRegistry;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import org.lwjgl.opengl.GL11;

import java.util.HashMap;
import java.util.Map;
Expand All @@ -31,6 +33,10 @@
*
*/
public class SimpleMachineGUI implements ITileEntityGUI {
private static final int MACHINE_TOP = 12;
private static final int MACHINE_HEIGHT = 128;
private static final int PLAYER_PANEL_TOP = 150;
private static final int PLAYER_SLOTS_TOP = PLAYER_PANEL_TOP + 8;

/**
* This is the texture resource for drawing the player's inventory.
Expand Down Expand Up @@ -63,7 +69,7 @@ public class SimpleMachineGUI implements ITileEntityGUI {
public SimpleMachineGUI(ResourceLocation guiImage, Integer2D... inventorySlotCoordinates){
this.guiDisplayImage = guiImage;
this.guiWidth = 176;
this.guiHeight = 222;
this.guiHeight = 240;
if(inventorySlotCoordinates == null){
this.inventorySlotCoordinates = new Integer2D[0];
} else {
Expand Down Expand Up @@ -149,14 +155,14 @@ public Container(InventoryPlayer playerItems, IInventory entity){
int index = 0;
for(Integer2D pt : inventorySlotCoordinates){
if(specialSlots.containsKey(index)){
SlotContext params = new SlotContext(playerItems,entity,index, pt.X, pt.Y);
SlotContext params = new SlotContext(playerItems,entity,index, pt.X, pt.Y + MACHINE_TOP);
this.addSlotToContainer(specialSlots.get(index).apply(params));
} else {
this.addSlotToContainer(new Slot(entity, index, pt.X, pt.Y));
this.addSlotToContainer(new Slot(entity, index, pt.X, pt.Y + MACHINE_TOP));
}
index++;
}
bindPlayerInventory(playerItems, 140);
bindPlayerInventory(playerItems, PLAYER_SLOTS_TOP);
}

@Override
Expand Down Expand Up @@ -221,25 +227,61 @@ else if (!this.mergeItemStack(stackInSlot, 0, hostSize, false)) {
@SideOnly(Side.CLIENT)
public class GUIContainer extends net.minecraft.client.gui.inventory.GuiContainer{
private final Object entity;
private final InventoryPlayer playerInventory;
private final ITextComponent machineTitle;
public GUIContainer(InventoryPlayer playerItems, IInventory entity) {
super(new Container(playerItems,entity));
//System.out.println("Size: "+this.xSize+"x"+this.ySize+" at coordinate ("+this.guiLeft+","+this.guiTop+")");
this.xSize = guiWidth;
this.ySize = guiHeight;
this.entity = entity;
this.playerInventory = playerItems;
if (entity instanceof TileEntity && ((TileEntity) entity).hasWorld()) {
TileEntity tile = (TileEntity) entity;
this.machineTitle = MachineGUIRegistry.menuTitle(tile, tile.getWorld().getBlockState(tile.getPos()).getBlock());
} else {
this.machineTitle = entity.getDisplayName();
}
}

@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
this.drawDefaultBackground();
super.drawScreen(mouseX, mouseY, partialTicks);
if (fontRendererObj.getStringWidth(machineTitle.getUnformattedText()) > width - 8
&& mouseY >= guiTop + 1 && mouseY < guiTop + MACHINE_TOP) {
this.drawCreativeTabHoveringText(machineTitle.getUnformattedText(), mouseX, mouseY);
}
}

@Override
protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY) {
String machineName = fitLabel(machineTitle.getUnformattedText(), width - 8);
fontRendererObj.drawStringWithShadow(machineName, (xSize - fontRendererObj.getStringWidth(machineName)) / 2, 1, 0xFFFFFF);
fontRendererObj.drawStringWithShadow(fitLabel(playerInventory.getDisplayName().getUnformattedText(), xSize - 16),
8, MACHINE_TOP + MACHINE_HEIGHT, 0xFFFFFF);
}

private String fitLabel(String text, int availableWidth) {
return fontRendererObj.getStringWidth(text) <= availableWidth ? text
: fontRendererObj.trimStringToWidth(text, Math.max(0, availableWidth - fontRendererObj.getStringWidth("..."))) + "...";
}

@Override
protected void drawGuiContainerBackgroundLayer(float f, int i, int j) {
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
GlStateManager.enableBlend();
GlStateManager.tryBlendFuncSeparate(770, 771, 1, 0);
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
this.mc.renderEngine.bindTexture(playerInventoryBox);
int x = (width - xSize) / 2;
int y = (height - ySize) / 2;
final int playerInventoryWidth = 176;
final int playerInventoryHeight = 222;
this.drawTexturedModalRect(x, y, 0, 0, playerInventoryWidth, playerInventoryHeight); // x, y, textureOffsetX, textureOffsetY, width, height)
final int playerInventoryHeight = 90;
this.drawTexturedModalRect(x, y + PLAYER_PANEL_TOP, 0, 132, playerInventoryWidth, playerInventoryHeight);
this.mc.renderEngine.bindTexture(guiDisplayImage);
this.drawTexturedModalRect(x, y, 0, 0, guiWidth, guiHeight); // x, y, textureOffsetX, textureOffsetY, width, height)
drawGUIDecorations(entity, this, x, y, this.zLevel);
this.drawTexturedModalRect(x, y + MACHINE_TOP, 0, 0, guiWidth, MACHINE_HEIGHT);
drawGUIDecorations(entity, this, x, y + MACHINE_TOP, this.zLevel);
GlStateManager.disableBlend();
}
/**
* Gets the z-coordinate of this GUI element
Expand Down
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 @@ -189,6 +190,11 @@
acceptedMinecraftVersions = "[1.10.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 Expand Up @@ -394,6 +400,8 @@ public void init(FMLInitializationEvent event)
{

FMLLog.info("%s: starting main inititalization", MODID);
// BaseMetals has initialized; fill missing aliases before fuel and child recipe setup.
zone.moddev.mc.poweradvantage.compat.BaseMetalsCompat.registerOreDictionaryFallbacks();
try {
FMLLog.info("%s: testing whether we have to support redstone flux", MODID);
Class<?> rfClass = Class.forName("cofh.api.energy.IEnergyReceiver", false, getClass().getClassLoader());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import zone.moddev.mc.poweradvantage.registry.FuelRegistry;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.common.FMLLog;
Expand All @@ -15,10 +16,13 @@
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;

public final class BaseMetalsCompat {

private static final String MODID = "basemetals";
private static final ResourceLocation STEEL_BLOCK = new ResourceLocation(MODID, "steel_block");
private static final String[] CRUSHER_REGISTRY_CLASSES = {
"cyano.basemetals.registry.CrusherRecipeRegistry",
"com.mcmoddev.lib.registry.CrusherRecipeRegistry"
Expand All @@ -32,6 +36,31 @@ public static boolean isLoaded() {
return Loader.isModLoaded(MODID);
}

public static void registerOreDictionaryFallbacks() {
registerOreDictionaryFallbacks(OreDictionary::getOres, OreDictionary::registerOre,
id -> GameData.getItemRegistry().getObject(id));
}

static void registerOreDictionaryFallbacks(Function<String, List<ItemStack>> ores,
BiConsumer<String, ItemStack> register, Function<ResourceLocation, Item> items) {
if (ores.apply("dustCarbon").isEmpty()) {
for (String source : new String[]{"dustCoal", "dustCharcoal"}) {
for (ItemStack stack : ores.apply(source)) {
if (stack != null && stack.stackSize > 0 && stack.getItem() != null) {
register.accept("dustCarbon", stack.copy());
}
}
}
}
if (ores.apply("blockSteel").isEmpty()) {
Item item = items.apply(STEEL_BLOCK);
if (item instanceof ItemBlock && STEEL_BLOCK.equals(item.getRegistryName())
&& STEEL_BLOCK.equals(((ItemBlock)item).getBlock().getRegistryName())) {
register.accept("blockSteel", new ItemStack(item, 1));
}
}
}

public static void setStrongHammers(boolean enabled) {
if (!isLoaded()) return;
if (setOldStrongHammers(enabled)) return;
Expand Down
Loading
Loading