diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b56bea532..b3064d9e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -137,9 +137,9 @@ jobs: if-no-files-found: error retention-days: 30 path: | - build/libs/SkysBuildingPieces-0.3.10.121012.jar - build/libs/SkysBuildingPieces-0.3.10.121012-sources.jar - build/libs/SkysBuildingPieces-0.3.10.121012-javadoc.jar + build/libs/SkysBuildingPieces-0.3.11.121012.jar + build/libs/SkysBuildingPieces-0.3.11.121012-sources.jar + build/libs/SkysBuildingPieces-0.3.11.121012-javadoc.jar build/release/SHA256SUMS CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md index b3da8c95c..97c9e24b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.3.11.121012 + +- Fix fuel checks for stone, soil and other non-wood pieces in recipe viewers. This also covers the BOP and Mineralogy pieces. +- Keep datapack fuel settings working for non-wood pieces. + ## 0.3.10.121012 - Give each crafting result a stable recipe-book button, with alternative ways to make that item grouped together. diff --git a/gradle.properties b/gradle.properties index 41077fc92..d7ba4ee05 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,7 +21,7 @@ release_enabled=true curseforge_project_id=1754162 mod_name=Sky's Building Pieces mod_license=LGPL-2.1-only -mod_version=0.3.10.121012 +mod_version=0.3.11.121012 mod_group=zone.moddev.mc.skysbuildingpieces mod_authors=SkyBlade1978 mod_description=Extra building shapes for vanilla materials, with built-in Mineralogy support when installed. No world generation. diff --git a/src/bootstrapTest/java/zone/moddev/mc/skysbuildingpieces/probe/RuntimeBootstrap.java b/src/bootstrapTest/java/zone/moddev/mc/skysbuildingpieces/probe/RuntimeBootstrap.java index 57949897b..dbc38cce2 100644 --- a/src/bootstrapTest/java/zone/moddev/mc/skysbuildingpieces/probe/RuntimeBootstrap.java +++ b/src/bootstrapTest/java/zone/moddev/mc/skysbuildingpieces/probe/RuntimeBootstrap.java @@ -127,6 +127,10 @@ private void audit(MinecraftServer server, DeferredAudit pending) { int recipes = 0; int stacks = 0; + if (pending == null && (phase.equals("fresh") || phase.equals("reload"))) { + FuelChecks.run(); + } + if (phase.equals("water")) { WaterloggingChecks.run(world); } else if (phase.equals("staging")) { diff --git a/src/integrationTest/java/zone/moddev/mc/skysbuildingpieces/probe/ClientProbe.java b/src/integrationTest/java/zone/moddev/mc/skysbuildingpieces/probe/ClientProbe.java index a4c108027..20017e5ea 100644 --- a/src/integrationTest/java/zone/moddev/mc/skysbuildingpieces/probe/ClientProbe.java +++ b/src/integrationTest/java/zone/moddev/mc/skysbuildingpieces/probe/ClientProbe.java @@ -75,6 +75,8 @@ public void tick(net.neoforged.neoforge.client.event.ClientTickEvent.Post event) && !minecraft.getConnection().getRecipeManager().getRecipes().isEmpty()) { RecipeBookGroupChecks.run(minecraft); RecipePreviewModels.check(minecraft); + FuelChecks.run(); + org.apache.logging.log4j.LogManager.getLogger().info("BUILDING_PIECES_CLIENT_FUEL_PASS context=with_world"); colours(minecraft); SnowCoverClientChecks.run(minecraft); net.minecraft.world.item.CreativeModeTabs.tryRebuildTabContents(minecraft.level.enabledFeatures(), @@ -99,6 +101,8 @@ public void tick(net.neoforged.neoforge.client.event.ClientTickEvent.Post event) org.lwjgl.glfw.GLFW.glfwHideWindow(minecraft.getWindow().getWindow()); ConfigScreenChecks.run(minecraft); + FuelChecks.run(); + org.apache.logging.log4j.LogManager.getLogger().info("BUILDING_PIECES_CLIENT_FUEL_PASS context=before_world"); var availableCreative = PiecesTab.entries(); for (net.minecraft.world.item.Item retired : Pieces.RETIRED_TEMPLATES.values()) { diff --git a/src/integrationTest/java/zone/moddev/mc/skysbuildingpieces/probe/FuelChecks.java b/src/integrationTest/java/zone/moddev/mc/skysbuildingpieces/probe/FuelChecks.java index bf24c4d71..43216f1c0 100644 --- a/src/integrationTest/java/zone/moddev/mc/skysbuildingpieces/probe/FuelChecks.java +++ b/src/integrationTest/java/zone/moddev/mc/skysbuildingpieces/probe/FuelChecks.java @@ -2,37 +2,92 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.core.registries.BuiltInRegistries; +import net.neoforged.neoforge.registries.datamaps.builtin.NeoForgeDataMaps; import zone.moddev.mc.skysbuildingpieces.content.PieceItem; import zone.moddev.mc.skysbuildingpieces.content.Pieces; -/** Cutting and recombining wood must not multiply its furnace fuel value. */ +/** Recipe viewers ask about every item, including pieces that cannot burn. */ public final class FuelChecks { public static void run() { - int cases = 0; - for (var block : Pieces.playableBlocks()) { - var definition = block.definition(block.defaultBlockState()); - if (!definition.type.equals("wood")) { + reportedItems(); + configuredNonWoodFuel(); + int woodCases = 0; + int otherCases = 0; + for (var registered : BuiltInRegistries.ITEM) { + if (!(registered instanceof PieceItem item)) { continue; } - PieceItem item = (PieceItem) block.asItem(); - ItemStack full = Pieces.stack(Pieces.nativeState(definition, "full"), 1); - int divisor = switch (block.palette.shape) { + var definition = zone.moddev.mc.skysbuildingpieces.catalogue.Catalogue.INSTANCE.materials.get(item.material); + boolean wooden = definition.type.equals("wood"); + ItemStack full = wooden ? Pieces.stack(Pieces.nativeState(definition, "full"), 1) : ItemStack.EMPTY; + var dataFuel = item.builtInRegistryHolder().getData(NeoForgeDataMaps.FURNACE_FUELS); + int defaultFuel = dataFuel == null ? 0 : dataFuel.burnTime(); + int divisor = switch (item.piece.palette.shape) { case SLAB, VERTICAL_SLAB -> 2; case STEP, HORIZONTAL_STEP, VERTICAL_STEP -> 4; case CORNER -> 8; default -> 1; }; - for (var furnace : java.util.List.of(RecipeType.SMELTING, RecipeType.SMOKING, RecipeType.BLASTING)) { - int original = Math.max(0, full.getBurnTime(furnace)); + // A null recipe type is the general fuel query used by recipe viewers. + for (var furnace : java.util.Arrays.asList(null, RecipeType.SMELTING, RecipeType.SMOKING, RecipeType.BLASTING)) { + int expected = wooden ? Math.max(0, full.getBurnTime(furnace)) / divisor : defaultFuel; int actual = new ItemStack(item).getBurnTime(furnace); - if (actual != original / divisor) { - throw new AssertionError("Wood fuel changed through cutting: " + definition.id + "/" - + block.palette.shape + " expected=" + original / divisor + " actual=" + actual); + if (actual != expected) { + throw new AssertionError("Piece fuel differs from its material or configured default: " + + BuiltInRegistries.ITEM.getKey(item) + " recipe=" + furnace + + " expected=" + expected + " actual=" + actual); } - cases++; + if (wooden) { + woodCases++; + } else { + otherCases++; + } + } + } + if (woodCases == 0 || otherCases == 0) { + throw new AssertionError("Fuel checks must include both wood and non-wood pieces"); + } + org.apache.logging.log4j.LogManager.getLogger().info( + "BUILDING_PIECES_FUEL_PASS wood_cases={} nonwood_cases={} general_query=true", + woodCases, otherCases); + } + + private static void reportedItems() { + java.util.List failures = new java.util.ArrayList<>(); + for (String identity : new String[] { "skysbuildingpieces:mineralogy_tuff_corner", + "skysbuildingpiecesbop:crag_rock_slab" }) { + var id = net.minecraft.resources.ResourceLocation.parse(identity); + if (!BuiltInRegistries.ITEM.containsKey(id)) { + continue; } + try { + int burnTime = new ItemStack(BuiltInRegistries.ITEM.get(id)).getBurnTime(null); + org.apache.logging.log4j.LogManager.getLogger().info( + "BUILDING_PIECES_REPORTED_FUEL_QUERY item={} burn_time={}", identity, burnTime); + } catch (IllegalStateException failure) { + failures.add(identity + ": " + failure.getMessage()); + } + } + if (!failures.isEmpty()) { + throw new AssertionError("Reported recipe-viewer fuel queries failed: " + failures); + } + } + + private static void configuredNonWoodFuel() { + Integer expected = Integer.getInteger("skysbuildingpieces.expectedSnowCornerFuel"); + if (expected == null) { + return; + } + var item = BuiltInRegistries.ITEM.get(net.minecraft.resources.ResourceLocation.parse( + "skysbuildingpieces:snow_corner")); + int actual = new ItemStack(item).getBurnTime(RecipeType.SMELTING); + if (actual != expected) { + throw new AssertionError("The non-wood fuel data map was ignored: expected=" + expected + + " actual=" + actual); } - org.apache.logging.log4j.LogManager.getLogger().info("BUILDING_PIECES_FUEL_PASS cases=" + cases); + org.apache.logging.log4j.LogManager.getLogger().info( + "BUILDING_PIECES_CONFIGURED_FUEL_PASS item=skysbuildingpieces:snow_corner burn_time={}", actual); } private FuelChecks() { diff --git a/src/integrationTest/java/zone/moddev/mc/skysbuildingpieces/probe/RuntimeProbe.java b/src/integrationTest/java/zone/moddev/mc/skysbuildingpieces/probe/RuntimeProbe.java index 0c7d83433..cfea7a707 100644 --- a/src/integrationTest/java/zone/moddev/mc/skysbuildingpieces/probe/RuntimeProbe.java +++ b/src/integrationTest/java/zone/moddev/mc/skysbuildingpieces/probe/RuntimeProbe.java @@ -47,7 +47,7 @@ public void started(ServerStartedEvent event) { String phase = System.getProperty("skysbuildingpieces.integrationPhase", ""); needsGameplay = !(phase.startsWith("sylvester") || phase.startsWith("retired-rocks-") || phase.startsWith("bop-legacy-") || phase.equals("path-upgrade") - || phase.startsWith("recipe-book-") || phase.startsWith("legacy-")); + || phase.startsWith("recipe-book-") || phase.startsWith("legacy-") || phase.startsWith("fuel-")); if (needsGameplay) { GameplayChecks.prepare(server.getLevel(Level.OVERWORLD)); } @@ -111,6 +111,8 @@ public void tick(net.neoforged.neoforge.event.tick.ServerTickEvent.Post event) { org.apache.logging.log4j.LogManager.getLogger().info("RECIPE_BOOK_PHASE_PASS " + phase); } else if (phase.startsWith("legacy-")) { LegacyFixture.run(world, phase); + } else if (phase.startsWith("fuel-")) { + FuelChecks.run(); } else if (phase.equals("compat")) { NativeLegacyChecks.run(world); CompatibilityChecks.run(world); diff --git a/src/main/java/zone/moddev/mc/skysbuildingpieces/SkysBuildingPieces.java b/src/main/java/zone/moddev/mc/skysbuildingpieces/SkysBuildingPieces.java index 3b5797053..a70964b71 100644 --- a/src/main/java/zone/moddev/mc/skysbuildingpieces/SkysBuildingPieces.java +++ b/src/main/java/zone/moddev/mc/skysbuildingpieces/SkysBuildingPieces.java @@ -23,7 +23,7 @@ @Mod(SkysBuildingPieces.ID) public final class SkysBuildingPieces { public static final String ID = "skysbuildingpieces"; - public static final String VERSION = "0.3.10.121012"; + public static final String VERSION = "0.3.11.121012"; public static final Logger LOGGER = LogManager.getLogger(); public static boolean forceReplace; public static Block HOLDER; diff --git a/src/main/java/zone/moddev/mc/skysbuildingpieces/content/PieceItem.java b/src/main/java/zone/moddev/mc/skysbuildingpieces/content/PieceItem.java index cec8d2acd..8d940c64d 100644 --- a/src/main/java/zone/moddev/mc/skysbuildingpieces/content/PieceItem.java +++ b/src/main/java/zone/moddev/mc/skysbuildingpieces/content/PieceItem.java @@ -61,7 +61,9 @@ public Component getName(ItemStack stack) { public int getBurnTime(ItemStack stack, net.minecraft.world.item.crafting.RecipeType recipeType) { Catalogue.MaterialDef definition = Catalogue.INSTANCE.materials.get(material); if (!definition.type.equals("wood")) { - return -1; + // NeoForge rejects negative burn times. Keep its normal data-map lookup + // so non-fuels return zero and modpacks can still configure other fuels. + return super.getBurnTime(stack, recipeType); } ItemStack full = Pieces.stack(Pieces.nativeState(definition, "full"), 1); int fullBurnTime = full == null ? 0 : Math.max(0, full.getBurnTime(recipeType));