From edf0e12be41cce1080a6ef274ae80b1874505741 Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Fri, 30 Oct 2026 09:48:48 +0000 Subject: [PATCH 1/4] Port Base Metals to Forge 26.3 --- .github/workflows/ci.yml | 42 +- .github/workflows/codeql-analysis.yml | 4 +- .github/workflows/validate-gradle-build.yml | 4 +- CHANGELOG.txt | 10 + README.md | 25 +- build.gradle | 10 +- ci-fixtures/tools/README.md | 6 +- ...craft-mavenizer-0.5.21-orespawn-compat.jar | Bin 2566958 -> 2566958 bytes ...aft-mavenizer-0.5.21-orespawn-compat.patch | 2 +- .../tools/minecraft-source-compatibility.json | 2 +- docs/COMPATIBILITY.md | 2 +- docs/MIGRATION.md | 7 +- docs/VERSIONS.md | 22 +- docs/WORLDGEN.md | 4 +- docs/schemas/orespawn-4.1.0/README.md | 4 +- gradle.properties | 26 +- gradle/README.md | 2 +- gradle/dependencies.gradle | 4 +- gradle/ide/eclipse.gradle | 4 +- gradle/release/artifacts.gradle | 28 +- gradle/verification/packaged-runtime.gradle | 22 +- .../mc/basemetals/data/GeneratedData.java | 80 +- .../basemetals/data/SmithTradeProvider.java | 6 +- .../content/BaseMetalsGameTests.java | 59 +- .../content/NativeComponentGameTests.java | 6 +- .../advancement/recipes/activator_rail.json | 4 +- .../advancement/recipes/adamantine_anvil.json | 4 +- .../advancement/recipes/adamantine_arrow.json | 4 +- .../advancement/recipes/adamantine_axe.json | 4 +- .../advancement/recipes/adamantine_bars.json | 4 +- .../recipes/adamantine_bars_2.json | 4 +- .../advancement/recipes/adamantine_block.json | 4 +- .../recipes/adamantine_block_ingot.json | 4 +- .../advancement/recipes/adamantine_bolt.json | 4 +- .../advancement/recipes/adamantine_boots.json | 4 +- .../advancement/recipes/adamantine_bow.json | 4 +- .../recipes/adamantine_button.json | 4 +- .../recipes/adamantine_chestplate.json | 4 +- .../recipes/adamantine_crackhammer.json | 4 +- .../recipes/adamantine_crossbow.json | 4 +- .../advancement/recipes/adamantine_door.json | 4 +- .../recipes/adamantine_fishingrod.json | 4 +- .../advancement/recipes/adamantine_gear.json | 4 +- .../recipes/adamantine_helmet.json | 4 +- .../advancement/recipes/adamantine_hoe.json | 4 +- .../recipes/adamantine_horsearmor.json | 4 +- .../adamantine_ingot_from_nuggets.json | 4 +- .../recipes/adamantine_leggings.json | 4 +- .../advancement/recipes/adamantine_lever.json | 4 +- .../recipes/adamantine_nuggets.json | 4 +- .../recipes/adamantine_pickaxe.json | 4 +- .../advancement/recipes/adamantine_plate.json | 4 +- .../recipes/adamantine_powder_from_small.json | 4 +- .../recipes/adamantine_pressure_plate.json | 4 +- .../recipes/adamantine_raw_blasting.json | 4 +- .../recipes/adamantine_raw_smelting.json | 4 +- .../advancement/recipes/adamantine_rod.json | 4 +- .../recipes/adamantine_scythe.json | 4 +- .../recipes/adamantine_shears.json | 4 +- .../recipes/adamantine_shield.json | 4 +- .../recipes/adamantine_shovel.json | 4 +- .../advancement/recipes/adamantine_slab.json | 4 +- .../recipes/adamantine_smallpowder.json | 4 +- .../recipes/adamantine_stairs.json | 4 +- .../advancement/recipes/adamantine_sword.json | 4 +- .../recipes/adamantine_trapdoor.json | 4 +- .../advancement/recipes/adamantine_wall.json | 4 +- .../advancement/recipes/antimony_arrow.json | 4 +- .../advancement/recipes/antimony_axe.json | 4 +- .../advancement/recipes/antimony_bars.json | 4 +- .../advancement/recipes/antimony_bars_2.json | 4 +- .../advancement/recipes/antimony_block.json | 4 +- .../recipes/antimony_block_ingot.json | 4 +- .../advancement/recipes/antimony_bolt.json | 4 +- .../advancement/recipes/antimony_boots.json | 4 +- .../advancement/recipes/antimony_bow.json | 4 +- .../advancement/recipes/antimony_button.json | 4 +- .../recipes/antimony_chestplate.json | 4 +- .../recipes/antimony_crackhammer.json | 4 +- .../recipes/antimony_crossbow.json | 4 +- .../advancement/recipes/antimony_door.json | 4 +- .../recipes/antimony_fishingrod.json | 4 +- .../advancement/recipes/antimony_gear.json | 4 +- .../advancement/recipes/antimony_helmet.json | 4 +- .../advancement/recipes/antimony_hoe.json | 4 +- .../recipes/antimony_horsearmor.json | 4 +- .../recipes/antimony_ingot_from_nuggets.json | 4 +- .../recipes/antimony_leggings.json | 4 +- .../advancement/recipes/antimony_lever.json | 4 +- .../advancement/recipes/antimony_nuggets.json | 4 +- .../advancement/recipes/antimony_pickaxe.json | 4 +- .../advancement/recipes/antimony_plate.json | 4 +- .../recipes/antimony_powder_from_small.json | 4 +- .../recipes/antimony_pressure_plate.json | 4 +- .../recipes/antimony_raw_blasting.json | 4 +- .../recipes/antimony_raw_smelting.json | 4 +- .../advancement/recipes/antimony_rod.json | 4 +- .../advancement/recipes/antimony_scythe.json | 4 +- .../advancement/recipes/antimony_shears.json | 4 +- .../advancement/recipes/antimony_shield.json | 4 +- .../advancement/recipes/antimony_shovel.json | 4 +- .../advancement/recipes/antimony_slab.json | 4 +- .../recipes/antimony_smallpowder.json | 4 +- .../advancement/recipes/antimony_stairs.json | 4 +- .../advancement/recipes/antimony_sword.json | 4 +- .../recipes/antimony_trapdoor.json | 4 +- .../advancement/recipes/antimony_wall.json | 4 +- .../advancement/recipes/aquarium_arrow.json | 4 +- .../advancement/recipes/aquarium_axe.json | 4 +- .../advancement/recipes/aquarium_bars.json | 4 +- .../advancement/recipes/aquarium_bars_2.json | 4 +- .../advancement/recipes/aquarium_blend.json | 4 +- .../recipes/aquarium_blend_from_small.json | 4 +- .../advancement/recipes/aquarium_block.json | 4 +- .../recipes/aquarium_block_ingot.json | 4 +- .../advancement/recipes/aquarium_bolt.json | 4 +- .../advancement/recipes/aquarium_boots.json | 4 +- .../advancement/recipes/aquarium_bow.json | 4 +- .../advancement/recipes/aquarium_button.json | 4 +- .../recipes/aquarium_chestplate.json | 4 +- .../recipes/aquarium_crackhammer.json | 4 +- .../recipes/aquarium_crossbow.json | 4 +- .../advancement/recipes/aquarium_door.json | 4 +- .../recipes/aquarium_fishingrod.json | 4 +- .../advancement/recipes/aquarium_gear.json | 4 +- .../advancement/recipes/aquarium_helmet.json | 4 +- .../advancement/recipes/aquarium_hoe.json | 4 +- .../recipes/aquarium_horsearmor.json | 4 +- .../recipes/aquarium_ingot_from_nuggets.json | 4 +- .../recipes/aquarium_leggings.json | 4 +- .../advancement/recipes/aquarium_lever.json | 4 +- .../advancement/recipes/aquarium_nuggets.json | 4 +- .../advancement/recipes/aquarium_pickaxe.json | 4 +- .../advancement/recipes/aquarium_plate.json | 4 +- .../recipes/aquarium_powder_from_small.json | 4 +- .../recipes/aquarium_pressure_plate.json | 4 +- .../advancement/recipes/aquarium_rod.json | 4 +- .../advancement/recipes/aquarium_scythe.json | 4 +- .../advancement/recipes/aquarium_shears.json | 4 +- .../advancement/recipes/aquarium_shield.json | 4 +- .../advancement/recipes/aquarium_shovel.json | 4 +- .../advancement/recipes/aquarium_slab.json | 4 +- .../recipes/aquarium_smallblend.json | 4 +- .../recipes/aquarium_smallpowder.json | 4 +- .../advancement/recipes/aquarium_stairs.json | 4 +- .../advancement/recipes/aquarium_sword.json | 4 +- .../recipes/aquarium_trapdoor.json | 4 +- .../advancement/recipes/aquarium_wall.json | 4 +- .../advancement/recipes/bismuth_arrow.json | 4 +- .../advancement/recipes/bismuth_axe.json | 4 +- .../advancement/recipes/bismuth_bars.json | 4 +- .../advancement/recipes/bismuth_bars_2.json | 4 +- .../advancement/recipes/bismuth_block.json | 4 +- .../recipes/bismuth_block_ingot.json | 4 +- .../advancement/recipes/bismuth_bolt.json | 4 +- .../advancement/recipes/bismuth_boots.json | 4 +- .../advancement/recipes/bismuth_bow.json | 4 +- .../advancement/recipes/bismuth_button.json | 4 +- .../recipes/bismuth_chestplate.json | 4 +- .../recipes/bismuth_crackhammer.json | 4 +- .../advancement/recipes/bismuth_crossbow.json | 4 +- .../advancement/recipes/bismuth_door.json | 4 +- .../recipes/bismuth_fishingrod.json | 4 +- .../advancement/recipes/bismuth_gear.json | 4 +- .../advancement/recipes/bismuth_helmet.json | 4 +- .../advancement/recipes/bismuth_hoe.json | 4 +- .../recipes/bismuth_horsearmor.json | 4 +- .../recipes/bismuth_ingot_from_nuggets.json | 4 +- .../advancement/recipes/bismuth_leggings.json | 4 +- .../advancement/recipes/bismuth_lever.json | 4 +- .../advancement/recipes/bismuth_nuggets.json | 4 +- .../advancement/recipes/bismuth_pickaxe.json | 4 +- .../advancement/recipes/bismuth_plate.json | 4 +- .../recipes/bismuth_powder_from_small.json | 4 +- .../recipes/bismuth_pressure_plate.json | 4 +- .../recipes/bismuth_raw_blasting.json | 4 +- .../recipes/bismuth_raw_smelting.json | 4 +- .../advancement/recipes/bismuth_rod.json | 4 +- .../advancement/recipes/bismuth_scythe.json | 4 +- .../advancement/recipes/bismuth_shears.json | 4 +- .../advancement/recipes/bismuth_shield.json | 4 +- .../advancement/recipes/bismuth_shovel.json | 4 +- .../advancement/recipes/bismuth_slab.json | 4 +- .../recipes/bismuth_smallpowder.json | 4 +- .../advancement/recipes/bismuth_stairs.json | 4 +- .../advancement/recipes/bismuth_sword.json | 4 +- .../advancement/recipes/bismuth_trapdoor.json | 4 +- .../advancement/recipes/bismuth_wall.json | 4 +- .../advancement/recipes/brass_arrow.json | 4 +- .../advancement/recipes/brass_axe.json | 4 +- .../advancement/recipes/brass_bars.json | 4 +- .../advancement/recipes/brass_bars_2.json | 4 +- .../advancement/recipes/brass_blend.json | 4 +- .../recipes/brass_blend_from_small.json | 4 +- .../advancement/recipes/brass_block.json | 4 +- .../recipes/brass_block_ingot.json | 4 +- .../advancement/recipes/brass_bolt.json | 4 +- .../advancement/recipes/brass_boots.json | 4 +- .../advancement/recipes/brass_bow.json | 4 +- .../advancement/recipes/brass_button.json | 4 +- .../advancement/recipes/brass_chestplate.json | 4 +- .../recipes/brass_crackhammer.json | 4 +- .../advancement/recipes/brass_crossbow.json | 4 +- .../advancement/recipes/brass_door.json | 4 +- .../advancement/recipes/brass_fishingrod.json | 4 +- .../advancement/recipes/brass_gear.json | 4 +- .../advancement/recipes/brass_helmet.json | 4 +- .../advancement/recipes/brass_hoe.json | 4 +- .../advancement/recipes/brass_horsearmor.json | 4 +- .../recipes/brass_ingot_from_nuggets.json | 4 +- .../advancement/recipes/brass_leggings.json | 4 +- .../advancement/recipes/brass_lever.json | 4 +- .../advancement/recipes/brass_nuggets.json | 4 +- .../advancement/recipes/brass_pickaxe.json | 4 +- .../advancement/recipes/brass_plate.json | 4 +- .../recipes/brass_powder_from_small.json | 4 +- .../recipes/brass_pressure_plate.json | 4 +- .../advancement/recipes/brass_rod.json | 4 +- .../advancement/recipes/brass_scythe.json | 4 +- .../advancement/recipes/brass_shears.json | 4 +- .../advancement/recipes/brass_shield.json | 4 +- .../advancement/recipes/brass_shovel.json | 4 +- .../advancement/recipes/brass_slab.json | 4 +- .../advancement/recipes/brass_smallblend.json | 4 +- .../recipes/brass_smallpowder.json | 4 +- .../advancement/recipes/brass_stairs.json | 4 +- .../advancement/recipes/brass_sword.json | 4 +- .../advancement/recipes/brass_trapdoor.json | 4 +- .../advancement/recipes/brass_wall.json | 4 +- .../advancement/recipes/bronze_arrow.json | 4 +- .../advancement/recipes/bronze_axe.json | 4 +- .../advancement/recipes/bronze_bars.json | 4 +- .../advancement/recipes/bronze_bars_2.json | 4 +- .../advancement/recipes/bronze_blend.json | 4 +- .../recipes/bronze_blend_from_small.json | 4 +- .../advancement/recipes/bronze_block.json | 4 +- .../recipes/bronze_block_ingot.json | 4 +- .../advancement/recipes/bronze_bolt.json | 4 +- .../advancement/recipes/bronze_boots.json | 4 +- .../advancement/recipes/bronze_bow.json | 4 +- .../advancement/recipes/bronze_button.json | 4 +- .../recipes/bronze_chestplate.json | 4 +- .../recipes/bronze_crackhammer.json | 4 +- .../advancement/recipes/bronze_crossbow.json | 4 +- .../advancement/recipes/bronze_door.json | 4 +- .../recipes/bronze_fishingrod.json | 4 +- .../advancement/recipes/bronze_gear.json | 4 +- .../advancement/recipes/bronze_helmet.json | 4 +- .../advancement/recipes/bronze_hoe.json | 4 +- .../recipes/bronze_horsearmor.json | 4 +- .../recipes/bronze_ingot_from_nuggets.json | 4 +- .../advancement/recipes/bronze_leggings.json | 4 +- .../advancement/recipes/bronze_lever.json | 4 +- .../advancement/recipes/bronze_nuggets.json | 4 +- .../advancement/recipes/bronze_pickaxe.json | 4 +- .../advancement/recipes/bronze_plate.json | 4 +- .../recipes/bronze_powder_from_small.json | 4 +- .../recipes/bronze_pressure_plate.json | 4 +- .../advancement/recipes/bronze_rod.json | 4 +- .../advancement/recipes/bronze_scythe.json | 4 +- .../advancement/recipes/bronze_shears.json | 4 +- .../advancement/recipes/bronze_shield.json | 4 +- .../advancement/recipes/bronze_shovel.json | 4 +- .../advancement/recipes/bronze_slab.json | 4 +- .../recipes/bronze_smallblend.json | 4 +- .../recipes/bronze_smallpowder.json | 4 +- .../advancement/recipes/bronze_stairs.json | 4 +- .../advancement/recipes/bronze_sword.json | 4 +- .../advancement/recipes/bronze_trapdoor.json | 4 +- .../advancement/recipes/bronze_wall.json | 4 +- .../advancement/recipes/charcoal_block.json | 4 +- .../recipes/charcoal_block_dust.json | 4 +- .../advancement/recipes/charcoal_ingot.json | 4 +- .../advancement/recipes/charcoal_nugget.json | 4 +- .../recipes/charcoal_powder_from_small.json | 4 +- .../recipes/charcoal_smallpowder.json | 4 +- .../advancement/recipes/coal_block_dust.json | 4 +- .../advancement/recipes/coal_ingot.json | 4 +- .../advancement/recipes/coal_nugget.json | 4 +- .../recipes/coal_powder_from_small.json | 4 +- .../advancement/recipes/coal_smallpowder.json | 4 +- .../advancement/recipes/coldiron_arrow.json | 4 +- .../advancement/recipes/coldiron_axe.json | 4 +- .../advancement/recipes/coldiron_bars.json | 4 +- .../advancement/recipes/coldiron_bars_2.json | 4 +- .../advancement/recipes/coldiron_block.json | 4 +- .../recipes/coldiron_block_ingot.json | 4 +- .../advancement/recipes/coldiron_bolt.json | 4 +- .../advancement/recipes/coldiron_boots.json | 4 +- .../advancement/recipes/coldiron_bow.json | 4 +- .../advancement/recipes/coldiron_button.json | 4 +- .../recipes/coldiron_chestplate.json | 4 +- .../recipes/coldiron_crackhammer.json | 4 +- .../recipes/coldiron_crossbow.json | 4 +- .../advancement/recipes/coldiron_door.json | 4 +- .../recipes/coldiron_fishingrod.json | 4 +- .../advancement/recipes/coldiron_gear.json | 4 +- .../advancement/recipes/coldiron_helmet.json | 4 +- .../advancement/recipes/coldiron_hoe.json | 4 +- .../recipes/coldiron_horsearmor.json | 4 +- .../recipes/coldiron_ingot_from_nuggets.json | 4 +- .../recipes/coldiron_leggings.json | 4 +- .../advancement/recipes/coldiron_lever.json | 4 +- .../advancement/recipes/coldiron_nuggets.json | 4 +- .../advancement/recipes/coldiron_pickaxe.json | 4 +- .../advancement/recipes/coldiron_plate.json | 4 +- .../recipes/coldiron_powder_from_small.json | 4 +- .../recipes/coldiron_pressure_plate.json | 4 +- .../recipes/coldiron_raw_blasting.json | 4 +- .../recipes/coldiron_raw_smelting.json | 4 +- .../advancement/recipes/coldiron_rod.json | 4 +- .../advancement/recipes/coldiron_scythe.json | 4 +- .../advancement/recipes/coldiron_shears.json | 4 +- .../advancement/recipes/coldiron_shield.json | 4 +- .../advancement/recipes/coldiron_shovel.json | 4 +- .../advancement/recipes/coldiron_slab.json | 4 +- .../recipes/coldiron_smallpowder.json | 4 +- .../advancement/recipes/coldiron_stairs.json | 4 +- .../advancement/recipes/coldiron_sword.json | 4 +- .../recipes/coldiron_trapdoor.json | 4 +- .../advancement/recipes/coldiron_wall.json | 4 +- .../advancement/recipes/copper_arrow.json | 4 +- .../advancement/recipes/copper_axe.json | 4 +- .../advancement/recipes/copper_bars.json | 4 +- .../advancement/recipes/copper_bars_2.json | 4 +- .../advancement/recipes/copper_block.json | 4 +- .../recipes/copper_block_ingot.json | 4 +- .../advancement/recipes/copper_bolt.json | 4 +- .../advancement/recipes/copper_boots.json | 4 +- .../advancement/recipes/copper_bow.json | 4 +- .../advancement/recipes/copper_button.json | 4 +- .../recipes/copper_chestplate.json | 4 +- .../recipes/copper_crackhammer.json | 4 +- .../advancement/recipes/copper_crossbow.json | 4 +- .../advancement/recipes/copper_door.json | 4 +- .../recipes/copper_fishingrod.json | 4 +- .../advancement/recipes/copper_gear.json | 4 +- .../advancement/recipes/copper_helmet.json | 4 +- .../advancement/recipes/copper_hoe.json | 4 +- .../recipes/copper_horsearmor.json | 4 +- .../recipes/copper_ingot_from_nuggets.json | 4 +- .../advancement/recipes/copper_leggings.json | 4 +- .../advancement/recipes/copper_lever.json | 4 +- .../advancement/recipes/copper_nuggets.json | 4 +- .../advancement/recipes/copper_pickaxe.json | 4 +- .../advancement/recipes/copper_plate.json | 4 +- .../recipes/copper_powder_from_small.json | 4 +- .../recipes/copper_pressure_plate.json | 4 +- .../advancement/recipes/copper_rod.json | 4 +- .../advancement/recipes/copper_scythe.json | 4 +- .../advancement/recipes/copper_shears.json | 4 +- .../advancement/recipes/copper_shield.json | 4 +- .../advancement/recipes/copper_shovel.json | 4 +- .../advancement/recipes/copper_slab.json | 4 +- .../recipes/copper_smallpowder.json | 4 +- .../advancement/recipes/copper_stairs.json | 4 +- .../advancement/recipes/copper_sword.json | 4 +- .../advancement/recipes/copper_trapdoor.json | 4 +- .../advancement/recipes/copper_wall.json | 4 +- .../recipes/cupronickel_arrow.json | 4 +- .../advancement/recipes/cupronickel_axe.json | 4 +- .../advancement/recipes/cupronickel_bars.json | 4 +- .../recipes/cupronickel_bars_2.json | 4 +- .../recipes/cupronickel_blend.json | 4 +- .../recipes/cupronickel_blend_from_small.json | 4 +- .../recipes/cupronickel_block.json | 4 +- .../recipes/cupronickel_block_ingot.json | 4 +- .../advancement/recipes/cupronickel_bolt.json | 4 +- .../recipes/cupronickel_boots.json | 4 +- .../advancement/recipes/cupronickel_bow.json | 4 +- .../recipes/cupronickel_button.json | 4 +- .../recipes/cupronickel_chestplate.json | 4 +- .../recipes/cupronickel_crackhammer.json | 4 +- .../recipes/cupronickel_crossbow.json | 4 +- .../advancement/recipes/cupronickel_door.json | 4 +- .../recipes/cupronickel_fishingrod.json | 4 +- .../advancement/recipes/cupronickel_gear.json | 4 +- .../recipes/cupronickel_helmet.json | 4 +- .../advancement/recipes/cupronickel_hoe.json | 4 +- .../recipes/cupronickel_horsearmor.json | 4 +- .../cupronickel_ingot_from_nuggets.json | 4 +- .../recipes/cupronickel_leggings.json | 4 +- .../recipes/cupronickel_lever.json | 4 +- .../recipes/cupronickel_nuggets.json | 4 +- .../recipes/cupronickel_pickaxe.json | 4 +- .../recipes/cupronickel_plate.json | 4 +- .../cupronickel_powder_from_small.json | 4 +- .../recipes/cupronickel_pressure_plate.json | 4 +- .../advancement/recipes/cupronickel_rod.json | 4 +- .../recipes/cupronickel_scythe.json | 4 +- .../recipes/cupronickel_shears.json | 4 +- .../recipes/cupronickel_shield.json | 4 +- .../recipes/cupronickel_shovel.json | 4 +- .../advancement/recipes/cupronickel_slab.json | 4 +- .../recipes/cupronickel_smallblend.json | 4 +- .../recipes/cupronickel_smallpowder.json | 4 +- .../recipes/cupronickel_stairs.json | 4 +- .../recipes/cupronickel_sword.json | 4 +- .../recipes/cupronickel_trapdoor.json | 4 +- .../advancement/recipes/cupronickel_wall.json | 4 +- .../advancement/recipes/detector_rail.json | 4 +- .../advancement/recipes/diamond_arrow.json | 4 +- .../advancement/recipes/diamond_bars.json | 4 +- .../advancement/recipes/diamond_bars_2.json | 4 +- .../advancement/recipes/diamond_bolt.json | 4 +- .../advancement/recipes/diamond_bow.json | 4 +- .../advancement/recipes/diamond_button.json | 4 +- .../recipes/diamond_crackhammer.json | 4 +- .../advancement/recipes/diamond_crossbow.json | 4 +- .../advancement/recipes/diamond_door.json | 4 +- .../recipes/diamond_fishingrod.json | 4 +- .../advancement/recipes/diamond_gear.json | 4 +- .../advancement/recipes/diamond_lever.json | 4 +- .../recipes/diamond_powder_from_small.json | 4 +- .../recipes/diamond_pressure_plate.json | 4 +- .../advancement/recipes/diamond_rod.json | 4 +- .../advancement/recipes/diamond_scythe.json | 4 +- .../advancement/recipes/diamond_shears.json | 4 +- .../advancement/recipes/diamond_shield.json | 4 +- .../advancement/recipes/diamond_slab.json | 4 +- .../recipes/diamond_smallpowder.json | 4 +- .../advancement/recipes/diamond_stairs.json | 4 +- .../advancement/recipes/diamond_trapdoor.json | 4 +- .../advancement/recipes/diamond_wall.json | 4 +- .../advancement/recipes/electrum_arrow.json | 4 +- .../advancement/recipes/electrum_axe.json | 4 +- .../advancement/recipes/electrum_bars.json | 4 +- .../advancement/recipes/electrum_bars_2.json | 4 +- .../advancement/recipes/electrum_blend.json | 4 +- .../recipes/electrum_blend_from_small.json | 4 +- .../advancement/recipes/electrum_block.json | 4 +- .../recipes/electrum_block_ingot.json | 4 +- .../advancement/recipes/electrum_bolt.json | 4 +- .../advancement/recipes/electrum_boots.json | 4 +- .../advancement/recipes/electrum_bow.json | 4 +- .../advancement/recipes/electrum_button.json | 4 +- .../recipes/electrum_chestplate.json | 4 +- .../recipes/electrum_crackhammer.json | 4 +- .../recipes/electrum_crossbow.json | 4 +- .../advancement/recipes/electrum_door.json | 4 +- .../recipes/electrum_fishingrod.json | 4 +- .../advancement/recipes/electrum_gear.json | 4 +- .../advancement/recipes/electrum_helmet.json | 4 +- .../advancement/recipes/electrum_hoe.json | 4 +- .../recipes/electrum_horsearmor.json | 4 +- .../recipes/electrum_ingot_from_nuggets.json | 4 +- .../recipes/electrum_leggings.json | 4 +- .../advancement/recipes/electrum_lever.json | 4 +- .../advancement/recipes/electrum_nuggets.json | 4 +- .../advancement/recipes/electrum_pickaxe.json | 4 +- .../advancement/recipes/electrum_plate.json | 4 +- .../recipes/electrum_powder_from_small.json | 4 +- .../recipes/electrum_pressure_plate.json | 4 +- .../advancement/recipes/electrum_rod.json | 4 +- .../advancement/recipes/electrum_scythe.json | 4 +- .../advancement/recipes/electrum_shears.json | 4 +- .../advancement/recipes/electrum_shield.json | 4 +- .../advancement/recipes/electrum_shovel.json | 4 +- .../advancement/recipes/electrum_slab.json | 4 +- .../recipes/electrum_smallblend.json | 4 +- .../recipes/electrum_smallpowder.json | 4 +- .../advancement/recipes/electrum_stairs.json | 4 +- .../advancement/recipes/electrum_sword.json | 4 +- .../recipes/electrum_trapdoor.json | 4 +- .../advancement/recipes/electrum_wall.json | 4 +- .../advancement/recipes/emerald_arrow.json | 4 +- .../advancement/recipes/emerald_axe.json | 4 +- .../advancement/recipes/emerald_bars.json | 4 +- .../advancement/recipes/emerald_bars_2.json | 4 +- .../advancement/recipes/emerald_bolt.json | 4 +- .../advancement/recipes/emerald_boots.json | 4 +- .../advancement/recipes/emerald_bow.json | 4 +- .../advancement/recipes/emerald_button.json | 4 +- .../recipes/emerald_chestplate.json | 4 +- .../recipes/emerald_crackhammer.json | 4 +- .../advancement/recipes/emerald_crossbow.json | 4 +- .../advancement/recipes/emerald_door.json | 4 +- .../recipes/emerald_fishingrod.json | 4 +- .../advancement/recipes/emerald_gear.json | 4 +- .../advancement/recipes/emerald_helmet.json | 4 +- .../advancement/recipes/emerald_hoe.json | 4 +- .../recipes/emerald_horsearmor.json | 4 +- .../advancement/recipes/emerald_leggings.json | 4 +- .../advancement/recipes/emerald_lever.json | 4 +- .../advancement/recipes/emerald_pickaxe.json | 4 +- .../recipes/emerald_powder_from_small.json | 4 +- .../recipes/emerald_pressure_plate.json | 4 +- .../advancement/recipes/emerald_rod.json | 4 +- .../advancement/recipes/emerald_scythe.json | 4 +- .../advancement/recipes/emerald_shears.json | 4 +- .../advancement/recipes/emerald_shield.json | 4 +- .../advancement/recipes/emerald_shovel.json | 4 +- .../advancement/recipes/emerald_slab.json | 4 +- .../recipes/emerald_smallpowder.json | 4 +- .../advancement/recipes/emerald_stairs.json | 4 +- .../advancement/recipes/emerald_sword.json | 4 +- .../advancement/recipes/emerald_trapdoor.json | 4 +- .../advancement/recipes/emerald_wall.json | 4 +- .../advancement/recipes/flint_and_steel.json | 4 +- .../advancement/recipes/gold_arrow.json | 4 +- .../advancement/recipes/gold_bars.json | 4 +- .../advancement/recipes/gold_bars_2.json | 4 +- .../advancement/recipes/gold_bolt.json | 4 +- .../advancement/recipes/gold_bow.json | 4 +- .../advancement/recipes/gold_button.json | 4 +- .../advancement/recipes/gold_crackhammer.json | 4 +- .../advancement/recipes/gold_crossbow.json | 4 +- .../advancement/recipes/gold_door.json | 4 +- .../advancement/recipes/gold_fishingrod.json | 4 +- .../advancement/recipes/gold_gear.json | 4 +- .../advancement/recipes/gold_lever.json | 4 +- .../advancement/recipes/gold_plate.json | 4 +- .../recipes/gold_powder_from_small.json | 4 +- .../advancement/recipes/gold_rod.json | 4 +- .../advancement/recipes/gold_scythe.json | 4 +- .../advancement/recipes/gold_shears.json | 4 +- .../advancement/recipes/gold_shield.json | 4 +- .../advancement/recipes/gold_slab.json | 4 +- .../advancement/recipes/gold_smallpowder.json | 4 +- .../advancement/recipes/gold_stairs.json | 4 +- .../advancement/recipes/gold_trapdoor.json | 4 +- .../advancement/recipes/gold_wall.json | 4 +- .../advancement/recipes/human_detector.json | 4 +- .../advancement/recipes/invar_arrow.json | 4 +- .../advancement/recipes/invar_axe.json | 4 +- .../advancement/recipes/invar_bars.json | 4 +- .../advancement/recipes/invar_bars_2.json | 4 +- .../advancement/recipes/invar_blend.json | 4 +- .../recipes/invar_blend_from_small.json | 4 +- .../advancement/recipes/invar_block.json | 4 +- .../recipes/invar_block_ingot.json | 4 +- .../advancement/recipes/invar_bolt.json | 4 +- .../advancement/recipes/invar_boots.json | 4 +- .../advancement/recipes/invar_bow.json | 4 +- .../advancement/recipes/invar_button.json | 4 +- .../advancement/recipes/invar_chestplate.json | 4 +- .../recipes/invar_crackhammer.json | 4 +- .../advancement/recipes/invar_crossbow.json | 4 +- .../advancement/recipes/invar_door.json | 4 +- .../advancement/recipes/invar_fishingrod.json | 4 +- .../advancement/recipes/invar_gear.json | 4 +- .../advancement/recipes/invar_helmet.json | 4 +- .../advancement/recipes/invar_hoe.json | 4 +- .../advancement/recipes/invar_horsearmor.json | 4 +- .../recipes/invar_ingot_from_nuggets.json | 4 +- .../advancement/recipes/invar_leggings.json | 4 +- .../advancement/recipes/invar_lever.json | 4 +- .../advancement/recipes/invar_nuggets.json | 4 +- .../advancement/recipes/invar_pickaxe.json | 4 +- .../advancement/recipes/invar_plate.json | 4 +- .../recipes/invar_powder_from_small.json | 4 +- .../recipes/invar_pressure_plate.json | 4 +- .../advancement/recipes/invar_rod.json | 4 +- .../advancement/recipes/invar_scythe.json | 4 +- .../advancement/recipes/invar_shears.json | 4 +- .../advancement/recipes/invar_shield.json | 4 +- .../advancement/recipes/invar_shovel.json | 4 +- .../advancement/recipes/invar_slab.json | 4 +- .../advancement/recipes/invar_smallblend.json | 4 +- .../recipes/invar_smallpowder.json | 4 +- .../advancement/recipes/invar_stairs.json | 4 +- .../advancement/recipes/invar_sword.json | 4 +- .../advancement/recipes/invar_trapdoor.json | 4 +- .../advancement/recipes/invar_wall.json | 4 +- .../advancement/recipes/iron_arrow.json | 4 +- .../advancement/recipes/iron_bars_2.json | 4 +- .../advancement/recipes/iron_bolt.json | 4 +- .../advancement/recipes/iron_bow.json | 4 +- .../advancement/recipes/iron_button.json | 4 +- .../advancement/recipes/iron_crackhammer.json | 4 +- .../advancement/recipes/iron_crossbow.json | 4 +- .../advancement/recipes/iron_fishingrod.json | 4 +- .../advancement/recipes/iron_gear.json | 4 +- .../advancement/recipes/iron_lever.json | 4 +- .../advancement/recipes/iron_plate.json | 4 +- .../recipes/iron_powder_from_small.json | 4 +- .../advancement/recipes/iron_rod.json | 4 +- .../advancement/recipes/iron_scythe.json | 4 +- .../advancement/recipes/iron_shield.json | 4 +- .../advancement/recipes/iron_slab.json | 4 +- .../advancement/recipes/iron_smallpowder.json | 4 +- .../advancement/recipes/iron_stairs.json | 4 +- .../advancement/recipes/iron_wall.json | 4 +- .../advancement/recipes/lead_arrow.json | 4 +- .../advancement/recipes/lead_axe.json | 4 +- .../advancement/recipes/lead_bars.json | 4 +- .../advancement/recipes/lead_bars_2.json | 4 +- .../advancement/recipes/lead_block.json | 4 +- .../advancement/recipes/lead_block_ingot.json | 4 +- .../advancement/recipes/lead_bolt.json | 4 +- .../advancement/recipes/lead_boots.json | 4 +- .../advancement/recipes/lead_bow.json | 4 +- .../advancement/recipes/lead_button.json | 4 +- .../advancement/recipes/lead_chestplate.json | 4 +- .../advancement/recipes/lead_crackhammer.json | 4 +- .../advancement/recipes/lead_crossbow.json | 4 +- .../advancement/recipes/lead_door.json | 4 +- .../advancement/recipes/lead_fishingrod.json | 4 +- .../advancement/recipes/lead_gear.json | 4 +- .../advancement/recipes/lead_helmet.json | 4 +- .../advancement/recipes/lead_hoe.json | 4 +- .../advancement/recipes/lead_horsearmor.json | 4 +- .../recipes/lead_ingot_from_nuggets.json | 4 +- .../advancement/recipes/lead_leggings.json | 4 +- .../advancement/recipes/lead_lever.json | 4 +- .../advancement/recipes/lead_nuggets.json | 4 +- .../advancement/recipes/lead_pickaxe.json | 4 +- .../advancement/recipes/lead_plate.json | 4 +- .../recipes/lead_powder_from_small.json | 4 +- .../recipes/lead_pressure_plate.json | 4 +- .../recipes/lead_raw_blasting.json | 4 +- .../recipes/lead_raw_smelting.json | 4 +- .../advancement/recipes/lead_rod.json | 4 +- .../advancement/recipes/lead_scythe.json | 4 +- .../advancement/recipes/lead_shears.json | 4 +- .../advancement/recipes/lead_shield.json | 4 +- .../advancement/recipes/lead_shovel.json | 4 +- .../advancement/recipes/lead_slab.json | 4 +- .../advancement/recipes/lead_smallpowder.json | 4 +- .../advancement/recipes/lead_stairs.json | 4 +- .../advancement/recipes/lead_sword.json | 4 +- .../advancement/recipes/lead_trapdoor.json | 4 +- .../advancement/recipes/lead_wall.json | 4 +- .../recipes/mercury_ingot_from_nuggets.json | 4 +- .../advancement/recipes/mercury_nuggets.json | 4 +- .../recipes/mercury_powder_from_small.json | 4 +- .../recipes/mercury_raw_blasting.json | 4 +- .../recipes/mercury_raw_smelting.json | 4 +- .../recipes/mercury_smallpowder.json | 4 +- .../advancement/recipes/minecart.json | 4 +- .../advancement/recipes/mithril_arrow.json | 4 +- .../advancement/recipes/mithril_axe.json | 4 +- .../advancement/recipes/mithril_bars.json | 4 +- .../advancement/recipes/mithril_bars_2.json | 4 +- .../advancement/recipes/mithril_blend.json | 4 +- .../recipes/mithril_blend_from_small.json | 4 +- .../advancement/recipes/mithril_block.json | 4 +- .../recipes/mithril_block_ingot.json | 4 +- .../advancement/recipes/mithril_bolt.json | 4 +- .../advancement/recipes/mithril_boots.json | 4 +- .../advancement/recipes/mithril_bow.json | 4 +- .../advancement/recipes/mithril_button.json | 4 +- .../recipes/mithril_chestplate.json | 4 +- .../recipes/mithril_crackhammer.json | 4 +- .../advancement/recipes/mithril_crossbow.json | 4 +- .../advancement/recipes/mithril_door.json | 4 +- .../recipes/mithril_fishingrod.json | 4 +- .../advancement/recipes/mithril_gear.json | 4 +- .../advancement/recipes/mithril_helmet.json | 4 +- .../advancement/recipes/mithril_hoe.json | 4 +- .../recipes/mithril_horsearmor.json | 4 +- .../recipes/mithril_ingot_from_nuggets.json | 4 +- .../advancement/recipes/mithril_leggings.json | 4 +- .../advancement/recipes/mithril_lever.json | 4 +- .../advancement/recipes/mithril_nuggets.json | 4 +- .../advancement/recipes/mithril_pickaxe.json | 4 +- .../advancement/recipes/mithril_plate.json | 4 +- .../recipes/mithril_powder_from_small.json | 4 +- .../recipes/mithril_pressure_plate.json | 4 +- .../advancement/recipes/mithril_rod.json | 4 +- .../advancement/recipes/mithril_scythe.json | 4 +- .../advancement/recipes/mithril_shears.json | 4 +- .../advancement/recipes/mithril_shield.json | 4 +- .../advancement/recipes/mithril_shovel.json | 4 +- .../advancement/recipes/mithril_slab.json | 4 +- .../recipes/mithril_smallblend.json | 4 +- .../recipes/mithril_smallpowder.json | 4 +- .../advancement/recipes/mithril_stairs.json | 4 +- .../advancement/recipes/mithril_sword.json | 4 +- .../advancement/recipes/mithril_trapdoor.json | 4 +- .../advancement/recipes/mithril_wall.json | 4 +- .../advancement/recipes/nickel_arrow.json | 4 +- .../advancement/recipes/nickel_axe.json | 4 +- .../advancement/recipes/nickel_bars.json | 4 +- .../advancement/recipes/nickel_bars_2.json | 4 +- .../advancement/recipes/nickel_block.json | 4 +- .../recipes/nickel_block_ingot.json | 4 +- .../advancement/recipes/nickel_bolt.json | 4 +- .../advancement/recipes/nickel_boots.json | 4 +- .../advancement/recipes/nickel_bow.json | 4 +- .../advancement/recipes/nickel_button.json | 4 +- .../recipes/nickel_chestplate.json | 4 +- .../recipes/nickel_crackhammer.json | 4 +- .../advancement/recipes/nickel_crossbow.json | 4 +- .../advancement/recipes/nickel_door.json | 4 +- .../recipes/nickel_fishingrod.json | 4 +- .../advancement/recipes/nickel_gear.json | 4 +- .../advancement/recipes/nickel_helmet.json | 4 +- .../advancement/recipes/nickel_hoe.json | 4 +- .../recipes/nickel_horsearmor.json | 4 +- .../recipes/nickel_ingot_from_nuggets.json | 4 +- .../advancement/recipes/nickel_leggings.json | 4 +- .../advancement/recipes/nickel_lever.json | 4 +- .../advancement/recipes/nickel_nuggets.json | 4 +- .../advancement/recipes/nickel_pickaxe.json | 4 +- .../advancement/recipes/nickel_plate.json | 4 +- .../recipes/nickel_powder_from_small.json | 4 +- .../recipes/nickel_pressure_plate.json | 4 +- .../recipes/nickel_raw_blasting.json | 4 +- .../recipes/nickel_raw_smelting.json | 4 +- .../advancement/recipes/nickel_rod.json | 4 +- .../advancement/recipes/nickel_scythe.json | 4 +- .../advancement/recipes/nickel_shears.json | 4 +- .../advancement/recipes/nickel_shield.json | 4 +- .../advancement/recipes/nickel_shovel.json | 4 +- .../advancement/recipes/nickel_slab.json | 4 +- .../recipes/nickel_smallpowder.json | 4 +- .../advancement/recipes/nickel_stairs.json | 4 +- .../advancement/recipes/nickel_sword.json | 4 +- .../advancement/recipes/nickel_trapdoor.json | 4 +- .../advancement/recipes/nickel_wall.json | 4 +- .../advancement/recipes/obsidian_arrow.json | 4 +- .../advancement/recipes/obsidian_axe.json | 4 +- .../advancement/recipes/obsidian_bars.json | 4 +- .../advancement/recipes/obsidian_bars_2.json | 4 +- .../advancement/recipes/obsidian_block.json | 4 +- .../advancement/recipes/obsidian_bolt.json | 4 +- .../advancement/recipes/obsidian_boots.json | 4 +- .../advancement/recipes/obsidian_bow.json | 4 +- .../advancement/recipes/obsidian_button.json | 4 +- .../recipes/obsidian_chestplate.json | 4 +- .../recipes/obsidian_crackhammer.json | 4 +- .../recipes/obsidian_crossbow.json | 4 +- .../advancement/recipes/obsidian_door.json | 4 +- .../recipes/obsidian_fishingrod.json | 4 +- .../advancement/recipes/obsidian_gear.json | 4 +- .../advancement/recipes/obsidian_helmet.json | 4 +- .../advancement/recipes/obsidian_hoe.json | 4 +- .../recipes/obsidian_horsearmor.json | 4 +- .../advancement/recipes/obsidian_ingot.json | 4 +- .../recipes/obsidian_leggings.json | 4 +- .../advancement/recipes/obsidian_lever.json | 4 +- .../advancement/recipes/obsidian_nugget.json | 4 +- .../advancement/recipes/obsidian_pickaxe.json | 4 +- .../recipes/obsidian_powder_from_small.json | 4 +- .../recipes/obsidian_pressure_plate.json | 4 +- .../advancement/recipes/obsidian_rod.json | 4 +- .../advancement/recipes/obsidian_scythe.json | 4 +- .../advancement/recipes/obsidian_shears.json | 4 +- .../advancement/recipes/obsidian_shield.json | 4 +- .../advancement/recipes/obsidian_shovel.json | 4 +- .../advancement/recipes/obsidian_slab.json | 4 +- .../recipes/obsidian_smallpowder.json | 4 +- .../advancement/recipes/obsidian_stairs.json | 4 +- .../advancement/recipes/obsidian_sword.json | 4 +- .../recipes/obsidian_trapdoor.json | 4 +- .../advancement/recipes/obsidian_wall.json | 4 +- .../advancement/recipes/pewter_arrow.json | 4 +- .../advancement/recipes/pewter_axe.json | 4 +- .../advancement/recipes/pewter_bars.json | 4 +- .../advancement/recipes/pewter_bars_2.json | 4 +- .../advancement/recipes/pewter_blend.json | 4 +- .../recipes/pewter_blend_from_small.json | 4 +- .../advancement/recipes/pewter_block.json | 4 +- .../recipes/pewter_block_ingot.json | 4 +- .../advancement/recipes/pewter_bolt.json | 4 +- .../advancement/recipes/pewter_boots.json | 4 +- .../advancement/recipes/pewter_bow.json | 4 +- .../advancement/recipes/pewter_button.json | 4 +- .../recipes/pewter_chestplate.json | 4 +- .../recipes/pewter_crackhammer.json | 4 +- .../advancement/recipes/pewter_crossbow.json | 4 +- .../advancement/recipes/pewter_door.json | 4 +- .../recipes/pewter_fishingrod.json | 4 +- .../advancement/recipes/pewter_gear.json | 4 +- .../advancement/recipes/pewter_helmet.json | 4 +- .../advancement/recipes/pewter_hoe.json | 4 +- .../recipes/pewter_horsearmor.json | 4 +- .../recipes/pewter_ingot_from_nuggets.json | 4 +- .../advancement/recipes/pewter_leggings.json | 4 +- .../advancement/recipes/pewter_lever.json | 4 +- .../advancement/recipes/pewter_nuggets.json | 4 +- .../advancement/recipes/pewter_pickaxe.json | 4 +- .../advancement/recipes/pewter_plate.json | 4 +- .../recipes/pewter_powder_from_small.json | 4 +- .../recipes/pewter_pressure_plate.json | 4 +- .../advancement/recipes/pewter_rod.json | 4 +- .../advancement/recipes/pewter_scythe.json | 4 +- .../advancement/recipes/pewter_shears.json | 4 +- .../advancement/recipes/pewter_shield.json | 4 +- .../advancement/recipes/pewter_shovel.json | 4 +- .../advancement/recipes/pewter_slab.json | 4 +- .../recipes/pewter_smallblend.json | 4 +- .../recipes/pewter_smallpowder.json | 4 +- .../advancement/recipes/pewter_stairs.json | 4 +- .../advancement/recipes/pewter_sword.json | 4 +- .../advancement/recipes/pewter_trapdoor.json | 4 +- .../advancement/recipes/pewter_wall.json | 4 +- .../advancement/recipes/piston.json | 4 +- .../advancement/recipes/platinum_arrow.json | 4 +- .../advancement/recipes/platinum_axe.json | 4 +- .../advancement/recipes/platinum_bars.json | 4 +- .../advancement/recipes/platinum_bars_2.json | 4 +- .../advancement/recipes/platinum_block.json | 4 +- .../recipes/platinum_block_ingot.json | 4 +- .../advancement/recipes/platinum_bolt.json | 4 +- .../advancement/recipes/platinum_boots.json | 4 +- .../advancement/recipes/platinum_bow.json | 4 +- .../advancement/recipes/platinum_button.json | 4 +- .../recipes/platinum_chestplate.json | 4 +- .../recipes/platinum_crackhammer.json | 4 +- .../recipes/platinum_crossbow.json | 4 +- .../advancement/recipes/platinum_door.json | 4 +- .../recipes/platinum_fishingrod.json | 4 +- .../advancement/recipes/platinum_gear.json | 4 +- .../advancement/recipes/platinum_helmet.json | 4 +- .../advancement/recipes/platinum_hoe.json | 4 +- .../recipes/platinum_horsearmor.json | 4 +- .../recipes/platinum_ingot_from_nuggets.json | 4 +- .../recipes/platinum_leggings.json | 4 +- .../advancement/recipes/platinum_lever.json | 4 +- .../advancement/recipes/platinum_nuggets.json | 4 +- .../advancement/recipes/platinum_pickaxe.json | 4 +- .../advancement/recipes/platinum_plate.json | 4 +- .../recipes/platinum_powder_from_small.json | 4 +- .../recipes/platinum_pressure_plate.json | 4 +- .../recipes/platinum_raw_blasting.json | 4 +- .../recipes/platinum_raw_smelting.json | 4 +- .../advancement/recipes/platinum_rod.json | 4 +- .../advancement/recipes/platinum_scythe.json | 4 +- .../advancement/recipes/platinum_shears.json | 4 +- .../advancement/recipes/platinum_shield.json | 4 +- .../advancement/recipes/platinum_shovel.json | 4 +- .../advancement/recipes/platinum_slab.json | 4 +- .../recipes/platinum_smallpowder.json | 4 +- .../advancement/recipes/platinum_stairs.json | 4 +- .../advancement/recipes/platinum_sword.json | 4 +- .../recipes/platinum_trapdoor.json | 4 +- .../advancement/recipes/platinum_wall.json | 4 +- .../advancement/recipes/quartz_arrow.json | 4 +- .../advancement/recipes/quartz_axe.json | 4 +- .../advancement/recipes/quartz_bars.json | 4 +- .../advancement/recipes/quartz_bars_2.json | 4 +- .../advancement/recipes/quartz_bolt.json | 4 +- .../advancement/recipes/quartz_boots.json | 4 +- .../advancement/recipes/quartz_bow.json | 4 +- .../advancement/recipes/quartz_button.json | 4 +- .../recipes/quartz_chestplate.json | 4 +- .../recipes/quartz_crackhammer.json | 4 +- .../advancement/recipes/quartz_crossbow.json | 4 +- .../advancement/recipes/quartz_door.json | 4 +- .../recipes/quartz_fishingrod.json | 4 +- .../advancement/recipes/quartz_gear.json | 4 +- .../advancement/recipes/quartz_helmet.json | 4 +- .../advancement/recipes/quartz_hoe.json | 4 +- .../recipes/quartz_horsearmor.json | 4 +- .../advancement/recipes/quartz_ingot.json | 4 +- .../advancement/recipes/quartz_leggings.json | 4 +- .../advancement/recipes/quartz_lever.json | 4 +- .../advancement/recipes/quartz_nugget.json | 4 +- .../advancement/recipes/quartz_pickaxe.json | 4 +- .../recipes/quartz_powder_from_small.json | 4 +- .../recipes/quartz_pressure_plate.json | 4 +- .../advancement/recipes/quartz_rod.json | 4 +- .../advancement/recipes/quartz_scythe.json | 4 +- .../advancement/recipes/quartz_shears.json | 4 +- .../advancement/recipes/quartz_shield.json | 4 +- .../advancement/recipes/quartz_shovel.json | 4 +- .../recipes/quartz_smallpowder.json | 4 +- .../advancement/recipes/quartz_sword.json | 4 +- .../advancement/recipes/quartz_trapdoor.json | 4 +- .../advancement/recipes/quartz_wall.json | 4 +- .../basemetals/advancement/recipes/rail.json | 4 +- .../recipes/redstone_from_smallpowder.json | 4 +- .../recipes/redstone_smallpowder.json | 4 +- .../advancement/recipes/silver_arrow.json | 4 +- .../advancement/recipes/silver_axe.json | 4 +- .../advancement/recipes/silver_bars.json | 4 +- .../advancement/recipes/silver_bars_2.json | 4 +- .../advancement/recipes/silver_block.json | 4 +- .../recipes/silver_block_ingot.json | 4 +- .../advancement/recipes/silver_bolt.json | 4 +- .../advancement/recipes/silver_boots.json | 4 +- .../advancement/recipes/silver_bow.json | 4 +- .../advancement/recipes/silver_button.json | 4 +- .../recipes/silver_chestplate.json | 4 +- .../recipes/silver_crackhammer.json | 4 +- .../advancement/recipes/silver_crossbow.json | 4 +- .../advancement/recipes/silver_door.json | 4 +- .../recipes/silver_fishingrod.json | 4 +- .../advancement/recipes/silver_gear.json | 4 +- .../advancement/recipes/silver_helmet.json | 4 +- .../advancement/recipes/silver_hoe.json | 4 +- .../recipes/silver_horsearmor.json | 4 +- .../recipes/silver_ingot_from_nuggets.json | 4 +- .../advancement/recipes/silver_leggings.json | 4 +- .../advancement/recipes/silver_lever.json | 4 +- .../advancement/recipes/silver_nuggets.json | 4 +- .../advancement/recipes/silver_pickaxe.json | 4 +- .../advancement/recipes/silver_plate.json | 4 +- .../recipes/silver_powder_from_small.json | 4 +- .../recipes/silver_pressure_plate.json | 4 +- .../recipes/silver_raw_blasting.json | 4 +- .../recipes/silver_raw_smelting.json | 4 +- .../advancement/recipes/silver_rod.json | 4 +- .../advancement/recipes/silver_scythe.json | 4 +- .../advancement/recipes/silver_shears.json | 4 +- .../advancement/recipes/silver_shield.json | 4 +- .../advancement/recipes/silver_shovel.json | 4 +- .../advancement/recipes/silver_slab.json | 4 +- .../recipes/silver_smallpowder.json | 4 +- .../advancement/recipes/silver_stairs.json | 4 +- .../advancement/recipes/silver_sword.json | 4 +- .../advancement/recipes/silver_trapdoor.json | 4 +- .../advancement/recipes/silver_wall.json | 4 +- .../advancement/recipes/starsteel_arrow.json | 4 +- .../advancement/recipes/starsteel_axe.json | 4 +- .../advancement/recipes/starsteel_bars.json | 4 +- .../advancement/recipes/starsteel_bars_2.json | 4 +- .../advancement/recipes/starsteel_block.json | 4 +- .../recipes/starsteel_block_ingot.json | 4 +- .../advancement/recipes/starsteel_bolt.json | 4 +- .../advancement/recipes/starsteel_boots.json | 4 +- .../advancement/recipes/starsteel_bow.json | 4 +- .../advancement/recipes/starsteel_button.json | 4 +- .../recipes/starsteel_chestplate.json | 4 +- .../recipes/starsteel_crackhammer.json | 4 +- .../recipes/starsteel_crossbow.json | 4 +- .../advancement/recipes/starsteel_door.json | 4 +- .../recipes/starsteel_fishingrod.json | 4 +- .../advancement/recipes/starsteel_gear.json | 4 +- .../advancement/recipes/starsteel_helmet.json | 4 +- .../advancement/recipes/starsteel_hoe.json | 4 +- .../recipes/starsteel_horsearmor.json | 4 +- .../recipes/starsteel_ingot_from_nuggets.json | 4 +- .../recipes/starsteel_leggings.json | 4 +- .../advancement/recipes/starsteel_lever.json | 4 +- .../recipes/starsteel_nuggets.json | 4 +- .../recipes/starsteel_pickaxe.json | 4 +- .../advancement/recipes/starsteel_plate.json | 4 +- .../recipes/starsteel_powder_from_small.json | 4 +- .../recipes/starsteel_pressure_plate.json | 4 +- .../recipes/starsteel_raw_blasting.json | 4 +- .../recipes/starsteel_raw_smelting.json | 4 +- .../advancement/recipes/starsteel_rod.json | 4 +- .../advancement/recipes/starsteel_scythe.json | 4 +- .../advancement/recipes/starsteel_shears.json | 4 +- .../advancement/recipes/starsteel_shield.json | 4 +- .../advancement/recipes/starsteel_shovel.json | 4 +- .../advancement/recipes/starsteel_slab.json | 4 +- .../recipes/starsteel_smallpowder.json | 4 +- .../advancement/recipes/starsteel_stairs.json | 4 +- .../advancement/recipes/starsteel_sword.json | 4 +- .../recipes/starsteel_trapdoor.json | 4 +- .../advancement/recipes/starsteel_wall.json | 4 +- .../advancement/recipes/steel_anvil.json | 4 +- .../advancement/recipes/steel_arrow.json | 4 +- .../advancement/recipes/steel_axe.json | 4 +- .../advancement/recipes/steel_bars.json | 4 +- .../advancement/recipes/steel_bars_2.json | 4 +- .../advancement/recipes/steel_blend.json | 4 +- .../recipes/steel_blend_from_small.json | 4 +- .../advancement/recipes/steel_block.json | 4 +- .../recipes/steel_block_ingot.json | 4 +- .../advancement/recipes/steel_bolt.json | 4 +- .../advancement/recipes/steel_boots.json | 4 +- .../advancement/recipes/steel_bow.json | 4 +- .../advancement/recipes/steel_button.json | 4 +- .../advancement/recipes/steel_chestplate.json | 4 +- .../recipes/steel_crackhammer.json | 4 +- .../advancement/recipes/steel_crossbow.json | 4 +- .../advancement/recipes/steel_door.json | 4 +- .../advancement/recipes/steel_fishingrod.json | 4 +- .../advancement/recipes/steel_gear.json | 4 +- .../advancement/recipes/steel_helmet.json | 4 +- .../advancement/recipes/steel_hoe.json | 4 +- .../advancement/recipes/steel_horsearmor.json | 4 +- .../recipes/steel_ingot_from_nuggets.json | 4 +- .../advancement/recipes/steel_leggings.json | 4 +- .../advancement/recipes/steel_lever.json | 4 +- .../advancement/recipes/steel_nuggets.json | 4 +- .../advancement/recipes/steel_pickaxe.json | 4 +- .../advancement/recipes/steel_plate.json | 4 +- .../recipes/steel_powder_from_small.json | 4 +- .../recipes/steel_pressure_plate.json | 4 +- .../advancement/recipes/steel_rod.json | 4 +- .../advancement/recipes/steel_scythe.json | 4 +- .../advancement/recipes/steel_shears.json | 4 +- .../advancement/recipes/steel_shield.json | 4 +- .../advancement/recipes/steel_shovel.json | 4 +- .../advancement/recipes/steel_slab.json | 4 +- .../advancement/recipes/steel_smallblend.json | 4 +- .../recipes/steel_smallpowder.json | 4 +- .../advancement/recipes/steel_stairs.json | 4 +- .../advancement/recipes/steel_sword.json | 4 +- .../advancement/recipes/steel_trapdoor.json | 4 +- .../advancement/recipes/steel_wall.json | 4 +- .../advancement/recipes/stone_anvil.json | 4 +- .../recipes/stone_crackhammer.json | 4 +- .../advancement/recipes/stone_scythe.json | 4 +- .../advancement/recipes/tin_arrow.json | 4 +- .../advancement/recipes/tin_axe.json | 4 +- .../advancement/recipes/tin_bars.json | 4 +- .../advancement/recipes/tin_bars_2.json | 4 +- .../advancement/recipes/tin_block.json | 4 +- .../advancement/recipes/tin_block_ingot.json | 4 +- .../advancement/recipes/tin_bolt.json | 4 +- .../advancement/recipes/tin_boots.json | 4 +- .../advancement/recipes/tin_bow.json | 4 +- .../advancement/recipes/tin_button.json | 4 +- .../advancement/recipes/tin_chestplate.json | 4 +- .../advancement/recipes/tin_crackhammer.json | 4 +- .../advancement/recipes/tin_crossbow.json | 4 +- .../advancement/recipes/tin_door.json | 4 +- .../advancement/recipes/tin_fishingrod.json | 4 +- .../advancement/recipes/tin_gear.json | 4 +- .../advancement/recipes/tin_helmet.json | 4 +- .../advancement/recipes/tin_hoe.json | 4 +- .../advancement/recipes/tin_horsearmor.json | 4 +- .../recipes/tin_ingot_from_nuggets.json | 4 +- .../advancement/recipes/tin_leggings.json | 4 +- .../advancement/recipes/tin_lever.json | 4 +- .../advancement/recipes/tin_nuggets.json | 4 +- .../advancement/recipes/tin_pickaxe.json | 4 +- .../advancement/recipes/tin_plate.json | 4 +- .../recipes/tin_powder_from_small.json | 4 +- .../recipes/tin_pressure_plate.json | 4 +- .../advancement/recipes/tin_raw_blasting.json | 4 +- .../advancement/recipes/tin_raw_smelting.json | 4 +- .../advancement/recipes/tin_rod.json | 4 +- .../advancement/recipes/tin_scythe.json | 4 +- .../advancement/recipes/tin_shears.json | 4 +- .../advancement/recipes/tin_shield.json | 4 +- .../advancement/recipes/tin_shovel.json | 4 +- .../advancement/recipes/tin_slab.json | 4 +- .../advancement/recipes/tin_smallpowder.json | 4 +- .../advancement/recipes/tin_stairs.json | 4 +- .../advancement/recipes/tin_sword.json | 4 +- .../advancement/recipes/tin_trapdoor.json | 4 +- .../advancement/recipes/tin_wall.json | 4 +- .../advancement/recipes/tripwire_hook.json | 4 +- .../advancement/recipes/wood_crackhammer.json | 4 +- .../advancement/recipes/wood_scythe.json | 4 +- .../advancement/recipes/zinc_arrow.json | 4 +- .../advancement/recipes/zinc_axe.json | 4 +- .../advancement/recipes/zinc_bars.json | 4 +- .../advancement/recipes/zinc_bars_2.json | 4 +- .../advancement/recipes/zinc_block.json | 4 +- .../advancement/recipes/zinc_block_ingot.json | 4 +- .../advancement/recipes/zinc_bolt.json | 4 +- .../advancement/recipes/zinc_boots.json | 4 +- .../advancement/recipes/zinc_bow.json | 4 +- .../advancement/recipes/zinc_button.json | 4 +- .../advancement/recipes/zinc_chestplate.json | 4 +- .../advancement/recipes/zinc_crackhammer.json | 4 +- .../advancement/recipes/zinc_crossbow.json | 4 +- .../advancement/recipes/zinc_door.json | 4 +- .../advancement/recipes/zinc_fishingrod.json | 4 +- .../advancement/recipes/zinc_gear.json | 4 +- .../advancement/recipes/zinc_helmet.json | 4 +- .../advancement/recipes/zinc_hoe.json | 4 +- .../advancement/recipes/zinc_horsearmor.json | 4 +- .../recipes/zinc_ingot_from_nuggets.json | 4 +- .../advancement/recipes/zinc_leggings.json | 4 +- .../advancement/recipes/zinc_lever.json | 4 +- .../advancement/recipes/zinc_nuggets.json | 4 +- .../advancement/recipes/zinc_pickaxe.json | 4 +- .../advancement/recipes/zinc_plate.json | 4 +- .../recipes/zinc_powder_from_small.json | 4 +- .../recipes/zinc_pressure_plate.json | 4 +- .../recipes/zinc_raw_blasting.json | 4 +- .../recipes/zinc_raw_smelting.json | 4 +- .../advancement/recipes/zinc_rod.json | 4 +- .../advancement/recipes/zinc_scythe.json | 4 +- .../advancement/recipes/zinc_shears.json | 4 +- .../advancement/recipes/zinc_shield.json | 4 +- .../advancement/recipes/zinc_shovel.json | 4 +- .../advancement/recipes/zinc_slab.json | 4 +- .../advancement/recipes/zinc_smallpowder.json | 4 +- .../advancement/recipes/zinc_stairs.json | 4 +- .../advancement/recipes/zinc_sword.json | 4 +- .../advancement/recipes/zinc_trapdoor.json | 4 +- .../advancement/recipes/zinc_wall.json | 4 +- .../loot_modifiers/abandoned_mineshaft.json | 2 +- .../loot_modifiers/desert_pyramid.json | 2 +- .../loot_modifiers/end_city_treasure.json | 2 +- .../loot_modifiers/jungle_temple.json | 2 +- .../loot_modifiers/nether_bridge.json | 2 +- .../loot_modifiers/simple_dungeon.json | 2 +- .../loot_modifiers/spawn_bonus_chest.json | 2 +- .../loot_modifiers/stronghold_corridor.json | 2 +- .../loot_modifiers/stronghold_crossing.json | 2 +- .../loot_modifiers/village_armorer.json | 2 +- .../loot_modifiers/village_toolsmith.json | 2 +- .../loot_modifiers/village_weaponsmith.json | 2 +- .../loot_table/blocks/adamantine_anvil.json | 12 +- .../loot_table/blocks/adamantine_bars.json | 10 +- .../loot_table/blocks/adamantine_block.json | 10 +- .../loot_table/blocks/adamantine_button.json | 10 +- .../loot_table/blocks/adamantine_door.json | 27 +- .../loot_table/blocks/adamantine_lever.json | 10 +- .../loot_table/blocks/adamantine_ore.json | 34 +- .../loot_table/blocks/adamantine_plate.json | 10 +- .../blocks/adamantine_pressure_plate.json | 10 +- .../loot_table/blocks/adamantine_slab.json | 28 +- .../loot_table/blocks/adamantine_stairs.json | 10 +- .../blocks/adamantine_trapdoor.json | 10 +- .../loot_table/blocks/adamantine_wall.json | 10 +- .../loot_table/blocks/antimony_bars.json | 10 +- .../loot_table/blocks/antimony_block.json | 10 +- .../loot_table/blocks/antimony_button.json | 10 +- .../loot_table/blocks/antimony_door.json | 27 +- .../loot_table/blocks/antimony_lever.json | 10 +- .../loot_table/blocks/antimony_ore.json | 34 +- .../loot_table/blocks/antimony_plate.json | 10 +- .../blocks/antimony_pressure_plate.json | 10 +- .../loot_table/blocks/antimony_slab.json | 28 +- .../loot_table/blocks/antimony_stairs.json | 10 +- .../loot_table/blocks/antimony_trapdoor.json | 10 +- .../loot_table/blocks/antimony_wall.json | 10 +- .../loot_table/blocks/aquarium_bars.json | 10 +- .../loot_table/blocks/aquarium_block.json | 10 +- .../loot_table/blocks/aquarium_button.json | 10 +- .../loot_table/blocks/aquarium_door.json | 27 +- .../loot_table/blocks/aquarium_lever.json | 10 +- .../loot_table/blocks/aquarium_plate.json | 10 +- .../blocks/aquarium_pressure_plate.json | 10 +- .../loot_table/blocks/aquarium_slab.json | 28 +- .../loot_table/blocks/aquarium_stairs.json | 10 +- .../loot_table/blocks/aquarium_trapdoor.json | 10 +- .../loot_table/blocks/aquarium_wall.json | 10 +- .../loot_table/blocks/bismuth_bars.json | 10 +- .../loot_table/blocks/bismuth_block.json | 10 +- .../loot_table/blocks/bismuth_button.json | 10 +- .../loot_table/blocks/bismuth_door.json | 27 +- .../loot_table/blocks/bismuth_lever.json | 10 +- .../loot_table/blocks/bismuth_ore.json | 34 +- .../loot_table/blocks/bismuth_plate.json | 10 +- .../blocks/bismuth_pressure_plate.json | 10 +- .../loot_table/blocks/bismuth_slab.json | 28 +- .../loot_table/blocks/bismuth_stairs.json | 10 +- .../loot_table/blocks/bismuth_trapdoor.json | 10 +- .../loot_table/blocks/bismuth_wall.json | 10 +- .../loot_table/blocks/brass_bars.json | 10 +- .../loot_table/blocks/brass_block.json | 10 +- .../loot_table/blocks/brass_button.json | 10 +- .../loot_table/blocks/brass_door.json | 27 +- .../loot_table/blocks/brass_lever.json | 10 +- .../loot_table/blocks/brass_plate.json | 10 +- .../blocks/brass_pressure_plate.json | 10 +- .../loot_table/blocks/brass_slab.json | 28 +- .../loot_table/blocks/brass_stairs.json | 10 +- .../loot_table/blocks/brass_trapdoor.json | 10 +- .../loot_table/blocks/brass_wall.json | 10 +- .../loot_table/blocks/bronze_bars.json | 10 +- .../loot_table/blocks/bronze_block.json | 10 +- .../loot_table/blocks/bronze_button.json | 10 +- .../loot_table/blocks/bronze_door.json | 27 +- .../loot_table/blocks/bronze_lever.json | 10 +- .../loot_table/blocks/bronze_plate.json | 10 +- .../blocks/bronze_pressure_plate.json | 10 +- .../loot_table/blocks/bronze_slab.json | 28 +- .../loot_table/blocks/bronze_stairs.json | 10 +- .../loot_table/blocks/bronze_trapdoor.json | 10 +- .../loot_table/blocks/bronze_wall.json | 10 +- .../loot_table/blocks/charcoal_block.json | 10 +- .../loot_table/blocks/coldiron_bars.json | 10 +- .../loot_table/blocks/coldiron_block.json | 10 +- .../loot_table/blocks/coldiron_button.json | 10 +- .../loot_table/blocks/coldiron_door.json | 27 +- .../loot_table/blocks/coldiron_lever.json | 10 +- .../loot_table/blocks/coldiron_ore.json | 34 +- .../loot_table/blocks/coldiron_plate.json | 10 +- .../blocks/coldiron_pressure_plate.json | 10 +- .../loot_table/blocks/coldiron_slab.json | 28 +- .../loot_table/blocks/coldiron_stairs.json | 10 +- .../loot_table/blocks/coldiron_trapdoor.json | 10 +- .../loot_table/blocks/coldiron_wall.json | 10 +- .../loot_table/blocks/copper_bars.json | 10 +- .../loot_table/blocks/copper_block.json | 10 +- .../loot_table/blocks/copper_button.json | 10 +- .../loot_table/blocks/copper_door.json | 27 +- .../loot_table/blocks/copper_lever.json | 10 +- .../loot_table/blocks/copper_ore.json | 38 +- .../loot_table/blocks/copper_plate.json | 10 +- .../blocks/copper_pressure_plate.json | 10 +- .../loot_table/blocks/copper_slab.json | 28 +- .../loot_table/blocks/copper_stairs.json | 10 +- .../loot_table/blocks/copper_trapdoor.json | 10 +- .../loot_table/blocks/copper_wall.json | 10 +- .../loot_table/blocks/cupronickel_bars.json | 10 +- .../loot_table/blocks/cupronickel_block.json | 10 +- .../loot_table/blocks/cupronickel_button.json | 10 +- .../loot_table/blocks/cupronickel_door.json | 27 +- .../loot_table/blocks/cupronickel_lever.json | 10 +- .../loot_table/blocks/cupronickel_plate.json | 10 +- .../blocks/cupronickel_pressure_plate.json | 10 +- .../loot_table/blocks/cupronickel_slab.json | 28 +- .../loot_table/blocks/cupronickel_stairs.json | 10 +- .../blocks/cupronickel_trapdoor.json | 10 +- .../loot_table/blocks/cupronickel_wall.json | 10 +- .../loot_table/blocks/diamond_bars.json | 10 +- .../loot_table/blocks/diamond_button.json | 10 +- .../loot_table/blocks/diamond_door.json | 27 +- .../loot_table/blocks/diamond_lever.json | 10 +- .../blocks/diamond_pressure_plate.json | 10 +- .../loot_table/blocks/diamond_slab.json | 28 +- .../loot_table/blocks/diamond_stairs.json | 10 +- .../loot_table/blocks/diamond_trapdoor.json | 10 +- .../loot_table/blocks/diamond_wall.json | 10 +- .../blocks/double_adamantine_slab.json | 14 +- .../blocks/double_antimony_slab.json | 14 +- .../blocks/double_aquarium_slab.json | 14 +- .../blocks/double_bismuth_slab.json | 14 +- .../loot_table/blocks/double_brass_slab.json | 14 +- .../loot_table/blocks/double_bronze_slab.json | 14 +- .../blocks/double_coldiron_slab.json | 14 +- .../loot_table/blocks/double_copper_slab.json | 14 +- .../blocks/double_cupronickel_slab.json | 14 +- .../blocks/double_diamond_slab.json | 14 +- .../blocks/double_electrum_slab.json | 14 +- .../blocks/double_emerald_slab.json | 14 +- .../loot_table/blocks/double_gold_slab.json | 14 +- .../loot_table/blocks/double_invar_slab.json | 14 +- .../loot_table/blocks/double_iron_slab.json | 14 +- .../loot_table/blocks/double_lead_slab.json | 14 +- .../blocks/double_mithril_slab.json | 14 +- .../loot_table/blocks/double_nickel_slab.json | 14 +- .../blocks/double_obsidian_slab.json | 14 +- .../loot_table/blocks/double_pewter_slab.json | 14 +- .../blocks/double_platinum_slab.json | 14 +- .../loot_table/blocks/double_silver_slab.json | 14 +- .../blocks/double_starsteel_slab.json | 14 +- .../loot_table/blocks/double_steel_slab.json | 14 +- .../loot_table/blocks/double_tin_slab.json | 14 +- .../loot_table/blocks/double_zinc_slab.json | 14 +- .../loot_table/blocks/electrum_bars.json | 10 +- .../loot_table/blocks/electrum_block.json | 10 +- .../loot_table/blocks/electrum_button.json | 10 +- .../loot_table/blocks/electrum_door.json | 27 +- .../loot_table/blocks/electrum_lever.json | 10 +- .../loot_table/blocks/electrum_plate.json | 10 +- .../blocks/electrum_pressure_plate.json | 10 +- .../loot_table/blocks/electrum_slab.json | 28 +- .../loot_table/blocks/electrum_stairs.json | 10 +- .../loot_table/blocks/electrum_trapdoor.json | 10 +- .../loot_table/blocks/electrum_wall.json | 10 +- .../loot_table/blocks/emerald_bars.json | 10 +- .../loot_table/blocks/emerald_button.json | 10 +- .../loot_table/blocks/emerald_door.json | 27 +- .../loot_table/blocks/emerald_lever.json | 10 +- .../blocks/emerald_pressure_plate.json | 10 +- .../loot_table/blocks/emerald_slab.json | 28 +- .../loot_table/blocks/emerald_stairs.json | 10 +- .../loot_table/blocks/emerald_trapdoor.json | 10 +- .../loot_table/blocks/emerald_wall.json | 10 +- .../loot_table/blocks/gold_bars.json | 10 +- .../loot_table/blocks/gold_button.json | 10 +- .../loot_table/blocks/gold_door.json | 27 +- .../loot_table/blocks/gold_lever.json | 10 +- .../loot_table/blocks/gold_plate.json | 10 +- .../loot_table/blocks/gold_slab.json | 28 +- .../loot_table/blocks/gold_stairs.json | 10 +- .../loot_table/blocks/gold_trapdoor.json | 10 +- .../loot_table/blocks/gold_wall.json | 10 +- .../loot_table/blocks/human_detector.json | 10 +- .../loot_table/blocks/invar_bars.json | 10 +- .../loot_table/blocks/invar_block.json | 10 +- .../loot_table/blocks/invar_button.json | 10 +- .../loot_table/blocks/invar_door.json | 27 +- .../loot_table/blocks/invar_lever.json | 10 +- .../loot_table/blocks/invar_plate.json | 10 +- .../blocks/invar_pressure_plate.json | 10 +- .../loot_table/blocks/invar_slab.json | 28 +- .../loot_table/blocks/invar_stairs.json | 10 +- .../loot_table/blocks/invar_trapdoor.json | 10 +- .../loot_table/blocks/invar_wall.json | 10 +- .../loot_table/blocks/iron_button.json | 10 +- .../loot_table/blocks/iron_lever.json | 10 +- .../loot_table/blocks/iron_plate.json | 10 +- .../loot_table/blocks/iron_slab.json | 28 +- .../loot_table/blocks/iron_stairs.json | 10 +- .../loot_table/blocks/iron_wall.json | 10 +- .../loot_table/blocks/lead_bars.json | 10 +- .../loot_table/blocks/lead_block.json | 10 +- .../loot_table/blocks/lead_button.json | 10 +- .../loot_table/blocks/lead_door.json | 27 +- .../loot_table/blocks/lead_lever.json | 10 +- .../loot_table/blocks/lead_ore.json | 34 +- .../loot_table/blocks/lead_plate.json | 10 +- .../blocks/lead_pressure_plate.json | 10 +- .../loot_table/blocks/lead_slab.json | 28 +- .../loot_table/blocks/lead_stairs.json | 10 +- .../loot_table/blocks/lead_trapdoor.json | 10 +- .../loot_table/blocks/lead_wall.json | 10 +- .../loot_table/blocks/mercury_ore.json | 34 +- .../loot_table/blocks/mithril_bars.json | 10 +- .../loot_table/blocks/mithril_block.json | 10 +- .../loot_table/blocks/mithril_button.json | 10 +- .../loot_table/blocks/mithril_door.json | 27 +- .../loot_table/blocks/mithril_lever.json | 10 +- .../loot_table/blocks/mithril_plate.json | 10 +- .../blocks/mithril_pressure_plate.json | 10 +- .../loot_table/blocks/mithril_slab.json | 28 +- .../loot_table/blocks/mithril_stairs.json | 10 +- .../loot_table/blocks/mithril_trapdoor.json | 10 +- .../loot_table/blocks/mithril_wall.json | 10 +- .../loot_table/blocks/nickel_bars.json | 10 +- .../loot_table/blocks/nickel_block.json | 10 +- .../loot_table/blocks/nickel_button.json | 10 +- .../loot_table/blocks/nickel_door.json | 27 +- .../loot_table/blocks/nickel_lever.json | 10 +- .../loot_table/blocks/nickel_ore.json | 34 +- .../loot_table/blocks/nickel_plate.json | 10 +- .../blocks/nickel_pressure_plate.json | 10 +- .../loot_table/blocks/nickel_slab.json | 28 +- .../loot_table/blocks/nickel_stairs.json | 10 +- .../loot_table/blocks/nickel_trapdoor.json | 10 +- .../loot_table/blocks/nickel_wall.json | 10 +- .../loot_table/blocks/obsidian_bars.json | 10 +- .../loot_table/blocks/obsidian_button.json | 10 +- .../loot_table/blocks/obsidian_door.json | 27 +- .../loot_table/blocks/obsidian_lever.json | 10 +- .../blocks/obsidian_pressure_plate.json | 10 +- .../loot_table/blocks/obsidian_slab.json | 28 +- .../loot_table/blocks/obsidian_stairs.json | 10 +- .../loot_table/blocks/obsidian_trapdoor.json | 10 +- .../loot_table/blocks/obsidian_wall.json | 10 +- .../loot_table/blocks/pewter_bars.json | 10 +- .../loot_table/blocks/pewter_block.json | 10 +- .../loot_table/blocks/pewter_button.json | 10 +- .../loot_table/blocks/pewter_door.json | 27 +- .../loot_table/blocks/pewter_lever.json | 10 +- .../loot_table/blocks/pewter_plate.json | 10 +- .../blocks/pewter_pressure_plate.json | 10 +- .../loot_table/blocks/pewter_slab.json | 28 +- .../loot_table/blocks/pewter_stairs.json | 10 +- .../loot_table/blocks/pewter_trapdoor.json | 10 +- .../loot_table/blocks/pewter_wall.json | 10 +- .../loot_table/blocks/platinum_bars.json | 10 +- .../loot_table/blocks/platinum_block.json | 10 +- .../loot_table/blocks/platinum_button.json | 10 +- .../loot_table/blocks/platinum_door.json | 27 +- .../loot_table/blocks/platinum_lever.json | 10 +- .../loot_table/blocks/platinum_ore.json | 34 +- .../loot_table/blocks/platinum_plate.json | 10 +- .../blocks/platinum_pressure_plate.json | 10 +- .../loot_table/blocks/platinum_slab.json | 28 +- .../loot_table/blocks/platinum_stairs.json | 10 +- .../loot_table/blocks/platinum_trapdoor.json | 10 +- .../loot_table/blocks/platinum_wall.json | 10 +- .../loot_table/blocks/quartz_bars.json | 10 +- .../loot_table/blocks/quartz_button.json | 10 +- .../loot_table/blocks/quartz_door.json | 27 +- .../loot_table/blocks/quartz_lever.json | 10 +- .../blocks/quartz_pressure_plate.json | 10 +- .../loot_table/blocks/quartz_trapdoor.json | 10 +- .../loot_table/blocks/quartz_wall.json | 10 +- .../loot_table/blocks/silver_bars.json | 10 +- .../loot_table/blocks/silver_block.json | 10 +- .../loot_table/blocks/silver_button.json | 10 +- .../loot_table/blocks/silver_door.json | 27 +- .../loot_table/blocks/silver_lever.json | 10 +- .../loot_table/blocks/silver_ore.json | 34 +- .../loot_table/blocks/silver_plate.json | 10 +- .../blocks/silver_pressure_plate.json | 10 +- .../loot_table/blocks/silver_slab.json | 28 +- .../loot_table/blocks/silver_stairs.json | 10 +- .../loot_table/blocks/silver_trapdoor.json | 10 +- .../loot_table/blocks/silver_wall.json | 10 +- .../loot_table/blocks/starsteel_bars.json | 10 +- .../loot_table/blocks/starsteel_block.json | 10 +- .../loot_table/blocks/starsteel_button.json | 10 +- .../loot_table/blocks/starsteel_door.json | 27 +- .../loot_table/blocks/starsteel_lever.json | 10 +- .../loot_table/blocks/starsteel_ore.json | 34 +- .../loot_table/blocks/starsteel_plate.json | 10 +- .../blocks/starsteel_pressure_plate.json | 10 +- .../loot_table/blocks/starsteel_slab.json | 28 +- .../loot_table/blocks/starsteel_stairs.json | 10 +- .../loot_table/blocks/starsteel_trapdoor.json | 10 +- .../loot_table/blocks/starsteel_wall.json | 10 +- .../loot_table/blocks/steel_anvil.json | 12 +- .../loot_table/blocks/steel_bars.json | 10 +- .../loot_table/blocks/steel_block.json | 10 +- .../loot_table/blocks/steel_button.json | 10 +- .../loot_table/blocks/steel_door.json | 27 +- .../loot_table/blocks/steel_lever.json | 10 +- .../loot_table/blocks/steel_plate.json | 10 +- .../blocks/steel_pressure_plate.json | 10 +- .../loot_table/blocks/steel_slab.json | 28 +- .../loot_table/blocks/steel_stairs.json | 10 +- .../loot_table/blocks/steel_trapdoor.json | 10 +- .../loot_table/blocks/steel_wall.json | 10 +- .../loot_table/blocks/stone_anvil.json | 12 +- .../loot_table/blocks/tin_bars.json | 10 +- .../loot_table/blocks/tin_block.json | 10 +- .../loot_table/blocks/tin_button.json | 10 +- .../loot_table/blocks/tin_door.json | 27 +- .../loot_table/blocks/tin_lever.json | 10 +- .../basemetals/loot_table/blocks/tin_ore.json | 34 +- .../loot_table/blocks/tin_plate.json | 10 +- .../loot_table/blocks/tin_pressure_plate.json | 10 +- .../loot_table/blocks/tin_slab.json | 28 +- .../loot_table/blocks/tin_stairs.json | 10 +- .../loot_table/blocks/tin_trapdoor.json | 10 +- .../loot_table/blocks/tin_wall.json | 10 +- .../loot_table/blocks/zinc_bars.json | 10 +- .../loot_table/blocks/zinc_block.json | 10 +- .../loot_table/blocks/zinc_button.json | 10 +- .../loot_table/blocks/zinc_door.json | 27 +- .../loot_table/blocks/zinc_lever.json | 10 +- .../loot_table/blocks/zinc_ore.json | 34 +- .../loot_table/blocks/zinc_plate.json | 10 +- .../blocks/zinc_pressure_plate.json | 10 +- .../loot_table/blocks/zinc_slab.json | 28 +- .../loot_table/blocks/zinc_stairs.json | 10 +- .../loot_table/blocks/zinc_trapdoor.json | 10 +- .../loot_table/blocks/zinc_wall.json | 10 +- .../chests/inject/abandoned_mineshaft.json | 50 +- .../chests/inject/desert_pyramid.json | 163 +- .../chests/inject/end_city_treasure.json | 202 +- .../chests/inject/jungle_temple.json | 163 +- .../chests/inject/nether_bridge.json | 162 +- .../chests/inject/simple_dungeon.json | 163 +- .../chests/inject/stronghold_corridor.json | 162 +- .../chests/inject/stronghold_crossing.json | 43 +- .../chests/inject/village_armorer.json | 50 +- .../chests/inject/village_toolsmith.json | 50 +- .../chests/inject/village_weaponsmith.json | 50 +- .../data/basemetals/registry_manifest.json | 1702 ++++++++++++++++- .../armorer/antimony_boots.json | 2 +- .../armorer/antimony_chestplate.json | 2 +- .../armorer/antimony_helmet.json | 2 +- .../armorer/antimony_ingot.json | 2 +- .../armorer/antimony_leggings.json | 2 +- .../villager_trade/armorer/bismuth_boots.json | 2 +- .../armorer/bismuth_chestplate.json | 2 +- .../armorer/bismuth_helmet.json | 2 +- .../villager_trade/armorer/bismuth_ingot.json | 2 +- .../armorer/bismuth_leggings.json | 2 +- .../villager_trade/armorer/brass_boots.json | 2 +- .../armorer/brass_boots_enchanted.json | 6 +- .../armorer/brass_chestplate.json | 2 +- .../armorer/brass_chestplate_enchanted.json | 6 +- .../villager_trade/armorer/brass_helmet.json | 2 +- .../armorer/brass_helmet_enchanted.json | 6 +- .../villager_trade/armorer/brass_ingot.json | 2 +- .../armorer/brass_leggings.json | 2 +- .../armorer/brass_leggings_enchanted.json | 6 +- .../villager_trade/armorer/bronze_boots.json | 2 +- .../armorer/bronze_chestplate.json | 2 +- .../villager_trade/armorer/bronze_helmet.json | 2 +- .../villager_trade/armorer/bronze_ingot.json | 2 +- .../armorer/bronze_leggings.json | 2 +- .../armorer/charcoal_powder.json | 2 +- .../villager_trade/armorer/coal_powder.json | 2 +- .../villager_trade/armorer/copper_boots.json | 2 +- .../armorer/copper_chestplate.json | 2 +- .../villager_trade/armorer/copper_helmet.json | 2 +- .../villager_trade/armorer/copper_ingot.json | 2 +- .../armorer/copper_leggings.json | 2 +- .../armorer/cupronickel_boots.json | 2 +- .../armorer/cupronickel_boots_enchanted.json | 6 +- .../armorer/cupronickel_chestplate.json | 2 +- .../cupronickel_chestplate_enchanted.json | 6 +- .../armorer/cupronickel_helmet.json | 2 +- .../armorer/cupronickel_helmet_enchanted.json | 6 +- .../armorer/cupronickel_ingot.json | 2 +- .../armorer/cupronickel_leggings.json | 2 +- .../cupronickel_leggings_enchanted.json | 6 +- .../armorer/electrum_boots.json | 2 +- .../armorer/electrum_boots_enchanted.json | 6 +- .../armorer/electrum_chestplate.json | 2 +- .../electrum_chestplate_enchanted.json | 6 +- .../armorer/electrum_helmet.json | 2 +- .../armorer/electrum_helmet_enchanted.json | 6 +- .../armorer/electrum_ingot.json | 2 +- .../armorer/electrum_leggings.json | 2 +- .../armorer/electrum_leggings_enchanted.json | 6 +- .../villager_trade/armorer/invar_boots.json | 2 +- .../armorer/invar_chestplate.json | 2 +- .../villager_trade/armorer/invar_helmet.json | 2 +- .../villager_trade/armorer/invar_ingot.json | 2 +- .../armorer/invar_leggings.json | 2 +- .../villager_trade/armorer/lead_boots.json | 2 +- .../armorer/lead_chestplate.json | 2 +- .../villager_trade/armorer/lead_helmet.json | 2 +- .../villager_trade/armorer/lead_ingot.json | 2 +- .../villager_trade/armorer/lead_leggings.json | 2 +- .../villager_trade/armorer/mercury_ingot.json | 2 +- .../villager_trade/armorer/nickel_boots.json | 2 +- .../armorer/nickel_boots_enchanted.json | 6 +- .../armorer/nickel_chestplate.json | 2 +- .../armorer/nickel_chestplate_enchanted.json | 6 +- .../villager_trade/armorer/nickel_helmet.json | 2 +- .../armorer/nickel_helmet_enchanted.json | 6 +- .../villager_trade/armorer/nickel_ingot.json | 2 +- .../armorer/nickel_leggings.json | 2 +- .../armorer/nickel_leggings_enchanted.json | 6 +- .../villager_trade/armorer/pewter_boots.json | 2 +- .../armorer/pewter_chestplate.json | 2 +- .../villager_trade/armorer/pewter_helmet.json | 2 +- .../villager_trade/armorer/pewter_ingot.json | 2 +- .../armorer/pewter_leggings.json | 2 +- .../villager_trade/armorer/silver_boots.json | 2 +- .../armorer/silver_boots_enchanted.json | 6 +- .../armorer/silver_chestplate.json | 2 +- .../armorer/silver_chestplate_enchanted.json | 6 +- .../villager_trade/armorer/silver_helmet.json | 2 +- .../armorer/silver_helmet_enchanted.json | 6 +- .../villager_trade/armorer/silver_ingot.json | 2 +- .../armorer/silver_leggings.json | 2 +- .../armorer/silver_leggings_enchanted.json | 6 +- .../villager_trade/armorer/steel_boots.json | 2 +- .../armorer/steel_chestplate.json | 2 +- .../villager_trade/armorer/steel_helmet.json | 2 +- .../villager_trade/armorer/steel_ingot.json | 2 +- .../armorer/steel_leggings.json | 2 +- .../villager_trade/armorer/tin_boots.json | 2 +- .../armorer/tin_chestplate.json | 2 +- .../villager_trade/armorer/tin_helmet.json | 2 +- .../villager_trade/armorer/tin_ingot.json | 2 +- .../villager_trade/armorer/tin_leggings.json | 2 +- .../villager_trade/armorer/zinc_boots.json | 2 +- .../armorer/zinc_chestplate.json | 2 +- .../villager_trade/armorer/zinc_helmet.json | 2 +- .../villager_trade/armorer/zinc_ingot.json | 2 +- .../villager_trade/armorer/zinc_leggings.json | 2 +- .../toolsmith/antimony_axe.json | 2 +- .../toolsmith/antimony_crackhammer.json | 2 +- .../toolsmith/antimony_hoe.json | 2 +- .../toolsmith/antimony_ingot.json | 2 +- .../toolsmith/antimony_pickaxe.json | 2 +- .../toolsmith/antimony_shovel.json | 2 +- .../villager_trade/toolsmith/bismuth_axe.json | 2 +- .../toolsmith/bismuth_crackhammer.json | 2 +- .../villager_trade/toolsmith/bismuth_hoe.json | 2 +- .../toolsmith/bismuth_ingot.json | 2 +- .../toolsmith/bismuth_pickaxe.json | 2 +- .../toolsmith/bismuth_shovel.json | 2 +- .../villager_trade/toolsmith/brass_axe.json | 2 +- .../toolsmith/brass_axe_enchanted.json | 6 +- .../toolsmith/brass_crackhammer.json | 2 +- .../brass_crackhammer_enchanted.json | 6 +- .../villager_trade/toolsmith/brass_hoe.json | 2 +- .../toolsmith/brass_hoe_enchanted.json | 6 +- .../villager_trade/toolsmith/brass_ingot.json | 2 +- .../toolsmith/brass_pickaxe.json | 2 +- .../toolsmith/brass_pickaxe_enchanted.json | 6 +- .../toolsmith/brass_shovel.json | 2 +- .../toolsmith/brass_shovel_enchanted.json | 6 +- .../villager_trade/toolsmith/bronze_axe.json | 2 +- .../toolsmith/bronze_crackhammer.json | 2 +- .../villager_trade/toolsmith/bronze_hoe.json | 2 +- .../toolsmith/bronze_ingot.json | 2 +- .../toolsmith/bronze_pickaxe.json | 2 +- .../toolsmith/bronze_shovel.json | 2 +- .../toolsmith/charcoal_powder.json | 2 +- .../villager_trade/toolsmith/coal_powder.json | 2 +- .../villager_trade/toolsmith/copper_axe.json | 2 +- .../toolsmith/copper_crackhammer.json | 2 +- .../villager_trade/toolsmith/copper_hoe.json | 2 +- .../toolsmith/copper_ingot.json | 2 +- .../toolsmith/copper_pickaxe.json | 2 +- .../toolsmith/copper_shovel.json | 2 +- .../toolsmith/cupronickel_axe.json | 2 +- .../toolsmith/cupronickel_axe_enchanted.json | 6 +- .../toolsmith/cupronickel_crackhammer.json | 2 +- .../cupronickel_crackhammer_enchanted.json | 6 +- .../toolsmith/cupronickel_hoe.json | 2 +- .../toolsmith/cupronickel_hoe_enchanted.json | 6 +- .../toolsmith/cupronickel_ingot.json | 2 +- .../toolsmith/cupronickel_pickaxe.json | 2 +- .../cupronickel_pickaxe_enchanted.json | 6 +- .../toolsmith/cupronickel_shovel.json | 2 +- .../cupronickel_shovel_enchanted.json | 6 +- .../toolsmith/electrum_axe.json | 2 +- .../toolsmith/electrum_axe_enchanted.json | 6 +- .../toolsmith/electrum_crackhammer.json | 2 +- .../electrum_crackhammer_enchanted.json | 6 +- .../toolsmith/electrum_hoe.json | 2 +- .../toolsmith/electrum_hoe_enchanted.json | 6 +- .../toolsmith/electrum_ingot.json | 2 +- .../toolsmith/electrum_pickaxe.json | 2 +- .../toolsmith/electrum_pickaxe_enchanted.json | 6 +- .../toolsmith/electrum_shovel.json | 2 +- .../toolsmith/electrum_shovel_enchanted.json | 6 +- .../villager_trade/toolsmith/invar_axe.json | 2 +- .../toolsmith/invar_crackhammer.json | 2 +- .../villager_trade/toolsmith/invar_hoe.json | 2 +- .../villager_trade/toolsmith/invar_ingot.json | 2 +- .../toolsmith/invar_pickaxe.json | 2 +- .../toolsmith/invar_shovel.json | 2 +- .../villager_trade/toolsmith/lead_axe.json | 2 +- .../toolsmith/lead_crackhammer.json | 2 +- .../villager_trade/toolsmith/lead_hoe.json | 2 +- .../villager_trade/toolsmith/lead_ingot.json | 2 +- .../toolsmith/lead_pickaxe.json | 2 +- .../villager_trade/toolsmith/lead_shovel.json | 2 +- .../toolsmith/mercury_ingot.json | 2 +- .../villager_trade/toolsmith/nickel_axe.json | 2 +- .../toolsmith/nickel_axe_enchanted.json | 6 +- .../toolsmith/nickel_crackhammer.json | 2 +- .../nickel_crackhammer_enchanted.json | 6 +- .../villager_trade/toolsmith/nickel_hoe.json | 2 +- .../toolsmith/nickel_hoe_enchanted.json | 6 +- .../toolsmith/nickel_ingot.json | 2 +- .../toolsmith/nickel_pickaxe.json | 2 +- .../toolsmith/nickel_pickaxe_enchanted.json | 6 +- .../toolsmith/nickel_shovel.json | 2 +- .../toolsmith/nickel_shovel_enchanted.json | 6 +- .../villager_trade/toolsmith/pewter_axe.json | 2 +- .../toolsmith/pewter_crackhammer.json | 2 +- .../villager_trade/toolsmith/pewter_hoe.json | 2 +- .../toolsmith/pewter_ingot.json | 2 +- .../toolsmith/pewter_pickaxe.json | 2 +- .../toolsmith/pewter_shovel.json | 2 +- .../villager_trade/toolsmith/silver_axe.json | 2 +- .../toolsmith/silver_axe_enchanted.json | 6 +- .../toolsmith/silver_crackhammer.json | 2 +- .../silver_crackhammer_enchanted.json | 6 +- .../villager_trade/toolsmith/silver_hoe.json | 2 +- .../toolsmith/silver_hoe_enchanted.json | 6 +- .../toolsmith/silver_ingot.json | 2 +- .../toolsmith/silver_pickaxe.json | 2 +- .../toolsmith/silver_pickaxe_enchanted.json | 6 +- .../toolsmith/silver_shovel.json | 2 +- .../toolsmith/silver_shovel_enchanted.json | 6 +- .../villager_trade/toolsmith/steel_axe.json | 2 +- .../toolsmith/steel_crackhammer.json | 2 +- .../villager_trade/toolsmith/steel_hoe.json | 2 +- .../villager_trade/toolsmith/steel_ingot.json | 2 +- .../toolsmith/steel_pickaxe.json | 2 +- .../toolsmith/steel_shovel.json | 2 +- .../villager_trade/toolsmith/tin_axe.json | 2 +- .../toolsmith/tin_crackhammer.json | 2 +- .../villager_trade/toolsmith/tin_hoe.json | 2 +- .../villager_trade/toolsmith/tin_ingot.json | 2 +- .../villager_trade/toolsmith/tin_pickaxe.json | 2 +- .../villager_trade/toolsmith/tin_shovel.json | 2 +- .../villager_trade/toolsmith/zinc_axe.json | 2 +- .../toolsmith/zinc_crackhammer.json | 2 +- .../villager_trade/toolsmith/zinc_hoe.json | 2 +- .../villager_trade/toolsmith/zinc_ingot.json | 2 +- .../toolsmith/zinc_pickaxe.json | 2 +- .../villager_trade/toolsmith/zinc_shovel.json | 2 +- .../weaponsmith/antimony_bow.json | 2 +- .../weaponsmith/antimony_crossbow.json | 2 +- .../weaponsmith/antimony_ingot.json | 2 +- .../weaponsmith/antimony_sword.json | 2 +- .../weaponsmith/bismuth_bow.json | 2 +- .../weaponsmith/bismuth_crossbow.json | 2 +- .../weaponsmith/bismuth_ingot.json | 2 +- .../weaponsmith/bismuth_sword.json | 2 +- .../villager_trade/weaponsmith/brass_bow.json | 2 +- .../weaponsmith/brass_bow_enchanted.json | 6 +- .../weaponsmith/brass_crossbow.json | 2 +- .../weaponsmith/brass_crossbow_enchanted.json | 6 +- .../weaponsmith/brass_ingot.json | 2 +- .../weaponsmith/brass_sword.json | 2 +- .../weaponsmith/brass_sword_enchanted.json | 6 +- .../weaponsmith/bronze_bow.json | 2 +- .../weaponsmith/bronze_crossbow.json | 2 +- .../weaponsmith/bronze_ingot.json | 2 +- .../weaponsmith/bronze_sword.json | 2 +- .../weaponsmith/charcoal_powder.json | 2 +- .../weaponsmith/coal_powder.json | 2 +- .../weaponsmith/copper_bow.json | 2 +- .../weaponsmith/copper_crossbow.json | 2 +- .../weaponsmith/copper_ingot.json | 2 +- .../weaponsmith/copper_sword.json | 2 +- .../weaponsmith/cupronickel_bow.json | 2 +- .../cupronickel_bow_enchanted.json | 6 +- .../weaponsmith/cupronickel_crossbow.json | 2 +- .../cupronickel_crossbow_enchanted.json | 6 +- .../weaponsmith/cupronickel_ingot.json | 2 +- .../weaponsmith/cupronickel_sword.json | 2 +- .../cupronickel_sword_enchanted.json | 6 +- .../weaponsmith/electrum_bow.json | 2 +- .../weaponsmith/electrum_bow_enchanted.json | 6 +- .../weaponsmith/electrum_crossbow.json | 2 +- .../electrum_crossbow_enchanted.json | 6 +- .../weaponsmith/electrum_ingot.json | 2 +- .../weaponsmith/electrum_sword.json | 2 +- .../weaponsmith/electrum_sword_enchanted.json | 6 +- .../villager_trade/weaponsmith/invar_bow.json | 2 +- .../weaponsmith/invar_crossbow.json | 2 +- .../weaponsmith/invar_ingot.json | 2 +- .../weaponsmith/invar_sword.json | 2 +- .../villager_trade/weaponsmith/lead_bow.json | 2 +- .../weaponsmith/lead_crossbow.json | 2 +- .../weaponsmith/lead_ingot.json | 2 +- .../weaponsmith/lead_sword.json | 2 +- .../weaponsmith/mercury_ingot.json | 2 +- .../weaponsmith/nickel_bow.json | 2 +- .../weaponsmith/nickel_bow_enchanted.json | 6 +- .../weaponsmith/nickel_crossbow.json | 2 +- .../nickel_crossbow_enchanted.json | 6 +- .../weaponsmith/nickel_ingot.json | 2 +- .../weaponsmith/nickel_sword.json | 2 +- .../weaponsmith/nickel_sword_enchanted.json | 6 +- .../weaponsmith/pewter_bow.json | 2 +- .../weaponsmith/pewter_crossbow.json | 2 +- .../weaponsmith/pewter_ingot.json | 2 +- .../weaponsmith/pewter_sword.json | 2 +- .../weaponsmith/silver_bow.json | 2 +- .../weaponsmith/silver_bow_enchanted.json | 6 +- .../weaponsmith/silver_crossbow.json | 2 +- .../silver_crossbow_enchanted.json | 6 +- .../weaponsmith/silver_ingot.json | 2 +- .../weaponsmith/silver_sword.json | 2 +- .../weaponsmith/silver_sword_enchanted.json | 6 +- .../villager_trade/weaponsmith/steel_bow.json | 2 +- .../weaponsmith/steel_crossbow.json | 2 +- .../weaponsmith/steel_ingot.json | 2 +- .../weaponsmith/steel_sword.json | 2 +- .../villager_trade/weaponsmith/tin_bow.json | 2 +- .../weaponsmith/tin_crossbow.json | 2 +- .../villager_trade/weaponsmith/tin_ingot.json | 2 +- .../villager_trade/weaponsmith/tin_sword.json | 2 +- .../villager_trade/weaponsmith/zinc_bow.json | 2 +- .../weaponsmith/zinc_crossbow.json | 2 +- .../weaponsmith/zinc_ingot.json | 2 +- .../weaponsmith/zinc_sword.json | 2 +- .../smoke/IntegratedWorldSmoke.java | 6 +- .../client/MaterialProjectileRenderer.java | 6 +- .../basemetals/content/CrackhammerItem.java | 7 - .../mc/basemetals/content/MaterialItems.java | 96 +- .../mc/basemetals/content/ModContent.java | 2 +- .../migration/Legacy112WorldMigrator.java | 3 +- .../migration/LegacyBlockStateCodec.java | 48 + .../migration/LegacyWorldDataHook.java | 9 +- .../mixin/MonsterRoomFeatureMixin.java | 27 + .../recipe/ContentCraftingRecipe.java | 18 +- .../recipe/LegacySmeltingRecipe.java | 2 +- src/main/resources/META-INF/mods.toml | 2 +- src/main/resources/basemetals.mixins.json | 1 + src/main/resources/pack.mcmeta | 4 +- .../packaged-resources/META-INF/mods.toml | 4 +- .../basemetals/smoke/PackagedServerSmoke.java | 17 +- .../packaged-resources/META-INF/mods.toml | 4 +- src/smoke/datapack/pack.mcmeta | 4 +- .../data/Minecraft1211ResourceTest.java | 51 +- .../basemetals/data/RecipeRegressionTest.java | 2 + .../data/ResourceIntegrityTest.java | 2 +- .../migration/LegacyBlockStateCodecTest.java | 52 + .../migration/LegacyMigrationSafetyTest.java | 4 +- .../migration/LegacyWorldDataHookTest.java | 82 + tools/validation/README.md | 11 +- tools/validation/compare_current_world.py | 42 +- tools/validation/compare_world_terrain.py | 20 +- tools/validation/test_content_modes.js | 8 +- tools/validation/test_current_world.py | 33 + tools/validation/test_legacy_worlds.js | 5 +- tools/validation/test_vanilla_registry.js | 19 + tools/validation/test_world_terrain.py | 31 + tools/validation/vanilla_registry.js | 10 + 1748 files changed, 8735 insertions(+), 4858 deletions(-) create mode 100644 src/main/java/zone/moddev/mc/basemetals/migration/LegacyBlockStateCodec.java create mode 100644 src/main/java/zone/moddev/mc/basemetals/mixin/MonsterRoomFeatureMixin.java create mode 100644 src/test/java/zone/moddev/mc/basemetals/migration/LegacyBlockStateCodecTest.java create mode 100644 tools/validation/test_vanilla_registry.js create mode 100644 tools/validation/vanilla_registry.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6fa868247..c0ac0592c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,19 +1,19 @@ -name: Base Metals 26.2 CI +name: Base Metals 26.3 CI on: push: branches: - - master-26.2 + - master-26.3 - 'feature/**' pull_request: branches: - - master-26.2 + - master-26.3 permissions: contents: read concurrency: - group: basemetals-26.2-${{ github.workflow }}-${{ github.ref }} + group: basemetals-26.3-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: @@ -49,7 +49,7 @@ jobs: "$RUNNER_TEMP/nbt-tests/bin/python" -m unittest discover -s tools/validation -p 'test_*.py' - name: Test upgrade log boundaries - run: node --test tools/validation/test_painting_registry.js tools/validation/test_runtime_log.js + run: node --test tools/validation/test_painting_registry.js tools/validation/test_runtime_log.js tools/validation/test_vanilla_registry.js - name: Build, test, and audit first candidate run: | @@ -59,9 +59,9 @@ jobs: --no-daemon --stacktrace first="$RUNNER_TEMP/basemetals-first" mkdir -p "$first" - cp build/libs/BaseMetals-3.1.1.2602001.jar "$first/" - cp build/libs/BaseMetals-3.1.1.2602001-sources.jar "$first/" - cp build/libs/BaseMetals-3.1.1.2602001-javadoc.jar "$first/" + cp build/libs/BaseMetals-3.1.1.2603001.jar "$first/" + cp build/libs/BaseMetals-3.1.1.2603001-sources.jar "$first/" + cp build/libs/BaseMetals-3.1.1.2603001-javadoc.jar "$first/" cp build/release/SHA256SUMS "$first/" - name: Rebuild and prove byte-identical archives @@ -69,9 +69,9 @@ jobs: ./gradlew clean assemble verifyReleaseArtifacts writeReleaseChecksums \ -PorespawnVerificationRepository="${{ steps.orespawn.outputs.repository }}" \ --no-daemon --stacktrace - cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.1.1.2602001.jar" build/libs/BaseMetals-3.1.1.2602001.jar - cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.1.1.2602001-sources.jar" build/libs/BaseMetals-3.1.1.2602001-sources.jar - cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.1.1.2602001-javadoc.jar" build/libs/BaseMetals-3.1.1.2602001-javadoc.jar + cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.1.1.2603001.jar" build/libs/BaseMetals-3.1.1.2603001.jar + cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.1.1.2603001-sources.jar" build/libs/BaseMetals-3.1.1.2603001-sources.jar + cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.1.1.2603001-javadoc.jar" build/libs/BaseMetals-3.1.1.2603001-javadoc.jar cmp "$RUNNER_TEMP/basemetals-first/SHA256SUMS" build/release/SHA256SUMS - name: Run all required GameTests @@ -83,14 +83,14 @@ jobs: - name: Install official Forge packaged runtimes id: forge-runtimes run: | - server_runtime="$RUNNER_TEMP/forge-26.2-65.1.0-server" - client_runtime="$RUNNER_TEMP/forge-26.2-65.1.0-client" - installer="$RUNNER_TEMP/forge-26.2-65.1.0-installer.jar" + server_runtime="$RUNNER_TEMP/forge-26.3-66.0.0-server" + client_runtime="$RUNNER_TEMP/forge-26.3-66.0.0-client" + installer="$RUNNER_TEMP/forge-26.3-66.0.0-installer.jar" mkdir -p "$server_runtime" "$client_runtime" curl --fail --location --retry 3 \ --output "$installer" \ - https://maven.minecraftforge.net/net/minecraftforge/forge/26.2-65.1.0/forge-26.2-65.1.0-installer.jar - echo "813FBBFECC3086A492783B9F5023AE15007737B42E17AAB75F48BA77403314DF $installer" | sha256sum --check --strict + https://maven.minecraftforge.net/net/minecraftforge/forge/26.3-66.0.0/forge-26.3-66.0.0-installer.jar + echo "C6173EEA56AED0B947722F314542196E28A35285844961D517C7FB571492BBE3 $installer" | sha256sum --check --strict ( cd "$server_runtime" java -jar "$installer" --installServer @@ -135,13 +135,13 @@ jobs: - name: Upload audited release candidate uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: BaseMetals-26.2-${{ github.sha }} + name: BaseMetals-26.3-${{ github.sha }} if-no-files-found: error retention-days: 30 path: | - build/libs/BaseMetals-3.1.1.2602001.jar - build/libs/BaseMetals-3.1.1.2602001-sources.jar - build/libs/BaseMetals-3.1.1.2602001-javadoc.jar + build/libs/BaseMetals-3.1.1.2603001.jar + build/libs/BaseMetals-3.1.1.2603001-sources.jar + build/libs/BaseMetals-3.1.1.2603001-javadoc.jar build/release/SHA256SUMS CHANGELOG.txt @@ -149,7 +149,7 @@ jobs: if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: BaseMetals-26.2-diagnostics-${{ github.sha }} + name: BaseMetals-26.3-diagnostics-${{ github.sha }} if-no-files-found: ignore retention-days: 14 path: | diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 7bb158a99..50322ad2d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -3,11 +3,11 @@ name: CodeQL on: push: branches: - - master-26.2 + - master-26.3 - 'feature/**' pull_request: branches: - - master-26.2 + - master-26.3 permissions: actions: read diff --git a/.github/workflows/validate-gradle-build.yml b/.github/workflows/validate-gradle-build.yml index d8bacce9a..d77ba0452 100644 --- a/.github/workflows/validate-gradle-build.yml +++ b/.github/workflows/validate-gradle-build.yml @@ -3,11 +3,11 @@ name: Validate Gradle Wrapper on: push: branches: - - master-26.2 + - master-26.3 - 'feature/**' pull_request: branches: - - master-26.2 + - master-26.3 permissions: contents: read diff --git a/CHANGELOG.txt b/CHANGELOG.txt index d7289ae53..f2ecdba8d 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,13 @@ +Base Metals 3.1.1.2603001 + +- Port the released 26.2 version to Minecraft 26.3 and Forge 66, keeping Java 25. +- Keep raw metals, ore overlays, filled buckets, High/Low Fantasy and the existing gameplay fixes. +- Update recipes, loot, screens and save handling for the newer Minecraft formats. +- Keep coal and charcoal fuels, tool interactions and enchanted smith trades working with the new item components. +- Use published OreSpawn 4.1.0.2603001 with the same eleven rules and copper defaults. +- Read the new block-state save format while keeping older worlds, current player data and upgrade backups intact. +- Skip dungeon placement in protected imported terrain, while leaving new chunks and new underground areas available for generation. + Base Metals 3.1.1.2602001 - Port the released 26.1.2 version to Minecraft 26.2 and Forge 65, keeping Java 25. diff --git a/README.md b/README.md index 18a85fcab..71b88f908 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Discord](https://img.shields.io/badge/Discord-MMD-green.svg?style=flat&logo=Discord)](https://discord.moddev.zone) [![CurseForge downloads](https://cf.way2muchnoise.eu/full_base-metals_downloads.svg)](https://www.curseforge.com/minecraft/mc-mods/base-metals) [![Supported Minecraft versions](https://cf.way2muchnoise.eu/versions/Minecraft_base-metals_all.svg)](https://www.curseforge.com/minecraft/mc-mods/base-metals) -[![Build, test, and audit](https://github.com/MinecraftModDevelopmentMods/BaseMetals/actions/workflows/ci.yml/badge.svg?branch=master-26.2)](https://github.com/MinecraftModDevelopmentMods/BaseMetals/actions/workflows/ci.yml?query=branch%3Amaster-26.2) +[![Build, test, and audit](https://github.com/MinecraftModDevelopmentMods/BaseMetals/actions/workflows/ci.yml/badge.svg?branch=master-26.3)](https://github.com/MinecraftModDevelopmentMods/BaseMetals/actions/workflows/ci.yml?query=branch%3Amaster-26.3) # Base Metals @@ -12,23 +12,23 @@ shields, bows, crossbows, arrows, and bolts. The mod also includes Crack Hammers, scythes, metal anvils, the human detector, villager trades, advancements, and structure loot. -This is the Minecraft 26.2 Forge version, `3.1.1.2602001`. It keeps the -fixes and features from the released 26.1.2 version, including raw metals, ore +This is the Minecraft 26.3 Forge version, `3.1.1.2603001`. It keeps the +fixes and features from the released 26.2 version, including raw metals, ore texture overlays and High/Low Fantasy modes. Existing Base Metals items and blocks keep their registry IDs. OreSpawn handles ore generation; MMDLib and Additional Loot Tables are no longer needed. ## Requirements -- Minecraft `26.2` -- Forge `65.1.0` or newer in the Minecraft 26.2 Forge 65 line +- Minecraft `26.3` +- Forge `66.0.0` or newer in the Minecraft 26.3 Forge 66 line - A current Java 25 runtime -- OreSpawn `[4.1.0.2602001,5.0.0)` on both client and server +- OreSpawn `[4.1.0.2603001,5.0.0)` on both client and server -The build uses Forge `65.1.0` and is also tested on `65.1.5`. +The build uses Forge `66.0.0` and is also tested on `66.1.0`. -This version uses OreSpawn `4.1.0.2602001`. Put -`BaseMetals-3.1.1.2602001.jar` and a compatible OreSpawn JAR in the `mods` +This version uses OreSpawn `4.1.0.2603001`. Put +`BaseMetals-3.1.1.2603001.jar` and a compatible OreSpawn JAR in the `mods` directory. MMDLib and Additional Loot Tables must not be carried forward from an older installation. Mineralogy is optional. @@ -144,13 +144,16 @@ Adding Base Metals to a modern world does not trigger the old numeric-ID conversion just because an older backup is present. Existing player data stays in place; genuine legacy upgrades still make backups before changing saved data. +The 26.3 update also protects imported terrain from dungeon placement during +conversion. New chunks and the new underground areas can still generate normally. + ## Mod and data-pack compatibility Forge item, block, and fluid tags are Base Metals' public compatibility API. Compatible tags and the existing recipe data for Mekanism, Thermal, Tinkers' Construct and Ender IO are retained, but their Java integrations are not enabled -in this Forge 26.2 port. Do not add jars made for older Minecraft versions or +in this Forge 26.3 port. Do not add jars made for older Minecraft versions or NeoForge to this instance. No optional integration API is required to compile or launch Base Metals. @@ -201,7 +204,7 @@ early splash window. Minecraft still opens its game window and runs the full world check. Ordinary client launches keep the normal splash screen. Release artifacts use Maven coordinate -`zone.moddev.mc.basemetals:BaseMetals:3.1.1.2602001`. +`zone.moddev.mc.basemetals:BaseMetals:3.1.1.2603001`. Report defects and compatibility problems through the [Base Metals issue tracker](https://github.com/MinecraftModDevelopmentMods/BaseMetals/issues). diff --git a/build.gradle b/build.gradle index bf2e3ceb7..1da84c143 100644 --- a/build.gradle +++ b/build.gradle @@ -9,14 +9,14 @@ plugins { ext.mavenizerCompatibilityJar = layout.projectDirectory.file( 'ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar') ext.mavenizerCompatibilitySha256 = - '5ABAB52A3F1BE115381C3CE80D5FD38DBE6C9F4C0B9ECF70A957700EC90FB88B' + '4150926F33A696B2088AB2675C1C3B45745CE4F9F2DEB31CCD88C9ADC834062F' ext.mavenizerFixtureChecksums = [ 'minecraft-mavenizer-0.5.21-orespawn-compat.jar': mavenizerCompatibilitySha256, - 'minecraft-source-compatibility.json': '9437E03FC5E1CB5D605D72F0538C4DF4417B64DB0A57D0AABDC5184781517AC1', - 'minecraft-mavenizer-0.5.21-orespawn-compat.patch': '2DA8DD0FD5F112F5B7591113AED06D9A58C584A25F8705E26445A6D2E9D5C78E', + 'minecraft-source-compatibility.json': '829400D74ECB1BF0CC6107D9980B8328365E3F56D9A5342002D8DF340571BDB7', + 'minecraft-mavenizer-0.5.21-orespawn-compat.patch': 'F1402D730A356589AE46C2483AC10E41062E28A748FEBB72F6CB663A28027A41', 'LICENSE-MAVENIZER.txt': '20C17D8B8C48A600800DFD14F95D5CB9FF47066A9641DDEAB48DC54AEC96E331', - 'README.md': 'EAD1477F393897C491A10FB86178213A99C360571A8063D551DA0579DF65BA6F' + 'README.md': '45AA8B3238D2E8A873FF49862A8EA1ADDBC186E39655436723C0C85773447D36' ] // Mavenizer runs during configuration, so verify its inputs before configuring Forge. mavenizerFixtureChecksums.each { name, expected -> @@ -27,7 +27,7 @@ mavenizerFixtureChecksums.each { name, expected -> } } -// Forge 65 needs no source repairs. The sealed fixture records that result +// Forge 66 needs no source repairs. The sealed fixture records that result // and stays separate from the mod and its runtime dependencies. fgtools.configure('mavenizer') { classpath.from(mavenizerCompatibilityJar) diff --git a/ci-fixtures/tools/README.md b/ci-fixtures/tools/README.md index 1365b4203..673af87f1 100644 --- a/ci-fixtures/tools/README.md +++ b/ci-fixtures/tools/README.md @@ -2,7 +2,7 @@ This directory contains a build-only derivative of MinecraftForge's MinecraftMavenizer `0.5.21`. It is used only while ForgeGradle prepares the -exact Forge `26.2-65.1.0` development dependency and is excluded from every +exact Forge `26.3-66.0.0` development dependency and is excluded from every OreSpawn publication artifact. ## Provenance and licence @@ -21,13 +21,13 @@ declaration and one accessor in that record. Missing, partial, duplicate or ambiguous states fail preparation. Targets without an explicit rule set are left unchanged. -Forge `26.2-65.1.0` compiles without source compatibility edits. Its explicit +Forge `26.3-66.0.0` compiles without source compatibility edits. Its explicit zero-rule entry proves that result is intentional and target-qualified rather than an accidental fall-through. A marker beside Mavenizer's output records the target and manifest SHA-256. Reprocessing the same source is idempotent. The derivative also propagates Gradle offline mode when the build sets -`ORESPAWN_MAVENIZER_OFFLINE=true`. Mavenizer, OreSpawn and Minecraft 26.2 all +`ORESPAWN_MAVENIZER_OFFLINE=true`. Mavenizer, OreSpawn and Minecraft 26.3 all run and compile with Java 25. ## Rebuild diff --git a/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar b/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar index b5f7fb50b4efd168da56917d7ef34be60b184315..86aeb5ef2f658691a67b20ef6cfac3aeb787a8d2 100644 GIT binary patch delta 197 zcmWm8ISztA06@_}7R7zR4HsO{6WDtQ8%{vw0f?2w7%J;YjAx)SA@KnAT2R?patS}h ztN%~3yO!WAHCd9nG#;j-tk*LPPnL3(xwAj|<#!Z9T-E#LFXKp=uuw(?Hmazhjs}`& up^Xl@=)plB0}L_37%nE5Vum?9EKoF8Sf`QlzXzijii6z8CJpj!w*Lc;mQox5 delta 197 zcmWm8DGtIw06@_eI)$6X;<~RyVJl|0w!|E!$JW?lu$+m uRn$;N15Mayp^Xl@=)plB0}L_37!#z;8Rn6%+~3wnhXOBgv53568L$7a !optionalClient.classpath.files.any { it.name.contains("${expected.module}-${expected.file}") } }) { - throw new GradleException('The optional client must load the pinned 26.2 dependencies') + throw new GradleException('The optional client must load the pinned 26.3 dependencies') } } } @@ -334,7 +334,7 @@ def verifyEclipseProductionClasspath = tasks.register('verifyEclipseProductionCl } } String metadata = new File(projectDir, 'bin/main/META-INF/mods.toml').getText('UTF-8') - if (metadata.contains('${') || !metadata.contains('version="3.1.1.2602001"')) { + if (metadata.contains('${') || !metadata.contains('version="3.1.1.2603001"')) { throw new GradleException('Eclipse output contains unresolved or invalid Base Metals version metadata') } } diff --git a/gradle/release/artifacts.gradle b/gradle/release/artifacts.gradle index 11de2a215..441ca7daf 100644 --- a/gradle/release/artifacts.gradle +++ b/gradle/release/artifacts.gradle @@ -184,7 +184,7 @@ tasks.register('verifyMavenCoordinates') { tasks.register('verifyMavenizerCompatibilityFixture') { group = 'verification' - description = 'Verifies the checksum-sealed Forge 26.2 Mavenizer compatibility fixture.' + description = 'Verifies the checksum-sealed Forge 26.3 Mavenizer compatibility fixture.' inputs.file project.ext.mavenizerCompatibilityJar doLast { File fixture = project.ext.mavenizerCompatibilityJar.asFile @@ -198,23 +198,23 @@ tasks.register('verifyMavenizerCompatibilityFixture') { tasks.register('verifyReleaseConfiguration') { group = 'verification' - description = 'Verifies the exact Base Metals 26.2 release identity.' + description = 'Verifies the exact Base Metals 26.3 release identity.' dependsOn tasks.named('verifyMavenizerCompatibilityFixture') dependsOn tasks.named('verifyJava25Toolchain') doLast { - if (configuredReleaseIdentity.modVersion != '3.1.1.2602001' + if (configuredReleaseIdentity.modVersion != '3.1.1.2603001' || configuredReleaseIdentity.modGroup != expectedMavenGroup - || configuredReleaseIdentity.minecraftVersion != '26.2' - || configuredReleaseIdentity.forgeVersion != '65.1.0' - || configuredReleaseIdentity.forgeInstallerSha256 != '813FBBFECC3086A492783B9F5023AE15007737B42E17AAB75F48BA77403314DF' + || configuredReleaseIdentity.minecraftVersion != '26.3' + || configuredReleaseIdentity.forgeVersion != '66.0.0' + || configuredReleaseIdentity.forgeInstallerSha256 != 'C6173EEA56AED0B947722F314542196E28A35285844961D517C7FB571492BBE3' || configuredReleaseIdentity.mappingChannel != 'official' - || configuredReleaseIdentity.mappingVersion != '26.2' + || configuredReleaseIdentity.mappingVersion != '26.3' || configuredReleaseIdentity.loaderName != 'forge' || configuredReleaseIdentity.loaderCode != '1' || configuredReleaseIdentity.javaVersion != '25' || configuredReleaseIdentity.gradleJavaVersion != '25' || configuredReleaseIdentity.curseForgeProjectId != '240967') { - throw new GradleException('Unexpected Base Metals 26.2 release identity') + throw new GradleException('Unexpected Base Metals 26.3 release identity') } } } @@ -223,10 +223,10 @@ tasks.register('verifyReleaseDependencies') { group = 'verification' description = 'Resolves and verifies the exact released OreSpawn qualification dependency.' doLast { - if (configuredOreSpawnIdentity.version != '4.1.0.2602001' + if (configuredOreSpawnIdentity.version != '4.1.0.2603001' || configuredOreSpawnIdentity.curseProjectId != '245586' - || configuredOreSpawnIdentity.curseFileId != '9332183' - || configuredOreSpawnIdentity.sha256 != 'F74785025898DF059E0F00F6ADA53364E07ECBBC306807ED7CD55350609E2547') { + || configuredOreSpawnIdentity.curseFileId != '9333781' + || configuredOreSpawnIdentity.sha256 != 'A7FCDD626B9044091BFDB7277DA3169A5307EB5F97FCF88741A878401DCF1AEA') { throw new GradleException('Unexpected OreSpawn release coordinates') } File artifact = configurations.orespawnRelease.resolvedConfiguration.resolvedArtifacts @@ -326,14 +326,14 @@ tasks.register('verifyReleaseArtifacts') { if (manifest.mainAttributes.getValue('Maven-Artifact') != expectedMavenCoordinate) { throw new GradleException('Release manifest contains the wrong Maven coordinate') } - String expectedBuildTarget = 'minecraft-26.2-forge-65.1.0-java-25' + String expectedBuildTarget = 'minecraft-26.3-forge-66.0.0-java-25' if (manifest.mainAttributes.getValue('Build-Target') != expectedBuildTarget) { throw new GradleException('Release manifest contains the wrong stable build target') } String modsToml = zip.getInputStream(zip.getEntry('META-INF/mods.toml')).getText('UTF-8') if (modsToml.contains('${') - || !modsToml.contains('version="3.1.1.2602001"') - || !modsToml.contains('versionRange="[4.1.0.2602001,5.0.0)"')) { + || !modsToml.contains('version="3.1.1.2603001"') + || !modsToml.contains('versionRange="[4.1.0.2603001,5.0.0)"')) { throw new GradleException('Release mods.toml contains unresolved or incompatible metadata') } def provider = new JsonSlurper().parse(zip.getInputStream( diff --git a/gradle/verification/packaged-runtime.gradle b/gradle/verification/packaged-runtime.gradle index bce75d64b..2f85183b6 100644 --- a/gradle/verification/packaged-runtime.gradle +++ b/gradle/verification/packaged-runtime.gradle @@ -134,8 +134,8 @@ def packagedServerStartupSmoke = tasks.register('packagedServerStartupSmoke', Ex doFirst { File runtime = requireRuntimeDirectory(packagedForgeServerRuntime, 'packagedForgeServerRuntime') File libraries = new File(runtime, 'libraries') - File forgeDirectory = new File(libraries, 'net/minecraftforge/forge/26.2-65.1.0') - File launcher = new File(forgeDirectory, 'forge-26.2-65.1.0-server.jar') + File forgeDirectory = new File(libraries, 'net/minecraftforge/forge/26.3-66.0.0') + File launcher = new File(forgeDirectory, 'forge-26.3-66.0.0-server.jar') File sourceArguments = new File(forgeDirectory, System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows') ? 'win_args.txt' : 'unix_args.txt') @@ -174,9 +174,9 @@ def packagedServerStartupSmoke = tasks.register('packagedServerStartupSmoke', Ex if (!marker.isFile()) throw new GradleException('Packaged dedicated-server completion marker is missing') Properties values = new Properties() marker.withInputStream { values.load(it) } - if (values.getProperty('basemetals') != '3.1.1.2602001' - || values.getProperty('orespawn') != '4.1.0.2602001' - || values.getProperty('forge') != '65.1.0') { + if (values.getProperty('basemetals') != '3.1.1.2603001' + || values.getProperty('orespawn') != '4.1.0.2603001' + || values.getProperty('forge') != '66.0.0') { throw new GradleException("Packaged dedicated-server versions are wrong: ${values}") } verifyPackagedLog(runDirectory, 'BASEMETALS_PACKAGED_SERVER_SMOKE PASS', @@ -219,11 +219,11 @@ def preparePackagedClientSmoke = tasks.register('preparePackagedClientSmoke') { File hostPack = new File(runDirectory, 'resourcepacks/basemetals-host-test') File texture = new File(hostPack, 'assets/minecraft/textures/block/stone.png') texture.parentFile.mkdirs() - new java.util.zip.ZipFile(new File(runtime, 'versions/26.2/26.2.jar')).withCloseable { zip -> + new java.util.zip.ZipFile(new File(runtime, 'versions/26.3/26.3.jar')).withCloseable { zip -> texture.bytes = zip.getInputStream(zip.getEntry('assets/minecraft/textures/block/dirt.png')).bytes } new File(hostPack, 'pack.mcmeta').setText( - '{"pack":{"min_format":[88,0],"max_format":[88,0],"description":"Ore host replacement test"}}\n', 'UTF-8') + '{"pack":{"min_format":[97,1],"max_format":[97,1],"description":"Ore host replacement test"}}\n', 'UTF-8') new File(runDirectory, 'options.txt').append( 'resourcePacks:["vanilla","mod_resources","file/basemetals-host-test"]\n', 'UTF-8') } @@ -243,13 +243,13 @@ def packagedClientStartupSmoke = tasks.register('packagedClientStartupSmoke', Ex dependsOn preparePackagedClientSmoke doFirst { File runtime = requireRuntimeDirectory(packagedForgeClientRuntime, 'packagedForgeClientRuntime') - String forgeVersionId = ['26.2-forge-65.1.0', 'forge-65.1.0'].find { candidate -> + String forgeVersionId = ['26.3-forge-66.0.0', 'forge-66.0.0'].find { candidate -> new File(runtime, "versions/${candidate}/${candidate}.json").isFile() } - if (forgeVersionId == null) throw new GradleException('Official client runtime has no Forge 65.1.0 profile') + if (forgeVersionId == null) throw new GradleException('Official client runtime has no Forge 66.0.0 profile') File forgeJsonFile = new File(runtime, "versions/${forgeVersionId}/${forgeVersionId}.json") - File baseJsonFile = new File(runtime, 'versions/26.2/26.2.json') - File baseJar = new File(runtime, 'versions/26.2/26.2.jar') + File baseJsonFile = new File(runtime, 'versions/26.3/26.3.json') + File baseJar = new File(runtime, 'versions/26.3/26.3.jar') File nativesDirectory = new File(runtime, "natives/${forgeVersionId}") [forgeJsonFile, baseJsonFile, baseJar, new File(runtime, 'libraries'), new File(runtime, 'assets'), nativesDirectory].each { diff --git a/src/dataGenerator/java/zone/moddev/mc/basemetals/data/GeneratedData.java b/src/dataGenerator/java/zone/moddev/mc/basemetals/data/GeneratedData.java index c2a34ed48..f16b0a124 100644 --- a/src/dataGenerator/java/zone/moddev/mc/basemetals/data/GeneratedData.java +++ b/src/dataGenerator/java/zone/moddev/mc/basemetals/data/GeneratedData.java @@ -27,12 +27,9 @@ import net.minecraft.data.DataProvider; import net.minecraft.data.CachedOutput; import net.minecraft.resources.Identifier; -import net.minecraft.world.item.AxeItem; import net.minecraft.world.item.BowItem; import net.minecraft.world.item.FishingRodItem; -import net.minecraft.world.item.HoeItem; import net.minecraft.world.item.ShearsItem; -import net.minecraft.world.item.ShovelItem; /** Small catalogue-driven generator for server data and compatibility manifests. */ final class GeneratedData implements DataProvider { @@ -278,12 +275,12 @@ private void writeEnchantmentTags(CachedOutput cache) throws IOException { String name = "basemetals:" + id; if (item instanceof zone.moddev.mc.basemetals.content.MaterialItems.Sword) addAggregate(families, "swords", name); - if (item instanceof AxeItem) addAggregate(families, "axes", name); + if (item instanceof zone.moddev.mc.basemetals.content.MaterialItems.Axe) addAggregate(families, "axes", name); if (item instanceof zone.moddev.mc.basemetals.content.MaterialItems.Pickaxe) addAggregate(families, "pickaxes", name); - if (item instanceof ShovelItem) addAggregate(families, "shovels", name); - if (item instanceof HoeItem) addAggregate(families, "hoes", name); + if (item instanceof zone.moddev.mc.basemetals.content.MaterialItems.Shovel) addAggregate(families, "shovels", name); + if (item instanceof zone.moddev.mc.basemetals.content.MaterialItems.Hoe) addAggregate(families, "hoes", name); - if (item instanceof zone.moddev.mc.basemetals.content.CrackhammerItem || item instanceof zone.moddev.mc.basemetals.content.ScytheItem || item instanceof zone.moddev.mc.basemetals.content.MaterialItems.Pickaxe || item instanceof AxeItem || item instanceof ShovelItem || item instanceof HoeItem) { + if (item instanceof zone.moddev.mc.basemetals.content.CrackhammerItem || item instanceof zone.moddev.mc.basemetals.content.ScytheItem || item instanceof zone.moddev.mc.basemetals.content.MaterialItems.Pickaxe || item instanceof zone.moddev.mc.basemetals.content.MaterialItems.Axe || item instanceof zone.moddev.mc.basemetals.content.MaterialItems.Shovel || item instanceof zone.moddev.mc.basemetals.content.MaterialItems.Hoe) { addAggregate(families, "enchantable/mining", name); addAggregate(families, "enchantable/mining_loot", name); } else if (item instanceof ShearsItem) { @@ -1583,6 +1580,43 @@ private static void modernizeLootJson(JsonElement element) { } if (!element.isJsonObject()) return; JsonObject object = element.getAsJsonObject(); + + for (String dispatch : List.of("function", "condition")) { + if (object.has(dispatch) && object.get(dispatch).isJsonPrimitive()) { + object.add("type", object.remove(dispatch)); + } + } + + if (object.has("type") && "minecraft:block_state_property".equals(object.get("type").getAsString())) { + object.addProperty("type", "minecraft:match_block"); + object.add("blocks", object.remove("block")); + object.add("state", object.remove("properties")); + } + + if (object.has("functions")) { + object.add("modifier", object.remove("functions")); + } + + if (object.has("conditions")) { + JsonArray conditions = object.remove("conditions").getAsJsonArray(); + + if (conditions.size() == 1) { + object.add("condition", conditions.get(0)); + } else if (!conditions.isEmpty()) { + object.add("condition", object("type", "minecraft:all_of", "terms", conditions)); + } + } + + for (String field : List.of("count", "rolls", "bonus_rolls")) { + if (object.has(field) && object.get(field).isJsonObject()) { + JsonObject number = object.getAsJsonObject(field); + + if (!number.has("type") && number.has("min") && number.has("max")) { + number.addProperty("type", "minecraft:uniform"); + } + } + } + new ArrayList<>(object.keySet()).stream() .filter(key -> key.startsWith("__comment")) .forEach(object::remove); @@ -1593,7 +1627,7 @@ private static void modernizeLootJson(JsonElement element) { && entry.getValue().getAsJsonPrimitive().isString() && !entry.getValue().getAsString().contains(":")) { entry.setValue(new com.google.gson.JsonPrimitive("minecraft:" + entry.getValue().getAsString())); - } else { + } else if (!entry.getKey().equals("state") && !entry.getKey().equals("properties")) { modernizeLootJson(entry.getValue()); } } @@ -1621,8 +1655,21 @@ private void writeManifest(CachedOutput cache) throws IOException { JsonObject root = new JsonObject(); root.addProperty("format", 1); - root.addProperty("source", "Base Metals 26.2 catalogue"); + root.addProperty("source", "Base Metals 26.3 catalogue"); root.add("blocks", array(blocks)); + JsonObject blockDefaults = new JsonObject(); + + ModContent.blocksById().forEach((id, block) -> { + var state = block.get().defaultBlockState(); + JsonObject properties = new JsonObject(); + + state.getProperties().stream().sorted(java.util.Comparator.comparing( + net.minecraft.world.level.block.state.properties.Property::getName)) + .forEach(property -> properties.addProperty(property.getName(), propertyValue(state, property))); + blockDefaults.add("basemetals:" + id, properties); + }); + + root.add("block_defaults", blockDefaults); root.add("items", array(items)); root.add("fluids", array(ModContent.fluids().keySet().stream().flatMap(id -> java.util.stream.Stream.of("basemetals:" + id, "basemetals:flowing_" + id)).toList())); @@ -1642,6 +1689,12 @@ private void writeManifest(CachedOutput cache) throws IOException { save(cache, root, "data/basemetals/registry_manifest.json"); } + private static > String propertyValue( + net.minecraft.world.level.block.state.BlockState state, + net.minecraft.world.level.block.state.properties.Property property) { + return property.getName(state.getValue(property)); + } + private static List difference(List current, List baseline, List exclusions) { LinkedHashSet remaining = new LinkedHashSet<>(current); remaining.removeAll(baseline); @@ -1845,6 +1898,10 @@ private void save(CachedOutput cache, JsonObject value, String relative) throws value = object("type", "basemetals:content_crafting", "recipe", value); } if (relative.contains("/recipe/")) normalizeRecipeResults(value); + if (relative.contains("/loot_table/")) modernizeLootJson(value); + if (relative.contains("/loot_modifiers/") && value.has("conditions")) { + value.getAsJsonArray("conditions").forEach(GeneratedData::modernizeLootJson); + } if (relative.contains("/advancement/") || relative.contains("/loot_table/")) normalizePredicates(value); JsonObject previous = scheduledResources.putIfAbsent(relative, value.deepCopy()); if (previous != null) { @@ -2009,7 +2066,7 @@ private void writeRawProcessing(CachedOutput cache) throws IOException { "minecraft:" + method); JsonObject criteria = object("has_raw", inventoryCriterion(ingredient("#forge:raw_materials/" + material)), "has_recipe", object("trigger", "minecraft:recipe_unlocked", - "conditions", object("recipe", "basemetals:" + id))); + "conditions", object("recipes", array("basemetals:" + id)))); writeUnlock(cache, id, criteria); } } @@ -2029,7 +2086,8 @@ private void writeCraftingUnlock(CachedOutput cache, String id, JsonObject recip default -> "#forge:ingots/" + material; }; JsonObject criteria = object("has_material", inventoryCriterion(ingredient(discovery)), - "has_recipe", object("trigger", "minecraft:recipe_unlocked", "conditions", object("recipe", "basemetals:" + id))); + "has_recipe", object("trigger", "minecraft:recipe_unlocked", "conditions", + object("recipes", array("basemetals:" + id)))); List inputs = new ArrayList<>(); if (recipe.has("key")) recipe.getAsJsonObject("key").entrySet().forEach(entry -> inputs.add(entry.getValue())); else recipe.getAsJsonArray("ingredients").forEach(inputs::add); diff --git a/src/dataGenerator/java/zone/moddev/mc/basemetals/data/SmithTradeProvider.java b/src/dataGenerator/java/zone/moddev/mc/basemetals/data/SmithTradeProvider.java index 312c900e8..6f900c61d 100644 --- a/src/dataGenerator/java/zone/moddev/mc/basemetals/data/SmithTradeProvider.java +++ b/src/dataGenerator/java/zone/moddev/mc/basemetals/data/SmithTradeProvider.java @@ -35,14 +35,14 @@ public CompletableFuture run(CachedOutput cache) { "max_uses", offer.enchanted() ? 3 : 6, "xp", offer.enchanted() ? 15 : 5, "reputation_discount", 0.2, - "merchant_predicate", object("condition", "basemetals:trade_enabled", "item", offer.item())); + "merchant_predicate", object("type", "basemetals:trade_enabled", "item", offer.item())); if (offer.enchanted()) { JsonArray modifiers = new JsonArray(); - modifiers.add(object("function", "minecraft:enchant_with_levels", + modifiers.add(object("type", "minecraft:enchant_with_levels", "levels", object("type", "minecraft:uniform", "min", 5, "max", 19), "options", "#minecraft:on_random_loot", "include_additional_cost_component", true)); - trade.add("given_item_modifiers", modifiers); + trade.add("given_item_modifier", modifiers); } writes.add(DataProvider.saveStable(cache, trade, diff --git a/src/gametest/java/zone/moddev/mc/basemetals/content/BaseMetalsGameTests.java b/src/gametest/java/zone/moddev/mc/basemetals/content/BaseMetalsGameTests.java index 85c6df11f..e59896eba 100644 --- a/src/gametest/java/zone/moddev/mc/basemetals/content/BaseMetalsGameTests.java +++ b/src/gametest/java/zone/moddev/mc/basemetals/content/BaseMetalsGameTests.java @@ -159,6 +159,27 @@ public static void compatibilitySlabsStayDoubleAndHidden(GameTestHelper helper) require(helper, !ForgeRegistries.ITEMS.containsKey(Identifier.fromNamespaceAndPath(BaseMetals.MOD_ID, id)), id + " unexpectedly has a block item"); } + + var player = survivalPlayer(helper); + BlockPos position = helper.absolutePos(new BlockPos(1, 1, 1)); + ItemStack pickaxe = Items.NETHERITE_PICKAXE.getDefaultInstance(); + + ModContent.blocksById().forEach((id, block) -> { + if (block.get() instanceof net.minecraft.world.level.block.SlabBlock + && !ModContent.hiddenBlocks().contains(id)) { + for (SlabType type : SlabType.values()) { + var state = block.get().defaultBlockState() + .setValue(net.minecraft.world.level.block.state.properties.BlockStateProperties.SLAB_TYPE, type); + var drops = Block.getDrops(state, helper.getLevel(), position, null, player, pickaxe); + int count = drops.stream().filter(stack -> stack.is(block.get().asItem())) + .mapToInt(ItemStack::getCount).sum(); + + require(helper, count == (type == SlabType.DOUBLE ? 2 : 1), + id + " has the wrong loot count for " + type); + } + } + }); + helper.succeed(); } @@ -431,6 +452,10 @@ public static void legacyToolAttributesAndFuelValuesSurviveThePort(GameTestHelpe require(helper, attribute(hammer, Attributes.ATTACK_SPEED) == -3.5D, "Crackhammer attack speed changed"); + verifyToolTransformation(helper, "bronze_axe", Blocks.OAK_LOG, Blocks.STRIPPED_OAK_LOG, 1); + verifyToolTransformation(helper, "bronze_shovel", Blocks.GRASS_BLOCK, Blocks.DIRT_PATH, 2); + verifyToolTransformation(helper, "bronze_hoe", Blocks.DIRT, Blocks.FARMLAND, 3); + require(helper, burnTime("coal_powder") == 1600, "Coal powder no longer burns for one item"); require(helper, burnTime("coal_smallpowder") == 200, "Tiny coal dust has the wrong burn time"); require(helper, burnTime("wood_gear") == 300, "Wood gear has the wrong burn time"); @@ -1157,8 +1182,38 @@ private static net.minecraft.core.Holder(); for (int index = 0; index < countField.getInt(state); index++) { - result.addAll(layers[index].prepareQuadList()); + var quads = (net.minecraft.client.resources.model.geometry.ItemQuads) quadsField.get(layers[index]); + result.addAll(quads.all()); } return result; diff --git a/src/main/java/zone/moddev/mc/basemetals/client/MaterialProjectileRenderer.java b/src/main/java/zone/moddev/mc/basemetals/client/MaterialProjectileRenderer.java index 799cfbea9..f3ebf624a 100644 --- a/src/main/java/zone/moddev/mc/basemetals/client/MaterialProjectileRenderer.java +++ b/src/main/java/zone/moddev/mc/basemetals/client/MaterialProjectileRenderer.java @@ -45,11 +45,11 @@ public void extractRenderState(MaterialProjectile projectile, State state, float @Override public void submit(State state, PoseStack poses, SubmitNodeCollector collector, CameraRenderState camera) { poses.pushPose(); - poses.mulPose(Axis.YP.rotationDegrees(state.yRot - 90.0F)); - poses.mulPose(Axis.ZP.rotationDegrees(state.xRot)); + poses.rotateDegrees(Axis.YP, state.yRot - 90.0F); + poses.rotateDegrees(Axis.ZP, state.xRot); collector.submitModel(model, state, poses, RenderTypes.entityCutout(TEXTURE), state.lightCoords, OverlayTexture.NO_OVERLAY, state.colour, null, - state.outlineColor, null); + state.outlineColor); poses.popPose(); super.submit(state, poses, collector, camera); diff --git a/src/main/java/zone/moddev/mc/basemetals/content/CrackhammerItem.java b/src/main/java/zone/moddev/mc/basemetals/content/CrackhammerItem.java index 58d4045c2..63bdddef6 100644 --- a/src/main/java/zone/moddev/mc/basemetals/content/CrackhammerItem.java +++ b/src/main/java/zone/moddev/mc/basemetals/content/CrackhammerItem.java @@ -21,8 +21,6 @@ import net.minecraft.world.phys.AABB; import net.minecraft.tags.BlockTags; import net.minecraft.world.entity.EquipmentSlot; -import net.minecraftforge.common.ToolAction; -import net.minecraftforge.common.ToolActions; public final class CrackhammerItem extends Item implements MaterialBacked { private final MaterialDefinition material; @@ -44,11 +42,6 @@ && canHarvest(stack, state) : 1.0F; } - @Override - public boolean canPerformAction(ItemStack stack, ToolAction action) { - return ToolActions.DEFAULT_PICKAXE_ACTIONS.contains(action); - } - @Override public void appendHoverText(ItemStack stack, net.minecraft.world.item.Item.TooltipContext context, TooltipDisplay display, Consumer tooltip, diff --git a/src/main/java/zone/moddev/mc/basemetals/content/MaterialItems.java b/src/main/java/zone/moddev/mc/basemetals/content/MaterialItems.java index 163bac708..562874565 100644 --- a/src/main/java/zone/moddev/mc/basemetals/content/MaterialItems.java +++ b/src/main/java/zone/moddev/mc/basemetals/content/MaterialItems.java @@ -7,6 +7,7 @@ import com.google.common.collect.Multimap; import net.minecraft.resources.Identifier; +import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.Registries; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; @@ -18,23 +19,23 @@ import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.arrow.AbstractArrow; -import net.minecraft.world.item.AxeItem; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.BowItem; import net.minecraft.world.item.FishingRodItem; -import net.minecraft.world.item.HoeItem; import net.minecraft.world.item.component.ItemAttributeModifiers; +import net.minecraft.world.item.component.CookingFuel; import net.minecraft.world.entity.EquipmentSlotGroup; import net.minecraft.world.item.Item; import net.minecraft.world.item.ShearsItem; import net.minecraft.world.item.ShieldItem; -import net.minecraft.world.item.ShovelItem; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.network.chat.Component; import net.minecraft.world.level.Level; +import net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt; +import net.minecraft.world.level.storage.loot.providers.number.floats.ResolvableFloat; import javax.annotation.Nullable; import java.util.List; import java.util.function.Consumer; @@ -49,18 +50,34 @@ private MaterialItems() {} public static final class Basic extends Item implements MaterialBacked { private final MaterialDefinition material; private final int burnTime; + public Basic(MaterialDefinition material, int burnTime, Properties properties) { - super(properties); + super(withFuel(properties, burnTime)); this.material = material; this.burnTime = burnTime; } - @Override public MaterialDefinition baseMetalsMaterial() { return material; } - @Override public int getBurnTime(ItemStack stack, + + @Override + public MaterialDefinition baseMetalsMaterial() { + return material; + } + + @Override + public int getBurnTime(ItemStack stack, @Nullable net.minecraft.world.item.crafting.RecipeType recipeType) { return burnTime; } } + private static Item.Properties withFuel(Item.Properties properties, int burnTime) { + if (burnTime > 0) { + return properties.component(DataComponents.COOKING_FUEL, new CookingFuel( + new ResolvableInt.Constant(burnTime), new ResolvableFloat.Constant(1.0F))); + } + + return properties; + } + public static final class Pickaxe extends Item implements MaterialBacked { private final MaterialDefinition material; public Pickaxe(MaterialDefinition material, Properties properties) { @@ -72,40 +89,66 @@ public Pickaxe(MaterialDefinition material, Properties properties) { TooltipDisplay display, Consumer tooltip, TooltipFlag flag) { addToolTooltip(material, tooltip); } } - public static final class Axe extends AxeItem implements MaterialBacked { + public static final class Axe extends Item implements MaterialBacked { private final MaterialDefinition material; + public Axe(MaterialDefinition material, Properties properties) { - super(new MaterialTier(material).toolMaterial(), 4.0F + material.baseAttackDamage(), - material.axeAttackSpeed(), properties); + super(properties.axe(new MaterialTier(material).toolMaterial(), + 4.0F + material.baseAttackDamage(), material.axeAttackSpeed())); this.material = material; } - @Override public MaterialDefinition baseMetalsMaterial() { return material; } - @Override public void appendHoverText(ItemStack stack, net.minecraft.world.item.Item.TooltipContext context, - TooltipDisplay display, Consumer tooltip, TooltipFlag flag) { addToolTooltip(material, tooltip); } + + @Override + public MaterialDefinition baseMetalsMaterial() { + return material; + } + + @Override + public void appendHoverText(ItemStack stack, Item.TooltipContext context, + TooltipDisplay display, Consumer tooltip, TooltipFlag flag) { + addToolTooltip(material, tooltip); + } } - public static final class Shovel extends ShovelItem implements MaterialBacked { + public static final class Shovel extends Item implements MaterialBacked { private final MaterialDefinition material; + public Shovel(MaterialDefinition material, Properties properties) { - super(new MaterialTier(material).toolMaterial(), 1.5F, -3.0F, properties); + super(properties.shovel(new MaterialTier(material).toolMaterial(), 1.5F, -3.0F)); this.material = material; } - @Override public MaterialDefinition baseMetalsMaterial() { return material; } - @Override public void appendHoverText(ItemStack stack, net.minecraft.world.item.Item.TooltipContext context, - TooltipDisplay display, Consumer tooltip, TooltipFlag flag) { addToolTooltip(material, tooltip); } + + @Override + public MaterialDefinition baseMetalsMaterial() { + return material; + } + + @Override + public void appendHoverText(ItemStack stack, Item.TooltipContext context, + TooltipDisplay display, Consumer tooltip, TooltipFlag flag) { + addToolTooltip(material, tooltip); + } } - public static final class Hoe extends HoeItem implements MaterialBacked { + public static final class Hoe extends Item implements MaterialBacked { private final MaterialDefinition material; + public Hoe(MaterialDefinition material, Properties properties) { - super(new MaterialTier(material).toolMaterial(), -material.baseAttackDamage(), - material.baseAttackDamage() - 3.0F, properties); + super(properties.hoe(new MaterialTier(material).toolMaterial(), + -material.baseAttackDamage(), material.baseAttackDamage() - 3.0F)); this.material = material; } - @Override public MaterialDefinition baseMetalsMaterial() { return material; } - @Override public void appendHoverText(ItemStack stack, net.minecraft.world.item.Item.TooltipContext context, - TooltipDisplay display, Consumer tooltip, TooltipFlag flag) { addToolTooltip(material, tooltip); } + @Override + public MaterialDefinition baseMetalsMaterial() { + return material; + } + + @Override + public void appendHoverText(ItemStack stack, Item.TooltipContext context, + TooltipDisplay display, Consumer tooltip, TooltipFlag flag) { + addToolTooltip(material, tooltip); + } } public static class Sword extends Item implements MaterialBacked { @@ -154,11 +197,14 @@ private static ArmorType armorType(EquipmentSlot slot) { public static final class BurnableBlock extends BlockItem { private final int burnTime; + public BurnableBlock(net.minecraft.world.level.block.Block block, int burnTime, Properties properties) { - super(block, properties); + super(block, withFuel(properties, burnTime)); this.burnTime = burnTime; } - @Override public int getBurnTime(ItemStack stack, + + @Override + public int getBurnTime(ItemStack stack, @Nullable net.minecraft.world.item.crafting.RecipeType recipeType) { return burnTime; } diff --git a/src/main/java/zone/moddev/mc/basemetals/content/ModContent.java b/src/main/java/zone/moddev/mc/basemetals/content/ModContent.java index 07d7ea582..fb08e21fe 100644 --- a/src/main/java/zone/moddev/mc/basemetals/content/ModContent.java +++ b/src/main/java/zone/moddev/mc/basemetals/content/ModContent.java @@ -445,7 +445,7 @@ private static void registerFluid(String name, int colour, boolean mercury) { flowing[0] = FLUIDS.register("flowing_" + name, () -> new ForgeFlowingFluid.Flowing(properties)); block[0] = BLOCKS.register(name, () -> new MoltenMetalBlock(() -> source[0].get(), BlockBehaviour.Properties.of().setId(BLOCKS.key(name)).mapColor(MapColor.FIRE).liquid().replaceable() - .pushReaction(PushReaction.DESTROY).noCollision().strength(100.0F).noLootTable(), mercury)); + .pushReaction(PushReaction.POPPED).noCollision().strength(100.0F).noLootTable(), mercury)); BLOCKS_BY_ID.put(name, block[0]); // Forge 1.12 registered an ItemBlock under every molten block ID. Keep // those hidden inventory IDs so old stacks can survive a world upgrade. diff --git a/src/main/java/zone/moddev/mc/basemetals/migration/Legacy112WorldMigrator.java b/src/main/java/zone/moddev/mc/basemetals/migration/Legacy112WorldMigrator.java index b8b55a2d6..a0cfe0f3a 100644 --- a/src/main/java/zone/moddev/mc/basemetals/migration/Legacy112WorldMigrator.java +++ b/src/main/java/zone/moddev/mc/basemetals/migration/Legacy112WorldMigrator.java @@ -40,7 +40,6 @@ import net.minecraft.nbt.ListTag; import net.minecraft.nbt.LongArrayTag; import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.NbtUtils; import net.minecraft.nbt.Tag; import net.minecraft.resources.Identifier; import net.minecraft.util.datafix.DataFixTypes; @@ -668,7 +667,7 @@ private static CompoundTag restoredState(LegacyBlock legacy, Map property : state.getProperties()) { + writeProperty(properties, state, property); + } + tag.put("Properties", properties); + } + + return tag; + } + + private static > void writeProperty(CompoundTag target, + BlockState state, Property property) { + target.putString(property.getName(), property.getName(state.getValue(property))); + } + + static String blockName(Tag state) { + if (state instanceof CompoundTag compound) { + return compound.getStringOr("id", compound.getStringOr("Name", "")); + } + if (state instanceof StringTag text) { + return text.value(); + } + + return ""; + } + + static CompoundTag properties(CompoundTag state) { + return state.getCompoundOrEmpty(state.contains("properties") ? "properties" : "Properties"); + } +} diff --git a/src/main/java/zone/moddev/mc/basemetals/migration/LegacyWorldDataHook.java b/src/main/java/zone/moddev/mc/basemetals/migration/LegacyWorldDataHook.java index 4b1d74f68..71b5b4d66 100644 --- a/src/main/java/zone/moddev/mc/basemetals/migration/LegacyWorldDataHook.java +++ b/src/main/java/zone/moddev/mc/basemetals/migration/LegacyWorldDataHook.java @@ -56,7 +56,6 @@ import net.minecraft.nbt.Tag; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.NbtUtils; import net.minecraft.world.level.block.state.properties.AttachFace; import net.minecraft.world.level.block.state.properties.DoorHingeSide; import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; @@ -370,9 +369,9 @@ static int migrateWallStates(Tag value) { int changed = 0; if (value instanceof CompoundTag) { CompoundTag compound = (CompoundTag) value; - String name = compound.getStringOr("Name", ""); + String name = LegacyBlockStateCodec.blockName(compound); if (name.startsWith("basemetals:") && name.endsWith("_wall")) { - CompoundTag properties = compound.getCompoundOrEmpty("Properties"); + CompoundTag properties = LegacyBlockStateCodec.properties(compound); for (String direction : new String[] {"north", "east", "south", "west"}) { String connection = properties.getStringOr(direction, ""); if ("true".equals(connection) || "false".equals(connection)) { @@ -441,7 +440,7 @@ private static String paletteBlockAt(CompoundTag level, int x, int y, int z) { if (word >= states.length) return ""; state = (int) ((states[word] >>> ((position % perWord) * bits)) & ((1L << bits) - 1)); } - return state < palette.size() ? palette.getCompoundOrEmpty(state).getStringOr("Name", "") : ""; + return state < palette.size() ? LegacyBlockStateCodec.blockName(palette.get(state)) : ""; } return ""; } @@ -511,7 +510,7 @@ private static int installLegacyBlockStates(CompoundTag snapshot) { for (int meta = 0; meta < 16; meta++) { BlockState state = legacyState(block, entry.getKey().getPath(), meta); table[(entry.getValue().intValue() << 4) | meta] = - new Dynamic<>(net.minecraft.nbt.NbtOps.INSTANCE, NbtUtils.writeBlockState(state)); + new Dynamic<>(net.minecraft.nbt.NbtOps.INSTANCE, LegacyBlockStateCodec.writeFlattenedState(state)); mapped++; } } diff --git a/src/main/java/zone/moddev/mc/basemetals/mixin/MonsterRoomFeatureMixin.java b/src/main/java/zone/moddev/mc/basemetals/mixin/MonsterRoomFeatureMixin.java new file mode 100644 index 000000000..22c6028b5 --- /dev/null +++ b/src/main/java/zone/moddev/mc/basemetals/mixin/MonsterRoomFeatureMixin.java @@ -0,0 +1,27 @@ +package zone.moddev.mc.basemetals.mixin; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.WorldGenRegion; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.feature.MonsterRoomFeature; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import zone.moddev.mc.basemetals.migration.LegacyWorldDataHook; + +/** Keeps dungeon generation out of imported terrain before it tries to place a spawner. */ +@Mixin(MonsterRoomFeature.class) +abstract class MonsterRoomFeatureMixin { + @Inject(method = "place(Lnet/minecraft/world/level/WorldGenLevel;Lnet/minecraft/world/level/chunk/ChunkGenerator;Lnet/minecraft/util/RandomSource;Lnet/minecraft/core/BlockPos;)Z", + at = @At("HEAD"), cancellable = true) + private void basemetals$leaveImportedRoomsAlone(WorldGenLevel level, ChunkGenerator generator, + RandomSource random, BlockPos position, CallbackInfoReturnable callback) { + if (level instanceof WorldGenRegion region + && LegacyWorldDataHook.shouldBlockWorldgenWrite(region, position)) { + callback.setReturnValue(false); + } + } +} diff --git a/src/main/java/zone/moddev/mc/basemetals/recipe/ContentCraftingRecipe.java b/src/main/java/zone/moddev/mc/basemetals/recipe/ContentCraftingRecipe.java index 79159a3ce..6cb8208d1 100644 --- a/src/main/java/zone/moddev/mc/basemetals/recipe/ContentCraftingRecipe.java +++ b/src/main/java/zone/moddev/mc/basemetals/recipe/ContentCraftingRecipe.java @@ -14,6 +14,7 @@ import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.item.crafting.RecipeSerializer; import net.minecraft.world.item.crafting.display.RecipeDisplay; +import net.minecraft.world.item.crafting.display.SlotDisplay; import net.minecraft.world.level.Level; import net.minecraftforge.registries.ForgeRegistries; import zone.moddev.mc.basemetals.config.ContentPolicy; @@ -22,14 +23,23 @@ public final class ContentCraftingRecipe implements CraftingRecipe { public static final RecipeSerializer SERIALIZER = Serializer.create(); private final CraftingRecipe plain; + private final String resultItem; private ContentCraftingRecipe(CraftingRecipe plain) { this.plain = plain; + List displays = plain.display(); + + if (displays.size() != 1 + || !(displays.getFirst().result() instanceof SlotDisplay.ItemStackSlotDisplay result)) { + throw new IllegalArgumentException("Content-mode recipes need a fixed crafting output"); + } + + // Recipe loading happens before item components are ready to make stacks. + this.resultItem = ForgeRegistries.ITEMS.getKey(result.stack().item().value()).toString(); } private boolean allowed() { - ItemStack result = plain.assemble(CraftingInput.EMPTY); - return ContentPolicy.active().allows(ForgeRegistries.ITEMS.getKey(result.getItem()).toString()); + return ContentPolicy.active().allows(resultItem); } private static ContentCraftingRecipe wrap(Recipe recipe) { @@ -86,11 +96,11 @@ public RecipeSerializer getSerializer() { } private static final class Serializer { - private static final MapCodec CODEC = Recipe.CODEC.fieldOf("recipe") + private static final MapCodec CODEC = Recipe.DIRECT_CODEC.fieldOf("recipe") .xmap(ContentCraftingRecipe::wrap, recipe -> recipe.plain); // Preserve empty pattern slots and tag names instead of flattening the ingredients. private static final StreamCodec STREAM_CODEC = - ByteBufCodecs.fromCodecWithRegistries(Recipe.CODEC) + ByteBufCodecs.fromCodecWithRegistries(Recipe.DIRECT_CODEC) .map(ContentCraftingRecipe::wrap, recipe -> recipe.plain); private static RecipeSerializer create() { diff --git a/src/main/java/zone/moddev/mc/basemetals/recipe/LegacySmeltingRecipe.java b/src/main/java/zone/moddev/mc/basemetals/recipe/LegacySmeltingRecipe.java index cfe299618..96808dd50 100644 --- a/src/main/java/zone/moddev/mc/basemetals/recipe/LegacySmeltingRecipe.java +++ b/src/main/java/zone/moddev/mc/basemetals/recipe/LegacySmeltingRecipe.java @@ -44,7 +44,7 @@ public static final class Serializer { private Serializer() {} public static RecipeSerializer create() { - return new RecipeSerializer<>(cookingMapCodec(LegacySmeltingRecipe::new, 200), + return new RecipeSerializer<>(cookingMapCodec(LegacySmeltingRecipe::new), cookingStreamCodec(LegacySmeltingRecipe::new)); } } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 1b932ddd2..ac2e67484 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -14,7 +14,7 @@ description='''${mod_description}''' [[dependencies.${mod_id}]] modId="orespawn" mandatory=true -versionRange="[4.1.0.2602001,5.0.0)" +versionRange="[4.1.0.2603001,5.0.0)" ordering="AFTER" side="BOTH" diff --git a/src/main/resources/basemetals.mixins.json b/src/main/resources/basemetals.mixins.json index a6b98faa4..0a223a165 100644 --- a/src/main/resources/basemetals.mixins.json +++ b/src/main/resources/basemetals.mixins.json @@ -9,6 +9,7 @@ "BlockStateDataMixin", "LevelCapabilityDataMixin", "LevelStorageSourceMixin", + "MonsterRoomFeatureMixin", "OreSpawnEarlyTagCompatibilityMixin", "SimpleRegionStorageMixin", "WorldGenRegionMixin" diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index 9ea2b4f5f..328c020bc 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,7 +1,7 @@ { "pack": { "description": "${mod_id} resources", - "min_format": [107, 1], - "max_format": [107, 1] + "min_format": [121, 0], + "max_format": [121, 0] } } diff --git a/src/packagedClientSmoke/packaged-resources/META-INF/mods.toml b/src/packagedClientSmoke/packaged-resources/META-INF/mods.toml index 6ad3ab50d..8ff095bcb 100644 --- a/src/packagedClientSmoke/packaged-resources/META-INF/mods.toml +++ b/src/packagedClientSmoke/packaged-resources/META-INF/mods.toml @@ -10,13 +10,13 @@ displayName="Base Metals packaged client smoke" [[dependencies.basemetals_client_smoke]] modId="basemetals" mandatory=true -versionRange="[3.1.1.2602001]" +versionRange="[3.1.1.2603001]" ordering="AFTER" side="CLIENT" [[dependencies.basemetals_client_smoke]] modId="orespawn" mandatory=true -versionRange="[4.1.0.2602001]" +versionRange="[4.1.0.2603001]" ordering="AFTER" side="CLIENT" diff --git a/src/packagedServerSmoke/java/zone/moddev/mc/basemetals/smoke/PackagedServerSmoke.java b/src/packagedServerSmoke/java/zone/moddev/mc/basemetals/smoke/PackagedServerSmoke.java index cb5cdfff3..7520225d2 100644 --- a/src/packagedServerSmoke/java/zone/moddev/mc/basemetals/smoke/PackagedServerSmoke.java +++ b/src/packagedServerSmoke/java/zone/moddev/mc/basemetals/smoke/PackagedServerSmoke.java @@ -40,7 +40,7 @@ public PackagedServerSmoke() { public void onServerStarted(ServerStartedEvent event) { String baseMetals = version("basemetals"); String oreSpawn = version("orespawn"); - if (!"3.1.1.2602001".equals(baseMetals) || !"4.1.0.2602001".equals(oreSpawn)) { + if (!"3.1.1.2603001".equals(baseMetals) || !"4.1.0.2603001".equals(oreSpawn)) { throw new IllegalStateException("PACKAGED_SERVER_SMOKE FAIL versions basemetals=" + baseMetals + " orespawn=" + oreSpawn); } @@ -80,8 +80,7 @@ private static void verifyContent(net.minecraft.server.MinecraftServer server) { for (var entry : items.entrySet()) { String id = entry.getKey(); if (id.endsWith("_powder") || id.endsWith("_smallpowder") || id.endsWith("_nugget")) { - int burn = server.fuelValues().burnDuration(entry.getValue().get().getDefaultInstance(), - net.minecraft.world.item.crafting.RecipeType.SMELTING); + int burn = new TestFurnace().burnDuration(level, entry.getValue().get().getDefaultInstance()); int expected = id.startsWith("coal_") || id.startsWith("charcoal_") ? (id.endsWith("_powder") ? 1600 : 200) : 0; if (burn != expected) throw new IllegalStateException(id + " has burn time " + burn + ", expected " + expected); @@ -103,6 +102,16 @@ private static void verifyContent(net.minecraft.server.MinecraftServer server) { zone.moddev.mc.basemetals.config.BaseMetalsConfig.activeMode().serializedName(), items.size()); } + private static final class TestFurnace extends net.minecraft.world.level.block.entity.FurnaceBlockEntity { + private TestFurnace() { + super(net.minecraft.core.BlockPos.ZERO, net.minecraft.world.level.block.Blocks.FURNACE.defaultBlockState()); + } + + private int burnDuration(net.minecraft.server.level.ServerLevel level, net.minecraft.world.item.ItemStack stack) { + return getBurnDuration(level, stack); + } + } + private static void verifyIntegrationRecipes(net.minecraft.server.MinecraftServer server) throws IOException { var policy = zone.moddev.mc.basemetals.config.ContentPolicy.active(); int checked = 0; @@ -176,7 +185,7 @@ private static void verifyOreSample(net.minecraft.server.MinecraftServer server) } private static void verifyTinkers() { - throw new IllegalStateException("Tinkers integration is not enabled in this Forge 26.2 build"); + throw new IllegalStateException("Tinkers integration is not enabled in this Forge 26.3 build"); } private static void verifyModernFlatTerrain(net.minecraft.server.MinecraftServer server) { diff --git a/src/packagedServerSmoke/packaged-resources/META-INF/mods.toml b/src/packagedServerSmoke/packaged-resources/META-INF/mods.toml index 898db2c93..42d533b43 100644 --- a/src/packagedServerSmoke/packaged-resources/META-INF/mods.toml +++ b/src/packagedServerSmoke/packaged-resources/META-INF/mods.toml @@ -10,13 +10,13 @@ displayName="Base Metals packaged server smoke" [[dependencies.basemetals_server_smoke]] modId="basemetals" mandatory=true -versionRange="[3.1.1.2602001]" +versionRange="[3.1.1.2603001]" ordering="AFTER" side="SERVER" [[dependencies.basemetals_server_smoke]] modId="orespawn" mandatory=true -versionRange="[4.1.0.2602001]" +versionRange="[4.1.0.2603001]" ordering="AFTER" side="SERVER" diff --git a/src/smoke/datapack/pack.mcmeta b/src/smoke/datapack/pack.mcmeta index 0b61bc891..694a36be6 100644 --- a/src/smoke/datapack/pack.mcmeta +++ b/src/smoke/datapack/pack.mcmeta @@ -1,7 +1,7 @@ { "pack": { - "min_format": [107, 1], - "max_format": [107, 1], + "min_format": [121, 0], + "max_format": [121, 0], "description": "Base Metals custom ordinary dimension smoke fixture" } } diff --git a/src/test/java/zone/moddev/mc/basemetals/data/Minecraft1211ResourceTest.java b/src/test/java/zone/moddev/mc/basemetals/data/Minecraft1211ResourceTest.java index 95c17fc82..492aeb149 100644 --- a/src/test/java/zone/moddev/mc/basemetals/data/Minecraft1211ResourceTest.java +++ b/src/test/java/zone/moddev/mc/basemetals/data/Minecraft1211ResourceTest.java @@ -14,15 +14,15 @@ class Minecraft1211ResourceTest { @Test - void resourceAndDataPacksUseThe262Formats() throws Exception { + void resourceAndDataPacksUseThe263Formats() throws Exception { var pack = read("src/main/resources/pack.mcmeta").getAsJsonObject("pack"); - assertEquals("[107,1]", pack.get("min_format").toString()); - assertEquals("[107,1]", pack.get("max_format").toString()); + assertEquals("[121,0]", pack.get("min_format").toString()); + assertEquals("[121,0]", pack.get("max_format").toString()); net.minecraft.SharedConstants.tryDetectVersion(); var resources = net.minecraft.SharedConstants.getCurrentVersion() .packVersion(net.minecraft.server.packs.PackType.CLIENT_RESOURCES); - assertEquals(88, resources.major()); - assertEquals(0, resources.minor()); + assertEquals(97, resources.major()); + assertEquals(1, resources.minor()); } @Test @@ -46,7 +46,7 @@ void silkTouchPredicatesUseTheEnchantmentSetField() throws Exception { var loot = read("src/generated/resources/data/basemetals/loot_table/blocks/adamantine_ore.json"); var silk = loot.getAsJsonArray("pools").get(0).getAsJsonObject().getAsJsonArray("entries").get(0) .getAsJsonObject().getAsJsonArray("children").get(0).getAsJsonObject() - .getAsJsonArray("conditions").get(0).getAsJsonObject().getAsJsonObject("predicate") + .getAsJsonObject("condition").getAsJsonObject("predicate") .getAsJsonObject("predicates").getAsJsonArray("minecraft:enchantments").get(0).getAsJsonObject(); assertEquals("minecraft:silk_touch", silk.get("enchantments").getAsString()); @@ -54,6 +54,45 @@ void silkTouchPredicatesUseTheEnchantmentSetField() throws Exception { assertFalse(silk.has("predicates")); } + @Test + void lootAndTradeDispatchKeysUseTheNativeTypeField() throws Exception { + for (String directory : new String[] {"loot_table", "loot_modifiers", "villager_trade"}) { + Path root = Path.of("src/generated/resources/data/basemetals", directory); + + try (var paths = Files.walk(root)) { + for (Path path : paths.filter(file -> file.toString().endsWith(".json")).toList()) { + String json = Files.readString(path); + assertFalse(json.contains("\"condition\": \""), path.toString()); + assertFalse(json.contains("\"function\":"), path.toString()); + assertFalse(json.contains("\"functions\":"), path.toString()); + if (directory.equals("loot_table")) { + assertFalse(json.contains("\"conditions\":"), path.toString()); + } + } + } + } + + var predicate = read("src/generated/resources/data/basemetals/villager_trade/weaponsmith/steel_sword.json") + .getAsJsonObject("merchant_predicate"); + assertEquals("basemetals:trade_enabled", predicate.get("type").getAsString()); + var enchanted = read("src/generated/resources/data/basemetals/villager_trade/armorer/brass_helmet_enchanted.json"); + assertTrue(enchanted.has("given_item_modifier")); + assertFalse(enchanted.has("given_item_modifiers")); + + var door = read("src/generated/resources/data/basemetals/loot_table/blocks/steel_door.json") + .getAsJsonArray("pools").get(0).getAsJsonObject().getAsJsonObject("condition") + .getAsJsonArray("terms").get(1).getAsJsonObject(); + assertEquals("minecraft:match_block", door.get("type").getAsString()); + assertEquals("basemetals:steel_door", door.get("blocks").getAsString()); + assertEquals("lower", door.getAsJsonObject("state").get("half").getAsString()); + + var slab = read("src/generated/resources/data/basemetals/loot_table/blocks/steel_slab.json") + .getAsJsonArray("pools").get(0).getAsJsonObject().getAsJsonArray("entries").get(0) + .getAsJsonObject().getAsJsonArray("modifier").get(0).getAsJsonObject() + .getAsJsonObject("condition"); + assertEquals("double", slab.getAsJsonObject("state").get("type").getAsString()); + } + @Test void scythesKeepTheirPlantAndCobwebHarvestingTag() throws Exception { var values = read("src/main/resources/data/basemetals/tags/block/scythe_harvestable.json") diff --git a/src/test/java/zone/moddev/mc/basemetals/data/RecipeRegressionTest.java b/src/test/java/zone/moddev/mc/basemetals/data/RecipeRegressionTest.java index 2d7b7f4a6..b3a9133af 100644 --- a/src/test/java/zone/moddev/mc/basemetals/data/RecipeRegressionTest.java +++ b/src/test/java/zone/moddev/mc/basemetals/data/RecipeRegressionTest.java @@ -106,6 +106,8 @@ void existing113RecipeAdvancementCriteriaRemainAvailable() throws Exception { JsonObject criteria = read(DATA.resolve("advancement/recipes/adamantine_bow.json")) .getAsJsonObject("criteria"); assertEquals("minecraft:recipe_unlocked", criteria.getAsJsonObject("has_recipe").get("trigger").getAsString()); + assertEquals("basemetals:adamantine_bow", criteria.getAsJsonObject("has_recipe") + .getAsJsonObject("conditions").getAsJsonArray("recipes").get(0).getAsString()); assertEquals("#forge:rods/adamantine", criteria.getAsJsonObject("has_ingredient_1") .getAsJsonObject("conditions").getAsJsonArray("items").get(0).getAsJsonObject().get("items").getAsString()); } diff --git a/src/test/java/zone/moddev/mc/basemetals/data/ResourceIntegrityTest.java b/src/test/java/zone/moddev/mc/basemetals/data/ResourceIntegrityTest.java index 8fe3c56a9..fc18e2599 100644 --- a/src/test/java/zone/moddev/mc/basemetals/data/ResourceIntegrityTest.java +++ b/src/test/java/zone/moddev/mc/basemetals/data/ResourceIntegrityTest.java @@ -518,7 +518,7 @@ void customAnvilLootRetainsDamageWithoutUsingTheUnsafeVanillaTag() throws Except JsonObject loot = read(resource("data/basemetals/loot_table/blocks/stone_anvil.json")) .getAsJsonObject(); JsonArray functions = loot.getAsJsonArray("pools").get(0).getAsJsonObject() - .getAsJsonArray("entries").get(0).getAsJsonObject().getAsJsonArray("functions"); + .getAsJsonArray("entries").get(0).getAsJsonObject().getAsJsonArray("modifier"); assertTrue(functions.toString().contains("minecraft:copy_state")); assertTrue(functions.toString().contains("\"damage\"")); } diff --git a/src/test/java/zone/moddev/mc/basemetals/migration/LegacyBlockStateCodecTest.java b/src/test/java/zone/moddev/mc/basemetals/migration/LegacyBlockStateCodecTest.java new file mode 100644 index 000000000..ea4219175 --- /dev/null +++ b/src/test/java/zone/moddev/mc/basemetals/migration/LegacyBlockStateCodecTest.java @@ -0,0 +1,52 @@ +package zone.moddev.mc.basemetals.migration; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import com.mojang.serialization.Dynamic; +import net.minecraft.SharedConstants; +import net.minecraft.core.Direction; +import net.minecraft.nbt.NbtOps; +import net.minecraft.server.Bootstrap; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.DoorBlock; +import net.minecraft.world.level.block.state.BlockState; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class LegacyBlockStateCodecTest { + @BeforeAll + static void bootstrap() { + SharedConstants.tryDetectVersion(); + Bootstrap.bootStrap(); + } + + @Test + void intermediatePalettesUseHistoricalFieldNames() { + var state = Blocks.OAK_DOOR.defaultBlockState() + .setValue(DoorBlock.FACING, Direction.WEST) + .setValue(DoorBlock.OPEN, true); + var tag = LegacyBlockStateCodec.writeFlattenedState(state); + + assertEquals("minecraft:oak_door", tag.getStringOr("Name", "")); + assertEquals("west", tag.getCompoundOrEmpty("Properties").getStringOr("facing", "")); + assertEquals("true", tag.getCompoundOrEmpty("Properties").getStringOr("open", "")); + assertFalse(tag.contains("id")); + assertFalse(tag.contains("properties")); + } + + @Test + void vanillaCanUpgradeTheIntermediateStateWithoutLosingItsProperties() { + BlockState state = Blocks.OAK_DOOR.defaultBlockState() + .setValue(DoorBlock.FACING, Direction.WEST) + .setValue(DoorBlock.OPEN, true); + var source = new Dynamic<>(NbtOps.INSTANCE, LegacyBlockStateCodec.writeFlattenedState(state)); + var updated = DataFixers.getDataFixer().update(References.BLOCK_STATE, source, + 1631, SharedConstants.getCurrentVersion().dataVersion().version()); + var restored = BlockState.CODEC.parse(NbtOps.INSTANCE, updated.getValue()).getOrThrow(); + + assertEquals(state, restored); + } +} diff --git a/src/test/java/zone/moddev/mc/basemetals/migration/LegacyMigrationSafetyTest.java b/src/test/java/zone/moddev/mc/basemetals/migration/LegacyMigrationSafetyTest.java index 402af8953..c3051b407 100644 --- a/src/test/java/zone/moddev/mc/basemetals/migration/LegacyMigrationSafetyTest.java +++ b/src/test/java/zone/moddev/mc/basemetals/migration/LegacyMigrationSafetyTest.java @@ -100,7 +100,7 @@ void modernExternalChunk() throws Exception { @Test void modernLz4Chunk() throws Exception { - assumeTrue(VERSION.equals("1.20.6") || VERSION.equals("1.21.11") || VERSION.equals("26.2"), "Native LZ4 is not available on older targets"); + assumeTrue(VERSION.equals("1.20.6") || VERSION.equals("1.21.11") || VERSION.equals("26.3"), "Native LZ4 is not available on older targets"); Path world = world("lz4", false); Path file = region(world, false, true); assertTrue(nativeAccepts(file, false)); @@ -177,7 +177,7 @@ void savedRegistryCanBeScannedWithAnEmptyRegion() throws Exception { @Test void savedRegistryCanBeScannedWithLz4Chunks() throws Exception { - assumeTrue(VERSION.equals("1.20.6") || VERSION.equals("1.21.11") || VERSION.equals("26.2")); + assumeTrue(VERSION.equals("1.20.6") || VERSION.equals("1.21.11") || VERSION.equals("26.3")); Path world = world("saved-registry-lz4", false); writeRegistry(world.resolve("basemetals-legacy-registry.dat")); Path file = region(world, false, true); diff --git a/src/test/java/zone/moddev/mc/basemetals/migration/LegacyWorldDataHookTest.java b/src/test/java/zone/moddev/mc/basemetals/migration/LegacyWorldDataHookTest.java index 95c9c5ceb..c1d1aa356 100644 --- a/src/test/java/zone/moddev/mc/basemetals/migration/LegacyWorldDataHookTest.java +++ b/src/test/java/zone/moddev/mc/basemetals/migration/LegacyWorldDataHookTest.java @@ -9,6 +9,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.StringTag; import net.minecraft.resources.Identifier; class LegacyWorldDataHookTest { @@ -26,6 +27,25 @@ void levelHookPassesTheFileBeforeComponentConversion() throws IOException { assertFalse(hook.contains("@Mixin(LevelStorageSource.class)")); } + @Test + void dungeonHookChecksTheExistingTerrainBoundaryBeforePlacement() throws Exception { + var method = net.minecraft.world.level.levelgen.feature.MonsterRoomFeature.class.getDeclaredMethod( + "place", net.minecraft.world.level.WorldGenLevel.class, + net.minecraft.world.level.chunk.ChunkGenerator.class, net.minecraft.util.RandomSource.class, + BlockPos.class); + assertEquals(boolean.class, method.getReturnType()); + + String hook = Files.readString(Path.of( + "src/main/java/zone/moddev/mc/basemetals/mixin/MonsterRoomFeatureMixin.java")); + assertTrue(hook.contains("@Mixin(MonsterRoomFeature.class)")); + assertTrue(hook.contains("at = @At(\"HEAD\")")); + assertTrue(hook.contains("level instanceof WorldGenRegion region")); + assertTrue(hook.contains("shouldBlockWorldgenWrite(region, position)")); + assertFalse(hook.contains("getBlockEntity")); + assertTrue(Files.readString(Path.of("src/main/resources/basemetals.mixins.json")) + .contains("\"MonsterRoomFeatureMixin\"")); + } + @Test void obsoleteVanillaBiomeSnapshotIsRemovedWithoutChangingSavedBiomesOrOtherRegistries() { CompoundTag root = biomeSnapshot("minecraft:plains", "minecraft:mountains"); @@ -169,6 +189,68 @@ void modernChunkCleanupRemovesOnlyBedsAtBaseMetalsBlocks() { assertEquals(17, entities.getCompoundOrEmpty(0).getIntOr("y", 0)); } + @Test + void nativeStateObjectsKeepPhantomBedCleanupScopedToBaseMetals() { + assertNativeBedCleanup(false); + } + + @Test + void compactNativeStatesKeepPhantomBedCleanupScopedToBaseMetals() { + assertNativeBedCleanup(true); + } + + private static void assertNativeBedCleanup(boolean compact) { + CompoundTag root = new CompoundTag(); + ListTag sections = new ListTag(); + sections.add(nativeSection(0, "basemetals:steel_block", compact)); + sections.add(nativeSection(1, "minecraft:red_bed", compact)); + root.put("sections", sections); + + ListTag entities = new ListTag(); + entities.add(bed(1)); + entities.add(bed(17)); + root.put("block_entities", entities); + + assertEquals(1, LegacyWorldDataHook.removePhantomBedEntities(root)); + assertEquals(1, entities.size()); + assertEquals(17, entities.getCompoundOrEmpty(0).getIntOr("y", 0)); + } + + private static CompoundTag nativeSection(int y, String block, boolean compact) { + CompoundTag section = section(y, block); + ListTag palette = new ListTag(); + + if (compact) { + palette.add(StringTag.valueOf(block)); + } else { + CompoundTag state = new CompoundTag(); + state.putString("id", block); + palette.add(state); + } + + section.getCompoundOrEmpty("block_states").put("palette", palette); + return section; + } + + @Test + void nativeWallFieldsKeepConnectionValuesDuringAnUpgrade() { + CompoundTag wall = new CompoundTag(); + wall.putString("id", "basemetals:steel_wall"); + CompoundTag properties = new CompoundTag(); + properties.putString("north", "true"); + properties.putString("east", "false"); + properties.putString("south", "tall"); + wall.put("properties", properties); + + assertEquals(2, LegacyWorldDataHook.migrateWallStates(wall)); + assertEquals("low", properties.getStringOr("north", "")); + assertEquals("none", properties.getStringOr("east", "")); + assertEquals("tall", properties.getStringOr("south", "")); + assertEquals(0, LegacyWorldDataHook.migrateWallStates(wall)); + assertFalse(wall.contains("Name")); + assertFalse(wall.contains("Properties")); + } + @Test void convertedChunksKeepBelowZeroRetrogen() { CompoundTag root = new CompoundTag(); diff --git a/tools/validation/README.md b/tools/validation/README.md index 749f15b7e..3998d62b1 100644 --- a/tools/validation/README.md +++ b/tools/validation/README.md @@ -1,11 +1,11 @@ # World upgrade checks -`test_legacy_worlds.js` starts the packaged Forge 26.2 server against a fresh +`test_legacy_worlds.js` starts the packaged Forge 26.3 server against a fresh copy of each supplied world, then starts it again to check the saved result. It refuses to overwrite an existing test directory and fingerprints every source world before and after the test. -An optional `forgeVersion` selects another installed Forge 26.2 build for -compatibility checks; the default is the compilation target, `65.1.0`. +An optional `forgeVersion` selects another installed Forge 26.3 build for +compatibility checks; the default is the compilation target, `66.0.0`. Pass a local JSON specification containing `java` (the Java 25 executable), `serverRuntime`, `output`, `modJar`, `probeJar`, `oreSpawn` (a `path` and `sha256`), and a `profiles` list. @@ -24,6 +24,9 @@ OreSpawn profile and verifies that those values survive both starts. The log check accepts known vanilla registry changes, including paintings moved to data files. Unknown or modded entries still fail the test. Check that boundary with `node --test tools/validation/test_painting_registry.js`. +The retired vanilla `minecraft:is_tempted` memory is accepted only in its +original memory registry. Unknown and modded entries still fail; +`test_vanilla_registry.js` checks that boundary. On Windows, the exact missing-Perflib-counter diagnostic from Minecraft's system probe is reported separately. It is not accepted on Linux, from another logger @@ -59,3 +62,5 @@ Its local specification needs `java` (the Java 25 executable), `serverRuntime`, `output`, `modJar`, `serverProbe`, `clientProbe`, and the same checksum-pinned `oreSpawn` object used by the world tests. The test uses localhost only and chooses a free port; no public Minecraft server is involved. +The disposable login servers explicitly disable the whitelist, which Minecraft +26.3 enables by default. Normal server settings are not changed. diff --git a/tools/validation/compare_current_world.py b/tools/validation/compare_current_world.py index 03c4722d5..bcf8bf66f 100644 --- a/tools/validation/compare_current_world.py +++ b/tools/validation/compare_current_world.py @@ -13,7 +13,7 @@ import nbtlib -from compare_world_terrain import block_names, chunks +from compare_world_terrain import block_names, chunks, state_name, unwrap_state from world_paths import dimensions, player_files @@ -126,9 +126,30 @@ def canonical_player(player): return result +def canonical_block_state(state, defaults): + state = unwrap_state(state) + identifier = state_name(state) + properties = dict(defaults.get(identifier, {})) + + if isinstance(state, (dict, nbtlib.Compound)): + properties.update({str(key): str(value) for key, value in + state.get('properties', state.get('Properties', {})).items()}) + elif identifier.startswith('basemetals:') and identifier not in defaults: + raise AssertionError(f'No registered default state for compact palette entry: {identifier}') + + if identifier.startswith('basemetals:') and identifier.endswith('_wall'): + for direction in ('north', 'south', 'east', 'west'): + if properties.get(direction) in ('false', 'true'): + properties[direction] = 'low' if properties[direction] == 'true' else 'none' + + return json.dumps({'id': identifier, 'properties': properties}, sort_keys=True) + + def snapshot(world): blocks = {} stacks = Counter() + manifest = Path(__file__).resolve().parents[2] / 'src/generated/resources/data/basemetals/registry_manifest.json' + defaults = json.loads(manifest.read_text(encoding='utf8')).get('block_defaults', {}) def items(value): if isinstance(value, (dict, nbtlib.Compound)): @@ -149,19 +170,18 @@ def items(value): for key, chunk in chunks(world).items(): for section in chunk.get("sections", chunk.get("Sections", [])): original_palette = section.get('block_states', {}).get('palette', section.get('Palette', [])) - if not any(str(entry.get('Name', '')).startswith('basemetals:') for entry in original_palette): + if not any(state_name(entry).startswith('basemetals:') for entry in original_palette): continue source = copy.deepcopy(section) palette = source.get("block_states", {}).get("palette", source.get("Palette", [])) - for entry in palette: - # Older wall connections used booleans; Minecraft now stores none, low or tall. - if str(entry.get("Name", "")).startswith("basemetals:") and str(entry["Name"]).endswith("_wall"): - properties = entry.get("Properties", {}) - for direction in ("north", "south", "east", "west"): - if str(properties.get(direction, "")) in ("false", "true"): - properties[direction] = nbtlib.String("low" if str(properties[direction]) == "true" else "none") - state = ordered(entry).snbt() - entry["Name"] = nbtlib.String(state) + canonical = nbtlib.List[nbtlib.Compound]([ + nbtlib.Compound({'Name': nbtlib.String(canonical_block_state(entry, defaults))}) + for entry in palette + ]) + if 'block_states' in source: + source['block_states']['palette'] = canonical + else: + source['Palette'] = canonical for index, state in enumerate(block_names(source)): if state and "basemetals:" in state: blocks[(*key, int(section["Y"]), index)] = state diff --git a/tools/validation/compare_world_terrain.py b/tools/validation/compare_world_terrain.py index f3042660c..aad47d6e5 100644 --- a/tools/validation/compare_world_terrain.py +++ b/tools/validation/compare_world_terrain.py @@ -53,6 +53,20 @@ def chunks(world): return result +def unwrap_state(state): + # Native mixed lists wrap entries that are not already compounds. + while isinstance(state, (dict, nbtlib.Compound)) and len(state) == 1 and "" in state: + state = state[""] + return state + + +def state_name(state): + state = unwrap_state(state) + if isinstance(state, (str, nbtlib.String)): + return str(state) + return str(state.get("id", state.get("Name", ""))) + + def block_names(section): if "Blocks" in section: blocks = section["Blocks"] @@ -68,7 +82,7 @@ def block_names(section): return block_states = section.get("block_states", {}) - palette = [str(state["Name"]) for state in section.get("Palette", block_states.get("palette", []))] + palette = [state_name(state) for state in section.get("Palette", block_states.get("palette", []))] if not palette: yield from [None] * 4096 return @@ -110,13 +124,13 @@ def collect(dimension, entities): name = str(item.get("id", "")) if name in FALLING_BLOCKS: dropped_items.append({"dimension": dimension, "name": name, - "count": int(item.get("Count", 0)), + "count": int(item.get("count", item.get("Count", 0))), "position": tuple(float(value) for value in entity["Pos"]), "uuid": tuple(entity.get("UUID", []))}) continue if str(entity.get("id", "")) != "minecraft:falling_block": continue - name = str(entity.get("BlockState", {}).get("Name", "")) + name = state_name(entity.get("BlockState", entity.get("block_state", {}))) if name not in FALLING_BLOCKS: continue x, y, z = [math.floor(float(value)) for value in entity["Pos"]] diff --git a/tools/validation/test_content_modes.js b/tools/validation/test_content_modes.js index b9b4de559..dc2e84676 100644 --- a/tools/validation/test_content_modes.js +++ b/tools/validation/test_content_modes.js @@ -94,7 +94,7 @@ function verifyConfig(directory, initial) { (async () => { const results = []; - const client = clientLaunch(spec.clientRuntime, '26.2', '65.1.0'); + const client = clientLaunch(spec.clientRuntime, '26.3', '66.0.0'); const selected = spec.cases ? cases.filter(([name]) => spec.cases.includes(name)) : cases; if (!selected.length || (spec.cases && selected.length !== spec.cases.length)) { throw new Error('The requested mode-test case is unknown or duplicated.'); @@ -109,19 +109,19 @@ function verifyConfig(directory, initial) { const port = await freePort(); fs.writeFileSync(path.join(serverDir, 'eula.txt'), 'eula=true\n'); fs.writeFileSync(path.join(serverDir, 'server.properties'), - `online-mode=false\nserver-ip=127.0.0.1\nserver-port=${port}\nview-distance=3\n` + `online-mode=false\nwhite-list=false\nserver-ip=127.0.0.1\nserver-port=${port}\nview-distance=3\n` + 'simulation-distance=3\nlevel-type=minecraft:flat\n' + 'generator-settings={"layers":[{"block":"minecraft:bedrock","height":1},{"block":"minecraft:dirt","height":2},{"block":"minecraft:grass_block","height":1}],"biome":"minecraft:plains"}\n' + 'generate-structures=false\nmax-tick-time=-1\n'); const server = launch(serverDir, ['-Xms256m', '-Xmx2g', '-Dbasemetals.awaitLoginSmoke=true', - ...serverArguments(spec.serverRuntime, serverDir, '26.2', '65.1.0')], 'server.log'); + ...serverArguments(spec.serverRuntime, serverDir, '26.3', '66.0.0')], 'server.log'); let player; try { await waitFor(server, 'BASEMETALS_PACKAGED_SERVER_SMOKE PASS', 240000); player = launch(clientDir, ['-Xms512m', '-Xmx2g', '-Dbasemetals.remoteLoginSmoke=true', `-Dbasemetals.remoteAddress=127.0.0.1:${port}`, `-Dbasemetals.loginSmokeConnections=${reject ? 1 : 3}`, `-Dbasemetals.expectModeReject=${!!reject}`, ...client.args, ...client.metadata.arguments.game, - '--username', 'BMValidation', '--version', '26.2', '--gameDir', clientDir, + '--username', 'BMValidation', '--version', '26.3', '--gameDir', clientDir, '--assetsDir', path.join(spec.clientRuntime, 'assets'), '--assetIndex', client.vanilla.assetIndex.id, '--uuid', '00000000000000000000000000000118', '--accessToken', '0', '--userType', 'legacy'], 'client.log'); await waitFor(player, 'BASEMETALS_MODE_LOGIN_CLIENT PASS result=' + (reject ? 'rejected' : 'connected'), 240000); diff --git a/tools/validation/test_current_world.py b/tools/validation/test_current_world.py index f0982dcb6..f6b1fa1ee 100644 --- a/tools/validation/test_current_world.py +++ b/tools/validation/test_current_world.py @@ -13,6 +13,39 @@ import compare_current_world as content +class NativeBlockStateTest(unittest.TestCase): + def setUp(self): + self.defaults = {'basemetals:steel_wall': { + 'north': 'none', 'south': 'none', 'east': 'none', 'west': 'none', + 'up': 'true', 'waterlogged': 'false', + }} + + def canonical(self, value): + return content.canonical_block_state(nbtlib.parse_nbt(value), self.defaults) + + def test_old_and_native_default_states_match(self): + old = '{Name:"basemetals:steel_wall",Properties:{north:"false",south:"false",east:"false",west:"false",up:"true",waterlogged:"false"}}' + native = '{id:"basemetals:steel_wall",properties:{north:"none",south:"none",east:"none",west:"none",up:"true",waterlogged:"false"}}' + self.assertEqual(self.canonical(old), self.canonical(native)) + self.assertEqual(self.canonical(old), self.canonical('"basemetals:steel_wall"')) + + def test_non_default_connections_are_not_lost(self): + old = '{Name:"basemetals:steel_wall",Properties:{north:"true",east:"tall"}}' + native = '{id:"basemetals:steel_wall",properties:{north:"low",east:"tall"}}' + self.assertEqual(self.canonical(old), self.canonical(native)) + self.assertNotEqual(self.canonical(old), self.canonical('"basemetals:steel_wall"')) + + def test_unknown_properties_are_not_discarded(self): + old = '{Name:"basemetals:steel_wall",Properties:{future_property:"keep_me"}}' + self.assertNotEqual(self.canonical(old), self.canonical('"basemetals:steel_wall"')) + + def test_native_mixed_palette_wrappers(self): + self.assertEqual(self.canonical('"basemetals:steel_wall"'), + self.canonical('{"":"basemetals:steel_wall"}')) + self.assertEqual(self.canonical('{id:"basemetals:steel_wall",properties:{east:"tall"}}'), + self.canonical('{"":{id:"basemetals:steel_wall",properties:{east:"tall"}}}')) + + class SavedEntitiesTest(unittest.TestCase): def test_entity_regions_include_ammunition_and_equipment(self): for compression in (1, 2): diff --git a/tools/validation/test_legacy_worlds.js b/tools/validation/test_legacy_worlds.js index fea11afc0..d41221e4b 100644 --- a/tools/validation/test_legacy_worlds.js +++ b/tools/validation/test_legacy_worlds.js @@ -5,6 +5,7 @@ const crypto = require('crypto'); const { spawn } = require('child_process'); const { serverArguments } = require('./forge_launcher'); const { isVanillaPaintingSnapshot } = require('./painting_registry'); +const { isRetiredVanillaMemorySnapshot } = require('./vanilla_registry'); const { isWindowsCounterDiagnostic } = require('./runtime_log'); const spec = JSON.parse(fs.readFileSync(process.argv[2], 'utf8')); @@ -132,7 +133,7 @@ function checkUpgradeLog(log) { 'minecraft:block.trial_spawner.charge_activate' ].includes(entry)); - if (knownPaintingSnapshot || knownVanillaNames) { + if (knownPaintingSnapshot || knownVanillaNames || isRetiredVanillaMemorySnapshot(registry, entries)) { entries.forEach(entry => acceptedEntries.add(entry)); continue; } @@ -154,7 +155,7 @@ async function run(directory, launcher, label, marker, properties = []) { const output = fs.createWriteStream(path.join(directory, label + '.log')); const executable = phase === 'capture' ? spec.java8 : (spec.java || spec.java17); const launchArguments = phase === 'capture' ? ['-jar', launcher, 'nogui'] - : serverArguments(spec.serverRuntime, directory, '26.2', spec.forgeVersion || '65.1.0'); + : serverArguments(spec.serverRuntime, directory, '26.3', spec.forgeVersion || '66.0.0'); const child = spawn(executable, ['-Xms256m', '-Xmx2g', ...properties, ...launchArguments], { cwd: directory, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] }); child.stdout.pipe(output, { end: false }); diff --git a/tools/validation/test_vanilla_registry.js b/tools/validation/test_vanilla_registry.js new file mode 100644 index 000000000..e797f8791 --- /dev/null +++ b/tools/validation/test_vanilla_registry.js @@ -0,0 +1,19 @@ +const assert = require('node:assert/strict'); +const test = require('node:test'); +const { isRetiredVanillaMemorySnapshot } = require('./vanilla_registry'); + +test('accepts the vanilla AI flag removed in 26.3', () => { + assert.equal(isRetiredVanillaMemorySnapshot('minecraft:memory_module_type', ['minecraft:is_tempted']), true); +}); + +test('rejects unknown or modded memories', () => { + for (const name of ['minecraft:unknown', 'basemetals:is_tempted']) { + assert.equal(isRetiredVanillaMemorySnapshot('minecraft:memory_module_type', + ['minecraft:is_tempted', name]), false); + } +}); + +test('does not excuse another registry or an empty report', () => { + assert.equal(isRetiredVanillaMemorySnapshot('minecraft:block', ['minecraft:is_tempted']), false); + assert.equal(isRetiredVanillaMemorySnapshot('minecraft:memory_module_type', []), false); +}); diff --git a/tools/validation/test_world_terrain.py b/tools/validation/test_world_terrain.py index df8f16856..ae6b3f385 100644 --- a/tools/validation/test_world_terrain.py +++ b/tools/validation/test_world_terrain.py @@ -12,6 +12,18 @@ def test_native_118_block_states(self): section = {"block_states": {"palette": [{"Name": "minecraft:deepslate"}]}} self.assertEqual(["minecraft:deepslate"] * 4096, list(terrain.block_names(section))) + def test_native_263_block_state_objects(self): + section = {"block_states": {"palette": [{"id": "minecraft:deepslate"}]}} + self.assertEqual(["minecraft:deepslate"] * 4096, list(terrain.block_names(section))) + + def test_native_263_compact_block_states(self): + section = {"block_states": {"palette": ["minecraft:stone"]}} + self.assertEqual(["minecraft:stone"] * 4096, list(terrain.block_names(section))) + + def test_native_263_mixed_palette_wrappers(self): + section = {"block_states": {"palette": [{"": "minecraft:stone"}]}} + self.assertEqual(["minecraft:stone"] * 4096, list(terrain.block_names(section))) + def test_contiguous_and_padded_palettes(self): for palette_size in (16, 17, 33, 65): bits = max(4, (palette_size - 1).bit_length()) @@ -41,6 +53,25 @@ def gravel(x=0.5, y=35, z=0.5, dimension=""): class FallingTerrainTest(unittest.TestCase): + def test_falling_blocks_accept_old_and_native_state_encodings(self): + for state in ({'Name': 'minecraft:gravel'}, {'id': 'minecraft:gravel'}, + 'minecraft:gravel', {'': 'minecraft:gravel'}): + entity = {'id': 'minecraft:falling_block', 'BlockState': state, 'Pos': [0.5, 35, 0.5]} + + with self.subTest(state=state), patch.object(terrain, 'dimensions', return_value=[]): + actual = terrain.falling_blocks(Path('unused'), {('', 0, 0): {'Entities': [entity]}}) + + self.assertEqual({('', 0, 0, 0): [(35, 'minecraft:gravel')]}, actual) + + def test_dropped_falling_blocks_keep_the_native_item_count(self): + entity = {'id': 'minecraft:item', 'Item': {'id': 'minecraft:sand', 'count': 2}, 'Pos': [0.5, 35, 0.5]} + dropped = [] + + with patch.object(terrain, 'dimensions', return_value=[]): + terrain.falling_blocks(Path('unused'), {('', 0, 0): {'Entities': [entity]}}, dropped) + + self.assertEqual(2, dropped[0]['count']) + def test_exposed_dirt_beside_old_grass_can_grow_normally(self): self.compare_surface("minecraft:dirt", "minecraft:grass_block", nearby_grass=True) diff --git a/tools/validation/vanilla_registry.js b/tools/validation/vanilla_registry.js new file mode 100644 index 000000000..d3f4b6973 --- /dev/null +++ b/tools/validation/vanilla_registry.js @@ -0,0 +1,10 @@ +'use strict'; + +// Minecraft 26.3 no longer registers this AI flag. +function isRetiredVanillaMemorySnapshot(registry, entries) { + return registry === 'minecraft:memory_module_type' + && entries.length > 0 + && entries.every(entry => entry === 'minecraft:is_tempted'); +} + +module.exports = { isRetiredVanillaMemorySnapshot }; From d8a7468dd441e02425d9b5312fe95efc2a2a444c Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Fri, 30 Oct 2026 10:21:09 +0000 Subject: [PATCH 2/4] Give Linux client checks a working graphics setup --- .github/workflows/ci.yml | 10 +++- README.md | 4 +- tools/validation/README.md | 5 ++ tools/validation/check_sdl_graphics.py | 78 ++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 tools/validation/check_sdl_graphics.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0ac0592c..bb890ac28 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,8 +19,11 @@ concurrency: jobs: build: name: Build, test, and audit - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 90 + env: + LIBGL_ALWAYS_SOFTWARE: '1' + SDL_VIDEO_DRIVER: x11 steps: - name: Check out source @@ -109,6 +112,11 @@ jobs: -PorespawnVerificationRepository=${{ steps.orespawn.outputs.repository }} --no-daemon --stacktrace + - name: Check the virtual display supports Minecraft's OpenGL context + run: >- + xvfb-run -a python3 tools/validation/check_sdl_graphics.py + "${{ steps.forge-runtimes.outputs.client_runtime }}/libraries/org/lwjgl/lwjgl-sdl/3.4.3/lwjgl-sdl-3.4.3-natives-linux.jar" + - name: Run exact packaged integrated-world client smoke run: >- timeout --kill-after=30s 15m xvfb-run -a ./gradlew packagedClientStartupSmoke diff --git a/README.md b/README.md index 71b88f908..5a1e234ff 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,9 @@ are isolated from the published JAR. Virtual-display client checks use `-PvirtualDisplaySmoke=true` to skip Forge's early splash window. Minecraft still opens its game window and runs the full -world check. Ordinary client launches keep the normal splash screen. +world check. Linux CI uses Ubuntu 26.04 with Mesa software rendering and checks +the SDL/OpenGL context before starting Minecraft. Ordinary client launches keep +the normal splash screen and graphics settings. Release artifacts use Maven coordinate `zone.moddev.mc.basemetals:BaseMetals:3.1.1.2603001`. diff --git a/tools/validation/README.md b/tools/validation/README.md index 3998d62b1..b5a78dd49 100644 --- a/tools/validation/README.md +++ b/tools/validation/README.md @@ -1,5 +1,10 @@ # World upgrade checks +The Linux client checks use Ubuntu 26.04, Xvfb and Mesa software rendering. +`check_sdl_graphics.py` first checks the same OpenGL 3.3 core and sRGB window +requirements as Minecraft. An unusable display fails immediately instead of +leaving the client waiting at a graphics error dialog. Normal launches are unchanged. + `test_legacy_worlds.js` starts the packaged Forge 26.3 server against a fresh copy of each supplied world, then starts it again to check the saved result. It refuses to overwrite an existing test directory and fingerprints every diff --git a/tools/validation/check_sdl_graphics.py b/tools/validation/check_sdl_graphics.py new file mode 100644 index 000000000..f74b48afe --- /dev/null +++ b/tools/validation/check_sdl_graphics.py @@ -0,0 +1,78 @@ +"""Check Minecraft's SDL/OpenGL requirements before starting a headless client.""" + +import argparse +import ctypes +import os +from pathlib import Path +import tempfile +from zipfile import ZipFile + + +def check_context(library: Path) -> None: + sdl = ctypes.CDLL(str(library)) + sdl.SDL_Init.argtypes = [ctypes.c_uint32] + sdl.SDL_Init.restype = ctypes.c_bool + sdl.SDL_GL_LoadLibrary.argtypes = [ctypes.c_char_p] + sdl.SDL_GL_LoadLibrary.restype = ctypes.c_bool + sdl.SDL_GL_SetAttribute.argtypes = [ctypes.c_int, ctypes.c_int] + sdl.SDL_GL_SetAttribute.restype = ctypes.c_bool + sdl.SDL_CreateWindow.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_int, ctypes.c_uint64] + sdl.SDL_CreateWindow.restype = ctypes.c_void_p + sdl.SDL_GL_CreateContext.argtypes = [ctypes.c_void_p] + sdl.SDL_GL_CreateContext.restype = ctypes.c_void_p + sdl.SDL_GL_DestroyContext.argtypes = [ctypes.c_void_p] + sdl.SDL_DestroyWindow.argtypes = [ctypes.c_void_p] + sdl.SDL_GetError.restype = ctypes.c_char_p + + def require(success: object, operation: str) -> None: + if not success: + error = sdl.SDL_GetError().decode('utf-8', errors='replace') + raise RuntimeError(f'{operation}: {error}') + + window = None + context = None + + try: + require(sdl.SDL_Init(0x20), 'SDL video initialization failed') + require(sdl.SDL_GL_LoadLibrary(b'libGL.so.1'), 'OpenGL library could not load') + + # Match GlBackend: OpenGL 3.3 core, forward-compatible, with an sRGB framebuffer. + for attribute, value in ((17, 3), (18, 3), (20, 1), (19, 2), (22, 1)): + require(sdl.SDL_GL_SetAttribute(attribute, value), 'OpenGL attribute was rejected') + + window = sdl.SDL_CreateWindow(b'Base Metals graphics check', 320, 480, 2147614730) + require(window, 'OpenGL window could not open') + context = sdl.SDL_GL_CreateContext(window) + require(context, 'OpenGL context could not open') + print('SDL_OPENGL_CONTEXT_PASS version=3.3 profile=core srgb=requested') + finally: + if context: + sdl.SDL_GL_DestroyContext(context) + if window: + sdl.SDL_DestroyWindow(window) + sdl.SDL_Quit() + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('native_jar', type=Path, help='The checksum-verified LWJGL SDL Linux native JAR') + args = parser.parse_args() + + if not os.environ.get('DISPLAY'): + raise RuntimeError('No X11 display is available. Run this check under xvfb-run.') + if os.environ.get('SDL_OPENGL_FORCE_SRGB_FRAMEBUFFER'): + raise RuntimeError('Remove the sRGB override; this check must use Minecraft\'s graphics requirements.') + + with tempfile.TemporaryDirectory(prefix='basemetals-sdl-check-') as directory: + library = Path(directory) / 'libSDL3.so' + with ZipFile(args.native_jar) as archive: + entries = [entry for entry in archive.namelist() if entry.endswith('/libSDL3.so')] + if len(entries) != 1: + raise RuntimeError('Expected exactly one SDL3 library in the verified native JAR') + library.write_bytes(archive.read(entries[0])) + + check_context(library) + + +if __name__ == '__main__': + main() From 7e4d952d3e2788e173ee303d37559e82d17ef8ac Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Fri, 30 Oct 2026 10:31:15 +0000 Subject: [PATCH 3/4] Install the software graphics drivers before client checks --- .github/workflows/ci.yml | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb890ac28..78d079e5d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,12 +23,28 @@ jobs: timeout-minutes: 90 env: LIBGL_ALWAYS_SOFTWARE: '1' + GALLIUM_DRIVER: llvmpipe SDL_VIDEO_DRIVER: x11 steps: - name: Check out source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + - name: Prepare Mesa software rendering + run: | + sudo apt-get update + sudo apt-get install --yes --no-install-recommends libgl1-mesa-dri libglx-mesa0 mesa-utils + xvfb-run -a -s '-screen 0 1280x1024x24 +extension GLX +render -noreset' glxinfo -B + + - name: Check the virtual display supports Minecraft's OpenGL context + run: | + native="$RUNNER_TEMP/basemetals-sdl-linux.jar" + curl --fail --location --retry 3 --output "$native" \ + https://libraries.minecraft.net/org/lwjgl/lwjgl-sdl/3.4.3/lwjgl-sdl-3.4.3-natives-linux.jar + echo "E1880683A4AF33299E9A1B8D7EC1799BED8FB709064EA08DCC2A15CD1101C0B3 $native" | sha256sum --check --strict + timeout 30s xvfb-run -a -s '-screen 0 1280x1024x24 +extension GLX +render -noreset' \ + python3 tools/validation/check_sdl_graphics.py "$native" + - name: Install pinned Java 25 runtime and toolchain uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: @@ -112,14 +128,11 @@ jobs: -PorespawnVerificationRepository=${{ steps.orespawn.outputs.repository }} --no-daemon --stacktrace - - name: Check the virtual display supports Minecraft's OpenGL context - run: >- - xvfb-run -a python3 tools/validation/check_sdl_graphics.py - "${{ steps.forge-runtimes.outputs.client_runtime }}/libraries/org/lwjgl/lwjgl-sdl/3.4.3/lwjgl-sdl-3.4.3-natives-linux.jar" - - name: Run exact packaged integrated-world client smoke run: >- - timeout --kill-after=30s 15m xvfb-run -a ./gradlew packagedClientStartupSmoke + timeout --kill-after=30s 15m xvfb-run -a + -s '-screen 0 1280x1024x24 +extension GLX +render -noreset' + ./gradlew packagedClientStartupSmoke -PvirtualDisplaySmoke=true -PpackagedForgeClientRuntime=${{ steps.forge-runtimes.outputs.client_runtime }} -PorespawnVerificationRepository=${{ steps.orespawn.outputs.repository }} @@ -127,7 +140,9 @@ jobs: - name: Run bounded development integrated-world client smoke run: >- - timeout --kill-after=30s 15m xvfb-run -a ./gradlew runClient + timeout --kill-after=30s 15m xvfb-run -a + -s '-screen 0 1280x1024x24 +extension GLX +render -noreset' + ./gradlew runClient -PvirtualDisplaySmoke=true -PintegratedWorldSmoke=true -PintegratedWorldSmokeName=basemetals-ci-${{ github.run_id }} From ab2dddded1421df86021be4522082edc8aa2c664 Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Fri, 30 Oct 2026 10:34:49 +0000 Subject: [PATCH 4/4] Use EGL for the virtual client display --- .github/workflows/ci.yml | 1 + README.md | 2 +- tools/validation/README.md | 2 +- tools/validation/check_sdl_graphics.py | 9 +++++++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78d079e5d..39ec71a84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,7 @@ jobs: LIBGL_ALWAYS_SOFTWARE: '1' GALLIUM_DRIVER: llvmpipe SDL_VIDEO_DRIVER: x11 + SDL_VIDEO_FORCE_EGL: '1' steps: - name: Check out source diff --git a/README.md b/README.md index 5a1e234ff..d614dd6e4 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ are isolated from the published JAR. Virtual-display client checks use `-PvirtualDisplaySmoke=true` to skip Forge's early splash window. Minecraft still opens its game window and runs the full -world check. Linux CI uses Ubuntu 26.04 with Mesa software rendering and checks +world check. Linux CI uses Ubuntu 26.04 with Mesa/EGL software rendering and checks the SDL/OpenGL context before starting Minecraft. Ordinary client launches keep the normal splash screen and graphics settings. diff --git a/tools/validation/README.md b/tools/validation/README.md index b5a78dd49..a46bbbda1 100644 --- a/tools/validation/README.md +++ b/tools/validation/README.md @@ -1,6 +1,6 @@ # World upgrade checks -The Linux client checks use Ubuntu 26.04, Xvfb and Mesa software rendering. +The Linux client checks use Ubuntu 26.04, Xvfb and Mesa software rendering through EGL. `check_sdl_graphics.py` first checks the same OpenGL 3.3 core and sRGB window requirements as Minecraft. An unusable display fails immediately instead of leaving the client waiting at a graphics error dialog. Normal launches are unchanged. diff --git a/tools/validation/check_sdl_graphics.py b/tools/validation/check_sdl_graphics.py index f74b48afe..e9e964e0b 100644 --- a/tools/validation/check_sdl_graphics.py +++ b/tools/validation/check_sdl_graphics.py @@ -14,6 +14,8 @@ def check_context(library: Path) -> None: sdl.SDL_Init.restype = ctypes.c_bool sdl.SDL_GL_LoadLibrary.argtypes = [ctypes.c_char_p] sdl.SDL_GL_LoadLibrary.restype = ctypes.c_bool + sdl.SDL_GL_GetProcAddress.argtypes = [ctypes.c_char_p] + sdl.SDL_GL_GetProcAddress.restype = ctypes.c_void_p sdl.SDL_GL_SetAttribute.argtypes = [ctypes.c_int, ctypes.c_int] sdl.SDL_GL_SetAttribute.restype = ctypes.c_bool sdl.SDL_CreateWindow.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_int, ctypes.c_uint64] @@ -36,6 +38,13 @@ def require(success: object, operation: str) -> None: require(sdl.SDL_Init(0x20), 'SDL video initialization failed') require(sdl.SDL_GL_LoadLibrary(b'libGL.so.1'), 'OpenGL library could not load') + # Minecraft also checks that SDL and LWJGL resolve the same OpenGL functions. + opengl = ctypes.CDLL('libGL.so.1') + opengl.glXGetProcAddress.argtypes = [ctypes.c_char_p] + opengl.glXGetProcAddress.restype = ctypes.c_void_p + if opengl.glXGetProcAddress(b'glGetError') != sdl.SDL_GL_GetProcAddress(b'glGetError'): + raise RuntimeError('SDL and the OpenGL loader resolve different glGetError functions') + # Match GlBackend: OpenGL 3.3 core, forward-compatible, with an sRGB framebuffer. for attribute, value in ((17, 3), (18, 3), (20, 1), (19, 2), (22, 1)): require(sdl.SDL_GL_SetAttribute(attribute, value), 'OpenGL attribute was rejected')