From f966175c0b3f2a0fa4dbe928b9ec46f57ccc1469 Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Sun, 8 Nov 2026 15:52:31 +0000 Subject: [PATCH] Update Modern Metals for Minecraft 1.11.2 Bring over the legacy build, settings and material fixes. Use the OreSpawn 4.1 provider and keep the existing 1.11.2 save IDs. --- .gitattributes | 11 + .github/workflows/ci.yml | 230 +++ .github/workflows/codeql-analysis.yml | 80 + .github/workflows/deploy-release.yml | 799 +++++++++ .github/workflows/release-on-tag.yml | 89 + .github/workflows/validate-gradle-build.yml | 23 + .gitignore | 13 +- .travis.yml | 13 - CHANGELOG.txt | 29 +- README.md | 109 +- build.gradle | 483 ++--- docs/BUILDING.md | 62 + docs/CONTENT_MODE.md | 57 + docs/ORE_GENERATION.md | 70 + docs/ORE_OVERLAYS.md | 31 + docs/RELEASING.md | 34 + gradle.properties | 58 +- gradle/dependencies.gradle | 75 + gradle/dependencies.json | 52 + gradle/ide/eclipse.gradle | 206 +++ gradle/ide/log4j2.xml | 21 + gradle/ore-models.gradle | 71 + gradle/prepare-packaged-forge-runtime.sh | 34 + gradle/release/artifacts.gradle | 184 ++ gradle/release/publishing.gradle | 96 + gradle/stage-legacy-dependencies.py | 53 + gradle/stage-legacy-dependencies.sh | 9 + gradle/verification/runtime.gradle | 48 + gradle/wrapper/gradle-wrapper.jar | Bin 54413 -> 48462 bytes gradle/wrapper/gradle-wrapper.properties | 7 +- gradlew | 302 ++-- gradlew.bat | 100 +- settings.gradle | 5 + .../com/mcmoddev/alt/ModernMetalsALT.java | 7 - .../modernmetals/init/Achievements.java | 59 - .../mcmoddev/modernmetals/init/Fluids.java | 88 - .../mcmoddev/modernmetals/init/Materials.java | 247 --- .../mcmoddev/modernmetals/init/Recipes.java | 83 - .../modernmetals/init/VillagerTrades.java | 33 - .../integration/plugins/EnderIO.java | 58 - .../modernmetals/integration/plugins/IC2.java | 22 - .../integration/plugins/Mekanism.java | 48 - .../integration/plugins/ThermalExpansion.java | 84 - .../integration/plugins/TinkersConstruct.java | 111 -- .../integration/plugins/VeinMiner.java | 55 - .../modernmetals/proxy/ClientProxy.java | 33 - .../modernmetals/proxy/CommonProxy.java | 83 - .../mcmoddev/modernmetals/util/Config.java | 68 - .../modernmetals/util/EventHandler.java | 43 - .../orespawn/ModernMetalsOreSpawn.java | 16 - .../moddev/mc}/modernmetals/ModernMetals.java | 38 +- .../modernmetals/block/OverlayOreBlock.java | 20 + .../client/ModernMetalsConfigScreen.java | 36 + .../client/ModernMetalsGuiFactory.java | 20 + .../compat/LegacyNamespaceTransformer.java | 89 + .../compat/LegacyRegistryMappings.java | 58 + .../compat/ModernMetalsLoadingPlugin.java | 19 + .../content/ContentModeNetwork.java | 64 + .../modernmetals/content/ContentPolicy.java | 84 + .../modernmetals/data/AchievementNames.java | 2 +- .../mc}/modernmetals/data/MaterialNames.java | 4 +- .../mc}/modernmetals/data/TraitNames.java | 2 +- .../mc/modernmetals/init/Achievements.java | 65 + .../moddev/mc}/modernmetals/init/Blocks.java | 29 +- .../moddev/mc/modernmetals/init/Fluids.java | 177 ++ .../mc}/modernmetals/init/ItemGroups.java | 8 +- .../moddev/mc}/modernmetals/init/Items.java | 4 +- .../mc/modernmetals/init/Materials.java | 162 ++ .../moddev/mc/modernmetals/init/Recipes.java | 86 + .../mc/modernmetals/init/VillagerTrades.java | 68 + .../integration/plugins/DenseOres.java | 9 +- .../integration/plugins/EnderIO.java | 105 ++ .../modernmetals/integration/plugins/IC2.java | 31 + .../integration/plugins/Mekanism.java | 68 + .../integration/plugins/Thaumcraft.java | 7 +- .../integration/plugins/ThermalExpansion.java | 76 + .../integration/plugins/TinkersConstruct.java | 151 ++ .../integration/plugins/VeinMiner.java | 105 ++ .../mc/modernmetals/proxy/ClientProxy.java | 86 + .../mc/modernmetals/proxy/CommonProxy.java | 63 + .../mc}/modernmetals/proxy/ServerProxy.java | 3 +- .../moddev/mc/modernmetals/util/Config.java | 145 ++ .../mc/modernmetals/util/EventHandler.java | 54 + .../ModernMetalsWorldgenProvider.java | 85 + .../META-INF/modernmetals-legacy-api.list | 25 + .../assets/modernmetals/lang/de_DE.lang | 350 +++- .../assets/modernmetals/lang/en_AU.lang | 51 + .../assets/modernmetals/lang/en_CA.lang | 51 + .../assets/modernmetals/lang/en_GB.lang | 96 + .../assets/modernmetals/lang/en_NZ.lang | 51 + .../assets/modernmetals/lang/en_PT.lang | 90 + .../assets/modernmetals/lang/en_US.lang | 39 + .../assets/modernmetals/lang/ru_RU.lang | 1556 +++++++++++++++++ .../assets/modernmetals/lang/zh_CN.lang | 45 +- .../models/block/aluminum_ore.json | 6 - .../models/block/aluminumbrass_ore.json | 6 - .../models/block/beryllium_ore.json | 6 - .../modernmetals/models/block/boron_ore.json | 6 - .../models/block/cadmium_ore.json | 6 - .../models/block/chromium_ore.json | 6 - .../models/block/galvanizedsteel_ore.json | 6 - .../models/block/iridium_ore.json | 6 - .../models/block/magnesium_ore.json | 6 - .../models/block/manganese_ore.json | 6 - .../models/block/nichrome_ore.json | 6 - .../modernmetals/models/block/osmium_ore.json | 6 - .../models/block/plutonium_ore.json | 6 - .../modernmetals/models/block/rutile_ore.json | 6 - .../models/block/stainlesssteel_ore.json | 6 - .../models/block/tantalum_ore.json | 6 - .../models/block/titanium_ore.json | 6 - .../models/block/tungsten_ore.json | 6 - .../models/block/uranium_ore.json | 6 - .../models/block/zirconium_ore.json | 6 - .../textures/blocks/aluminum_ore.png | Bin 668 -> 0 bytes .../textures/blocks/aluminumbrass_ore.png | Bin 699 -> 0 bytes .../textures/blocks/beryllium_ore.png | Bin 654 -> 0 bytes .../textures/blocks/boron_ore.png | Bin 670 -> 0 bytes .../textures/blocks/cadmium_ore.png | Bin 668 -> 0 bytes .../textures/blocks/chromium_ore.png | Bin 655 -> 0 bytes .../textures/blocks/galvanizedsteel_ore.png | Bin 663 -> 0 bytes .../textures/blocks/iridium_ore.png | Bin 688 -> 0 bytes .../textures/blocks/magnesium_ore.png | Bin 644 -> 0 bytes .../textures/blocks/manganese_ore.png | Bin 683 -> 0 bytes .../textures/blocks/nichrome_ore.png | Bin 691 -> 0 bytes .../blocks/ore_overlays/aluminum_ore.png | Bin 0 -> 391 bytes .../blocks/ore_overlays/aluminumbrass_ore.png | Bin 0 -> 418 bytes .../blocks/ore_overlays/beryllium_ore.png | Bin 0 -> 387 bytes .../blocks/ore_overlays/boron_ore.png | Bin 0 -> 331 bytes .../blocks/ore_overlays/cadmium_ore.png | Bin 0 -> 405 bytes .../blocks/ore_overlays/chromium_ore.png | Bin 0 -> 384 bytes .../ore_overlays/galvanizedsteel_ore.png | Bin 0 -> 398 bytes .../blocks/ore_overlays/iridium_ore.png | Bin 0 -> 408 bytes .../blocks/ore_overlays/magnesium_ore.png | Bin 0 -> 388 bytes .../blocks/ore_overlays/manganese_ore.png | Bin 0 -> 414 bytes .../blocks/ore_overlays/nichrome_ore.png | Bin 0 -> 421 bytes .../blocks/ore_overlays/osmium_ore.png | Bin 0 -> 407 bytes .../blocks/ore_overlays/plutonium_ore.png | Bin 0 -> 419 bytes .../blocks/ore_overlays/rutile_ore.png | Bin 0 -> 411 bytes .../ore_overlays/stainlesssteel_ore.png | Bin 0 -> 394 bytes .../blocks/ore_overlays/tantalum_ore.png | Bin 0 -> 390 bytes .../blocks/ore_overlays/titanium_ore.png | Bin 0 -> 398 bytes .../blocks/ore_overlays/tungsten_ore.png | Bin 0 -> 381 bytes .../blocks/ore_overlays/uranium_ore.png | Bin 0 -> 412 bytes .../blocks/ore_overlays/zirconium_ore.png | Bin 0 -> 400 bytes .../textures/blocks/osmium_ore.png | Bin 662 -> 0 bytes .../textures/blocks/plutonium_ore.png | Bin 704 -> 0 bytes .../textures/blocks/rutile_ore.png | Bin 672 -> 0 bytes .../textures/blocks/stainlesssteel_ore.png | Bin 669 -> 0 bytes .../textures/blocks/tantalum_ore.png | Bin 672 -> 0 bytes .../textures/blocks/titanium_ore.png | Bin 644 -> 0 bytes .../textures/blocks/tungsten_ore.png | Bin 621 -> 0 bytes .../textures/blocks/uranium_ore.png | Bin 690 -> 0 bytes .../textures/blocks/zirconium_ore.png | Bin 649 -> 0 bytes src/main/resources/pack.mcmeta | 2 +- .../mc/modernmetals/ContentPolicyTests.java | 39 + .../MaterialConfigurationTests.java | 17 + .../mc/modernmetals/NativeProviderTests.java | 56 + .../LegacyNamespaceTransformerTests.java | 87 + .../util/ContentModeConfigurationTests.java | 41 + .../legacy111/EclipseModelCheck.java | 95 + tools/validation/legacy111/README.md | 105 ++ tools/validation/legacy111/audit_resources.py | 119 ++ .../legacy111/check_content_modes.py | 59 + .../legacy111/check_multiplayer_mode.py | 84 + .../legacy111/check_original_saves.py | 111 ++ .../legacy111/dependency_requirements.py | 44 + .../fixtures/orespawn3-modernmetals.json | 0 .../legacy111/run_eclipse_profile.py | 72 + .../legacy111/run_packaged_client.py | 170 ++ .../legacy111/run_packaged_suite.py | 192 ++ .../legacy111/run_runtime_checks.py | 94 + .../review/modern/ClientChecks.java | 280 +++ .../review/modern/ModernMetalsProbe.java | 322 ++++ .../modern/OptionalIntegrationChecks.java | 121 ++ .../review/modern/OreOverlayChecks.java | 104 ++ tools/validation/requirements.txt | 2 + update.json | 8 +- 178 files changed, 9347 insertions(+), 1966 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 .github/workflows/deploy-release.yml create mode 100644 .github/workflows/release-on-tag.yml create mode 100644 .github/workflows/validate-gradle-build.yml delete mode 100644 .travis.yml create mode 100644 docs/BUILDING.md create mode 100644 docs/CONTENT_MODE.md create mode 100644 docs/ORE_GENERATION.md create mode 100644 docs/ORE_OVERLAYS.md create mode 100644 docs/RELEASING.md create mode 100644 gradle/dependencies.gradle create mode 100644 gradle/dependencies.json create mode 100644 gradle/ide/eclipse.gradle create mode 100644 gradle/ide/log4j2.xml create mode 100644 gradle/ore-models.gradle create mode 100644 gradle/prepare-packaged-forge-runtime.sh create mode 100644 gradle/release/artifacts.gradle create mode 100644 gradle/release/publishing.gradle create mode 100644 gradle/stage-legacy-dependencies.py create mode 100644 gradle/stage-legacy-dependencies.sh create mode 100644 gradle/verification/runtime.gradle create mode 100644 settings.gradle delete mode 100644 src/main/java/com/mcmoddev/alt/ModernMetalsALT.java delete mode 100644 src/main/java/com/mcmoddev/modernmetals/init/Achievements.java delete mode 100644 src/main/java/com/mcmoddev/modernmetals/init/Fluids.java delete mode 100644 src/main/java/com/mcmoddev/modernmetals/init/Materials.java delete mode 100644 src/main/java/com/mcmoddev/modernmetals/init/Recipes.java delete mode 100644 src/main/java/com/mcmoddev/modernmetals/init/VillagerTrades.java delete mode 100644 src/main/java/com/mcmoddev/modernmetals/integration/plugins/EnderIO.java delete mode 100644 src/main/java/com/mcmoddev/modernmetals/integration/plugins/IC2.java delete mode 100644 src/main/java/com/mcmoddev/modernmetals/integration/plugins/Mekanism.java delete mode 100755 src/main/java/com/mcmoddev/modernmetals/integration/plugins/ThermalExpansion.java delete mode 100644 src/main/java/com/mcmoddev/modernmetals/integration/plugins/TinkersConstruct.java delete mode 100644 src/main/java/com/mcmoddev/modernmetals/integration/plugins/VeinMiner.java delete mode 100644 src/main/java/com/mcmoddev/modernmetals/proxy/ClientProxy.java delete mode 100644 src/main/java/com/mcmoddev/modernmetals/proxy/CommonProxy.java delete mode 100644 src/main/java/com/mcmoddev/modernmetals/util/Config.java delete mode 100644 src/main/java/com/mcmoddev/modernmetals/util/EventHandler.java delete mode 100644 src/main/java/com/mcmoddev/orespawn/ModernMetalsOreSpawn.java rename src/main/java/{com/mcmoddev => zone/moddev/mc}/modernmetals/ModernMetals.java (56%) create mode 100644 src/main/java/zone/moddev/mc/modernmetals/block/OverlayOreBlock.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/client/ModernMetalsConfigScreen.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/client/ModernMetalsGuiFactory.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/compat/LegacyNamespaceTransformer.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/compat/LegacyRegistryMappings.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/compat/ModernMetalsLoadingPlugin.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/content/ContentModeNetwork.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/content/ContentPolicy.java rename src/main/java/{com/mcmoddev => zone/moddev/mc}/modernmetals/data/AchievementNames.java (94%) rename src/main/java/{com/mcmoddev => zone/moddev/mc}/modernmetals/data/MaterialNames.java (96%) rename src/main/java/{com/mcmoddev => zone/moddev/mc}/modernmetals/data/TraitNames.java (94%) create mode 100644 src/main/java/zone/moddev/mc/modernmetals/init/Achievements.java rename src/main/java/{com/mcmoddev => zone/moddev/mc}/modernmetals/init/Blocks.java (63%) create mode 100644 src/main/java/zone/moddev/mc/modernmetals/init/Fluids.java rename src/main/java/{com/mcmoddev => zone/moddev/mc}/modernmetals/init/ItemGroups.java (89%) rename src/main/java/{com/mcmoddev => zone/moddev/mc}/modernmetals/init/Items.java (97%) create mode 100644 src/main/java/zone/moddev/mc/modernmetals/init/Materials.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/init/Recipes.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/init/VillagerTrades.java rename src/main/java/{com/mcmoddev => zone/moddev/mc}/modernmetals/integration/plugins/DenseOres.java (86%) create mode 100644 src/main/java/zone/moddev/mc/modernmetals/integration/plugins/EnderIO.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/integration/plugins/IC2.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/integration/plugins/Mekanism.java rename src/main/java/{com/mcmoddev => zone/moddev/mc}/modernmetals/integration/plugins/Thaumcraft.java (64%) create mode 100644 src/main/java/zone/moddev/mc/modernmetals/integration/plugins/ThermalExpansion.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/integration/plugins/TinkersConstruct.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/integration/plugins/VeinMiner.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/proxy/ClientProxy.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/proxy/CommonProxy.java rename src/main/java/{com/mcmoddev => zone/moddev/mc}/modernmetals/proxy/ServerProxy.java (62%) create mode 100644 src/main/java/zone/moddev/mc/modernmetals/util/Config.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/util/EventHandler.java create mode 100644 src/main/java/zone/moddev/mc/modernmetals/worldgen/ModernMetalsWorldgenProvider.java create mode 100644 src/main/resources/META-INF/modernmetals-legacy-api.list create mode 100644 src/main/resources/assets/modernmetals/lang/en_AU.lang create mode 100644 src/main/resources/assets/modernmetals/lang/en_CA.lang create mode 100644 src/main/resources/assets/modernmetals/lang/en_GB.lang create mode 100644 src/main/resources/assets/modernmetals/lang/en_NZ.lang create mode 100644 src/main/resources/assets/modernmetals/lang/en_PT.lang create mode 100644 src/main/resources/assets/modernmetals/lang/ru_RU.lang delete mode 100644 src/main/resources/assets/modernmetals/models/block/aluminum_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/aluminumbrass_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/beryllium_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/boron_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/cadmium_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/chromium_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/galvanizedsteel_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/iridium_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/magnesium_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/manganese_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/nichrome_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/osmium_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/plutonium_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/rutile_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/stainlesssteel_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/tantalum_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/titanium_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/tungsten_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/uranium_ore.json delete mode 100644 src/main/resources/assets/modernmetals/models/block/zirconium_ore.json delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/aluminum_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/aluminumbrass_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/beryllium_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/boron_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/cadmium_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/chromium_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/galvanizedsteel_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/iridium_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/magnesium_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/manganese_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/nichrome_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/aluminum_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/aluminumbrass_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/beryllium_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/boron_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/cadmium_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/chromium_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/galvanizedsteel_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/iridium_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/magnesium_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/manganese_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/nichrome_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/osmium_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/plutonium_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/rutile_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/stainlesssteel_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/tantalum_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/titanium_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/tungsten_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/uranium_ore.png create mode 100644 src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/zirconium_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/osmium_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/plutonium_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/rutile_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/stainlesssteel_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/tantalum_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/titanium_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/tungsten_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/uranium_ore.png delete mode 100644 src/main/resources/assets/modernmetals/textures/blocks/zirconium_ore.png create mode 100644 src/test/java/zone/moddev/mc/modernmetals/ContentPolicyTests.java create mode 100644 src/test/java/zone/moddev/mc/modernmetals/MaterialConfigurationTests.java create mode 100644 src/test/java/zone/moddev/mc/modernmetals/NativeProviderTests.java create mode 100644 src/test/java/zone/moddev/mc/modernmetals/compat/LegacyNamespaceTransformerTests.java create mode 100644 src/test/java/zone/moddev/mc/modernmetals/util/ContentModeConfigurationTests.java create mode 100644 tools/validation/legacy111/EclipseModelCheck.java create mode 100644 tools/validation/legacy111/README.md create mode 100644 tools/validation/legacy111/audit_resources.py create mode 100644 tools/validation/legacy111/check_content_modes.py create mode 100644 tools/validation/legacy111/check_multiplayer_mode.py create mode 100644 tools/validation/legacy111/check_original_saves.py create mode 100644 tools/validation/legacy111/dependency_requirements.py rename src/main/resources/assets/modernmetals/orespawn/modernmetals.json => tools/validation/legacy111/fixtures/orespawn3-modernmetals.json (100%) mode change 100755 => 100644 create mode 100644 tools/validation/legacy111/run_eclipse_profile.py create mode 100644 tools/validation/legacy111/run_packaged_client.py create mode 100644 tools/validation/legacy111/run_packaged_suite.py create mode 100644 tools/validation/legacy111/run_runtime_checks.py create mode 100644 tools/validation/legacy111/runtime-probe/review/modern/ClientChecks.java create mode 100644 tools/validation/legacy111/runtime-probe/review/modern/ModernMetalsProbe.java create mode 100644 tools/validation/legacy111/runtime-probe/review/modern/OptionalIntegrationChecks.java create mode 100644 tools/validation/legacy111/runtime-probe/review/modern/OreOverlayChecks.java create mode 100644 tools/validation/requirements.txt diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..2dc7c427 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +* text=auto +*.java text eol=lf +*.gradle text eol=lf +*.json text eol=lf +*.yml text eol=lf +*.py text eol=lf +*.sh text eol=lf +gradlew text eol=lf +gradlew.bat text eol=crlf +*.png binary +*.jar binary diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..edabbad7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,230 @@ +name: Modern Metals 1.11 CI + +on: + push: + branches: + - master-1.11.2 + - 'feature/**' + pull_request: + branches: + - master-1.11.2 + +permissions: + contents: read + +concurrency: + group: modernmetals-1.11-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + cold-forge-bootstrap: + name: Cold Forge bootstrap + runs-on: ubuntu-latest + timeout-minutes: 75 + + steps: + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Install pinned Java 25 ForgeGradle Mavenizer runtime + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '25.0.3+9.0.LTS' + + - name: Install pinned Java 8 compilation toolchain + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '8.0.502+7' + + - name: Install pinned Java 17 Gradle runtime + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '17.0.1+12' + + - name: Stage checksum-verified legacy dependencies + id: dependencies + shell: bash + run: | + set -euo pipefail + mirror="$RUNNER_TEMP/legacy-dependency-mirror" + bash gradle/stage-legacy-dependencies.sh "$mirror" + echo "repository=$mirror" >> "$GITHUB_OUTPUT" + + - name: Bootstrap Forge from an empty cache + shell: bash + env: + GRADLE_USER_HOME: ${{ runner.temp }}/modernmetals-cold-gradle + run: | + set -euo pipefail + test ! -e .gradle + test ! -e "$GRADLE_USER_HOME" + mkdir -p "$GRADLE_USER_HOME" + chmod +x ./gradlew + gradle_args=( + classes verifyReleaseDependencies + -PlegacyDependencyVerificationRepository="${{ steps.dependencies.outputs.repository }}" + --no-daemon --no-build-cache --stacktrace --max-workers=2 + -Porg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" + -Porg.gradle.java.installations.auto-detect=false + -Porg.gradle.java.installations.auto-download=false + ) + ./gradlew "${gradle_args[@]}" + + - name: Verify same-cache bootstrap offline + shell: bash + env: + GRADLE_USER_HOME: ${{ runner.temp }}/modernmetals-cold-gradle + run: | + set -euo pipefail + gradle_args=( + classes verifyReleaseDependencies + -PlegacyDependencyVerificationRepository="${{ steps.dependencies.outputs.repository }}" + --rerun-tasks --offline --no-daemon --no-build-cache --stacktrace --max-workers=2 + -Porg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" + -Porg.gradle.java.installations.auto-detect=false + -Porg.gradle.java.installations.auto-download=false + ) + ./gradlew "${gradle_args[@]}" + + build: + name: Build, test, and audit + runs-on: ubuntu-latest + timeout-minutes: 90 + + steps: + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Install pinned Java 25 ForgeGradle Mavenizer runtime + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '25.0.3+9.0.LTS' + + - name: Install pinned Java 8 compilation and runtime toolchain + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '8.0.502+7' + + - name: Install pinned Java 17 Gradle runtime + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '17.0.1+12' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 + + - name: Stage checksum-verified dependencies + id: dependencies + shell: bash + run: | + set -euo pipefail + mirror="$RUNNER_TEMP/legacy-dependency-mirror" + bash gradle/stage-legacy-dependencies.sh "$mirror" + echo "repository=$mirror" >> "$GITHUB_OUTPUT" + + - name: Prepare clean packaged Forge runtime + id: forge + shell: bash + run: | + set -euo pipefail + runtime="$RUNNER_TEMP/forge-1.11.2-2588-runtime" + bash gradle/prepare-packaged-forge-runtime.sh "$runtime" "$JAVA_HOME_8_X64/bin/java" + echo "runtime=$runtime" >> "$GITHUB_OUTPUT" + + - name: Install world-audit tools + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + with: + python-version: '3.12' + + - name: Install pinned schema and workflow validators + run: python -m pip install -r tools/validation/requirements.txt + + - name: Build, test, audit, qualify runtimes, and generate Eclipse launches + shell: bash + run: | + set -euo pipefail + chmod +x ./gradlew + gradle_args=( + clean check build javadoc verifyReleaseDependencies verifyReleaseArtifacts + writeReleaseChecksums prepareEclipse verifyProjectResources packagedForgeRuntimeTest + -PpythonExecutable=python3 + -PpackagedForgeRuntimeOutput="$RUNNER_TEMP/modernmetals-packaged-checks" + -PreleaseBuild=true + -PlegacyDependencyVerificationRepository="${{ steps.dependencies.outputs.repository }}" + -PpackagedForgeRuntimeRoot="${{ steps.forge.outputs.runtime }}" + --no-daemon --stacktrace --max-workers=2 + -Porg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" + -Porg.gradle.java.installations.auto-detect=false + -Porg.gradle.java.installations.auto-download=false + ) + ./gradlew "${gradle_args[@]}" + cp build/release/SHA256SUMS "$RUNNER_TEMP/first-SHA256SUMS" + + - name: Verify the prepared release bundle + shell: bash + run: | + set -euo pipefail + bundle="$RUNNER_TEMP/release-input" + mkdir -p "$bundle" + cp build/libs/ModernMetals-2.5.1.111021{,-sources,-javadoc}.jar "$bundle/" + cp build/release/SHA256SUMS CHANGELOG.txt "$bundle/" + ./gradlew verifyPreparedReleaseArtifacts verifyMavenCoordinates \ + -PpreparedReleaseDir="$bundle" \ + -PlegacyDependencyVerificationRepository="${{ steps.dependencies.outputs.repository }}" \ + -Porg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" \ + -Porg.gradle.java.installations.auto-detect=false \ + -Porg.gradle.java.installations.auto-download=false \ + --offline --no-daemon --stacktrace --max-workers=2 + + - name: Verify reproducible release archives + shell: bash + run: | + set -euo pipefail + gradle_args=( + clean build javadoc verifyReleaseArtifacts writeReleaseChecksums + -PreleaseBuild=true + -PlegacyDependencyVerificationRepository="${{ steps.dependencies.outputs.repository }}" + --offline --no-daemon --no-build-cache --stacktrace --max-workers=2 + -Porg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" + -Porg.gradle.java.installations.auto-detect=false + -Porg.gradle.java.installations.auto-download=false + ) + ./gradlew "${gradle_args[@]}" + cmp "$RUNNER_TEMP/first-SHA256SUMS" build/release/SHA256SUMS + + - name: Upload audited release candidate + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: ModernMetals-1.11.2-${{ github.sha }} + if-no-files-found: error + retention-days: 30 + path: | + build/libs/ModernMetals-2.5.1.111021.jar + build/libs/ModernMetals-2.5.1.111021-sources.jar + build/libs/ModernMetals-2.5.1.111021-javadoc.jar + build/release/SHA256SUMS + CHANGELOG.txt + + - name: Upload bounded diagnostics on failure + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: ModernMetals-1.11.2-diagnostics-${{ github.sha }} + if-no-files-found: ignore + retention-days: 14 + path: | + build/test-results/** + build/reports/** + ${{ runner.temp }}/modernmetals-packaged-checks/**/*-console.log + ${{ runner.temp }}/modernmetals-packaged-checks/**/logs/*.log + ${{ runner.temp }}/modernmetals-packaged-checks/**/crash-reports/*.txt + ${{ runner.temp }}/modernmetals-packaged-checks/**/*-modern.json + ${{ runner.temp }}/modernmetals-packaged-checks/**/*-review.json + ${{ runner.temp }}/modernmetals-packaged-checks/**/*-launch.json + build/problems/** diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..6f8b40a3 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,80 @@ +name: CodeQL + +on: + push: + branches: + - master-1.11.2 + - 'feature/**' + pull_request: + branches: + - master-1.11.2 + schedule: + - cron: '47 7 * * 4' + +permissions: + actions: read + contents: read + security-events: write + +jobs: + analyze: + name: Analyze Java + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Install pinned Java 25 ForgeGradle Mavenizer runtime + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '25.0.3+9.0.LTS' + + - name: Install pinned Java 8 compilation toolchain + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '8.0.502+7' + + - name: Install pinned Java 17 Gradle runtime + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '17.0.1+12' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 + + - name: Initialize CodeQL + uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 + with: + languages: java-kotlin + + - name: Stage checksum-verified dependencies + id: dependencies + shell: bash + run: | + set -euo pipefail + mirror="$RUNNER_TEMP/legacy-dependency-mirror" + bash gradle/stage-legacy-dependencies.sh "$mirror" + echo "repository=$mirror" >> "$GITHUB_OUTPUT" + + - name: Compile production code + shell: bash + run: | + set -euo pipefail + chmod +x ./gradlew + gradle_args=( + clean classes + -PlegacyDependencyVerificationRepository="${{ steps.dependencies.outputs.repository }}" + --no-daemon --no-build-cache --stacktrace --max-workers=2 + -Porg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" + -Porg.gradle.java.installations.auto-detect=false + -Porg.gradle.java.installations.auto-download=false + ) + ./gradlew "${gradle_args[@]}" + + - name: Analyze + uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml new file mode 100644 index 00000000..ddaec55b --- /dev/null +++ b/.github/workflows/deploy-release.yml @@ -0,0 +1,799 @@ +name: Release Modern Metals + +run-name: Release Modern Metals ${{ inputs.release_version }} (CurseForge ${{ inputs.curseforge_release_level }}) + +on: + workflow_dispatch: + inputs: + release_version: + description: Four-component Modern Metals version; the target branch is derived from its fourth component. + required: true + type: string + release_commit: + description: Full commit SHA of the checked MMD target branch. + required: true + type: string + tested_jar_sha256: + description: SHA-256 of the verified Modern Metals game jar. + required: true + type: string + curseforge_release_level: + description: CurseForge release level only; this does not change the GitHub Release type. + required: true + default: release + type: choice + options: + - release + - beta + - alpha + confirm_live_publication: + description: Confirm publication to Maven, CurseForge, and GitHub after the build passes. + required: true + default: false + type: boolean + +permissions: + actions: read + contents: read + +concurrency: + group: modernmetals-release-${{ inputs.release_version }} + cancel-in-progress: false + +jobs: + preflight: + name: Validate release request + runs-on: ubuntu-latest + timeout-minutes: 10 + outputs: + target_branch: ${{ steps.route.outputs.target_branch }} + release_sha: ${{ steps.validate.outputs.release_sha }} + release_version: ${{ steps.validate.outputs.release_version }} + release_tag: ${{ steps.validate.outputs.release_tag }} + minecraft_version: ${{ steps.validate.outputs.minecraft_version }} + loader_name: ${{ steps.validate.outputs.loader_name }} + loader_display: ${{ steps.validate.outputs.loader_display }} + java_version: ${{ steps.validate.outputs.java_version }} + java_toolchain_version: ${{ steps.validate.outputs.java_toolchain_version }} + java_setup_version: ${{ steps.validate.outputs.java_setup_version }} + gradle_java_version: ${{ steps.validate.outputs.gradle_java_version }} + gradle_java_setup_version: ${{ steps.validate.outputs.gradle_java_setup_version }} + install_gradle_java: ${{ steps.validate.outputs.install_gradle_java }} + curseforge_project_id: ${{ steps.validate.outputs.curseforge_project_id }} + curseforge_dependencies: ${{ steps.validate.outputs.curseforge_dependencies }} + main_jar: ${{ steps.validate.outputs.main_jar }} + sources_jar: ${{ steps.validate.outputs.sources_jar }} + javadoc_jar: ${{ steps.validate.outputs.javadoc_jar }} + + steps: + - name: Resolve target branch from version + id: route + env: + GH_TOKEN: ${{ github.token }} + RELEASE_VERSION: ${{ inputs.release_version }} + run: | + set -euo pipefail + + if [[ "$GITHUB_REPOSITORY" != "MinecraftModDevelopmentMods/ModernMetals" ]]; then + echo "Releases can only run in MinecraftModDevelopmentMods/ModernMetals" >&2 + exit 1 + fi + if [[ ! "$RELEASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.([0-9]+)$ ]]; then + echo "release_version must contain four numeric components" >&2 + exit 1 + fi + + target_suffix="${BASH_REMATCH[1]}" + if (( ${#target_suffix} < 6 )); then + echo "Invalid target suffix $target_suffix" >&2 + exit 1 + fi + + if [[ "$target_suffix" != 110021 && "$target_suffix" != 111021 ]]; then + echo 'Only the Minecraft 1.10.2 and 1.11.2 Forge targets are supported by this dispatcher' >&2; exit 1 + fi + + loader_code="${target_suffix: -1}" + target_digits="${target_suffix::-1}" + mc_patch_padded="${target_digits: -2}" + without_patch="${target_digits::-2}" + mc_minor_padded="${without_patch: -2}" + mc_major="${without_patch::-2}" + + if [[ -z "$mc_major" || ! "$mc_major" =~ ^[0-9]+$ ]]; then + echo "Invalid Minecraft major version in target suffix $target_suffix" >&2 + exit 1 + fi + mc_minor="$((10#$mc_minor_padded))" + mc_patch="$((10#$mc_patch_padded))" + case "$loader_code" in + 1) loader_suffix= ;; + 2) loader_suffix=-neo ;; + *) echo "Unknown loader code $loader_code in target suffix $target_suffix" >&2; exit 1 ;; + esac + + candidates=( + "master-$mc_major.$mc_minor.$mc_patch$loader_suffix" + "master-$mc_major.$mc_minor$loader_suffix" + ) + target_branch= + for candidate in "${candidates[@]}"; do + if gh api "repos/$GITHUB_REPOSITORY/branches/$candidate" >/dev/null 2>&1; then + target_branch="$candidate" + break + fi + done + if [[ -z "$target_branch" ]]; then + echo "No MMD release branch matches target suffix $target_suffix" >&2 + printf 'Checked branch %s\n' "${candidates[@]}" >&2 + exit 1 + fi + + echo "target_branch=$target_branch" >> "$GITHUB_OUTPUT" + echo "Resolved $RELEASE_VERSION to $target_branch" + + - name: Check out derived release branch + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + ref: ${{ steps.route.outputs.target_branch }} + fetch-depth: 0 + + - name: Validate version, target, tag, and prior CI + id: validate + env: + GH_TOKEN: ${{ github.token }} + REQUESTED_VERSION: ${{ inputs.release_version }} + REQUESTED_COMMIT: ${{ inputs.release_commit }} + TESTED_JAR_SHA256: ${{ inputs.tested_jar_sha256 }} + run: | + set -euo pipefail + + value() { sed -n "s/^$1=//p" gradle.properties; } + release_version="$(value mod_version)" + minecraft_version="$(value minecraft_version)" + loader_name="$(value loader_name)" + loader_code="$(value loader_code)" + java_version="$(value java_version)" + java_toolchain_version="$(value java_toolchain_version)" + java_setup_version="$(value java_setup_version)" + gradle_java_version="$(value gradle_java_version)" + gradle_java_setup_version="$(value gradle_java_setup_version)" + curseforge_project_id="$(value curseforge_project_id)" + cf_requirements="$(value cf_requirements)" + orespawn_project_id="$(value orespawn_curse_project_id)" + + for required in release_version minecraft_version loader_name loader_code \ + java_version gradle_java_version curseforge_project_id; do + if [[ -z "${!required}" ]]; then + echo "Selected ref is missing required release metadata: $required" >&2 + exit 1 + fi + done + java_toolchain_version="${java_toolchain_version:-$java_version}" + java_setup_version="${java_setup_version:-$java_toolchain_version}" + + java_major() { + local version="$1" + if [[ ! "$version" =~ ^([0-9]+)([._+-].*)?$ ]]; then + echo "Invalid Java toolchain version $version" >&2 + return 1 + fi + printf '%s' "${BASH_REMATCH[1]}" + } + requires_separate_gradle_java() { + local toolchain_major + toolchain_major="$(java_major "$1")" + [[ "$toolchain_major" != "$2" ]] + } + + if [[ "$(java_major "$java_setup_version")" != "$(java_major "$java_toolchain_version")" ]]; then + echo "Hosted Java selector $java_setup_version does not match toolchain $java_toolchain_version" >&2 + exit 1 + fi + + java_setup_contracts=( + '8.0.502+7 17 true' + '16.0.2+7 17 true' + '17.0.1+12 17 false' + '17 17 false' + '21.0.8+9 21 false' + ) + for contract in "${java_setup_contracts[@]}"; do + read -r contract_toolchain contract_gradle expected <<<"$contract" + actual=false + if requires_separate_gradle_java "$contract_toolchain" "$contract_gradle"; then + actual=true + fi + if [[ "$actual" != "$expected" ]]; then + echo "Java setup contract failed for toolchain $contract_toolchain and Gradle $contract_gradle" >&2 + exit 1 + fi + done + + install_gradle_java=false + if requires_separate_gradle_java "$java_toolchain_version" "$gradle_java_version"; then + install_gradle_java=true + echo "Gradle Java $gradle_java_version will be installed separately from toolchain $java_toolchain_version" + else + echo "Pinned toolchain $java_toolchain_version also satisfies Gradle Java $gradle_java_version" + fi + + IFS=. read -r mc_major mc_minor mc_patch extra <<<"$minecraft_version" + if [[ -n "${extra:-}" || -z "${mc_major:-}" || -z "${mc_minor:-}" ]]; then + echo "Invalid minecraft_version=$minecraft_version" >&2 + exit 1 + fi + mc_patch="${mc_patch:-0}" + if [[ ! "$mc_major" =~ ^[0-9]+$ || ! "$mc_minor" =~ ^[0-9]+$ || ! "$mc_patch" =~ ^[0-9]+$ ]]; then + echo "Invalid minecraft_version=$minecraft_version" >&2 + exit 1 + fi + case "$loader_name:$loader_code" in + forge:1) loader_display=Forge ;; + neoforge:2) loader_display=NeoForge ;; + *) echo "Invalid loader metadata $loader_name/$loader_code" >&2; exit 1 ;; + esac + if [[ ! "$java_version" =~ ^[0-9]+$ || ! "$gradle_java_version" =~ ^[0-9]+$ ]]; then + echo "Invalid Java metadata $java_version/$gradle_java_version" >&2 + exit 1 + fi + if [[ "$curseforge_project_id" != "244357" ]]; then + echo "Unexpected Modern Metals CurseForge project $curseforge_project_id" >&2 + exit 1 + fi + + printf -v minor_padded '%02d' "$((10#$mc_minor))" + printf -v patch_padded '%02d' "$((10#$mc_patch))" + target_suffix="${mc_major}${minor_padded}${patch_padded}${loader_code}" + if [[ ! "$release_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.${target_suffix}$ ]]; then + echo "mod_version $release_version does not match $minecraft_version $loader_display target $target_suffix" >&2 + exit 1 + fi + if [[ "$REQUESTED_VERSION" != "$release_version" ]]; then + echo "Requested version $REQUESTED_VERSION does not match ${{ steps.route.outputs.target_branch }} mod_version $release_version" >&2 + exit 1 + fi + if [[ -z "$gradle_java_setup_version" ]]; then + if [[ "$install_gradle_java" == false ]]; then + gradle_java_setup_version="$java_setup_version" + elif [[ "$gradle_java_version" == 17 ]]; then + # Legacy targets keep the same pinned Gradle JDK as their CI builds. + gradle_java_setup_version=17.0.1+12 + else + gradle_java_setup_version="$gradle_java_version" + fi + fi + if [[ "$(java_major "$java_toolchain_version")" != "$java_version" \ + || "$(java_major "$gradle_java_setup_version")" != "$gradle_java_version" ]]; then + echo "Java setup selectors do not match the declared game and Gradle Java versions" >&2 + exit 1 + fi + + if [[ -n "$orespawn_project_id" && "$orespawn_project_id" != 245586 ]]; then + echo "Unexpected OreSpawn CurseForge project $orespawn_project_id" >&2 + exit 1 + fi + if [[ -z "$cf_requirements" && "$orespawn_project_id" == 245586 ]]; then + # MMDLib-free ports declare their required OreSpawn dependency directly. + cf_requirements=mmd-orespawn + fi + + curseforge_dependencies=() + IFS=',' read -ra requirement_slugs <<<"$cf_requirements" + for slug in "${requirement_slugs[@]}"; do + case "$slug" in + mmd-orespawn) curseforge_dependencies+=('245586(required)') ;; + base-metals) curseforge_dependencies+=('240967(required)') ;; + mmdlib) curseforge_dependencies+=('261744(required)') ;; + '') ;; + *) echo "Unsupported required CurseForge relation $slug" >&2; exit 1 ;; + esac + done + if [[ " ${curseforge_dependencies[*]} " != *' 245586(required) '* ]]; then + echo "The required OreSpawn CurseForge relation was not declared" >&2 + exit 1 + fi + + curseforge_dependencies+=('243157(optional)') + if [[ " ${curseforge_dependencies[*]} " != *' 240967(required) '* ]]; then + echo 'Base Metals must be a required relation for this release' >&2; exit 1 + fi + + release_sha="$(git rev-parse HEAD)" + if [[ ! "$REQUESTED_COMMIT" =~ ^[0-9a-f]{40}$ || "$REQUESTED_COMMIT" != "$release_sha" ]]; then + echo 'The target branch no longer points at the checked release commit' >&2 + exit 1 + fi + if [[ ! "$TESTED_JAR_SHA256" =~ ^[0-9a-f]{64}$ ]]; then + echo 'Supply the lowercase SHA-256 of the verified game jar' >&2 + exit 1 + fi + release_tag="$release_version" + echo "release_sha=$release_sha" >> "$GITHUB_OUTPUT" + echo "release_version=$release_version" >> "$GITHUB_OUTPUT" + echo "release_tag=$release_tag" >> "$GITHUB_OUTPUT" + echo "minecraft_version=$minecraft_version" >> "$GITHUB_OUTPUT" + echo "loader_name=$loader_name" >> "$GITHUB_OUTPUT" + echo "loader_display=$loader_display" >> "$GITHUB_OUTPUT" + echo "java_version=$java_version" >> "$GITHUB_OUTPUT" + echo "java_toolchain_version=$java_toolchain_version" >> "$GITHUB_OUTPUT" + echo "java_setup_version=$java_setup_version" >> "$GITHUB_OUTPUT" + echo "gradle_java_version=$gradle_java_version" >> "$GITHUB_OUTPUT" + echo "gradle_java_setup_version=$gradle_java_setup_version" >> "$GITHUB_OUTPUT" + echo "install_gradle_java=$install_gradle_java" >> "$GITHUB_OUTPUT" + echo "curseforge_project_id=$curseforge_project_id" >> "$GITHUB_OUTPUT" + { + echo 'curseforge_dependencies<> "$GITHUB_OUTPUT" + echo "main_jar=ModernMetals-$release_version.jar" >> "$GITHUB_OUTPUT" + echo "sources_jar=ModernMetals-$release_version-sources.jar" >> "$GITHUB_OUTPUT" + echo "javadoc_jar=ModernMetals-$release_version-javadoc.jar" >> "$GITHUB_OUTPUT" + + successful_ci="$(gh api \ + "repos/$GITHUB_REPOSITORY/commits/$release_sha/check-runs?per_page=100" \ + --jq '[.check_runs[] | select(.name == "Build, test, and audit" and .conclusion == "success")] | length')" + if [[ "$successful_ci" -lt 1 ]]; then + echo "The selected commit has no successful Build, test, and audit check" >&2 + exit 1 + fi + + if tag_json="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$release_tag" 2>/dev/null)"; then + tag_sha="$(jq -r '.object.sha' <<<"$tag_json")" + if [[ "$tag_sha" != "$release_sha" ]]; then + echo "Existing tag $release_tag resolves to $tag_sha instead of $release_sha" >&2 + exit 1 + fi + fi + + build: + name: Build immutable release artifact + needs: preflight + runs-on: ubuntu-latest + timeout-minutes: 60 + env: + MAIN_JAR: ${{ needs.preflight.outputs.main_jar }} + SOURCES_JAR: ${{ needs.preflight.outputs.sources_jar }} + JAVADOC_JAR: ${{ needs.preflight.outputs.javadoc_jar }} + outputs: + artifact_name: ${{ steps.artifact.outputs.name }} + + steps: + - name: Check out validated release ref + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + ref: ${{ needs.preflight.outputs.release_sha }} + + - name: Install pinned Java 25 Mavenizer runtime + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '25.0.3+9.0.LTS' + + - name: Install pinned Java 8 launcher toolchain + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '8.0.502+7' + + - name: Install target Java toolchain + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: ${{ needs.preflight.outputs.java_setup_version }} + + - name: Record target Java toolchain path + shell: bash + run: echo "MODERNMETALS_TARGET_JAVA_HOME=$JAVA_HOME" >> "$GITHUB_ENV" + + - name: Install separate Java for Gradle + if: needs.preflight.outputs.install_gradle_java == 'true' + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: ${{ needs.preflight.outputs.gradle_java_setup_version }} + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 + + - name: Resolve explicit Gradle Java installations + id: java-paths + shell: bash + run: | + set -euo pipefail + candidates=( + "$JAVA_HOME_25_X64" + "$JAVA_HOME_8_X64" + "$MODERNMETALS_TARGET_JAVA_HOME" + "$JAVA_HOME" + ) + paths= + for candidate in "${candidates[@]}"; do + test -x "$candidate/bin/java" + canonical="$(cd "$candidate" && pwd -P)" + if [[ ",$paths," != *",$canonical,"* ]]; then + paths="${paths:+$paths,}$canonical" + fi + done + echo "paths=$paths" >> "$GITHUB_OUTPUT" + + - name: Stage exact target dependencies + id: dependencies + run: | + set -euo pipefail + mirror="$RUNNER_TEMP/dependency-mirror" + if [[ -f gradle/stage-legacy-dependencies.sh ]]; then + bash gradle/stage-legacy-dependencies.sh "$mirror" + property=legacyDependencyVerificationRepository + elif [[ -f gradle/stage-orespawn-release.sh ]]; then + bash gradle/stage-orespawn-release.sh "$mirror" + property=orespawnVerificationRepository + else + echo "Selected target has no checksum-sealed dependency staging script" >&2 + exit 1 + fi + echo "repository=$mirror" >> "$GITHUB_OUTPUT" + echo "property=$property" >> "$GITHUB_OUTPUT" + + - name: Build, test, and audit once + env: + LOADER_NAME: ${{ needs.preflight.outputs.loader_name }} + run: | + set -euo pipefail + chmod +x ./gradlew + java_args=( + "-Dorg.gradle.java.installations.paths=${{ steps.java-paths.outputs.paths }}" + -Dorg.gradle.java.installations.auto-detect=false + -Dorg.gradle.java.installations.auto-download=false + ) + dependency_argument="-P${{ steps.dependencies.outputs.property }}=${{ steps.dependencies.outputs.repository }}" + if [[ "$LOADER_NAME" == neoforge ]]; then + # NeoGradle prepares inputs in build/tmp during configuration. Clean first. + ./gradlew clean "$dependency_argument" \ + --no-daemon --stacktrace --max-workers=2 "${java_args[@]}" + ./gradlew check build javadoc verifyReleaseArtifacts writeReleaseChecksums \ + "$dependency_argument" \ + --no-daemon --stacktrace --max-workers=2 "${java_args[@]}" + else + ./gradlew clean check build javadoc verifyReleaseArtifacts writeReleaseChecksums \ + "$dependency_argument" \ + --no-daemon --stacktrace "${java_args[@]}" + fi + + - name: Stage the immutable release artifact + id: artifact + env: + TESTED_JAR_SHA256: ${{ inputs.tested_jar_sha256 }} + run: | + set -euo pipefail + bundle="$RUNNER_TEMP/release-bundle" + mkdir -p "$bundle" + cp "build/libs/$MAIN_JAR" "$bundle/" + cp "build/libs/$SOURCES_JAR" "$bundle/" + cp "build/libs/$JAVADOC_JAR" "$bundle/" + cp build/release/SHA256SUMS "$bundle/" + cp CHANGELOG.txt "$bundle/" + (cd "$bundle" && sha256sum --check SHA256SUMS) + printf '%s %s\n' "$TESTED_JAR_SHA256" "$bundle/$MAIN_JAR" | sha256sum --check - + echo "name=ModernMetals-${{ needs.preflight.outputs.minecraft_version }}-${{ needs.preflight.outputs.loader_name }}-${{ needs.preflight.outputs.release_sha }}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> "$GITHUB_OUTPUT" + + - name: Upload immutable release artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: ${{ steps.artifact.outputs.name }} + if-no-files-found: error + retention-days: 90 + path: ${{ runner.temp }}/release-bundle/* + + - name: Summarize the prepared release candidate + env: + RELEASE_BRANCH: ${{ needs.preflight.outputs.target_branch }} + RELEASE_LEVEL: ${{ inputs.curseforge_release_level }} + RELEASE_SHA: ${{ needs.preflight.outputs.release_sha }} + RELEASE_VERSION: ${{ needs.preflight.outputs.release_version }} + run: | + { + echo "## Prepared release candidate" + echo + echo "- Version: \`$RELEASE_VERSION\`" + echo "- Target branch: \`$RELEASE_BRANCH\`" + echo "- Commit: \`$RELEASE_SHA\`" + echo "- CurseForge release level: \`$RELEASE_LEVEL\`" + echo "- GitHub Release type: ordinary release" + echo + echo "### Public artifact checksums" + echo '```text' + cat build/release/SHA256SUMS + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + release_confirmation: + name: Confirm ${{ inputs.release_version }} live publication + needs: + - preflight + - build + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Confirm publication and release-secret access + env: + CONFIRM_LIVE_PUBLICATION: ${{ inputs.confirm_live_publication }} + CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }} + MAVEN_UPLOAD_URL: ${{ secrets.MAVEN_UPLOAD_URL }} + MAVEN_UPLOAD_USERNAME: ${{ secrets.MAVEN_UPLOAD_USERNAME }} + MAVEN_UPLOAD_PASSWORD: ${{ secrets.MAVEN_UPLOAD_PASSWORD }} + run: | + set -euo pipefail + if [[ "$CONFIRM_LIVE_PUBLICATION" != "true" ]]; then + echo "Confirm live publication in the workflow form before releasing ${{ needs.preflight.outputs.release_tag }}" >&2 + exit 1 + fi + echo "Live publication confirmed for ${{ needs.preflight.outputs.release_tag }}" + missing=() + for name in CURSEFORGE_TOKEN MAVEN_UPLOAD_URL MAVEN_UPLOAD_USERNAME MAVEN_UPLOAD_PASSWORD; do + if [[ -z "${!name:-}" ]]; then + missing+=("$name") + fi + done + if (( ${#missing[@]} > 0 )); then + printf 'Required release secret is unavailable: %s\n' "${missing[@]}" >&2 + exit 1 + fi + echo "All four required release secrets are available." + + create_release_tag: + name: Create validated release tag + needs: + - preflight + - build + - release_confirmation + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + actions: read + contents: write + + steps: + - name: Create or verify the exact tag + env: + GH_TOKEN: ${{ github.token }} + RELEASE_SHA: ${{ needs.preflight.outputs.release_sha }} + RELEASE_TAG: ${{ needs.preflight.outputs.release_tag }} + run: | + set -euo pipefail + + if tag_json="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$RELEASE_TAG" 2>/dev/null)"; then + tag_sha="$(jq -r '.object.sha' <<<"$tag_json")" + if [[ "$tag_sha" != "$RELEASE_SHA" ]]; then + echo "Existing tag $RELEASE_TAG resolves to $tag_sha instead of $RELEASE_SHA" >&2 + exit 1 + fi + echo "Existing tag $RELEASE_TAG already points to $RELEASE_SHA" + else + gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" \ + -f ref="refs/tags/$RELEASE_TAG" \ + -f sha="$RELEASE_SHA" >/dev/null + echo "Created tag $RELEASE_TAG at $RELEASE_SHA" + fi + + created_sha="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$RELEASE_TAG" --jq '.object.sha')" + if [[ "$created_sha" != "$RELEASE_SHA" ]]; then + echo "Tag verification returned $created_sha instead of $RELEASE_SHA" >&2 + exit 1 + fi + + publish_maven: + name: Publish Maven + needs: + - preflight + - build + - create_release_tag + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Check out the tagged publication definition + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + ref: ${{ needs.preflight.outputs.release_sha }} + + - name: Install pinned Java 25 Mavenizer runtime + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '25.0.3+9.0.LTS' + + - name: Install pinned Java 8 launcher toolchain + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '8.0.502+7' + + - name: Install target Java toolchain + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: ${{ needs.preflight.outputs.java_setup_version }} + + - name: Record target Java toolchain path + shell: bash + run: echo "MODERNMETALS_TARGET_JAVA_HOME=$JAVA_HOME" >> "$GITHUB_ENV" + + - name: Install separate Java for Gradle + if: needs.preflight.outputs.install_gradle_java == 'true' + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: ${{ needs.preflight.outputs.gradle_java_setup_version }} + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 + + - name: Resolve explicit Gradle Java installations + id: java-paths + shell: bash + run: | + set -euo pipefail + candidates=( + "$JAVA_HOME_25_X64" + "$JAVA_HOME_8_X64" + "$MODERNMETALS_TARGET_JAVA_HOME" + "$JAVA_HOME" + ) + paths= + for candidate in "${candidates[@]}"; do + test -x "$candidate/bin/java" + canonical="$(cd "$candidate" && pwd -P)" + if [[ ",$paths," != *",$canonical,"* ]]; then + paths="${paths:+$paths,}$canonical" + fi + done + echo "paths=$paths" >> "$GITHUB_OUTPUT" + + - name: Download immutable release artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: ${{ needs.build.outputs.artifact_name }} + path: ${{ runner.temp }}/release-input + + - name: Stage exact target dependencies + id: dependencies + shell: bash + run: | + set -euo pipefail + mirror="$RUNNER_TEMP/dependency-mirror" + if [[ -f gradle/stage-legacy-dependencies.sh ]]; then + bash gradle/stage-legacy-dependencies.sh "$mirror" + property=legacyDependencyVerificationRepository + elif [[ -f gradle/stage-orespawn-release.sh ]]; then + bash gradle/stage-orespawn-release.sh "$mirror" + property=orespawnVerificationRepository + else + echo "Selected target has no checksum-sealed dependency staging script" >&2 + exit 1 + fi + echo "repository=$mirror" >> "$GITHUB_OUTPUT" + echo "property=$property" >> "$GITHUB_OUTPUT" + + - name: Verify artifact and Maven credentials + env: + MAVEN_UPLOAD_URL: ${{ secrets.MAVEN_UPLOAD_URL }} + MAVEN_UPLOAD_USERNAME: ${{ secrets.MAVEN_UPLOAD_USERNAME }} + MAVEN_UPLOAD_PASSWORD: ${{ secrets.MAVEN_UPLOAD_PASSWORD }} + run: | + set -euo pipefail + (cd "$RUNNER_TEMP/release-input" && sha256sum --check SHA256SUMS) + if [[ -z "$MAVEN_UPLOAD_URL" || -z "$MAVEN_UPLOAD_USERNAME" || -z "$MAVEN_UPLOAD_PASSWORD" ]]; then + echo "Maven upload URL and credentials must all be configured" >&2 + exit 1 + fi + if [[ "$MAVEN_UPLOAD_URL" == file:* ]]; then + echo "Maven publication cannot target a runner-local repository" >&2 + exit 1 + fi + + - name: Publish the prepared Maven artifacts + env: + MAVEN_UPLOAD_URL: ${{ secrets.MAVEN_UPLOAD_URL }} + MAVEN_UPLOAD_USERNAME: ${{ secrets.MAVEN_UPLOAD_USERNAME }} + MAVEN_UPLOAD_PASSWORD: ${{ secrets.MAVEN_UPLOAD_PASSWORD }} + run: | + chmod +x ./gradlew + dependency_argument="-P${{ steps.dependencies.outputs.property }}=${{ steps.dependencies.outputs.repository }}" + ./gradlew publishMavenJavaPublicationToReleaseRepository \ + -PpreparedReleaseDir="$RUNNER_TEMP/release-input" \ + "$dependency_argument" \ + --no-daemon --stacktrace \ + -Dorg.gradle.java.installations.paths="${{ steps.java-paths.outputs.paths }}" \ + -Dorg.gradle.java.installations.auto-detect=false \ + -Dorg.gradle.java.installations.auto-download=false + + publish_curseforge: + name: Publish CurseForge + needs: + - preflight + - build + - publish_maven + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Download immutable release artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: ${{ needs.build.outputs.artifact_name }} + path: ${{ runner.temp }}/release-input + + - name: Verify artifact and CurseForge credential + env: + CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }} + run: | + set -euo pipefail + (cd "$RUNNER_TEMP/release-input" && sha256sum --check SHA256SUMS) + if [[ -z "$CURSEFORGE_TOKEN" ]]; then + echo "CURSEFORGE_TOKEN must be configured" >&2 + exit 1 + fi + + - name: Publish Modern Metals + uses: Kira-NT/mc-publish@52307b03863581dec6b652b83e597aec02ebb075 # v3.3.1 + with: + curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} + curseforge-id: ${{ needs.preflight.outputs.curseforge_project_id }} + name: Modern Metals ${{ needs.preflight.outputs.release_version }} for Minecraft ${{ needs.preflight.outputs.minecraft_version }} (${{ needs.preflight.outputs.loader_display }}) + version: ${{ needs.preflight.outputs.release_version }} + version-type: ${{ inputs.curseforge_release_level }} + changelog-file: ${{ runner.temp }}/release-input/CHANGELOG.txt + loaders: ${{ needs.preflight.outputs.loader_name }} + game-versions: ${{ needs.preflight.outputs.minecraft_version }} + game-version-filter: none + environment: client | server + java: Java ${{ needs.preflight.outputs.java_version }} + curseforge-dependencies: ${{ needs.preflight.outputs.curseforge_dependencies }} + fail-mode: fail + files: | + ${{ runner.temp }}/release-input/${{ needs.preflight.outputs.main_jar }} + ${{ runner.temp }}/release-input/${{ needs.preflight.outputs.sources_jar }} + ${{ runner.temp }}/release-input/${{ needs.preflight.outputs.javadoc_jar }} + + publish_github: + name: Publish GitHub Release + needs: + - preflight + - build + - publish_curseforge + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + actions: read + contents: write + + steps: + - name: Download immutable release artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: ${{ needs.build.outputs.artifact_name }} + path: ${{ runner.temp }}/release-input + + - name: Verify artifact + run: (cd "$RUNNER_TEMP/release-input" && sha256sum --check SHA256SUMS) + + - name: Publish GitHub Release last + uses: Kira-NT/mc-publish@52307b03863581dec6b652b83e597aec02ebb075 # v3.3.1 + with: + github-token: ${{ github.token }} + github-tag: ${{ needs.preflight.outputs.release_tag }} + github-commitish: ${{ needs.preflight.outputs.release_sha }} + github-draft: false + github-prerelease: false + name: Modern Metals ${{ needs.preflight.outputs.release_version }} for Minecraft ${{ needs.preflight.outputs.minecraft_version }} (${{ needs.preflight.outputs.loader_display }}) + version: ${{ needs.preflight.outputs.release_version }} + version-type: release + changelog-file: ${{ runner.temp }}/release-input/CHANGELOG.txt + fail-mode: fail + files: | + ${{ runner.temp }}/release-input/${{ needs.preflight.outputs.main_jar }} + ${{ runner.temp }}/release-input/${{ needs.preflight.outputs.sources_jar }} + ${{ runner.temp }}/release-input/${{ needs.preflight.outputs.javadoc_jar }} + ${{ runner.temp }}/release-input/SHA256SUMS diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml new file mode 100644 index 00000000..718b4afa --- /dev/null +++ b/.github/workflows/release-on-tag.yml @@ -0,0 +1,89 @@ +name: Start Modern Metals release from tag + +on: + push: + tags: + - '*.*.*.*' + +permissions: + actions: read + contents: read + +concurrency: + group: modernmetals-release-starter-${{ github.ref_name }} + cancel-in-progress: false + +jobs: + validate-release-tag: + name: Validate tag for manual release confirmation + if: github.repository == 'MinecraftModDevelopmentMods/ModernMetals' + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Check out tagged source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + fetch-depth: 0 + + - name: Validate release tag, target metadata, and prior CI + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + + value() { sed -n "s/^$1=//p" gradle.properties; } + release_version="$(value mod_version)" + minecraft_version="$(value minecraft_version)" + loader_name="$(value loader_name)" + loader_code="$(value loader_code)" + + IFS=. read -r mc_major mc_minor mc_patch extra <<<"$minecraft_version" + mc_patch="${mc_patch:-0}" + if [[ -n "${extra:-}" || ! "$mc_major" =~ ^[0-9]+$ || ! "$mc_minor" =~ ^[0-9]+$ \ + || ! "$mc_patch" =~ ^[0-9]+$ || "$loader_name:$loader_code" != 'forge:1' ]]; then + echo "Invalid target metadata $minecraft_version/$loader_name/$loader_code" >&2 + exit 1 + fi + printf -v minor_padded '%02d' "$((10#$mc_minor))" + printf -v patch_padded '%02d' "$((10#$mc_patch))" + target_suffix="${mc_major}${minor_padded}${patch_padded}${loader_code}" + + if [[ ! "$release_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.${target_suffix}$ ]]; then + echo "mod_version $release_version does not match target $target_suffix" >&2 + exit 1 + fi + if [[ "$GITHUB_REF_NAME" != "$release_version" ]]; then + echo "Release tag must equal mod_version $release_version; found $GITHUB_REF_NAME" >&2 + exit 1 + fi + + successful_ci="$(gh api \ + "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/check-runs?per_page=100" \ + --jq '[.check_runs[] | select(.name == "Build, test, and audit" and .conclusion == "success")] | length')" + if [[ "$successful_ci" -lt 1 ]]; then + echo "The tagged commit has no successful Build, test, and audit check" >&2 + exit 1 + fi + + - name: Record the required manual publication step + env: + RELEASE_WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/workflows/deploy-release.yml + shell: bash + run: | + { + echo "## Release candidate validated" + echo + echo "Tag \`$GITHUB_REF_NAME\` matches the selected target and has a successful Build, test, and audit check." + echo + echo "**Nothing has been published.**" + echo + echo "To continue, open [Release Modern Metals]($RELEASE_WORKFLOW_URL), select **Run workflow**, and enter:" + echo + echo "- release_version: \`$GITHUB_REF_NAME\`" + echo "- release_commit: \`$GITHUB_SHA\`" + echo "- tested_jar_sha256: SHA-256 of the tested game jar" + echo "- curseforge_release_level: \`release\`, \`beta\`, or \`alpha\`" + echo "- confirm_live_publication: \`true\`" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/validate-gradle-build.yml b/.github/workflows/validate-gradle-build.yml new file mode 100644 index 00000000..0b533e41 --- /dev/null +++ b/.github/workflows/validate-gradle-build.yml @@ -0,0 +1,23 @@ +name: Validate Gradle Wrapper + +on: + push: + branches: + - master-1.11.2 + - 'feature/**' + pull_request: + branches: + - master-1.11.2 + +permissions: + contents: read + +jobs: + validation: + name: Validation + runs-on: ubuntu-latest + steps: + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + - name: Validate wrapper integrity + uses: gradle/actions/wrapper-validation@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 diff --git a/.gitignore b/.gitignore index 3f67a97d..3a14b223 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,15 @@ run /README.txt /forge-*-changelog.txt -secret.json \ No newline at end of file +secret.json +# Local context and generated verification +/AGENTS.md +/agent-notes/ +/.codex/ +/.verification*/ +/release-candidates/ +__pycache__/ +*.pyc +.DS_Store +Thumbs.db +desktop.ini diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4553e94c..00000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: java -sudo: false -cache: - directories: - - $HOME/.gradle -notifications: - email: false -jdk: - - oraclejdk8 -before_install: - - chmod a+x gradlew -install: ./gradlew setupCIWorkspace -S -script: ./gradlew clean build -S diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 97b106a9..8abbc745 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,15 +1,14 @@ -1) Tinkers Construct Tools -2) Code cleanups -3) Support Items for Mekanism -4) Support Items for IC2 -5) Balanced metals (They now have different stats) -6) Fixed Fishing Rod item name ($metal_fishing_rod instead of $metal_fishingrod) -7) Added Buttons, Levers, Stairs, Pressure Plates, Walls & Slabs -8) Building Bricks integration support -9) Tungsten oredicted as wolfram for more mod compatibility -10) Small Blends/small piles of powder oredicted as dustTiny instead of smalldust -11) Fishing Rods now actually work. (Thanks to lclc98 for the assistance) -12) Veinminer integration -13) Ender IO Integration -14) More Zinc Stuff -15) Ported to 1.11.2 +2.5.1.111021 for 1.11.2 + +This brings the 1.11.2 branch up to date. Use Java 8, Forge 13.20.1.2588 or later for 1.11.2, Base Metals 2.5.1.111021 and MMD OreSpawn 4.1.5.111021. + +- Kept boron, beryllium and thorium with their ores, molten forms, buckets and recipes. +- Kept titanium's rutile and magnesium recipe. Titanium ore generation is optional and starts off. +- Moved ore generation to OreSpawn 4. Existing OreSpawn settings can carry forward and retrogen stays off. +- Used KiriCattus's ore overlays so the stone in your resource pack shows through. Thorium keeps its original texture. +- Put Modern Metals settings in the Forge Mods menu and OreSpawn Mods directory. The screens have German, Simplified Chinese and Russian translations, with regional English files also included. +- Added High and Low Fantasy and a switch for new villager trades. Restart after changing them. Clients need the same Fantasy Level as their server. +- Fixed IC2 and Mekanism processing and a Tinkers casting issue when the aluminum fluid was already registered. +- Use the Base Metals crusher registry for all 20 ores. +- Restored old door, fishing rod, horse armor, alloy ore and molten aluminum brass IDs for 0.11.0 worlds. Back up an older world before opening it with the new jars. +- Updated the build and Eclipse setup to ForgeGradle 7. The game still runs on Java 8. diff --git a/README.md b/README.md index 4fece25c..e1afbc9f 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,75 @@ -# ModernMetals [![Build Status](https://travis-ci.org/jriwanek/ModernMetals.svg?branch=master)](https://travis-ci.org/jriwanek/ModernMetals) -Addon to BaseMetals supporting Modern Metals +# Modern Metals for Minecraft 1.11.2 -Content -------- -Bars +Modern Metals adds industrial metals and alloys to Base Metals, with ores, +building blocks, tools, armour and molten metals. This branch is for Minecraft +**1.11.2**, local candidate **2.5.1.111021**. -Doors +## Installation -Ores +Use Java **8** and Forge **13.20.1.2588 or newer for Minecraft 1.11.2**. +For local testing, put these three jars in `mods/` on both the client and server. -Plates +- Modern Metals `2.5.1.111021`. +- Base Metals `2.5.1.111021` from the staged 1.11.2 build, supported range `[2.5.1.111021,2.6.0)`. +- MMD OreSpawn `4.1.5.111021` from the staged 1.11.2 build, supported range `[4.1.5.111021,5.0.0)`. -Trapdoors +Remove older copies first. Use the normal mod jars rather than the sources, +Javadocs or development jars. This version uses Base Metals' shared library, so +you don't need a separate MMDLib or Mineralogy jar. -Armor +## Materials -Tools: Crackhammers, Axes, Hoes, Pickaxes, Shovels, Fishing Rods +The 21 configurable materials are aluminum, aluminum brass, beryllium, boron, +cadmium, chromium, galvanized steel, iridium, magnesium, manganese, nichrome, +osmium, plutonium, rutile, stainless steel, tantalum, thorium, titanium, tungsten, +uranium and zirconium. Chromium/Chrome, Tungsten/Wolfram and Aluminum/Aluminium +Ore Dictionary names are kept for compatibility. -Ores, Blocks, Ingots & Nuggets +Boron, beryllium and thorium have ores, molten forms, buckets and processing +recipes. Titanium is still made from **rutile and magnesium**, using +titanium blend and smelting, or molten alloying and casting. Titanium ore generation +is **off by default**. If enabled, it uses the same placement settings as rutile. -Powders & Blends (Ingot and Nugget size) +## Configuration -Rods +Open **Mods -> Modern Metals -> Config**, or use the Modern Metals cog under +**Create World -> OreSpawn -> Mods**. Both save to `config/ModernMetals.cfg`. +Done saves your changes. Escape cancels them. Restart the game or server to apply +them. Keep materials enabled in worlds that use their blocks and items. -Gears +**Fantasy Level** defaults to High Fantasy, which keeps the usual recipes. Low +Fantasy restricts unrealistic equipment and building recipes across the materials, +including trades and Tinkers parts. In 1.11 this is a Modern Metals setting and is +independent of Base Metals. See [the fantasy settings](docs/CONTENT_MODE.md) for +the full list of restrictions. A multiplayer server and its clients need the +same Fantasy Level. Use **Villager Trades** to turn off new Modern Metals offers +in either mode. -Weapons: Swords, Bows, Arrows, Crossbows & Bolts +The config screen is available in English, German, Simplified Chinese and +Russian. Regional English strings are included for Australia, Canada, the UK, +New Zealand and Pirate Speak. -Materials ---------- -Aluminum +Ore generation is handled by OreSpawn 4. Use its editor to change ore settings. +Existing chunks are left alone and retrogen stays off. See [ore generation and +migration](docs/ORE_GENERATION.md) for defaults, ore groups and older config files. +Ores use KiriCattus's overlays over the stone texture, so stone resource packs +also change the background of the ore. Thorium keeps its original texture. -Aluminum Brass +Modern Metals ores work in the Base Metals Crack Hammer and in the rock +crushers that use the same Base Metals registry. They use the Base Metals crusher +settings, including the powder yield and any ores you disable there. -Cadmium +## Updating an older 1.11.2 world -Chromium +Keep a backup before opening the world with the new jars. See the [migration +notes](docs/ORE_GENERATION.md) before moving an existing configuration. Modern +Metals resolves the old door, fishing rod, horse armor and molten aluminum brass IDs from 0.11.0. +Placed galvanized steel, nichrome and stainless steel ores still load. Those +three ores have no default OreSpawn rules. Keep the materials enabled when first +opening a world that contains them. -Iridium +## Development -Lithium - -Magnesium - -Manganese - -Niter - -Osmium - -Phosphorous - -Plutonium - -Rutile - -Silicon - -Sulfur - -Tantalum - -Titanium - -Tungsten - -Uranium - -Zirconium +The build uses ForgeGradle 7 and still produces Java 8 jars. See [building and +Eclipse](docs/BUILDING.md), [integration checks and known limits](tools/validation/legacy111/README.md) +and [release preparation](docs/RELEASING.md). diff --git a/build.gradle b/build.gradle index 1d83d820..c38bd3be 100644 --- a/build.gradle +++ b/build.gradle @@ -1,367 +1,190 @@ -def corePlugin = '' +import org.apache.tools.ant.filters.FixCrLfFilter -buildscript { - repositories { - jcenter() - maven { - name = 'forge' - url = 'http://files.minecraftforge.net/maven' - } - maven { - name = 'gradle' - url 'https://plugins.gradle.org/m2/' - } - maven { - name = 'sonatype' - url = 'https://oss.sonatype.org/content/groups/public' - } - } - dependencies { - classpath 'net.minecraftforge.gradle:ForgeGradle:2.2-SNAPSHOT' - classpath 'gradle.plugin.com.matthewprenger:CurseGradle:1.0.11' - classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.2' - classpath 'org.junit.platform:junit-platform-gradle-plugin:1.2.0' - } +plugins { + id 'java' + id 'eclipse' + id 'idea' + id 'maven-publish' + id 'net.minecraftforge.renamer' version '1.1.5' + id 'net.minecraftforge.gradle' version '7.0.34' } -apply plugin: 'net.minecraftforge.gradle.forge' -apply plugin: 'com.matthewprenger.cursegradle' -apply plugin: 'maven-publish' -apply plugin: 'org.sonarqube' -apply plugin: 'org.junit.platform.gradle.plugin' -apply plugin: 'jacoco' +group = project.mod_group +version = project.mod_version +base.archivesName = 'ModernMetals' -repositories { - mavenLocal() - mavenCentral() -} - -dependencies { - testCompile('org.junit.jupiter:junit-jupiter-api:5.2.+') - testCompile('org.junit.platform:junit-platform-launcher:1.2.+') - testRuntime('org.junit.jupiter:junit-jupiter-engine:5.2.+') - testCompile('org.mockito:mockito-core:2.+') +if (!(project.version ==~ /\d+\.\d+\.\d+\.111021/)) { + throw new GradleException('Use Major.Minor.Patch.111021 for Minecraft 1.11.2 on Forge.') } -project.afterEvaluate { - def junitPlatformTestTask = project.tasks.getByName('junitPlatformTest') - - // configure jacoco to analyze the junitPlatformTest task - jacoco { - // this tool version is compatible with - toolVersion = '0.8.1' - applyTo junitPlatformTestTask - } - - // create junit platform jacoco task - project.task(type: JacocoReport, "junitPlatformJacocoReport", - { - sourceDirectories = files("./src/main") - classDirectories = files("$buildDir/classes/main") - executionData junitPlatformTestTask - }) +// Gradle and Mavenizer use modern JDKs; the mod and game still use Java 8. +java { + toolchain.languageVersion = JavaLanguageVersion.of(8) + withSourcesJar() + withJavadocJar() } - -junitPlatform { - platformVersion '1.0.3' // optional, defaults to plugin version - //logManager 'org.apache.logging.log4j.jul.LogManager' - reportsDir file('build/test-results/junit-platform') // this is the default - // enableStandardTestTask true - // selectors (optional) - // filters (optional) +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' + sourceCompatibility = targetCompatibility = '1.8' + options.compilerArgs.addAll(['-Xmaxerrs', '1000']) } - -javadoc { +tasks.named('compileTestJava', JavaCompile) { options.compilerArgs.add('-proc:none') } +tasks.withType(Test).configureEach { useJUnitPlatform() } +tasks.withType(Javadoc).configureEach { failOnError = false + options.encoding = 'UTF-8' + options.addStringOption('Xdoclint:none', '-quiet') + options.addStringOption('docencoding', 'UTF-8') + options.addStringOption('charset', 'UTF-8') + options.addBooleanOption('notimestamp', true) } - -description = 'Modern Metals' -def mod_file = getModFile() -def mc_version = '1.11.2' - -ext { - short_version = getVersion('VERSION', mod_file) +tasks.withType(AbstractArchiveTask).configureEach { + preserveFileTimestamps = false + reproducibleFileOrder = true + includeEmptyDirs = false + exclude '**/desktop.ini', '**/Thumbs.db', '**/.DS_Store' } - -version = mc_version + '-' + short_version - -if (System.getenv().BUILD_NUMBER) { - version += '.' + System.getenv().BUILD_NUMBER +// Mapped dependency bytes no longer match their original signatures. Keep the +// verified downloads intact and strip signatures only from renamed copies. +tasks.withType(net.minecraftforge.renamer.gradle.RenameJar).configureEach { + additionalArgs.addAll('--strip-sigs', 'ALL', '--disable-abstract-param') } -group = 'com.mcmoddev' -archivesBaseName = 'ModernMetals' -sourceCompatibility = targetCompatibility = '1.8' - -class Secrets { - def data = null - def getProperty(String key) { - return data ? data[key] : '' - } +def textPatterns = ['cfg', 'css', 'html', 'info', 'java', 'js', 'json', 'lang', 'mcmeta', 'md', 'properties', 'txt', 'xml'] + .collect { "**/*.${it}".toString() } +def normalizeText = { details -> + details.filter(FixCrLfFilter, eol: FixCrLfFilter.CrLf.newInstance('lf'), + eof: FixCrLfFilter.AddAsisRemove.newInstance('asis')) } -import groovy.json.JsonSlurper - -def secretFile -if (System.getenv().SECRET_FILE) { - secretFile = file System.getenv().SECRET_FILE -} else { - secretFile = file 'secret.json' +// Preserve the tested game jar's line endings for this compatibility list. +def legacyApiList = 'META-INF/modernmetals-legacy-api.list' +def normalizeLegacyApi = { details -> + details.filter(FixCrLfFilter, eol: FixCrLfFilter.CrLf.newInstance('crlf'), + eof: FixCrLfFilter.AddAsisRemove.newInstance('asis')) } -project.ext.secret = new Secrets() -if (secretFile.exists()) { - secretFile.withReader { - project.ext.secret.data = new JsonSlurper().parse it - } +def developmentOutput = layout.buildDirectory.dir('forge11-development/main') +def prepareDevelopmentMod = tasks.register('prepareDevelopmentMod', Sync) { + group = 'ide' + description = 'Merges production classes and processed resources for Forge 1.11 launches.' + dependsOn tasks.named('classes') + from tasks.named('compileJava', JavaCompile).flatMap { it.destinationDirectory } + from sourceSets.main.output.resourcesDir + into developmentOutput } minecraft { - version = '1.11.2-13.20.1.2386' - runDir = 'run' - mappings = 'stable_32' -// coreMod = corePlugin -// clientJvmArgs = ["-Dfml.coreMods.load=$corePlugin"] -// serverJvmArgs = ["-Dfml.coreMods.load=$corePlugin"] - - replace '@FINGERPRINT@', project.findProperty('signSHA1') -} - -repositories { - maven { // Mantle, TCon, JEI - name 'DVS1 Maven FS' - url 'http://dvs1.progwml6.com/files/maven/' - } - maven { // CCL, CCC, NEI - name 'Chickenbones Repo' - url 'http://chickenbones.net/maven/' - } - maven { // The One Probe - name 'tterrag' - url 'http://maven.tterrag.com/' - } - maven { - name 'MMD' - url 'https://maven.mcmoddev.com/' - } - maven { // MCMultipart - name 'amadornes' - url 'http://maven.amadornes.com/' - } - maven { // CraftTweaker, ZenScript - name 'jared maven' - url 'http://blamejared.com/maven/' - } - maven { // IC2 - name 'industrialcraft' - url 'http://maven.ic2.player.to/' - } - maven { // WAILA/HWYLA - name 'tehnut' - url 'http://tehnut.info/maven/' - } - maven { // CoFH - name 'Covers Maven' - url 'http://maven.covers1624.net/' - } - maven { - name = 'CurseForge' - url = 'https://minecraft.curseforge.com/api/maven/' - } - maven { - name 'opencomputers' - url 'http://maven.cil.li/' - } - maven { // Mekanism, TAIGA - name 'jitpack' - url 'https://jitpack.io/' - } - maven { // Buildcraft - name 'buildcraft' - url 'https://mod-buildcraft.com/maven/' - } - maven { // Storage Drawers - name 'thiakil' - url 'http://maven.thiakil.com/' + mappings channel: project.mappings_channel, version: project.mappings_version + if (gradle.startParameter.offline) mavenizerArguments.add('--offline') + runs { + configureEach { + workingDir.convention layout.projectDirectory.dir( + providers.gradleProperty('modernMetalsRunDirectory').getOrElse('run')) + environment 'MC_VERSION', project.minecraft_version + environment 'MOD_CLASSES', developmentOutput.get().asFile.absolutePath + systemProperty 'fml.coreMods.load', project.coreplugin + systemProperty 'log4j.skipJansi', 'true' + systemProperty 'forge.logging.console.level', 'info' + systemProperty 'log4j.configurationFile', file('gradle/ide/log4j2.xml').toURI().toString() + if (providers.gradleProperty('modernMetalsLaunchProbePhase').isPresent()) { + systemProperty 'launch.phase', providers.gradleProperty('modernMetalsLaunchProbePhase').get() + } + } + register('client') + register('server') { args 'nogui' } } } -dependencies { - deobfCompile "com.mcmoddev:AdditionalLootTables:${mc_version}-${alt_version}" - deobfCompile "com.mcmoddev:OreSpawn:${mc_version}-${orespawn_version}" - deobfCompile "com.mcmoddev:BaseMetals:${mc_version}-${bme_version}" - deobfCompile "slimeknights:TConstruct:${mc_version}-${tconstruct_version}" - deobfCompile "slimeknights.mantle:Mantle:${mc_version}-${mantle_version}" - // CoFHCore for TE integration - deobfCompile ("cofh:CoFHCore:${mc_version}-${cofhcore_version}:universal") { - exclude group: 'mezz.jei' +tasks.matching { it.name in ['runClient', 'runServer'] }.configureEach { + dependsOn prepareDevelopmentMod + dependsOn 'prepareJava8Launcher' + doFirst { + Set splitOutputs = sourceSets.main.output.files.collect { it.canonicalFile } as Set + classpath = files(developmentOutput, tasks.named('prepareJava8Launcher'), classpath.filter { + !splitOutputs.contains(it.canonicalFile) && it.name != 'slime-launcher-0.2.2.jar' + }) + environment 'MC_VERSION', project.minecraft_version + environment 'MOD_CLASSES', developmentOutput.get().asFile.absolutePath } } -processResources { - inputs.property 'version', project.version - inputs.property 'mcversion', project.minecraft.version - - from (sourceSets.main.resources.srcDirs) { - include 'mcmod.info' - expand 'version': short_version, 'mcversion': project.minecraft.version - } - - from (sourceSets.main.resources.srcDirs) { - exclude 'mcmod.info' - } -} +def legacyMirror = file(providers.gradleProperty('legacyDependencyVerificationRepository') + .orElse(providers.gradleProperty('legacyDependencyRepository')) + .getOrElse('.gradle/legacy-dependencies')) +def legacyRepository = { String remote -> legacyMirror.isDirectory() ? legacyMirror.toURI() : uri(remote) } -jar { - manifest { -// attributes 'FMLCorePluginContainsFMLMod': 'true' -// attributes 'FMLCorePlugin': corePlugin -// attributes 'FMLAT' : '' +repositories { + minecraft.mavenizer(it) + maven fg.forgeMaven + maven fg.minecraftLibsMaven + maven { + name = 'VerifiedLegacyMirror' + url = legacyMirror.toURI() + metadataSources { artifact() } } -} - -task deobfJar(type: Jar) { - classifier = 'deobf' - from sourceSets.main.output -} - -task signJar(type: SignJar, dependsOn: reobfJar) { - - // Skips if the keyStore property is missing. - onlyIf { - project.hasProperty('keyStore') + exclusiveContent { + forRepository { maven { url = legacyRepository('https://www.cursemaven.com'); metadataSources { artifact() } } } + filter { includeGroup 'curse.maven' } } - - // findProperty allows us to reference the property without it existing. - // Using project.propName would cause the script to fail validation if - // the property did not exist. - keyStore = project.findProperty('keyStore') - alias = project.findProperty('keyStoreAlias') - storePass = project.findProperty('keyStorePass') - keyPass = project.findProperty('keyStoreKeyPass') - inputFile = jar.archivePath - outputFile = jar.archivePath -} - -build.dependsOn signJar - -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.getDestinationDir() -} - -artifacts { - archives deobfJar - archives sourceJar - archives javadocJar -} - -publishing { - publications { - mavenJava(MavenPublication) { - groupId project.group - artifactId project.archivesBaseName - version project.version - from components.java - artifact sourceJar { - classifier 'sources' - } - artifact deobfJar { - classifier 'deobf' - } - artifact javadocJar { - classifier 'javadoc' - } - } + exclusiveContent { + forRepository { maven { url = legacyRepository('https://chickenbones.net/maven/'); metadataSources { artifact() } } } + filter { includeGroup 'codechicken' } } - repositories { - maven { - credentials { - username secret.username - password secret.password - } - url secret.url - } + exclusiveContent { + forRepository { maven { url = legacyRepository('https://dvs1.progwml6.com/files/maven/'); metadataSources { artifact() } } } + filter { includeGroup 'slimeknights'; includeGroup 'slimeknights.mantle'; includeGroup 'mezz.jei' } } -} - -curseforge { - apiKey = secret.curseforgeAPIKey - project { - id = '244357' - changelog = file('CHANGELOG.txt') - releaseType = 'release' - addGameVersion(project.mc_version) - def projectName = 'Modern Metals' - def displayVersion = getVersion('VERSION', mod_file) - if (System.getenv().BUILD_NUMBER) { - displayVersion += '.' + System.getenv().BUILD_NUMBER - } - mainArtifact(jar) { - displayName = "$project.description $displayVersion" - relations { - requiredLibrary 'mmd-orespawn' - requiredLibrary 'base-metals' - optionalLibrary 'additional-loot-tables' - } - } - addArtifact(sourceJar) { - displayName = "$project.description $displayVersion Sources" - } - addArtifact(deobfJar) { - displayName = "$project.description $displayVersion Development" - } - addArtifact(javadocJar) { - displayName = "$project.description $displayVersion Javadoc" - } + exclusiveContent { + forRepository { maven { url = legacyRepository('https://maven.ic2.player.to/'); metadataSources { artifact() } } } + filter { includeGroup 'net.industrial-craft' } } -} - -sonarqube { - properties { - property 'sonar.host.url', secret.sonarHost - property 'sonar.organization', secret.sonarOrganization - property 'sonar.login', secret.sonarToken - property 'sonar.projectName', project.archivesBaseName - property 'sonar.projectKey', "$project.group:$project.archivesBaseName" + exclusiveContent { + forRepository { maven { url = legacyRepository('https://maven.covers1624.net/'); metadataSources { artifact() } } } + filter { includeGroup 'cofh' } } -} - -String getModFile() { - String path = '' - FileTree tree = fileTree(dir: 'src/main/java') - tree.include '**/*.java' - tree.visit { element -> - if (element.file.isFile()) { - element.file.eachLine { String s -> - s = s.trim() - if (s.startsWith('@Mod(')) { - path = "src/main/java/$element.relativePath" - } - } - } + exclusiveContent { + forRepository { maven { url = legacyRepository('https://maven.moddev.zone/releases'); metadataSources { artifact() } } } + filter { includeGroup 'zone.moddev.mc.basemetals'; includeGroup 'zone.moddev.mc.orespawn' } } - return path + mavenCentral() } -String getVersion(String type, String mod_file) { - String major = '0' - String revision = '0' - String patch = '0' - File file = file(mod_file) - def prefix = ~/^(?:public|protected|private) static final String $type = ".*$/ - file.eachLine { String s -> - s = s.trim() - if (s ==~ prefix || prefix.matcher(s).matches()) { - String[] bits = s.split("=") - String interesting = bits[1].trim() - interesting = interesting.substring(1, interesting.length() - 2) - String[] pts = interesting.trim().split("\\.") +apply from: 'gradle/dependencies.gradle' +renamer.mappings.from minecraft.dependency.toSrgFile - major = pts[0] ?: '0' - revision = pts[1] ?: '0' - patch = pts[2] ?: '0' - } - } - return "$major.$revision.$patch" -} +tasks.named('processResources', ProcessResources) { + filteringCharset = 'UTF-8' + exclude '**/desktop.ini', '**/Thumbs.db', '**/.DS_Store' + inputs.property 'version', project.version + inputs.property 'mcversion', project.minecraft_version + filesMatching('mcmod.info') { expand version: project.version, mcversion: project.minecraft_version } + rename '(.+_at.cfg)', 'META-INF/$1' + filesMatching('**/*.lang') { details -> details.name = details.name.toLowerCase(Locale.ROOT) } + filesMatching(textPatterns, normalizeText) + filesMatching(legacyApiList, normalizeLegacyApi) +} + +tasks.named('jar', Jar) { + archiveClassifier = 'deobf' + destinationDirectory = layout.buildDirectory.dir('libs-dev') + manifest.attributes( + 'Implementation-Title': 'ModernMetals', 'Implementation-Version': project.version, + 'Maven-Artifact': "${project.group}:ModernMetals:${project.version}", + 'Built-On-Java': '8', 'Built-On': "${project.minecraft_version}-${project.forge_version}", + 'FMLCorePluginContainsFMLMod': 'true', 'FMLCorePlugin': project.coreplugin) +} +['sourcesJar', 'javadocJar'].each { name -> + tasks.named(name, Jar) { + filteringCharset = 'UTF-8' + filesMatching(textPatterns, normalizeText) + filesMatching(legacyApiList, normalizeLegacyApi) + } +} + +ext.modernMetalsBuild = [developmentOutput: developmentOutput].asImmutable() +apply from: 'gradle/ore-models.gradle' +apply from: 'gradle/release/artifacts.gradle' +apply from: 'gradle/release/publishing.gradle' +apply from: 'gradle/verification/runtime.gradle' +apply from: 'gradle/ide/eclipse.gradle' diff --git a/docs/BUILDING.md b/docs/BUILDING.md new file mode 100644 index 00000000..e780903d --- /dev/null +++ b/docs/BUILDING.md @@ -0,0 +1,62 @@ +# Building Modern Metals 1.11.2 + +Use Gradle 9.6.1, ForgeGradle 7.0.34, Renamer 1.1.5 and Foojay 1.0.0. Gradle runs +on Java 17 and Mavenizer uses Java 25. Compilation, bytecode and Minecraft use Java 8. +Forge is 13.20.1.2588 with stable-32 mappings. Release pins are Temurin 8u502-b07, +17.0.1+12 and 25.0.3+9-LTS. Install them or pass their homes with +`org.gradle.java.installations.paths`. Do not launch Gradle on Java 8. + +Use Java 17 for `JAVA_HOME` and `PATH`. Until the pinned Base Metals and +OreSpawn builds are published, start with the staged checksum-verified Maven +mirror and pass its location as +`-PlegacyDependencyVerificationRepository=`. The mirror stores OreSpawn +at `zone/moddev/mc/orespawn/OreSpawn/4.1.5.111021/OreSpawn-4.1.5.111021.jar`. +The dependency audit checks its bytes against `gradle/dependencies.json`. +Once both artifacts are available, create the mirror and build as follows. + +```powershell +$env:GRADLE_USER_HOME='' +python gradle/stage-legacy-dependencies.py .gradle/legacy-dependencies +python -m pip install -r tools/validation/requirements.txt +.\gradlew.bat clean check build javadoc verifyReleaseArtifacts verifyProjectResources prepareEclipse +``` + +Use `-PpythonExecutable=` if Python is not named `python3`. +`gradle/dependencies.json` pins production downloads and SHA-256. Staging creates +a verified Maven mirror. Use `-PlegacyDependencyVerificationRepository=` +to select another location. Remapping and signature removal apply to development +copies. No floating mod versions, JCenter or +`mavenLocal()` are used. A normal build never publishes. + +`build/libs/ModernMetals-2.5.1.111021.jar` is the installed jar. Sources/Javadocs +are separate. Test probes are isolated from all release archives. + +## Eclipse + +Run `prepareEclipse` and import the inner Git/Gradle project using Buildship. Use +Java 17 for Gradle, a Java 8 project JRE and one physical cache for Eclipse and the +terminal. Production profiles contain Modern Metals, Base Metals and OreSpawn. +Optional APIs are compilation dependencies. Test profiles are separate. Regenerate +profiles when dependency/toolchain locations change. FG7 uses `genEclipseRuns` and +`prepareEclipse`. + +Gradle launches merge production classes and processed resources into one mod +directory. Eclipse uses `bin/main`, with resources from `build/resources/main` so +metadata is not duplicated. `verifyEclipseProductionClasspath` checks isolation. + +## Verification + +`generateOreModels` generates the ore block models as part of the normal build. +See [ore overlays](ORE_OVERLAYS.md) for the inputs, output and client checks. + +`test` covers config switches, provider conversion, titanium defaults, fantasy +policy and namespace compatibility. `verifyProjectResources` parses resources, +checks material assets, validates the provider against the pinned OreSpawn schema +and audits workflow pins. Packaged server checks require a clean Forge template: + +```text +gradlew packagedForgeRuntimeTest -PpackagedForgeRuntimeRoot= -PpackagedForgeRuntimeOutput= +``` + +The output directory must not already exist. See the validation README for exact +client commands, migration scenarios and limitations. diff --git a/docs/CONTENT_MODE.md b/docs/CONTENT_MODE.md new file mode 100644 index 00000000..a0227be8 --- /dev/null +++ b/docs/CONTENT_MODE.md @@ -0,0 +1,57 @@ +# Modern Metals fantasy setting + +In Minecraft 1.11.2, Modern Metals owns its fantasy setting. Open **Mods → Modern +Metals → Config → Fantasy Level** and choose **High Fantasy** or **Low Fantasy**. +The same selector is available through OreSpawn's child Mods list. Save with Done +and restart the game or server to apply the recipes. Forge checks for matching +settings before joining a multiplayer world and explains how to fix a mismatch. +Use the same build on both sides so the check is available. + +The setting is stored in **`config/ModernMetals.cfg`**: + +```text +general { + S:contentMode=low_fantasy + B:enableVillagerTrades=true +} +``` + +High Fantasy is the default. Missing or invalid values use High Fantasy. Invalid +file values are normalized at startup. Changes remain pending until a full restart +and do not rewrite live registries or recipes. + +**Villager Trades** defaults to on. Turn it off to stop new Modern Metals +blacksmith offers in either fantasy mode. The change applies after restart. +Offers already stored in a villager's inventory and offers from Base Metals +keep their existing behavior. + +The 1.11 version does not read or change Base Metals' fantasy setting or recipes. +If an earlier local candidate used `BaseMetals.cfg`, move the desired value to +`ModernMetals.cfg`. Core-mod inheritance is reserved for the 1.12 modernization. + +## Low Fantasy + +| Form | Allowed Modern Metals materials | +| --- | --- | +| Tools, swords, ammunition, Tinkers tool parts | Galvanized steel, stainless steel, titanium | +| Armor, shields, horse armor | Aluminum, aluminum brass, galvanized steel, stainless steel, titanium | +| Bows, crossbows, fishing rods, Tinkers bow parts | None | +| Gears | Aluminum, aluminum brass, galvanized steel, stainless steel, titanium | +| Anvils | Galvanized steel, stainless steel | +| Doors, bars, stairs, slabs, fences, walls, mechanisms and other structural products | Aluminum, aluminum brass, galvanized steel, nichrome, stainless steel, tantalum, titanium, tungsten, zirconium | +| Ores, ingots, powders, fluids, buckets, plates and storage blocks | Unchanged | + +Plutonium pickaxes remain registered so old stacks load, while their crafting +recipes and new equipment trade offers are unavailable. Titanium retains its +rutile-and-magnesium route. Molten Tinkers processing remains available for +materials that cannot supply parts. Restricted handles and ranged parts cannot +bypass the policy. Dynamic shared-library repair/upgrade recipes check their +resulting Modern Metals item too. + +The policy covers every material and product family. +it is not a chemistry or radiation simulation. For example, elemental boron, +rutile and radioactive materials stay useful as processing inputs but cannot be +turned into structural doors, stairs or gears in Low Fantasy. + +High Fantasy retains historical recipes, trades and integration behavior. Neither +mode unregisters IDs, rewrites saved inventories or enables retrogen. diff --git a/docs/ORE_GENERATION.md b/docs/ORE_GENERATION.md new file mode 100644 index 00000000..36b346fd --- /dev/null +++ b/docs/ORE_GENERATION.md @@ -0,0 +1,70 @@ +# Native OreSpawn 4 generation + +Modern Metals enqueues a schema-5 provider through the public OreSpawn API after +blocks register. It supplies ore declarations only. No OS3 plugin, legacy IMC +generator or second fallback generator is packaged. It supplies no terrain, +rock/strata, biome or fluid-deposit policy and requires no Mineralogy. + +Rules use `modernmetals:legacy/_ore` with their existing block IDs. +Disabled or missing blocks are omitted. Empty legacy dimension lists mean ordinary dimensions except +the vanilla Nether and End, rather than Overworld only. + +## Defaults + +The legacy exclusive maximum becomes inclusive. Quantity conversion is +`size - variation` through `size + variation - 1`, clamped to 1..64 as documented +by OreSpawn. The log reports minimum clamps at startup. + +| Ore | Attempts/chunk | Quantity | Inclusive Y | +| --- | ---: | --- | --- | +| Boron | 8 | 1..15 | 0..31 | +| Thorium | 8 | 4..11 | 0..31 | +| Beryllium | 20 | 2..7 | 0..127 | +| Aluminum | 8 | 2..17 | 0..95 | +| Cadmium | 8 | 1..11 | 0..95 | +| Chromium | 3 | 1..5 | 0..31 | +| Iridium | 5 | 2..9 | 0..63 | +| Magnesium | 8 | 2..9 | 0..95 | +| Manganese | 6 | 2..9 | 0..63 | +| Osmium | 8 | 6..13 | 0..95 | +| Plutonium | 4 | 1..5 | 0..31 | +| Rutile | 6 | 2..9 | 0..63 | +| Tantalum | 6 | 2..9 | 0..63 | +| Tungsten | 6 | 1..7 | 0..31 | +| Uranium | 6 | 1..5 | 0..31 | +| Zirconium | 6 | 4..11 | 0..63 | +| Titanium (disabled) | 6 | 2..9 | 0..63 | + +Titanium is independent and disabled by default. Its placement equals rutile's. +Enable it in the world's OreSpawn editor for future chunks of an existing world. +For future-world defaults, use `config/modernmetals-orespawn.json`. `test` exports +the full native declaration to `build/reports/native-provider.json` for pack authors. +There is no packaged JSON competing with the typed declaration. + +## Migration and saved choices + +OreSpawn 4.1.5 imports OS1 `config/orespawn/modernmetals.json` and OS3 +`config/orespawn3/modernmetals.json` into native overrides. It preserves +existing native overrides and saved world settings. Keep the legacy files +until you have checked the imported settings. + +Each world keeps `serverconfig/orespawn-worldgen.json`. Global edits do not replace +saved choices. New provider entries may merge into existing profiles while existing +entries retain their settings. Retrogen stays off: generated chunks and saved +stacks remain intact. Enabled ores can appear in later chunks. + +## Groups and overlaps + +The 1.11.2 Base Metals and historical Base Minerals defaults have no matching +generated material. Nether Metals +and End Metals have corresponding variants in distinct dimension domains. Custom +configs can create real same-domain conflicts that need pack-specific review. + +Each native rule declares its canonical `orespawn:` and the +`orespawn:standard` placement channel. The Ore Sources editor can group +equivalent sources and choose which source controls placement. The legacy Ore +Dictionary aliases still identify equivalent blocks and items. + +External IC2/Mekanism candidates in OreSpawn's catalog may be unloaded. A catalog +entry alone does not prove control over their native generation. Qualify the exact +installed pack before changing its source policies. diff --git a/docs/ORE_OVERLAYS.md b/docs/ORE_OVERLAYS.md new file mode 100644 index 00000000..d8f2bdce --- /dev/null +++ b/docs/ORE_OVERLAYS.md @@ -0,0 +1,31 @@ +# Ore overlays in Forge 1.11.2 + +Ore models use Minecraft's stone texture underneath KiriCattus's transparent +ore art. A resource pack that replaces stone also changes the stone part of +the ore. Thorium still uses its original full texture. + +Forge 1.11 predates Minecraft's data generator. Run `gradlew generateOreModels` +to generate the models without starting the game. The normal build runs it +automatically, and Eclipse uses the processed resources. The inputs are the +existing ore blockstates and PNGs in +`src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/`. +Generated models go to `build/generated/ore-models`. Existing blockstate and +item model IDs still point to the same block +model names. + +The generated models use the two cubes from Base Metals' generator. The outer +cube sits 0.05 model units above the host faces so they do not fight over the +same depth. On Forge 1.11 both cubes render in the cutout layer with mipmaps, +so the transparent pixels leave the host visible. The ore blocks still inherit +their mining and drop behaviour from Base Metals. + +The 20 overlay files come from [KiriCattus's art commit](https://github.com/MinecraftModDevelopmentMods/ModernMetals/commit/6c892309ab9c2cd19371a8a65ad59b250a647216). +Nineteen are used by registered ores, including the three alloy ores restored +for worlds saved by 0.11.0. The aluminum brass overlay is available as an asset. +OreSpawn still uses the same 17 rules, with titanium disabled by default. + +`verifyProjectResources` checks the generated resources and texture references. +The packaged client probe checks the placed and inventory models, face offsets +and render layer. Add `--ore-host-pack` on its reload run to test a replacement +stone texture and capture the ore display. See the +[validation guide](../tools/validation/legacy111/README.md). diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 00000000..e98805d6 --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,34 @@ +# Release preparation + +Normal builds never publish. Main, sources, Javadocs and SHA-256 form one audited +candidate. Gradle enforces Java 8 bytecode, deterministic archives, processed +metadata, dependency hashes and exclusion of tests, local context and shared +library copies. Prepared bundles contain exactly those three jars, `SHA256SUMS` +and `CHANGELOG.txt`. `verifyPreparedReleaseArtifacts` audits the bundle as supplied. + +CI has a cold bootstrap/offline rebuild, build/tests, resource/schema checks, +packaged server qualification, Eclipse generation and clean reproducibility check. +CodeQL/wrapper checks are separate. Actions use full commit hashes. + +The 1.11.2 version uses `Major.Minor.Patch.111021`. Tags validate metadata/prior CI without +publishing. The dispatcher requires `confirm_live_publication`, the upstream +repository, the full checked target commit, the verified game jar's SHA-256 +and successful hosted checks. It publishes +the same candidate bytes to Maven, CurseForge **244357**, then GitHub. CurseForge +requires Base Metals **240967** and MMD OreSpawn **245586**, with Additional Loot +Tables **243157** optional. Credentials and an HTTPS Maven target are required. + +The dispatcher must be installed on the repository's default branch before a +manual release can be started. The current dispatcher on the 1.12 default +branch routes 1.10.2. Install the reviewed 1.11.2 routing there as part of an +authorized release. If an upload times out, check its status before trying again. + +For the default-branch change, copy only the audited `deploy-release.yml`. It +routes each legacy suffix to its target branch and builds there. After a +release is publicly available, update the Forge 1.11.2 entries in `update.json` +to the exact version reported by Forge. + +The checksum-pinned Base Metals 1.11.2 and OreSpawn 4.1.5.111021 jars are +staged locally. Hosted CI requires matching files at their public Maven +coordinates. Reverify the pins if the published bytes differ from the +locally tested artifacts. diff --git a/gradle.properties b/gradle.properties index 81447da3..7635e2f8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,25 +1,33 @@ -mc_version=1.11.2 -top_mc_version=1.1x -ccl_version=2.7.0.+ -ccc_version=2.4.1.+ -nei_version=2.3.0.+ -jei_version=4.4.1.+ -mantle_version=1.2.0.+ -tconstruct_version=2.7.0.+ -mcmp_version=2.0.1 -mcmp_experimental_version=2.0.1_31 -top_version=1.4.7-57 -alt_version=2.0.+ -orespawn_version=3.2.+ -tesla_version=1.3.0.+ -bme_version=2.5.0-beta+ -mme_version=2.5.0-beta+ -fme_version=2.5.0-beta+ -bmi_version=2.5.0-beta+ -pa_version=2.3.0 -cofhcore_version=4.2.8.+ -mekanism_version=471563e083 -taiga_version=b874a41abb -ic2_version=2.7.71-ex111 -minetweaker_version=3.+ -hwyla_version=+ +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false +org.gradle.configuration-cache=false +org.gradle.caching=true +org.gradle.parallel=false +net.minecraftforge.gradle.merge-source-sets=false + +minecraft_version=1.11.2 +forge_version=13.20.1.2588 +mappings_channel=stable +mappings_version=32-1.11 + +mod_id=modernmetals +mod_group=zone.moddev.mc.modernmetals +mod_version=2.5.1.111021 +mod_artifact=ModernMetals +loader_name=forge +loader_code=1 +java_version=8 +java_toolchain_version=8.0.502+7 +java_setup_version=8.0.502+7 +gradle_java_version=17 +gradle_java_setup_version=17.0.1+12 +mavenizer_java_version=25.0.3+9 +curseforge_project_id=244357 +cf_requirements=mmd-orespawn,base-metals +orespawn_curse_project_id=245586 +coreplugin=zone.moddev.mc.modernmetals.compat.ModernMetalsLoadingPlugin +orespawn_version=4.1.5.111021 +orespawn_version_range=[4.1.5.111021,5.0.0) + +basemetals_version=2.5.1.111021 +basemetals_version_range=[2.5.1.111021,2.6.0) diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle new file mode 100644 index 00000000..9748783b --- /dev/null +++ b/gradle/dependencies.gradle @@ -0,0 +1,75 @@ +import groovy.json.JsonSlurper +import java.security.MessageDigest + +def specs = new JsonSlurper().parse(file('gradle/dependencies.json')) +def mapped = specs.collectEntries { [(it.id): renamer.dependency(it.coordinate)] } + +configurations { + legacyModArtifacts { canBeConsumed = false; transitive = false } + java8LauncherArtifact { canBeConsumed = false; transitive = false } +} +dependencies { + implementation minecraft.dependency("net.minecraftforge:forge:${minecraft_version}-${forge_version}") + java8LauncherArtifact 'net.minecraftforge:slime-launcher:0.2.2' + compileOnly 'org.lwjgl.lwjgl:lwjgl_util:2.9.4-nightly-20150209' + specs.each { spec -> + if (spec.runtime) implementation mapped[spec.id] + else compileOnly mapped[spec.id] + legacyModArtifacts spec.coordinate + } + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.2' +} + +// Forge 1.11's mod scanner cannot read Java 11 multi-release entries. They are +// unused on Java 8, so omit them from the development launcher copy. +tasks.register('prepareJava8Launcher', Jar) { + dependsOn configurations.java8LauncherArtifact + archiveFileName = 'slime-launcher-0.2.2-java8.jar' + destinationDirectory = layout.buildDirectory.dir('development-dependencies') + from { zipTree(configurations.java8LauncherArtifact.singleFile) } + exclude 'META-INF/versions/**', 'META-INF/MANIFEST.MF', 'META-INF/*.SF', 'META-INF/*.RSA', 'META-INF/*.DSA' +} + +// Verify the original files, not renamed copies whose bytes depend on mappings. +tasks.register('verifyReleaseDependencies') { + group = 'verification' + description = 'Checks every pinned integration API against its SHA-256.' + inputs.file 'gradle/dependencies.json' + doLast { + def artifacts = configurations.legacyModArtifacts.resolvedConfiguration.resolvedArtifacts + def orespawn = specs.find { it.id == 'orespawn' } + if (orespawn.coordinate != 'zone.moddev.mc.orespawn:OreSpawn:4.1.5.111021' + || orespawn.sha256 != '5B200A9CE09DF96089FBD7781A026D15B6A7566A0F11B025EBBF486AB8A3655A') { + throw new GradleException('Use the checksum-pinned OreSpawn 4.1.5.111021 jar.') + } + def basemetals = specs.find { it.id == 'basemetals' } + if (basemetals.coordinate != 'zone.moddev.mc.basemetals:BaseMetals:2.5.1.111021' + || basemetals.sha256 != 'F56C77ACE4963991AB56A76468EB398F555E4F6D059710C41DD7483054D76B8C') { + throw new GradleException('Use the verified Base Metals 2.5.1.111021 release.') + } + specs.each { spec -> + def parts = spec.coordinate.split(':') + def artifact = artifacts.find { it.moduleVersion.id.group == parts[0] + && it.name == parts[1] && it.moduleVersion.id.version == parts[2] } + if (artifact == null) throw new GradleException("Missing ${spec.coordinate}") + String actual = MessageDigest.getInstance('SHA-256').digest(artifact.file.bytes).encodeHex().toString() + if (!actual.equalsIgnoreCase(spec.sha256)) { + throw new GradleException("Checksum mismatch for ${spec.coordinate}: ${actual}") + } + if (spec.id == 'orespawn') { + new java.util.jar.JarFile(artifact.file).withCloseable { jar -> + def metadata = new JsonSlurper().parse(jar.getInputStream(jar.getJarEntry('mcmod.info'))) + if (!metadata.any { it.modid == 'orespawn' && it.version == project.orespawn_version } + || jar.getJarEntry('zone/moddev/mc/orespawn/api/OreSpawnApi.class') == null + || jar.manifest.mainAttributes.getValue('OreSpawn-API-Version') != '1') { + throw new GradleException('Wrong OreSpawn identity or public API.') + } + } + } + } + } +} +tasks.named('compileJava') { dependsOn tasks.named('verifyReleaseDependencies') } +tasks.named('check') { dependsOn tasks.named('verifyReleaseDependencies') } diff --git a/gradle/dependencies.json b/gradle/dependencies.json new file mode 100644 index 00000000..35d602d2 --- /dev/null +++ b/gradle/dependencies.json @@ -0,0 +1,52 @@ +[ + { + "id": "basemetals", + "coordinate": "zone.moddev.mc.basemetals:BaseMetals:2.5.1.111021", + "sha256": "F56C77ACE4963991AB56A76468EB398F555E4F6D059710C41DD7483054D76B8C", + "repository": "https://maven.moddev.zone/releases", + "runtime": true + }, + { + "id": "orespawn", + "coordinate": "zone.moddev.mc.orespawn:OreSpawn:4.1.5.111021", + "sha256": "5B200A9CE09DF96089FBD7781A026D15B6A7566A0F11B025EBBF486AB8A3655A", + "repository": "https://maven.moddev.zone/releases", + "runtime": true + }, + { + "id": "mantle", + "coordinate": "curse.maven:mantle-74924:2413993", + "sha256": "6C1C79BD8491106D65240D4DF139E2BEA7914BA2622FAE0662ADB3047D5EDF9D", + "repository": "https://www.cursemaven.com" + }, + { + "id": "tconstruct", + "coordinate": "curse.maven:tinkers-construct-74072:2451489", + "sha256": "2D7F4A08EDDA2881A432CD80FF26575CCEA593FCC8A597A435652C1A53B14CB8", + "repository": "https://www.cursemaven.com" + }, + { + "id": "ic2", + "coordinate": "curse.maven:industrial-craft-242638:2457397", + "sha256": "ACCFA9A503A4B194CFF9528EA521AB42E00A85620F13DDE268BE14C112305B98", + "repository": "https://www.cursemaven.com" + }, + { + "id": "cofh", + "coordinate": "curse.maven:cofh-core-69162:2469613", + "sha256": "7C3EC573F62AE6F3F98B34361123E9D706CFDF03419AE6242293515960391C11", + "repository": "https://www.cursemaven.com" + }, + { + "id": "mekanism", + "coordinate": "curse.maven:mekanism-268560:2475809", + "sha256": "AF013AB6843174E4E766EC98A576D60047EAEA372CD01F6BF5460678EFE86D19", + "repository": "https://www.cursemaven.com" + }, + { + "id": "veinminer", + "coordinate": "curse.maven:veinminer-67133:2528292", + "sha256": "75B37AC8778261875A692D468ABAA3B1BD8AFF61D18B4EA900C26AA2C8528AD7", + "repository": "https://www.cursemaven.com" + } +] diff --git a/gradle/ide/eclipse.gradle b/gradle/ide/eclipse.gradle new file mode 100644 index 00000000..b63025ae --- /dev/null +++ b/gradle/ide/eclipse.gradle @@ -0,0 +1,206 @@ +def escapeXml = { String text -> + text.replace('&', '&').replace('"', '"').replace('<', '<').replace('>', '>') +} + +eclipse { + project { + buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder' + natures 'org.eclipse.buildship.core.gradleprojectnature' + } + classpath { + downloadSources = true + downloadJavadoc = true + file.beforeMerged { classpath -> + Set managed = ['src/main/java', 'src/main/resources', 'src/test/java', + 'build/resources/main', 'tools/validation/legacy111', + 'tools/validation/legacy111/migration-probe', + 'tools/validation/legacy111/launch-probe', + 'tools/validation/legacy111/orespawn-probe'] as Set + classpath.entries.removeAll { it.kind == 'src' && managed.contains(it.path) } + } + file.whenMerged { classpath -> + // Eclipse must copy the expanded metadata, not the source placeholders. + classpath.entries.findAll { it.kind == 'src' && it.path == 'src/main/resources' }.each { + it.path = 'build/resources/main' + } + classpath.entries.findAll { it.kind == 'src' && !it.path.startsWith('src/main/') }.each { + if (it.path != 'build/resources/main') it.entryAttributes['test'] = 'true' + } + } + file.withXml { provider -> + Node root = provider.asNode() + root.classpathentry.findAll { it.@kind == 'lib' }.each { root.remove(it) } + if (!root.classpathentry.any { it.@path == 'org.eclipse.buildship.core.gradleclasspathcontainer' }) { + root.appendNode('classpathentry', [kind: 'con', path: 'org.eclipse.buildship.core.gradleclasspathcontainer']) + } + } + } + synchronizationTasks 'genEclipseRuns' +} +idea.module { downloadSources = true; downloadJavadoc = true } + +def configureBuildship = tasks.register('configureEclipseBuildship') { + group = 'ide' + doLast { + File target = file('.settings/org.eclipse.buildship.core.prefs') + Properties values = new Properties() + if (target.isFile()) target.withInputStream { values.load(it) } + [ + 'eclipse.preferences.version': '1', + 'connection.gradle.distribution': 'GRADLE_DISTRIBUTION(WRAPPER)', + 'connection.gradle.user.home': gradle.gradleUserHomeDir.canonicalPath, + 'gradle.user.home': gradle.gradleUserHomeDir.canonicalPath, + 'connection.java.home': System.getProperty('java.home'), + 'java.home': System.getProperty('java.home'), + 'connection.project.dir': '', 'override.workspace.settings': 'true' + ].each { key, value -> values.setProperty(key, value) } + target.parentFile.mkdirs() + target.withOutputStream { values.store(it, 'Generated by configureEclipseBuildship.') } + } +} + +tasks.register('isolateEclipseProductionRuns') { + group = 'ide' + description = 'Quotes launcher paths and keeps optional API jars and tests out of ordinary launches.' + dependsOn configureBuildship, tasks.named('processResources'), tasks.named('prepareJava8Launcher') + doLast { + // Copy, rather than Sync: Eclipse owns and compiles the classes in bin/main. + copy { from layout.buildDirectory.dir('resources/main'); into file('bin/main') } + file('run').mkdirs() + List runtime = configurations.runtimeClasspath.files.collect { it.canonicalFile } + ['runClient.launch', 'runServer.launch'].each { name -> + File launch = file(name) + if (!launch.isFile()) throw new GradleException("Missing generated launch: ${name}") + String text = launch.getText('UTF-8').replace('\r\n', '\n') + text = text.replace(escapeXml(configurations.java8LauncherArtifact.singleFile.canonicalPath), + escapeXml(tasks.named('prepareJava8Launcher', Jar).get().archiveFile.get().asFile.canonicalPath)) + def arguments = text =~ /()/ + if (arguments.count != 1) throw new GradleException("Malformed launcher arguments: ${name}") + String args = arguments[0][2] + ['cache', 'metadata', 'to-srg', 'to-obf'].each { flag -> + int start = args.indexOf("--${flag} ") + if (start < 0) throw new GradleException("Missing --${flag} in ${name}") + start += flag.length() + 3 + int end = args.indexOf(' --', start) + if (end < 0) throw new GradleException("Unterminated --${flag} in ${name}") + String value = args.substring(start, end) + if (!value.startsWith('"')) args = args.substring(0, start) + '"' + value + '"' + args.substring(end) + } + text = text.replace(arguments[0][0], arguments[0][1] + args + arguments[0][3]) + text = text.replaceAll(//, + '') + String modClasses = '' + if (text.contains('key="MOD_CLASSES"')) { + text = text.replaceAll(//, + java.util.regex.Matcher.quoteReplacement(modClasses)) + } else { + text = text.replace('', ' ' + modClasses + '\n ') + } + List lines = text.split('\n', -1) as List + def entries = (0.. + String memento = '' + ' ' + }.join('\n') + text = lines.join('\n') + if (!text.contains('ATTR_EXCLUDE_TEST_CODE')) { + text = text.replace('', + ' \n') + } + launch.setText(text, 'UTF-8') + } + } +} +tasks.named('genEclipseRuns') { finalizedBy tasks.named('isolateEclipseProductionRuns') } +tasks.named('eclipse') { dependsOn tasks.named('genEclipseRuns') } + +def verifyJavaSourceLayout = tasks.register('verifyJavaSourceLayout') { + group = 'verification' + description = 'Checks that Java package names match their source folders, as Eclipse requires.' + inputs.files(provider { sourceSets.collect { it.java } }) + doLast { + List mismatches = [] + sourceSets.each { sourceSet -> + sourceSet.java.visit { entry -> + if (entry.directory || !entry.name.endsWith('.java')) return + + String path = entry.relativePath.pathString + int separator = path.lastIndexOf('/') + String expected = separator < 0 ? '' : path.substring(0, separator).replace('/', '.') + def declaration = entry.file.getText('UTF-8') =~ /(?m)^\s*package\s+([\w.]+)\s*;/ + String declared = declaration.find() ? declaration.group(1) : '' + + if (declared != expected) { + mismatches.add("${sourceSet.name}: ${path} declares '${declared}', expected '${expected}'") + } + } + } + if (!mismatches.isEmpty()) { + throw new GradleException('Java packages do not match their source folders:\n' + mismatches.join('\n')) + } + } +} + +tasks.named('check') { dependsOn verifyJavaSourceLayout } + +tasks.register('verifyEclipseProductionClasspath') { + group = 'verification' + dependsOn tasks.named('genEclipseRuns'), verifyJavaSourceLayout + mustRunAfter tasks.named('eclipse') + doLast { + List runtime = configurations.runtimeClasspath.files.collect { it.canonicalFile } + Set optional = configurations.compileClasspath.files.collect { it.canonicalFile } as Set + optional.removeAll(runtime) + ['runClient.launch', 'runServer.launch'].each { name -> + String text = file(name).getText('UTF-8').replace('\\', '/') + if (!text.contains('MAIN_TYPE" value="net.minecraftforge.launcher.Main"') + || !text.contains('StandardVMType/JavaSE-8') + || !text.contains('MC_VERSION" value="1.11.2"') + || !text.contains('ATTR_EXCLUDE_TEST_CODE') + || text.contains('gradleclasspathcontainer') || text.contains('bin/test') + || text.contains('slime-launcher-0.2.2.jar') + || text.contains('junit-') || text.contains('legacy111-probe')) { + throw new GradleException("Unsafe production launch: ${name}") + } + if (runtime.any { !text.contains(it.path.replace('\\', '/')) } + || optional.any { text.contains(it.path.replace('\\', '/')) }) { + throw new GradleException("Runtime/compile-only separation failed in ${name}") + } + ['cache', 'metadata', 'to-srg', 'to-obf'].each { flag -> + if (!text.contains("--${flag} "")) throw new GradleException("Unquoted --${flag} in ${name}") + } + } + String info = file('bin/main/mcmod.info').getText('UTF-8') + if (info.contains('${version}') || !info.contains(project.version.toString())) { + throw new GradleException('Eclipse is using unprocessed resources.') + } + def classpath = new groovy.xml.XmlSlurper().parse(file('.classpath')) + List sourcePaths = classpath.classpathentry.findAll { it.@kind == 'src' }.collect { it.@path.text() } + if (sourcePaths.size() != sourcePaths.unique(false).size() + || sourcePaths.contains('tools/validation/legacy111')) { + throw new GradleException('Eclipse retains stale or duplicate source-folder entries.') + } + classpath.classpathentry.findAll { it.@kind == 'src' && !it.@path.text().startsWith('src/main/') + && it.@path.text() != 'build/resources/main' }.each { + if (!it.attributes.attribute.any { it.@name == 'test' && it.@value == 'true' }) { + throw new GradleException("Non-production Eclipse source is not marked as test code: ${it.@path}") + } + } + } +} +tasks.register('prepareEclipse') { + group = 'ide' + description = 'Generates Eclipse metadata and verifies Java 8 production-only launches.' + dependsOn tasks.named('eclipse'), tasks.named('verifyEclipseProductionClasspath'), 'writeEclipseCompileClasspath' +} + +tasks.register('writeEclipseCompileClasspath') { + group = 'verification' + description = 'Writes the resolved editor classpath for headless Eclipse compiler checks.' + doLast { + File target = layout.buildDirectory.file('ide/compile-classpath.txt').get().asFile + target.parentFile.mkdirs() + target.setText(configurations.compileClasspath.asPath, 'UTF-8') + } +} diff --git a/gradle/ide/log4j2.xml b/gradle/ide/log4j2.xml new file mode 100644 index 00000000..a770e981 --- /dev/null +++ b/gradle/ide/log4j2.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/ore-models.gradle b/gradle/ore-models.gradle new file mode 100644 index 00000000..5f7de636 --- /dev/null +++ b/gradle/ore-models.gradle @@ -0,0 +1,71 @@ +import groovy.json.JsonOutput + +// Forge 1.11 has no GatherDataEvent. Generate the equivalent model resources +// before processResources, without launching Minecraft or writing into src. +def assets = layout.projectDirectory.dir('src/main/resources/assets/modernmetals') +def overlayFiles = fileTree(assets.dir('textures/blocks/ore_overlays')) { include '*_ore.png' } +def oreStates = fileTree(assets.dir('blockstates')) { include '*_ore.json' } +def generated = layout.buildDirectory.dir('generated/ore-models') + +def generateOreModels = tasks.register('generateOreModels') { + group = 'build' + description = 'Generates the Forge 1.11 ore models from KiriCattus’s overlays.' + inputs.files(overlayFiles, oreStates).withPathSensitivity(PathSensitivity.RELATIVE) + outputs.dir(generated) + + doLast { + def names = overlayFiles.files.collect { it.name - '.png' }.sort() + def expected = oreStates.files.collect { it.name - '.json' }.findAll { it != 'thorium_ore' }.sort() + if (names != expected) { + throw new GradleException("Ore overlays do not match ore blockstates: missing ${expected - names}, extra ${names - expected}") + } + + def dir = generated.get().dir('assets/modernmetals/models/block').asFile + dir.mkdirs() + def writeModel = { String name, Map model -> + new File(dir, "${name}.json").setText(JsonOutput.prettyPrint(JsonOutput.toJson(model)) + '\n', 'UTF-8') + } + def cube = { Number from, Number to, String texture -> + def faces = ['down', 'up', 'north', 'south', 'west', 'east'].collectEntries { face -> + [(face): [texture: texture, cullface: face, uv: [0, 0, 16, 16]]] + } + [from: [from, from, from], to: [to, to, to], faces: faces] + } + // Same two cubes and offset as Base Metals, with the legacy texture paths. + writeModel('ore_overlay', [ + parent: 'minecraft:block/block', + elements: [cube(0, 16, '#base'), cube(-0.05, 16.05, '#overlay')] + ]) + names.each { name -> + writeModel(name, [ + parent: 'modernmetals:block/ore_overlay', + textures: [particle: 'minecraft:blocks/stone', base: 'minecraft:blocks/stone', + overlay: "modernmetals:blocks/ore_overlays/${name}"] + ]) + } + // Clean up only obsolete ore JSON files in this task's own output directory. + dir.listFiles().findAll { it.isFile() && it.name.endsWith('_ore.json') && !(it.name - '.json' in names) }.each { + if (!it.delete()) throw new GradleException("Could not remove stale generated ore model ${it.name}") + } + } +} + +tasks.named('processResources', ProcessResources) { + dependsOn generateOreModels + from generated + duplicatesStrategy = DuplicatesStrategy.FAIL + doFirst { + // Copy tasks can leave the old flat textures behind after an incremental upgrade. + overlayFiles.files.each { overlay -> + def flat = new File(destinationDir, "assets/modernmetals/textures/blocks/${overlay.name}") + if (flat.isFile() && !flat.delete()) { + throw new GradleException("Could not remove obsolete processed texture ${flat.name}") + } + } + } +} + +tasks.named('sourcesJar', Jar) { + dependsOn generateOreModels + from generated +} diff --git a/gradle/prepare-packaged-forge-runtime.sh b/gradle/prepare-packaged-forge-runtime.sh new file mode 100644 index 00000000..577a08a4 --- /dev/null +++ b/gradle/prepare-packaged-forge-runtime.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 2 ]]; then + echo "usage: $0 " >&2 + exit 2 +fi +runtime="$1" +java8="$2" +version=1.11.2-13.20.1.2588 +expected=00AEA3DDF1E78BBB4D5D05E8C22FB3E849DE09B47D828E57DAD373A16BB8A26F + +if [[ -e "$runtime" && -n "$(find "$runtime" -mindepth 1 -maxdepth 1 -print -quit)" ]]; then + echo "Packaged Forge destination must be empty: $runtime" >&2 + exit 1 +fi +mkdir -p "$runtime" +installer="$runtime/forge-$version-installer.jar" +curl --fail --location --silent --show-error --retry 3 --connect-timeout 20 --max-time 180 \ + "https://maven.minecraftforge.net/net/minecraftforge/forge/$version/forge-$version-installer.jar" \ + --output "$installer" +actual="$(sha256sum "$installer" | awk '{print toupper($1)}')" +if [[ "$actual" != "$expected" ]]; then + echo "Forge installer checksum mismatch: $actual" >&2 + exit 1 +fi +# The legacy installer writes to its working directory. +(cd "$runtime" && "$java8" -jar "forge-$version-installer.jar" --installServer) +if [[ ! -f "$runtime/forge-$version-universal.jar" && -f "$runtime/forge-$version.jar" ]]; then + cp "$runtime/forge-$version.jar" "$runtime/forge-$version-universal.jar" +fi +test -f "$runtime/forge-$version-universal.jar" +test -f "$runtime/minecraft_server.1.11.2.jar" +test -d "$runtime/libraries" diff --git a/gradle/release/artifacts.gradle b/gradle/release/artifacts.gradle new file mode 100644 index 00000000..5406280d --- /dev/null +++ b/gradle/release/artifacts.gradle @@ -0,0 +1,184 @@ +import groovy.json.JsonSlurper +import java.security.MessageDigest +import java.util.jar.Manifest +import java.util.zip.ZipFile + +def releaseJar = renamer.classes(tasks.named('jar', Jar)) { + map.from minecraft.dependency.toSrgFile + archiveClassifier = null + output = layout.buildDirectory.file("libs/ModernMetals-${project.version}.jar") +} +['apiElements', 'runtimeElements'].each { name -> + configurations.named(name) { artifacts.clear() } + artifacts.add(name, releaseJar) +} +tasks.named('assemble') { dependsOn releaseJar, tasks.named('sourcesJar'), tasks.named('javadocJar') } + +tasks.register('verifyJava8Toolchain') { + group = 'verification' + description = 'Checks Java 8 compatibility; exact compiler versions are required only for release builds.' + doLast { + def compiler = javaToolchains.compilerFor { languageVersion = JavaLanguageVersion.of(8) }.get().metadata + if (compiler.languageVersion.asInt() != 8) throw new GradleException('Minecraft 1.11 requires Java 8.') + if (providers.gradleProperty('releaseBuild').getOrElse('false').toBoolean() + && (compiler.javaRuntimeVersion != '1.8.0_502-b07' + || System.getProperty('java.runtime.version') != '17.0.1+12')) { + throw new GradleException('Release builds require Temurin 8u502-b07 and Gradle on 17.0.1+12.') + } + if (providers.gradleProperty('releaseBuild').getOrElse('false').toBoolean()) { + def mavenizer = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(25) }.get().metadata + if (mavenizer.javaRuntimeVersion != '25.0.3+9-LTS') { + throw new GradleException('Release setup requires Mavenizer on Temurin 25.0.3+9.') + } + } + } +} + +tasks.register('verifyReleaseConfiguration') { + group = 'verification' + dependsOn tasks.named('verifyJava8Toolchain') + doLast { + if (project.group != 'zone.moddev.mc.modernmetals' || project.version != '2.5.1.111021' + || project.minecraft_version != '1.11.2' || project.forge_version != '13.20.1.2588' + || project.mappings_channel != 'stable' || project.mappings_version != '32-1.11') { + throw new GradleException('Modern Metals target, mappings or Maven identity changed.') + } + String source = file('src/main/java/zone/moddev/mc/modernmetals/ModernMetals.java').getText('UTF-8') + if (!source.contains('VERSION = "' + project.version + '"') || !source.contains('MODID = "modernmetals"')) { + throw new GradleException('The mod identity differs from the build metadata.') + } + if (project.orespawn_version != '4.1.5.111021' + || project.orespawn_version_range != '[4.1.5.111021,5.0.0)' + || !source.contains('required-after:orespawn@' + project.orespawn_version_range) + || !source.contains('required-after:forge@[13.20.1.2588,)')) { + throw new GradleException('The OreSpawn or Forge runtime requirement changed.') + } + if (!source.contains('required-after:basemetals@[2.5.1.111021,2.6.0)')) { + throw new GradleException('The published Base Metals runtime requirement changed.') + } + } +} + +def expectedFiles = ['', '-sources', '-javadoc'].collect { "ModernMetals-${project.version}${it}.jar".toString() }.asImmutable() +def preparedDirectory = providers.gradleProperty('preparedReleaseDir') +def auditJars = { File directory -> + ['','-sources','-javadoc'].each { suffix -> + File jar = new File(directory, "ModernMetals-${project.version}${suffix}.jar") + if (!jar.isFile() || jar.length() == 0) throw new GradleException("Missing ${jar.name}") + new ZipFile(jar).withCloseable { zip -> + zip.entries().each { entry -> + String path = entry.name.toLowerCase(Locale.ROOT) + if (path.startsWith('review/') || path.contains('probe') || path.contains('fixture') + || path.contains('agent-notes') || path.contains('agents.md') || path.contains('.codex') + || path.contains('.agents') || path.contains('claude') || path.contains('gemini') + || path.tokenize('/').any { it in ['desktop.ini', 'thumbs.db', '.ds_store'] } + || path.startsWith('tools/') || path.startsWith('reference/') + || path.startsWith('com/mcmoddev/lib/') || path.startsWith('zone/moddev/mc/basemetals/') + || path.startsWith('com/mcmoddev/orespawn/') + || path == 'assets/modernmetals/orespawn/modernmetals.json' + || path.startsWith('org/junit/') || path.startsWith('net/minecraft/')) { + throw new GradleException("Non-production entry in ${jar.name}: ${entry.name}") + } + def date = entry.timeLocal + if (!entry.directory && !(date.toString() in ['1980-01-01T00:00', '1980-02-01T00:00'])) { + throw new GradleException("Volatile archive timestamp: ${entry.name}") + } + if (!entry.directory && path ==~ /.*\.(java|html|css|js|json|lang|mcmeta|md|txt|xml|cfg|info|properties)/) { + def decoder = java.nio.charset.StandardCharsets.UTF_8.newDecoder() + .onMalformedInput(java.nio.charset.CodingErrorAction.REPORT) + .onUnmappableCharacter(java.nio.charset.CodingErrorAction.REPORT) + String content = decoder.decode(java.nio.ByteBuffer.wrap(zip.getInputStream(entry).bytes)).toString() + if (content.contains('\r')) throw new GradleException("Non-LF text in ${entry.name}") + } + if (!entry.directory && path ==~ /.*\.(class|java|html|json|info|xml|properties|txt|md|lang|cfg)/) { + String content = new String(zip.getInputStream(entry).bytes, + java.nio.charset.StandardCharsets.ISO_8859_1) + if (content.contains(project.projectDir.absolutePath) + || content.contains('/home/runner/work/')) { + throw new GradleException("Local build path in ${entry.name}") + } + } + if (entry.name.endsWith('.class')) { + byte[] data = zip.getInputStream(entry).bytes + int major = ((data[6] & 255) << 8) | (data[7] & 255) + if (major != 52) throw new GradleException("Not Java 8 bytecode: ${entry.name}") + String constants = new String(data, java.nio.charset.StandardCharsets.ISO_8859_1) + if (constants.contains(project.projectDir.absolutePath)) { + throw new GradleException("Local path in ${entry.name}") + } + } + } + if (suffix == '') { + def manifest = new Manifest(zip.getInputStream(zip.getEntry('META-INF/MANIFEST.MF'))).mainAttributes + if (manifest.getValue('Maven-Artifact') != "${project.group}:ModernMetals:${project.version}" + || manifest.getValue('FMLCorePlugin') != project.coreplugin + || manifest.keySet().any { it.toString().toLowerCase().contains('timestamp') }) { + throw new GradleException('Incorrect release manifest.') + } + def info = new JsonSlurper().parse(zip.getInputStream(zip.getEntry('mcmod.info')))[0] + if (info.modid != 'modernmetals' || info.version != project.version || info.mcversion != '1.11.2') { + throw new GradleException('Incorrect processed mcmod.info.') + } + if (zip.getEntry('zone/moddev/mc/modernmetals/ModernMetals.class') == null + || zip.getEntry('META-INF/modernmetals-legacy-api.list') == null) { + throw new GradleException('Missing mod entry point or legacy API map.') + } + } + } + } +} + +tasks.register('verifyReleaseArtifacts') { + group = 'verification' + description = 'Audits the shipped jars for Java 8 bytecode, metadata and test isolation.' + dependsOn tasks.named('assemble'), tasks.named('verifyReleaseConfiguration'), tasks.named('verifyReleaseDependencies') + doLast { auditJars(layout.buildDirectory.dir('libs').get().asFile) } +} + +tasks.register('writeReleaseChecksums') { + group = 'verification' + dependsOn tasks.named('verifyReleaseArtifacts') + doLast { + File directory = layout.buildDirectory.dir('libs').get().asFile + List lines = ['','-sources','-javadoc'].collect { suffix -> + File jar = new File(directory, "ModernMetals-${project.version}${suffix}.jar") + MessageDigest.getInstance('SHA-256').digest(jar.bytes).encodeHex().toString() + ' ' + jar.name + } + File sums = layout.buildDirectory.file('release/SHA256SUMS').get().asFile + sums.parentFile.mkdirs() + sums.setText(lines.join('\n') + '\n', 'UTF-8') + } +} +tasks.named('check') { dependsOn tasks.named('verifyReleaseConfiguration') } + +tasks.register('verifyPreparedReleaseArtifacts') { + group = 'verification' + description = 'Verifies a previously built bundle without rebuilding its jars.' + doLast { + if (!preparedDirectory.isPresent()) throw new GradleException('Set preparedReleaseDir to the immutable release bundle.') + File directory = file(preparedDirectory.get()) + Set expected = (expectedFiles + ['SHA256SUMS', 'CHANGELOG.txt']) as Set + if (!directory.isDirectory() || directory.listFiles().collect { it.name }.toSet() != expected) { + throw new GradleException('The prepared release must contain exactly three jars, SHA256SUMS and CHANGELOG.txt.') + } + List lines = new File(directory, 'SHA256SUMS').readLines('UTF-8') + if (lines.size() != 3) throw new GradleException('The prepared checksum list must contain exactly three files.') + Set checked = [] as Set + lines.each { line -> + def match = line =~ /^([a-fA-F0-9]{64}) (ModernMetals-[^\/\\]+\.jar)$/ + if (!match.matches() || !expectedFiles.contains(match[0][2]) || !checked.add(match[0][2])) { + throw new GradleException('Invalid or duplicate prepared checksum entry.') + } + File jar = new File(directory, match[0][2]) + String actual = MessageDigest.getInstance('SHA-256').digest(jar.bytes).encodeHex().toString() + if (!actual.equalsIgnoreCase(match[0][1])) throw new GradleException("Prepared checksum mismatch: ${jar.name}") + } + if (!new File(directory, 'CHANGELOG.txt').getText('UTF-8').startsWith(project.version.toString() + ' for 1.11.2')) { + throw new GradleException('The prepared changelog belongs to a different release.') + } + auditJars(directory) + } +} + +ext.modernMetalsRelease = [jar: releaseJar, expectedFiles: expectedFiles, + preparedDirectory: preparedDirectory].asImmutable() diff --git a/gradle/release/publishing.gradle b/gradle/release/publishing.gradle new file mode 100644 index 00000000..3f6513cc --- /dev/null +++ b/gradle/release/publishing.gradle @@ -0,0 +1,96 @@ +import groovy.xml.XmlSlurper + +def releaseJar = project.ext.modernMetalsRelease.jar +def expectedReleaseFiles = project.ext.modernMetalsRelease.expectedFiles +def preparedReleaseDir = project.ext.modernMetalsRelease.preparedDirectory + +// A normal build does not publish anything; publishing needs credentials. +def mavenUploadUrl = providers.environmentVariable('MAVEN_UPLOAD_URL') + .map { it.trim() }.filter { !it.isEmpty() } + .orElse('https://invalid.invalid/missing-maven-upload-url') +def mavenUploadUsername = providers.environmentVariable('MAVEN_UPLOAD_USERNAME') +def mavenUploadPassword = providers.environmentVariable('MAVEN_UPLOAD_PASSWORD') + +publishing { + publications { + mavenJava(MavenPublication) { + groupId = project.group.toString() + artifactId = base.archivesName.get() + version = project.version.toString() + if (preparedReleaseDir.isPresent()) { + File prepared = file(preparedReleaseDir.get()) + artifact(new File(prepared, expectedReleaseFiles[0])) + artifact(new File(prepared, expectedReleaseFiles[1])) { classifier = 'sources' } + artifact(new File(prepared, expectedReleaseFiles[2])) { classifier = 'javadoc' } + } else { + artifact(releaseJar) + artifact(tasks.named('sourcesJar')) + artifact(tasks.named('javadocJar')) + } + pom { + name = 'Modern Metals' + description = 'Metals, alloys, equipment and Crack Hammer processing for Minecraft 1.11.2.' + url = 'https://github.com/MinecraftModDevelopmentMods/ModernMetals' + licenses { + license { + name = 'GNU Lesser General Public License, Version 2.1' + url = 'https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt' + } + } + scm { + connection = 'scm:git:https://github.com/MinecraftModDevelopmentMods/ModernMetals.git' + developerConnection = 'scm:git:ssh://git@github.com/MinecraftModDevelopmentMods/ModernMetals.git' + url = 'https://github.com/MinecraftModDevelopmentMods/ModernMetals' + } + } + } + } + repositories { + maven { + name = 'release' + url = uri(mavenUploadUrl.get()) + credentials { + username = mavenUploadUsername.orNull ?: '' + password = mavenUploadPassword.orNull ?: '' + } + } + } +} + +tasks.register('validateMavenReleaseCredentials') { + group = 'publishing' + doLast { + if (!providers.environmentVariable('MAVEN_UPLOAD_URL').getOrElse('').trim() + || !mavenUploadUsername.getOrElse('').trim() || !mavenUploadPassword.getOrElse('').trim()) { + throw new GradleException('MAVEN_UPLOAD_URL, MAVEN_UPLOAD_USERNAME, and MAVEN_UPLOAD_PASSWORD are required') + } + URI target = uri(providers.environmentVariable('MAVEN_UPLOAD_URL').get()) + if (target.scheme != 'https' || !target.host || target.host == 'invalid.invalid' + || !mavenUploadUsername.get().trim() || !mavenUploadPassword.get().trim()) { + throw new GradleException('Maven release publication requires an HTTPS repository and non-empty credentials') + } + } +} + +tasks.withType(PublishToMavenRepository).configureEach { + dependsOn tasks.named('validateMavenReleaseCredentials') + dependsOn 'verifyMavenCoordinates' + if (preparedReleaseDir.isPresent()) { + dependsOn tasks.named('verifyPreparedReleaseArtifacts') + } else { + dependsOn tasks.named('verifyReleaseArtifacts') + } +} + +tasks.register('verifyMavenCoordinates') { + group = 'verification' + dependsOn tasks.named('generatePomFileForMavenJavaPublication') + doLast { + def pom = new XmlSlurper(false, false).parse(layout.buildDirectory.file('publications/mavenJava/pom-default.xml').get().asFile) + def actual = [pom.groupId.text(), pom.artifactId.text(), pom.version.text()] + def expected = ['zone.moddev.mc.modernmetals', 'ModernMetals', project.version.toString()] + if (actual != expected) throw new GradleException("Wrong Maven coordinate: ${actual.join(':')}") + } +} + +tasks.named('check') { dependsOn tasks.named('verifyMavenCoordinates') } diff --git a/gradle/stage-legacy-dependencies.py b/gradle/stage-legacy-dependencies.py new file mode 100644 index 00000000..48030759 --- /dev/null +++ b/gradle/stage-legacy-dependencies.py @@ -0,0 +1,53 @@ +"""Build a checksum-verified Maven mirror from downloads or an existing Gradle cache.""" + +import argparse +import hashlib +import json +import time +import urllib.error +import urllib.request +from pathlib import Path + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("destination", type=Path) + parser.add_argument("--cache", type=Path, help="Existing Gradle user home; matching files are verified before reuse") + args = parser.parse_args() + specs = json.loads(Path(__file__).with_name("dependencies.json").read_text(encoding="utf-8")) + + for spec in specs: + group, artifact, version, *classifier = spec["coordinate"].split(":") + filename = f"{artifact}-{version}" + ("-" + classifier[0] if classifier else "") + ".jar" + relative = Path(group.replace(".", "/")) / artifact / version / filename + target = args.destination.resolve() / relative + target.parent.mkdir(parents=True, exist_ok=True) + + if not target.exists(): + cached = list((args.cache / "caches/modules-2/files-2.1" / group / artifact / version).glob("*/" + filename)) if args.cache else [] + if cached: + data = cached[0].read_bytes() + else: + url = spec["repository"].rstrip("/") + "/" + relative.as_posix() + request = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) + for attempt in range(3): + try: + with urllib.request.urlopen(request, timeout=30) as response: + data = response.read() + break + except (OSError, urllib.error.URLError): + if attempt == 2: + raise + print(f"Retrying {spec['coordinate']}", flush=True) + time.sleep(2) + if hashlib.sha256(data).hexdigest().upper() != spec["sha256"]: + raise ValueError(f"Checksum mismatch: {spec['coordinate']}") + target.write_bytes(data) + + if hashlib.sha256(target.read_bytes()).hexdigest().upper() != spec["sha256"]: + raise ValueError(f"Checksum mismatch in mirror: {target}") + print(spec["id"], spec["sha256"], flush=True) + + +if __name__ == "__main__": + main() diff --git a/gradle/stage-legacy-dependencies.sh b/gradle/stage-legacy-dependencies.sh new file mode 100644 index 00000000..530f8933 --- /dev/null +++ b/gradle/stage-legacy-dependencies.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "usage: $0 " >&2 + exit 2 +fi + +"${PYTHON:-python3}" "$(dirname "$0")/stage-legacy-dependencies.py" "$1" diff --git a/gradle/verification/runtime.gradle b/gradle/verification/runtime.gradle new file mode 100644 index 00000000..38da7e41 --- /dev/null +++ b/gradle/verification/runtime.gradle @@ -0,0 +1,48 @@ +// Isolated probes are never included in ordinary launches or release jars. +sourceSets { + runtimeProbe { + java.srcDirs = ['tools/validation/legacy111/runtime-probe'] + compileClasspath = sourceSets.main.compileClasspath + sourceSets.main.output + } +} +def probe = tasks.register('runtimeProbeJar', Jar) { + group = 'verification' + archiveFileName = 'modernmetals-probe.jar' + destinationDirectory = layout.buildDirectory.dir('test-mods') + from sourceSets.runtimeProbe.output +} +def packagedProbe = renamer.classes(probe) { + map.from minecraft.dependency.toSrgFile + output = layout.buildDirectory.file('test-mods/modernmetals-probe-packaged.jar') +} +tasks.register('packagedRuntimeProbeJar') { dependsOn packagedProbe } + +// The schema audit also needs this deterministic JUnit export after a cache hit. +tasks.named('test', Test) { + outputs.file(layout.buildDirectory.file('reports/native-provider.json')) +} + +tasks.register('verifyProjectResources', Exec) { + group = 'verification' + dependsOn tasks.named('test') + dependsOn tasks.named('processResources') + doFirst { + commandLine providers.gradleProperty('pythonExecutable').getOrElse('python3'), + 'tools/validation/legacy111/audit_resources.py', '--mirror', + file(providers.gradleProperty('legacyDependencyVerificationRepository').getOrElse('.gradle/legacy-dependencies')).absolutePath + } +} +tasks.register('packagedForgeRuntimeTest', Exec) { + group = 'verification' + dependsOn 'verifyReleaseArtifacts', 'packagedRuntimeProbeJar', 'test' + doFirst { + if (!providers.gradleProperty('packagedForgeRuntimeRoot').isPresent()) + throw new GradleException('Set packagedForgeRuntimeRoot to a disposable Forge server template.') + final def java8 = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(8) }.get() + commandLine providers.gradleProperty('pythonExecutable').getOrElse('python3'), + 'tools/validation/legacy111/run_packaged_suite.py', '--java', java8.executablePath.asFile.absolutePath, + '--template', file(providers.gradleProperty('packagedForgeRuntimeRoot').get()).absolutePath, + '--output', file(providers.gradleProperty('packagedForgeRuntimeOutput').getOrElse('build/packaged-forge-checks')).absolutePath, + '--mirror', file(providers.gradleProperty('legacyDependencyVerificationRepository').getOrElse('.gradle/legacy-dependencies')).absolutePath + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 0d4a9516871afd710a9d84d89e31ba77745607bd..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644 GIT binary patch literal 48462 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 R2np~f1o-$>Q}y+){|A@R9n$~+ literal 54413 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a95009c3..dbe66e1d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,10 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip +distributionSha256Sum=9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index cccdd3d5..249efbb0 100755 --- a/gradlew +++ b/gradlew @@ -1,78 +1,128 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,92 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" fi +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index f9553162..a51ec4f5 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,84 +1,82 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 -:mainEnd -if "%OS%"=="Windows_NT" endlocal +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..c4091f15 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,5 @@ +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' +} + +rootProject.name = 'ModernMetals' diff --git a/src/main/java/com/mcmoddev/alt/ModernMetalsALT.java b/src/main/java/com/mcmoddev/alt/ModernMetalsALT.java deleted file mode 100644 index a2e1e862..00000000 --- a/src/main/java/com/mcmoddev/alt/ModernMetalsALT.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.mcmoddev.alt; -import com.mcmoddev.alt.api.IALTPlugin; - -//@ALTPlugin(modid = "modernmetals") -class ModernMetalsALT implements IALTPlugin { - // Intentionally left blank -} \ No newline at end of file diff --git a/src/main/java/com/mcmoddev/modernmetals/init/Achievements.java b/src/main/java/com/mcmoddev/modernmetals/init/Achievements.java deleted file mode 100644 index e312b1ac..00000000 --- a/src/main/java/com/mcmoddev/modernmetals/init/Achievements.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.mcmoddev.modernmetals.init; - -import com.mcmoddev.lib.data.Names; -import com.mcmoddev.modernmetals.data.AchievementNames; -import com.mcmoddev.modernmetals.data.MaterialNames; -import com.mcmoddev.lib.util.ConfigBase.Options; - -import net.minecraft.stats.Achievement; -import net.minecraftforge.common.AchievementPage; -import net.minecraftforge.fml.common.Loader; - -/** initializer for achievements */ -public class Achievements extends com.mcmoddev.lib.init.Achievements { - - private static boolean initDone = false; - - private Achievements() { - throw new IllegalAccessError("Not a instantiable class"); - } - - /** - * - */ - public static void init() { - if (initDone) { - return; - } - - if (Options.enableAchievements()) { - AchievementPage page = new AchievementPage(Loader.instance().activeModContainer().getModId()); - AchievementPage.registerAchievementPage(page); - - final Achievement metallurgy = getAchievementByName(AchievementNames.METALLURGY); - if (metallurgy != null) { - if (Materials.hasMaterial(MaterialNames.ALUMINUM_BRASS)) { - makeAchievement(AchievementNames.ALUMINUM_BRASS_MAKER, metallurgy, 0, 0, Materials.getMaterialByName(MaterialNames.ALUMINUM_BRASS).getItem(Names.INGOT), page); - } - - if (Materials.hasMaterial(MaterialNames.GALVANIZED_STEEL)) { - makeAchievement(AchievementNames.GALVANIZED_STEEL_MAKER, metallurgy, 0, 1, Materials.getMaterialByName(MaterialNames.GALVANIZED_STEEL).getItem(Names.INGOT), page); - } - - if (Materials.hasMaterial(MaterialNames.NICHROME)) { - makeAchievement(AchievementNames.NICHROME_MAKER, metallurgy, 0, 2, Materials.getMaterialByName(MaterialNames.NICHROME).getItem(Names.INGOT), page); - } - - if (Materials.hasMaterial(MaterialNames.STAINLESS_STEEL)) { - makeAchievement(AchievementNames.STAINLESS_STEEL_MAKER, metallurgy, 0, 3, Materials.getMaterialByName(MaterialNames.STAINLESS_STEEL).getItem(Names.INGOT), page); - } - - if (Materials.hasMaterial(MaterialNames.STAINLESS_STEEL)) { - makeAchievement(AchievementNames.TITANIUM_MAKER, metallurgy, 0, 4, Materials.getMaterialByName(MaterialNames.TITANIUM).getItem(Names.INGOT), page); - } - } - } - - initDone = true; - } -} diff --git a/src/main/java/com/mcmoddev/modernmetals/init/Fluids.java b/src/main/java/com/mcmoddev/modernmetals/init/Fluids.java deleted file mode 100644 index d7485ae0..00000000 --- a/src/main/java/com/mcmoddev/modernmetals/init/Fluids.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.mcmoddev.modernmetals.init; - -import java.util.Arrays; -import java.util.List; - -import com.mcmoddev.modernmetals.data.MaterialNames; -import com.mcmoddev.lib.data.SharedStrings; -import com.mcmoddev.lib.init.Materials; - -/** - * This class initializes all fluids in Modern Metals. - * - * @author Jasmine Iwanek - * - */ -public class Fluids extends com.mcmoddev.lib.init.Fluids { - - private static boolean initDone = false; - - private Fluids() { - throw new IllegalAccessError(SharedStrings.NOT_INSTANTIABLE); - } - - /** - * - */ - public static void init() { - if (initDone) { - return; - } - - if (Materials.hasMaterial(MaterialNames.ALUMINUM)) { - addFluid(MaterialNames.ALUMINUM, 2000, 10000, 330, 10); - addFluidBlock(MaterialNames.ALUMINUM); - } - - if (Materials.hasMaterial(MaterialNames.ALUMINUM_BRASS)) { - addFluid(MaterialNames.ALUMINUM_BRASS, 2000, 10000, 500, 10); - addFluidBlock(MaterialNames.ALUMINUM_BRASS); - } - - if (Materials.hasMaterial(MaterialNames.BERYLLIUM)) { - addFluid(MaterialNames.BERYLLIUM, 2000, 10000, 400, 10); - addFluidBlock(MaterialNames.BERYLLIUM); - } - - if (Materials.hasMaterial(MaterialNames.BORON)) { - addFluid(MaterialNames.BORON, 2000, 10000, 400, 10); - addFluidBlock(MaterialNames.BORON); - } - - if (Materials.hasMaterial(MaterialNames.CADMIUM)) { - addFluid(MaterialNames.CADMIUM, 2000, 10000, 300, 10); - addFluidBlock(MaterialNames.CADMIUM); - } - - List standardMaterialsMap = Arrays.asList(MaterialNames.CHROMIUM, - MaterialNames.GALVANIZED_STEEL, - MaterialNames.IRIDIUM, - MaterialNames.MAGNESIUM, - MaterialNames.MANGANESE, - MaterialNames.NICHROME, - MaterialNames.OSMIUM, - MaterialNames.PLUTONIUM, - MaterialNames.RUTILE, - MaterialNames.STAINLESS_STEEL, - MaterialNames.TANTALUM, - MaterialNames.TITANIUM, - MaterialNames.TUNGSTEN, - MaterialNames.URANIUM, - MaterialNames.ZIRCONIUM - ); - - standardMaterialsMap.stream() - .filter(Materials::hasMaterial) - .forEach(name -> { - addFluid(name, 2000, 10000, 769, 10); - addFluidBlock(name); - }); - - if (Materials.hasMaterial(MaterialNames.THORIUM)) { - addFluid(MaterialNames.THORIUM, 2000, 10000, 514, 10); - addFluidBlock(MaterialNames.THORIUM); - } - - initDone = true; - } -} diff --git a/src/main/java/com/mcmoddev/modernmetals/init/Materials.java b/src/main/java/com/mcmoddev/modernmetals/init/Materials.java deleted file mode 100644 index f6d2210e..00000000 --- a/src/main/java/com/mcmoddev/modernmetals/init/Materials.java +++ /dev/null @@ -1,247 +0,0 @@ -package com.mcmoddev.modernmetals.init; - - -import java.util.Arrays; -import java.util.List; - -import javax.annotation.Nonnull; - -import com.mcmoddev.modernmetals.data.MaterialNames; -import com.mcmoddev.lib.data.SharedStrings; -import com.mcmoddev.lib.material.MMDMaterial.MaterialType; -import com.mcmoddev.lib.util.ConfigBase.Options; - -/** - * This class initializes all of the materials in Modern Metals. - * - * @author Jasmine Iwanek - * - */ -public class Materials extends com.mcmoddev.lib.init.Materials { - - private static boolean initDone = false; - - private Materials() { - throw new IllegalAccessError(SharedStrings.NOT_INSTANTIABLE); - } - - /** - * - */ - public static void init() { - if (initDone) { - return; - } - - List modMaterials = Arrays.asList( - MaterialNames.ALUMINUM, - MaterialNames.BERYLLIUM, - MaterialNames.BORON, - MaterialNames.CHROMIUM, - MaterialNames.MAGNESIUM, - MaterialNames.MANGANESE, - MaterialNames.OSMIUM, - MaterialNames.RUTILE, - MaterialNames.TANTALUM, - MaterialNames.THORIUM, - MaterialNames.TUNGSTEN, - MaterialNames.ZIRCONIUM - ); - - List rareMaterials = Arrays.asList( - MaterialNames.CADMIUM, - MaterialNames.IRIDIUM, - MaterialNames.PLUTONIUM, - MaterialNames.URANIUM - ); - - List alloyMaterials = Arrays.asList( - MaterialNames.ALUMINUM_BRASS, - MaterialNames.GALVANIZED_STEEL, - MaterialNames.NICHROME, - MaterialNames.STAINLESS_STEEL - ); - - modMaterials.stream() - .filter(Options::isMaterialEnabled) - .forEach(name -> createMaterial(name, MaterialType.METAL, getHardness(name), getStrength(name), getMagic(name), getColor(name))); - - alloyMaterials.stream() - .filter(Options::isMaterialEnabled) - .forEach(name -> createAlloyMaterial(name, MaterialType.METAL, getHardness(name), getStrength(name), getMagic(name), getColor(name))); - - rareMaterials.stream() - .filter(Options::isMaterialEnabled) - .forEach(name -> createRareMaterial(name, MaterialType.METAL, getHardness(name), getStrength(name), getMagic(name), getColor(name))); - - if (Options.isMaterialEnabled(MaterialNames.TITANIUM)) { - final String name = MaterialNames.TITANIUM; - createSpecialMaterial(name, MaterialType.METAL, getHardness(name), getStrength(name), getMagic(name), getColor(name)); - } - - initDone = true; - } - - private static int getColor(@Nonnull final String name) { - switch (name) { - case MaterialNames.ALUMINUM: - return 0xFFC5C8C1; - case MaterialNames.ALUMINUM_BRASS: - return 0xFFEBAA56; - case MaterialNames.CADMIUM: - return 0xFFC9D4DA; - case MaterialNames.CHROMIUM: - return 0xFFCDCDCF; - case MaterialNames.GALVANIZED_STEEL: - return 0xFF9BA6A2; - case MaterialNames.IRIDIUM: - return 0xFFF8EDCC; - case MaterialNames.MAGNESIUM: - return 0xFF7F7F77; - case MaterialNames.MANGANESE: - return 0xFFF5CFDA; - case MaterialNames.NICHROME: - return 0xFFDEA054; - case MaterialNames.OSMIUM: - return 0xFF7C8E99; - case MaterialNames.PLUTONIUM: - return 0xFFB333EA; - case MaterialNames.RUTILE: - return 0xFFBF928B; - case MaterialNames.STAINLESS_STEEL: - return 0xFFC5BFC1; - case MaterialNames.TANTALUM: - return 0xFFC4BEC2; - case MaterialNames.TITANIUM: - return 0xFF73787E; - case MaterialNames.TUNGSTEN: - return 0xFF969696; - case MaterialNames.URANIUM: - return 0xFFA7B345; - case MaterialNames.ZIRCONIUM: - return 0xFF929793; - case MaterialNames.BERYLLIUM: - return 0xFFD5D3D5; - case MaterialNames.BORON: - return 0xFF888888; - case MaterialNames.THORIUM: - return 0xFF4F5A5A; - default: - return 0x00000000; - } - } - - private static double getMagic(@Nonnull final String name) { - switch (name) { - case MaterialNames.ALUMINUM: - case MaterialNames.ALUMINUM_BRASS: - case MaterialNames.CADMIUM: - case MaterialNames.CHROMIUM: - case MaterialNames.GALVANIZED_STEEL: - case MaterialNames.IRIDIUM: - case MaterialNames.MAGNESIUM: - case MaterialNames.MANGANESE: - case MaterialNames.NICHROME: - case MaterialNames.OSMIUM: - case MaterialNames.PLUTONIUM: - case MaterialNames.RUTILE: - case MaterialNames.STAINLESS_STEEL: - case MaterialNames.TANTALUM: - case MaterialNames.TITANIUM: - case MaterialNames.TUNGSTEN: - case MaterialNames.URANIUM: - case MaterialNames.ZIRCONIUM: - return 4.5d; - case MaterialNames.BERYLLIUM: - case MaterialNames.BORON: - case MaterialNames.THORIUM: - return 1.0d; - default: - return 1.0d; - } - } - - private static double getStrength(@Nonnull final String name) { - switch (name) { - case MaterialNames.TANTALUM: - return 17.5d; - case MaterialNames.TITANIUM: - case MaterialNames.URANIUM: - return 16.25d; - case MaterialNames.STAINLESS_STEEL: - return 16d; - case MaterialNames.NICHROME: - return 15.5d; - case MaterialNames.GALVANIZED_STEEL: - return 15.25d; - case MaterialNames.TUNGSTEN: - return 12.5d; - case MaterialNames.BORON: - return 8d; - case MaterialNames.ALUMINUM_BRASS: - case MaterialNames.PLUTONIUM: - return 7.5d; - case MaterialNames.ALUMINUM: - return 3.75d; - case MaterialNames.MAGNESIUM: - case MaterialNames.ZIRCONIUM: - return 3.5d; - case MaterialNames.CHROMIUM: - case MaterialNames.IRIDIUM: - return 3d; - case MaterialNames.MANGANESE: - case MaterialNames.OSMIUM: - return 2.75d; - case MaterialNames.BERYLLIUM: - return 2.5d; - case MaterialNames.CADMIUM: - case MaterialNames.THORIUM: - return 1d; - case MaterialNames.RUTILE: - return 0.25d; - default: - return 1.0d; - } - } - - private static double getHardness(@Nonnull final String name) { - switch (name) { - case MaterialNames.BORON: - return 9.5d; - case MaterialNames.CHROMIUM: - return 9d; - case MaterialNames.TUNGSTEN: - return 7.5d; - case MaterialNames.OSMIUM: - return 7d; - case MaterialNames.IRIDIUM: - case MaterialNames.TANTALUM: - case MaterialNames.TITANIUM: - return 6.5d; - case MaterialNames.NICHROME: - case MaterialNames.RUTILE: - case MaterialNames.STAINLESS_STEEL: - case MaterialNames.URANIUM: - return 6d; - case MaterialNames.BERYLLIUM: - case MaterialNames.GALVANIZED_STEEL: - return 5.5d; - case MaterialNames.MANGANESE: - case MaterialNames.ZIRCONIUM: - return 5d; - case MaterialNames.PLUTONIUM: - return 4d; - case MaterialNames.THORIUM: - return 3d; - case MaterialNames.ALUMINUM: - case MaterialNames.MAGNESIUM: - return 2.5d; - case MaterialNames.CADMIUM: - return 2d; - case MaterialNames.ALUMINUM_BRASS: - return 1.0d; - default: - return 1.0d; - } - } -} diff --git a/src/main/java/com/mcmoddev/modernmetals/init/Recipes.java b/src/main/java/com/mcmoddev/modernmetals/init/Recipes.java deleted file mode 100644 index 18f14e21..00000000 --- a/src/main/java/com/mcmoddev/modernmetals/init/Recipes.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.mcmoddev.modernmetals.init; - -import com.mcmoddev.modernmetals.data.MaterialNames; -import com.mcmoddev.lib.init.Materials; -import com.mcmoddev.lib.data.SharedStrings; -import com.mcmoddev.lib.material.MMDMaterial; - -/** - * - * @author Jasmine Iwanek - * - */ -public class Recipes extends com.mcmoddev.lib.init.Recipes { - - private static boolean initDone = false; - - private Recipes() { - throw new IllegalAccessError(SharedStrings.NOT_INSTANTIABLE); - } - - /** - * - */ - public static void init() { - if (initDone) { - return; - } - - Materials.init(); - Blocks.init(); - Items.init(); - - initModSpecificRecipes(); - - initDone = true; - } - - private static void initModSpecificRecipes() { - if (Materials.hasMaterial(MaterialNames.ALUMINUM)) { - final MMDMaterial aluminum = Materials.getMaterialByName(MaterialNames.ALUMINUM); - addAdditionalOredicts(aluminum, "Aluminium"); - addAdditionalOredicts(aluminum, "Bauxite"); - } - - if (Materials.hasMaterial(MaterialNames.ALUMINUM_BRASS)) { - final MMDMaterial aluminumBrass = Materials.getMaterialByName(MaterialNames.ALUMINUM_BRASS); - addSimpleAlloyRecipe(aluminumBrass, 2, MaterialNames.ALUMINUM, com.mcmoddev.basemetals.data.MaterialNames.BRASS); - addAdditionalOredicts(aluminumBrass, "AluminumBrass"); - addAdditionalOredicts(aluminumBrass, "Aluminiumbrass"); - addAdditionalOredicts(aluminumBrass, "AluminiumBrass"); - addAdditionalOredicts(aluminumBrass, "Alubrass"); - addAdditionalOredicts(aluminumBrass, "AluBrass"); - } - - if (Materials.hasMaterial(MaterialNames.CHROMIUM)) { - addAdditionalOredicts(MaterialNames.CHROMIUM, "Chrome"); - } - - if (Materials.hasMaterial(MaterialNames.GALVANIZED_STEEL)) { - final MMDMaterial galvanizedSteel = Materials.getMaterialByName(MaterialNames.GALVANIZED_STEEL); - addSimpleAlloyRecipe(galvanizedSteel, 2, com.mcmoddev.basemetals.data.MaterialNames.STEEL, com.mcmoddev.basemetals.data.MaterialNames.ZINC); - addAdditionalOredicts(galvanizedSteel, "GalvinizedSteel"); - } - - if (Materials.hasMaterial(MaterialNames.NICHROME)) { - addSimpleAlloyRecipe(MaterialNames.NICHROME, 2, com.mcmoddev.basemetals.data.MaterialNames.NICKEL, MaterialNames.CHROMIUM); - } - - if (Materials.hasMaterial(MaterialNames.STAINLESS_STEEL)) { - final MMDMaterial stainlessSteel = Materials.getMaterialByName(MaterialNames.STAINLESS_STEEL); - addSimpleAlloyRecipe(stainlessSteel, 2, com.mcmoddev.basemetals.data.MaterialNames.STEEL, MaterialNames.CHROMIUM); - addAdditionalOredicts(stainlessSteel, "StainlessSteel"); - } - - if (Materials.hasMaterial(MaterialNames.TITANIUM)) { - addSimpleAlloyRecipe(MaterialNames.TITANIUM, 2, MaterialNames.RUTILE, MaterialNames.MAGNESIUM); - } - - if (Materials.hasMaterial(MaterialNames.TUNGSTEN)) { - addAdditionalOredicts(MaterialNames.TUNGSTEN, "Wolfram"); - } - } -} diff --git a/src/main/java/com/mcmoddev/modernmetals/init/VillagerTrades.java b/src/main/java/com/mcmoddev/modernmetals/init/VillagerTrades.java deleted file mode 100644 index e8187e45..00000000 --- a/src/main/java/com/mcmoddev/modernmetals/init/VillagerTrades.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.mcmoddev.modernmetals.init; - -import com.mcmoddev.lib.data.SharedStrings; - -/** - * - * @author Jasmine Iwanek - * - */ -public class VillagerTrades extends com.mcmoddev.lib.init.VillagerTrades { - - private static boolean initDone = false; - - private VillagerTrades() { - throw new IllegalAccessError(SharedStrings.NOT_INSTANTIABLE); - } - - /** - * - */ - public static void init() { - if (initDone) { - return; - } - - Materials.init(); - Items.init(); - - registerCommonTrades(); - - initDone = true; - } -} diff --git a/src/main/java/com/mcmoddev/modernmetals/integration/plugins/EnderIO.java b/src/main/java/com/mcmoddev/modernmetals/integration/plugins/EnderIO.java deleted file mode 100644 index 563dc0fc..00000000 --- a/src/main/java/com/mcmoddev/modernmetals/integration/plugins/EnderIO.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.mcmoddev.modernmetals.integration.plugins; - -import com.mcmoddev.modernmetals.ModernMetals; -import com.mcmoddev.modernmetals.data.MaterialNames; -import com.mcmoddev.lib.util.ConfigBase.Options; -import com.mcmoddev.lib.init.Materials; -import com.mcmoddev.lib.integration.MMDPlugin; -import com.mcmoddev.lib.integration.IIntegration; - -/** - * - * @author Jasmine Iwanek - * - */ -@MMDPlugin(addonId = ModernMetals.MODID, pluginId = EnderIO.PLUGIN_MODID) -public class EnderIO extends com.mcmoddev.lib.integration.plugins.EnderIOBase implements IIntegration { - - private static boolean initDone = false; - - /** - * - */ - @Override - public void init() { - if (initDone || !Options.isModEnabled(EnderIO.PLUGIN_MODID)) { - return; - } - - final String[] baseNames = new String[] { - MaterialNames.ALUMINUM, - MaterialNames.ALUMINUM_BRASS, - MaterialNames.CADMIUM, - MaterialNames.CHROMIUM, - MaterialNames.GALVANIZED_STEEL, - MaterialNames.IRIDIUM, - MaterialNames.MAGNESIUM, - MaterialNames.MANGANESE, - MaterialNames.NICHROME, - MaterialNames.OSMIUM, - MaterialNames.PLUTONIUM, - MaterialNames.RUTILE, - MaterialNames.STAINLESS_STEEL, - MaterialNames.TANTALUM, - MaterialNames.TITANIUM, - MaterialNames.TUNGSTEN, - MaterialNames.URANIUM, - MaterialNames.ZIRCONIUM - }; - - for (final String materialName : baseNames) { - if (Materials.hasMaterial(materialName)) { - addSagMillRecipe(materialName, 3600); - } - } - - initDone = true; - } -} diff --git a/src/main/java/com/mcmoddev/modernmetals/integration/plugins/IC2.java b/src/main/java/com/mcmoddev/modernmetals/integration/plugins/IC2.java deleted file mode 100644 index fca31d3c..00000000 --- a/src/main/java/com/mcmoddev/modernmetals/integration/plugins/IC2.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.mcmoddev.modernmetals.integration.plugins; - -import com.mcmoddev.modernmetals.ModernMetals; -import com.mcmoddev.lib.integration.MMDPlugin; -import com.mcmoddev.lib.util.ConfigBase.Options; -import com.mcmoddev.lib.init.Materials; -import com.mcmoddev.lib.integration.IIntegration; - -@MMDPlugin(addonId = ModernMetals.MODID, pluginId = IC2.PLUGIN_MODID) -public class IC2 extends com.mcmoddev.lib.integration.plugins.IC2Base implements IIntegration { - - private static boolean initDone = false; - - @Override - public void init() { - if (initDone || !Options.isModEnabled(IC2.PLUGIN_MODID)) { - return; - } - - initDone = true; - } -} diff --git a/src/main/java/com/mcmoddev/modernmetals/integration/plugins/Mekanism.java b/src/main/java/com/mcmoddev/modernmetals/integration/plugins/Mekanism.java deleted file mode 100644 index bf0ceabd..00000000 --- a/src/main/java/com/mcmoddev/modernmetals/integration/plugins/Mekanism.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.mcmoddev.modernmetals.integration.plugins; - -import com.mcmoddev.modernmetals.ModernMetals; -import com.mcmoddev.modernmetals.data.MaterialNames; -import com.mcmoddev.lib.util.ConfigBase.Options; -import com.mcmoddev.lib.init.Materials; -import com.mcmoddev.lib.integration.MMDPlugin; -import com.mcmoddev.lib.integration.IIntegration; - -@MMDPlugin(addonId = ModernMetals.MODID, pluginId = Mekanism.PLUGIN_MODID) -public class Mekanism extends com.mcmoddev.lib.integration.plugins.MekanismBase implements IIntegration { - - private static boolean initDone = false; - - @Override - public void init() { - if (initDone || !Options.isModEnabled(Mekanism.PLUGIN_MODID)) { - return; - } - - final String[] baseNames = new String[] { - MaterialNames.ALUMINUM, - MaterialNames.BERYLLIUM, - MaterialNames.BORON, - MaterialNames.CADMIUM, - MaterialNames.CHROMIUM, - MaterialNames.IRIDIUM, - MaterialNames.MAGNESIUM, - MaterialNames.MANGANESE, - MaterialNames.PLUTONIUM, - MaterialNames.RUTILE, - MaterialNames.TANTALUM, - MaterialNames.THORIUM, - MaterialNames.TITANIUM, - MaterialNames.TUNGSTEN, - MaterialNames.URANIUM, - MaterialNames.ZIRCONIUM - }; - - for (final String materialName : baseNames) { - if (Materials.hasMaterial(materialName)) { - addOreMultiplicationRecipes(materialName); - } - } - - initDone = true; - } -} diff --git a/src/main/java/com/mcmoddev/modernmetals/integration/plugins/ThermalExpansion.java b/src/main/java/com/mcmoddev/modernmetals/integration/plugins/ThermalExpansion.java deleted file mode 100755 index 41821497..00000000 --- a/src/main/java/com/mcmoddev/modernmetals/integration/plugins/ThermalExpansion.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.mcmoddev.modernmetals.integration.plugins; - -import java.util.HashMap; -import java.util.Map; - -import com.mcmoddev.modernmetals.ModernMetals; -import com.mcmoddev.modernmetals.data.MaterialNames; -import com.mcmoddev.lib.init.Materials; -import com.mcmoddev.lib.util.ConfigBase.Options; -import com.mcmoddev.lib.data.Names; -import com.mcmoddev.lib.integration.IIntegration; -import com.mcmoddev.lib.integration.MMDPlugin; -import com.mcmoddev.lib.material.MMDMaterial; -import cofh.api.util.ThermalExpansionHelper; -import net.minecraft.item.ItemStack; - -@MMDPlugin(addonId = ModernMetals.MODID, pluginId = ThermalExpansion.PLUGIN_MODID) -public class ThermalExpansion extends com.mcmoddev.lib.integration.plugins.ThermalExpansionBase implements IIntegration { - - private static boolean initDone = false; - - public void init() { - if (initDone || !Options.isModEnabled(ThermalExpansion.PLUGIN_MODID)) { - return; - } - - final String[] baseNames = new String[] { - MaterialNames.ALUMINUM_BRASS, - MaterialNames.BERYLLIUM, - MaterialNames.BORON, - MaterialNames.CADMIUM, - MaterialNames.CHROMIUM, - MaterialNames.GALVANIZED_STEEL, - MaterialNames.MAGNESIUM, - MaterialNames.MANGANESE, - MaterialNames.NICHROME, - MaterialNames.OSMIUM, - MaterialNames.PLUTONIUM, - MaterialNames.RUTILE, - MaterialNames.STAINLESS_STEEL, - MaterialNames.TANTALUM, - MaterialNames.THORIUM, - MaterialNames.TITANIUM, - MaterialNames.TUNGSTEN, - MaterialNames.URANIUM, - MaterialNames.ZIRCONIUM - }; - - for (final String materialName : baseNames) { - boolean enabled = Materials.hasMaterial(materialName); - if (Materials.hasMaterial(materialName)) { - addFurnace(materialName); - addCrucible(materialName); - addPlatePress(materialName); - addPressStorage(materialName); - } - } - - if (Materials.hasMaterial(MaterialNames.PLUTONIUM)) { - ThermalExpansionHelper.addMagmaticFuel(Materials.getMaterialByName(MaterialNames.PLUTONIUM).getFluid().getName(), 1000000); - } - - if (Materials.hasMaterial(MaterialNames.URANIUM)) { - ThermalExpansionHelper.addMagmaticFuel(Materials.getMaterialByName(MaterialNames.URANIUM).getFluid().getName(), 750000); - } - - if ((Materials.hasMaterial(com.mcmoddev.basemetals.data.MaterialNames.STEEL))) { - final MMDMaterial steel = Materials.getMaterialByName(com.mcmoddev.basemetals.data.MaterialNames.STEEL); - if ((Materials.hasMaterial(MaterialNames.CHROMIUM) && (Materials.hasMaterial(MaterialNames.STAINLESS_STEEL)))) { - ThermalExpansionHelper.addSmelterRecipe(4000, new ItemStack(steel.getItem(Names.INGOT), 1), new ItemStack(Materials.getMaterialByName(MaterialNames.CHROMIUM).getItem(Names.INGOT), 1), new ItemStack(Materials.getMaterialByName(MaterialNames.STAINLESS_STEEL).getItem(Names.INGOT), 2)); - } - - if ((Materials.hasMaterial(com.mcmoddev.basemetals.data.MaterialNames.ZINC) && (Materials.hasMaterial(MaterialNames.GALVANIZED_STEEL)))) { - ThermalExpansionHelper.addSmelterRecipe(4000, new ItemStack(steel.getItem(Names.INGOT), 1), new ItemStack(Materials.getMaterialByName(com.mcmoddev.basemetals.data.MaterialNames.ZINC).getItem(Names.INGOT), 1), new ItemStack(Materials.getMaterialByName(MaterialNames.GALVANIZED_STEEL).getItem(Names.INGOT), 2)); - } - } - - if ((Materials.hasMaterial(MaterialNames.RUTILE) && Materials.hasMaterial(MaterialNames.MAGNESIUM) && (Materials.hasMaterial(MaterialNames.TITANIUM)))) { - ThermalExpansionHelper.addSmelterRecipe(4000, new ItemStack(Materials.getMaterialByName(MaterialNames.RUTILE).getItem(Names.INGOT), 1), new ItemStack(Materials.getMaterialByName(MaterialNames.MAGNESIUM).getItem(Names.INGOT), 1), new ItemStack( Materials.getMaterialByName(MaterialNames.TITANIUM).getItem(Names.INGOT), 2)); - } - - initDone = true; - } -} diff --git a/src/main/java/com/mcmoddev/modernmetals/integration/plugins/TinkersConstruct.java b/src/main/java/com/mcmoddev/modernmetals/integration/plugins/TinkersConstruct.java deleted file mode 100644 index 5a820a62..00000000 --- a/src/main/java/com/mcmoddev/modernmetals/integration/plugins/TinkersConstruct.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.mcmoddev.modernmetals.integration.plugins; - -import com.mcmoddev.modernmetals.ModernMetals; -import com.mcmoddev.modernmetals.data.MaterialNames; -import com.mcmoddev.modernmetals.data.TraitNames; - - -import com.mcmoddev.lib.util.ConfigBase.Options; -import com.mcmoddev.lib.init.Materials; -import com.mcmoddev.lib.integration.MMDPlugin; -import com.mcmoddev.lib.integration.plugins.tinkers.TraitLocations; -import com.mcmoddev.lib.integration.IIntegration; - -/** - * - * @author Jasmine Iwanek - * - */ -@MMDPlugin(addonId = ModernMetals.MODID, pluginId = TinkersConstruct.PLUGIN_MODID) -public class TinkersConstruct extends com.mcmoddev.lib.integration.plugins.TinkersConstructBase implements IIntegration { - - private static boolean initDone = false; - - @Override - public void init() { - if (initDone || !Options.isModEnabled(TinkersConstruct.PLUGIN_MODID)) { - return; - } - - if (Materials.hasMaterial(MaterialNames.ALUMINUM)) { - registry.getMaterial(MaterialNames.ALUMINUM, Materials.getMaterialByName(MaterialNames.ALUMINUM)).addTrait(TraitNames.LIGHTWEIGHT).setCastable(true).settle(); - } - - if (Materials.hasMaterial(MaterialNames.ALUMINUM_BRASS)) { - registry.getMaterial(MaterialNames.ALUMINUM_BRASS, Materials.getMaterialByName(MaterialNames.ALUMINUM_BRASS)).setCastable(true).settle(); - } - - if (Materials.hasMaterial(MaterialNames.CADMIUM)) { - registry.getMaterial(MaterialNames.CADMIUM, Materials.getMaterialByName(MaterialNames.CADMIUM)).setCastable(true).settle(); - } - - if (Materials.hasMaterial(MaterialNames.CHROMIUM)) { - registry.getMaterial(MaterialNames.CHROMIUM, Materials.getMaterialByName(MaterialNames.CHROMIUM)).setCastable(true).addTrait(TraitNames.MAGNETIC).addTrait(TraitNames.MAGNETIC2,TraitLocations.HEAD).settle(); - } - - if (Materials.hasMaterial(MaterialNames.GALVANIZED_STEEL)) { - registry.getMaterial(MaterialNames.GALVANIZED_STEEL, Materials.getMaterialByName(MaterialNames.GALVANIZED_STEEL)).addTrait(TraitNames.SHARP, TraitLocations.HEAD).addTrait(TraitNames.STIFF).setCastable(true).settle(); - registry.registerAlloy(MaterialNames.GALVANIZED_STEEL, Materials.getMaterialByName(MaterialNames.GALVANIZED_STEEL).getFluid(), 2, com.mcmoddev.basemetals.data.MaterialNames.STEEL, 1, com.mcmoddev.basemetals.data.MaterialNames.ZINC, 1); - } - - if (Materials.hasMaterial(MaterialNames.IRIDIUM)) { - registry.getMaterial(MaterialNames.IRIDIUM, Materials.getMaterialByName(MaterialNames.IRIDIUM)).setCastable(true).addTrait(TraitNames.HEAVY).addTrait(TraitNames.BRITTLE).settle(); - } - - if (Materials.hasMaterial(MaterialNames.MAGNESIUM)) { - registry.getMaterial(MaterialNames.MAGNESIUM, Materials.getMaterialByName(MaterialNames.MAGNESIUM)).setCastable(true).addTrait(TraitNames.REACTIVE).settle(); - } - - if (Materials.hasMaterial(MaterialNames.MANGANESE)) { - registry.getMaterial(MaterialNames.MANGANESE, Materials.getMaterialByName(MaterialNames.MANGANESE)).addTrait(TraitNames.BRITTLE).setCastable(true).settle(); - } - - if (Materials.hasMaterial(MaterialNames.NICHROME)) { - registry.getMaterial(MaterialNames.NICHROME, Materials.getMaterialByName(MaterialNames.NICHROME)).setCastable(true).settle(); - registry.registerAlloy(MaterialNames.NICHROME, Materials.getMaterialByName(MaterialNames.NICHROME).getFluid(), 2, com.mcmoddev.basemetals.data.MaterialNames.NICKEL, 1, MaterialNames.CHROMIUM, 1); - } - - if (Materials.hasMaterial(MaterialNames.OSMIUM)) { - registry.getMaterial(MaterialNames.OSMIUM, Materials.getMaterialByName(MaterialNames.OSMIUM)).setCastable(true).addTrait(TraitNames.HEAVY).addTrait(TraitNames.BRITTLE).settle(); - } - - if (Materials.hasMaterial(MaterialNames.PLUTONIUM)) { - registry.getMaterial(MaterialNames.PLUTONIUM, Materials.getMaterialByName(MaterialNames.PLUTONIUM)).addTrait(TraitNames.RADIOACTIVE).addTrait(TraitNames.TOXIC, TraitLocations.HEAD).addTrait(TraitNames.TOXIC, TraitLocations.PROJECTILE).setCastable(true).settle(); - } - - // TODO: request is for custom modifier item to grant +50% durability - // This is not yet in the API - might require extra classes in MMDLib - if (Materials.hasMaterial(MaterialNames.RUTILE)) { - registry.getMaterial(MaterialNames.RUTILE, Materials.getMaterialByName(MaterialNames.RUTILE)).setCastable(true).settle(); - } - - if (Materials.hasMaterial(MaterialNames.STAINLESS_STEEL)) { - registry.getMaterial(MaterialNames.STAINLESS_STEEL, Materials.getMaterialByName(MaterialNames.STAINLESS_STEEL)).setCastable(true).addTrait(TraitNames.SHARP, TraitLocations.HEAD).addTrait(TraitNames.STIFF).settle(); - registry.registerAlloy(MaterialNames.STAINLESS_STEEL, Materials.getMaterialByName(MaterialNames.STAINLESS_STEEL).getFluid(), 2, com.mcmoddev.basemetals.data.MaterialNames.STEEL, 1, MaterialNames.CHROMIUM, 1); - } - - if (Materials.hasMaterial(MaterialNames.TANTALUM)) { - registry.getMaterial(MaterialNames.TANTALUM, Materials.getMaterialByName(MaterialNames.TANTALUM)).setCastable(true).settle(); - } - - if (Materials.hasMaterial(MaterialNames.TITANIUM)) { - registry.getMaterial(MaterialNames.TITANIUM, Materials.getMaterialByName(MaterialNames.TITANIUM)).setCastable(true).settle(); - registry.registerAlloy(MaterialNames.TITANIUM, Materials.getMaterialByName(MaterialNames.TITANIUM).getFluid(), 2, MaterialNames.RUTILE, 1, MaterialNames.MAGNESIUM, 1 ); - } - - if (Materials.hasMaterial(MaterialNames.TUNGSTEN)) { - registry.getMaterial(MaterialNames.TUNGSTEN, Materials.getMaterialByName(MaterialNames.TUNGSTEN)).setCastable(true).settle(); - } - - if (Materials.hasMaterial(MaterialNames.URANIUM)) { - registry.getMaterial(MaterialNames.URANIUM, Materials.getMaterialByName(MaterialNames.URANIUM)).setCastable(true).addTrait(TraitNames.POISONOUS, TraitLocations.HEAD).addTrait(TraitNames.POISONOUS, TraitLocations.PROJECTILE).addTrait(TraitNames.RADIOACTIVE).settle(); - } - - if (Materials.hasMaterial(MaterialNames.ZIRCONIUM)) { - registry.getMaterial(MaterialNames.ZIRCONIUM, Materials.getMaterialByName(MaterialNames.ZIRCONIUM)).setCastable(true).settle(); - } - - registry.registerAll(); - initDone = true; - } -} diff --git a/src/main/java/com/mcmoddev/modernmetals/integration/plugins/VeinMiner.java b/src/main/java/com/mcmoddev/modernmetals/integration/plugins/VeinMiner.java deleted file mode 100644 index 3155ed01..00000000 --- a/src/main/java/com/mcmoddev/modernmetals/integration/plugins/VeinMiner.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.mcmoddev.modernmetals.integration.plugins; - -import com.mcmoddev.modernmetals.ModernMetals; -import com.mcmoddev.modernmetals.data.MaterialNames; -import com.mcmoddev.lib.util.ConfigBase.Options; -import com.mcmoddev.lib.integration.MMDPlugin; -import com.mcmoddev.lib.integration.IIntegration; - -/** - * VeinMiner Integration Plugin - * - * @author Jasmine Iwanek - * - */ -@MMDPlugin(addonId = ModernMetals.MODID, pluginId = VeinMiner.PLUGIN_MODID) -public class VeinMiner extends com.mcmoddev.lib.integration.plugins.VeinMinerBase implements IIntegration { - - private static boolean initDone = false; - - @Override - public void init() { - if (initDone || !Options.isModEnabled(VeinMiner.PLUGIN_MODID)) { - return; - } - - final String[] baseNames = new String[] { - MaterialNames.ALUMINUM, - MaterialNames.ALUMINUM_BRASS, - MaterialNames.CADMIUM, - MaterialNames.CHROMIUM, - MaterialNames.GALVANIZED_STEEL, - MaterialNames.IRIDIUM, - MaterialNames.MAGNESIUM, - MaterialNames.MANGANESE, - MaterialNames.NICHROME, - MaterialNames.OSMIUM, - MaterialNames.PLUTONIUM, - MaterialNames.RUTILE, - MaterialNames.STAINLESS_STEEL, - MaterialNames.TANTALUM, - MaterialNames.TITANIUM, - MaterialNames.TUNGSTEN, - MaterialNames.URANIUM, - MaterialNames.ZIRCONIUM - }; - - for (final String materialName : baseNames) { - if (Options.isMaterialEnabled(materialName)) { - addToolsForMaterial(materialName); - } - } - - initDone = true; - } -} diff --git a/src/main/java/com/mcmoddev/modernmetals/proxy/ClientProxy.java b/src/main/java/com/mcmoddev/modernmetals/proxy/ClientProxy.java deleted file mode 100644 index 4e26b91e..00000000 --- a/src/main/java/com/mcmoddev/modernmetals/proxy/ClientProxy.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.mcmoddev.modernmetals.proxy; - -import com.mcmoddev.lib.client.registrations.RegistrationHelper; -import com.mcmoddev.modernmetals.init.Blocks; -import com.mcmoddev.modernmetals.init.Fluids; -import com.mcmoddev.modernmetals.init.Items; - -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; - -/** - * Modern Metals Client Proxy - * - * @author Jasmine Iwanek - * - */ -public class ClientProxy extends CommonProxy { - - @Override - public void preInit(FMLPreInitializationEvent event) { - super.preInit(event); - for (final String name : Items.getItemRegistry().keySet()) { - RegistrationHelper.registerItemRender(name); - } - - for (final String name : Blocks.getBlockRegistry().keySet()) { - RegistrationHelper.registerBlockRender(name); - } - - for (final String name : Fluids.getFluidBlockRegistry().keySet()) { - RegistrationHelper.registerFluidRender(name); - } - } -} diff --git a/src/main/java/com/mcmoddev/modernmetals/proxy/CommonProxy.java b/src/main/java/com/mcmoddev/modernmetals/proxy/CommonProxy.java deleted file mode 100644 index a0c1cf2c..00000000 --- a/src/main/java/com/mcmoddev/modernmetals/proxy/CommonProxy.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.mcmoddev.modernmetals.proxy; - -import java.util.HashSet; - -import com.mcmoddev.modernmetals.ModernMetals; -import com.mcmoddev.modernmetals.data.MaterialNames; -import com.mcmoddev.modernmetals.init.*; -import com.mcmoddev.modernmetals.util.Config; -import com.mcmoddev.modernmetals.util.EventHandler; -import com.mcmoddev.lib.integration.IntegrationManager; -import com.mcmoddev.lib.oregen.FallbackGenerator; -import com.mcmoddev.lib.util.ConfigBase.Options; - -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.fml.common.Loader; -import net.minecraftforge.fml.common.MissingModsException; -import net.minecraftforge.fml.common.event.FMLInitializationEvent; -import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; -import net.minecraftforge.fml.common.registry.GameRegistry; -import net.minecraftforge.fml.common.versioning.ArtifactVersion; -import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion; - -/** - * Modern Metals Common Proxy - * - * @author Jasmine Iwanek - * - */ -public class CommonProxy { - - public void preInit(FMLPreInitializationEvent event) { - - Config.init(); - - if ((Options.requireMMDOreSpawn()) && (!Loader.isModLoaded("orespawn"))) { - if(Options.fallbackOrespawn()) { - GameRegistry.registerWorldGenerator(new FallbackGenerator(), 0); - } else { - final HashSet orespawnMod = new HashSet<>(); - orespawnMod.add(new DefaultArtifactVersion("3.2.0")); - throw new MissingModsException(orespawnMod, "orespawn", "MMD Ore Spawn Mod (fallback generator disabled, MMD OreSpawn enabled)"); - } - } - - Materials.init(); - Fluids.init(); - ItemGroups.init(); - Blocks.init(); - Items.init(); - - VillagerTrades.init(); - - IntegrationManager.INSTANCE.preInit(event); - } - - /* Right now this isn't needed, but might be at some point - public void onRemap(FMLMissingMappingsEvent event) { - for (final MissingMapping mapping : event.get()) { - if (mapping.resourceLocation.getResourceDomain().equals(ModernMetals.MODID)) { - if (mapping.type.equals(GameRegistry.Type.BLOCK)) { - // Dummy - } else if (mapping.type.equals(GameRegistry.Type.ITEM)) { - // Dummy - } - } - } - }*/ - - public void init(FMLInitializationEvent event) { - Recipes.init(); - - Achievements.init(); - - MinecraftForge.EVENT_BUS.register(new EventHandler()); - - ItemGroups.setupIcons(MaterialNames.ALUMINUM); - } - - public void postInit(FMLPostInitializationEvent event) { - Config.postInit(); - } -} diff --git a/src/main/java/com/mcmoddev/modernmetals/util/Config.java b/src/main/java/com/mcmoddev/modernmetals/util/Config.java deleted file mode 100644 index 7e7bef14..00000000 --- a/src/main/java/com/mcmoddev/modernmetals/util/Config.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.mcmoddev.modernmetals.util; - -import java.io.File; - -import com.mcmoddev.modernmetals.ModernMetals; -import com.mcmoddev.modernmetals.data.MaterialNames; -import com.mcmoddev.lib.registry.CrusherRecipeRegistry; - -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.common.config.Configuration; -import net.minecraftforge.fml.client.event.ConfigChangedEvent; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; - -/** - * @author Jasmine Iwanek - * - */ -public class Config extends com.mcmoddev.lib.util.ConfigBase { - - private static Configuration configuration; - private static final String CONFIG_FILE = "config/ModernMetals.cfg"; - private static final String MATERIALS_CAT = "Metals"; - - @SubscribeEvent - public void onConfigChange(ConfigChangedEvent.OnConfigChangedEvent e) { - if (e.getModID().equals(ModernMetals.MODID)) { - init(); - } - } - - public static void init() { - if (configuration == null) { - configuration = new Configuration(new File(CONFIG_FILE)); - MinecraftForge.EVENT_BUS.register(new Config()); - } - - // METALS - Options.materialEnabled(MaterialNames.ALUMINUM, configuration.getBoolean("EnableAluminum", MATERIALS_CAT, true, "Enable Aluminum Items and Materials")); - Options.materialEnabled(MaterialNames.ALUMINUM_BRASS, configuration.getBoolean("EnableAluminumBrass", MATERIALS_CAT, true, "Enable Aluminum Brass Items and Materials")); - Options.materialEnabled(MaterialNames.BERYLLIUM, configuration.getBoolean("EnableBeryllium", MATERIALS_CAT, true, "Enable Beryllium Items and Materials")); - Options.materialEnabled(MaterialNames.BORON, configuration.getBoolean("EnableBoron", MATERIALS_CAT, true, "Enable Boron Items and Materials")); - Options.materialEnabled(MaterialNames.CADMIUM, configuration.getBoolean("EnableCadmium", MATERIALS_CAT, true, "Enable Cadmium Items and Materials")); - Options.materialEnabled(MaterialNames.CHROMIUM, configuration.getBoolean("EnableChromium", MATERIALS_CAT, true, "Enable Chromium Items and Materials")); - Options.materialEnabled(MaterialNames.GALVANIZED_STEEL, configuration.getBoolean("EnableGalvanizedSteel", MATERIALS_CAT, true, "Enable Galvanized Steel Items and Materials")); - Options.materialEnabled(MaterialNames.IRIDIUM, configuration.getBoolean("EnableIridium", MATERIALS_CAT, true, "Enable Iridium Items and Materials")); - Options.materialEnabled(MaterialNames.MAGNESIUM, configuration.getBoolean("EnableMagnesium", MATERIALS_CAT, true, "Enable Magnesium Items and Materials")); - Options.materialEnabled(MaterialNames.MANGANESE, configuration.getBoolean("EnableManganese", MATERIALS_CAT, true, "Enable Manganese Items and Materials")); - Options.materialEnabled(MaterialNames.NICHROME, configuration.getBoolean("EnableNichrome", MATERIALS_CAT, true, "Enable Nichrome Items and Materials")); - Options.materialEnabled(MaterialNames.OSMIUM, configuration.getBoolean("EnableOsmium", MATERIALS_CAT, true, "Enable Osmium Items and Materials")); - Options.materialEnabled(MaterialNames.PLUTONIUM, configuration.getBoolean("EnablePlutonium", MATERIALS_CAT, true, "Enable Plutonium Items and Materials")); - Options.materialEnabled(MaterialNames.RUTILE, configuration.getBoolean("EnableRutile", MATERIALS_CAT, true, "Enable Rutile Items and Materials")); - Options.materialEnabled(MaterialNames.STAINLESS_STEEL, configuration.getBoolean("EnableStainlessSteel", MATERIALS_CAT, true, "Enable Stainless Steel Items and Materials")); - Options.materialEnabled(MaterialNames.TANTALUM, configuration.getBoolean("EnableTantalum", MATERIALS_CAT, true, "Enable Tantalum Items and Materials")); - Options.materialEnabled(MaterialNames.THORIUM, configuration.getBoolean("EnableThorium", MATERIALS_CAT, true, "Enable Thorium Items and Materials")); - Options.materialEnabled(MaterialNames.TITANIUM, configuration.getBoolean("EnableTitanium", MATERIALS_CAT, true, "Enable Titanium Items and Materials")); - Options.materialEnabled(MaterialNames.TUNGSTEN, configuration.getBoolean("EnableTungsten", MATERIALS_CAT, true, "Enable Tungsten Items and Materials")); - Options.materialEnabled(MaterialNames.URANIUM, configuration.getBoolean("EnableUranium", MATERIALS_CAT, true, "Enable Uranium Items and Materials")); - Options.materialEnabled(MaterialNames.ZIRCONIUM, configuration.getBoolean("EnableZirconium", MATERIALS_CAT, true, "Enable Zirconium Items and Materials")); - - if (configuration.hasChanged()) { - configuration.save(); - } - } - - public static void postInit() { - CrusherRecipeRegistry.getInstance().clearCache(); - } -} diff --git a/src/main/java/com/mcmoddev/modernmetals/util/EventHandler.java b/src/main/java/com/mcmoddev/modernmetals/util/EventHandler.java deleted file mode 100644 index 0620d8a0..00000000 --- a/src/main/java/com/mcmoddev/modernmetals/util/EventHandler.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.mcmoddev.modernmetals.util; - -import com.mcmoddev.modernmetals.data.AchievementNames; -import com.mcmoddev.modernmetals.data.MaterialNames; -import com.mcmoddev.modernmetals.init.Achievements; - -import com.mcmoddev.lib.material.IMMDObject; -import com.mcmoddev.lib.util.ConfigBase.Options; -import com.mcmoddev.lib.item.ItemMMDIngot; - -import net.minecraft.item.Item; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import net.minecraftforge.fml.common.gameevent.PlayerEvent.ItemSmeltedEvent; - -public class EventHandler { - - @SubscribeEvent - void event(ItemSmeltedEvent event) { - if (!(Options.enableAchievements())) { - return; - } - - final Item item = event.smelting.getItem(); - if (!(item instanceof IMMDObject)) { - return; - } - - final String materialName = ((IMMDObject) item).getMMDMaterial().getName(); - if (item instanceof ItemMMDIngot) { - if (materialName.equals(MaterialNames.ALUMINUM_BRASS)) { - event.player.addStat(Achievements.getAchievementByName(AchievementNames.ALUMINUM_BRASS_MAKER), 1); - } else if (materialName.equals(MaterialNames.GALVANIZED_STEEL)) { - event.player.addStat(Achievements.getAchievementByName(AchievementNames.GALVANIZED_STEEL_MAKER), 1); - } else if (materialName.equals(MaterialNames.NICHROME)) { - event.player.addStat(Achievements.getAchievementByName(AchievementNames.NICHROME_MAKER), 1); - } else if (materialName.equals(MaterialNames.STAINLESS_STEEL)) { - event.player.addStat(Achievements.getAchievementByName(AchievementNames.STAINLESS_STEEL_MAKER), 1); - } else if (materialName.equals(MaterialNames.TITANIUM)) { - event.player.addStat(Achievements.getAchievementByName(AchievementNames.TITANIUM_MAKER), 1); - } - } - } -} diff --git a/src/main/java/com/mcmoddev/orespawn/ModernMetalsOreSpawn.java b/src/main/java/com/mcmoddev/orespawn/ModernMetalsOreSpawn.java deleted file mode 100644 index dbbd7401..00000000 --- a/src/main/java/com/mcmoddev/orespawn/ModernMetalsOreSpawn.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.mcmoddev.orespawn; - -import com.mcmoddev.modernmetals.ModernMetals; -import com.mcmoddev.orespawn.api.os3.*; -import com.mcmoddev.orespawn.api.plugin.IOreSpawnPlugin; -import com.mcmoddev.orespawn.api.plugin.OreSpawnPlugin; - -@OreSpawnPlugin(modid = ModernMetals.MODID, resourcePath = "orespawn") -public class ModernMetalsOreSpawn implements IOreSpawnPlugin { - - @Override - public void register(OS3API apiInterface) { - // nothing for us to do - all of our ores are in the - // jar and the code handles that - } -} diff --git a/src/main/java/com/mcmoddev/modernmetals/ModernMetals.java b/src/main/java/zone/moddev/mc/modernmetals/ModernMetals.java similarity index 56% rename from src/main/java/com/mcmoddev/modernmetals/ModernMetals.java rename to src/main/java/zone/moddev/mc/modernmetals/ModernMetals.java index 7862f940..3f406c03 100644 --- a/src/main/java/com/mcmoddev/modernmetals/ModernMetals.java +++ b/src/main/java/zone/moddev/mc/modernmetals/ModernMetals.java @@ -1,9 +1,10 @@ -package com.mcmoddev.modernmetals; +package zone.moddev.mc.modernmetals; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import com.mcmoddev.modernmetals.proxy.CommonProxy; +import zone.moddev.mc.modernmetals.proxy.CommonProxy; +import zone.moddev.mc.modernmetals.compat.LegacyRegistryMappings; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod.EventHandler; @@ -12,6 +13,7 @@ import net.minecraftforge.fml.common.event.FMLFingerprintViolationEvent; import net.minecraftforge.fml.common.event.FMLInitializationEvent; import net.minecraftforge.fml.common.event.FMLMissingMappingsEvent; +import net.minecraftforge.fml.common.event.FMLModIdMappingEvent; import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; @@ -25,9 +27,9 @@ modid = ModernMetals.MODID, name = ModernMetals.NAME, version = ModernMetals.VERSION, - dependencies = "required-after:forge@[13.20.1.2386,);required-after:basemetals;before:buildingbricks", - acceptedMinecraftVersions = "[1.11.2,)", - certificateFingerprint = "@FINGERPRINT@", + dependencies = "required-after:forge@[13.20.1.2588,);required-after:basemetals@[2.5.1.111021,2.6.0);required-after:orespawn@[4.1.5.111021,5.0.0);after:tconstruct;after:ic2;after:cofhcore;after:thermalexpansion;after:mekanism;before:buildingbricks", + acceptedMinecraftVersions = "[1.11.2]", + guiFactory = "zone.moddev.mc.modernmetals.client.ModernMetalsGuiFactory", updateJSON = ModernMetals.UPDATEJSON) public class ModernMetals { @@ -38,25 +40,24 @@ public class ModernMetals { public static final String MODID = "modernmetals"; /** Display name of this Mod */ - static final String NAME = "Modern Metals"; + public static final String NAME = "Modern Metals"; /** - * Version number, in Major.Minor.Patch format. The minor number is + * Version number, in Major.Minor.Build format. The minor number is * increased whenever a change is made that has the potential to break * compatibility with other mods that depend on this one. */ - public static final String VERSION = "2.5.0-beta4"; + public static final String VERSION = "2.5.1.111021"; - static final String UPDATEJSON = "https://raw.githubusercontent.com/MinecraftModDevelopmentMods/ModernMetals/master/update.json"; + public static final String UPDATEJSON = "https://raw.githubusercontent.com/MinecraftModDevelopmentMods/ModernMetals/master-1.11.2/update.json"; - private static final String PROXY_BASE = "com.mcmoddev." + MODID + ".proxy."; + private static final String PROXY_BASE = "zone.moddev.mc.modernmetals.proxy."; @SidedProxy(clientSide = PROXY_BASE + "ClientProxy", serverSide = PROXY_BASE + "ServerProxy") public static CommonProxy proxy; public static final Logger logger = LogManager.getFormatterLogger(ModernMetals.MODID); - @EventHandler public void onFingerprintViolation(FMLFingerprintViolationEvent event) { logger.warn("Invalid fingerprint detected!"); } @@ -78,7 +79,18 @@ public static void postInit(FMLPostInitializationEvent event) { @EventHandler public static void onRemap(FMLMissingMappingsEvent event) { - // we are not using this right now... - // proxy.onRemap(event); + proxy.onRemap(event); + } + + @EventHandler + public static void onIdMapping(FMLModIdMappingEvent event) { + LegacyRegistryMappings.restoreItemAliases(); + } + + @EventHandler + public static void loadComplete(net.minecraftforge.fml.common.event.FMLLoadCompleteEvent event) { + zone.moddev.mc.modernmetals.content.ContentPolicy.filterRecipes(); + if (net.minecraftforge.fml.common.Loader.isModLoaded("tconstruct")) + zone.moddev.mc.modernmetals.integration.plugins.TinkersConstruct.applyContentPolicy(); } } diff --git a/src/main/java/zone/moddev/mc/modernmetals/block/OverlayOreBlock.java b/src/main/java/zone/moddev/mc/modernmetals/block/OverlayOreBlock.java new file mode 100644 index 00000000..9a877421 --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/block/OverlayOreBlock.java @@ -0,0 +1,20 @@ +package zone.moddev.mc.modernmetals.block; + +import com.mcmoddev.lib.block.BlockMMDOre; +import com.mcmoddev.lib.material.MMDMaterial; +import net.minecraft.util.BlockRenderLayer; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +/** The shared ore block with alpha testing enabled for its model's outer faces. */ +public final class OverlayOreBlock extends BlockMMDOre { + public OverlayOreBlock(MMDMaterial material) { + super(material); + } + + @Override + @SideOnly(Side.CLIENT) + public BlockRenderLayer getBlockLayer() { + return BlockRenderLayer.CUTOUT_MIPPED; + } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/client/ModernMetalsConfigScreen.java b/src/main/java/zone/moddev/mc/modernmetals/client/ModernMetalsConfigScreen.java new file mode 100644 index 00000000..8e5f781d --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/client/ModernMetalsConfigScreen.java @@ -0,0 +1,36 @@ +package zone.moddev.mc.modernmetals.client; + +import java.util.ArrayList; +import java.util.List; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import net.minecraftforge.common.config.ConfigElement; +import net.minecraftforge.common.config.Property; +import net.minecraftforge.fml.client.config.GuiConfig; +import net.minecraftforge.fml.client.config.IConfigElement; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import zone.moddev.mc.modernmetals.ModernMetals; +import zone.moddev.mc.modernmetals.util.Config; + +/** One parent-aware screen for Forge's mod list and OreSpawn's child-mod list. */ +@SideOnly(Side.CLIENT) +public final class ModernMetalsConfigScreen extends GuiConfig { + public ModernMetalsConfigScreen(final GuiScreen parent) { + super(parent, elements(), + ModernMetals.MODID, false, true, I18n.format("config.modernmetals.title"), + I18n.format("config.modernmetals.description")); + } + private static List elements() { + final List elements = new ArrayList<>( + new ConfigElement(Config.getConfiguration().getCategory("General")).getChildElements()); + for (final Property material : Config.getConfiguration().getCategory("Metals").getOrderedValues()) { + elements.add(new ConfigElement(material) { + @Override public String getComment() { + return I18n.format("config.modernmetals.material.tooltip"); + } + }); + } + return elements; + } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/client/ModernMetalsGuiFactory.java b/src/main/java/zone/moddev/mc/modernmetals/client/ModernMetalsGuiFactory.java new file mode 100644 index 00000000..573b5957 --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/client/ModernMetalsGuiFactory.java @@ -0,0 +1,20 @@ +package zone.moddev.mc.modernmetals.client; + +import java.util.Collections; +import java.util.Set; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraftforge.fml.client.IModGuiFactory; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +/** Forge 1.11's configuration factory; never loaded by a dedicated server. */ +@SideOnly(Side.CLIENT) +public final class ModernMetalsGuiFactory implements IModGuiFactory { + @Override public void initialize(final Minecraft minecraft) { } + @Override public Class mainConfigGuiClass() { return ModernMetalsConfigScreen.class; } + @Override public boolean hasConfigGui() { return true; } + @Override public GuiScreen createConfigGui(final GuiScreen parent) { return new ModernMetalsConfigScreen(parent); } + @Override public Set runtimeGuiCategories() { return Collections.emptySet(); } + @Override public RuntimeOptionGuiHandler getHandlerFor(final RuntimeOptionCategoryElement element) { return null; } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/compat/LegacyNamespaceTransformer.java b/src/main/java/zone/moddev/mc/modernmetals/compat/LegacyNamespaceTransformer.java new file mode 100644 index 00000000..0c3635d6 --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/compat/LegacyNamespaceTransformer.java @@ -0,0 +1,89 @@ +package zone.moddev.mc.modernmetals.compat; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import net.minecraft.launchwrapper.IClassTransformer; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.commons.Remapper; +import org.objectweb.asm.commons.RemappingClassAdapter; + +/** Relinks existing MMD add-ons to the same classes and registries as Modern Metals. */ +public final class LegacyNamespaceTransformer implements IClassTransformer { + + private static final String OLD = "com/mcmoddev/modernmetals/"; + private static final String CURRENT = "zone/moddev/mc/modernmetals/"; + private static final Set EXPORTS = loadExports(); + + @Override + public byte[] transform(final String name, final String transformedName, final byte[] bytes) { + if (bytes == null || name == null || name.startsWith("zone.moddev.mc.modernmetals.") + || !(contains(bytes, OLD) || contains(bytes, OLD.replace('/', '.')))) { + return bytes; + } + + final ClassWriter output = new ClassWriter(0); + final Remapper names = new Remapper() { + @Override + public String map(final String internalName) { + return currentName(internalName); + } + + @Override + public Object mapValue(final Object value) { + if (value instanceof String) { + final String text = (String) value; + final String internal = text.replace('.', '/'); + final String mapped = currentName(internal); + if (mapped.equals(internal)) return value; + return text.indexOf('/') < 0 ? mapped.replace('/', '.') : mapped; + } + return super.mapValue(value); + } + }; + + new ClassReader(bytes).accept(new RemappingClassAdapter(output, names), 0); + return output.toByteArray(); + } + + private static String currentName(final String name) { + if (!name.startsWith(OLD)) return name; + + final String suffix = name.substring(OLD.length()); + final int nested = suffix.indexOf('$'); + final String owner = nested < 0 ? suffix : suffix.substring(0, nested); + return EXPORTS.contains(owner) ? CURRENT + suffix : name; + } + + private static Set loadExports() { + final Set result = new HashSet<>(); + final InputStream stream = LegacyNamespaceTransformer.class.getClassLoader() + .getResourceAsStream("META-INF/modernmetals-legacy-api.list"); + if (stream == null) throw new IllegalStateException("Missing Modern Metals API name list"); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + if (!line.isEmpty() && !line.startsWith("#")) result.add(line); + } + } catch (final IOException failure) { + throw new IllegalStateException("Cannot read Modern Metals API names", failure); + } + return Collections.unmodifiableSet(result); + } + + private static boolean contains(final byte[] bytes, final String text) { + for (int offset = 0; offset <= bytes.length - text.length(); offset++) { + int index = 0; + while (index < text.length() && bytes[offset + index] == text.charAt(index)) index++; + if (index == text.length()) return true; + } + return false; + } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/compat/LegacyRegistryMappings.java b/src/main/java/zone/moddev/mc/modernmetals/compat/LegacyRegistryMappings.java new file mode 100644 index 00000000..24d52537 --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/compat/LegacyRegistryMappings.java @@ -0,0 +1,58 @@ +package zone.moddev.mc.modernmetals.compat; + +import java.util.LinkedHashMap; +import java.util.Map; + +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.event.FMLMissingMappingsEvent; +import net.minecraftforge.fml.common.registry.ForgeRegistries; +import net.minecraftforge.fml.common.registry.GameData; +import net.minecraftforge.fml.common.registry.GameRegistry; +import zone.moddev.mc.modernmetals.ModernMetals; + +/** Registry names written by Modern Metals 0.11.0. */ +public final class LegacyRegistryMappings { + private static final String OLD_BRASS = "alubrass"; + private static final ResourceLocation MOLTEN_BRASS = id("aluminumbrass"); + private static final Map ITEM_ALIASES = itemAliases(); + + private LegacyRegistryMappings() {} + + public static void remap(final FMLMissingMappingsEvent event) { + for (final FMLMissingMappingsEvent.MissingMapping mapping : event.get()) { + if (mapping.type == GameRegistry.Type.BLOCK && mapping.resourceLocation.equals(id(OLD_BRASS)) + && ForgeRegistries.BLOCKS.containsKey(MOLTEN_BRASS)) { + mapping.remap(ForgeRegistries.BLOCKS.getValue(MOLTEN_BRASS)); + } else if (mapping.type == GameRegistry.Type.ITEM) { + final ResourceLocation replacement = ITEM_ALIASES.get(mapping.resourceLocation); + // Some saves contain both the door block's item ID and the old door item ID. + if (replacement != null && ForgeRegistries.ITEMS.containsKey(replacement)) mapping.ignore(); + } + } + } + + public static void restoreItemAliases() { + final Map aliases = new LinkedHashMap<>(); + ITEM_ALIASES.forEach((oldId, newId) -> { + if (ForgeRegistries.ITEMS.containsKey(newId)) aliases.put(oldId, newId); + }); + GameData.getItemRegistry().loadAliases(aliases); + } + + private static Map itemAliases() { + final Map aliases = new LinkedHashMap<>(); + aliases.put(id(OLD_BRASS), MOLTEN_BRASS); + for (final String material : new String[] { "aluminum", "aluminumbrass", "cadmium", "chromium", + "galvanizedsteel", "iridium", "magnesium", "manganese", "nichrome", "osmium", + "plutonium", "rutile", "stainlesssteel", "tantalum", "titanium", "tungsten", "uranium", "zirconium" }) { + aliases.put(id(material + "_door_item"), id(material + "_door")); + aliases.put(id(material + "_fishingrod"), id(material + "_fishing_rod")); + aliases.put(id(material + "_horsearmor"), id(material + "_horse_armor")); + } + return aliases; + } + + private static ResourceLocation id(final String path) { + return new ResourceLocation(ModernMetals.MODID, path); + } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/compat/ModernMetalsLoadingPlugin.java b/src/main/java/zone/moddev/mc/modernmetals/compat/ModernMetalsLoadingPlugin.java new file mode 100644 index 00000000..5ca19d4f --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/compat/ModernMetalsLoadingPlugin.java @@ -0,0 +1,19 @@ +package zone.moddev.mc.modernmetals.compat; + +import java.util.Map; +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; + +/** Relinks old add-on names without installing a second Base Metals transformer. */ +@IFMLLoadingPlugin.Name("ModernMetals") +@IFMLLoadingPlugin.MCVersion("1.11.2") +@IFMLLoadingPlugin.SortingIndex(1002) +@IFMLLoadingPlugin.TransformerExclusions("zone.moddev.mc.modernmetals.compat.") +public final class ModernMetalsLoadingPlugin implements IFMLLoadingPlugin { + @Override public String[] getASMTransformerClass() { + return new String[] { "zone.moddev.mc.modernmetals.compat.LegacyNamespaceTransformer" }; + } + @Override public String getModContainerClass() { return null; } + @Override public String getSetupClass() { return null; } + @Override public void injectData(final Map data) { } + @Override public String getAccessTransformerClass() { return null; } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/content/ContentModeNetwork.java b/src/main/java/zone/moddev/mc/modernmetals/content/ContentModeNetwork.java new file mode 100644 index 00000000..c19b2699 --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/content/ContentModeNetwork.java @@ -0,0 +1,64 @@ +package zone.moddev.mc.modernmetals.content; + +import java.util.Set; + +import net.minecraft.network.NetworkManager; +import net.minecraft.network.play.server.SPacketDisconnect; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.network.FMLNetworkEvent.CustomPacketRegistrationEvent; +import net.minecraftforge.fml.common.network.NetworkRegistry; + +/** Advertise the active recipe mode in Forge's login channel registration. */ +public final class ContentModeNetwork { + private static final String HIGH = "ModernMetals|High"; + private static final String LOW = "ModernMetals|Low"; + + private ContentModeNetwork() {} + + public static void register() { + NetworkRegistry.INSTANCE.newEventDrivenChannel(ContentPolicy.active().lowFantasy() ? LOW : HIGH); + MinecraftForge.EVENT_BUS.register(new ContentModeNetwork()); + } + + @SubscribeEvent + public void checkMode(final CustomPacketRegistrationEvent event) { + final Set channels = event.getRegistrations(); + // Forge includes FML|HS in its login registration, not ordinary channel updates. + if (!"REGISTER".equals(event.getOperation()) || !channels.contains("FML|HS")) return; + + final boolean localLow = ContentPolicy.active().lowFantasy(); + final boolean remoteLow = channels.contains(LOW); + final boolean remoteHigh = channels.contains(HIGH); + final boolean server = event.getSide().isServer(); + final ITextComponent reason; + if (remoteLow == remoteHigh) { + // A client with no mode channel may also lack the translation key. + reason = server + ? new TextComponentString("Modern Metals could not check the Fantasy Level. " + + "Use the same Modern Metals build on the server and client.") + : new TextComponentTranslation("config.modernmetals.connection.protocol"); + } else if (remoteLow == localLow) { + return; + } else { + final boolean serverLow = server ? localLow : remoteLow; + reason = new TextComponentTranslation("config.modernmetals.connection.mismatch", + modeLabel(serverLow), modeLabel(!serverLow)); + } + + final NetworkManager manager = event.getManager(); + manager.channel().config().setAutoRead(false); + if (server) { + manager.sendPacket(new SPacketDisconnect(reason), future -> manager.closeChannel(reason)); + } else { + manager.closeChannel(reason); + } + } + + private static ITextComponent modeLabel(final boolean low) { + return new TextComponentTranslation(low ? "low_fantasy" : "high_fantasy"); + } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/content/ContentPolicy.java b/src/main/java/zone/moddev/mc/modernmetals/content/ContentPolicy.java new file mode 100644 index 00000000..7597da5c --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/content/ContentPolicy.java @@ -0,0 +1,84 @@ +package zone.moddev.mc.modernmetals.content; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.CraftingManager; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import zone.moddev.mc.modernmetals.ModernMetals; + +/** Restricts obtainability, while retaining every registered item and saved stack. */ +public final class ContentPolicy { + private static final Set TOOLS = names("axe","crackhammer","hoe","pickaxe","scythe","shears","shovel","sword","arrow","bolt"); + private static final Set PROTECTION = names("boots","chestplate","helmet","leggings","horse_armor","shield"); + private static final Set FULL_TOOLS = names("galvanizedsteel","stainlesssteel","titanium"); + private static final Set ARMOR = names("aluminum","aluminumbrass","galvanizedsteel","stainlesssteel","titanium"); + private static final Set GEARS = names("aluminum","aluminumbrass","galvanizedsteel","stainlesssteel","titanium"); + private static final Set STRUCTURAL_FORMS = names("bars","bookshelf","button","door","double_slab","fence","fence_gate","flower_pot","ladder","lever","pressure_plate","slab","stairs","trapdoor","tripwire_hook","wall"); + private static final Set STRUCTURAL_METALS = names("aluminum","aluminumbrass","galvanizedsteel","nichrome","stainlesssteel","tantalum","titanium","tungsten","zirconium"); + private static boolean initialized; + private static ContentPolicy active = forMode("high_fantasy"); + private final boolean low; + private ContentPolicy(final boolean low) { this.low=low; } + public static ContentPolicy forMode(final String mode) { + return new ContentPolicy(mode!=null && "low_fantasy".equals(mode.trim().toLowerCase(Locale.ROOT))); + } + public static ContentPolicy active() { return active; } + public boolean lowFantasy() { return low; } + /** Latch Modern Metals' own startup choice until the next full game restart. */ + public static void initialize(final String mode) { + if(initialized) return; + active=forMode(mode);initialized=true; + ModernMetals.logger.info("Modern Metals content mode: " + (active.low ? "low_fantasy" : "high_fantasy") + " (fixed until restart)"); + } + public boolean allows(final String material,final String form) { + if(!low || material==null || form==null) return true; + if(TOOLS.contains(form)) return FULL_TOOLS.contains(material); + if(PROTECTION.contains(form)) return ARMOR.contains(material); + if("bow".equals(form) || "crossbow".equals(form) || "fishing_rod".equals(form)) return false; + if("gear".equals(form)) return GEARS.contains(material); + if("anvil".equals(form)) return "stainlesssteel".equals(material) || "galvanizedsteel".equals(material); + if(STRUCTURAL_FORMS.contains(form)) return STRUCTURAL_METALS.contains(material); + return true; + } + public boolean allows(final ItemStack stack) { + if(!low || stack==null || stack.isEmpty()) return true; + final ResourceLocation id=stack.getItem().getRegistryName(); + if(id==null || !ModernMetals.MODID.equals(id.getResourceDomain())) return true; + final String path=id.getResourcePath().startsWith("double_") ? id.getResourcePath().substring(7) : id.getResourcePath(); + final int split=path.indexOf('_'); + return split<0 || allows(path.substring(0,split),path.substring(split+1)); + } + /** Called after all mods have registered their ordinary crafting recipes. */ + public static void filterRecipes() { + if(!active.low) return; + final int before=CraftingManager.getInstance().getRecipeList().size(); + CraftingManager.getInstance().getRecipeList().removeIf(recipe->!active.allows(recipe.getRecipeOutput())); + final java.util.List recipes=CraftingManager.getInstance().getRecipeList(); + for(int i=0;i getRemainingItems(final net.minecraft.inventory.InventoryCrafting grid) {return delegate.getRemainingItems(grid);} + } + private static Set names(final String...values) {return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(values)));} +} diff --git a/src/main/java/com/mcmoddev/modernmetals/data/AchievementNames.java b/src/main/java/zone/moddev/mc/modernmetals/data/AchievementNames.java similarity index 94% rename from src/main/java/com/mcmoddev/modernmetals/data/AchievementNames.java rename to src/main/java/zone/moddev/mc/modernmetals/data/AchievementNames.java index 5e14de51..ef77a790 100644 --- a/src/main/java/com/mcmoddev/modernmetals/data/AchievementNames.java +++ b/src/main/java/zone/moddev/mc/modernmetals/data/AchievementNames.java @@ -1,4 +1,4 @@ -package com.mcmoddev.modernmetals.data; +package zone.moddev.mc.modernmetals.data; public class AchievementNames { diff --git a/src/main/java/com/mcmoddev/modernmetals/data/MaterialNames.java b/src/main/java/zone/moddev/mc/modernmetals/data/MaterialNames.java similarity index 96% rename from src/main/java/com/mcmoddev/modernmetals/data/MaterialNames.java rename to src/main/java/zone/moddev/mc/modernmetals/data/MaterialNames.java index c732fede..32b57f88 100644 --- a/src/main/java/com/mcmoddev/modernmetals/data/MaterialNames.java +++ b/src/main/java/zone/moddev/mc/modernmetals/data/MaterialNames.java @@ -1,4 +1,4 @@ -package com.mcmoddev.modernmetals.data; +package zone.moddev.mc.modernmetals.data; import com.mcmoddev.lib.data.SharedStrings; @@ -29,4 +29,4 @@ public class MaterialNames { private MaterialNames() { throw new IllegalAccessError(SharedStrings.NOT_INSTANTIABLE); } -} +} \ No newline at end of file diff --git a/src/main/java/com/mcmoddev/modernmetals/data/TraitNames.java b/src/main/java/zone/moddev/mc/modernmetals/data/TraitNames.java similarity index 94% rename from src/main/java/com/mcmoddev/modernmetals/data/TraitNames.java rename to src/main/java/zone/moddev/mc/modernmetals/data/TraitNames.java index 301f6254..82ededc3 100644 --- a/src/main/java/com/mcmoddev/modernmetals/data/TraitNames.java +++ b/src/main/java/zone/moddev/mc/modernmetals/data/TraitNames.java @@ -1,4 +1,4 @@ -package com.mcmoddev.modernmetals.data; +package zone.moddev.mc.modernmetals.data; import com.mcmoddev.lib.data.SharedStrings; diff --git a/src/main/java/zone/moddev/mc/modernmetals/init/Achievements.java b/src/main/java/zone/moddev/mc/modernmetals/init/Achievements.java new file mode 100644 index 00000000..4287d332 --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/init/Achievements.java @@ -0,0 +1,65 @@ +package zone.moddev.mc.modernmetals.init; + +import com.mcmoddev.lib.data.Names; +import zone.moddev.mc.modernmetals.util.Config.Options; + +import net.minecraft.stats.Achievement; +import net.minecraftforge.common.AchievementPage; +import net.minecraftforge.fml.common.Loader; + +/** initializer for achievements */ +public class Achievements extends com.mcmoddev.lib.init.Achievements { + + /** Make Aluminum Brass Blend */ + public static Achievement aluminumBrassMaker; + /** Make Galvanized Steel Blend */ + public static Achievement galvanizedSteelMaker; + /** Make Nichrome Blend */ + public static Achievement nichromeMaker; + /** Make Stainless Steel Blend */ + public static Achievement stainlessSteelMaker; + /** Make Titanium Blend */ + public static Achievement titaniumMaker; + + private static boolean initDone = false; + + private Achievements() { + throw new IllegalAccessError("Not a instantiable class"); + } + + /** + * + */ + public static void init() { + if (initDone) { + return; + } + + if (zone.moddev.mc.basemetals.util.Config.Options.enableAchievements()) { + AchievementPage page = new AchievementPage(Loader.instance().activeModContainer().getModId()); + AchievementPage.registerAchievementPage(page); + + if (Options.enableAluminumBrass) { + aluminumBrassMaker = makeAchievement("aluminum_brass_maker", zone.moddev.mc.basemetals.init.Achievements.getAchievementByName("metallurgy"), 0, 0, Materials.getMaterialByName("aluminumbrass").getItem(Names.INGOT), page); + } + + if (Options.enableGalvanizedSteel) { + galvanizedSteelMaker = makeAchievement("galvanized_steel_maker", zone.moddev.mc.basemetals.init.Achievements.getAchievementByName("metallurgy"), 0, 1, Materials.getMaterialByName("galvanizedsteel").getItem(Names.INGOT), page); + } + + if (Options.enableNichrome) { + nichromeMaker = makeAchievement("nichrome_maker", zone.moddev.mc.basemetals.init.Achievements.getAchievementByName("metallurgy"), 0, 2, Materials.getMaterialByName("nichrome").getItem(Names.INGOT), page); + } + + if (Options.enableStainlessSteel) { + stainlessSteelMaker = makeAchievement("stainless_steel_maker", zone.moddev.mc.basemetals.init.Achievements.getAchievementByName("metallurgy"), 0, 3, Materials.getMaterialByName("stainlesssteel").getItem(Names.INGOT), page); + } + + if (Options.enableTitanium) { + titaniumMaker = makeAchievement("titanium_maker", zone.moddev.mc.basemetals.init.Achievements.getAchievementByName("metallurgy"), 0, 4, Materials.getMaterialByName("titanium").getItem(Names.INGOT), page); + } + } + + initDone = true; + } +} diff --git a/src/main/java/com/mcmoddev/modernmetals/init/Blocks.java b/src/main/java/zone/moddev/mc/modernmetals/init/Blocks.java similarity index 63% rename from src/main/java/com/mcmoddev/modernmetals/init/Blocks.java rename to src/main/java/zone/moddev/mc/modernmetals/init/Blocks.java index 0a6821e2..51384363 100644 --- a/src/main/java/com/mcmoddev/modernmetals/init/Blocks.java +++ b/src/main/java/zone/moddev/mc/modernmetals/init/Blocks.java @@ -1,14 +1,14 @@ -package com.mcmoddev.modernmetals.init; +package zone.moddev.mc.modernmetals.init; import java.util.Arrays; -import javax.annotation.Nonnull; - import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.material.MMDMaterial; -import com.mcmoddev.modernmetals.data.MaterialNames; +import com.mcmoddev.lib.util.Oredicts; +import zone.moddev.mc.modernmetals.block.OverlayOreBlock; +import zone.moddev.mc.modernmetals.data.MaterialNames; import net.minecraft.block.Block; @@ -67,7 +67,7 @@ public static void init() { create(Names.BLOCK, material); create(Names.PLATE, material); - create(Names.ORE, material); + createOre(material); create(Names.BARS, material); create(Names.DOOR, material); create(Names.TRAPDOOR, material); @@ -83,4 +83,23 @@ public static void init() { initDone = true; } + + private static void createOre(MMDMaterial material) { + if (MaterialNames.THORIUM.equals(material.getName())) { + // Thorium still has its original opaque texture. + create(Names.ORE, material); + } else if ((material.hasOre() || hasLegacyOre(material.getName())) + && !material.hasBlock(Names.ORE) && isNameEnabled(Names.ORE)) { + final Block ore = addBlock(new OverlayOreBlock(material), Names.ORE, material, + ItemGroups.getTab(SharedStrings.TAB_BLOCKS)); + material.addNewBlock(Names.ORE.toString(), ore); + Oredicts.registerOre("ore" + material.getCapitalizedName(), ore); + } + } + + private static boolean hasLegacyOre(final String name) { + // 0.11.0 saved these alloy ores. OreSpawn has no placement rules for them. + return MaterialNames.GALVANIZED_STEEL.equals(name) || MaterialNames.NICHROME.equals(name) + || MaterialNames.STAINLESS_STEEL.equals(name); + } } diff --git a/src/main/java/zone/moddev/mc/modernmetals/init/Fluids.java b/src/main/java/zone/moddev/mc/modernmetals/init/Fluids.java new file mode 100644 index 00000000..4217fa4f --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/init/Fluids.java @@ -0,0 +1,177 @@ +package zone.moddev.mc.modernmetals.init; + +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidRegistry; +import zone.moddev.mc.modernmetals.util.Config.Options; + +/** + * This class initializes all fluids in Modern Metals. + * + * @author Jasmine Iwanek + * + */ +public class Fluids extends com.mcmoddev.lib.init.Fluids { + + private static boolean initDone = false; + + private Fluids() { + throw new IllegalAccessError("Not a instantiable class"); + } + + /** Reuse another mod's registered fluid instead of retaining a rejected duplicate. */ + protected static net.minecraftforge.fluids.Fluid addFluid(final com.mcmoddev.lib.material.MMDMaterial material, + final int density, final int viscosity, final int temperature, final int luminosity) { + final net.minecraftforge.fluids.Fluid registered=net.minecraftforge.fluids.FluidRegistry.getFluid(material.getName()); + if(registered!=null) { + material.setFluid(registered); + net.minecraftforge.fluids.FluidRegistry.addBucketForFluid(registered); + return registered; + } + return com.mcmoddev.lib.init.Fluids.addFluid(material,density,viscosity,temperature,luminosity); + } + + /** Preserve the legacy Modern Metals query API for borrowed fluids too. */ + public static net.minecraftforge.fluids.Fluid getFluidByName(final String name) { + for(final com.mcmoddev.lib.material.MMDMaterial material:Materials.getMaterialsByMod("modernmetals")) + if(material.getName().equals(name)) return material.getFluid(); + return com.mcmoddev.lib.init.Fluids.getFluidByName(name); + } + public static String getNameOfFluid(final net.minecraftforge.fluids.Fluid fluid) { + if(fluid==null) return null; + for(final com.mcmoddev.lib.material.MMDMaterial material:Materials.getMaterialsByMod("modernmetals")) + if(material.getFluid()==fluid) return material.getName(); + return com.mcmoddev.lib.init.Fluids.getNameOfFluid(fluid); + } + public static java.util.Map getFluidRegistry() { + final java.util.Map view=new java.util.LinkedHashMap<>(com.mcmoddev.lib.init.Fluids.getFluidRegistry()); + for(final com.mcmoddev.lib.material.MMDMaterial material:Materials.getMaterialsByMod("modernmetals")) + if(material.getFluid()!=null) view.put(material.getName(),material.getFluid()); + return java.util.Collections.unmodifiableMap(view); + } + + /** + * + */ + public static void init() { + if (initDone) { + return; + } + + if (Options.enableAluminum) { + addFluid(Materials.aluminum, 2000, 10000, 330, 10); + addFluidBlock(Materials.aluminum); + } + + if (Options.enableAluminumBrass) { + final Fluid brass = addFluid(Materials.aluminumBrass, 2000, 10000, 500, 10); + addFluidBlock(Materials.aluminumBrass); + // Forge 1.11 saves the fluid name in buckets and tanks independently of the block ID. + if (FluidRegistry.getFluid("alubrass") == null) { + final Fluid legacy = new Fluid("alubrass", brass.getStill(), brass.getFlowing()) { + @Override public int getColor() { return brass.getColor(); } + } + .setUnlocalizedName("modernmetals.aluminumbrass").setDensity(brass.getDensity()) + .setViscosity(brass.getViscosity()).setTemperature(brass.getTemperature()) + .setLuminosity(brass.getLuminosity()).setBlock(Materials.aluminumBrass.getFluidBlock()); + FluidRegistry.registerFluid(legacy); + } + } + + if (Options.enableCadmium) { + addFluid(Materials.cadmium, 2000, 10000, 300, 10); + addFluidBlock(Materials.cadmium); + } + + if (Options.enableChromium) { + addFluid(Materials.chromium, 2000, 10000, 769, 10); + addFluidBlock(Materials.chromium); + } + + if (Options.enableGalvanizedSteel) { + addFluid(Materials.galvanizedSteel, 2000, 10000, 769, 10); + addFluidBlock(Materials.galvanizedSteel); + } + + if (Options.enableIridium) { + addFluid(Materials.iridium, 2000, 10000, 769, 10); + addFluidBlock(Materials.iridium); + } + + if (Options.enableMagnesium) { + addFluid(Materials.magnesium, 2000, 10000, 769, 10); + addFluidBlock(Materials.magnesium); + } + + if (Options.enableManganese) { + addFluid(Materials.manganese, 2000, 10000, 769, 10); + addFluidBlock(Materials.manganese); + } + + if (Options.enableNichrome) { + addFluid(Materials.nichrome, 2000, 10000, 769, 10); + addFluidBlock(Materials.nichrome); + } + + if (Options.enableOsmium) { + addFluid(Materials.osmium, 2000, 10000, 769, 10); + addFluidBlock(Materials.osmium); + } + + if (Options.enablePlutonium) { + addFluid(Materials.plutonium, 2000, 10000, 769, 10); + addFluidBlock(Materials.plutonium); + } + + if (Options.enableRutile) { + addFluid(Materials.rutile, 2000, 10000, 769, 10); + addFluidBlock(Materials.rutile); + } + + if (Options.enableStainlessSteel) { + addFluid(Materials.stainlessSteel, 2000, 10000, 769, 10); + addFluidBlock(Materials.stainlessSteel); + } + + if (Options.enableTantalum) { + addFluid(Materials.tantalum, 2000, 10000, 769, 10); + addFluidBlock(Materials.tantalum); + } + + if (Options.enableTitanium) { + addFluid(Materials.titanium, 2000, 10000, 769, 10); + addFluidBlock(Materials.titanium); + } + + if (Options.enableTungsten) { + addFluid(Materials.tungsten, 2000, 10000, 769, 10); + addFluidBlock(Materials.tungsten); + } + + if (Options.enableUranium) { + addFluid(Materials.uranium, 2000, 10000, 769, 10); + addFluidBlock(Materials.uranium); + } + + if (Options.enableZirconium) { + addFluid(Materials.zirconium, 2000, 10000, 769, 10); + addFluidBlock(Materials.zirconium); + } + + if (Options.enableBeryllium) { + addFluid(Materials.beryllium, 2000, 10000, 400, 10); + addFluidBlock(Materials.beryllium); + } + + if (Options.enableBoron) { + addFluid(Materials.boron, 2000, 10000, 400, 10); + addFluidBlock(Materials.boron); + } + + if (Options.enableThorium) { + addFluid(Materials.thorium, 2000, 10000, 514, 10); + addFluidBlock(Materials.thorium); + } + + initDone = true; + } +} diff --git a/src/main/java/com/mcmoddev/modernmetals/init/ItemGroups.java b/src/main/java/zone/moddev/mc/modernmetals/init/ItemGroups.java similarity index 89% rename from src/main/java/com/mcmoddev/modernmetals/init/ItemGroups.java rename to src/main/java/zone/moddev/mc/modernmetals/init/ItemGroups.java index 08599db2..930ce1b9 100644 --- a/src/main/java/com/mcmoddev/modernmetals/init/ItemGroups.java +++ b/src/main/java/zone/moddev/mc/modernmetals/init/ItemGroups.java @@ -1,6 +1,6 @@ -package com.mcmoddev.modernmetals.init; +package zone.moddev.mc.modernmetals.init; -import com.mcmoddev.modernmetals.data.MaterialNames; +import zone.moddev.mc.basemetals.data.MaterialNames; import com.mcmoddev.lib.data.Names; import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.init.Materials; @@ -8,6 +8,7 @@ import com.mcmoddev.lib.material.MMDMaterial; import net.minecraft.block.Block; +import net.minecraft.init.Items; import net.minecraft.item.Item; /** @@ -19,14 +20,13 @@ public class ItemGroups extends com.mcmoddev.lib.init.ItemGroups { private static boolean initDone = false; - private static final MMDCreativeTab blocksTab = addTab(SharedStrings.TAB_BLOCKS); private static final MMDCreativeTab itemsTab = addTab(SharedStrings.TAB_ITEMS); private static final MMDCreativeTab toolsTab = addTab(SharedStrings.TAB_TOOLS); private static final MMDCreativeTab combatTab = addTab(SharedStrings.TAB_COMBAT); private ItemGroups() { - throw new IllegalAccessError(SharedStrings.NOT_INSTANTIABLE); + throw new IllegalAccessError("Not a instantiable class"); } /** diff --git a/src/main/java/com/mcmoddev/modernmetals/init/Items.java b/src/main/java/zone/moddev/mc/modernmetals/init/Items.java similarity index 97% rename from src/main/java/com/mcmoddev/modernmetals/init/Items.java rename to src/main/java/zone/moddev/mc/modernmetals/init/Items.java index 4efa2716..6053764f 100644 --- a/src/main/java/com/mcmoddev/modernmetals/init/Items.java +++ b/src/main/java/zone/moddev/mc/modernmetals/init/Items.java @@ -1,4 +1,4 @@ -package com.mcmoddev.modernmetals.init; +package zone.moddev.mc.modernmetals.init; import java.util.Arrays; import java.util.List; @@ -9,7 +9,7 @@ import com.mcmoddev.lib.data.SharedStrings; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.material.MMDMaterial; -import com.mcmoddev.modernmetals.data.MaterialNames; +import zone.moddev.mc.modernmetals.data.MaterialNames; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.item.Item; diff --git a/src/main/java/zone/moddev/mc/modernmetals/init/Materials.java b/src/main/java/zone/moddev/mc/modernmetals/init/Materials.java new file mode 100644 index 00000000..d6394c30 --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/init/Materials.java @@ -0,0 +1,162 @@ +package zone.moddev.mc.modernmetals.init; + +import zone.moddev.mc.modernmetals.util.Config.Options; + +import com.mcmoddev.lib.material.MMDMaterial; +import com.mcmoddev.lib.material.MMDMaterial.MaterialType; + +/** + * This class initializes all of the materials in Modern Metals. + * + * @author Jasmine Iwanek + * + */ +public class Materials extends com.mcmoddev.lib.init.Materials { + + /** Aluminum */ + public static MMDMaterial aluminum; + /** Aluminum-Brass*/ + public static MMDMaterial aluminumBrass; + /** Cadmium */ + public static MMDMaterial cadmium; + /** Chromium */ + public static MMDMaterial chromium; + /** Galvanized Steel */ + public static MMDMaterial galvanizedSteel; + /** Iridium */ + public static MMDMaterial iridium; + /** Magnesium */ + public static MMDMaterial magnesium; + /** Manganese */ + public static MMDMaterial manganese; + /** Nichrome */ + public static MMDMaterial nichrome; + /** Osmium */ + public static MMDMaterial osmium; + /** Plutonium */ + public static MMDMaterial plutonium; + /** Rutile */ + public static MMDMaterial rutile; + /** Stainless Steel */ + public static MMDMaterial stainlessSteel; + /** Tantalum */ + public static MMDMaterial tantalum; + /** Titanium */ + public static MMDMaterial titanium; + /** Tungsten */ + public static MMDMaterial tungsten; + /** Uranium */ + public static MMDMaterial uranium; + /** Zirconium */ + public static MMDMaterial zirconium; + + /** Beryllium */ + public static MMDMaterial beryllium; + + /** Boron */ + public static MMDMaterial boron; + + /** Thorium */ + public static MMDMaterial thorium; + + private static boolean initDone = false; + + protected Materials() { + throw new IllegalAccessError("Not a instantiable class"); + } + + /** + * + */ + public static void init() { + if (initDone) { + return; + } + + if (Options.enableAluminum) { + aluminum = createMaterial("aluminum", MaterialType.METAL, 2.5, 3.75, 4.5, 0xFFC5C8C1); + } + + if (Options.enableAluminumBrass) { + aluminumBrass = createAlloyMaterial("aluminumbrass", MaterialType.METAL, 1, 7.5, 4.5, 0xFFEBAA56); + } + + if (Options.enableCadmium) { + cadmium = createRareMaterial("cadmium", MaterialType.METAL, 2, 1, 4.5, 0xFFC9D4DA); + } + + if (Options.enableChromium) { + chromium = createMaterial("chromium", MaterialType.METAL, 9, 3, 4.5, 0xFFCDCDCF); + } + + if (Options.enableGalvanizedSteel) { + galvanizedSteel = createAlloyMaterial("galvanizedsteel", MaterialType.METAL, 5.5, 15.25, 4.5, 0xFF9BA6A2); + } + + if (Options.enableIridium) { + iridium = createRareMaterial("iridium", MaterialType.METAL, 6.5, 3, 4.5, 0xFFF8EDCC); + } + + if (Options.enableMagnesium) { + magnesium = createMaterial("magnesium", MaterialType.METAL, 2.5, 3.5, 4.5, 0xFF7F7F77); + } + + if (Options.enableManganese) { + manganese = createMaterial("manganese", MaterialType.METAL, 5, 2.75, 4.5, 0xFFF5CFDA); + } + + if (Options.enableNichrome) { + nichrome = createAlloyMaterial("nichrome", MaterialType.METAL, 6, 15.5, 4.5, 0xFFDEA054); + } + + if (Options.enableOsmium) { + osmium = createMaterial("osmium", MaterialType.METAL, 7, 2.75, 4.5, 0xFF7C8E99); + } + + if (Options.enablePlutonium) { + plutonium = createRareMaterial("plutonium", MaterialType.METAL, 4, 7.5, 4.5, 0xFFB333EA); + } + + if (Options.enableRutile) { + rutile = createMaterial("rutile", MaterialType.METAL, 6, 0.25, 4.5, 0xFFBF928B); + } + + if (Options.enableStainlessSteel) { + stainlessSteel = createAlloyMaterial("stainlesssteel", MaterialType.METAL, 6, 16, 4.5, 0xFFC5BFC1); + } + + if (Options.enableTantalum) { + tantalum = createMaterial("tantalum", MaterialType.METAL, 6.5, 17.5, 4.5, 0xFFC4BEC2); + } + + if (Options.enableTitanium) { + titanium = createSpecialMaterial("titanium", MaterialType.METAL, 6.5, 16.25, 4.5, 0xFF73787E); + } + + if (Options.enableTungsten) { + tungsten = createMaterial("tungsten", MaterialType.METAL, 7.5, 12.5, 4.5, 0xFF969696); + } + + if (Options.enableUranium) { + uranium = createRareMaterial("uranium", MaterialType.METAL, 6, 16.25, 4.5, 0xFFA7B345); + } + + if (Options.enableZirconium) { + zirconium = createMaterial("zirconium", MaterialType.METAL, 5, 3.5, 4.5, 0xFF929793); + } + + if (Options.enableBeryllium) { + beryllium = createMaterial("beryllium", MaterialType.METAL, 5.5, 2.5, 1.0, 0xFFD5D3D5); + } + + if (Options.enableBoron) { + boron = createMaterial("boron", MaterialType.METAL, 9.5, 8, 1.0, 0xFF888888); + } + + if (Options.enableThorium) { + thorium = createMaterial("thorium", MaterialType.METAL, 3, 1, 1.0, 0xFF4F5A5A); + } + + initDone = true; + } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/init/Recipes.java b/src/main/java/zone/moddev/mc/modernmetals/init/Recipes.java new file mode 100644 index 00000000..93b7ee15 --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/init/Recipes.java @@ -0,0 +1,86 @@ +package zone.moddev.mc.modernmetals.init; + +import zone.moddev.mc.modernmetals.init.Materials; +import zone.moddev.mc.modernmetals.util.Config.Options; +import com.mcmoddev.lib.data.Names; +import com.mcmoddev.lib.util.Oredicts; + +import net.minecraftforge.oredict.OreDictionary; + +/** + * + * @author Jasmine Iwanek + * + */ +public class Recipes extends com.mcmoddev.lib.init.Recipes { + + private static boolean initDone = false; + + private Recipes() { + throw new IllegalAccessError("Not a instantiable class"); + } + + /** + * + */ + public static void init() { + if (initDone) { + return; + } + + Materials.init(); + Blocks.init(); + Items.init(); + + initModSpecificRecipes(); + + initDone = true; + } + + private static void initModSpecificRecipes() { + if (Options.enableAluminum) { + String oreDictName = "Bauxite"; + + addAdditionalOredicts(Materials.aluminum, "Aluminium"); + OreDictionary.registerOre(Oredicts.ORE + oreDictName, Materials.getMaterialByName("aluminum").getBlock(Names.ORE)); + OreDictionary.registerOre(Oredicts.DUST + oreDictName, Materials.getMaterialByName("aluminum").getItem(Names.POWDER)); + OreDictionary.registerOre(Oredicts.DUST_TINY + oreDictName, Materials.getMaterialByName("aluminum").getItem(Names.SMALLPOWDER)); + OreDictionary.registerOre(Oredicts.DUST_SMALL + oreDictName, Materials.getMaterialByName("aluminum").getItem(Names.SMALLPOWDER)); + } + + if (Options.enableAluminumBrass) { + addSimpleAlloyRecipe(Materials.aluminumBrass, 2, "Aluminum", "Brass"); + addAdditionalOredicts(Materials.aluminumBrass, "AluminumBrass"); + addAdditionalOredicts(Materials.aluminumBrass, "Aluminiumbrass"); + addAdditionalOredicts(Materials.aluminumBrass, "AluminiumBrass"); + addAdditionalOredicts(Materials.aluminumBrass, "Alubrass"); + addAdditionalOredicts(Materials.aluminumBrass, "AluBrass"); + } + + if (Options.enableChromium) { + addAdditionalOredicts(Materials.chromium, "Chrome"); + } + + if (Options.enableGalvanizedSteel) { + addSimpleAlloyRecipe(Materials.galvanizedSteel, 2, "Steel", "Zinc"); + addAdditionalOredicts(Materials.galvanizedSteel, "GalvinizedSteel"); + } + + if (Options.enableNichrome) { + addSimpleAlloyRecipe(Materials.nichrome, 2, "Nickel", "Chromium"); + } + + if (Options.enableStainlessSteel) { + addSimpleAlloyRecipe(Materials.stainlessSteel, 2, "Steel", "Chromium"); + addAdditionalOredicts(Materials.stainlessSteel, "StainlessSteel"); + } + + if (Options.enableTitanium) { + addSimpleAlloyRecipe(Materials.titanium, 2, "Rutile", "Magnesium"); + } + + if (Options.enableTungsten) { + addAdditionalOredicts(Materials.tungsten, "Wolfram"); + } + } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/init/VillagerTrades.java b/src/main/java/zone/moddev/mc/modernmetals/init/VillagerTrades.java new file mode 100644 index 00000000..322ac3a5 --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/init/VillagerTrades.java @@ -0,0 +1,68 @@ +package zone.moddev.mc.modernmetals.init; + +import zone.moddev.mc.modernmetals.util.Config; + +/** + * + * @author Jasmine Iwanek + * + */ +public class VillagerTrades extends com.mcmoddev.lib.init.VillagerTrades { + + private static boolean initDone = false; + + private VillagerTrades() { + throw new IllegalAccessError("Not a instantiable class"); + } + + /** + * + */ + public static void init() { + if (initDone) { + return; + } + + Materials.init(); + Items.init(); + + if (Config.Options.enableVillagerTrades) { + if (zone.moddev.mc.modernmetals.content.ContentPolicy.active().lowFantasy()) registerRestrictedTrades(); + else registerCommonTrades(); + } + + initDone = true; + } + + /** Preserve common trade prices while omitting disallowed equipment offers. */ + private static void registerRestrictedTrades() { + final zone.moddev.mc.modernmetals.content.ContentPolicy policy=zone.moddev.mc.modernmetals.content.ContentPolicy.active(); + for(final com.mcmoddev.lib.material.MMDMaterial material:Materials.getMaterialsByMod("modernmetals")) { + if(material.isRare()) continue; + final float hardness=material.getStat(com.mcmoddev.lib.data.MaterialStats.HARDNESS); + final float value=hardness+material.getStat(com.mcmoddev.lib.data.MaterialStats.STRENGTH) + +material.getStat(com.mcmoddev.lib.data.MaterialStats.MAGICAFFINITY)+material.getToolHarvestLevel(); + final int purchase=emeraldPurchaseValue(value),sale=emeraldSaleValue(value),level=tradeLevel(value); + if(purchase>64 || sale>64) continue; + for(final com.mcmoddev.lib.data.Names form:new com.mcmoddev.lib.data.Names[]{ + com.mcmoddev.lib.data.Names.HELMET,com.mcmoddev.lib.data.Names.CHESTPLATE,com.mcmoddev.lib.data.Names.LEGGINGS, + com.mcmoddev.lib.data.Names.BOOTS,com.mcmoddev.lib.data.Names.CRACKHAMMER,com.mcmoddev.lib.data.Names.SWORD, + com.mcmoddev.lib.data.Names.AXE,com.mcmoddev.lib.data.Names.HOE,com.mcmoddev.lib.data.Names.SHOVEL,com.mcmoddev.lib.data.Names.PICKAXE}) { + if(!material.hasItem(form) || !policy.allows(material.getItemStack(form))) continue; + final boolean armor=form==com.mcmoddev.lib.data.Names.HELMET || form==com.mcmoddev.lib.data.Names.CHESTPLATE + || form==com.mcmoddev.lib.data.Names.LEGGINGS || form==com.mcmoddev.lib.data.Names.BOOTS; + final boolean weapon=form==com.mcmoddev.lib.data.Names.SWORD; + final int cost=armor?purchase+(int)(hardness/2):weapon?purchase+(int)(material.getBaseAttackDamage()/2)-1:purchase; + zone.moddev.mc.basemetals.util.VillagerTradeHelper.insertTrades(SMITH_RL, + armor?ARMOR_SMITH_ID:weapon?WEAPON_SMITH_ID:TOOL_SMITH_ID,level, + makeTradePalette(makePurchasePalette(cost,material.getItemStack(form)))); + } + if(material.hasItem(com.mcmoddev.lib.data.Names.INGOT)) { + final net.minecraft.item.ItemStack ingots=material.getItemStack(com.mcmoddev.lib.data.Names.INGOT,12); + for(final int career:new int[]{ARMOR_SMITH_ID,WEAPON_SMITH_ID,TOOL_SMITH_ID}) + zone.moddev.mc.basemetals.util.VillagerTradeHelper.insertTrades(SMITH_RL,career,level, + makeTradePalette(makePurchasePalette(purchase,ingots),makeSalePalette(sale,ingots))); + } + } + } +} diff --git a/src/main/java/com/mcmoddev/modernmetals/integration/plugins/DenseOres.java b/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/DenseOres.java similarity index 86% rename from src/main/java/com/mcmoddev/modernmetals/integration/plugins/DenseOres.java rename to src/main/java/zone/moddev/mc/modernmetals/integration/plugins/DenseOres.java index 95ce3bf3..5864cd93 100644 --- a/src/main/java/com/mcmoddev/modernmetals/integration/plugins/DenseOres.java +++ b/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/DenseOres.java @@ -1,7 +1,7 @@ -package com.mcmoddev.modernmetals.integration.plugins; +package zone.moddev.mc.modernmetals.integration.plugins; -import com.mcmoddev.modernmetals.ModernMetals; -import com.mcmoddev.modernmetals.data.MaterialNames; +import zone.moddev.mc.modernmetals.ModernMetals; +import zone.moddev.mc.modernmetals.data.MaterialNames; import com.mcmoddev.lib.util.ConfigBase.Options; import com.mcmoddev.lib.init.Materials; import com.mcmoddev.lib.integration.IIntegration; @@ -33,6 +33,8 @@ public void init() { private static void registerOres() { final String[] baseNames = new String[] { MaterialNames.ALUMINUM, + MaterialNames.BERYLLIUM, + MaterialNames.BORON, MaterialNames.CADMIUM, MaterialNames.CHROMIUM, MaterialNames.IRIDIUM, @@ -42,6 +44,7 @@ private static void registerOres() { MaterialNames.PLUTONIUM, MaterialNames.RUTILE, MaterialNames.TANTALUM, + MaterialNames.THORIUM, MaterialNames.TITANIUM, MaterialNames.TUNGSTEN, MaterialNames.URANIUM, diff --git a/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/EnderIO.java b/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/EnderIO.java new file mode 100644 index 00000000..3a8f5682 --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/EnderIO.java @@ -0,0 +1,105 @@ +package zone.moddev.mc.modernmetals.integration.plugins; + +import zone.moddev.mc.modernmetals.ModernMetals; +import zone.moddev.mc.modernmetals.util.Config.Options; +import com.mcmoddev.lib.integration.MMDPlugin; +import com.mcmoddev.lib.integration.IIntegration; + +/** + * + * @author Jasmine Iwanek + * + */ +@MMDPlugin(addonId = ModernMetals.MODID, pluginId = EnderIO.PLUGIN_MODID) +public class EnderIO extends com.mcmoddev.lib.integration.plugins.EnderIOBase implements IIntegration { + + private static boolean initDone = false; + + /** + * + */ + @Override + public void init() { + if (initDone || !zone.moddev.mc.basemetals.util.Config.Options.isModEnabled("enderio")) { + return; + } + + if (Options.enableAluminum) { + addSagMillRecipe("aluminum", null, 3600); + } + + if (Options.enableAluminumBrass) { + addSagMillRecipe("aluminumbrass", null, 3600); + } + + if (Options.enableBeryllium) addSagMillRecipe("beryllium", null, 3600); + if (Options.enableBoron) addSagMillRecipe("boron", null, 3600); + if (Options.enableThorium) addSagMillRecipe("thorium", null, 3600); + + if (Options.enableCadmium) { + addSagMillRecipe("cadmium", null, 3600); + } + + if (Options.enableChromium) { + addSagMillRecipe("chromium", null, 3600); + } + + if (Options.enableGalvanizedSteel) { + addSagMillRecipe("galvanizedsteel", null, 3600); + } + + if (Options.enableIridium) { + addSagMillRecipe("iridium", null, 3600); + } + + if (Options.enableMagnesium) { + addSagMillRecipe("magnesium", null, 3600); + } + + if (Options.enableManganese) { + addSagMillRecipe("manganese", null, 3600); + } + + if (Options.enableNichrome) { + addSagMillRecipe("nichrome", null, 3600); + } + + if (Options.enableOsmium) { + addSagMillRecipe("osmium", null, 3600); + } + + if (Options.enablePlutonium) { + addSagMillRecipe("plutonium", null, 3600); + } + + if (Options.enableRutile) { + addSagMillRecipe("rutile", null, 3600); + } + + if (Options.enableStainlessSteel) { + addSagMillRecipe("stainlesssteel", null, 3600); + } + + if (Options.enableTantalum) { + addSagMillRecipe("tantalum", null, 3600); + } + + if (Options.enableTitanium) { + addSagMillRecipe("titanium", null, 3600); + } + + if (Options.enableTungsten) { + addSagMillRecipe("tungsten", null, 3600); + } + + if (Options.enableUranium) { + addSagMillRecipe("uranium", null, 3600); + } + + if (Options.enableZirconium) { + addSagMillRecipe("zirconium", null, 3600); + } + + initDone = true; + } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/IC2.java b/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/IC2.java new file mode 100644 index 00000000..bf27759b --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/IC2.java @@ -0,0 +1,31 @@ +package zone.moddev.mc.modernmetals.integration.plugins; + +import com.mcmoddev.lib.data.Names; +import com.mcmoddev.lib.init.Materials; +import com.mcmoddev.lib.integration.IIntegration; +import com.mcmoddev.lib.integration.MMDPlugin; +import com.mcmoddev.lib.integration.plugins.IC2Base; +import com.mcmoddev.lib.material.MMDMaterial; +import com.mcmoddev.lib.util.ConfigBase.Options; +import zone.moddev.mc.modernmetals.ModernMetals; + +@MMDPlugin(addonId = ModernMetals.MODID, pluginId = IC2.PLUGIN_MODID) +public class IC2 extends IC2Base implements IIntegration { + private static boolean registered; + @Override public void init() { } + /** Recipe managers are available after IC2's pre-initialization. */ + public static void registerRecipes() { + if (registered || !Options.isModEnabled(PLUGIN_MODID)) return; + final IC2 integration = new IC2(); + for (final MMDMaterial material : Materials.getMaterialsByMod(ModernMetals.MODID)) { + if (!Materials.hasMaterial(material.getName()) || !material.hasItem(Names.CRUSHED)) continue; + integration.registerVanillaRecipes(material.getName()); + integration.addMaceratorRecipes(material.getName()); + integration.addOreWashingPlantRecipes(material.getName()); + integration.addThermalCentrifugeRecipes(material.getName()); + integration.addMetalFormerRecipes(material.getName()); + integration.addCompressorRecipes(material.getName()); + } + registered = true; + } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/Mekanism.java b/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/Mekanism.java new file mode 100644 index 00000000..dc8a5795 --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/Mekanism.java @@ -0,0 +1,68 @@ +package zone.moddev.mc.modernmetals.integration.plugins; + +import com.mcmoddev.lib.data.Names; +import com.mcmoddev.lib.init.Materials; +import com.mcmoddev.lib.integration.IIntegration; +import com.mcmoddev.lib.integration.MMDPlugin; +import com.mcmoddev.lib.integration.plugins.MekanismBase; +import com.mcmoddev.lib.material.MMDMaterial; +import com.mcmoddev.lib.util.ConfigBase.Options; +import zone.moddev.mc.modernmetals.ModernMetals; + +@MMDPlugin(addonId = ModernMetals.MODID, pluginId = Mekanism.PLUGIN_MODID) +public class Mekanism extends MekanismBase implements IIntegration { + private static boolean registered; + /** Discovery happens before the optional mod's registries are ready. */ + @Override public void init() { } + /** Called after Mekanism's initialization and before its IMC processing. */ + public static void registerRecipes() { + if (registered || !Options.isModEnabled(PLUGIN_MODID)) return; + for (final MMDMaterial material : Materials.getMaterialsByMod(ModernMetals.MODID)) { + if (!Materials.hasMaterial(material.getName()) || !material.hasOre() || !material.hasBlock(Names.ORE)) continue; + registerMaterial(material); + } + registered = true; + } + + private static void registerMaterial(final MMDMaterial material) { + final String name = material.getName(); + final mekanism.api.gas.Gas raw = gas(name, material.getCapitalizedName()); + final mekanism.api.gas.Gas clean = gas("clean" + name, "Clean" + material.getCapitalizedName()); + final net.minecraft.item.ItemStack ore = material.getBlockItemStack(Names.ORE); + if (material.hasItem(Names.POWDER)) { + mekanism.common.recipe.RecipeHandler.addEnrichmentChamberRecipe(ore, material.getItemStack(Names.POWDER, 2)); + mekanism.common.recipe.RecipeHandler.addCrusherRecipe(material.getItemStack(Names.INGOT), material.getItemStack(Names.POWDER)); + } + if (material.hasItem(Names.CLUMP)) + mekanism.common.recipe.RecipeHandler.addPurificationChamberRecipe(ore, material.getItemStack(Names.CLUMP, 3)); + if (material.hasItem(Names.POWDER_DIRTY) && material.hasItem(Names.CLUMP)) { + mekanism.common.recipe.RecipeHandler.addCrusherRecipe(material.getItemStack(Names.CLUMP), material.getItemStack(Names.POWDER_DIRTY)); + mekanism.common.recipe.RecipeHandler.addEnrichmentChamberRecipe(material.getItemStack(Names.POWDER_DIRTY), material.getItemStack(Names.POWDER)); + } + if (material.hasItem(Names.SHARD)) { + final mekanism.api.gas.Gas chloride = mekanism.api.gas.GasRegistry.getGas("hydrogenChloride"); + if (chloride == null) throw new IllegalStateException("Mekanism hydrogen chloride is unavailable after initialization"); + mekanism.common.recipe.RecipeHandler.addChemicalInjectionChamberRecipe(ore, chloride, material.getItemStack(Names.SHARD, 4)); + if (material.hasItem(Names.CLUMP)) + mekanism.common.recipe.RecipeHandler.addPurificationChamberRecipe(material.getItemStack(Names.SHARD), material.getItemStack(Names.CLUMP)); + } + if (material.hasItem(Names.CRYSTAL)) { + mekanism.common.recipe.RecipeHandler.addChemicalDissolutionChamberRecipe(ore, new mekanism.api.gas.GasStack(raw, 1000)); + mekanism.common.recipe.RecipeHandler.addChemicalWasherRecipe(new mekanism.api.gas.GasStack(raw, 1), new mekanism.api.gas.GasStack(clean, 1)); + mekanism.common.recipe.RecipeHandler.addChemicalCrystallizerRecipe(new mekanism.api.gas.GasStack(clean, 200), material.getItemStack(Names.CRYSTAL)); + if (material.hasItem(Names.SHARD)) + mekanism.common.recipe.RecipeHandler.addChemicalInjectionChamberRecipe(material.getItemStack(Names.CRYSTAL), + mekanism.api.gas.GasRegistry.getGas("hydrogenChloride"), material.getItemStack(Names.SHARD)); + } + } + + private static mekanism.api.gas.Gas gas(final String name, final String display) { + mekanism.api.gas.Gas gas = mekanism.api.gas.GasRegistry.getGas(name); + if (gas == null) { + gas = new mekanism.api.gas.Gas(name, name + "-Icon"); + gas.setUnlocalizedName("gas" + display); + mekanism.api.gas.GasRegistry.register(gas); + } + return gas; + } +} diff --git a/src/main/java/com/mcmoddev/modernmetals/integration/plugins/Thaumcraft.java b/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/Thaumcraft.java similarity index 64% rename from src/main/java/com/mcmoddev/modernmetals/integration/plugins/Thaumcraft.java rename to src/main/java/zone/moddev/mc/modernmetals/integration/plugins/Thaumcraft.java index b0b1f900..8cf998bf 100644 --- a/src/main/java/com/mcmoddev/modernmetals/integration/plugins/Thaumcraft.java +++ b/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/Thaumcraft.java @@ -1,8 +1,7 @@ -package com.mcmoddev.modernmetals.integration.plugins; +package zone.moddev.mc.modernmetals.integration.plugins; -import com.mcmoddev.modernmetals.ModernMetals; +import zone.moddev.mc.modernmetals.ModernMetals; import com.mcmoddev.lib.integration.MMDPlugin; -import com.mcmoddev.lib.util.ConfigBase.Options; import com.mcmoddev.lib.integration.IIntegration; @MMDPlugin(addonId = ModernMetals.MODID, pluginId = Thaumcraft.PLUGIN_MODID) @@ -12,7 +11,7 @@ public class Thaumcraft extends com.mcmoddev.lib.integration.plugins.ThaumcraftB @Override public void init() { - if (initDone || !Options.isModEnabled(Thaumcraft.PLUGIN_MODID)) { + if (initDone || !zone.moddev.mc.basemetals.util.Config.Options.isModEnabled("thaumcraft")) { return; } diff --git a/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/ThermalExpansion.java b/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/ThermalExpansion.java new file mode 100644 index 00000000..cfb43ede --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/ThermalExpansion.java @@ -0,0 +1,76 @@ +package zone.moddev.mc.modernmetals.integration.plugins; + +import java.util.HashMap; +import java.util.Map; + +import zone.moddev.mc.modernmetals.ModernMetals; +import zone.moddev.mc.modernmetals.init.Materials; +import zone.moddev.mc.modernmetals.util.Config.Options; +import com.mcmoddev.lib.data.Names; +import com.mcmoddev.lib.integration.IIntegration; +import com.mcmoddev.lib.integration.MMDPlugin; +import cofh.api.util.ThermalExpansionHelper; +import net.minecraft.item.ItemStack; + +@MMDPlugin( addonId = ModernMetals.MODID, pluginId = ThermalExpansion.PLUGIN_MODID ) +public class ThermalExpansion extends com.mcmoddev.lib.integration.plugins.ThermalExpansionBase implements IIntegration { + + public ThermalExpansion() { + + } + + public void init() { + if (!zone.moddev.mc.basemetals.util.Config.Options.isModEnabled(PLUGIN_MODID)) return; + Map materials = new HashMap<>(); + materials.put("aluminum", Options.enableAluminum); + materials.put("iridium", Options.enableIridium); + materials.put("beryllium", Options.enableBeryllium); + materials.put("boron", Options.enableBoron); + materials.put("thorium", Options.enableThorium); + + materials.put("aluminumbrass", Options.enableAluminumBrass); + materials.put("cadmium", Options.enableCadmium); + materials.put("chromium", Options.enableChromium); + materials.put("galvanizedsteel", Options.enableGalvanizedSteel); + materials.put("magnesium", Options.enableMagnesium); + materials.put("manganese", Options.enableManganese); + materials.put("nichrome", Options.enableNichrome); + materials.put("osmium", Options.enableOsmium); + materials.put("plutonium", Options.enablePlutonium); + materials.put("rutile", Options.enableRutile); + materials.put("stainlesssteel", Options.enableStainlessSteel); + materials.put("tantalum", Options.enableTantalum); + materials.put("titanium", Options.enableTitanium); + materials.put("tungsten", Options.enableTungsten); + materials.put("uranium", Options.enableUranium); + materials.put("zirconium", Options.enableZirconium); + + for( Map.Entry e : materials.entrySet()) { + if (!e.getValue() || !Materials.hasMaterial(e.getKey())) continue; + addFurnace(e.getKey()); + addCrucible(e.getKey()); + addPlatePress(e.getKey()); + addPressStorage(e.getKey()); + } + + if( Options.enablePlutonium ) { + ThermalExpansionHelper.addMagmaticFuel(Materials.getMaterialByName("plutonium").getFluid().getName(), 1000000); + } + + if( Options.enableUranium ) { + ThermalExpansionHelper.addMagmaticFuel(Materials.getMaterialByName("uranium").getFluid().getName(), 750000); + } + + if( Options.enableChromium && Options.enableStainlessSteel && Materials.hasMaterial("steel")) { + ThermalExpansionHelper.addSmelterRecipe(4000, new ItemStack( Materials.getMaterialByName("steel").getItem(Names.INGOT), 1), new ItemStack(Materials.getMaterialByName("chromium").getItem(Names.INGOT), 1), new ItemStack(Materials.getMaterialByName("stainlesssteel").getItem(Names.INGOT), 2)); + } + + if( Options.enableGalvanizedSteel && Materials.hasMaterial("steel") && Materials.hasMaterial("zinc")) { + ThermalExpansionHelper.addSmelterRecipe(4000, new ItemStack( Materials.getMaterialByName("steel").getItem(Names.INGOT), 1), new ItemStack(Materials.getMaterialByName("zinc").getItem(Names.INGOT), 1), new ItemStack(Materials.getMaterialByName("galvanizedsteel").getItem(Names.INGOT), 2)); + } + + if( Options.enableRutile && Options.enableMagnesium && Options.enableTitanium ) { + ThermalExpansionHelper.addSmelterRecipe(4000, new ItemStack( Materials.getMaterialByName("rutile").getItem(Names.INGOT), 1), new ItemStack( Materials.getMaterialByName("magnesium").getItem(Names.INGOT), 1), new ItemStack( Materials.getMaterialByName("titanium").getItem(Names.INGOT), 2)); + } + } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/TinkersConstruct.java b/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/TinkersConstruct.java new file mode 100644 index 00000000..4c1ce1de --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/TinkersConstruct.java @@ -0,0 +1,151 @@ +package zone.moddev.mc.modernmetals.integration.plugins; + +import zone.moddev.mc.modernmetals.ModernMetals; +import zone.moddev.mc.modernmetals.init.Materials; +import zone.moddev.mc.modernmetals.util.Config.Options; +import com.mcmoddev.lib.integration.MMDPlugin; +import com.mcmoddev.lib.integration.plugins.tinkers.TraitLocations; + +import com.mcmoddev.lib.integration.IIntegration; + +/** + * + * @author Jasmine Iwanek + * + */ +@MMDPlugin(addonId = ModernMetals.MODID, pluginId = TinkersConstruct.PLUGIN_MODID) +public class TinkersConstruct extends com.mcmoddev.lib.integration.plugins.TinkersConstructBase implements IIntegration { + + private static boolean initDone = false; + + @Override + public void init() { + if (initDone || !zone.moddev.mc.basemetals.util.Config.Options.isModEnabled(PLUGIN_MODID)) { + return; + } + if (zone.moddev.mc.modernmetals.content.ContentPolicy.active().lowFantasy()) + net.minecraftforge.common.MinecraftForge.EVENT_BUS.register(this); + + if (Options.enableAluminum) { + registry.getMaterial("aluminum", Materials.getMaterialByName("aluminum")).addTrait("lightweight").setCastable(true).settle(); + } + + if (Options.enableAluminumBrass) { + registry.getMaterial("aluminumbrass", Materials.getMaterialByName("aluminumbrass")).setCastable(true).settle(); + } + + if (Options.enableCadmium) { + registry.getMaterial("cadmium", Materials.getMaterialByName("cadmium")).setCastable(true).settle(); + } + + if (Options.enableChromium) { + registry.getMaterial("chromium", Materials.getMaterialByName("chromium")).setCastable(true).addTrait("magnetic").addTrait("magnetic2",TraitLocations.HEAD).settle(); + } + + if (Options.enableGalvanizedSteel) { + registry.getMaterial("galvanizedsteel", Materials.getMaterialByName("galvanizedsteel")).addTrait("sharp", TraitLocations.HEAD).addTrait("stiff").setCastable(true).settle(); + registry.registerAlloy("galvanizedsteel",Materials.getMaterialByName("galvanizedsteel").getFluid(), 2, "steel", 1, "zinc", 1); + } + + if (Options.enableIridium) { + registry.getMaterial("iridium", Materials.getMaterialByName("iridium")).setCastable(true).addTrait("heavy").addTrait("brittle").settle(); + } + + if (Options.enableMagnesium) { + registry.getMaterial("magnesium", Materials.getMaterialByName("magnesium")).setCastable(true).addTrait("reactive").settle(); + } + + if (Options.enableManganese) { + registry.getMaterial("manganese", Materials.getMaterialByName("manganese")).addTrait("brittle").setCastable(true).settle(); + } + + if (Options.enableNichrome) { + registry.getMaterial("nichrome", Materials.getMaterialByName("nichrome")).setCastable(true).settle(); + registry.registerAlloy("nichrome", Materials.getMaterialByName("nichrome").getFluid(), 2, "nickel", 1, "chromium", 1); + } + + if (Options.enableOsmium) { + registry.getMaterial("osmium", Materials.getMaterialByName("osmium")).setCastable(true).addTrait("heavy").addTrait("brittle").settle(); + } + + if (Options.enablePlutonium) { + registry.getMaterial("plutonium", Materials.getMaterialByName("plutonium")).addTrait("radioactive").addTrait("toxic", TraitLocations.HEAD).addTrait("toxic", TraitLocations.PROJECTILE).setCastable(true).settle(); + } + + // request is for custom modifier item to grant +50% durability + // This is not yet in the API - might require extra classes in MMDLib + if (Options.enableRutile) { + registry.getMaterial("rutile", Materials.getMaterialByName("rutile")).setCastable(true).settle(); + } + + if (Options.enableStainlessSteel) { + registry.getMaterial("stainlesssteel", Materials.getMaterialByName("stainlesssteel")).setCastable(true).addTrait("sharp", TraitLocations.HEAD).addTrait("stiff").settle(); + registry.registerAlloy("stainlesssteel", Materials.getMaterialByName("stainlesssteel").getFluid(), 2, "steel", 1, "chromium", 1); + } + + if (Options.enableTantalum) { + registry.getMaterial("tantalum", Materials.getMaterialByName("tantalum")).setCastable(true).settle(); + } + + if (Options.enableTitanium) { + registry.getMaterial("titanium", Materials.getMaterialByName("titanium")).setCastable(true).settle(); + registry.registerAlloy("titanium", Materials.getMaterialByName("titanium").getFluid(), 2, "rutile", 1, "magnesium", 1 ); + } + + if (Options.enableTungsten) { + registry.getMaterial("tungsten", Materials.getMaterialByName("tungsten")).setCastable(true).settle(); + } + + if (Options.enableUranium) { + registry.getMaterial("uranium", Materials.getMaterialByName("uranium")).setCastable(true).addTrait("poisonous", TraitLocations.HEAD).addTrait("poisonous", TraitLocations.PROJECTILE).addTrait("radioactive").settle(); + } + + if (Options.enableZirconium) { + registry.getMaterial("zirconium", Materials.getMaterialByName("zirconium")).setCastable(true).settle(); + } + + if (Options.enableBeryllium) { registry.getMaterial("beryllium", Materials.getMaterialByName("beryllium")).setCastable(true).settle(); } + + if (Options.enableBoron) { registry.getMaterial("boron", Materials.getMaterialByName("boron")).setCastable(true).settle(); } + + if (Options.enableThorium) { registry.getMaterial("thorium", Materials.getMaterialByName("thorium")).setCastable(true).settle(); } + + registry.registerAll(); + initDone = true; + } + + /** Keep molten processing while preventing restricted tool-part and ranged bypasses. */ + public static void applyContentPolicy() { + if(!com.mcmoddev.lib.util.ConfigBase.Options.isModEnabled(PLUGIN_MODID)) return; + final zone.moddev.mc.modernmetals.content.ContentPolicy policy=zone.moddev.mc.modernmetals.content.ContentPolicy.active(); + if(!policy.lowFantasy()) return; + for(final com.mcmoddev.lib.material.MMDMaterial material:Materials.getMaterialsByMod(ModernMetals.MODID)) { + final slimeknights.tconstruct.library.materials.Material tc=slimeknights.tconstruct.library.TinkerRegistry.getMaterial(material.getName()); + if(tc==slimeknights.tconstruct.library.materials.Material.UNKNOWN) continue; + tc.getAllStats().removeIf(stat->!policy.allows(material.getName(),"pickaxe") + || "bow".equals(stat.getIdentifier()) || "bowstring".equals(stat.getIdentifier())); + } + } + + @net.minecraftforge.fml.common.eventhandler.SubscribeEvent + public void tableCasting(final slimeknights.tconstruct.library.events.TinkerRegisterEvent.TableCastingRegisterEvent event) { + restrictCasting(event); + } + @net.minecraftforge.fml.common.eventhandler.SubscribeEvent + public void basinCasting(final slimeknights.tconstruct.library.events.TinkerRegisterEvent.BasinCastingRegisterEvent event) { + restrictCasting(event); + } + /** Reject fixed restricted outputs before Tinkers freezes its casting recipes. */ + private static void restrictCasting(final slimeknights.tconstruct.library.events.TinkerRegisterEvent event) { + if(!(event.getRecipe() instanceof slimeknights.tconstruct.library.smeltery.CastingRecipe)) return; + final net.minecraft.item.ItemStack output=((slimeknights.tconstruct.library.smeltery.CastingRecipe)event.getRecipe()).getResult(); + final zone.moddev.mc.modernmetals.content.ContentPolicy policy=zone.moddev.mc.modernmetals.content.ContentPolicy.active(); + if(!policy.allows(output)) {event.setCanceled(true);return;} + if(output==null || !(output.getItem() instanceof slimeknights.tconstruct.library.tools.IToolPart)) return; + final slimeknights.tconstruct.library.tools.IToolPart part=(slimeknights.tconstruct.library.tools.IToolPart)output.getItem(); + final String name=part.getMaterialID(output); + final boolean ours=Materials.getMaterialsByMod(ModernMetals.MODID).stream().anyMatch(material->material.getName().equals(name)); + if(ours && (!policy.allows(name,"pickaxe") || part.hasUseForStat("bow") || part.hasUseForStat("bowstring"))) + event.setCanceled(true); + } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/VeinMiner.java b/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/VeinMiner.java new file mode 100644 index 00000000..d35fee94 --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/integration/plugins/VeinMiner.java @@ -0,0 +1,105 @@ +package zone.moddev.mc.modernmetals.integration.plugins; + +import zone.moddev.mc.modernmetals.ModernMetals; +import zone.moddev.mc.modernmetals.util.Config.Options; +import com.mcmoddev.lib.integration.MMDPlugin; +import com.mcmoddev.lib.integration.IIntegration; + +/** + * VeinMiner Integration Plugin + * + * @author Jasmine Iwanek + * + */ +@MMDPlugin(addonId = ModernMetals.MODID, pluginId = VeinMiner.PLUGIN_MODID) +public class VeinMiner extends com.mcmoddev.lib.integration.plugins.VeinMinerBase implements IIntegration { + + private static boolean initDone = false; + + @Override + public void init() { + if (initDone || !zone.moddev.mc.basemetals.util.Config.Options.isModEnabled("veinminer")) { + return; + } + + if (Options.enableAluminum) { + addToolsForMaterial("aluminum"); + } + + if (Options.enableAluminumBrass) { + addToolsForMaterial("aluminumbrass"); + } + + if (Options.enableCadmium) { + addToolsForMaterial("cadmium"); + } + + if (Options.enableChromium) { + addToolsForMaterial("chromium"); + } + + if (Options.enableGalvanizedSteel) { + addToolsForMaterial("galvanizedsteel"); + } + + if (Options.enableIridium) { + addToolsForMaterial("iridium"); + } + + if (Options.enableMagnesium) { + addToolsForMaterial("magnesium"); + } + + if (Options.enableManganese) { + addToolsForMaterial("manganese"); + } + + if (Options.enableNichrome) { + addToolsForMaterial("nichrome"); + } + + if (Options.enableOsmium) { + addToolsForMaterial("osmium"); + } + + if (Options.enablePlutonium) { + addToolsForMaterial("plutonium"); + } + + if (Options.enableRutile) { + addToolsForMaterial("rutile"); + } + + if (Options.enableStainlessSteel) { + addToolsForMaterial("stainlesssteel"); + } + + if (Options.enableTantalum) { + addToolsForMaterial("tantalum"); + } + + if (Options.enableTitanium) { + addToolsForMaterial("titanium"); + } + + if (Options.enableTungsten) { + addToolsForMaterial("tungsten"); + } + + if (Options.enableUranium) { + addToolsForMaterial("uranium"); + } + + if (Options.enableZirconium) { + addToolsForMaterial("zirconium"); + } + + if (Options.enableBeryllium) { addToolsForMaterial("beryllium"); } + + if (Options.enableBoron) { addToolsForMaterial("boron"); } + + if (Options.enableThorium) { addToolsForMaterial("thorium"); } + + initDone = true; + } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/proxy/ClientProxy.java b/src/main/java/zone/moddev/mc/modernmetals/proxy/ClientProxy.java new file mode 100644 index 00000000..571b3ff9 --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/proxy/ClientProxy.java @@ -0,0 +1,86 @@ +package zone.moddev.mc.modernmetals.proxy; + +import zone.moddev.mc.modernmetals.ModernMetals; +import zone.moddev.mc.modernmetals.client.ModernMetalsConfigScreen; +import zone.moddev.mc.orespawn.api.client.WorldSettingsExtensionRegistry; +import zone.moddev.mc.modernmetals.init.Blocks; +import zone.moddev.mc.modernmetals.init.Fluids; +import zone.moddev.mc.modernmetals.init.Items; + +import net.minecraft.block.*; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.ItemModelMesher; +import net.minecraft.client.renderer.block.model.ModelBakery; +import net.minecraft.client.renderer.block.model.ModelResourceLocation; +import net.minecraft.client.renderer.block.statemap.StateMapperBase; +import net.minecraft.item.Item; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.model.ModelLoader; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; + +/** + * Modern Metals Client Proxy + * + * @author Jasmine Iwanek + * + */ +public class ClientProxy extends CommonProxy { + + @Override + public void preInit(FMLPreInitializationEvent event) { + super.preInit(event); + for (final String name : Fluids.getFluidBlockRegistry().keySet()) { + final Block block = Fluids.getFluidBlockByName(name); + final Item item = Item.getItemFromBlock(block); + if (!item.getRegistryName().getResourceDomain().equals(ModernMetals.MODID)) + continue; + final ModelResourceLocation fluidModelLocation = new ModelResourceLocation(item.getRegistryName().getResourceDomain() + ":" + name, "fluid"); + ModelBakery.registerItemVariants(item); + ModelLoader.setCustomMeshDefinition(item, stack -> fluidModelLocation); + ModelLoader.setCustomStateMapper(block, new StateMapperBase() { + @Override + protected ModelResourceLocation getModelResourceLocation(IBlockState state) { + return fluidModelLocation; + } + }); + } + } + + @Override + public void init(FMLInitializationEvent event) { + super.init(event); + WorldSettingsExtensionRegistry.registerConfigScreen(ModernMetals.MODID, ModernMetalsConfigScreen::new); + + for (final String name : Items.getItemRegistry().keySet()) { + registerRenderOuter(Items.getItemByName(name)); + } + + for (final String name : Blocks.getBlockRegistry().keySet()) { + registerRenderOuter(Blocks.getBlockByName(name)); + } + } + + public void registerRenderOuter(Item item) { + if (item != null) { + registerRender(item, Items.getNameOfItem(item)); + } + } + + public void registerRenderOuter(Block block) { + if ((block instanceof BlockDoor) || (block instanceof BlockSlab)) + return; // do not add door blocks or slabs + + if (block != null) { + registerRender(Item.getItemFromBlock(block), Blocks.getNameOfBlock(block)); + } + } + + public void registerRender(Item item, String name) { + final ItemModelMesher itemModelMesher = Minecraft.getMinecraft().getRenderItem().getItemModelMesher(); + if (!item.getRegistryName().getResourceDomain().equals(ModernMetals.MODID)) + return; + itemModelMesher.register(item, 0, new ModelResourceLocation(new ResourceLocation(item.getRegistryName().getResourceDomain(), name), "inventory")); + } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/proxy/CommonProxy.java b/src/main/java/zone/moddev/mc/modernmetals/proxy/CommonProxy.java new file mode 100644 index 00000000..542f4f26 --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/proxy/CommonProxy.java @@ -0,0 +1,63 @@ +package zone.moddev.mc.modernmetals.proxy; + +import zone.moddev.mc.modernmetals.content.ContentModeNetwork; +import zone.moddev.mc.modernmetals.compat.LegacyRegistryMappings; +import zone.moddev.mc.modernmetals.data.MaterialNames; +import zone.moddev.mc.modernmetals.init.*; +import zone.moddev.mc.modernmetals.util.Config; +import zone.moddev.mc.modernmetals.util.EventHandler; +import com.mcmoddev.lib.integration.IntegrationManager; + +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLInterModComms; +import net.minecraftforge.fml.common.event.FMLMissingMappingsEvent; +import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; + +/** + * Modern Metals Common Proxy + * + * @author Jasmine Iwanek + * + */ +public class CommonProxy { + + public void preInit(FMLPreInitializationEvent event) { + + Config.init(); + ContentModeNetwork.register(); + + Materials.init(); + Fluids.init(); + ItemGroups.init(); + Blocks.init(); + Items.init(); + VillagerTrades.init(); + + IntegrationManager.INSTANCE.preInit(event); + } + + public void onRemap(FMLMissingMappingsEvent event) { + LegacyRegistryMappings.remap(event); + } + + public void init(FMLInitializationEvent event) { + zone.moddev.mc.modernmetals.worldgen.ModernMetalsWorldgenProvider.enqueue(); + if (net.minecraftforge.fml.common.Loader.isModLoaded("mekanism")) + zone.moddev.mc.modernmetals.integration.plugins.Mekanism.registerRecipes(); + if (net.minecraftforge.fml.common.Loader.isModLoaded("ic2")) + zone.moddev.mc.modernmetals.integration.plugins.IC2.registerRecipes(); + Recipes.init(); + + Achievements.init(); + + MinecraftForge.EVENT_BUS.register(new EventHandler()); + + ItemGroups.setupIcons(MaterialNames.ALUMINUM); + } + + public void postInit(FMLPostInitializationEvent event) { + Config.postInit(); + } +} diff --git a/src/main/java/com/mcmoddev/modernmetals/proxy/ServerProxy.java b/src/main/java/zone/moddev/mc/modernmetals/proxy/ServerProxy.java similarity index 62% rename from src/main/java/com/mcmoddev/modernmetals/proxy/ServerProxy.java rename to src/main/java/zone/moddev/mc/modernmetals/proxy/ServerProxy.java index 1f889d39..b3e68984 100644 --- a/src/main/java/com/mcmoddev/modernmetals/proxy/ServerProxy.java +++ b/src/main/java/zone/moddev/mc/modernmetals/proxy/ServerProxy.java @@ -1,4 +1,4 @@ -package com.mcmoddev.modernmetals.proxy; +package zone.moddev.mc.modernmetals.proxy; /** * Modern Metals Server Proxy @@ -7,5 +7,4 @@ * */ public class ServerProxy extends CommonProxy { - // Nothing to see here people } diff --git a/src/main/java/zone/moddev/mc/modernmetals/util/Config.java b/src/main/java/zone/moddev/mc/modernmetals/util/Config.java new file mode 100644 index 00000000..ae3f4b3a --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/util/Config.java @@ -0,0 +1,145 @@ +package zone.moddev.mc.modernmetals.util; + +import java.io.*; +import java.nio.charset.Charset; +import java.nio.file.*; +import java.util.*; + +import zone.moddev.mc.modernmetals.ModernMetals; +import com.mcmoddev.lib.registry.CrusherRecipeRegistry; + +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.fml.client.event.ConfigChangedEvent; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.MissingModsException; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.versioning.ArtifactVersion; +import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion; + +/** + * @author p455w0rd + * + */ +public class Config { + + private static Configuration configuration; + private static final String CONFIG_FILE = "config/ModernMetals.cfg"; + private static final String MATERIALS_CAT = "Metals"; + + @SubscribeEvent + public void onConfigChange(ConfigChangedEvent.OnConfigChangedEvent e) { + if (e.getModID().equals(ModernMetals.MODID)) { + configuration.save(); // Registry switches take effect on the next game start. + } + } + + public static void init() { + if (configuration == null) { + configuration = new Configuration(new File(CONFIG_FILE)); + MinecraftForge.EVENT_BUS.register(new Config()); + } + zone.moddev.mc.modernmetals.content.ContentPolicy.initialize(contentMode(configuration)); + Options.enableVillagerTrades = villagerTrades(configuration); + + // METALS + Options.enableThorium = material("thorium", "EnableThorium", "Thorium"); + Options.enableBoron = material("boron", "EnableBoron", "Boron"); + Options.enableBeryllium = material("beryllium", "EnableBeryllium", "Beryllium"); + Options.enableAluminum = material("aluminum", "EnableAluminum", "Aluminum"); + Options.enableAluminumBrass = material("aluminumbrass", "EnableAluminumBrass", "Aluminum Brass"); + Options.enableCadmium = material("cadmium", "EnableCadmium", "Cadmium"); + Options.enableChromium = material("chromium", "EnableChromium", "Chromium"); + Options.enableGalvanizedSteel = material("galvanizedsteel", "EnableGalvanizedSteel", "Galvanized Steel"); + Options.enableIridium = material("iridium", "EnableIridium", "Iridium"); + Options.enableMagnesium = material("magnesium", "EnableMagnesium", "Magnesium"); + Options.enableManganese = material("manganese", "EnableManganese", "Manganese"); + Options.enableNichrome = material("nichrome", "EnableNichrome", "Nichrome"); + Options.enableOsmium = material("osmium", "EnableOsmium", "Osmium"); + Options.enablePlutonium = material("plutonium", "EnablePlutonium", "Plutonium"); + Options.enableRutile = material("rutile", "EnableRutile", "Rutile"); + Options.enableStainlessSteel = material("stainlesssteel", "EnableStainlessSteel", "Stainless Steel"); + Options.enableTantalum = material("tantalum", "EnableTantalum", "Tantalum"); + Options.enableTitanium = material("titanium", "EnableTitanium", "Titanium"); + Options.enableTungsten = material("tungsten", "EnableTungsten", "Tungsten"); + Options.enableUranium = material("uranium", "EnableUranium", "Uranium"); + Options.enableZirconium = material("zirconium", "EnableZirconium", "Zirconium"); + + if (configuration.hasChanged()) { + configuration.save(); + } + } + + /** Configuration values are persisted immediately; live registries are never rebuilt. */ + public static Configuration getConfiguration() { + if (configuration == null) init(); + return configuration; + } + + /** Define the restart-required selector and normalize unsupported file values. */ + static String contentMode(final Configuration config) { + final net.minecraftforge.common.config.Property property = config.get("General", "contentMode", + "high_fantasy", "Fantasy level for Modern Metals recipes and equipment. Requires a game restart."); + property.setValidValues(new String[]{"high_fantasy", "low_fantasy"}); + property.setLanguageKey("config.modernmetals.content_mode"); + property.setRequiresMcRestart(true); + final String mode = zone.moddev.mc.modernmetals.content.ContentPolicy.forMode(property.getString()) + .lowFantasy() ? "low_fantasy" : "high_fantasy"; + property.set(mode); + return mode; + } + + static boolean villagerTrades(final Configuration config) { + final net.minecraftforge.common.config.Property property = config.get("General", "enableVillagerTrades", + true, "Let blacksmiths trade Modern Metals ingots and equipment. Requires a game restart."); + property.setLanguageKey("config.modernmetals.villager_trades"); + property.setRequiresMcRestart(true); + return property.getBoolean(); + } + + private static boolean material(final String name, final String key, final String display) { + final net.minecraftforge.common.config.Property property = configuration.get(MATERIALS_CAT, + key, true, "Enable " + display + " Items and Materials. Requires a game restart."); + property.setRequiresMcRestart(true); + property.setLanguageKey("config.modernmetals.enable_" + name); + final boolean enabled = property.getBoolean(); + com.mcmoddev.lib.util.ConfigBase.Options.materialEnabled(name, enabled); + com.mcmoddev.lib.util.ConfigBase.Options.fluidEnabled(name, enabled); + return enabled; + } + + public static class Options { + public static boolean enableVillagerTrades = true; + + // MATERIALS + public static boolean enableThorium = true; + public static boolean enableBoron = true; + public static boolean enableBeryllium = true; + public static boolean enableAluminum = true; + public static boolean enableAluminumBrass = true; + public static boolean enableCadmium= true; + public static boolean enableChromium = true; + public static boolean enableGalvanizedSteel = true; + public static boolean enableIridium = true; + public static boolean enableMagnesium = true; + public static boolean enableManganese = true; + public static boolean enableNichrome= true; + public static boolean enableOsmium = true; + public static boolean enablePlutonium = true; + public static boolean enableRutile = true; + public static boolean enableStainlessSteel = true; + public static boolean enableTantalum = true; + public static boolean enableTitanium = true; + public static boolean enableTungsten = true; + public static boolean enableUranium = true; + public static boolean enableZirconium = true; + + private Options() { + throw new IllegalAccessError("Not a instantiable class"); + } + } + + public static void postInit() { + CrusherRecipeRegistry.getInstance().clearCache(); + } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/util/EventHandler.java b/src/main/java/zone/moddev/mc/modernmetals/util/EventHandler.java new file mode 100644 index 00000000..cd784143 --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/util/EventHandler.java @@ -0,0 +1,54 @@ +package zone.moddev.mc.modernmetals.util; + +import zone.moddev.mc.modernmetals.init.Achievements; +import zone.moddev.mc.modernmetals.init.Materials; + +import com.mcmoddev.lib.material.IMMDObject; +import com.mcmoddev.lib.material.MMDMaterial; +import com.mcmoddev.lib.item.ItemMMDIngot; + +import net.minecraft.item.Item; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent.ItemSmeltedEvent; + +public class EventHandler { + +/* + @SubscribeEvent + void event(ItemCraftedEvent event) { + final Item item = event.crafting.getItem(); + if (item instanceof IMMDObject) { + final MMDMaterial material = ((IMMDObject) item).getMaterial(); + if (zone.moddev.mc.basemetals.util.Config.Options.enableAchievements) { + if (item instanceof ItemMetalBlend) { +// event.player.addStat(Achievements.metallurgy, 1); + } + } + } + } +*/ + + @SubscribeEvent + void event(ItemSmeltedEvent event) { + final Item item = event.smelting.getItem(); + if (item instanceof IMMDObject) { + final MMDMaterial material = ((IMMDObject) item).getMMDMaterial(); + if (zone.moddev.mc.basemetals.util.Config.Options.enableAchievements()) { + if (item instanceof ItemMMDIngot) { + // event.player.addStat(Achievements.this_is_new, 1); + if (material == Materials.aluminumBrass) { + event.player.addStat(Achievements.aluminumBrassMaker, 1); + } else if (material == Materials.galvanizedSteel) { + event.player.addStat(Achievements.galvanizedSteelMaker, 1); + } else if (material == Materials.nichrome) { + event.player.addStat(Achievements.nichromeMaker, 1); + } else if (material == Materials.stainlessSteel) { + event.player.addStat(Achievements.stainlessSteelMaker, 1); + } else if (material == Materials.titanium) { + event.player.addStat(Achievements.titaniumMaker, 1); + } + } + } + } + } +} diff --git a/src/main/java/zone/moddev/mc/modernmetals/worldgen/ModernMetalsWorldgenProvider.java b/src/main/java/zone/moddev/mc/modernmetals/worldgen/ModernMetalsWorldgenProvider.java new file mode 100644 index 00000000..90260f0a --- /dev/null +++ b/src/main/java/zone/moddev/mc/modernmetals/worldgen/ModernMetalsWorldgenProvider.java @@ -0,0 +1,85 @@ +package zone.moddev.mc.modernmetals.worldgen; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.registry.ForgeRegistries; +import zone.moddev.mc.modernmetals.ModernMetals; +import zone.moddev.mc.orespawn.api.GeologyFamily; +import zone.moddev.mc.orespawn.api.OreDimensionSelector; +import zone.moddev.mc.orespawn.api.OrePattern; +import zone.moddev.mc.orespawn.api.OreSpawnApi; +import zone.moddev.mc.orespawn.api.WorldgenProvider; + +/** Native declarations only; OreSpawn resolves, bakes and executes generation. */ +public final class ModernMetalsWorldgenProvider { + private static final Ore[] ORES = { + new Ore("boron", 0, 31, 8D, 8, 8, true), + new Ore("thorium", 0, 31, 8D, 8, 4, true), + new Ore("beryllium", 0, 127, 20D, 5, 3, true), + new Ore("aluminum", 0, 95, 8D, 10, 8, true), + new Ore("cadmium", 0, 95, 8D, 4, 8, true), + new Ore("chromium", 0, 31, 3D, 2, 4, true), + new Ore("iridium", 0, 63, 5D, 6, 4, true), + new Ore("magnesium", 0, 95, 8D, 6, 4, true), + new Ore("manganese", 0, 63, 6D, 6, 4, true), + new Ore("osmium", 0, 95, 8D, 10, 4, true), + new Ore("plutonium", 0, 31, 4D, 2, 4, true), + new Ore("rutile", 0, 63, 6D, 6, 4, true), + new Ore("tantalum", 0, 63, 6D, 6, 4, true), + new Ore("tungsten", 0, 31, 6D, 4, 4, true), + new Ore("uranium", 0, 31, 6D, 2, 4, true), + new Ore("zirconium", 0, 63, 6D, 8, 4, true), + new Ore("titanium", 0, 63, 6D, 6, 4, false) + }; + private ModernMetalsWorldgenProvider() { } + public static void enqueue() { + build(ForgeRegistries.BLOCKS::containsKey).ifPresent(provider -> { + if (!OreSpawnApi.enqueue(provider)) throw new IllegalStateException("Modern Metals provider registered twice"); + }); + for (final Ore ore : ORES) { + final int minimum = ore.size - ore.variation; + if (minimum < 1 && ForgeRegistries.BLOCKS.containsKey(id("modernmetals", ore.name + "_ore"))) { + ModernMetals.logger.info("OreSpawn native quantity minimum for " + ore.name + + " is clamped from " + minimum + " to 1, matching the legacy importer"); + } + } + } + public static Optional build(final Predicate available) { + final List ores = new ArrayList<>(); + for (final Ore rule : ORES) { + final ResourceLocation block = id("modernmetals", rule.name + "_ore"); + if (!available.test(block)) continue; + final WorldgenProvider.OreDefinition.Builder ore = WorldgenProvider.OreDefinition + .builder(id("modernmetals", "legacy/" + rule.name + "_ore"), block) + .enabled(rule.enabled).nativeGeneration(false).retrogen(false) + .material(id("orespawn", rule.name)).output(block, 100D); + ore.dimensionSelector(OreDimensionSelector.ALL_EXCEPT_NETHER_AND_END, placement -> { + placement.yRange(rule.minY, rule.maxY).attempts(rule.frequency) + .quantityRange(Math.max(1, rule.size - rule.variation), Math.min(64, rule.size + rule.variation - 1)) + .pattern(OrePattern.DEFAULT).placementChannel(id("orespawn", "standard")) + .hostBlock(id("minecraft", "stone")).hostBlock(id("minecraft", "netherrack")) + .hostBlock(id("minecraft", "end_stone")); + for (final GeologyFamily family : GeologyFamily.values()) placement.hostFamily(family); + }); + ores.add(ore.build()); + } + if (ores.isEmpty()) return Optional.empty(); + final WorldgenProvider.Builder provider = WorldgenProvider.builder("modernmetals", 1); + ores.forEach(provider::ore); + return Optional.of(provider.build()); + } + private static ResourceLocation id(final String namespace, final String path) { return new ResourceLocation(namespace, path); } + private static final class Ore { + final String name; + final int minY, maxY, size, variation; + final double frequency; + final boolean enabled; + Ore(String name, int minY, int maxY, double frequency, int size, int variation, boolean enabled) { + this.name = name; this.minY = minY; this.maxY = maxY; this.frequency = frequency; + this.size = size; this.variation = variation; this.enabled = enabled; + } + } +} diff --git a/src/main/resources/META-INF/modernmetals-legacy-api.list b/src/main/resources/META-INF/modernmetals-legacy-api.list new file mode 100644 index 00000000..ad342c62 --- /dev/null +++ b/src/main/resources/META-INF/modernmetals-legacy-api.list @@ -0,0 +1,25 @@ +data/MaterialNames +init/Achievements +init/Blocks +init/Fluids +init/ItemGroups +init/Items +init/Materials +init/Recipes +init/VillagerTrades +integration/plugins/EnderIO +integration/plugins/IC2 +integration/plugins/Mekanism +integration/plugins/Thaumcraft +integration/plugins/ThermalExpansion +integration/plugins/TinkersConstruct +integration/plugins/VeinMiner +ModernMetals +proxy/ClientProxy +proxy/CommonProxy +proxy/ServerProxy +util/Config +util/EventHandler +data.AchievementNames +data.TraitNames +integration.plugins.DenseOres diff --git a/src/main/resources/assets/modernmetals/lang/de_DE.lang b/src/main/resources/assets/modernmetals/lang/de_DE.lang index 815157a6..7227e1b6 100644 --- a/src/main/resources/assets/modernmetals/lang/de_DE.lang +++ b/src/main/resources/assets/modernmetals/lang/de_DE.lang @@ -36,38 +36,38 @@ item.modernmetals.aluminum_smallpowder.name=Kleiner Aluminiumpulverhaufen item.modernmetals.aluminum_sword.name=Aluminiumschwert item.modernmetals.aluminum_trapdoor.name=Aluminiumfalltür -material.aluminumbrass.name=Duraluminium -item.modernmetals.aluminumbrass_arrow.name=Duraluminiumpfeil -item.modernmetals.aluminumbrass_axe.name=Duraluminiumaxt -item.modernmetals.aluminumbrass_bars.name=Duraluminiumgitter -item.modernmetals.aluminumbrass_blend.name=Duraluminiumgemisch -item.modernmetals.aluminumbrass_block.name=Duraluminiumblock -item.modernmetals.aluminumbrass_boots.name=Duraluminiumstiefel -item.modernmetals.aluminumbrass_bolt.name=Duraluminiumbolzen -item.modernmetals.aluminumbrass_bow.name=Duraluminiumbogen -item.modernmetals.aluminumbrass_chestplate.name=Duraluminiumbrustpanzer -item.modernmetals.aluminumbrass_crackhammer.name=Duraluminiumbruchhammer -item.modernmetals.aluminumbrass_crossbow.name=Duraluminiumarmbrust -item.modernmetals.aluminumbrass_door.name=Duraluminiumtür -item.modernmetals.aluminumbrass_fishing_rod.name=Duraluminiumangel -item.modernmetals.aluminumbrass_gear.name=Duraluminiumzahnrad -item.modernmetals.aluminumbrass_helmet.name=Duraluminiumhelm -item.modernmetals.aluminumbrass_hoe.name=Duraluminiumhacke -item.modernmetals.aluminumbrass_horse_armor.name=Duraluminiumpferderüstung -item.modernmetals.aluminumbrass_ingot.name=Duraluminiumbarren -item.modernmetals.aluminumbrass_leggings.name=Duraluminiumbeinrüstung -item.modernmetals.aluminumbrass_nugget.name=Duraluminiumnugget -item.modernmetals.aluminumbrass_ore.name=Duraluminiumerz -item.modernmetals.aluminumbrass_pickaxe.name=Duraluminiumspitzhacke -item.modernmetals.aluminumbrass_powder.name=Duraluminiumpulver -item.modernmetals.aluminumbrass_rod.name=Duraluminiumstange -item.modernmetals.aluminumbrass_shears.name=Duraluminiumschere -item.modernmetals.aluminumbrass_shield.name=Duraluminiumschild -item.modernmetals.aluminumbrass_shovel.name=Duraluminiumschaufel -item.modernmetals.aluminumbrass_smallblend.name=Kleiner Duraluminiumgemischhaufen -item.modernmetals.aluminumbrass_smallpowder.name=Kleiner Duraluminiumpulverhaufen -item.modernmetals.aluminumbrass_sword.name=Duraluminiumschwert -item.modernmetals.aluminumbrass_trapdoor.name=Duraluminiumfalltür +material.aluminumbrass.name=Aluminiummessing +item.modernmetals.aluminumbrass_arrow.name=Aluminiummessingpfeil +item.modernmetals.aluminumbrass_axe.name=Aluminiummessingaxt +item.modernmetals.aluminumbrass_bars.name=Aluminiummessinggitter +item.modernmetals.aluminumbrass_blend.name=Aluminiummessinggemisch +item.modernmetals.aluminumbrass_block.name=Aluminiummessingblock +item.modernmetals.aluminumbrass_boots.name=Aluminiummessingstiefel +item.modernmetals.aluminumbrass_bolt.name=Aluminiummessingbolzen +item.modernmetals.aluminumbrass_bow.name=Aluminiummessingbogen +item.modernmetals.aluminumbrass_chestplate.name=Aluminiummessingbrustpanzer +item.modernmetals.aluminumbrass_crackhammer.name=Aluminiummessingbruchhammer +item.modernmetals.aluminumbrass_crossbow.name=Aluminiummessingarmbrust +item.modernmetals.aluminumbrass_door.name=Aluminiummessingtür +item.modernmetals.aluminumbrass_fishing_rod.name=Aluminiummessingangel +item.modernmetals.aluminumbrass_gear.name=Aluminiummessingzahnrad +item.modernmetals.aluminumbrass_helmet.name=Aluminiummessinghelm +item.modernmetals.aluminumbrass_hoe.name=Aluminiummessinghacke +item.modernmetals.aluminumbrass_horse_armor.name=Aluminiummessingpferderüstung +item.modernmetals.aluminumbrass_ingot.name=Aluminiummessingbarren +item.modernmetals.aluminumbrass_leggings.name=Aluminiummessingbeinrüstung +item.modernmetals.aluminumbrass_nugget.name=Aluminiummessingnugget +item.modernmetals.aluminumbrass_ore.name=Aluminiummessingerz +item.modernmetals.aluminumbrass_pickaxe.name=Aluminiummessingspitzhacke +item.modernmetals.aluminumbrass_powder.name=Aluminiummessingpulver +item.modernmetals.aluminumbrass_rod.name=Aluminiummessingstange +item.modernmetals.aluminumbrass_shears.name=Aluminiummessingschere +item.modernmetals.aluminumbrass_shield.name=Aluminiummessingschild +item.modernmetals.aluminumbrass_shovel.name=Aluminiummessingschaufel +item.modernmetals.aluminumbrass_smallblend.name=Kleiner Aluminiummessinggemischhaufen +item.modernmetals.aluminumbrass_smallpowder.name=Kleiner Aluminiummessingpulverhaufen +item.modernmetals.aluminumbrass_sword.name=Aluminiummessingschwert +item.modernmetals.aluminumbrass_trapdoor.name=Aluminiummessingfalltür material.antimony.name=Antimon item.modernmetals.antimony_arrow.name=Antimonpfeil @@ -315,7 +315,7 @@ item.modernmetals.manganese_smallpowder.name=Kleiner Manganpulverhaufen item.modernmetals.manganese_sword.name=Manganschwert item.modernmetals.manganese_trapdoor.name=Manganfalltür -material.nichrome.name=Nickel-Chrom- +material.nichrome.name=Nickel-Chrom-Legierung item.modernmetals.nichrome_arrow.name=Nickel-Chrom-Pfeil item.modernmetals.nichrome_axe.name=Nickel-Chrom-Axt item.modernmetals.nichrome_bars.name=Nickel-Chrom-Gitter @@ -677,12 +677,12 @@ tile.modernmetals.aluminum_ore.name=Aluminiumerz tile.modernmetals.aluminum_plate.name=Aluminiumblech tile.modernmetals.aluminum_trapdoor.name=Aluminiumfalltür -tile.modernmetals.aluminumbrass_bars.name=Duraluminiumgitter -tile.modernmetals.aluminumbrass_block.name=Duraluminium -tile.modernmetals.aluminumbrass_door.name=Duraluminiumtür -tile.modernmetals.aluminumbrass_ore.name=Duraluminiumerz -tile.modernmetals.aluminumbrass_plate.name=Duraluminiumblech -tile.modernmetals.aluminumbrass_trapdoor.name=Duraluminiumfalltür +tile.modernmetals.aluminumbrass_bars.name=Aluminiummessinggitter +tile.modernmetals.aluminumbrass_block.name=Aluminiummessing +tile.modernmetals.aluminumbrass_door.name=Aluminiummessingtür +tile.modernmetals.aluminumbrass_ore.name=Aluminiummessingerz +tile.modernmetals.aluminumbrass_plate.name=Aluminiummessingblech +tile.modernmetals.aluminumbrass_trapdoor.name=Aluminiummessingfalltür tile.modernmetals.cadmium_bars.name=Cadmiumgitter tile.modernmetals.cadmium_block.name=Cadmium @@ -834,19 +834,19 @@ tooltip.tungsten.armor=Nichts Besonderes tooltip.uranium.armor=Nichts Besonderes tooltip.zirconium.armor=Nichts Besonderes -achievement.aluminumbrass_maker=Duraluminium +achievement.aluminumbrass_maker=Aluminiummessing achievement.galvanized_steel_maker=Verzinkter Stahl achievement.nichrome_maker=Nickel-Chrom- achievement.stainless_steel_maker=Edelstahl achievement.titanium_maker=Titan -achievement.aluminumbrass_maker.desc=Kombiniere Aluminiumpulver und Kupferpulver, um Duraluminium herzustellen +achievement.aluminumbrass_maker.desc=Kombiniere Aluminiumpulver und Kupferpulver, um Aluminiummessing herzustellen achievement.galvanized_steel_maker.desc=Kombininiere Stahlpulver und Zinkpulver, um verzinkten Stahl herzustellen achievement.nichrome_maker.desc=Kombiniere Nickelpulver und Chrompulver, um Nickel-Chrom herzustellen achievement.stainless_steel_maker.desc=Kombiniere Stahlpulver und Chrompulver, um Edelstahl herzustellen achievement.titanium_maker.desc=Kombiniere Rutilpulver und Magnesiumpulver, um Titan herzustellen fluid.modernmetals.aluminum=Geschmolzenes Aluminium -fluid.modernmetals.aluminumbrass=Geschmolzenes Duraluminium +fluid.modernmetals.aluminumbrass=Geschmolzenes Aluminiummessing fluid.modernmetals.cadmium=Geschmolzenes Cadmium fluid.modernmetals.chromium=Geschmolzenes Chrom fluid.modernmetals.galvanizedsteel=Geschmolzener, vernickelter Stahl @@ -865,7 +865,7 @@ fluid.modernmetals.uranium=Geschmolzenes Uran fluid.modernmetals.zirconium=Geschmolzenes Zirkon fluid.modernmetals.aluminum.name=Geschmolzenes Aluminium -fluid.modernmetals.aluminumbrass.name=Geschmolzenes Duraluminium +fluid.modernmetals.aluminumbrass.name=Geschmolzenes Aluminiummessing fluid.modernmetals.cadmium.name=Geschmolzenes Cadmium fluid.modernmetals.chromium.name=Geschmolzenes Chrom fluid.modernmetals.galvanizedsteel.name=Geschmolzenes Verzinkter Stahl @@ -884,7 +884,7 @@ fluid.modernmetals.uranium.name=Geschmolzenes Uran fluid.modernmetals.zirconium.name=Geschmolzenes Zirkon tile.modernmetals.aluminum.name=Geschmolzenes Aluminium -tile.modernmetals.aluminumbrass.name=Geschmolzenes Duraluminium +tile.modernmetals.aluminumbrass.name=Geschmolzenes Aluminiummessing tile.modernmetals.cadmium.name=Geschmolzenes Cadmium tile.modernmetals.chromium.name=Geschmolzenes Chrom tile.modernmetals.galvanizedsteel.name=Geschmolzenes Verzinkter Stahl @@ -925,27 +925,27 @@ tile.modernmetals.aluminum_stairs.name=Aluminiumtreppe tile.modernmetals.aluminum_wall.name=Aluminiummauer -item.modernmetals.aluminumbrass_clump.name=Duraluminiumklumpen -item.modernmetals.aluminumbrass_crushed.name=Zerkleinertes Duraluminiumerz -item.modernmetals.aluminumbrass_crushed_purified.name=Gereinigtes, zerkleinertes Duraluminiumerz -item.modernmetals.aluminumbrass_crystal.name=Duraluminiumkristall -item.modernmetals.aluminumbrass_dense_plate.name=Verdichtetes Duraluminiumblech -item.modernmetals.aluminumbrass_powder_dirty.name=Verunreinigtes Duraluminiumpulver -item.modernmetals.aluminumbrass_shard.name=Duraluminiumsplitter +item.modernmetals.aluminumbrass_clump.name=Aluminiummessingklumpen +item.modernmetals.aluminumbrass_crushed.name=Zerkleinertes Aluminiummessingerz +item.modernmetals.aluminumbrass_crushed_purified.name=Gereinigtes, zerkleinertes Aluminiummessingerz +item.modernmetals.aluminumbrass_crystal.name=Aluminiummessingkristall +item.modernmetals.aluminumbrass_dense_plate.name=Verdichtetes Aluminiummessingblech +item.modernmetals.aluminumbrass_powder_dirty.name=Verunreinigtes Aluminiummessingpulver +item.modernmetals.aluminumbrass_shard.name=Aluminiummessingsplitter -item.modernmetals.aluminumbrass_button.name=Duraluminiumschalter -item.modernmetals.aluminumbrass_lever.name=Duraluminiumhebel -item.modernmetals.aluminumbrass_pressure_plate.name=Duraluminiumdruckplatte -item.modernmetals.aluminumbrass_slab.name=Duraluminiumfliese -item.modernmetals.aluminumbrass_stairs.name=Duraluminiumtreppe -item.modernmetals.aluminumbrass_wall.name=Duraluminiummauer +item.modernmetals.aluminumbrass_button.name=Aluminiummessingschalter +item.modernmetals.aluminumbrass_lever.name=Aluminiummessinghebel +item.modernmetals.aluminumbrass_pressure_plate.name=Aluminiummessingdruckplatte +item.modernmetals.aluminumbrass_slab.name=Aluminiummessingfliese +item.modernmetals.aluminumbrass_stairs.name=Aluminiummessingtreppe +item.modernmetals.aluminumbrass_wall.name=Aluminiummessingmauer -tile.modernmetals.aluminumbrass_button.name=Duraluminiumschalter -tile.modernmetals.aluminumbrass_lever.name=Duraluminiumhebel -tile.modernmetals.aluminumbrass_pressure_plate.name=Duraluminiumdruckplatte -tile.modernmetals.aluminumbrass_slab.name=Duraluminiumfliese -tile.modernmetals.aluminumbrass_stairs.name=Duraluminiumtreppe -tile.modernmetals.aluminumbrass_wall.name=Duraluminiummauer +tile.modernmetals.aluminumbrass_button.name=Aluminiummessingschalter +tile.modernmetals.aluminumbrass_lever.name=Aluminiummessinghebel +tile.modernmetals.aluminumbrass_pressure_plate.name=Aluminiummessingdruckplatte +tile.modernmetals.aluminumbrass_slab.name=Aluminiummessingfliese +tile.modernmetals.aluminumbrass_stairs.name=Aluminiummessingtreppe +tile.modernmetals.aluminumbrass_wall.name=Aluminiummessingmauer item.modernmetals.cadmium_clump.name=Cadmiumklumpen @@ -1313,4 +1313,226 @@ tile.modernmetals.zirconium_lever.name=Zirkonhebel tile.modernmetals.zirconium_pressure_plate.name=Zirkondruckplatte tile.modernmetals.zirconium_slab.name=Zirkonfliese tile.modernmetals.zirconium_stairs.name=Zirkontreppe -tile.modernmetals.zirconium_wall.name=Zirkonmauer \ No newline at end of file +tile.modernmetals.zirconium_wall.name=Zirkonmauer +# Modern Metals settings and restored content +config.modernmetals.material.tooltip=Legt fest, ob die Blöcke, Gegenstände und Rezepte dieses Materials verfügbar sind. Ein Neustart ist nötig. +config.modernmetals.title=Modern Metals +config.modernmetals.description=Neustart erforderlich. Erze in OreSpawn einstellen. +config.modernmetals.content_mode=Fantasy-Stufe +config.modernmetals.content_mode.tooltip=Viel Fantasy behält alle Rezepte bei. Wenig Fantasy schränkt unrealistische Ausrüstung und Bauteile ein. Ein Neustart ist nötig. +high_fantasy=Viel Fantasy +low_fantasy=Wenig Fantasy +config.modernmetals.villager_trades=Dorfbewohnerhandel +config.modernmetals.villager_trades.tooltip=Schmiede handeln mit Barren und Ausrüstung aus Modern Metals. Ein Neustart ist nötig. +config.modernmetals.connection.mismatch=Der Server verwendet %s, dein Spiel verwendet %s. Gleiche die Fantasy-Stufe unter Mods > Modern Metals > Konfiguration an und starte das Spiel neu. +config.modernmetals.connection.protocol=Modern Metals konnte die Fantasy-Stufe nicht prüfen. Verwende auf dem Server und im Spiel denselben Build von Modern Metals. +itemGroup.modernmetals.combat=Modern Metals – Kampf +material.beryllium.name=Beryllium +item.modernmetals.beryllium_arrow.name=Berylliumpfeil +item.modernmetals.beryllium_axe.name=Berylliumaxt +item.modernmetals.beryllium_bars.name=Berylliumgitter +item.modernmetals.beryllium_blend.name=Berylliumgemisch +item.modernmetals.beryllium_block.name=Berylliumblock +item.modernmetals.beryllium_boots.name=Berylliumstiefel +item.modernmetals.beryllium_bolt.name=Berylliumbolzen +item.modernmetals.beryllium_bow.name=Berylliumbogen +item.modernmetals.beryllium_chestplate.name=Berylliumbrustpanzer +item.modernmetals.beryllium_crackhammer.name=Berylliumbruchhammer +item.modernmetals.beryllium_crossbow.name=Berylliumarmbrust +item.modernmetals.beryllium_door.name=Berylliumtür +item.modernmetals.beryllium_fishing_rod.name=Berylliumangel +item.modernmetals.beryllium_gear.name=Berylliumzahnrad +item.modernmetals.beryllium_helmet.name=Berylliumhelm +item.modernmetals.beryllium_hoe.name=Berylliumhacke +item.modernmetals.beryllium_horse_armor.name=Berylliumpferderüstung +item.modernmetals.beryllium_ingot.name=Berylliumbarren +item.modernmetals.beryllium_leggings.name=Berylliumbeinrüstung +item.modernmetals.beryllium_nugget.name=Berylliumnugget +item.modernmetals.beryllium_ore.name=Berylliumerz +item.modernmetals.beryllium_pickaxe.name=Berylliumspitzhacke +item.modernmetals.beryllium_powder.name=Berylliumpulver +item.modernmetals.beryllium_rod.name=Berylliumstange +item.modernmetals.beryllium_shears.name=Berylliumschere +item.modernmetals.beryllium_shield.name=Berylliumschild +item.modernmetals.beryllium_shovel.name=Berylliumschaufel +item.modernmetals.beryllium_smallblend.name=Kleiner Berylliumgemischhaufen +item.modernmetals.beryllium_smallpowder.name=Kleiner Berylliumpulverhaufen +item.modernmetals.beryllium_sword.name=Berylliumschwert +item.modernmetals.beryllium_trapdoor.name=Berylliumfalltür +material.boron.name=Bor +item.modernmetals.boron_arrow.name=Borpfeil +item.modernmetals.boron_axe.name=Boraxt +item.modernmetals.boron_bars.name=Borgitter +item.modernmetals.boron_blend.name=Borgemisch +item.modernmetals.boron_block.name=Borblock +item.modernmetals.boron_boots.name=Borstiefel +item.modernmetals.boron_bolt.name=Borbolzen +item.modernmetals.boron_bow.name=Borbogen +item.modernmetals.boron_chestplate.name=Borbrustpanzer +item.modernmetals.boron_crackhammer.name=Borbruchhammer +item.modernmetals.boron_crossbow.name=Borarmbrust +item.modernmetals.boron_door.name=Bortür +item.modernmetals.boron_fishing_rod.name=Borangel +item.modernmetals.boron_gear.name=Borzahnrad +item.modernmetals.boron_helmet.name=Borhelm +item.modernmetals.boron_hoe.name=Borhacke +item.modernmetals.boron_horse_armor.name=Borpferderüstung +item.modernmetals.boron_ingot.name=Borbarren +item.modernmetals.boron_leggings.name=Borbeinrüstung +item.modernmetals.boron_nugget.name=Bornugget +item.modernmetals.boron_ore.name=Borerz +item.modernmetals.boron_pickaxe.name=Borspitzhacke +item.modernmetals.boron_powder.name=Borpulver +item.modernmetals.boron_rod.name=Borstange +item.modernmetals.boron_shears.name=Borschere +item.modernmetals.boron_shield.name=Borschild +item.modernmetals.boron_shovel.name=Borschaufel +item.modernmetals.boron_smallblend.name=Kleiner Borgemischhaufen +item.modernmetals.boron_smallpowder.name=Kleiner Borpulverhaufen +item.modernmetals.boron_sword.name=Borschwert +item.modernmetals.boron_trapdoor.name=Borfalltür +tile.modernmetals.boron_bars.name=Borgitter +tile.modernmetals.boron_block.name=Bor +tile.modernmetals.boron_door.name=Bortür +tile.modernmetals.boron_ore.name=Borerz +tile.modernmetals.boron_plate.name=Borblech +tile.modernmetals.boron_trapdoor.name=Borfalltür +fluid.modernmetals.boron=Geschmolzenes Bor +fluid.modernmetals.beryllium=Geschmolzenes Beryllium +fluid.modernmetals.boron.name=Geschmolzenes Bor +fluid.modernmetals.beryllium.name=Geschmolzenes Beryllium +tile.modernmetals.boron.name=Geschmolzenes Bor +tile.modernmetals.beryllium_bars.name=Berylliumgitter +tile.modernmetals.beryllium_block.name=Beryllium +tile.modernmetals.beryllium_door.name=Berylliumtür +tile.modernmetals.beryllium_ore.name=Berylliumerz +tile.modernmetals.beryllium_plate.name=Berylliumblech +tile.modernmetals.beryllium_trapdoor.name=Berylliumfalltür +item.modernmetals.beryllium_clump.name=Berylliumklumpen +item.modernmetals.beryllium_crushed.name=Zerkleinertes Berylliumerz +item.modernmetals.beryllium_crushed_purified.name=Gereinigtes, zerkleinertes Berylliumerz +item.modernmetals.beryllium_crystal.name=Berylliumkristall +item.modernmetals.beryllium_dense_plate.name=Verdichtetes Berylliumblech +item.modernmetals.beryllium_powder_dirty.name=Verunreinigtes Berylliumpulver +item.modernmetals.beryllium_shard.name=Berylliumsplitter +item.modernmetals.beryllium_button.name=Berylliumschalter +item.modernmetals.beryllium_lever.name=Berylliumhebel +item.modernmetals.beryllium_pressure_plate.name=Berylliumdruckplatte +item.modernmetals.beryllium_slab.name=Berylliumfliese +item.modernmetals.beryllium_stairs.name=Berylliumtreppe +item.modernmetals.beryllium_wall.name=Berylliummauer +tile.modernmetals.beryllium.name=Geschmolzenes Beryllium +tile.modernmetals.beryllium_button.name=Berylliumschalter +tile.modernmetals.beryllium_lever.name=Berylliumhebel +tile.modernmetals.beryllium_pressure_plate.name=Berylliumdruckplatte +tile.modernmetals.beryllium_slab.name=Berylliumfliese +tile.modernmetals.beryllium_stairs.name=Berylliumtreppe +tile.modernmetals.beryllium_wall.name=Berylliummauer +item.modernmetals.boron_clump.name=Borklumpen +item.modernmetals.boron_crushed.name=Zerkleinertes Borerz +item.modernmetals.boron_crushed_purified.name=Gereinigtes, zerkleinertes Borerz +item.modernmetals.boron_crystal.name=Borkristall +item.modernmetals.boron_dense_plate.name=Verdichtetes Borblech +item.modernmetals.boron_powder_dirty.name=Verunreinigtes Borpulver +item.modernmetals.boron_shard.name=Borsplitter +item.modernmetals.boron_button.name=Borschalter +item.modernmetals.boron_lever.name=Borhebel +item.modernmetals.boron_pressure_plate.name=Bordruckplatte +item.modernmetals.boron_slab.name=Borfliese +item.modernmetals.boron_stairs.name=Bortreppe +item.modernmetals.boron_wall.name=Bormauer +tile.modernmetals.boron_button.name=Borschalter +tile.modernmetals.boron_lever.name=Borhebel +tile.modernmetals.boron_pressure_plate.name=Bordruckplatte +tile.modernmetals.boron_slab.name=Borfliese +tile.modernmetals.boron_stairs.name=Bortreppe +tile.modernmetals.boron_wall.name=Bormauer +material.thorium.name=Thorium +item.modernmetals.thorium_arrow.name=Thoriumpfeil +item.modernmetals.thorium_axe.name=Thoriumaxt +item.modernmetals.thorium_bars.name=Thoriumgitter +item.modernmetals.thorium_blend.name=Thoriumgemisch +item.modernmetals.thorium_block.name=Thoriumblock +item.modernmetals.thorium_boots.name=Thoriumstiefel +item.modernmetals.thorium_bolt.name=Thoriumbolzen +item.modernmetals.thorium_bow.name=Thoriumbogen +item.modernmetals.thorium_chestplate.name=Thoriumbrustpanzer +item.modernmetals.thorium_crackhammer.name=Thoriumbruchhammer +item.modernmetals.thorium_crossbow.name=Thoriumarmbrust +item.modernmetals.thorium_door.name=Thoriumtür +item.modernmetals.thorium_fishing_rod.name=Thoriumangel +item.modernmetals.thorium_gear.name=Thoriumzahnrad +item.modernmetals.thorium_helmet.name=Thoriumhelm +item.modernmetals.thorium_hoe.name=Thoriumhacke +item.modernmetals.thorium_horse_armor.name=Thoriumpferderüstung +item.modernmetals.thorium_ingot.name=Thoriumbarren +item.modernmetals.thorium_leggings.name=Thoriumbeinrüstung +item.modernmetals.thorium_nugget.name=Thoriumnugget +item.modernmetals.thorium_ore.name=Thoriumerz +item.modernmetals.thorium_pickaxe.name=Thoriumspitzhacke +item.modernmetals.thorium_powder.name=Thoriumpulver +item.modernmetals.thorium_rod.name=Thoriumstange +item.modernmetals.thorium_shears.name=Thoriumschere +item.modernmetals.thorium_shield.name=Thoriumschild +item.modernmetals.thorium_shovel.name=Thoriumschaufel +item.modernmetals.thorium_smallblend.name=Kleiner Thoriumgemischhaufen +item.modernmetals.thorium_smallpowder.name=Kleiner Thoriumpulverhaufen +item.modernmetals.thorium_sword.name=Thoriumschwert +item.modernmetals.thorium_trapdoor.name=Thoriumfalltür +tile.modernmetals.thorium_bars.name=Thoriumgitter +tile.modernmetals.thorium_block.name=Thorium +tile.modernmetals.thorium_door.name=Thoriumtür +tile.modernmetals.thorium_ore.name=Thoriumerz +tile.modernmetals.thorium_plate.name=Thoriumblech +tile.modernmetals.thorium_trapdoor.name=Thoriumfalltür +fluid.modernmetals.thorium=Geschmolzenes Thorium +fluid.modernmetals.thorium.name=Geschmolzenes Thorium +tile.modernmetals.thorium.name=Geschmolzenes Thorium +item.modernmetals.thorium_clump.name=Thoriumklumpen +item.modernmetals.thorium_crushed.name=Zerkleinertes Thoriumerz +item.modernmetals.thorium_crushed_purified.name=Gereinigtes, zerkleinertes Thoriumerz +item.modernmetals.thorium_crystal.name=Thoriumkristall +item.modernmetals.thorium_dense_plate.name=Verdichtetes Thoriumblech +item.modernmetals.thorium_powder_dirty.name=Verunreinigtes Thoriumpulver +item.modernmetals.thorium_shard.name=Thoriumsplitter +item.modernmetals.thorium_button.name=Thoriumschalter +item.modernmetals.thorium_lever.name=Thoriumhebel +item.modernmetals.thorium_pressure_plate.name=Thoriumdruckplatte +item.modernmetals.thorium_slab.name=Thoriumfliese +item.modernmetals.thorium_stairs.name=Thoriumtreppe +item.modernmetals.thorium_wall.name=Thoriummauer +tile.modernmetals.thorium_button.name=Thoriumschalter +tile.modernmetals.thorium_lever.name=Thoriumhebel +tile.modernmetals.thorium_pressure_plate.name=Thoriumdruckplatte +tile.modernmetals.thorium_slab.name=Thoriumfliese +tile.modernmetals.thorium_stairs.name=Thoriumtreppe +tile.modernmetals.thorium_wall.name=Thoriummauer +item.modernmetals.titanium_casing.name=Titangehäuse +item.modernmetals.beryllium_scythe.name=Berylliumsichel +item.modernmetals.boron_scythe.name=Borsichel +item.modernmetals.thorium_scythe.name=Thoriumsichel +item.modernmetals.titanium_scythe.name=Titansichel +config.modernmetals.enable_thorium=Thorium aktivieren +config.modernmetals.enable_boron=Bor aktivieren +config.modernmetals.enable_beryllium=Beryllium aktivieren +config.modernmetals.enable_cadmium=Cadmium aktivieren +config.modernmetals.enable_nichrome=Nickel-Chrom-Legierung aktivieren +config.modernmetals.enable_aluminum=Aluminium aktivieren +config.modernmetals.enable_aluminumbrass=Aluminiummessing aktivieren +config.modernmetals.enable_chromium=Chrom aktivieren +config.modernmetals.enable_galvanizedsteel=Verzinkter Stahl aktivieren +config.modernmetals.enable_iridium=Iridium aktivieren +config.modernmetals.enable_magnesium=Magnesium aktivieren +config.modernmetals.enable_manganese=Mangan aktivieren +config.modernmetals.enable_osmium=Osmium aktivieren +config.modernmetals.enable_plutonium=Plutonium aktivieren +config.modernmetals.enable_rutile=Rutil aktivieren +config.modernmetals.enable_stainlesssteel=Edelstahl aktivieren +config.modernmetals.enable_tantalum=Tantal aktivieren +config.modernmetals.enable_titanium=Titan aktivieren +config.modernmetals.enable_tungsten=Wolfram aktivieren +config.modernmetals.enable_uranium=Uran aktivieren +config.modernmetals.enable_zirconium=Zirkon aktivieren + +achievement.aluminum_brass_maker=Aluminiummessing +achievement.aluminum_brass_maker.desc=Kombiniere Aluminiumpulver und Kupferpulver, um Aluminiummessing herzustellen diff --git a/src/main/resources/assets/modernmetals/lang/en_AU.lang b/src/main/resources/assets/modernmetals/lang/en_AU.lang new file mode 100644 index 00000000..0f62b48b --- /dev/null +++ b/src/main/resources/assets/modernmetals/lang/en_AU.lang @@ -0,0 +1,51 @@ +# Regional strings. Minecraft falls back to en_US for other entries. + +# Regional English +item.modernmetals.aluminum_horse_armor.name=Aluminum Horse Armour +item.modernmetals.aluminum_leggings.name=Aluminum Leg Armour +item.modernmetals.aluminumbrass_horse_armor.name=Aluminum Brass Horse Armour +item.modernmetals.aluminumbrass_leggings.name=Aluminum Brass Leg Armour +item.modernmetals.antimony_leggings.name=Antimony Leg Armour +item.modernmetals.beryllium_horse_armor.name=Beryllium Horse Armour +item.modernmetals.beryllium_leggings.name=Beryllium Leg Armour +item.modernmetals.bismuth_leggings.name=Bismuth Leg Armour +item.modernmetals.boron_horse_armor.name=Boron Horse Armour +item.modernmetals.boron_leggings.name=Boron Leg Armour +item.modernmetals.cadmium_horse_armor.name=Cadmium Horse Armour +item.modernmetals.cadmium_leggings.name=Cadmium Leg Armour +item.modernmetals.chromium_horse_armor.name=Chromium Horse Armour +item.modernmetals.chromium_leggings.name=Chromium Leg Armour +item.modernmetals.galvanizedsteel_horse_armor.name=Galvanized Steel Horse Armour +item.modernmetals.galvanizedsteel_leggings.name=Galvanized Steel Leg Armour +item.modernmetals.iridium_horse_armor.name=Iridium Horse Armour +item.modernmetals.iridium_leggings.name=Iridium Leg Armour +item.modernmetals.magnesium_horse_armor.name=Magnesium Horse Armour +item.modernmetals.magnesium_leggings.name=Magnesium Leg Armour +item.modernmetals.manganese_horse_armor.name=Manganese Horse Armour +item.modernmetals.manganese_leggings.name=Manganese Leg Armour +item.modernmetals.nichrome_horse_armor.name=Nichrome Horse Armour +item.modernmetals.nichrome_leggings.name=Nichrome Leg Armour +item.modernmetals.osmium_horse_armor.name=Osmium Horse Armour +item.modernmetals.osmium_leggings.name=Osmium Leg Armour +item.modernmetals.pewter_leggings.name=Pewter Leg Armour +item.modernmetals.plutonium_horse_armor.name=Plutonium Horse Armour +item.modernmetals.plutonium_leggings.name=Plutonium Leg Armour +item.modernmetals.rutile_horse_armor.name=Rutile Horse Armour +item.modernmetals.rutile_leggings.name=Rutile Leg Armour +item.modernmetals.stainlesssteel_horse_armor.name=Stainless Steel Horse Armour +item.modernmetals.stainlesssteel_leggings.name=Stainless Steel Leg Armour +item.modernmetals.tantalum_horse_armor.name=Tantalum Horse Armour +item.modernmetals.tantalum_leggings.name=Tantalum Leg Armour +item.modernmetals.titanium_horse_armor.name=Titanium Horse Armour +item.modernmetals.titanium_leggings.name=Titanium Leg Armour +item.modernmetals.tungsten_horse_armor.name=Tungsten Horse Armour +item.modernmetals.tungsten_leggings.name=Tungsten Leg Armour +item.modernmetals.uranium_horse_armor.name=Uranium Horse Armour +item.modernmetals.uranium_leggings.name=Uranium Leg Armour +item.modernmetals.zirconium_horse_armor.name=Zirconium Horse Armour +item.modernmetals.zirconium_leggings.name=Zirconium Leg Armour +item.modernmetals.thorium_horse_armor.name=Thorium Horse Armour +item.modernmetals.thorium_leggings.name=Thorium Leg Armour +config.modernmetals.enable_aluminum=Enable Aluminium +config.modernmetals.enable_aluminumbrass=Enable Aluminium Brass +config.modernmetals.enable_galvanizedsteel=Enable Galvanised Steel diff --git a/src/main/resources/assets/modernmetals/lang/en_CA.lang b/src/main/resources/assets/modernmetals/lang/en_CA.lang new file mode 100644 index 00000000..0f62b48b --- /dev/null +++ b/src/main/resources/assets/modernmetals/lang/en_CA.lang @@ -0,0 +1,51 @@ +# Regional strings. Minecraft falls back to en_US for other entries. + +# Regional English +item.modernmetals.aluminum_horse_armor.name=Aluminum Horse Armour +item.modernmetals.aluminum_leggings.name=Aluminum Leg Armour +item.modernmetals.aluminumbrass_horse_armor.name=Aluminum Brass Horse Armour +item.modernmetals.aluminumbrass_leggings.name=Aluminum Brass Leg Armour +item.modernmetals.antimony_leggings.name=Antimony Leg Armour +item.modernmetals.beryllium_horse_armor.name=Beryllium Horse Armour +item.modernmetals.beryllium_leggings.name=Beryllium Leg Armour +item.modernmetals.bismuth_leggings.name=Bismuth Leg Armour +item.modernmetals.boron_horse_armor.name=Boron Horse Armour +item.modernmetals.boron_leggings.name=Boron Leg Armour +item.modernmetals.cadmium_horse_armor.name=Cadmium Horse Armour +item.modernmetals.cadmium_leggings.name=Cadmium Leg Armour +item.modernmetals.chromium_horse_armor.name=Chromium Horse Armour +item.modernmetals.chromium_leggings.name=Chromium Leg Armour +item.modernmetals.galvanizedsteel_horse_armor.name=Galvanized Steel Horse Armour +item.modernmetals.galvanizedsteel_leggings.name=Galvanized Steel Leg Armour +item.modernmetals.iridium_horse_armor.name=Iridium Horse Armour +item.modernmetals.iridium_leggings.name=Iridium Leg Armour +item.modernmetals.magnesium_horse_armor.name=Magnesium Horse Armour +item.modernmetals.magnesium_leggings.name=Magnesium Leg Armour +item.modernmetals.manganese_horse_armor.name=Manganese Horse Armour +item.modernmetals.manganese_leggings.name=Manganese Leg Armour +item.modernmetals.nichrome_horse_armor.name=Nichrome Horse Armour +item.modernmetals.nichrome_leggings.name=Nichrome Leg Armour +item.modernmetals.osmium_horse_armor.name=Osmium Horse Armour +item.modernmetals.osmium_leggings.name=Osmium Leg Armour +item.modernmetals.pewter_leggings.name=Pewter Leg Armour +item.modernmetals.plutonium_horse_armor.name=Plutonium Horse Armour +item.modernmetals.plutonium_leggings.name=Plutonium Leg Armour +item.modernmetals.rutile_horse_armor.name=Rutile Horse Armour +item.modernmetals.rutile_leggings.name=Rutile Leg Armour +item.modernmetals.stainlesssteel_horse_armor.name=Stainless Steel Horse Armour +item.modernmetals.stainlesssteel_leggings.name=Stainless Steel Leg Armour +item.modernmetals.tantalum_horse_armor.name=Tantalum Horse Armour +item.modernmetals.tantalum_leggings.name=Tantalum Leg Armour +item.modernmetals.titanium_horse_armor.name=Titanium Horse Armour +item.modernmetals.titanium_leggings.name=Titanium Leg Armour +item.modernmetals.tungsten_horse_armor.name=Tungsten Horse Armour +item.modernmetals.tungsten_leggings.name=Tungsten Leg Armour +item.modernmetals.uranium_horse_armor.name=Uranium Horse Armour +item.modernmetals.uranium_leggings.name=Uranium Leg Armour +item.modernmetals.zirconium_horse_armor.name=Zirconium Horse Armour +item.modernmetals.zirconium_leggings.name=Zirconium Leg Armour +item.modernmetals.thorium_horse_armor.name=Thorium Horse Armour +item.modernmetals.thorium_leggings.name=Thorium Leg Armour +config.modernmetals.enable_aluminum=Enable Aluminium +config.modernmetals.enable_aluminumbrass=Enable Aluminium Brass +config.modernmetals.enable_galvanizedsteel=Enable Galvanised Steel diff --git a/src/main/resources/assets/modernmetals/lang/en_GB.lang b/src/main/resources/assets/modernmetals/lang/en_GB.lang new file mode 100644 index 00000000..9ec41df4 --- /dev/null +++ b/src/main/resources/assets/modernmetals/lang/en_GB.lang @@ -0,0 +1,96 @@ +# Regional strings. Minecraft falls back to en_US for other entries. + +# Regional English +item.modernmetals.aluminum_block.name=Aluminum +item.modernmetals.aluminum_horse_armor.name=Aluminum Horse Armour +item.modernmetals.aluminum_leggings.name=Aluminum Leg Armour +item.modernmetals.aluminumbrass_block.name=Aluminum Brass Block +item.modernmetals.aluminumbrass_horse_armor.name=Aluminum Brass Horse Armour +item.modernmetals.aluminumbrass_leggings.name=Aluminum Brass Leg Armour +item.modernmetals.antimony_block.name=Antimony Block +item.modernmetals.antimony_leggings.name=Antimony Leg Armour +item.modernmetals.beryllium_block.name=Beryllium Block +item.modernmetals.beryllium_horse_armor.name=Beryllium Horse Armour +item.modernmetals.beryllium_leggings.name=Beryllium Leg Armour +item.modernmetals.bismuth_block.name=Bismuth Block +item.modernmetals.bismuth_leggings.name=Bismuth Leg Armour +item.modernmetals.boron_block.name=Boron Block +item.modernmetals.boron_horse_armor.name=Boron Horse Armour +item.modernmetals.boron_leggings.name=Boron Leg Armour +item.modernmetals.cadmium_block.name=Cadmium Block +item.modernmetals.cadmium_horse_armor.name=Cadmium Horse Armour +item.modernmetals.cadmium_leggings.name=Cadmium Leg Armour +item.modernmetals.chromium_block.name=Chromium Block +item.modernmetals.chromium_horse_armor.name=Chromium Horse Armour +item.modernmetals.chromium_leggings.name=Chromium Leg Armour +item.modernmetals.galvanizedsteel_block.name=Galvanized Steel Block +item.modernmetals.galvanizedsteel_horse_armor.name=Galvanized Steel Horse Armour +item.modernmetals.galvanizedsteel_leggings.name=Galvanized Steel Leg Armour +item.modernmetals.iridium_block.name=Iridium Block +item.modernmetals.iridium_horse_armor.name=Iridium Horse Armour +item.modernmetals.iridium_leggings.name=Iridium Leg Armour +item.modernmetals.magnesium_block.name=Magnesium Block +item.modernmetals.magnesium_horse_armor.name=Magnesium Horse Armour +item.modernmetals.magnesium_leggings.name=Magnesium Leg Armour +item.modernmetals.manganese_block.name=Manganese Block +item.modernmetals.manganese_horse_armor.name=Manganese Horse Armour +item.modernmetals.manganese_leggings.name=Manganese Leg Armour +item.modernmetals.nichrome_block.name=Nichrome Block +item.modernmetals.nichrome_horse_armor.name=Nichrome Horse Armour +item.modernmetals.nichrome_leggings.name=Nichrome Leg Armour +item.modernmetals.osmium_block.name=Osmium Block +item.modernmetals.osmium_horse_armor.name=Osmium Horse Armour +item.modernmetals.osmium_leggings.name=Osmium Leg Armour +item.modernmetals.pewter_block.name=Pewter Block +item.modernmetals.pewter_leggings.name=Pewter Leg Armour +item.modernmetals.plutonium_block.name=Plutonium Block +item.modernmetals.plutonium_horse_armor.name=Plutonium Horse Armour +item.modernmetals.plutonium_leggings.name=Plutonium Leg Armour +item.modernmetals.rutile_block.name=Rutile Block +item.modernmetals.rutile_horse_armor.name=Rutile Horse Armour +item.modernmetals.rutile_leggings.name=Rutile Leg Armour +item.modernmetals.stainlesssteel_block.name=Stainless Steel Block +item.modernmetals.stainlesssteel_horse_armor.name=Stainless Steel Horse Armour +item.modernmetals.stainlesssteel_leggings.name=Stainless Steel Leg Armour +item.modernmetals.tantalum_block.name=Tantalum Block +item.modernmetals.tantalum_horse_armor.name=Tantalum Horse Armour +item.modernmetals.tantalum_leggings.name=Tantalum Leg Armour +item.modernmetals.titanium_block.name=Titanium Block +item.modernmetals.titanium_horse_armor.name=Titanium Horse Armour +item.modernmetals.titanium_leggings.name=Titanium Leg Armour +item.modernmetals.tungsten_block.name=Tungsten Block +item.modernmetals.tungsten_horse_armor.name=Tungsten Horse Armour +item.modernmetals.tungsten_leggings.name=Tungsten Leg Armour +item.modernmetals.uranium_block.name=Uranium Block +item.modernmetals.uranium_horse_armor.name=Uranium Horse Armour +item.modernmetals.uranium_leggings.name=Uranium Leg Armour +item.modernmetals.zirconium_block.name=Zirconium Block +item.modernmetals.zirconium_horse_armor.name=Zirconium Horse Armour +item.modernmetals.zirconium_leggings.name=Zirconium Leg Armour +tile.modernmetals.aluminum_block.name=Aluminum Block +tile.modernmetals.aluminumbrass_block.name=Aluminum Brass Block +tile.modernmetals.boron_block.name=Boron Block +tile.modernmetals.cadmium_block.name=Cadmium Block +tile.modernmetals.chromium_block.name=Chromium Block +tile.modernmetals.galvanizedsteel_block.name=Galvanized Steel Block +tile.modernmetals.iridium_block.name=Iridium Block +tile.modernmetals.magnesium_block.name=Magnesium Block +tile.modernmetals.manganese_block.name=Manganese Block +tile.modernmetals.nichrome_block.name=Nichrome Block +tile.modernmetals.osmium_block.name=Osmium Block +tile.modernmetals.plutonium_block.name=Plutonium Block +tile.modernmetals.rutile_block.name=Rutile Block +tile.modernmetals.stainlesssteel_block.name=Stainless Steel Block +tile.modernmetals.tantalum_block.name=Tantalum +tile.modernmetals.titanium_block.name=Titanium +tile.modernmetals.tungsten_block.name=Tungsten +tile.modernmetals.uranium_block.name=Uranium +tile.modernmetals.zirconium_block.name=Zirconium +tile.modernmetals.beryllium_block.name=Beryllium +item.modernmetals.thorium_block.name=Thorium +item.modernmetals.thorium_horse_armor.name=Thorium Horse Armour +item.modernmetals.thorium_leggings.name=Thorium Leg Armour +tile.modernmetals.thorium_block.name=Thorium +config.modernmetals.enable_aluminum=Enable Aluminium +config.modernmetals.enable_aluminumbrass=Enable Aluminium Brass +config.modernmetals.enable_galvanizedsteel=Enable Galvanised Steel diff --git a/src/main/resources/assets/modernmetals/lang/en_NZ.lang b/src/main/resources/assets/modernmetals/lang/en_NZ.lang new file mode 100644 index 00000000..0f62b48b --- /dev/null +++ b/src/main/resources/assets/modernmetals/lang/en_NZ.lang @@ -0,0 +1,51 @@ +# Regional strings. Minecraft falls back to en_US for other entries. + +# Regional English +item.modernmetals.aluminum_horse_armor.name=Aluminum Horse Armour +item.modernmetals.aluminum_leggings.name=Aluminum Leg Armour +item.modernmetals.aluminumbrass_horse_armor.name=Aluminum Brass Horse Armour +item.modernmetals.aluminumbrass_leggings.name=Aluminum Brass Leg Armour +item.modernmetals.antimony_leggings.name=Antimony Leg Armour +item.modernmetals.beryllium_horse_armor.name=Beryllium Horse Armour +item.modernmetals.beryllium_leggings.name=Beryllium Leg Armour +item.modernmetals.bismuth_leggings.name=Bismuth Leg Armour +item.modernmetals.boron_horse_armor.name=Boron Horse Armour +item.modernmetals.boron_leggings.name=Boron Leg Armour +item.modernmetals.cadmium_horse_armor.name=Cadmium Horse Armour +item.modernmetals.cadmium_leggings.name=Cadmium Leg Armour +item.modernmetals.chromium_horse_armor.name=Chromium Horse Armour +item.modernmetals.chromium_leggings.name=Chromium Leg Armour +item.modernmetals.galvanizedsteel_horse_armor.name=Galvanized Steel Horse Armour +item.modernmetals.galvanizedsteel_leggings.name=Galvanized Steel Leg Armour +item.modernmetals.iridium_horse_armor.name=Iridium Horse Armour +item.modernmetals.iridium_leggings.name=Iridium Leg Armour +item.modernmetals.magnesium_horse_armor.name=Magnesium Horse Armour +item.modernmetals.magnesium_leggings.name=Magnesium Leg Armour +item.modernmetals.manganese_horse_armor.name=Manganese Horse Armour +item.modernmetals.manganese_leggings.name=Manganese Leg Armour +item.modernmetals.nichrome_horse_armor.name=Nichrome Horse Armour +item.modernmetals.nichrome_leggings.name=Nichrome Leg Armour +item.modernmetals.osmium_horse_armor.name=Osmium Horse Armour +item.modernmetals.osmium_leggings.name=Osmium Leg Armour +item.modernmetals.pewter_leggings.name=Pewter Leg Armour +item.modernmetals.plutonium_horse_armor.name=Plutonium Horse Armour +item.modernmetals.plutonium_leggings.name=Plutonium Leg Armour +item.modernmetals.rutile_horse_armor.name=Rutile Horse Armour +item.modernmetals.rutile_leggings.name=Rutile Leg Armour +item.modernmetals.stainlesssteel_horse_armor.name=Stainless Steel Horse Armour +item.modernmetals.stainlesssteel_leggings.name=Stainless Steel Leg Armour +item.modernmetals.tantalum_horse_armor.name=Tantalum Horse Armour +item.modernmetals.tantalum_leggings.name=Tantalum Leg Armour +item.modernmetals.titanium_horse_armor.name=Titanium Horse Armour +item.modernmetals.titanium_leggings.name=Titanium Leg Armour +item.modernmetals.tungsten_horse_armor.name=Tungsten Horse Armour +item.modernmetals.tungsten_leggings.name=Tungsten Leg Armour +item.modernmetals.uranium_horse_armor.name=Uranium Horse Armour +item.modernmetals.uranium_leggings.name=Uranium Leg Armour +item.modernmetals.zirconium_horse_armor.name=Zirconium Horse Armour +item.modernmetals.zirconium_leggings.name=Zirconium Leg Armour +item.modernmetals.thorium_horse_armor.name=Thorium Horse Armour +item.modernmetals.thorium_leggings.name=Thorium Leg Armour +config.modernmetals.enable_aluminum=Enable Aluminium +config.modernmetals.enable_aluminumbrass=Enable Aluminium Brass +config.modernmetals.enable_galvanizedsteel=Enable Galvanised Steel diff --git a/src/main/resources/assets/modernmetals/lang/en_PT.lang b/src/main/resources/assets/modernmetals/lang/en_PT.lang new file mode 100644 index 00000000..4b2fa57e --- /dev/null +++ b/src/main/resources/assets/modernmetals/lang/en_PT.lang @@ -0,0 +1,90 @@ +# Regional strings. Minecraft falls back to en_US for other entries. + +# Regional English +item.modernmetals.aluminum_block.name=Block o' Aluminum +item.modernmetals.aluminumbrass_block.name=Block o' Aluminum Brass +item.modernmetals.antimony_block.name=Block o' Antimony +item.modernmetals.beryllium_block.name=Block o' Beryllium +item.modernmetals.bismuth_block.name=Block o' Bismuth +item.modernmetals.boron_block.name=Block o' Boron +item.modernmetals.cadmium_block.name=Block o' Cadmium +item.modernmetals.chromium_block.name=Block o' Chromium +item.modernmetals.galvanizedsteel_block.name=Block o' Galvanized Steel +item.modernmetals.iridium_block.name=Block o' Iridium +item.modernmetals.magnesium_block.name=Block o' Magnesium +item.modernmetals.manganese_block.name=Block o' Manganese +item.modernmetals.nichrome_block.name=Block o' Nichrome +item.modernmetals.osmium_block.name=Block o' Osmium +item.modernmetals.pewter_block.name=Block o' Pewter +item.modernmetals.plutonium_block.name=Block o' Plutonium +item.modernmetals.rutile_block.name=Block o' Rutile +item.modernmetals.stainlesssteel_block.name=Block o' Stainless Steel +item.modernmetals.tantalum_block.name=Block o' Tantalum +item.modernmetals.titanium_block.name=Block o' Titanium +item.modernmetals.tungsten_block.name=Block o' Tungsten +item.modernmetals.uranium_block.name=Block o' Uranium +item.modernmetals.zirconium_block.name=Block o' Zirconium +tile.modernmetals.aluminum_block.name=Block o' Aluminum +tile.modernmetals.aluminum_plate.name=Plate o' Aluminum +tile.modernmetals.aluminumbrass_block.name=Block o' Aluminum Brass +tile.modernmetals.aluminumbrass_plate.name=Plate o' Aluminum Brass +tile.modernmetals.boron_block.name=Block o' Boron +tile.modernmetals.boron_plate.name=Plate o' Boron +tile.modernmetals.cadmium_block.name=Block o' Cadmium +tile.modernmetals.cadmium_plate.name=Plate o' Cadmium +tile.modernmetals.chromium_block.name=Block o' Chromium +tile.modernmetals.chromium_plate.name=Plate o' Chromium +tile.modernmetals.galvanizedsteel_block.name=Block o' Galvanized Steel +tile.modernmetals.galvanizedsteel_plate.name=Plate o' Galvanized Steel +tile.modernmetals.iridium_block.name=Block o' Iridium +tile.modernmetals.iridium_plate.name=Plate o' Iridium +tile.modernmetals.magnesium_block.name=Block o' Magnesium +tile.modernmetals.magnesium_plate.name=Plate o' Magnesium +tile.modernmetals.manganese_block.name=Block o' Manganese +tile.modernmetals.manganese_plate.name=Plate o' Manganese +tile.modernmetals.nichrome_block.name=Block o' Nichrome +tile.modernmetals.nichrome_plate.name=Plate o' Nichrome +tile.modernmetals.osmium_block.name=Block o' Osmium +tile.modernmetals.osmium_plate.name=Plate o' Osmium +tile.modernmetals.plutonium_block.name=Block o' Plutonium +tile.modernmetals.plutonium_plate.name=Plate o' Plutonium +tile.modernmetals.rutile_block.name=Block o' Rutile +tile.modernmetals.rutile_plate.name=Plate o' Rutile +tile.modernmetals.stainlesssteel_block.name=Block o' Stainless Steel +tile.modernmetals.stainlesssteel_plate.name=Plate o' Stainless Steel +tile.modernmetals.tantalum_block.name=Block o' Tantalum +tile.modernmetals.tantalum_plate.name=Plate o' Tantalum +tile.modernmetals.titanium_block.name=Block o' Titanium +tile.modernmetals.titanium_plate.name=Plate o' Titanium +tile.modernmetals.tungsten_block.name=Block o' Tungsten +tile.modernmetals.tungsten_plate.name=Plate o' Tungsten +tile.modernmetals.uranium_block.name=Block o' Uranium +tile.modernmetals.uranium_plate.name=Plate o' Uranium +tile.modernmetals.zirconium_block.name=Block o' Zirconium +tile.modernmetals.zirconium_plate.name=Plate o' Zirconium +item.modernmetals.aluminum_dense_plate.name=Dense Plate o' Aluminum +item.modernmetals.aluminumbrass_dense_plate.name=Dense Plate o' Aluminum Brass +tile.modernmetals.beryllium_block.name=Block o' Beryllium +tile.modernmetals.beryllium_plate.name=Plate o' Beryllium +item.modernmetals.beryllium_dense_plate.name=Dense Plate o' Beryllium +item.modernmetals.boron_dense_plate.name=Dense Plate o' Boron +item.modernmetals.cadmium_dense_plate.name=Dense Plate o' Cadmium +item.modernmetals.chromium_dense_plate.name=Dense Plate o' Chromium +item.modernmetals.galvanizedsteel_dense_plate.name=Dense Plate o' Galvanized Steel +item.modernmetals.iridium_dense_plate.name=Dense Plate o' Iridium +item.modernmetals.magnesium_dense_plate.name=Dense Plate o' Magnesium +item.modernmetals.manganese_dense_plate.name=Dense Plate o' Manganese +item.modernmetals.nichrome_dense_plate.name=Dense Plate o' Nichrome +item.modernmetals.osmium_dense_plate.name=Dense Plate o' Osmium +item.modernmetals.plutonium_dense_plate.name=Dense Plate o' Plutonium +item.modernmetals.rutile_dense_plate.name=Dense Plate o' Rutile +item.modernmetals.stainlesssteel_dense_plate.name=Dense Plate o' Stainless Steel +item.modernmetals.tantalum_dense_plate.name=Dense Plate o' Tantalum +item.modernmetals.titanium_dense_plate.name=Dense Plate o' Titanium +item.modernmetals.tungsten_dense_plate.name=Dense Plate o' Tungsten +item.modernmetals.uranium_dense_plate.name=Dense Plate o' Uranium +item.modernmetals.zirconium_dense_plate.name=Dense Plate o' Zirconium +item.modernmetals.thorium_block.name=Block o' Thorium +tile.modernmetals.thorium_block.name=Block o' Thorium +tile.modernmetals.thorium_plate.name=Plate o' Thorium +item.modernmetals.thorium_dense_plate.name=Dense Plate o' Thorium diff --git a/src/main/resources/assets/modernmetals/lang/en_US.lang b/src/main/resources/assets/modernmetals/lang/en_US.lang index 32080748..2c588b64 100644 --- a/src/main/resources/assets/modernmetals/lang/en_US.lang +++ b/src/main/resources/assets/modernmetals/lang/en_US.lang @@ -1544,3 +1544,42 @@ item.modernmetals.titanium_scythe.name=Titanium Sickle item.modernmetals.tungsten_scythe.name=Tungsten Sickle item.modernmetals.uranium_scythe.name=Uranium Sickle item.modernmetals.zirconium_scythe.name=Zirconium Sickle + +config.modernmetals.title=Modern Metals +config.modernmetals.description=Restart to apply changes. Edit ores in OreSpawn. +config.modernmetals.content_mode=Fantasy Level +config.modernmetals.content_mode.tooltip=High Fantasy keeps all recipes. Low Fantasy restricts unrealistic equipment and structures. Requires a game restart. +# Forge 1.10 cycle selectors translate their stored values directly. +high_fantasy=High Fantasy +low_fantasy=Low Fantasy +config.modernmetals.enable_thorium=Enable Thorium +config.modernmetals.enable_boron=Enable Boron +config.modernmetals.enable_beryllium=Enable Beryllium +config.modernmetals.enable_cadmium=Enable Cadmium +config.modernmetals.enable_nichrome=Enable Nichrome +config.modernmetals.enable_aluminum=Enable Aluminum +config.modernmetals.enable_aluminumbrass=Enable Aluminum Brass +config.modernmetals.enable_chromium=Enable Chromium +config.modernmetals.enable_galvanizedsteel=Enable Galvanized Steel +config.modernmetals.enable_iridium=Enable Iridium +config.modernmetals.enable_magnesium=Enable Magnesium +config.modernmetals.enable_manganese=Enable Manganese +config.modernmetals.enable_osmium=Enable Osmium +config.modernmetals.enable_plutonium=Enable Plutonium +config.modernmetals.enable_rutile=Enable Rutile +config.modernmetals.enable_stainlesssteel=Enable Stainless Steel +config.modernmetals.enable_tantalum=Enable Tantalum +config.modernmetals.enable_titanium=Enable Titanium +config.modernmetals.enable_tungsten=Enable Tungsten +config.modernmetals.enable_uranium=Enable Uranium +config.modernmetals.enable_zirconium=Enable Zirconium + +# Modern Metals settings and restored content +config.modernmetals.material.tooltip=Controls which blocks, items and recipes are available for this material. Requires a game restart. +config.modernmetals.villager_trades=Villager Trades +config.modernmetals.villager_trades.tooltip=Let blacksmiths trade Modern Metals ingots and equipment. Requires a game restart. +config.modernmetals.connection.mismatch=The server uses %s, but your game uses %s. Match the Fantasy Level in Mods > Modern Metals > Config and restart. +config.modernmetals.connection.protocol=Modern Metals could not check the Fantasy Level. Use the same Modern Metals build on the server and client. + +achievement.aluminum_brass_maker=Aluminum Brass +achievement.aluminum_brass_maker.desc=Combine powdered Aluminum and Brass to make Aluminum Brass diff --git a/src/main/resources/assets/modernmetals/lang/ru_RU.lang b/src/main/resources/assets/modernmetals/lang/ru_RU.lang new file mode 100644 index 00000000..02925cb4 --- /dev/null +++ b/src/main/resources/assets/modernmetals/lang/ru_RU.lang @@ -0,0 +1,1556 @@ +itemGroup.modernmetals.items=Modern Metals Предметы +itemGroup.modernmetals.tools=Modern Metals Инструменты +itemGroup.modernmetals.blocks=Modern Metals Блоки +nei.modernmetals.recipehandler.crusher.name=Crushed Blocks/Items + +material.aluminum.name=Алюминий +item.modernmetals.aluminum_arrow.name=Алюминиевая Стрела +item.modernmetals.aluminum_axe.name=Алюминиевый Топор +item.modernmetals.aluminum_bars.name=Алюминиевый Прутья +item.modernmetals.aluminum_blend.name=Алюминиевый Смеси +item.modernmetals.aluminum_block.name=Блок Алюминя +item.modernmetals.aluminum_boots.name=Алюминиевый Ботинки +item.modernmetals.aluminum_bolt.name=Алюминиевый Арбалетная Стрела +item.modernmetals.aluminum_bow.name=Алюминиевый Лук +item.modernmetals.aluminum_chestplate.name=Алюминиевый Нагрудник +item.modernmetals.aluminum_crackhammer.name=Алюминиевый Измельчающий Молот +item.modernmetals.aluminum_crossbow.name=Алюминиевый Арбалет +item.modernmetals.aluminum_door.name=Алюминиевый Двери +item.modernmetals.aluminum_fishing_rod.name=Алюминиевый Удочка +item.modernmetals.aluminum_gear.name=Алюминиевый Шестерня +item.modernmetals.aluminum_helmet.name=Алюминиевый Шлем +item.modernmetals.aluminum_hoe.name=Алюминиевый Мотыга +item.modernmetals.aluminum_horse_armor.name=Алюминиевый Конская Броня +item.modernmetals.aluminum_ingot.name=Алюминиевый Слиток +item.modernmetals.aluminum_leggings.name=Алюминиевый Поножки +item.modernmetals.aluminum_nugget.name=Алюминиевый Самородок +item.modernmetals.aluminum_ore.name=Алюминиевый Руда +item.modernmetals.aluminum_pickaxe.name=Алюминиевый Кирка +item.modernmetals.aluminum_powder.name=Измельчённый Алюминиевый +item.modernmetals.aluminum_rod.name=Алюминиевый Стержень +item.modernmetals.aluminum_shears.name=Алюминиевый Ножницы +item.modernmetals.aluminum_shield.name=Алюминиевый Щит +item.modernmetals.aluminum_shovel.name=Алюминиевый Лопата +item.modernmetals.aluminum_smallblend.name=Маленькая Кучка Алюминиевый Смеси +item.modernmetals.aluminum_smallpowder.name=Маленькая Кучка Измельчённого Алюминиевый +item.modernmetals.aluminum_sword.name=Алюминиевый Меч +item.modernmetals.aluminum_trapdoor.name=Алюминиевый Люк + +material.aluminumbrass.name=Алюминиевая Латунь +item.modernmetals.aluminumbrass_arrow.name=Алюминиево-Латунный Стрела +item.modernmetals.aluminumbrass_axe.name=Алюминиево-Латунный Топор +item.modernmetals.aluminumbrass_bars.name=Алюминиево-Латунный Прутья +item.modernmetals.aluminumbrass_blend.name=Алюминиево-Латунный Смеси +item.modernmetals.aluminumbrass_block.name=Блок Алюминиево-Латунный +item.modernmetals.aluminumbrass_boots.name=Алюминиево-Латунный Ботинки +item.modernmetals.aluminumbrass_bolt.name=Алюминиево-Латунный Арбалетная Стрела +item.modernmetals.aluminumbrass_bow.name=Алюминиево-Латунный Лук +item.modernmetals.aluminumbrass_chestplate.name=Алюминиево-Латунный Нагрудник +item.modernmetals.aluminumbrass_crackhammer.name=Алюминиево-Латунный Измельчающий Молот +item.modernmetals.aluminumbrass_crossbow.name=Алюминиево-Латунный Арбалет +item.modernmetals.aluminumbrass_door.name=Алюминиево-Латунный Двери +item.modernmetals.aluminumbrass_fishing_rod.name=Алюминиево-Латунный Удочка +item.modernmetals.aluminumbrass_gear.name=Алюминиево-Латунный Шестерня +item.modernmetals.aluminumbrass_helmet.name=Алюминиево-Латунный Шлем +item.modernmetals.aluminumbrass_hoe.name=Алюминиево-Латунный Мотыга +item.modernmetals.aluminumbrass_horse_armor.name=Алюминиево-Латунный Конская Броня +item.modernmetals.aluminumbrass_ingot.name=Алюминиево-Латунный Слиток +item.modernmetals.aluminumbrass_leggings.name=Алюминиево-Латунный Поножки +item.modernmetals.aluminumbrass_nugget.name=Алюминиево-Латунный Самородок +item.modernmetals.aluminumbrass_ore.name=Алюминиево-Латунный Руда +item.modernmetals.aluminumbrass_pickaxe.name=Алюминиево-Латунный Кирка +item.modernmetals.aluminumbrass_powder.name=Измельчённый Алюминиево-Латунный +item.modernmetals.aluminumbrass_rod.name=Алюминиево-Латунный Стержень +item.modernmetals.aluminumbrass_shears.name=Алюминиево-Латунный Ножницы +item.modernmetals.aluminumbrass_shield.name=Алюминиево-Латунный Щит +item.modernmetals.aluminumbrass_shovel.name=Алюминиево-Латунный Лопата +item.modernmetals.aluminumbrass_smallblend.name=Маленькая Кучка Алюминиево-Латунный Смеси +item.modernmetals.aluminumbrass_smallpowder.name=Маленькая Кучка Измельчённого Алюминиево-Латунный +item.modernmetals.aluminumbrass_sword.name=Алюминиево-Латунный Меч +item.modernmetals.aluminumbrass_trapdoor.name=Алюминиево-Латунный Люк + +material.antimony.name=Antimony +item.modernmetals.antimony_arrow.name=Antimony Стрела +item.modernmetals.antimony_axe.name=Antimony Топор +item.modernmetals.antimony_bars.name=Antimony Прутья +item.modernmetals.antimony_block.name=Блок Antimony +item.modernmetals.antimony_boots.name=Antimony Ботинки +item.modernmetals.antimony_chestplate.name=Antimony Нагрудник +item.modernmetals.antimony_crackhammer.name=Antimony Измельчающий Молот +item.modernmetals.antimony_door.name=Antimony Двери +item.modernmetals.antimony_helmet.name=Antimony Шлем +item.modernmetals.antimony_hoe.name=Antimony Мотыга +item.modernmetals.antimony_ingot.name=Antimony Слиток +item.modernmetals.antimony_leggings.name=Antimony Поножки +item.modernmetals.antimony_nugget.name=Antimony Самородок +item.modernmetals.antimony_ore.name=Antimony Руда +item.modernmetals.antimony_pickaxe.name=Antimony Кирка +item.modernmetals.antimony_powder.name=Измельчённый Antimony +item.modernmetals.antimony_shears.name=Antimony Ножницы +item.modernmetals.antimony_shield.name=Antimony Щит +item.modernmetals.antimony_shovel.name=Antimony Лопата +item.modernmetals.antimony_smallpowder.name=Маленькая Кучка Измельчённого Antimony +item.modernmetals.antimony_sword.name=Antimony Меч +item.modernmetals.antimony_trapdoor.name=Antimony Люк + +material.beryllium.name=Beryllium +item.modernmetals.beryllium_arrow.name=Beryllium Стрела +item.modernmetals.beryllium_axe.name=Beryllium Топор +item.modernmetals.beryllium_bars.name=Beryllium Прутья +item.modernmetals.beryllium_blend.name=Beryllium Смеси +item.modernmetals.beryllium_block.name=Блок Beryllium +item.modernmetals.beryllium_boots.name=Beryllium Ботинки +item.modernmetals.beryllium_bolt.name=Beryllium Арбалетная Стрела +item.modernmetals.beryllium_bow.name=Beryllium Лук +item.modernmetals.beryllium_chestplate.name=Beryllium Нагрудник +item.modernmetals.beryllium_crackhammer.name=Beryllium Измельчающий Молот +item.modernmetals.beryllium_crossbow.name=Beryllium Арбалет +item.modernmetals.beryllium_door.name=Beryllium Двери +item.modernmetals.beryllium_fishing_rod.name=Beryllium Удочка +item.modernmetals.beryllium_gear.name=Beryllium Шестерня +item.modernmetals.beryllium_helmet.name=Beryllium Шлем +item.modernmetals.beryllium_hoe.name=Beryllium Мотыга +item.modernmetals.beryllium_horse_armor.name=Beryllium Конская Броня +item.modernmetals.beryllium_ingot.name=Beryllium Слиток +item.modernmetals.beryllium_leggings.name=Beryllium Поножки +item.modernmetals.beryllium_nugget.name=Beryllium Самородок +item.modernmetals.beryllium_ore.name=Beryllium Руда +item.modernmetals.beryllium_pickaxe.name=Beryllium Кирка +item.modernmetals.beryllium_powder.name=Измельчённый Beryllium +item.modernmetals.beryllium_rod.name=Beryllium Стержень +item.modernmetals.beryllium_shears.name=Beryllium Ножницы +item.modernmetals.beryllium_shield.name=Beryllium Щит +item.modernmetals.beryllium_shovel.name=Beryllium Лопата +item.modernmetals.beryllium_smallblend.name=Маленькая Кучка Beryllium Смеси +item.modernmetals.beryllium_smallpowder.name=Маленькая Кучка Измельчённого Beryllium +item.modernmetals.beryllium_sword.name=Beryllium Меч +item.modernmetals.beryllium_trapdoor.name=Beryllium Люк + + +material.bismuth.name=Bismuth +item.modernmetals.bismuth_arrow.name=Bismuth Стрела +item.modernmetals.bismuth_axe.name=Bismuth Топор +item.modernmetals.bismuth_bars.name=Bismuth Прутья +item.modernmetals.bismuth_block.name=Блок Bismuth +item.modernmetals.bismuth_boots.name=Bismuth Ботинки +item.modernmetals.bismuth_chestplate.name=Bismuth Нагрудник +item.modernmetals.bismuth_crackhammer.name=Bismuth Измельчающий Молот +item.modernmetals.bismuth_door.name=Bismuth Двери +item.modernmetals.bismuth_helmet.name=Bismuth Шлем +item.modernmetals.bismuth_hoe.name=Bismuth Мотыга +item.modernmetals.bismuth_ingot.name=Bismuth Слиток +item.modernmetals.bismuth_leggings.name=Bismuth Поножки +item.modernmetals.bismuth_nugget.name=Bismuth Самородок +item.modernmetals.bismuth_ore.name=Bismuth Руда +item.modernmetals.bismuth_pickaxe.name=Bismuth Кирка +item.modernmetals.bismuth_powder.name=Измельчённый Bismuth +item.modernmetals.bismuth_shears.name=Bismuth Ножницы +item.modernmetals.bismuth_shovel.name=Bismuth Лопата +item.modernmetals.bismuth_shield.name=Bismuth Щит +item.modernmetals.bismuth_smallpowder.name=Маленькая Кучка Измельчённого Bismuth +item.modernmetals.bismuth_sword.name=Bismuth Меч +item.modernmetals.bismuth_trapdoor.name=Bismuth Люк + +material.boron.name=Boron +item.modernmetals.boron_arrow.name=Boron Стрела +item.modernmetals.boron_axe.name=Boron Топор +item.modernmetals.boron_bars.name=Boron Прутья +item.modernmetals.boron_blend.name=Boron Смеси +item.modernmetals.boron_block.name=Блок Boron +item.modernmetals.boron_boots.name=Boron Ботинки +item.modernmetals.boron_bolt.name=Boron Арбалетная Стрела +item.modernmetals.boron_bow.name=Boron Лук +item.modernmetals.boron_chestplate.name=Boron Нагрудник +item.modernmetals.boron_crackhammer.name=Boron Измельчающий Молот +item.modernmetals.boron_crossbow.name=Boron Арбалет +item.modernmetals.boron_door.name=Boron Двери +item.modernmetals.boron_fishing_rod.name=Boron Удочка +item.modernmetals.boron_gear.name=Boron Шестерня +item.modernmetals.boron_helmet.name=Boron Шлем +item.modernmetals.boron_hoe.name=Boron Мотыга +item.modernmetals.boron_horse_armor.name=Boron Конская Броня +item.modernmetals.boron_ingot.name=Boron Слиток +item.modernmetals.boron_leggings.name=Boron Поножки +item.modernmetals.boron_nugget.name=Boron Самородок +item.modernmetals.boron_ore.name=Boron Руда +item.modernmetals.boron_pickaxe.name=Boron Кирка +item.modernmetals.boron_powder.name=Измельчённый Boron +item.modernmetals.boron_rod.name=Boron Стержень +item.modernmetals.boron_shears.name=Boron Ножницы +item.modernmetals.boron_shield.name=Boron Щит +item.modernmetals.boron_shovel.name=Boron Лопата +item.modernmetals.boron_smallblend.name=Маленькая Кучка Boron Смеси +item.modernmetals.boron_smallpowder.name=Маленькая Кучка Измельчённого Boron +item.modernmetals.boron_sword.name=Boron Меч +item.modernmetals.boron_trapdoor.name=Boron Люк + +material.cadmium.name=Cadmium +item.modernmetals.cadmium_arrow.name=Cadmium Стрела +item.modernmetals.cadmium_axe.name=Cadmium Топор +item.modernmetals.cadmium_bars.name=Cadmium Прутья +item.modernmetals.cadmium_blend.name=Cadmium Смеси +item.modernmetals.cadmium_block.name=Блок Cadmium +item.modernmetals.cadmium_bolt.name=Cadmium Арбалетная Стрела +item.modernmetals.cadmium_boots.name=Cadmium Ботинки +item.modernmetals.cadmium_bow.name=Cadmium Лук +item.modernmetals.cadmium_chestplate.name=Cadmium Нагрудник +item.modernmetals.cadmium_crackhammer.name=Cadmium Измельчающий Молот +item.modernmetals.cadmium_crossbow.name=Cadmium Арбалет +item.modernmetals.cadmium_door.name=Cadmium Двери +item.modernmetals.cadmium_fishing_rod.name=Cadmium Удочка +item.modernmetals.cadmium_gear.name=Cadmium Шестерня +item.modernmetals.cadmium_helmet.name=Cadmium Шлем +item.modernmetals.cadmium_hoe.name=Cadmium Мотыга +item.modernmetals.cadmium_horse_armor.name=Cadmium Конская Броня + + +item.modernmetals.cadmium_ingot.name=Cadmium Слиток +item.modernmetals.cadmium_leggings.name=Cadmium Поножки +item.modernmetals.cadmium_nugget.name=Cadmium Самородок +item.modernmetals.cadmium_ore.name=Cadmium Руда +item.modernmetals.cadmium_pickaxe.name=Cadmium Кирка +item.modernmetals.cadmium_powder.name=Измельчённый Cadmium +item.modernmetals.cadmium_rod.name=Cadmium Стержень +item.modernmetals.cadmium_shears.name=Cadmium Ножницы +item.modernmetals.cadmium_shovel.name=Cadmium Лопата +item.modernmetals.cadmium_shield.name=Cadmium Щит +item.modernmetals.cadmium_smallblend.name=Маленькая Кучка Cadmium Смеси +item.modernmetals.cadmium_smallpowder.name=Маленькая Кучка Измельчённого Cadmium +item.modernmetals.cadmium_sword.name=Cadmium Меч +item.modernmetals.cadmium_trapdoor.name=Cadmium Люк + +material.chromium.name=Chromium +item.modernmetals.chromium_arrow.name=Chromium Стрела +item.modernmetals.chromium_axe.name=Chromium Топор +item.modernmetals.chromium_bars.name=Chromium Прутья +item.modernmetals.chromium_blend.name=Chromium Смеси +item.modernmetals.chromium_block.name=Блок Chromium +item.modernmetals.chromium_bolt.name=Chromium Арбалетная Стрела +item.modernmetals.chromium_boots.name=Chromium Ботинки +item.modernmetals.chromium_bow.name=Chromium Лук +item.modernmetals.chromium_chestplate.name=Chromium Нагрудник +item.modernmetals.chromium_crackhammer.name=Chromium Измельчающий Молот +item.modernmetals.chromium_crossbow.name=Chromium Арбалет +item.modernmetals.chromium_door.name=Chromium Двери +item.modernmetals.chromium_fishing_rod.name=Chromium Удочка +item.modernmetals.chromium_gear.name=Chromium Шестерня +item.modernmetals.chromium_helmet.name=Chromium Шлем +item.modernmetals.chromium_hoe.name=Chromium Мотыга +item.modernmetals.chromium_horse_armor.name=Chromium Конская Броня +item.modernmetals.chromium_ingot.name=Chromium Слиток +item.modernmetals.chromium_leggings.name=Chromium Поножки +item.modernmetals.chromium_nugget.name=Chromium Самородок +item.modernmetals.chromium_ore.name=Chromium Руда +item.modernmetals.chromium_pickaxe.name=Chromium Кирка +item.modernmetals.chromium_powder.name=Измельчённый Chromium +item.modernmetals.chromium_rod.name=Chromium Стержень +item.modernmetals.chromium_shears.name=Chromium Ножницы +item.modernmetals.chromium_shield.name=Chromium Щит +item.modernmetals.chromium_shovel.name=Chromium Лопата +item.modernmetals.chromium_smallblend.name=Маленькая Кучка Chromium Смеси +item.modernmetals.chromium_smallpowder.name=Маленькая Кучка Измельчённого Chromium +item.modernmetals.chromium_sword.name=Chromium Меч +item.modernmetals.chromium_trapdoor.name=Chromium Люк + +material.galvanizedsteel.name=Galvanized Steel +item.modernmetals.galvanizedsteel_arrow.name=Galvanized Steel Стрела +item.modernmetals.galvanizedsteel_axe.name=Galvanized Steel Топор +item.modernmetals.galvanizedsteel_bars.name=Galvanized Steel Прутья +item.modernmetals.galvanizedsteel_blend.name=Galvanized Steel Смеси +item.modernmetals.galvanizedsteel_block.name=Блок Galvanized Steel +item.modernmetals.galvanizedsteel_bolt.name=Galvanized Steel Арбалетная Стрела +item.modernmetals.galvanizedsteel_boots.name=Galvanized Steel Ботинки +item.modernmetals.galvanizedsteel_bow.name=Galvanized Steel Лук +item.modernmetals.galvanizedsteel_chestplate.name=Galvanized Steel Нагрудник +item.modernmetals.galvanizedsteel_crackhammer.name=Galvanized Steel Измельчающий Молот +item.modernmetals.galvanizedsteel_crossbow.name=Galvanized Steel Арбалет +item.modernmetals.galvanizedsteel_door.name=Galvanized Steel Двери +item.modernmetals.galvanizedsteel_fishing_rod.name=Galvanized Steel Удочка +item.modernmetals.galvanizedsteel_gear.name=Galvanized Steel Шестерня +item.modernmetals.galvanizedsteel_helmet.name=Galvanized Steel Шлем +item.modernmetals.galvanizedsteel_hoe.name=Galvanized Steel Мотыга +item.modernmetals.galvanizedsteel_horse_armor.name=Galvanized Steel Конская Броня +item.modernmetals.galvanizedsteel_ingot.name=Galvanized Steel Слиток +item.modernmetals.galvanizedsteel_leggings.name=Galvanized Steel Поножки +item.modernmetals.galvanizedsteel_nugget.name=Galvanized Steel Самородок +item.modernmetals.galvanizedsteel_ore.name=Galvanized Steel Руда +item.modernmetals.galvanizedsteel_pickaxe.name=Galvanized Steel Кирка +item.modernmetals.galvanizedsteel_powder.name=Измельчённый Galvanized Steel +item.modernmetals.galvanizedsteel_rod.name=Galvanized Steel Стержень +item.modernmetals.galvanizedsteel_shears.name=Galvanized Steel Ножницы +item.modernmetals.galvanizedsteel_shield.name=Galvanized Steel Щит +item.modernmetals.galvanizedsteel_shovel.name=Galvanized Steel Лопата +item.modernmetals.galvanizedsteel_smallblend.name=Маленькая Кучка Galvanized Steel Смеси +item.modernmetals.galvanizedsteel_smallpowder.name=Маленькая Кучка Измельчённого Galvanized Steel +item.modernmetals.galvanizedsteel_sword.name=Galvanized Steel Меч +item.modernmetals.galvanizedsteel_trapdoor.name=Galvanized Steel Люк + +material.iridium.name=Iridium +item.modernmetals.iridium_arrow.name=Iridium Стрела +item.modernmetals.iridium_axe.name=Iridium Топор +item.modernmetals.iridium_bars.name=Iridium Прутья +item.modernmetals.iridium_blend.name=Iridium Смеси +item.modernmetals.iridium_block.name=Блок Iridium +item.modernmetals.iridium_bolt.name=Iridium Арбалетная Стрела +item.modernmetals.iridium_boots.name=Iridium Ботинки +item.modernmetals.iridium_bow.name=Iridium Лук +item.modernmetals.iridium_chestplate.name=Iridium Нагрудник +item.modernmetals.iridium_crackhammer.name=Iridium Измельчающий Молот +item.modernmetals.iridium_crossbow.name=Iridium Арбалет +item.modernmetals.iridium_door.name=Iridium Двери +item.modernmetals.iridium_fishing_rod.name=Iridium Удочка +item.modernmetals.iridium_gear.name=Iridium Шестерня +item.modernmetals.iridium_helmet.name=Iridium Шлем +item.modernmetals.iridium_hoe.name=Iridium Мотыга +item.modernmetals.iridium_horse_armor.name=Iridium Конская Броня +item.modernmetals.iridium_ingot.name=Iridium Слиток +item.modernmetals.iridium_leggings.name=Iridium Поножки +item.modernmetals.iridium_nugget.name=Iridium Самородок +item.modernmetals.iridium_ore.name=Iridium Руда +item.modernmetals.iridium_pickaxe.name=Iridium Кирка +item.modernmetals.iridium_powder.name=Измельчённый Iridium +item.modernmetals.iridium_rod.name=Iridium Стержень +item.modernmetals.iridium_shears.name=Iridium Ножницы +item.modernmetals.iridium_shield.name=Iridium Щит +item.modernmetals.iridium_shovel.name=Iridium Лопата +item.modernmetals.iridium_smallblend.name=Маленькая Кучка Iridium Смеси +item.modernmetals.iridium_smallpowder.name=Маленькая Кучка Измельчённого Iridium +item.modernmetals.iridium_sword.name=Iridium Меч +item.modernmetals.iridium_trapdoor.name=Iridium Люк + +material.magnesium.name=Magnesium +item.modernmetals.magnesium_arrow.name=Magnesium Стрела +item.modernmetals.magnesium_axe.name=Magnesium Топор +item.modernmetals.magnesium_bars.name=Magnesium Прутья +item.modernmetals.magnesium_blend.name=Magnesium Смеси +item.modernmetals.magnesium_block.name=Блок Magnesium +item.modernmetals.magnesium_boots.name=Magnesium Ботинки +item.modernmetals.magnesium_bolt.name=Magnesium Арбалетная Стрела +item.modernmetals.magnesium_bow.name=Magnesium Лук +item.modernmetals.magnesium_chestplate.name=Magnesium Нагрудник +item.modernmetals.magnesium_crackhammer.name=Magnesium Измельчающий Молот +item.modernmetals.magnesium_crossbow.name=Magnesium Арбалет +item.modernmetals.magnesium_door.name=Magnesium Двери +item.modernmetals.magnesium_fishing_rod.name=Magnesium Удочка +item.modernmetals.magnesium_gear.name=Magnesium Шестерня +item.modernmetals.magnesium_helmet.name=Magnesium Шлем +item.modernmetals.magnesium_hoe.name=Magnesium Мотыга +item.modernmetals.magnesium_horse_armor.name=Magnesium Конская Броня +item.modernmetals.magnesium_ingot.name=Magnesium Слиток +item.modernmetals.magnesium_leggings.name=Magnesium Поножки +item.modernmetals.magnesium_nugget.name=Magnesium Самородок +item.modernmetals.magnesium_ore.name=Magnesium Руда +item.modernmetals.magnesium_pickaxe.name=Magnesium Кирка +item.modernmetals.magnesium_powder.name=Измельчённый Magnesium +item.modernmetals.magnesium_rod.name=Magnesium Стержень +item.modernmetals.magnesium_shears.name=Magnesium Ножницы +item.modernmetals.magnesium_shield.name=Magnesium Щит +item.modernmetals.magnesium_shovel.name=Magnesium Лопата +item.modernmetals.magnesium_smallblend.name=Маленькая Кучка Magnesium Смеси +item.modernmetals.magnesium_smallpowder.name=Маленькая Кучка Измельчённого Magnesium +item.modernmetals.magnesium_sword.name=Magnesium Меч +item.modernmetals.magnesium_trapdoor.name=Magnesium Люк + +material.manganese.name=Manganese +item.modernmetals.manganese_arrow.name=Manganese Стрела +item.modernmetals.manganese_axe.name=Manganese Топор +item.modernmetals.manganese_bars.name=Manganese Прутья +item.modernmetals.manganese_blend.name=Manganese Смеси +item.modernmetals.manganese_block.name=Блок Manganese +item.modernmetals.manganese_bolt.name=Manganese Арбалетная Стрела +item.modernmetals.manganese_boots.name=Manganese Ботинки +item.modernmetals.manganese_bow.name=Manganese Лук +item.modernmetals.manganese_chestplate.name=Manganese Нагрудник +item.modernmetals.manganese_crackhammer.name=Manganese Измельчающий Молот +item.modernmetals.manganese_crossbow.name=Manganese Арбалет +item.modernmetals.manganese_door.name=Manganese Двери +item.modernmetals.manganese_fishing_rod.name=Manganese Удочка +item.modernmetals.manganese_gear.name=Manganese Шестерня +item.modernmetals.manganese_helmet.name=Manganese Шлем +item.modernmetals.manganese_hoe.name=Manganese Мотыга +item.modernmetals.manganese_horse_armor.name=Manganese Конская Броня +item.modernmetals.manganese_ingot.name=Manganese Слиток +item.modernmetals.manganese_leggings.name=Manganese Поножки +item.modernmetals.manganese_nugget.name=Manganese Самородок +item.modernmetals.manganese_ore.name=Manganese Руда +item.modernmetals.manganese_pickaxe.name=Manganese Кирка +item.modernmetals.manganese_powder.name=Измельчённый Manganese +item.modernmetals.manganese_rod.name=Manganese Стержень +item.modernmetals.manganese_shears.name=Manganese Ножницы +item.modernmetals.manganese_shield.name=Manganese Щит +item.modernmetals.manganese_shovel.name=Manganese Лопата +item.modernmetals.manganese_smallblend.name=Маленькая Кучка Manganese Смеси +item.modernmetals.manganese_smallpowder.name=Маленькая Кучка Измельчённого Manganese +item.modernmetals.manganese_sword.name=Manganese Меч +item.modernmetals.manganese_trapdoor.name=Manganese Люк + +material.nichrome.name=Nichrome +item.modernmetals.nichrome_arrow.name=Nichrome Стрела +item.modernmetals.nichrome_axe.name=Nichrome Топор +item.modernmetals.nichrome_bars.name=Nichrome Прутья +item.modernmetals.nichrome_blend.name=Nichrome Смеси +item.modernmetals.nichrome_block.name=Блок Nichrome +item.modernmetals.nichrome_bolt.name=Nichrome Арбалетная Стрела +item.modernmetals.nichrome_boots.name=Nichrome Ботинки +item.modernmetals.nichrome_bow.name=Nichrome Лук +item.modernmetals.nichrome_chestplate.name=Nichrome Нагрудник +item.modernmetals.nichrome_crackhammer.name=Nichrome Измельчающий Молот +item.modernmetals.nichrome_crossbow.name=Nichrome Арбалет +item.modernmetals.nichrome_door.name=Nichrome Двери +item.modernmetals.nichrome_fishing_rod.name=Nichrome Удочка +item.modernmetals.nichrome_gear.name=Nichrome Шестерня +item.modernmetals.nichrome_helmet.name=Nichrome Шлем +item.modernmetals.nichrome_hoe.name=Nichrome Мотыга +item.modernmetals.nichrome_horse_armor.name=Nichrome Конская Броня +item.modernmetals.nichrome_ingot.name=Nichrome Слиток +item.modernmetals.nichrome_leggings.name=Nichrome Поножки +item.modernmetals.nichrome_nugget.name=Nichrome Самородок +item.modernmetals.nichrome_ore.name=Nichrome Руда +item.modernmetals.nichrome_pickaxe.name=Nichrome Кирка +item.modernmetals.nichrome_powder.name=Измельчённый Nichrome +item.modernmetals.nichrome_rod.name=Nichrome Стержень +item.modernmetals.nichrome_shears.name=Nichrome Ножницы +item.modernmetals.nichrome_shield.name=Nichrome Щит +item.modernmetals.nichrome_shovel.name=Nichrome Лопата +item.modernmetals.nichrome_smallblend.name=Маленькая Кучка Nichrome Смеси +item.modernmetals.nichrome_smallpowder.name=Маленькая Кучка Измельчённого Nichrome +item.modernmetals.nichrome_sword.name=Nichrome Меч +item.modernmetals.nichrome_trapdoor.name=Nichrome Люк + +material.osmium.name=Osmium +item.modernmetals.osmium_arrow.name=Osmium Стрела +item.modernmetals.osmium_axe.name=Osmium Топор +item.modernmetals.osmium_bars.name=Osmium Прутья +item.modernmetals.osmium_blend.name=Osmium Смеси +item.modernmetals.osmium_block.name=Блок Osmium +item.modernmetals.osmium_boots.name=Osmium Ботинки +item.modernmetals.osmium_bolt.name=Osmium Арбалетная Стрела +item.modernmetals.osmium_bow.name=Osmium Лук +item.modernmetals.osmium_chestplate.name=Osmium Нагрудник +item.modernmetals.osmium_crackhammer.name=Osmium Измельчающий Молот +item.modernmetals.osmium_crossbow.name=Osmium Арбалет +item.modernmetals.osmium_door.name=Osmium Двери +item.modernmetals.osmium_fishing_rod.name=Osmium Удочка +item.modernmetals.osmium_gear.name=Osmium Шестерня +item.modernmetals.osmium_helmet.name=Osmium Шлем +item.modernmetals.osmium_hoe.name=Osmium Мотыга +item.modernmetals.osmium_horse_armor.name=Osmium Конская Броня +item.modernmetals.osmium_ingot.name=Osmium Слиток +item.modernmetals.osmium_leggings.name=Osmium Поножки +item.modernmetals.osmium_nugget.name=Osmium Самородок +item.modernmetals.osmium_ore.name=Osmium Руда +item.modernmetals.osmium_pickaxe.name=Osmium Кирка +item.modernmetals.osmium_powder.name=Измельчённый Osmium +item.modernmetals.osmium_rod.name=Osmium Стержень +item.modernmetals.osmium_shears.name=Osmium Ножницы +item.modernmetals.osmium_shield.name=Osmium Щит +item.modernmetals.osmium_shovel.name=Osmium Лопата +item.modernmetals.osmium_smallblend.name=Маленькая Кучка Osmium Смеси +item.modernmetals.osmium_smallpowder.name=Маленькая Кучка Измельчённого Osmium +item.modernmetals.osmium_sword.name=Osmium Меч +item.modernmetals.osmium_trapdoor.name=Osmium Люк + +material.pewter.name=Pewter +item.modernmetals.pewter_arrow.name=Pewter Стрела +item.modernmetals.pewter_axe.name=Pewter Топор +item.modernmetals.pewter_bars.name=Pewter Прутья +item.modernmetals.pewter_blend.name=Pewter Смеси +item.modernmetals.pewter_block.name=Блок Pewter +item.modernmetals.pewter_boots.name=Pewter Ботинки +item.modernmetals.pewter_chestplate.name=Pewter Нагрудник +item.modernmetals.pewter_crackhammer.name=Pewter Измельчающий Молот +item.modernmetals.pewter_door.name=Pewter Двери +item.modernmetals.pewter_helmet.name=Pewter Шлем +item.modernmetals.pewter_hoe.name=Pewter Мотыга +item.modernmetals.pewter_ingot.name=Pewter Слиток +item.modernmetals.pewter_leggings.name=Pewter Поножки +item.modernmetals.pewter_nugget.name=Pewter Самородок +item.modernmetals.pewter_ore.name=Pewter Руда +item.modernmetals.pewter_pickaxe.name=Pewter Кирка +item.modernmetals.pewter_powder.name=Измельчённый Pewter +item.modernmetals.pewter_shears.name=Pewter Ножницы +item.modernmetals.pewter_shield.name=Pewter Щит +item.modernmetals.pewter_shovel.name=Pewter Лопата +item.modernmetals.pewter_smallpowder.name=Маленькая Кучка Измельчённого Pewter +item.modernmetals.pewter_sword.name=Pewter Меч +item.modernmetals.pewter_trapdoor.name=Pewter Люк + +material.plutonium.name=Plutonium +item.modernmetals.plutonium_arrow.name=Plutonium Стрела +item.modernmetals.plutonium_axe.name=Plutonium Топор +item.modernmetals.plutonium_bars.name=Plutonium Прутья +item.modernmetals.plutonium_blend.name=Plutonium Смеси +item.modernmetals.plutonium_block.name=Блок Plutonium +item.modernmetals.plutonium_bolt.name=Plutonium Арбалетная Стрела +item.modernmetals.plutonium_boots.name=Plutonium Ботинки +item.modernmetals.plutonium_bow.name=Plutonium Лук +item.modernmetals.plutonium_chestplate.name=Plutonium Нагрудник +item.modernmetals.plutonium_crackhammer.name=Plutonium Измельчающий Молот +item.modernmetals.plutonium_crossbow.name=Plutonium Арбалет +item.modernmetals.plutonium_door.name=Plutonium Двери +item.modernmetals.plutonium_fishing_rod.name=Plutonium Удочка +item.modernmetals.plutonium_gear.name=Plutonium Шестерня +item.modernmetals.plutonium_helmet.name=Plutonium Шлем +item.modernmetals.plutonium_hoe.name=Plutonium Мотыга +item.modernmetals.plutonium_horse_armor.name=Plutonium Конская Броня +item.modernmetals.plutonium_ingot.name=Plutonium Слиток +item.modernmetals.plutonium_leggings.name=Plutonium Поножки +item.modernmetals.plutonium_nugget.name=Plutonium Самородок +item.modernmetals.plutonium_ore.name=Plutonium Руда +item.modernmetals.plutonium_pickaxe.name=Plutonium Кирка +item.modernmetals.plutonium_powder.name=Измельчённый Plutonium +item.modernmetals.plutonium_rod.name=Plutonium Стержень +item.modernmetals.plutonium_shears.name=Plutonium Ножницы +item.modernmetals.plutonium_shield.name=Plutonium Щит +item.modernmetals.plutonium_shovel.name=Plutonium Лопата +item.modernmetals.plutonium_smallblend.name=Маленькая Кучка Plutonium Смеси +item.modernmetals.plutonium_smallpowder.name=Маленькая Кучка Измельчённого Plutonium +item.modernmetals.plutonium_sword.name=Plutonium Меч +item.modernmetals.plutonium_trapdoor.name=Plutonium Люк + +material.rutile.name=Rutile +item.modernmetals.rutile_arrow.name=Rutile Стрела +item.modernmetals.rutile_axe.name=Rutile Топор +item.modernmetals.rutile_bars.name=Rutile Прутья +item.modernmetals.rutile_blend.name=Rutile Смеси +item.modernmetals.rutile_block.name=Блок Rutile +item.modernmetals.rutile_boots.name=Rutile Ботинки +item.modernmetals.rutile_bolt.name=Rutile Арбалетная Стрела +item.modernmetals.rutile_bow.name=Rutile Лук +item.modernmetals.rutile_chestplate.name=Rutile Нагрудник +item.modernmetals.rutile_crackhammer.name=Rutile Измельчающий Молот +item.modernmetals.rutile_crossbow.name=Rutile Арбалет +item.modernmetals.rutile_door.name=Rutile Двери +item.modernmetals.rutile_fishing_rod.name=Rutile Удочка +item.modernmetals.rutile_gear.name=Rutile Шестерня +item.modernmetals.rutile_helmet.name=Rutile Шлем +item.modernmetals.rutile_hoe.name=Rutile Мотыга +item.modernmetals.rutile_horse_armor.name=Rutile Конская Броня +item.modernmetals.rutile_ingot.name=Rutile Слиток +item.modernmetals.rutile_leggings.name=Rutile Поножки +item.modernmetals.rutile_nugget.name=Rutile Самородок +item.modernmetals.rutile_ore.name=Rutile Руда +item.modernmetals.rutile_pickaxe.name=Rutile Кирка +item.modernmetals.rutile_powder.name=Измельчённый Rutile +item.modernmetals.rutile_rod.name=Rutile Стержень +item.modernmetals.rutile_shears.name=Rutile Ножницы +item.modernmetals.rutile_shield.name=Rutile Щит +item.modernmetals.rutile_shovel.name=Rutile Лопата +item.modernmetals.rutile_smallblend.name=Маленькая Кучка Rutile Смеси +item.modernmetals.rutile_smallpowder.name=Маленькая Кучка Измельчённого Rutile +item.modernmetals.rutile_sword.name=Rutile Меч +item.modernmetals.rutile_trapdoor.name=Rutile Люк + +material.stainlesssteel.name=Stainless Steel +item.modernmetals.stainlesssteel_arrow.name=Stainless Steel Стрела +item.modernmetals.stainlesssteel_axe.name=Stainless Steel Топор +item.modernmetals.stainlesssteel_bars.name=Stainless Steel Прутья +item.modernmetals.stainlesssteel_blend.name=Stainless Steel Смеси +item.modernmetals.stainlesssteel_block.name=Блок Stainless Steel +item.modernmetals.stainlesssteel_bolt.name=Stainless Steel Арбалетная Стрела +item.modernmetals.stainlesssteel_boots.name=Stainless Steel Ботинки +item.modernmetals.stainlesssteel_bow.name=Stainless Steel Лук +item.modernmetals.stainlesssteel_chestplate.name=Stainless Steel Нагрудник +item.modernmetals.stainlesssteel_crackhammer.name=Stainless Steel Измельчающий Молот +item.modernmetals.stainlesssteel_crossbow.name=Stainless Steel Арбалет +item.modernmetals.stainlesssteel_door.name=Stainless Steel Двери +item.modernmetals.stainlesssteel_fishing_rod.name=Stainless Steel Удочка +item.modernmetals.stainlesssteel_gear.name=Stainless Steel Шестерня +item.modernmetals.stainlesssteel_helmet.name=Stainless Steel Шлем +item.modernmetals.stainlesssteel_hoe.name=Stainless Steel Мотыга +item.modernmetals.stainlesssteel_horse_armor.name=Stainless Steel Конская Броня +item.modernmetals.stainlesssteel_ingot.name=Stainless Steel Слиток +item.modernmetals.stainlesssteel_leggings.name=Stainless Steel Поножки +item.modernmetals.stainlesssteel_nugget.name=Stainless Steel Самородок +item.modernmetals.stainlesssteel_ore.name=Stainless Steel Руда +item.modernmetals.stainlesssteel_pickaxe.name=Stainless Steel Кирка +item.modernmetals.stainlesssteel_powder.name=Измельчённый Stainless Steel +item.modernmetals.stainlesssteel_rod.name=Stainless Steel Стержень +item.modernmetals.stainlesssteel_shears.name=Stainless Steel Ножницы +item.modernmetals.stainlesssteel_shield.name=Stainless Steel Щит +item.modernmetals.stainlesssteel_shovel.name=Stainless Steel Лопата +item.modernmetals.stainlesssteel_smallblend.name=Маленькая Кучка Stainless Steel Смеси +item.modernmetals.stainlesssteel_smallpowder.name=Маленькая Кучка Измельчённого Stainless Steel +item.modernmetals.stainlesssteel_sword.name=Stainless Steel Меч +item.modernmetals.stainlesssteel_trapdoor.name=Stainless Steel Люк + +material.tantalum.name=Tantalum +item.modernmetals.tantalum_arrow.name=Tantalum Стрела +item.modernmetals.tantalum_axe.name=Tantalum Топор +item.modernmetals.tantalum_bars.name=Tantalum Прутья +item.modernmetals.tantalum_blend.name=Tantalum Смеси +item.modernmetals.tantalum_block.name=Блок Tantalum +item.modernmetals.tantalum_bolt.name=Tantalum Арбалетная Стрела +item.modernmetals.tantalum_boots.name=Tantalum Ботинки +item.modernmetals.tantalum_bow.name=Tantalum Лук +item.modernmetals.tantalum_chestplate.name=Tantalum Нагрудник +item.modernmetals.tantalum_crackhammer.name=Tantalum Измельчающий Молот +item.modernmetals.tantalum_crossbow.name=Tantalum Арбалет +item.modernmetals.tantalum_door.name=Tantalum Двери +item.modernmetals.tantalum_fishing_rod.name=Tantalum Удочка +item.modernmetals.tantalum_gear.name=Tantalum Шестерня +item.modernmetals.tantalum_helmet.name=Tantalum Шлем +item.modernmetals.tantalum_hoe.name=Tantalum Мотыга +item.modernmetals.tantalum_horse_armor.name=Tantalum Конская Броня +item.modernmetals.tantalum_ingot.name=Tantalum Слиток +item.modernmetals.tantalum_leggings.name=Tantalum Поножки +item.modernmetals.tantalum_nugget.name=Tantalum Самородок +item.modernmetals.tantalum_ore.name=Tantalum Руда +item.modernmetals.tantalum_pickaxe.name=Tantalum Кирка +item.modernmetals.tantalum_powder.name=Измельчённый Tantalum +item.modernmetals.tantalum_rod.name=Tantalum Стержень +item.modernmetals.tantalum_shears.name=Tantalum Ножницы +item.modernmetals.tantalum_shield.name=Tantalum Щит +item.modernmetals.tantalum_shovel.name=Tantalum Лопата +item.modernmetals.tantalum_smallblend.name=Маленькая Кучка Tantalum Смеси +item.modernmetals.tantalum_smallpowder.name=Маленькая Кучка Измельчённого Tantalum +item.modernmetals.tantalum_sword.name=Tantalum Меч +item.modernmetals.tantalum_trapdoor.name=Tantalum Люк + +material.titanium.name=Titanium +item.modernmetals.titanium_arrow.name=Titanium Стрела +item.modernmetals.titanium_axe.name=Titanium Топор +item.modernmetals.titanium_bars.name=Titanium Прутья +item.modernmetals.titanium_blend.name=Titanium Смеси +item.modernmetals.titanium_block.name=Блок Titanium +item.modernmetals.titanium_boots.name=Titanium Ботинки +item.modernmetals.titanium_bolt.name=Titanium Арбалетная Стрела +item.modernmetals.titanium_bow.name=Titanium Лук +item.modernmetals.titanium_chestplate.name=Titanium Нагрудник +item.modernmetals.titanium_crackhammer.name=Titanium Измельчающий Молот +item.modernmetals.titanium_crossbow.name=Titanium Арбалет +item.modernmetals.titanium_door.name=Titanium Двери +item.modernmetals.titanium_fishing_rod.name=Titanium Удочка +item.modernmetals.titanium_gear.name=Titanium Шестерня +item.modernmetals.titanium_helmet.name=Titanium Шлем +item.modernmetals.titanium_hoe.name=Titanium Мотыга +item.modernmetals.titanium_horse_armor.name=Titanium Конская Броня +item.modernmetals.titanium_ingot.name=Titanium Слиток +item.modernmetals.titanium_leggings.name=Titanium Поножки +item.modernmetals.titanium_nugget.name=Titanium Самородок +item.modernmetals.titanium_ore.name=Titanium Руда +item.modernmetals.titanium_pickaxe.name=Titanium Кирка +item.modernmetals.titanium_powder.name=Измельчённый Titanium +item.modernmetals.titanium_rod.name=Titanium Стержень +item.modernmetals.titanium_shears.name=Titanium Ножницы +item.modernmetals.titanium_shield.name=Titanium Щит +item.modernmetals.titanium_shovel.name=Titanium Лопата +item.modernmetals.titanium_smallblend.name=Маленькая Кучка Titanium Смеси +item.modernmetals.titanium_smallpowder.name=Маленькая Кучка Измельчённого Titanium +item.modernmetals.titanium_sword.name=Titanium Меч +item.modernmetals.titanium_trapdoor.name=Titanium Люк + +material.tungsten.name=Tungsten +item.modernmetals.tungsten_arrow.name=Tungsten Стрела +item.modernmetals.tungsten_axe.name=Tungsten Топор +item.modernmetals.tungsten_bars.name=Tungsten Прутья +item.modernmetals.tungsten_blend.name=Tungsten Смеси +item.modernmetals.tungsten_block.name=Блок Tungsten +item.modernmetals.tungsten_boots.name=Tungsten Ботинки +item.modernmetals.tungsten_bolt.name=Tungsten Арбалетная Стрела +item.modernmetals.tungsten_bow.name=Tungsten Лук +item.modernmetals.tungsten_chestplate.name=Tungsten Нагрудник +item.modernmetals.tungsten_crackhammer.name=Tungsten Измельчающий Молот +item.modernmetals.tungsten_crossbow.name=Tungsten Арбалет +item.modernmetals.tungsten_door.name=Tungsten Двери +item.modernmetals.tungsten_fishing_rod.name=Tungsten Удочка +item.modernmetals.tungsten_gear.name=Tungsten Шестерня +item.modernmetals.tungsten_helmet.name=Tungsten Шлем +item.modernmetals.tungsten_hoe.name=Tungsten Мотыга +item.modernmetals.tungsten_horse_armor.name=Tungsten Конская Броня +item.modernmetals.tungsten_ingot.name=Tungsten Слиток +item.modernmetals.tungsten_leggings.name=Tungsten Поножки +item.modernmetals.tungsten_nugget.name=Tungsten Самородок +item.modernmetals.tungsten_ore.name=Tungsten Руда +item.modernmetals.tungsten_pickaxe.name=Tungsten Кирка +item.modernmetals.tungsten_powder.name=Измельчённый Tungsten +item.modernmetals.tungsten_rod.name=Tungsten Стержень +item.modernmetals.tungsten_shears.name=Tungsten Ножницы +item.modernmetals.tungsten_shield.name=Tungsten Щит +item.modernmetals.tungsten_shovel.name=Tungsten Лопата +item.modernmetals.tungsten_smallblend.name=Маленькая Кучка Tungsten Смеси +item.modernmetals.tungsten_smallpowder.name=Маленькая Кучка Измельчённого Tungsten +item.modernmetals.tungsten_sword.name=Tungsten Меч +item.modernmetals.tungsten_trapdoor.name=Tungsten Люк + +material.uranium.name=Uranium +item.modernmetals.uranium_arrow.name=Uranium Стрела +item.modernmetals.uranium_axe.name=Uranium Топор +item.modernmetals.uranium_bars.name=Uranium Прутья +item.modernmetals.uranium_blend.name=Uranium Смеси +item.modernmetals.uranium_block.name=Блок Uranium +item.modernmetals.uranium_boots.name=Uranium Ботинки +item.modernmetals.uranium_bolt.name=Uranium Арбалетная Стрела +item.modernmetals.uranium_bow.name=Uranium Лук +item.modernmetals.uranium_chestplate.name=Uranium Нагрудник +item.modernmetals.uranium_crackhammer.name=Uranium Измельчающий Молот +item.modernmetals.uranium_crossbow.name=Uranium Арбалет +item.modernmetals.uranium_door.name=Uranium Двери +item.modernmetals.uranium_fishing_rod.name=Uranium Удочка +item.modernmetals.uranium_gear.name=Uranium Шестерня +item.modernmetals.uranium_helmet.name=Uranium Шлем +item.modernmetals.uranium_hoe.name=Uranium Мотыга +item.modernmetals.uranium_horse_armor.name=Uranium Конская Броня +item.modernmetals.uranium_ingot.name=Uranium Слиток +item.modernmetals.uranium_leggings.name=Uranium Поножки +item.modernmetals.uranium_nugget.name=Uranium Самородок +item.modernmetals.uranium_ore.name=Uranium Руда +item.modernmetals.uranium_pickaxe.name=Uranium Кирка +item.modernmetals.uranium_powder.name=Измельчённый Uranium +item.modernmetals.uranium_rod.name=Uranium Стержень +item.modernmetals.uranium_shears.name=Uranium Ножницы +item.modernmetals.uranium_shield.name=Uranium Щит +item.modernmetals.uranium_shovel.name=Uranium Лопата +item.modernmetals.uranium_smallblend.name=Маленькая Кучка Uranium Смеси +item.modernmetals.uranium_smallpowder.name=Маленькая Кучка Измельчённого Uranium +item.modernmetals.uranium_sword.name=Uranium Меч +item.modernmetals.uranium_trapdoor.name=Uranium Люк + +material.zirconium.name=Zirconium +item.modernmetals.zirconium_arrow.name=Zirconium Стрела +item.modernmetals.zirconium_axe.name=Zirconium Топор +item.modernmetals.zirconium_bars.name=Zirconium Прутья +item.modernmetals.zirconium_blend.name=Zirconium Смеси +item.modernmetals.zirconium_block.name=Блок Zirconium +item.modernmetals.zirconium_bolt.name=Zirconium Арбалетная Стрела +item.modernmetals.zirconium_boots.name=Zirconium Ботинки +item.modernmetals.zirconium_bow.name=Zirconium Лук +item.modernmetals.zirconium_chestplate.name=Zirconium Нагрудник +item.modernmetals.zirconium_crackhammer.name=Zirconium Измельчающий Молот +item.modernmetals.zirconium_crossbow.name=Zirconium Арбалет +item.modernmetals.zirconium_door.name=Zirconium Двери +item.modernmetals.zirconium_fishing_rod.name=Zirconium Удочка +item.modernmetals.zirconium_gear.name=Zirconium Шестерня +item.modernmetals.zirconium_helmet.name=Zirconium Шлем +item.modernmetals.zirconium_hoe.name=Zirconium Мотыга +item.modernmetals.zirconium_horse_armor.name=Zirconium Конская Броня +item.modernmetals.zirconium_ingot.name=Zirconium Слиток +item.modernmetals.zirconium_leggings.name=Zirconium Поножки +item.modernmetals.zirconium_nugget.name=Zirconium Самородок +item.modernmetals.zirconium_ore.name=Zirconium Руда +item.modernmetals.zirconium_pickaxe.name=Zirconium Кирка +item.modernmetals.zirconium_powder.name=Измельчённый Zirconium +item.modernmetals.zirconium_rod.name=Zirconium Стержень +item.modernmetals.zirconium_shears.name=Zirconium Ножницы +item.modernmetals.zirconium_shield.name=Zirconium Щит +item.modernmetals.zirconium_shovel.name=Zirconium Лопата +item.modernmetals.zirconium_smallblend.name=Маленькая Кучка Zirconium Смеси +item.modernmetals.zirconium_smallpowder.name=Маленькая Кучка Измельчённого Zirconium +item.modernmetals.zirconium_sword.name=Zirconium Меч +item.modernmetals.zirconium_trapdoor.name=Zirconium Люк + +tile.modernmetals.aluminum_bars.name=Алюминиевый Прутья +tile.modernmetals.aluminum_block.name=Блок Алюминиевый +tile.modernmetals.aluminum_door.name=Алюминиевый Двери +tile.modernmetals.aluminum_ore.name=Алюминиевый Руда +tile.modernmetals.aluminum_plate.name=Алюминиевый Plate +tile.modernmetals.aluminum_trapdoor.name=Алюминиевый Люк + +tile.modernmetals.aluminumbrass_bars.name=Алюминиево-Латунный Прутья +tile.modernmetals.aluminumbrass_block.name=Блок Алюминиево-Латунный +tile.modernmetals.aluminumbrass_door.name=Алюминиево-Латунный Двери +tile.modernmetals.aluminumbrass_ore.name=Алюминиево-Латунный Руда +tile.modernmetals.aluminumbrass_plate.name=Алюминиево-Латунный Plate +tile.modernmetals.aluminumbrass_trapdoor.name=Алюминиево-Латунный Люк + +tile.modernmetals.boron_bars.name=Boron Прутья +tile.modernmetals.boron_block.name=Блок Boron +tile.modernmetals.boron_door.name=Boron Двери +tile.modernmetals.boron_ore.name=Boron Руда +tile.modernmetals.boron_plate.name=Boron Plate +tile.modernmetals.boron_trapdoor.name=Boron Люк + +tile.modernmetals.cadmium_bars.name=Cadmium Прутья +tile.modernmetals.cadmium_block.name=Блок Cadmium +tile.modernmetals.cadmium_door.name=Cadmium Двери +tile.modernmetals.cadmium_ore.name=Cadmium Руда +tile.modernmetals.cadmium_plate.name=Cadmium Plate +tile.modernmetals.cadmium_trapdoor.name=Cadmium Люк + +tile.modernmetals.chromium_bars.name=Chromium Прутья +tile.modernmetals.chromium_block.name=Блок Chromium +tile.modernmetals.chromium_door.name=Chromium Двери +tile.modernmetals.chromium_ore.name=Chromium Руда +tile.modernmetals.chromium_plate.name=Chromium Plate +tile.modernmetals.chromium_trapdoor.name=Chromium Люк + +tile.modernmetals.galvanizedsteel_bars.name=Galvanized Steel Прутья +tile.modernmetals.galvanizedsteel_block.name=Блок Galvanized Steel +tile.modernmetals.galvanizedsteel_door.name=Galvanized Steel Двери +tile.modernmetals.galvanizedsteel_ore.name=Galvanized Steel Руда +tile.modernmetals.galvanizedsteel_plate.name=Galvanized Steel Plate +tile.modernmetals.galvanizedsteel_trapdoor.name=Galvanized Steel Люк + +tile.modernmetals.iridium_bars.name=Iridium Прутья +tile.modernmetals.iridium_block.name=Блок Iridium +tile.modernmetals.iridium_door.name=Iridium Двери +tile.modernmetals.iridium_ore.name=Iridium Руда +tile.modernmetals.iridium_plate.name=Iridium Plate +tile.modernmetals.iridium_trapdoor.name=Iridium Люк + +tile.modernmetals.magnesium_bars.name=Magnesium Прутья +tile.modernmetals.magnesium_block.name=Блок Magnesium +tile.modernmetals.magnesium_door.name=Magnesium Двери +tile.modernmetals.magnesium_ore.name=Magnesium Руда +tile.modernmetals.magnesium_plate.name=Magnesium Plate +tile.modernmetals.magnesium_trapdoor.name=Magnesium Люк + +tile.modernmetals.manganese_bars.name=Manganese Прутья +tile.modernmetals.manganese_block.name=Блок Manganese +tile.modernmetals.manganese_door.name=Manganese Двери +tile.modernmetals.manganese_ore.name=Manganese Руда +tile.modernmetals.manganese_plate.name=Manganese Plate +tile.modernmetals.manganese_trapdoor.name=Manganese Люк + +tile.modernmetals.nichrome_bars.name=Nichrome Прутья +tile.modernmetals.nichrome_block.name=Блок Nichrome +tile.modernmetals.nichrome_door.name=Nichrome Двери +tile.modernmetals.nichrome_ore.name=Nichrome Руда +tile.modernmetals.nichrome_plate.name=Nichrome Plate +tile.modernmetals.nichrome_trapdoor.name=Nichrome Люк + +tile.modernmetals.osmium_bars.name=Osmium Прутья +tile.modernmetals.osmium_block.name=Блок Osmium +tile.modernmetals.osmium_door.name=Osmium Двери +tile.modernmetals.osmium_ore.name=Osmium Руда +tile.modernmetals.osmium_plate.name=Osmium Plate +tile.modernmetals.osmium_trapdoor.name=Osmium Люк + +tile.modernmetals.plutonium_bars.name=Plutonium Прутья +tile.modernmetals.plutonium_block.name=Блок Plutonium +tile.modernmetals.plutonium_door.name=Plutonium Двери +tile.modernmetals.plutonium_ore.name=Plutonium Руда +tile.modernmetals.plutonium_plate.name=Plutonium Plate +tile.modernmetals.plutonium_trapdoor.name=Plutonium Люк + +tile.modernmetals.rutile_bars.name=Rutile Прутья +tile.modernmetals.rutile_block.name=Блок Rutile +tile.modernmetals.rutile_door.name=Rutile Двери +tile.modernmetals.rutile_ore.name=Rutile Руда +tile.modernmetals.rutile_plate.name=Rutile Plate +tile.modernmetals.rutile_trapdoor.name=Rutile Люк + +tile.modernmetals.stainlesssteel_bars.name=Stainless Steel Прутья +tile.modernmetals.stainlesssteel_block.name=Блок Stainless Steel +tile.modernmetals.stainlesssteel_door.name=Stainless Steel Двери +tile.modernmetals.stainlesssteel_ore.name=Stainless Steel Руда +tile.modernmetals.stainlesssteel_plate.name=Stainless Steel Plate +tile.modernmetals.stainlesssteel_trapdoor.name=Stainless Steel Люк + +tile.modernmetals.tantalum_bars.name=Tantalum Прутья +tile.modernmetals.tantalum_block.name=Блок Tantalum +tile.modernmetals.tantalum_door.name=Tantalum Двери +tile.modernmetals.tantalum_ore.name=Tantalum Руда +tile.modernmetals.tantalum_plate.name=Tantalum Plate +tile.modernmetals.tantalum_trapdoor.name=Tantalum Люк + +tile.modernmetals.titanium_bars.name=Titanium Прутья +tile.modernmetals.titanium_block.name=Блок Titanium +tile.modernmetals.titanium_door.name=Titanium Двери +tile.modernmetals.titanium_ore.name=Titanium Руда +tile.modernmetals.titanium_plate.name=Titanium Plate +tile.modernmetals.titanium_trapdoor.name=Titanium Люк + +tile.modernmetals.tungsten_bars.name=Tungsten Прутья +tile.modernmetals.tungsten_block.name=Блок Tungsten +tile.modernmetals.tungsten_door.name=Tungsten Двери +tile.modernmetals.tungsten_ore.name=Tungsten Руда +tile.modernmetals.tungsten_plate.name=Tungsten Plate +tile.modernmetals.tungsten_trapdoor.name=Tungsten Люк + +tile.modernmetals.uranium_bars.name=Uranium Прутья +tile.modernmetals.uranium_block.name=Блок Uranium +tile.modernmetals.uranium_door.name=Uranium Двери +tile.modernmetals.uranium_ore.name=Uranium Руда +tile.modernmetals.uranium_plate.name=Uranium Plate +tile.modernmetals.uranium_trapdoor.name=Uranium Люк + +tile.modernmetals.zirconium_bars.name=Zirconium Прутья +tile.modernmetals.zirconium_block.name=Блок Zirconium +tile.modernmetals.zirconium_door.name=Zirconium Двери +tile.modernmetals.zirconium_ore.name=Zirconium Руда +tile.modernmetals.zirconium_plate.name=Zirconium Plate +tile.modernmetals.zirconium_trapdoor.name=Zirconium Люк + +tooltip.aluminum.tool=Nothing Special +tooltip.aluminumbrass.tool=Nothing Special +tooltip.cadmium.tool=Nothing Special +tooltip.chromium.tool=Nothing Special +tooltip.galvanized_steel.tool=Nothing Special +tooltip.iridium.tool=Nothing Special +tooltip.magnesium.tool=Nothing Special +tooltip.manganese.tool=Nothing Special +tooltip.nichrome.tool=Nothing Special +tooltip.osmium.tool=Nothing Special +tooltip.plutonium.tool=Nothing Special +tooltip.rutile.tool=Nothing Special +tooltip.stainless_steel.tool=Nothing Special +tooltip.tantalum.tool=Nothing Special +tooltip.titanium.tool=Nothing Special +tooltip.tungsten.tool=Nothing Special +tooltip.uranium.tool=Nothing Special +tooltip.zirconium.tool=Nothing Special + +tooltip.aluminum.armor=Nothing Special +tooltip.aluminumbrass.armor=Nothing Special +tooltip.cadmium.armor=Nothing Special +tooltip.chromium.armor=Nothing Special +tooltip.galvanized_steel.armor=Nothing Special +tooltip.iridium.armor=Nothing Special +tooltip.magnesium.armor=Nothing Special +tooltip.manganese.armor=Nothing Special +tooltip.nichrome.armor=Nothing Special +tooltip.osmium.armor=Nothing Special +tooltip.plutonium.armor=Nothing Special +tooltip.rutile.armor=Nothing Special +tooltip.stainless_steel.armor=Nothing Special +tooltip.tantalum.armor=Nothing Special +tooltip.titanium.armor=Nothing Special +tooltip.tungsten.armor=Nothing Special +tooltip.uranium.armor=Nothing Special +tooltip.zirconium.armor=Nothing Special + +achievement.aluminumbrass_maker=Алюминиево-Латунный +achievement.galvanized_steel_maker=Galvanized Steel +achievement.nichrome_maker=Nichrome +achievement.stainless_steel_maker=Stainless Steel +achievement.titanium_maker=Titanium +achievement.aluminumbrass_maker.desc=Combine powdered Алюминиевый and Brass to make Алюминиево-Латунный +achievement.galvanized_steel_maker.desc=Combine powdered Steel and Zinc to make Galvanized Steel +achievement.nichrome_maker.desc=Combine powdered Nickel and Chrome to make Nichrome +achievement.stainless_steel_maker.desc=Combine powdered Steel and Chrome to make Stainless Steel +achievement.titanium_maker.desc=Combine powdered Rutile and Magnesium to make Titanium + +fluid.modernmetals.aluminum=Molten Алюминиевый +fluid.modernmetals.aluminumbrass=Molten Алюминиево-Латунный +fluid.modernmetals.boron=Molten Boron +fluid.modernmetals.beryllium=Molten Beryllium +fluid.modernmetals.cadmium=Molten Cadmium +fluid.modernmetals.chromium=Molten Chromium +fluid.modernmetals.galvanizedsteel=Molten Galvanized Steel +fluid.modernmetals.iridium=Molten Iridium +fluid.modernmetals.magnesium=Molten Magnesium +fluid.modernmetals.manganese=Molten Manganese +fluid.modernmetals.nichrome=Molten Nichrome +fluid.modernmetals.osmium=Molten Osmium +fluid.modernmetals.plutonium=Molten Plutonium +fluid.modernmetals.rutile=Molten Rutile +fluid.modernmetals.stainlesssteel=Molten Stainless Steel +fluid.modernmetals.tantalum=Molten Tantalum +fluid.modernmetals.titanium=Molten Titanium +fluid.modernmetals.tungsten=Molten Tungsten +fluid.modernmetals.uranium=Molten Uranium +fluid.modernmetals.zirconium=Molten Zirconium + +fluid.modernmetals.aluminum.name=Molten Алюминиевый +fluid.modernmetals.aluminumbrass.name=Molten Алюминиево-Латунный +fluid.modernmetals.boron.name=Molten Boron +fluid.modernmetals.beryllium.name=Molten Beryllium +fluid.modernmetals.cadmium.name=Molten Cadmium +fluid.modernmetals.chromium.name=Molten Chromium +fluid.modernmetals.galvanizedsteel.name=Molten Galvanized Steel +fluid.modernmetals.iridium.name=Molten Iridium +fluid.modernmetals.magnesium.name=Molten Magnesium +fluid.modernmetals.manganese.name=Molten Manganese +fluid.modernmetals.nichrome.name=Molten Nichrome +fluid.modernmetals.osmium.name=Molten Osmium +fluid.modernmetals.plutonium.name=Molten Plutonium +fluid.modernmetals.rutile.name=Molten Rutile +fluid.modernmetals.stainlesssteel.name=Molten Stainless Steel +fluid.modernmetals.tantalum.name=Molten Tantalum +fluid.modernmetals.titanium.name=Molten Titanium +fluid.modernmetals.tungsten.name=Molten Tungsten +fluid.modernmetals.uranium.name=Molten Uranium +fluid.modernmetals.zirconium.name=Molten Zirconium + +tile.modernmetals.aluminum.name=Molten Алюминиевый +tile.modernmetals.aluminumbrass.name=Molten Алюминиево-Латунный +tile.modernmetals.boron.name=Molten Boron +tile.modernmetals.cadmium.name=Molten Cadmium +tile.modernmetals.chromium.name=Molten Chromium +tile.modernmetals.galvanizedsteel.name=Molten Galvanized Steel +tile.modernmetals.iridium.name=Molten Iridium +tile.modernmetals.magnesium.name=Molten Magnesium +tile.modernmetals.manganese.name=Molten Manganese +tile.modernmetals.nichrome.name=Molten Nichrome +tile.modernmetals.osmium.name=Molten Osmium +tile.modernmetals.plutonium.name=Molten Plutonium +tile.modernmetals.rutile.name=Molten Rutile +tile.modernmetals.stainlesssteel.name=Molten Stainless Steel +tile.modernmetals.tantalum.name=Molten Tantalum +tile.modernmetals.titanium.name=Molten Titanium +tile.modernmetals.tungsten.name=Molten Tungsten +tile.modernmetals.uranium.name=Molten Uranium +tile.modernmetals.zirconium.name=Molten Zirconium + +item.modernmetals.aluminum_clump.name=Алюминиевый Clump +item.modernmetals.aluminum_crushed.name=Crushed Алюминиевый Руда +item.modernmetals.aluminum_crushed_purified.name=Purified Crushed Алюминиевый Руда +item.modernmetals.aluminum_crystal.name=Алюминиевый Crystal +item.modernmetals.aluminum_dense_plate.name=Dense Алюминиевый Plate +item.modernmetals.aluminum_powder_dirty.name=Dirty Алюминиевый Dust +item.modernmetals.aluminum_shard.name=Алюминиевый Shard + +item.modernmetals.aluminum_button.name=Алюминиевый Button +item.modernmetals.aluminum_lever.name=Алюминиевый Lever +item.modernmetals.aluminum_pressure_plate.name=Алюминиевый Pressure Plate +item.modernmetals.aluminum_slab.name=Алюминиевый Slab +item.modernmetals.aluminum_stairs.name=Алюминиевый Stairs +item.modernmetals.aluminum_wall.name=Алюминиевый Wall + +tile.modernmetals.aluminum_button.name=Алюминиевый Button +tile.modernmetals.aluminum_lever.name=Алюминиевый Lever +tile.modernmetals.aluminum_pressure_plate.name=Алюминиевый Pressure Plate +tile.modernmetals.aluminum_slab.name=Алюминиевый Slab +tile.modernmetals.aluminum_stairs.name=Алюминиевый Stairs +tile.modernmetals.aluminum_wall.name=Алюминиевый Wall + + +item.modernmetals.aluminumbrass_clump.name=Алюминиево-Латунный Clump +item.modernmetals.aluminumbrass_crushed.name=Crushed Алюминиево-Латунный Руда +item.modernmetals.aluminumbrass_crushed_purified.name=Purified Crushed Алюминиево-Латунный Руда +item.modernmetals.aluminumbrass_crystal.name=Алюминиево-Латунный Crystal +item.modernmetals.aluminumbrass_dense_plate.name=Dense Алюминиево-Латунный Plate +item.modernmetals.aluminumbrass_powder_dirty.name=Dirty Алюминиево-Латунный Dust +item.modernmetals.aluminumbrass_shard.name=Алюминиево-Латунный Shard + +item.modernmetals.aluminumbrass_button.name=Алюминиево-Латунный Button +item.modernmetals.aluminumbrass_lever.name=Алюминиево-Латунный Lever +item.modernmetals.aluminumbrass_pressure_plate.name=Алюминиево-Латунный Pressure Plate +item.modernmetals.aluminumbrass_slab.name=Алюминиево-Латунный Slab +item.modernmetals.aluminumbrass_stairs.name=Алюминиево-Латунный Stairs +item.modernmetals.aluminumbrass_wall.name=Алюминиево-Латунный Wall + +tile.modernmetals.aluminumbrass_button.name=Алюминиево-Латунный Button +tile.modernmetals.aluminumbrass_lever.name=Алюминиево-Латунный Lever +tile.modernmetals.aluminumbrass_pressure_plate.name=Алюминиево-Латунный Pressure Plate +tile.modernmetals.aluminumbrass_slab.name=Алюминиево-Латунный Slab +tile.modernmetals.aluminumbrass_stairs.name=Алюминиево-Латунный Stairs +tile.modernmetals.aluminumbrass_wall.name=Алюминиево-Латунный Wall + + +tile.modernmetals.beryllium_bars.name=Beryllium Прутья +tile.modernmetals.beryllium_block.name=Блок Beryllium +tile.modernmetals.beryllium_door.name=Beryllium Двери +tile.modernmetals.beryllium_ore.name=Beryllium Руда +tile.modernmetals.beryllium_plate.name=Beryllium Plate +tile.modernmetals.beryllium_trapdoor.name=Beryllium Люк + +item.modernmetals.beryllium_clump.name=Beryllium Clump +item.modernmetals.beryllium_crushed.name=Crushed Beryllium Руда +item.modernmetals.beryllium_crushed_purified.name=Purified Crushed Beryllium Руда +item.modernmetals.beryllium_crystal.name=Beryllium Crystal +item.modernmetals.beryllium_dense_plate.name=Dense Beryllium Plate +item.modernmetals.beryllium_powder_dirty.name=Dirty Beryllium Dust +item.modernmetals.beryllium_shard.name=Beryllium Shard +item.modernmetals.beryllium_button.name=Beryllium Button +item.modernmetals.beryllium_lever.name=Beryllium Lever +item.modernmetals.beryllium_pressure_plate.name=Beryllium Pressure Plate +item.modernmetals.beryllium_slab.name=Beryllium Slab +item.modernmetals.beryllium_stairs.name=Beryllium Stairs +item.modernmetals.beryllium_wall.name=Beryllium Wall + + +tile.modernmetals.beryllium.name=Molten Beryllium +tile.modernmetals.beryllium_button.name=Beryllium Button +tile.modernmetals.beryllium_lever.name=Beryllium Lever +tile.modernmetals.beryllium_pressure_plate.name=Beryllium Pressure Plate +tile.modernmetals.beryllium_slab.name=Beryllium Slab +tile.modernmetals.beryllium_stairs.name=Beryllium Stairs +tile.modernmetals.beryllium_wall.name=Beryllium Wall + + +item.modernmetals.boron_clump.name=Boron Clump +item.modernmetals.boron_crushed.name=Crushed Boron Руда +item.modernmetals.boron_crushed_purified.name=Purified Crushed Boron Руда +item.modernmetals.boron_crystal.name=Boron Crystal +item.modernmetals.boron_dense_plate.name=Dense Boron Plate +item.modernmetals.boron_powder_dirty.name=Dirty Boron Dust +item.modernmetals.boron_shard.name=Boron Shard + +item.modernmetals.boron_button.name=Boron Button +item.modernmetals.boron_lever.name=Boron Lever +item.modernmetals.boron_pressure_plate.name=Boron Pressure Plate +item.modernmetals.boron_slab.name=Boron Slab +item.modernmetals.boron_stairs.name=Boron Stairs +item.modernmetals.boron_wall.name=Boron Wall + +tile.modernmetals.boron_button.name=Boron Button +tile.modernmetals.boron_lever.name=Boron Lever +tile.modernmetals.boron_pressure_plate.name=Boron Pressure Plate +tile.modernmetals.boron_slab.name=Boron Slab +tile.modernmetals.boron_stairs.name=Boron Stairs +tile.modernmetals.boron_wall.name=Boron Wall + + +item.modernmetals.cadmium_clump.name=Cadmium Clump +item.modernmetals.cadmium_crushed.name=Crushed Cadmium Руда +item.modernmetals.cadmium_crushed_purified.name=Purified Crushed Cadmium Руда +item.modernmetals.cadmium_crystal.name=Cadmium Crystal +item.modernmetals.cadmium_dense_plate.name=Dense Cadmium Plate +item.modernmetals.cadmium_powder_dirty.name=Dirty Cadmium Dust +item.modernmetals.cadmium_shard.name=Cadmium Shard + +item.modernmetals.cadmium_button.name=Cadmium Button +item.modernmetals.cadmium_lever.name=Cadmium Lever +item.modernmetals.cadmium_pressure_plate.name=Cadmium Pressure Plate +item.modernmetals.cadmium_slab.name=Cadmium Slab +item.modernmetals.cadmium_stairs.name=Cadmium Stairs +item.modernmetals.cadmium_wall.name=Cadmium Wall + +tile.modernmetals.cadmium_button.name=Cadmium Button +tile.modernmetals.cadmium_lever.name=Cadmium Lever +tile.modernmetals.cadmium_pressure_plate.name=Cadmium Pressure Plate +tile.modernmetals.cadmium_slab.name=Cadmium Slab +tile.modernmetals.cadmium_stairs.name=Cadmium Stairs +tile.modernmetals.cadmium_wall.name=Cadmium Wall + + +item.modernmetals.chromium_clump.name=Chromium Clump +item.modernmetals.chromium_crushed.name=Crushed Chromium Руда +item.modernmetals.chromium_crushed_purified.name=Purified Crushed Chromium Руда +item.modernmetals.chromium_crystal.name=Chromium Crystal +item.modernmetals.chromium_dense_plate.name=Dense Chromium Plate +item.modernmetals.chromium_powder_dirty.name=Dirty Chromium Dust +item.modernmetals.chromium_shard.name=Chromium Shard + +item.modernmetals.chromium_button.name=Chromium Button +item.modernmetals.chromium_lever.name=Chromium Lever +item.modernmetals.chromium_pressure_plate.name=Chromium Pressure Plate +item.modernmetals.chromium_slab.name=Chromium Slab +item.modernmetals.chromium_stairs.name=Chromium Stairs +item.modernmetals.chromium_wall.name=Chromium Wall + +tile.modernmetals.chromium_button.name=Chromium Button +tile.modernmetals.chromium_lever.name=Chromium Lever +tile.modernmetals.chromium_pressure_plate.name=Chromium Pressure Plate +tile.modernmetals.chromium_slab.name=Chromium Slab +tile.modernmetals.chromium_stairs.name=Chromium Stairs +tile.modernmetals.chromium_wall.name=Chromium Wall + + +item.modernmetals.galvanizedsteel_clump.name=Galvanized Steel Clump +item.modernmetals.galvanizedsteel_crushed.name=Crushed Galvanized Steel Руда +item.modernmetals.galvanizedsteel_crushed_purified.name=Purified Crushed Galvanized Steel Руда +item.modernmetals.galvanizedsteel_crystal.name=Galvanized Steel Crystal +item.modernmetals.galvanizedsteel_dense_plate.name=Dense Galvanized Steel Plate +item.modernmetals.galvanizedsteel_powder_dirty.name=Dirty Galvanized Steel Dust +item.modernmetals.galvanizedsteel_shard.name=Galvanized Steel Shard + +item.modernmetals.galvanizedsteel_button.name=Galvanized Steel Button +item.modernmetals.galvanizedsteel_lever.name=Galvanized Steel Lever +item.modernmetals.galvanizedsteel_pressure_plate.name=Galvanized Steel Pressure Plate +item.modernmetals.galvanizedsteel_slab.name=Galvanized Steel Slab +item.modernmetals.galvanizedsteel_stairs.name=Galvanized Steel Stairs +item.modernmetals.galvanizedsteel_wall.name=Galvanized Steel Wall + +tile.modernmetals.galvanizedsteel_button.name=Galvanized Steel Button +tile.modernmetals.galvanizedsteel_lever.name=Galvanized Steel Lever +tile.modernmetals.galvanizedsteel_pressure_plate.name=Galvanized Steel Pressure Plate +tile.modernmetals.galvanizedsteel_slab.name=Galvanized Steel Slab +tile.modernmetals.galvanizedsteel_stairs.name=Galvanized Steel Stairs +tile.modernmetals.galvanizedsteel_wall.name=Galvanized Steel Wall + + +item.modernmetals.iridium_clump.name=Iridium Clump +item.modernmetals.iridium_crushed.name=Crushed Iridium Руда +item.modernmetals.iridium_crushed_purified.name=Purified Crushed Iridium Руда +item.modernmetals.iridium_crystal.name=Iridium Crystal +item.modernmetals.iridium_dense_plate.name=Dense Iridium Plate +item.modernmetals.iridium_powder_dirty.name=Dirty Iridium Dust +item.modernmetals.iridium_shard.name=Iridium Shard + +item.modernmetals.iridium_button.name=Iridium Button +item.modernmetals.iridium_lever.name=Iridium Lever +item.modernmetals.iridium_pressure_plate.name=Iridium Pressure Plate +item.modernmetals.iridium_slab.name=Iridium Slab +item.modernmetals.iridium_stairs.name=Iridium Stairs +item.modernmetals.iridium_wall.name=Iridium Wall + +tile.modernmetals.iridium_button.name=Iridium Button +tile.modernmetals.iridium_lever.name=Iridium Lever +tile.modernmetals.iridium_pressure_plate.name=Iridium Pressure Plate +tile.modernmetals.iridium_slab.name=Iridium Slab +tile.modernmetals.iridium_stairs.name=Iridium Stairs +tile.modernmetals.iridium_wall.name=Iridium Wall + + +item.modernmetals.magnesium_clump.name=Magnesium Clump +item.modernmetals.magnesium_crushed.name=Crushed Magnesium Руда +item.modernmetals.magnesium_crushed_purified.name=Purified Crushed Magnesium Руда +item.modernmetals.magnesium_crystal.name=Magnesium Crystal +item.modernmetals.magnesium_dense_plate.name=Dense Magnesium Plate +item.modernmetals.magnesium_powder_dirty.name=Dirty Magnesium Dust +item.modernmetals.magnesium_shard.name=Magnesium Shard + +item.modernmetals.magnesium_button.name=Magnesium Button +item.modernmetals.magnesium_lever.name=Magnesium Lever +item.modernmetals.magnesium_pressure_plate.name=Magnesium Pressure Plate +item.modernmetals.magnesium_slab.name=Magnesium Slab +item.modernmetals.magnesium_stairs.name=Magnesium Stairs +item.modernmetals.magnesium_wall.name=Magnesium Wall + +tile.modernmetals.magnesium_button.name=Magnesium Button +tile.modernmetals.magnesium_lever.name=Magnesium Lever +tile.modernmetals.magnesium_pressure_plate.name=Magnesium Pressure Plate +tile.modernmetals.magnesium_slab.name=Magnesium Slab +tile.modernmetals.magnesium_stairs.name=Magnesium Stairs +tile.modernmetals.magnesium_wall.name=Magnesium Wall + + +item.modernmetals.manganese_clump.name=Manganese Clump +item.modernmetals.manganese_crushed.name=Crushed Manganese Руда +item.modernmetals.manganese_crushed_purified.name=Purified Crushed Manganese Руда +item.modernmetals.manganese_crystal.name=Manganese Crystal +item.modernmetals.manganese_dense_plate.name=Dense Manganese Plate +item.modernmetals.manganese_powder_dirty.name=Dirty Manganese Dust +item.modernmetals.manganese_shard.name=Manganese Shard + +item.modernmetals.manganese_button.name=Manganese Button +item.modernmetals.manganese_lever.name=Manganese Lever +item.modernmetals.manganese_pressure_plate.name=Manganese Pressure Plate +item.modernmetals.manganese_slab.name=Manganese Slab +item.modernmetals.manganese_stairs.name=Manganese Stairs +item.modernmetals.manganese_wall.name=Manganese Wall + +tile.modernmetals.manganese_button.name=Manganese Button +tile.modernmetals.manganese_lever.name=Manganese Lever +tile.modernmetals.manganese_pressure_plate.name=Manganese Pressure Plate +tile.modernmetals.manganese_slab.name=Manganese Slab +tile.modernmetals.manganese_stairs.name=Manganese Stairs +tile.modernmetals.manganese_wall.name=Manganese Wall + + +item.modernmetals.nichrome_clump.name=Nichrome Clump +item.modernmetals.nichrome_crushed.name=Crushed Nichrome Руда +item.modernmetals.nichrome_crushed_purified.name=Purified Crushed Nichrome Руда +item.modernmetals.nichrome_crystal.name=Nichrome Crystal +item.modernmetals.nichrome_dense_plate.name=Dense Nichrome Plate +item.modernmetals.nichrome_powder_dirty.name=Dirty Nichrome Dust +item.modernmetals.nichrome_shard.name=Nichrome Shard + +item.modernmetals.nichrome_button.name=Nichrome Button +item.modernmetals.nichrome_lever.name=Nichrome Lever +item.modernmetals.nichrome_pressure_plate.name=Nichrome Pressure Plate +item.modernmetals.nichrome_slab.name=Nichrome Slab +item.modernmetals.nichrome_stairs.name=Nichrome Stairs +item.modernmetals.nichrome_wall.name=Nichrome Wall + +tile.modernmetals.nichrome_button.name=Nichrome Button +tile.modernmetals.nichrome_lever.name=Nichrome Lever +tile.modernmetals.nichrome_pressure_plate.name=Nichrome Pressure Plate +tile.modernmetals.nichrome_slab.name=Nichrome Slab +tile.modernmetals.nichrome_stairs.name=Nichrome Stairs +tile.modernmetals.nichrome_wall.name=Nichrome Wall + + +item.modernmetals.osmium_clump.name=Osmium Clump +item.modernmetals.osmium_crushed.name=Crushed Osmium Руда +item.modernmetals.osmium_crushed_purified.name=Purified Crushed Osmium Руда +item.modernmetals.osmium_crystal.name=Osmium Crystal +item.modernmetals.osmium_dense_plate.name=Dense Osmium Plate +item.modernmetals.osmium_powder_dirty.name=Dirty Osmium Dust +item.modernmetals.osmium_shard.name=Osmium Shard + +item.modernmetals.osmium_button.name=Osmium Button +item.modernmetals.osmium_lever.name=Osmium Lever +item.modernmetals.osmium_pressure_plate.name=Osmium Pressure Plate +item.modernmetals.osmium_slab.name=Osmium Slab +item.modernmetals.osmium_stairs.name=Osmium Stairs +item.modernmetals.osmium_wall.name=Osmium Wall + +tile.modernmetals.osmium_button.name=Osmium Button +tile.modernmetals.osmium_lever.name=Osmium Lever +tile.modernmetals.osmium_pressure_plate.name=Osmium Pressure Plate +tile.modernmetals.osmium_slab.name=Osmium Slab +tile.modernmetals.osmium_stairs.name=Osmium Stairs +tile.modernmetals.osmium_wall.name=Osmium Wall + + +item.modernmetals.plutonium_clump.name=Plutonium Clump +item.modernmetals.plutonium_crushed.name=Crushed Plutonium Руда +item.modernmetals.plutonium_crushed_purified.name=Purified Crushed Plutonium Руда +item.modernmetals.plutonium_crystal.name=Plutonium Crystal +item.modernmetals.plutonium_dense_plate.name=Dense Plutonium Plate +item.modernmetals.plutonium_powder_dirty.name=Dirty Plutonium Dust +item.modernmetals.plutonium_shard.name=Plutonium Shard + +item.modernmetals.plutonium_button.name=Plutonium Button +item.modernmetals.plutonium_lever.name=Plutonium Lever +item.modernmetals.plutonium_pressure_plate.name=Plutonium Pressure Plate +item.modernmetals.plutonium_slab.name=Plutonium Slab +item.modernmetals.plutonium_stairs.name=Plutonium Stairs +item.modernmetals.plutonium_wall.name=Plutonium Wall + +tile.modernmetals.plutonium_button.name=Plutonium Button +tile.modernmetals.plutonium_lever.name=Plutonium Lever +tile.modernmetals.plutonium_pressure_plate.name=Plutonium Pressure Plate +tile.modernmetals.plutonium_slab.name=Plutonium Slab +tile.modernmetals.plutonium_stairs.name=Plutonium Stairs +tile.modernmetals.plutonium_wall.name=Plutonium Wall + + +item.modernmetals.rutile_clump.name=Rutile Clump +item.modernmetals.rutile_crushed.name=Crushed Rutile Руда +item.modernmetals.rutile_crushed_purified.name=Purified Crushed Rutile Руда +item.modernmetals.rutile_crystal.name=Rutile Crystal +item.modernmetals.rutile_dense_plate.name=Dense Rutile Plate +item.modernmetals.rutile_powder_dirty.name=Dirty Rutile Dust +item.modernmetals.rutile_shard.name=Rutile Shard + +item.modernmetals.rutile_button.name=Rutile Button +item.modernmetals.rutile_lever.name=Rutile Lever +item.modernmetals.rutile_pressure_plate.name=Rutile Pressure Plate +item.modernmetals.rutile_slab.name=Rutile Slab +item.modernmetals.rutile_stairs.name=Rutile Stairs +item.modernmetals.rutile_wall.name=Rutile Wall + +tile.modernmetals.rutile_button.name=Rutile Button +tile.modernmetals.rutile_lever.name=Rutile Lever +tile.modernmetals.rutile_pressure_plate.name=Rutile Pressure Plate +tile.modernmetals.rutile_slab.name=Rutile Slab +tile.modernmetals.rutile_stairs.name=Rutile Stairs +tile.modernmetals.rutile_wall.name=Rutile Wall + + +item.modernmetals.stainlesssteel_clump.name=Stainless Steel Clump +item.modernmetals.stainlesssteel_crushed.name=Crushed Stainless Steel Руда +item.modernmetals.stainlesssteel_crushed_purified.name=Purified Crushed Stainless Steel Руда +item.modernmetals.stainlesssteel_crystal.name=Stainless Steel Crystal +item.modernmetals.stainlesssteel_dense_plate.name=Dense Stainless Steel Plate +item.modernmetals.stainlesssteel_powder_dirty.name=Dirty Stainless Steel Dust +item.modernmetals.stainlesssteel_shard.name=Stainless Steel Shard + +item.modernmetals.stainlesssteel_button.name=Stainless Steel Button +item.modernmetals.stainlesssteel_lever.name=Stainless Steel Lever +item.modernmetals.stainlesssteel_pressure_plate.name=Stainless Steel Pressure Plate +item.modernmetals.stainlesssteel_slab.name=Stainless Steel Slab +item.modernmetals.stainlesssteel_stairs.name=Stainless Steel Stairs +item.modernmetals.stainlesssteel_wall.name=Stainless Steel Wall + +tile.modernmetals.stainlesssteel_button.name=Stainless Steel Button +tile.modernmetals.stainlesssteel_lever.name=Stainless Steel Lever +tile.modernmetals.stainlesssteel_pressure_plate.name=Stainless Steel Pressure Plate +tile.modernmetals.stainlesssteel_slab.name=Stainless Steel Slab +tile.modernmetals.stainlesssteel_stairs.name=Stainless Steel Stairs +tile.modernmetals.stainlesssteel_wall.name=Stainless Steel Wall + + +item.modernmetals.tantalum_clump.name=Tantalum Clump +item.modernmetals.tantalum_crushed.name=Crushed Tantalum Руда +item.modernmetals.tantalum_crushed_purified.name=Purified Crushed Tantalum Руда +item.modernmetals.tantalum_crystal.name=Tantalum Crystal +item.modernmetals.tantalum_dense_plate.name=Dense Tantalum Plate +item.modernmetals.tantalum_powder_dirty.name=Dirty Tantalum Dust +item.modernmetals.tantalum_shard.name=Tantalum Shard + +item.modernmetals.tantalum_button.name=Tantalum Button +item.modernmetals.tantalum_lever.name=Tantalum Lever +item.modernmetals.tantalum_pressure_plate.name=Tantalum Pressure Plate +item.modernmetals.tantalum_slab.name=Tantalum Slab +item.modernmetals.tantalum_stairs.name=Tantalum Stairs +item.modernmetals.tantalum_wall.name=Tantalum Wall + +tile.modernmetals.tantalum_button.name=Tantalum Button +tile.modernmetals.tantalum_lever.name=Tantalum Lever +tile.modernmetals.tantalum_pressure_plate.name=Tantalum Pressure Plate +tile.modernmetals.tantalum_slab.name=Tantalum Slab +tile.modernmetals.tantalum_stairs.name=Tantalum Stairs +tile.modernmetals.tantalum_wall.name=Tantalum Wall + + +item.modernmetals.titanium_clump.name=Titanium Clump +item.modernmetals.titanium_crushed.name=Crushed Titanium Руда +item.modernmetals.titanium_crushed_purified.name=Purified Crushed Titanium Руда +item.modernmetals.titanium_crystal.name=Titanium Crystal +item.modernmetals.titanium_dense_plate.name=Dense Titanium Plate +item.modernmetals.titanium_powder_dirty.name=Dirty Titanium Dust +item.modernmetals.titanium_shard.name=Titanium Shard + +item.modernmetals.titanium_button.name=Titanium Button +item.modernmetals.titanium_lever.name=Titanium Lever +item.modernmetals.titanium_pressure_plate.name=Titanium Pressure Plate +item.modernmetals.titanium_slab.name=Titanium Slab +item.modernmetals.titanium_stairs.name=Titanium Stairs +item.modernmetals.titanium_wall.name=Titanium Wall + +tile.modernmetals.titanium_button.name=Titanium Button +tile.modernmetals.titanium_lever.name=Titanium Lever +tile.modernmetals.titanium_pressure_plate.name=Titanium Pressure Plate +tile.modernmetals.titanium_slab.name=Titanium Slab +tile.modernmetals.titanium_stairs.name=Titanium Stairs +tile.modernmetals.titanium_wall.name=Titanium Wall + + +item.modernmetals.tungsten_clump.name=Tungsten Clump +item.modernmetals.tungsten_crushed.name=Crushed Tungsten Руда +item.modernmetals.tungsten_crushed_purified.name=Purified Crushed Tungsten Руда +item.modernmetals.tungsten_crystal.name=Tungsten Crystal +item.modernmetals.tungsten_dense_plate.name=Dense Tungsten Plate +item.modernmetals.tungsten_powder_dirty.name=Dirty Tungsten Dust +item.modernmetals.tungsten_shard.name=Tungsten Shard + +item.modernmetals.tungsten_button.name=Tungsten Button +item.modernmetals.tungsten_lever.name=Tungsten Lever +item.modernmetals.tungsten_pressure_plate.name=Tungsten Pressure Plate +item.modernmetals.tungsten_slab.name=Tungsten Slab +item.modernmetals.tungsten_stairs.name=Tungsten Stairs +item.modernmetals.tungsten_wall.name=Tungsten Wall + +tile.modernmetals.tungsten_button.name=Tungsten Button +tile.modernmetals.tungsten_lever.name=Tungsten Lever +tile.modernmetals.tungsten_pressure_plate.name=Tungsten Pressure Plate +tile.modernmetals.tungsten_slab.name=Tungsten Slab +tile.modernmetals.tungsten_stairs.name=Tungsten Stairs +tile.modernmetals.tungsten_wall.name=Tungsten Wall + + +item.modernmetals.uranium_clump.name=Uranium Clump +item.modernmetals.uranium_crushed.name=Crushed Uranium Руда +item.modernmetals.uranium_crushed_purified.name=Purified Crushed Uranium Руда +item.modernmetals.uranium_crystal.name=Uranium Crystal +item.modernmetals.uranium_dense_plate.name=Dense Uranium Plate +item.modernmetals.uranium_powder_dirty.name=Dirty Uranium Dust +item.modernmetals.uranium_shard.name=Uranium Shard + +item.modernmetals.uranium_button.name=Uranium Button +item.modernmetals.uranium_lever.name=Uranium Lever +item.modernmetals.uranium_pressure_plate.name=Uranium Pressure Plate +item.modernmetals.uranium_slab.name=Uranium Slab +item.modernmetals.uranium_stairs.name=Uranium Stairs +item.modernmetals.uranium_wall.name=Uranium Wall + +tile.modernmetals.uranium_button.name=Uranium Button +tile.modernmetals.uranium_lever.name=Uranium Lever +tile.modernmetals.uranium_pressure_plate.name=Uranium Pressure Plate +tile.modernmetals.uranium_slab.name=Uranium Slab +tile.modernmetals.uranium_stairs.name=Uranium Stairs +tile.modernmetals.uranium_wall.name=Uranium Wall + + +item.modernmetals.zirconium_clump.name=Zirconium Clump +item.modernmetals.zirconium_crushed.name=Crushed Zirconium Руда +item.modernmetals.zirconium_crushed_purified.name=Purified Crushed Zirconium Руда +item.modernmetals.zirconium_crystal.name=Zirconium Crystal +item.modernmetals.zirconium_dense_plate.name=Dense Zirconium Plate +item.modernmetals.zirconium_powder_dirty.name=Dirty Zirconium Dust +item.modernmetals.zirconium_shard.name=Zirconium Shard + +item.modernmetals.zirconium_button.name=Zirconium Button +item.modernmetals.zirconium_lever.name=Zirconium Lever +item.modernmetals.zirconium_pressure_plate.name=Zirconium Pressure Plate +item.modernmetals.zirconium_slab.name=Zirconium Slab +item.modernmetals.zirconium_stairs.name=Zirconium Stairs +item.modernmetals.zirconium_wall.name=Zirconium Wall + +tile.modernmetals.zirconium_button.name=Zirconium Button +tile.modernmetals.zirconium_lever.name=Zirconium Lever +tile.modernmetals.zirconium_pressure_plate.name=Zirconium Pressure Plate +tile.modernmetals.zirconium_slab.name=Zirconium Slab +tile.modernmetals.zirconium_stairs.name=Zirconium Stairs +tile.modernmetals.zirconium_wall.name=Zirconium Wall + + +material.thorium.name=Thorium +item.modernmetals.thorium_arrow.name=Thorium Стрела +item.modernmetals.thorium_axe.name=Thorium Топор +item.modernmetals.thorium_bars.name=Thorium Прутья +item.modernmetals.thorium_blend.name=Thorium Смеси +item.modernmetals.thorium_block.name=Блок Thorium +item.modernmetals.thorium_boots.name=Thorium Ботинки +item.modernmetals.thorium_bolt.name=Thorium Арбалетная Стрела +item.modernmetals.thorium_bow.name=Thorium Лук +item.modernmetals.thorium_chestplate.name=Thorium Нагрудник +item.modernmetals.thorium_crackhammer.name=Thorium Измельчающий Молот +item.modernmetals.thorium_crossbow.name=Thorium Арбалет +item.modernmetals.thorium_door.name=Thorium Двери +item.modernmetals.thorium_fishing_rod.name=Thorium Удочка +item.modernmetals.thorium_gear.name=Thorium Шестерня +item.modernmetals.thorium_helmet.name=Thorium Шлем +item.modernmetals.thorium_hoe.name=Thorium Мотыга +item.modernmetals.thorium_horse_armor.name=Thorium Конская Броня +item.modernmetals.thorium_ingot.name=Thorium Слиток +item.modernmetals.thorium_leggings.name=Thorium Поножки +item.modernmetals.thorium_nugget.name=Thorium Самородок +item.modernmetals.thorium_ore.name=Thorium Руда +item.modernmetals.thorium_pickaxe.name=Thorium Кирка +item.modernmetals.thorium_powder.name=Измельчённый Thorium +item.modernmetals.thorium_rod.name=Thorium Стержень +item.modernmetals.thorium_shears.name=Thorium Ножницы +item.modernmetals.thorium_shield.name=Thorium Щит +item.modernmetals.thorium_shovel.name=Thorium Лопата +item.modernmetals.thorium_smallblend.name=Маленькая Кучка Thorium Смеси +item.modernmetals.thorium_smallpowder.name=Маленькая Кучка Измельчённого Thorium +item.modernmetals.thorium_sword.name=Thorium Меч +item.modernmetals.thorium_trapdoor.name=Thorium Люк +tile.modernmetals.thorium_bars.name=Thorium Прутья +tile.modernmetals.thorium_block.name=Блок Thorium +tile.modernmetals.thorium_door.name=Thorium Двери +tile.modernmetals.thorium_ore.name=Thorium Руда +tile.modernmetals.thorium_plate.name=Thorium Plate +tile.modernmetals.thorium_trapdoor.name=Thorium Люк +fluid.modernmetals.thorium=Molten Thorium +fluid.modernmetals.thorium.name=Molten Thorium +tile.modernmetals.thorium.name=Molten Thorium +item.modernmetals.thorium_clump.name=Thorium Clump +item.modernmetals.thorium_crushed.name=Crushed Thorium Руда +item.modernmetals.thorium_crushed_purified.name=Purified Crushed Thorium Руда +item.modernmetals.thorium_crystal.name=Thorium Crystal +item.modernmetals.thorium_dense_plate.name=Dense Thorium Plate +item.modernmetals.thorium_powder_dirty.name=Dirty Thorium Dust +item.modernmetals.thorium_shard.name=Thorium Shard +item.modernmetals.thorium_button.name=Thorium Button +item.modernmetals.thorium_lever.name=Thorium Lever +item.modernmetals.thorium_pressure_plate.name=Thorium Pressure Plate +item.modernmetals.thorium_slab.name=Thorium Slab +item.modernmetals.thorium_stairs.name=Thorium Stairs +item.modernmetals.thorium_wall.name=Thorium Wall +tile.modernmetals.thorium_button.name=Thorium Button +tile.modernmetals.thorium_lever.name=Thorium Lever +tile.modernmetals.thorium_pressure_plate.name=Thorium Pressure Plate +tile.modernmetals.thorium_slab.name=Thorium Slab +tile.modernmetals.thorium_stairs.name=Thorium Stairs +tile.modernmetals.thorium_wall.name=Thorium Wall + +# Modern Metals settings and restored content +config.modernmetals.material.tooltip=Включает блоки, предметы и рецепты этого материала. Требуется перезапуск игры. +config.modernmetals.title=Modern Metals +config.modernmetals.description=Для применения нужен перезапуск. Руды настраиваются в OreSpawn. +config.modernmetals.content_mode=Уровень фэнтези +config.modernmetals.content_mode.tooltip=Высокий уровень сохраняет все рецепты. Низкий ограничивает нереалистичное снаряжение и строительные блоки. Требуется перезапуск игры. +high_fantasy=Высокий уровень фэнтези +low_fantasy=Низкий уровень фэнтези +config.modernmetals.villager_trades=Торговля с жителями +config.modernmetals.villager_trades.tooltip=Кузнецы могут торговать слитками и снаряжением Modern Metals. Требуется перезапуск игры. +config.modernmetals.connection.mismatch=Уровень на сервере — %s, в вашей игре — %s. Выберите тот же уровень в меню «Моды > Modern Metals > Настройки» и перезапустите игру. +config.modernmetals.connection.protocol=Не удалось проверить уровень фэнтези Modern Metals. Установите одну и ту же сборку Modern Metals на клиенте и сервере. +itemGroup.modernmetals.combat=Modern Metals: боевое снаряжение +item.modernmetals.titanium_casing.name=Корпус из материала «Титан» +item.modernmetals.beryllium_scythe.name=Серп из материала «Бериллий» +item.modernmetals.boron_scythe.name=Серп из материала «Бор» +item.modernmetals.thorium_scythe.name=Серп из материала «Торий» +item.modernmetals.titanium_scythe.name=Серп из материала «Титан» +config.modernmetals.enable_thorium=Включить торий +config.modernmetals.enable_boron=Включить бор +config.modernmetals.enable_beryllium=Включить бериллий +config.modernmetals.enable_cadmium=Включить кадмий +config.modernmetals.enable_nichrome=Включить нихром +config.modernmetals.enable_aluminum=Включить алюминий +config.modernmetals.enable_aluminumbrass=Включить алюминиевую латунь +config.modernmetals.enable_chromium=Включить хром +config.modernmetals.enable_galvanizedsteel=Включить оцинкованную сталь +config.modernmetals.enable_iridium=Включить иридий +config.modernmetals.enable_magnesium=Включить магний +config.modernmetals.enable_manganese=Включить марганец +config.modernmetals.enable_osmium=Включить осмий +config.modernmetals.enable_plutonium=Включить плутоний +config.modernmetals.enable_rutile=Включить рутил +config.modernmetals.enable_stainlesssteel=Включить нержавеющую сталь +config.modernmetals.enable_tantalum=Включить тантал +config.modernmetals.enable_titanium=Включить титан +config.modernmetals.enable_tungsten=Включить вольфрам +config.modernmetals.enable_uranium=Включить уран +config.modernmetals.enable_zirconium=Включить цирконий + +achievement.aluminum_brass_maker=Алюминиево-Латунный +achievement.aluminum_brass_maker.desc=Combine powdered Алюминиевый and Brass to make Алюминиево-Латунный diff --git a/src/main/resources/assets/modernmetals/lang/zh_CN.lang b/src/main/resources/assets/modernmetals/lang/zh_CN.lang index 57e1b4f6..2836870f 100644 --- a/src/main/resources/assets/modernmetals/lang/zh_CN.lang +++ b/src/main/resources/assets/modernmetals/lang/zh_CN.lang @@ -3,7 +3,7 @@ itemGroup.modernmetals.tools=Modern Metals itemGroup.modernmetals.blocks=Modern Metals nei.modernmetals.recipehandler.crusher.name=粉碎的方块/物品 -material.aluminum.name=Aluminum +material.aluminum.name=铝 item.modernmetals.aluminum_arrow.name=铝箭 item.modernmetals.aluminum_axe.name=铝斧 item.modernmetals.aluminum_bars.name=铝栏杆 @@ -1513,3 +1513,46 @@ tile.modernmetals.thorium_stairs.name=钍楼梯 tile.modernmetals.thorium_wall.name=钍墙 + +# Modern Metals settings and restored content +config.modernmetals.material.tooltip=控制是否启用此材料的方块、物品和配方。需要重启游戏。 +config.modernmetals.title=Modern Metals +config.modernmetals.description=重启游戏后应用更改。请在 OreSpawn 中设置矿石生成。 +config.modernmetals.content_mode=奇幻等级 +config.modernmetals.content_mode.tooltip=高奇幻保留全部配方。低奇幻限制不符合现实的装备和建筑部件。需要重启游戏。 +high_fantasy=高奇幻 +low_fantasy=低奇幻 +config.modernmetals.villager_trades=村民交易 +config.modernmetals.villager_trades.tooltip=允许铁匠交易 Modern Metals 的金属锭和装备。需要重启游戏。 +config.modernmetals.connection.mismatch=服务器使用%s,你的游戏使用%s。请在模组 > Modern Metals > 配置中选择与服务器一致的奇幻等级,然后重启游戏。 +config.modernmetals.connection.protocol=Modern Metals 无法核对奇幻等级。请在服务器和客户端使用相同构建版本的 Modern Metals。 +itemGroup.modernmetals.combat=Modern Metals 战斗用品 +item.modernmetals.titanium_casing.name=钛外壳 +item.modernmetals.beryllium_scythe.name=铍镰刀 +item.modernmetals.boron_scythe.name=硼镰刀 +item.modernmetals.thorium_scythe.name=钍镰刀 +item.modernmetals.titanium_scythe.name=钛镰刀 +config.modernmetals.enable_thorium=启用钍 +config.modernmetals.enable_boron=启用硼 +config.modernmetals.enable_beryllium=启用铍 +config.modernmetals.enable_cadmium=启用镉 +config.modernmetals.enable_nichrome=启用镍铬合金 +config.modernmetals.enable_aluminum=启用铝 +config.modernmetals.enable_aluminumbrass=启用铝黄铜 +config.modernmetals.enable_chromium=启用铬 +config.modernmetals.enable_galvanizedsteel=启用镀锌钢 +config.modernmetals.enable_iridium=启用铱 +config.modernmetals.enable_magnesium=启用镁 +config.modernmetals.enable_manganese=启用锰 +config.modernmetals.enable_osmium=启用锇 +config.modernmetals.enable_plutonium=启用钚 +config.modernmetals.enable_rutile=启用金红石 +config.modernmetals.enable_stainlesssteel=启用不锈钢 +config.modernmetals.enable_tantalum=启用钽 +config.modernmetals.enable_titanium=启用钛 +config.modernmetals.enable_tungsten=启用钨 +config.modernmetals.enable_uranium=启用铀 +config.modernmetals.enable_zirconium=启用锆 + +achievement.aluminum_brass_maker=铝黄铜 +achievement.aluminum_brass_maker.desc=混合铝粉和黄铜粉,并烧制铝黄铜 diff --git a/src/main/resources/assets/modernmetals/models/block/aluminum_ore.json b/src/main/resources/assets/modernmetals/models/block/aluminum_ore.json deleted file mode 100644 index 4616b535..00000000 --- a/src/main/resources/assets/modernmetals/models/block/aluminum_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/aluminum_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/aluminumbrass_ore.json b/src/main/resources/assets/modernmetals/models/block/aluminumbrass_ore.json deleted file mode 100644 index ed015015..00000000 --- a/src/main/resources/assets/modernmetals/models/block/aluminumbrass_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/aluminumbrass_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/beryllium_ore.json b/src/main/resources/assets/modernmetals/models/block/beryllium_ore.json deleted file mode 100644 index 6e531aa2..00000000 --- a/src/main/resources/assets/modernmetals/models/block/beryllium_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/beryllium_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/boron_ore.json b/src/main/resources/assets/modernmetals/models/block/boron_ore.json deleted file mode 100644 index 13dfb423..00000000 --- a/src/main/resources/assets/modernmetals/models/block/boron_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/boron_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/cadmium_ore.json b/src/main/resources/assets/modernmetals/models/block/cadmium_ore.json deleted file mode 100644 index cf3636e0..00000000 --- a/src/main/resources/assets/modernmetals/models/block/cadmium_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/cadmium_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/chromium_ore.json b/src/main/resources/assets/modernmetals/models/block/chromium_ore.json deleted file mode 100644 index f867a0b3..00000000 --- a/src/main/resources/assets/modernmetals/models/block/chromium_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/chromium_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/galvanizedsteel_ore.json b/src/main/resources/assets/modernmetals/models/block/galvanizedsteel_ore.json deleted file mode 100644 index f7e8487e..00000000 --- a/src/main/resources/assets/modernmetals/models/block/galvanizedsteel_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/galvanizedsteel_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/iridium_ore.json b/src/main/resources/assets/modernmetals/models/block/iridium_ore.json deleted file mode 100644 index b3bd98cb..00000000 --- a/src/main/resources/assets/modernmetals/models/block/iridium_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/iridium_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/magnesium_ore.json b/src/main/resources/assets/modernmetals/models/block/magnesium_ore.json deleted file mode 100644 index 9f6fb257..00000000 --- a/src/main/resources/assets/modernmetals/models/block/magnesium_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/magnesium_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/manganese_ore.json b/src/main/resources/assets/modernmetals/models/block/manganese_ore.json deleted file mode 100644 index 6c79d985..00000000 --- a/src/main/resources/assets/modernmetals/models/block/manganese_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/manganese_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/nichrome_ore.json b/src/main/resources/assets/modernmetals/models/block/nichrome_ore.json deleted file mode 100644 index 88f7cf51..00000000 --- a/src/main/resources/assets/modernmetals/models/block/nichrome_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/nichrome_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/osmium_ore.json b/src/main/resources/assets/modernmetals/models/block/osmium_ore.json deleted file mode 100644 index 9aabe0d0..00000000 --- a/src/main/resources/assets/modernmetals/models/block/osmium_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/osmium_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/plutonium_ore.json b/src/main/resources/assets/modernmetals/models/block/plutonium_ore.json deleted file mode 100644 index 2e4233ea..00000000 --- a/src/main/resources/assets/modernmetals/models/block/plutonium_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/plutonium_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/rutile_ore.json b/src/main/resources/assets/modernmetals/models/block/rutile_ore.json deleted file mode 100644 index 29f9be6c..00000000 --- a/src/main/resources/assets/modernmetals/models/block/rutile_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/rutile_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/stainlesssteel_ore.json b/src/main/resources/assets/modernmetals/models/block/stainlesssteel_ore.json deleted file mode 100644 index e757848f..00000000 --- a/src/main/resources/assets/modernmetals/models/block/stainlesssteel_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/stainlesssteel_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/tantalum_ore.json b/src/main/resources/assets/modernmetals/models/block/tantalum_ore.json deleted file mode 100644 index 4aae5e70..00000000 --- a/src/main/resources/assets/modernmetals/models/block/tantalum_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/tantalum_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/titanium_ore.json b/src/main/resources/assets/modernmetals/models/block/titanium_ore.json deleted file mode 100644 index ddfbdce7..00000000 --- a/src/main/resources/assets/modernmetals/models/block/titanium_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/titanium_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/tungsten_ore.json b/src/main/resources/assets/modernmetals/models/block/tungsten_ore.json deleted file mode 100644 index 369e7432..00000000 --- a/src/main/resources/assets/modernmetals/models/block/tungsten_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/tungsten_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/uranium_ore.json b/src/main/resources/assets/modernmetals/models/block/uranium_ore.json deleted file mode 100644 index b0dadbef..00000000 --- a/src/main/resources/assets/modernmetals/models/block/uranium_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/uranium_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/models/block/zirconium_ore.json b/src/main/resources/assets/modernmetals/models/block/zirconium_ore.json deleted file mode 100644 index c5835215..00000000 --- a/src/main/resources/assets/modernmetals/models/block/zirconium_ore.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "parent": "block/cube_all", - "textures": { - "all": "modernmetals:blocks/zirconium_ore" - } -} diff --git a/src/main/resources/assets/modernmetals/textures/blocks/aluminum_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/aluminum_ore.png deleted file mode 100644 index 0b32648ae1d98b9bbffb5a9255a34a344f40a05b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 668 zcmV;N0%QG&P)WFU8GbZ8()Nlj2>E@cM*00IC>L_t(I%WacOQxicH zMo-#~STu{-^`aLF7-yyBhT@emJl2-}qx>pMD53#^Qi5Q)QM)M1)huQ=i)lhRkGP1_ zNr1cS*LTnTZr?_?+Z{RQm=2{BK@b37ttAXY(lq71d-C>!I^sA6U}0eafFKA6f&gnR zT5ABDb7O%dNr*a|+x7!`n_;uF%!WeUB&svLg&f%OxDdjPv&L%}(&^!2uQi>!= zSZ*ycH}{Bx{T={WmZ7yq*2-y`(vH?C%L)M5lytTuzMfrr({6^>GbGkpWSXX|uB{-1 zV1KW7FMgI~93P(CX`$A7Y+_Ya2qDO_j4%wn>+NWrlffxFyIT}xfqWpbzpiG4B*0<+ z*!R3P(m6*_l&o*8k{3Bm*(5J=;y9+W9Wfe>xH$iYsB!Arc6PTg#$b$@X<1(6{J#2w zF@^?>2|7Ab^TN>~5@03B&m zSad^gZEa<4bN~PV002XBWnpw>WFU8GbZ8()Nlj2>E@cM*00JLLL_t(I%Wae2OB7KQ z$3Js-c6GZO4T#?2_R`aUgqWc8>Z1foi~m4>iT_MsBq>EgMURPkBQiJOjXWn{=1jyblTGrYH(N{AwU7 zoJ!l~atEz7T5EQWF6vHcI^^rW8MM|ojx#2Rq6noFD|0=vnI?mzk1<89^We|z+GHpv zkj-WYA&8;~S1Etj(9*a+R}toB&W1P0}m7HuQV>3oq{Z@3+|t0KpW5V(3<)xtL1YiPxSex9=`97 hx|#ZY@|>%!{STIl`BKGBi0=RZ002ovPDHLkV1j5>H)#L> diff --git a/src/main/resources/assets/modernmetals/textures/blocks/beryllium_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/beryllium_ore.png deleted file mode 100644 index 4a4c166fe3517e6f7dd91e258140b7b8e6221e03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 654 zcmV;90&)F`P)WFU8GbZ8()Nlj2>E@cM*00HtzL_t(I%WadrQrkcj zg+DLLkeQ`Wx5d_(wL=+79Pr=Z2MEvOXCi|kgY6J91B7&h%n&_*3Uw;9Q6a6Bg3;Or zPN$ze_q*rrZA~VV+BwI%lu|@d1V9|eSZm3$jJN)iFAp?Ik_3R=-CY2pC?bj?;y6ZY z4S;j**&t0*2K@nLS+c#oO;HrVz!*c4Bq*f-IQe{n_nw>En-zhzmc_S4sNV1OQA)A@ zaUZ4B2L8p@i;WgqlgXrR@SCb{$r$tMT^z?q=N!&Clu`k6&>v7$73UY{D5Xf#luoca=!QeH`3!)%uJM&$ zg}6SNy$^c`As7uu%;qzM5IE=95?g`l{?~oj4Y`izoTF{qc<+PZ^^fbYOvadSt((gy oU#{rsms6~@p<1Jt>)d?%55B7V8KTYp8~^|S07*qoM6N<$f?C5QQUCw| diff --git a/src/main/resources/assets/modernmetals/textures/blocks/boron_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/boron_ore.png deleted file mode 100644 index 1f281395640893fac701a79bd572e9f5bce4ee4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 670 zcmV;P0%84$P)WFU8GbZ8()Nlj2>E@cM*00II@L_t(I%Wab}Ybrq$ zg}=x`3^xlK5T=;YVt#|owx&v%qzyJ{1^dOu${&!+!e*a*#TCD&$KR*W` z2m*p2AWc)0QUH`?Nev*&GG?HP)U?)adcWW2^z@WCj+xKrJUu-Dpp?S%JYHU2 zu;-02WLdVi5XUjg<&s{ncND)U3YW%Vgup(9udgo#g8@ZRI2sY5(P$tdJUl!gA|y%T z2yCn;$BJb2{QOLknpsFfvZf;nw*T*dkheLOv zNs_Q!E{USZl_`#6cDo(7x3_x>7Flb}VzB^l5qh5IBDPf5*Vo+N-xCDEzRDJ@EKA$sXw~-M4}zWhtvLW~PXGV_07*qoM6N<$ Eg7u&x>i_@% diff --git a/src/main/resources/assets/modernmetals/textures/blocks/cadmium_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/cadmium_ore.png deleted file mode 100644 index ce83c5642c514d8faf6b2d03de89843881e4c5ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 668 zcmV;N0%QG&P)WFU8GbZ8()Nlj2>E@cM*00IC>L_t(I%Wad-OB+EH z$3Hu6us9y}HitR2(r!g*FNIKu1~n4!(o^Z5lmDn-QGzyNP18zgi?j$bhaUH^w>@MM zVRy1Um>rYo_cp)Y`@HX)7j!zE(pt-`q?ANa1b{JyIF8A(jK}VUiw`O#Ndmy?>M8(H z6cI%c#u$`R09b490;Flm*4{pOo>QyU7>~w2fYzEMNsv+k@b3KwoSX3TTmPOQj$^)_ zefHTKyZcBfdHtr&%F468_ImGv=ctsm?eV+gIaSZk3|dd#i8eTuxG z*F8c?Nt&jtt#9&Vd70DWPXG*uLzGg4RpczoXza9b&H)hCLYl2Mzb*&fw9$AxM`Da2 z%(9GnqlplLlcU2&@rT19{ocug7AmFgOq`S@VtO%-WAA!nr^VpB$8P(8!a2gFCCv4o zxj-Snm(#BAd1a)vmckXh-QJnDHzdysk|d$oYEuHgF9(PUr>bqYeSp>)t@T{X^1R^R z%{5wU0wL}R(lkXW#oGENPhYHadwWZkWxmdvzgOPmO3pb%5Rj%Rp;8KK?VL&5t+rp+ z8%_V{V!BtBC2lggH*vN#&o^Enf`DeL%|-77Aq1n*2rppD$b-(eB|#;yCu%DlLw4b@m^(5ceHp%t&wm0000MUyj diff --git a/src/main/resources/assets/modernmetals/textures/blocks/chromium_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/chromium_ore.png deleted file mode 100644 index 0a0dc93361bf4bd60550fe2bf6a0c561dcf62292..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 655 zcmV;A0&x9_P)WFU8GbZ8()Nlj2>E@cM*00Hw!L_t(I%Wad*PTN2f zg};mj6)`Nlo5d_4Ot*9wXmE_e1JD=BOBFQ*HGxv30%BK+T-IKA=Y=PUWKY}$@<4#I z^Us|7-E-%5Mx#;Voa3dGQbbV%K$@mlYblC?_x_Ww546g%41mqeO#q@OB8no?G(~F- zfOGE2AkTB+ctBNEtgWpvpU;DVF@`M5P)Y%CeEbFPJ=fR2p9!qBTwI)o>Tw*Slwx=H z6B`>FEBI$;rzvP5f*Y@Lgupx-}Wxm*Gur6frXxw*Lxq0MHC6_PYfkwsC^>+Kizx!mzO^o4v(no8Yv|Y4}VvTB*0|yBh0)F z(m99sp8fsLVZ2pUk!2Z4a!AuO+}_?GTAa4G;qVA!491w1kyTa2 z(OT2(?y$AB#bU9bD2mYM{r#U1ax3pWLJ0CaM{2Ec&aH$r7$o7j*V_w+rqc-kO#|N7 z&mq1XjkO;ULXaefOs5ls5X@#Xgy;mSySv-48}cQdbB^upF5dfKcy;wFER!)NTWFU8GbZ8()Nlj2>E@cM*00H|+L_t(I%WacgPZTi} zhMz4nsOgX`tFb1H7Z}(s>;mD=3*%qo?-`H}0g>@z#g9oGVsI{|OG0;cyuc~!%6r$e zJ$=smp0>8Pw`=DdcO|7nDFr~9rdVq!ih}#jMY9(gWmyJ5k|Y2qrBF(drYTx$0Gx9- z1@b({uJ^gVzGi-Yp3!Io{|Xpm$g&J6B>*p9zhY8VeEU4SC9u|#9={K-cl&EdDbddj zQpzd(ll{a0TWAm)?1PXjZkZS@t8P@_;c}ls!#-Q`tCSn z-WcheV_c1S(d**9Cyryh_heZ{Z(~4R*ZdrwBO08hx53sX#u$t-Q;~V^DK86*F^E>{ zmLSh_wALgY#Y%gD(bW}2QG`A(e_aHV8zqZvT0)TLIg!>H=iJmF+4X){FLjpzSa`h1 z+0h9AbzL(Vk8e%98%_I32O$K#jR9vzC$xmXImet3fhs%y9{xsj7tc9Ive>4os!*_* xMx_+Sm@wAO@I|xv?!DQ;S{q(#H2a;Ky}#bI_!&`~Ltg*@002ovPDHLkV1icJ9=HGi diff --git a/src/main/resources/assets/modernmetals/textures/blocks/iridium_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/iridium_ore.png deleted file mode 100644 index 3dbdbeec753514ea9c8d4c4df1726aaf8e65cca0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 688 zcmV;h0#E&kP)WFU8GbZ8()Nlj2>E@cM*00I6E081!jiqU_>>u^|5Z!K<_I9s;Qi?c^5kdg) z{ZD7wdAB=25=9X~nx-tyPvCn#&tAT}7QfT!(B3(|GD0b3&%~lA@oT+cSLT zZ@#kDn8W6lT2SN5_df%L0)UOJPj2Rwk;WLT%~^hUmn_c->H&F{6UQ;lm02iD_6|?+ zE1asgwZN7zO5u54pCCyRq?AmKk6~}$;JkZInx?ML{N=I{T*NcRP}rLkw(s)J{)r2dQp%;WdVIm7Bt8_We)!u)u W0s3vv#*4xL0000*iWFU8GbZ8()Nlj2>E@cM*00HPpL_t(I%WacUPunmQ zg}=CIspMz`s$$xtQ^iZP2YzFJr-9LlVjz5Y8VcOTwVPDpsFgAQp8l-|C-ZquUDw2MjQ8zMp%~!i<}P^N4bnMBUDqfz!h27@-^Y88F%QgU z6WX@r`T6&5kc=_R=Xv;LxmOBAe03B&m zSad^gZEa<4bN~PV002XBWnpw>WFU8GbZ8()Nlj2>E@cM*00Iw5L_t(I%WacSYZE~j z#(%q=Q0(New>ivF>?#%yUZhp}Co}{~5gIJ{1b&Wu7C|XksaRTTst_+-7btVOiD^g5Ci~3QA8Mqq-n}^`^4n~rQ$dS zpjxd05Cj215D-NXN+|%0F{1)WlF(eOlV=&CQen{Vv-`h*)|xnukx~NiwAsSiA%|bj z#spy)viIq|yS=$mM@mWk{sV5z&9Og$zjJ&z)q>Ynm$f6d{Z;7-Nu9 zI?U#3ojlLk=^P-XBuNq$=NFiro#n;5eE@pB9!e=hSvgHpwwBkiLkoZqg6&#^x8J(X zwEmz!MG{33B27~^msaq7pXWz!uEp>5db~RATp6KKYGh(DEWjUa48zd5zO}s0>$4BE z>KoYHG9zZ_{{A&pNC3S2c;seY8flEd+MLIa*2uGrN=1-mIdL4bU29MjaP+;4U*eR# zwdxyat zrKI!Yv&)8<#52ZVZ`}nuc6q;h>cXV8cBw2MpST>+X9s(PVd!ovbve%EzkhlV_#j8@ R)%gGb002ovPDHLkV1hT6DewRQ diff --git a/src/main/resources/assets/modernmetals/textures/blocks/nichrome_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/nichrome_ore.png deleted file mode 100644 index b281291cdffba40313de46396d02b476d2586137..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 691 zcmV;k0!;mhP)WFU8GbZ8()Nlj2>E@cM*00I|DL_t(I%WacQYt&E_ zhM(LdQ>O`S6!d~w7D?&IxE5DMD0S!`@Yl>Ql8S{^L>CozQnkevLl-KN#YKf%&7E=b zH7+vAw4-NvIQQJ=yyv{ua5$VAW4Iii=Me+}0C5}>MG;zS{#WNLK4>CI5&(SP2OtOn zf*>G{W2BS-7-Q-NN-4HCT(T^q)9ElCjhMYB3`3G6!Sg%-9zVE6Sr#0AcIE_86!H8_ z0~V08wcf?^T<+gCEPEQQy%vA(xM7^k8#IUw_U=)gdwC9ET9UcrNn7EYpvN@w<*gK0M~UH^g0}VVJ6yy@o02~ zB#vVotu>o#72>8~_vjb@Mn;;Z92mdZPrE`XWvxL~RR|$S)08NRTGhAKZC-pT+39yF ziyYf=dH?gqmG%z6{>KS0!T0@ENMj5|vCP9e7DZm-xE+c-BS{hly$;i9#p|z2hz6&b zZKvNQ3`4>&Wbb&?{#4`{Cl|7=v#dFRQVJ<0{Z&QJxA-$2qqT15Jo+BAB14@oR|;%l zqm;stQeuqZ;A}qEqm2m%X9WP8YZU-hP_loT)df?VqR8hWUS7?;>pu`eFz5;PPcwuN zjEq4DtEGDLt?|Z8#0J?I!`&5|*(m_<>KvdpTNu*1vN=9y(V|b@UJymm>^I%!7wz0! Z?O%LI`XP2h9%}#q002ovPDHLkV1i<(A6NhY diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/aluminum_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/aluminum_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..6cdc2b421855d28652dd7f9fa4ca994460cd4517 GIT binary patch literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB{*tc)B=-So9tZy3N;Q!1MOmhwFP5RQ&YoH4hMY6o0Z~!&Q;H zEROO$7u7Bp@A@|9Fx!Xs32Z9Mm7h(QnX`^L$V+0kaP;b9iaY_DtjgE3*G?By7YvG9 zCnn+cTj;;pq{|x*?)iFM;%K5X?|QvQDqDIF32xvv+uG){>}}8Q=KmYR{q)zCW=|B6 zWZLBLvy#8sd~RQlAzS6d<`*4C)r&QivpXV^?xh`n;CO#4%d(%xemxLno8Yq4B1ou3oE;zjfht{rE`!$3Zd6=i3_np3hn4a6fHwW%B;e hfT`~1t-0fSwSDs^H@wYTV*&I#gQu&X%Q~loCIHrcmN5VT literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/aluminumbrass_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/aluminumbrass_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..6d2804307d395276b383395de26b40a7850f90b4 GIT binary patch literal 418 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB|Az_H=O!vFJS-6zF%zL16COZ8DsTeD|~(CLJ>TA#|ndo7BGs zMW-s(Iswx|nM+Jd%8#FUwC-r`4+FROO?PjeIa8g#i~GZU0keoy&8f3_U3%H}h+E2R zTOYfw>-k5~%(V|5n5;Z2t+P4$QEl1vrPJGw9I5XPomO?~^FzzMer)rk*T_w~EPY*~ zP0*&V%9_`8n3s!k8kWKEqch3vpG;Se%C_Y8;gQY*(}nUb-ebx>UG6OdX9fi=AC?V)9RAf&g-5_ z98bNi|9nIId#zB%y2gC<`2CDY4DNHUtS;wU^{wIhJi$6q`NAucxGdCf9R!9IgQu&X J%Q~loCICOOqTB!g literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/beryllium_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/beryllium_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..a7accaa8bc63517dc1e0052ca16fb5e5494b43df GIT binary patch literal 387 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB{-jc)B=-So9tZI^EZ7z*7?RV`GqL)Yt6o?W|&_9?#F<%XxIw z#gtq1)xuS`CN{}hZcE?LX~(PTcmJzRpYqW{pJNhUn@hUR28NqPXJ^iT*Id}Y?ybbP zKc&ySYd`+0zF7WuRq?;M#g|RA&8q4$XP0^PEb8dm6QMF=HD~#nx>d`!X4T9oKByq! z$?Vavw|=o)=y zvOB5M^-`2RcgprxK|b#ead>W;yCAhHqMmbP=; ch2pyGlLV)QoptP=3G_FEr>mdKI;Vst0PtFuE&u=k literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/boron_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/boron_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..e6665f3f0658d79c639ceb7d140eef1047112bc4 GIT binary patch literal 331 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB{-5c)B=-SoEF^+RJrBfv3-}mcRXy(xZSS4-{(uul*qvJC}vO zxh&vyWr581Mz^!mcp%}1j!4$5bQ|`8hB$`={Ma=(EoUwLwk4IrY z%cNI~*Gmsp&RcRNeBSol-P}JkGw$s9zBcF7nW6>n#8*uV^g3wCe%WNtA)%pE$2RaC%e1hGnTzmnR&ry#M`8!VYf_InU496^tAG WT(wm^7m5Mh&*16m=d#Wzp$P!inRrwH literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/cadmium_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/cadmium_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..b12af2d760cbc1673ed146e455149a609f1165a0 GIT binary patch literal 405 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB{-@dAc};So9tZdh2z>Kw!?3TgQd`yqDI=+O4#=u{{0t)!eP; zr-&8Jo~pYu-qpZ`>U>)nn#|KPvdElO%H{oI*XQU6BB+$zRodH>8APbIgN3Jabw&?&!Tns0w@ z*2&|B$HSQ1pZ;?4Y&I6$bd_uS)6kONue3`gXErtUgeyf>A6uq1)kS!B(CeiZ`}S$- zJ!hL|Q+m~^??L`e!9*GNmK9y4k0y02nA*i^cJQ>Y;r?%ZEl=XrjhpApKBx9d|MxW4 xyTU0}$0Rb=eyj*reV)#~(Ds+wq4kf1--k?SpThU#BQPWwJYD@<);T3K0RW*pq0ayS literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/chromium_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/chromium_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..5b5db26404baf523052d08ec0a34b748623896ab GIT binary patch literal 384 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB{+2db&7ASP-x#qt$k=1trs4+lG=rzBpUXO@geCxCDwVMS literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/galvanizedsteel_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/galvanizedsteel_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..afbbc42b4b8976b22c627bebdd2533e33c5c6143 GIT binary patch literal 398 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB{-Dd%8G=So9tZO3ZIE5ZHUK;s1*g=iapc8S+_2rmNja0Ie2ld{=W0{*NGyMnM)IXidBDR zs|*!7e*SLTrR2OH{})+=D?iQMwS0|^Ti%BKCr(Y)<~b}-cK_r8hg040h9~*=&lCN* zeCs#0n-fJJ2wMERqbyhYTwB=5;r2EGmd4K46^a!mN=7oZk0b2r=UOsG=j3$n&P(8o ze!{M#f4I3z{qjWbr6L?h5}UuS-JP3u{aWDiDb;gx;>+j$=h?@nsklh{!R?i=rQ!r0 oChwGbTh90E+mC9KL-QYTzopr0P_c&r2qf` literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/iridium_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/iridium_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..f33bce703172b39347821c4d2fd11b96c27188bf GIT binary patch literal 408 zcmV;J0cZY+P)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vGf6951U69E94oEQKA0Rc%wK~y-6#Zt*m!!QsXPZRzJ zBvP@6D+rMKsazzdgbE>vev=j@nv-1*J}1mJda*(Xg+@Q{omF)AGVYyueY?AKDFFneX*Pk z0x(Han2h=jK&f?H@#Fpe$61jF=i*p)Bmx|P=kI6KE_Qx01qv!-AW1r4Dg)W+wer!3 ze2=j>(jRm$f`EX%s@m!%(y&GAER?!z5bk%+TP~fopb(n`*%!Nz$yyxo?~~t2MBuE6 z-@)|lju2G<@aq&s0oXw}I_tI02M2X~&hC!ztMv&n+?B7Vw~jyn0000eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB{-jdb&7b`tKzgr z{K+}&CYrwIEHk-3jQ-{8MD}G?B{%+c4Cvn@j{mKx23*|=53eJ zlORn^zAA7PEY&KN#UEH5O^~>yKCwF-E zMKriA_z|UiZ{y{uoQezHS8veqKB{noQ;JtGRL}Qu!Mk1mbPk;L3biTK^*f% e`@0@xs literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/manganese_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/manganese_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..c15b0575a5996fc66a18895f21507d05db27fa6d GIT binary patch literal 414 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB|9I_jGX#vFJS-blZ#BP++d-^8dWdz-=!lS9P04bZc5bQgRVmFlGeO8 zPF$Dxzvfcq`v9Gyjn#FwlS_ZSc(mVj;#1wHSADhe`Tcdxwe0qspWf)<^1;SinkDvC z-qGv3kFL*F-R+@kzo*9f)@+%Dq6vpDESBT(Ki>Q9@wPW*DGeNXdt3rS+1#dc-n!&+ z;OMH=GV=V()Ey5@o}1;ec*p58Jo2eGJuE!>qV?WdFWoRR;mg#!>Yvl!uSnzDAM9{o z#;)D$`rC^gTN@LWFE*D>|NY_Bny+>i9d8-)^%Ix>V0pTAqJctmCMYHtJYD@<);T3K F0RXElpYi|z literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/nichrome_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/nichrome_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..726c1eace40c2a3e7f3845d1a676b36963af1880 GIT binary patch literal 421 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB|Az^K@|xvFJS-6zj!oC@}BLKbh4|-181KiS23RUmCP@>(nOo z53a69FG>GMTAB2+bC>pRPq(zvN9&}21$5Q%pA=X7eAfIV!yof06RzgCL{4**IX*#+ z)1+T7c1_Rqc4MPs4xbC&^omFL>34j8{#v-I+D8A!{UlTAt6KZ-l)U_#8BpB(^l*_? zNoSzQ!9Ux=Js)iUm{fTG{;u#RN26ZzZc^4fp!?z9@4j10&c!dA!Zi1}K#SrFo93|e zMLSHDq6@xkRjyfYTf(aHbM+hly8#^0PZ)oCT<%JEoDfi)+0(|*yvo7*%KoEIbozAs z=liHLX3YJ4KcTMNtK)^xkMc=hr36IZ$j4uOXU(2keJm$0arqAxJNtem-^f>wfWgJ! M>FVdQ&MBb@0QwiFdH?_b literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/osmium_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/osmium_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..cf803c4a09fd23ee5a380de21b16bae9727ccb26 GIT binary patch literal 407 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB|9I@N{tuvFJS-li=@j{A-c_-=)_EIt&RD;FpJ88eh0pzH-l*3b z9_4KOR~q(Y;i^M>-*~WCFr9BtzhYQcXS7sAB3ZGkDxs(GWx)aqsld?d5?#t)-#$3Q z?NPjTRb7d)<_)F#35$N@2=?jymYses(&5ck&Sm%CIH_e99(nS~x|3ne)`!*=r@~rR y)G+TqSGS%~@L^ubRm=CXCT}gvZ4S+U#9erz-$wlOlYPJdV(@hJb6Mw<&;$UJ`Jg)h literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/plutonium_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/plutonium_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..ab038d659f127e8eb37b4e293b7621ce265db371 GIT binary patch literal 419 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB|Az@pN$vvFJS-bk>X6P+;DY-L=cQmj2-5(yREg$Vqgo{vvi! zn{PpZnQxgZG(x-IKEAQzOlW_Un3(Xzp6lYGTsrO{~w|k1o<9kh;blTI5**5zq z?#%hUx9G0yK7m_I+w`wB|6V8O_xwn4{r8FQ`qS3^zb*A$%=GEOdo69p*PEK}+9NUd zjIBYRvcwyIZHCP?{Mz@*=GV^&{Tz@Wak|f?LHWa{zgMosi1>H%@BVg-7=L z*QpCKMoI5$p53f|iT5evAFF3)EsoB8-FvI@{em3+!qS%bwfwTm6GR?gDLfAhECx?k KKbLh*2~7ZXRHXm_ literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/rutile_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/rutile_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..72639290f39df587f21a36a3ec553462b679d71f GIT binary patch literal 411 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB|9I@pN$vvFJS-blb1RKw$2*P4>$>h4vrt{owRy|Br+{4q{<_H(c;7;)mK~D(w{j*PAMp=yZZFk zk1i*ZV+S^tXP!0ph)-Jb_3Jmb{f{?vmUrt!_G#_6z4!3^<)kNW-^^bYUlE-8_&{}m zy~2gN5g&JQYrz4n18%d}Q*3y_&`^p-@#)T0g`KD-HUag_J;vJ@$ZE){!hYpo>o-)~d?s#A6B z@(-COSqZ-=?XqFlgTe~DWM4f DPLiX% literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/stainlesssteel_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/stainlesssteel_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..7683080dc592c32e5f738089c3e1a736867e9458 GIT binary patch literal 394 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB{*td%8G=So9tZdYi|jC=fH@^TR*uuC-rTcFlTfLe08Iu6;`* zZygQR`)A;5ee&R;P1eQ*4F7o#9y~dtV5h~4@7x=KH~+w*%<)veV_7zM2- zFX`Ys!F2X}`jN%&zN9#D6s%)hT6MvK{qoTmmu$vlo!xH=Z07U`Gm5)k6I=VJLB*`2 zVBPE4ZOtDwf?o m*Wc6xU6|&6UzvMeK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB{-jd%8G=So9tZddt^jz@sBvV<{Tgb3VgF>yzSxYE8FoS4DK2 zbp9B)={J_XTvz^P<&IxoaYIu2Pb+PY}#FnV6g6F}>`<&{|Ot^M7 ztJiZz$jAIbug`r&8}24c$y?{S6i-&o^evH*=G(o%b-_&uLH3Ks_j3KpT-)nebcE>& z)7tmxYxcb=NfqOmu&JfdmLbyX!=IV1YZa43xr41``tN$`9(dCJ%KQ4W1|6-X3wCT{ zH!X6Po$jSA;IxIi;{2;rH!>V&EyXKyL8l(B^wCnSlw+k}+ f_w5tg*JU01Xv&w6M_2v;z0Tn2>gTe~DWM4flwp?B literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/titanium_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/titanium_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..5ed04a22870a86dcf9c398f270dc8d1351d052d9 GIT binary patch literal 398 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB{-Dd%8G=So9tZ`kU8eAh18|L27IW&*dKs-JQltTt>D2UQU_$ zmYZHXNd1ox+pG5F)Ym%sT*f%rrpb%;BwjWVa1~q;b?|r5!>qp^n`L@UCmbox-`oAC z$aR+eHfx)CU3&F-B2ROV?76+X;?(JhQa9u0bn9Q2Xj80_>uY9PI%WRH33LCy3B7bY z>b349B{cmdKI;Vst0NFj65&!@I literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/tungsten_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/tungsten_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..cbe271a973bb700ce82509a24a373693871459b6 GIT binary patch literal 381 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB{+&dAc};So9tZddt;pz~j^XYZjN*50`&|fq}oii!-I}e3yM>QT8sI=Wv6_XPe{O za!db3Yb@1yx=3TgYpc_}Hu}HznfLB{?;JU8I-8ZW`m0_4D#ASXKem`N`$-3D220m_ z(?@-C*ZFWTzTVaJywYk}rdEcD%axray@xABT-_RGZ(UXKvTDu**D8h2Ht`&X4YtGt z&7JbeYl7X)nLXFvbj@fJYFHXS!eUB-sa7=zuvk1(31Of XZxS=_wd4;#A2WEm`njxgN@xNA-piGe literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/uranium_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/ore_overlays/uranium_ore.png new file mode 100644 index 0000000000000000000000000000000000000000..7fe6c81a9f4636fd9721508c59991dd187901506 GIT binary patch literal 412 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB|9I^>lFzvFJS-bUTa5P+)FK`P=WBTg28r5dRUxqPcCK=R%QR z>~>ootzCEH?^dbYWTVt3;j-Na+k?Iwc;_?YjAi;HhB?wrlTD-_Z2#BfRm7OJ`oXhT zC$E2+vCP)wn8RnInDU4;evd+vcMoz(pMScnbe2u^>Ghksde{C=lAamAr~c0GV=8_R za_{Mytaz)o#v=0DKbzG{=5JMtP;u9BP!9OB*Ig!gxwf!V(>19_7DXFF>%~l@7M1Sg z={@|j^zB9Ng3ZBaPac~y!L>?Z_gpQ3YpV}W^3LYrkQC*NVlPvBY-+7kR5w>lpkV#s zbc48(z*8s8FTBeK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a z5n0T@z%2~Ij105pNB{+Ydb&7@Zk~qz=Xw8iR~6d^-jgj0nbXxNEhBeo6;G$}+yl|u_8n@^B zMFndi#=OSw>&vf{U#(2>Vw6aB%;r5-GC?o+=vu*_^OD!dKJ4fJte|jGewBHwZi}wf z>xFy!WFU8GbZ8()Nlj2>E@cM*00H_*L_t(I%WacUPunmQ zg}-=7S8&TBRmgx*wNg|g#^YZ0>-dEm5)4L0yTL{}GBgoJoFbAoduS6%*Yg-V*WW$& zT<7rcu&~xLFDWIy?*kA70mc~OIA+;Bckzc(VHg6?Xfy!ueIMWV34#En6ad!RX@Mw; z*y`@_^z=lnRwGSEz_ftYnlKEJQUdVq{RhV533tQGX98mk!@+s=`sR*7O37+#6Hl%p z=kV?M$^R{sh$VLatX{{yC zbJp8kvMk5*JhCif7>4NGeTrhj{moBAiBtC0HV0^}(OPpgH?l0ta2_AfTH`p*GeHzZ zD5WUsP1M^SqtPR299Mm2fByh*Kl=u-_PT)(uZW@uS1EXq;6aAsqDXK4Je1Aq3W@2q7w}hu_!LZn$LzO5-+n8qA^q!0nGg wl~k>DwUuS~+{O7`9Dl?ZQ@vJdah=PvfBG}_3HzeBBme*a07*qoM6N<$f>+Wc?*IS* diff --git a/src/main/resources/assets/modernmetals/textures/blocks/plutonium_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/plutonium_ore.png deleted file mode 100644 index acad015acd199917cb478ddb15cd39fbfcdba537..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 704 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 Hu0mjfnUn{V diff --git a/src/main/resources/assets/modernmetals/textures/blocks/uranium_ore.png b/src/main/resources/assets/modernmetals/textures/blocks/uranium_ore.png deleted file mode 100644 index cf42b1453e3ff349db2ce73ffe1eac2293ac60f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 690 zcmV;j0!{siP)WFU8GbZ8()Nlj2>E@cM*00I_CL_t(I%WacQPt`ya zhMx{CU(-cNu1;WxVP%O4aFdH9>|FV`_-lRy6u|&dFhygGp~OZLXVEFpPZzf>mmAM+ z&dhn|nRm`{_V)G~Yb}!@r6dRf0HP=&3`2}D{O_DQUC>1w#{l@g4?qwE1VKO)MJS~J zu-1+Rk|bek8}j0gS#OT)CZnxKYfT)-NGSn$@iL&QGA=(a-V=mj$l0+0<^b5-UO-BR zwWohsT>OnOvd2F@=osz2K!@02zk5B-ouAACIgq2ZW;&uM!nM|7?O3hD+}ehsEI8WF zky4T*34@g@=I0kUe0vK(nx-hFa64sV3>%}Gs;U4F&-EDY6ns6WH7zB(v1t9|A%QW5 z_02n0)&%e0yjziPbe7o=w9Zu)PXxBw+ zjn~VC9M>RdIWpV~pwFdHo~kO@?uPuw3DY8ImNy zRZ3y4Z)qx1Q(|sutX39XiYzrUGurqBf9@8BMigV1JmcH$GPj; Yf3?H^&2FSd?EnA(07*qoM6N<$fWFU8GbZ8()Nlj2>E@cM*00HeuL_t(I%WacgZ`v>v zh95%s7)Dj5P4k6YGziS?{iglA{={jtRp(NKR5fkAn?+p=h$OI!D&}Lncb4tr=e+N6 z?eg++Bu9)|xa;Id(2Qyih4g5&*j0E&x#!5k(Qk7^IW{IOpC9SZj$# zLst2UW~0e`G53X(QY1-&5CVX&-@j27B~OpZmOyLGbaL-64Tdp72tIuJh!CAU{9iY> z|F=*fR@hfR$8U%;w?}d)V+^5l4(A*~2#*<$hUCSH+v{6|5Ljzzi#F|6o13c}0J1DY zN=aB%PSccr+^1ZZ05rlTLp|d8@yVMuUo7@Wj4_01nsVAZ5lSiF zJXo*S1ob*umZ7!wuJ_|Uv*`?daZXX@g!Km5>)*XXA;84k`OGULopTgrNw41{&kGuj zCV9RhNfL&7#AdVRC3zvJaH`()#W_kTlu~<<<@t)GTcVVrMr})Atwl;nTeSH+=(AWZ zNYm8!`TFP8n_Q{YX%Pf;nW28z7cnz$QH zrz;48fT137Zzcrw8qUoLYJsQv{cQc;2zT+Ea|}iU%JtUehuMRlRHc+3>uUJI!+gg- j#%QhmYn2YabG7#m3+(jjuroOc00000NkvXXu0mjfyiFcj diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index b0f2ba20..6932238a 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,6 +1,6 @@ { "pack":{ - "pack_format":2, + "pack_format":3, "description":"Modern Metals" } } \ No newline at end of file diff --git a/src/test/java/zone/moddev/mc/modernmetals/ContentPolicyTests.java b/src/test/java/zone/moddev/mc/modernmetals/ContentPolicyTests.java new file mode 100644 index 00000000..24404f76 --- /dev/null +++ b/src/test/java/zone/moddev/mc/modernmetals/ContentPolicyTests.java @@ -0,0 +1,39 @@ +package zone.moddev.mc.modernmetals; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import zone.moddev.mc.modernmetals.content.ContentPolicy; + +final class ContentPolicyTests { + @Test void oldAndInvalidSettingsPreserveHighFantasy() { + for(String mode:new String[]{null,"","high_fantasy","unknown"}) + assertTrue(ContentPolicy.forMode(mode).allows("plutonium","pickaxe")); + } + @Test void lowFantasyRetainsIndustrialMaterialsAndRestrictsEquipment() { + final ContentPolicy low=ContentPolicy.forMode(" LOW_FANTASY "); + for(String name:new String[]{"plutonium","uranium","thorium","boron","beryllium","cadmium","rutile","chromium","magnesium","tungsten"}) { + assertFalse(low.allows(name,"pickaxe"),name); + assertFalse(low.allows(name,"helmet"),name); + for(String form:new String[]{"ore","ingot","powder","fluid","plate","block"}) assertTrue(low.allows(name,form),name+":"+form); + } + for(String name:new String[]{"titanium","stainlesssteel","galvanizedsteel"}) { + assertTrue(low.allows(name,"pickaxe"));assertTrue(low.allows(name,"helmet")); + assertFalse(low.allows(name,"bow"));assertFalse(low.allows(name,"crossbow"));assertFalse(low.allows(name,"fishing_rod")); + } + } + @Test void allModernMaterialsFollowTheEquipmentAndEngineeringMatrix() { + final ContentPolicy low=ContentPolicy.forMode("low_fantasy"); + final java.util.Set tools=new java.util.HashSet<>(java.util.Arrays.asList("galvanizedsteel","stainlesssteel","titanium")); + final java.util.Set protective=new java.util.HashSet<>(java.util.Arrays.asList("aluminum","aluminumbrass","galvanizedsteel","stainlesssteel","titanium")); + final java.util.Set structural=new java.util.HashSet<>(java.util.Arrays.asList("aluminum","aluminumbrass","galvanizedsteel","nichrome","stainlesssteel","tantalum","titanium","tungsten","zirconium")); + for(String name:new String[]{"aluminum","aluminumbrass","beryllium","boron","cadmium","chromium","galvanizedsteel","iridium","magnesium","manganese","nichrome","osmium","plutonium","rutile","stainlesssteel","tantalum","thorium","titanium","tungsten","uranium","zirconium"}) { + for(String form:new String[]{"pickaxe","axe","sword","shovel","hoe","crackhammer","scythe","shears","arrow","bolt"}) + assertEquals(tools.contains(name),low.allows(name,form),name+":"+form); + for(String form:new String[]{"helmet","chestplate","leggings","boots","horse_armor","shield","gear"}) + assertEquals(protective.contains(name),low.allows(name,form),name+":"+form); + for(String form:new String[]{"door","trapdoor","bars","stairs","slab","pressure_plate","button","fence","fence_gate","wall","bookshelf","ladder","lever","tripwire_hook","flower_pot"}) + assertEquals(structural.contains(name),low.allows(name,form),name+":"+form); + for(String form:new String[]{"bow","crossbow","fishing_rod"}) assertFalse(low.allows(name,form),name+":"+form); + } + } +} diff --git a/src/test/java/zone/moddev/mc/modernmetals/MaterialConfigurationTests.java b/src/test/java/zone/moddev/mc/modernmetals/MaterialConfigurationTests.java new file mode 100644 index 00000000..bec2e26d --- /dev/null +++ b/src/test/java/zone/moddev/mc/modernmetals/MaterialConfigurationTests.java @@ -0,0 +1,17 @@ +package zone.moddev.mc.modernmetals; + +import static org.junit.jupiter.api.Assertions.*; +import java.lang.reflect.Field; +import org.junit.jupiter.api.Test; +import zone.moddev.mc.modernmetals.util.Config; + +class MaterialConfigurationTests { + private void enabled(final String name) throws Exception { + final Field field = Config.Options.class.getField("enable" + name); + assertTrue(field.getBoolean(null), name + " must be enabled by default"); + } + @Test void boronIsConfigurable() throws Exception { enabled("Boron"); } + @Test void berylliumIsConfigurable() throws Exception { enabled("Beryllium"); } + @Test void thoriumIsConfigurable() throws Exception { enabled("Thorium"); } + @Test void titaniumMaterialRemainsEnabled() throws Exception { enabled("Titanium"); } +} diff --git a/src/test/java/zone/moddev/mc/modernmetals/NativeProviderTests.java b/src/test/java/zone/moddev/mc/modernmetals/NativeProviderTests.java new file mode 100644 index 00000000..d0303833 --- /dev/null +++ b/src/test/java/zone/moddev/mc/modernmetals/NativeProviderTests.java @@ -0,0 +1,56 @@ +package zone.moddev.mc.modernmetals; + +import static org.junit.jupiter.api.Assertions.*; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import net.minecraft.util.ResourceLocation; +import org.junit.jupiter.api.Test; +import zone.moddev.mc.orespawn.api.WorldgenProvider; + +class NativeProviderTests { + private Optional build(final Predicate available) throws Exception { + final Class provider = Class.forName("zone.moddev.mc.modernmetals.worldgen.ModernMetalsWorldgenProvider"); + return (Optional) provider.getMethod("build", Predicate.class).invoke(null, available); + } + @Test void preservesHistoricalRulesAndAddsOptionalTitanium() throws Exception { + final JsonObject nativeProvider = build(id -> true).get().toJson(); + Files.createDirectories(Paths.get("build/reports")); + Files.write(Paths.get("build/reports/native-provider.json"), (nativeProvider.toString()+"\n").getBytes(StandardCharsets.UTF_8)); + assertEquals(5, nativeProvider.get("schema_version").getAsInt()); + final JsonObject ores = nativeProvider.getAsJsonObject("ores"); + assertEquals(17, ores.entrySet().size()); + final JsonObject old = new JsonParser().parse(new String(Files.readAllBytes(Paths.get( + "tools/validation/legacy111/fixtures/orespawn3-modernmetals.json")), StandardCharsets.UTF_8)).getAsJsonObject().getAsJsonObject("spawns"); + for (final Map.Entry entry : old.entrySet()) { + final JsonObject params = entry.getValue().getAsJsonObject().getAsJsonObject("parameters"); + final JsonObject ore = ores.getAsJsonObject("modernmetals:legacy/"+entry.getKey()); + assertEquals("orespawn:" + entry.getKey().replace("_ore", ""), ore.get("material").getAsString()); + assertTrue(ore.get("enabled").getAsBoolean()); + assertFalse(ore.get("retrogen").getAsBoolean()); + assertFalse(ore.get("native_generation").getAsBoolean()); + final JsonObject placement = ore.getAsJsonObject("dimension_selectors").getAsJsonObject("orespawn:all_except_nether_end"); + assertEquals("orespawn:standard", placement.get("placement_channel").getAsString()); + assertEquals(params.get("frequency").getAsDouble(), placement.get("frequency").getAsDouble()); + assertEquals(params.get("maxHeight").getAsInt()-1, placement.get("max_y").getAsInt()); + assertEquals(Math.max(1,params.get("size").getAsInt()-params.get("variation").getAsInt()), placement.get("min_quantity").getAsInt()); + assertEquals(params.get("size").getAsInt()+params.get("variation").getAsInt()-1, placement.get("max_quantity").getAsInt()); + } + final JsonObject titanium = ores.getAsJsonObject("modernmetals:legacy/titanium_ore"); + assertFalse(titanium.get("enabled").getAsBoolean()); + assertEquals(ores.getAsJsonObject("modernmetals:legacy/rutile_ore").get("dimension_selectors"),titanium.get("dimension_selectors")); + for (final String section : new String[]{"rocks","terrain_dimensions","geomes","fluid_deposits"}) + assertEquals(0,nativeProvider.getAsJsonObject(section).entrySet().size()); + } + @Test void omitsMissingOrDisabledBlocks() throws Exception { + assertFalse(build(id -> false).isPresent()); + final JsonObject ores = build(id -> !id.getResourcePath().equals("boron_ore")).get().toJson().getAsJsonObject("ores"); + assertEquals(16, ores.entrySet().size()); + assertFalse(ores.has("modernmetals:legacy/boron_ore")); + } +} diff --git a/src/test/java/zone/moddev/mc/modernmetals/compat/LegacyNamespaceTransformerTests.java b/src/test/java/zone/moddev/mc/modernmetals/compat/LegacyNamespaceTransformerTests.java new file mode 100644 index 00000000..bbd8b083 --- /dev/null +++ b/src/test/java/zone/moddev/mc/modernmetals/compat/LegacyNamespaceTransformerTests.java @@ -0,0 +1,87 @@ +package zone.moddev.mc.modernmetals.compat; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodNode; + +class LegacyNamespaceTransformerTests { + + @Test + void linksOldTypesFieldsInheritanceAndReflectionToCurrentClasses() { + final byte[] output = new LegacyNamespaceTransformer().transform("example.Consumer", null, fixture()); + final ClassNode type = new ClassNode(); + new ClassReader(output).accept(type, 0); + assertEquals("example/Consumer", type.name); + assertEquals("zone/moddev/mc/modernmetals/proxy/CommonProxy", type.superName); + assertEquals("Lzone/moddev/mc/modernmetals/ModernMetals;", ((FieldNode) type.fields.get(0)).desc); + + final MethodNode method = (MethodNode) type.methods.get(0); + assertEquals("zone/moddev/mc/modernmetals/ModernMetals", ((FieldInsnNode) method.instructions.get(0)).owner); + assertEquals("zone.moddev.mc.modernmetals.init.Materials", ((LdcInsnNode) method.instructions.get(2)).cst); + assertEquals(Type.getObjectType("zone/moddev/mc/modernmetals/util/Config$Options"), + ((LdcInsnNode) method.instructions.get(4)).cst); + assertEquals("other.mod/textures/ore.png", ((LdcInsnNode) method.instructions.get(6)).cst); + } + + @Test + void doesNotRelocateAddonOwnedClassesInsideTheHistoricalPackage() { + final ClassWriter source = new ClassWriter(0); + source.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "com/mcmoddev/modernmetals/addon/Extra", null, + "com/mcmoddev/modernmetals/proxy/CommonProxy", null); + source.visitEnd(); + + final ClassNode type = new ClassNode(); + new ClassReader(new LegacyNamespaceTransformer().transform("com.mcmoddev.modernmetals.addon.Extra", null, + source.toByteArray())).accept(type, 0); + assertEquals("com/mcmoddev/modernmetals/addon/Extra", type.name); + assertEquals("zone/moddev/mc/modernmetals/proxy/CommonProxy", type.superName); + } + + @Test + void leavesCanonicalClassesAndTheCyanoBridgeAlone() { + final LegacyNamespaceTransformer transformer = new LegacyNamespaceTransformer(); + final byte[] bytes = fixture(); + assertSame(bytes, transformer.transform("zone.moddev.mc.modernmetals.ModernMetals", null, bytes)); + assertNull(transformer.transform("missing", "missing", null)); + + final ClassWriter source = new ClassWriter(0); + source.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "example/Golem", null, "java/lang/Object", null); + source.visitField(Opcodes.ACC_PUBLIC, "old", "Lcyano/modernmetals/init/Blocks;", null, null).visitEnd(); + source.visitEnd(); + final byte[] golem = source.toByteArray(); + assertSame(golem, transformer.transform("example.Golem", null, golem)); + } + + private static byte[] fixture() { + final ClassWriter type = new ClassWriter(0); + type.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "example/Consumer", null, + "com/mcmoddev/modernmetals/proxy/CommonProxy", null); + type.visitField(Opcodes.ACC_PUBLIC, "mod", "Lcom/mcmoddev/modernmetals/ModernMetals;", null, null).visitEnd(); + + final MethodVisitor method = type.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "link", "()V", null, null); + method.visitCode(); + method.visitFieldInsn(Opcodes.GETSTATIC, "com/mcmoddev/modernmetals/ModernMetals", "instance", "Lcom/mcmoddev/modernmetals/ModernMetals;"); + method.visitInsn(Opcodes.POP); + method.visitLdcInsn("com.mcmoddev.modernmetals.init.Materials"); + method.visitInsn(Opcodes.POP); + method.visitLdcInsn(Type.getObjectType("com/mcmoddev/modernmetals/util/Config$Options")); + method.visitInsn(Opcodes.POP); + method.visitLdcInsn("other.mod/textures/ore.png"); + method.visitInsn(Opcodes.POP); + method.visitInsn(Opcodes.RETURN); + method.visitMaxs(1, 0); + method.visitEnd(); + type.visitEnd(); + return type.toByteArray(); + } +} diff --git a/src/test/java/zone/moddev/mc/modernmetals/util/ContentModeConfigurationTests.java b/src/test/java/zone/moddev/mc/modernmetals/util/ContentModeConfigurationTests.java new file mode 100644 index 00000000..8af4a5bd --- /dev/null +++ b/src/test/java/zone/moddev/mc/modernmetals/util/ContentModeConfigurationTests.java @@ -0,0 +1,41 @@ +package zone.moddev.mc.modernmetals.util; + +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +final class ContentModeConfigurationTests { + @Test void defaultSelectorIsHighFantasyAndRequiresRestart() { + final Configuration config = new Configuration(); + assertEquals("high_fantasy", Config.contentMode(config)); + final Property selector = config.getCategory("General").get("contentMode"); + assertArrayEquals(new String[]{"high_fantasy", "low_fantasy"}, selector.getValidValues()); + assertTrue(selector.requiresMcRestart()); + assertEquals("config.modernmetals.content_mode", selector.getLanguageKey()); + } + + @Test void selectedLowFantasySurvivesRepeatedReads() { + final Configuration config = new Configuration(); + config.get("General", "contentMode", "high_fantasy").set(" LOW_FANTASY "); + assertEquals("low_fantasy", Config.contentMode(config)); + assertEquals("low_fantasy", Config.contentMode(config)); + } + + @Test void invalidModeFallsBackToHighFantasy() { + final Configuration config = new Configuration(); + config.get("General", "contentMode", "high_fantasy").set("invalid_value"); + assertEquals("high_fantasy", Config.contentMode(config)); + assertEquals("high_fantasy", config.getCategory("General").get("contentMode").getString()); + } + + @Test void tradeSwitchDefaultsToEnabledAndPreservesAnExplicitOptOut() { + final Configuration config = new Configuration(); + assertTrue(Config.villagerTrades(config)); + final Property selector = config.getCategory("General").get("enableVillagerTrades"); + assertTrue(selector.requiresMcRestart()); + assertEquals("config.modernmetals.villager_trades", selector.getLanguageKey()); + selector.set(false); + assertFalse(Config.villagerTrades(config)); + } +} diff --git a/tools/validation/legacy111/EclipseModelCheck.java b/tools/validation/legacy111/EclipseModelCheck.java new file mode 100644 index 00000000..56af1d95 --- /dev/null +++ b/tools/validation/legacy111/EclipseModelCheck.java @@ -0,0 +1,95 @@ +package review.ide; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import java.util.stream.Collectors; +import org.gradle.tooling.GradleConnector; +import org.gradle.tooling.ProjectConnection; +import org.gradle.tooling.model.build.BuildEnvironment; +import org.gradle.tooling.model.eclipse.EclipseProject; +import org.gradle.tooling.model.eclipse.EclipseSourceDirectory; +import org.gradle.tooling.model.gradle.GradleBuild; + +/** Requests the same project models used by Eclipse Buildship's import wizard. */ +public final class EclipseModelCheck { + + public static void main(final String[] args) throws Exception { + final File project = new File(args[0]); + final File cache = new File(args[1]); + final File javaHome = new File(args[2]); + + try (ProjectConnection connection = GradleConnector.newConnector() + .forProjectDirectory(project).useGradleUserHomeDir(cache) + .useBuildDistribution().connect()) { + final BuildEnvironment environment = connection.model(BuildEnvironment.class) + .setJavaHome(javaHome).withArguments("--offline").get(); + final GradleBuild build = connection.model(GradleBuild.class) + .setJavaHome(javaHome).withArguments("--offline").get(); + final EclipseProject eclipse = connection.model(EclipseProject.class) + .setJavaHome(javaHome).withArguments("--offline").get(); + + if (!"9.6.1".equals(environment.getGradle().getGradleVersion()) + || !"ModernMetals".equals(build.getRootProject().getName()) + || !"ModernMetals".equals(eclipse.getName()) + || eclipse.getSourceDirectories().stream().noneMatch(source -> "build/resources/main".equals(source.getPath())) + || !"1.8".equals(eclipse.getJavaSourceSettings().getSourceLanguageLevel().toString())) { + throw new AssertionError("Wrong wrapper, project or Java source level in the Eclipse model"); + } + eclipse.getClasspath().forEach(dependency -> { + if (!dependency.getFile().isFile()) { + throw new AssertionError("Missing Eclipse library: " + dependency.getFile()); + } + }); + if (eclipse.getSourceDirectories().stream().map(source -> source.getPath()).distinct().count() + != eclipse.getSourceDirectories().size()) { + throw new AssertionError("Duplicate Eclipse source directories"); + } + + for (final EclipseSourceDirectory source : eclipse.getSourceDirectories()) { + verifySourcePackages(new File(project, source.getPath()).toPath()); + } + + if (args.length > 3) { + final List libraries = new ArrayList<>(); + eclipse.getClasspath().forEach(dependency -> libraries.add(dependency.getFile().getAbsolutePath())); + Files.write(new File(args[3]).toPath(), libraries, StandardCharsets.UTF_8); + } + + System.out.println("MODERN_METALS_ECLIPSE_MODEL_PASS gradle=" + + environment.getGradle().getGradleVersion() + " source=1.8 libraries=" + + eclipse.getClasspath().size() + " gradleJava=" + + environment.getJava().getJavaHome()); + } + } + + private static void verifySourcePackages(final Path root) throws IOException { + if (!Files.isDirectory(root)) return; + + final Pattern packageDeclaration = Pattern.compile("(?m)^\\s*package\\s+([\\w.]+)\\s*;"); + final List sources; + try (Stream files = Files.walk(root)) { + sources = files.filter(path -> path.toString().endsWith(".java")) + .collect(Collectors.toList()); + } + + for (final Path file : sources) { + final Path parent = root.relativize(file).getParent(); + final String expected = parent == null ? "" : parent.toString().replace(File.separatorChar, '.'); + final String text = new String(Files.readAllBytes(file), StandardCharsets.UTF_8); + final Matcher declaration = packageDeclaration.matcher(text); + final String declared = declaration.find() ? declaration.group(1) : ""; + + if (!expected.equals(declared)) { + throw new AssertionError("Package does not match the Eclipse source folder: " + file); + } + } + } +} diff --git a/tools/validation/legacy111/README.md b/tools/validation/legacy111/README.md new file mode 100644 index 00000000..4a25e67d --- /dev/null +++ b/tools/validation/legacy111/README.md @@ -0,0 +1,105 @@ +# Modern Metals 1.11 qualification + +Tests and probes are isolated from release jars. All game runs use disposable +directories and worlds. The packaged suite fingerprints its source fixture world +and confirms that imports/reloads do not rewrite it. + +## Automated coverage + +- **JUnit.** Checks default-enabled materials, historical ore conversion, missing + blocks, optional titanium, empty geology declarations, namespace linkage and + the Low Fantasy matrix for all 21 materials and product families. +- **Resources.** Parse every JSON/mcmeta/mod-info file, validate provider schema 5 + from the pinned production OreSpawn jar, check material plate assets/GUI labels + and registered material model references/parents, and parse all workflows with + full action pins. Historical unregistered silicon item placeholders are retained + and parsed. +- **Installed server.** Checks materials, items, storage, fluids, buckets and + five alloy chains. Covers aliases, statistics, named stacks, equipment and NBT. + Exercises ore and material switches, optional titanium, dimension boundaries, + legacy imports, saved overrides, later chunks and Low Fantasy with Tinkers. +- **Optional mods.** Tinkers 2.7.1.34, its matching Mantle, IC2 + 2.7.81-ex111 and Mekanism 9.3.5. Tests cover molten + forms, alloys, casting, actual tool construction/repair and processing recipes. +- **Dependency rejection.** missing/old Base Metals and OreSpawn fail with explicit + version requirements before `world/level.dat` exists. Historical fixture + downloads are checksum verified. +- **Client.** Checks the Forge configuration factory and the OreSpawn Mods entry, the High/Low Fantasy selector, translated labels, + scrolling, Escape/Done, saved restart-required switches, unchanged live registries, baked models, + creative tabs, achievement labels and integrated-world fresh/reload checks. + +`packagedForgeRuntimeTest` runs 18 world scenarios plus five dependency rejection +cases. See [BUILDING](../../../docs/BUILDING.md) for setup. The server template +contains Forge 1.11.2-13.20.1.2588, vanilla server and libraries, without mods, +configuration or worlds. Do not point a test at an active installation. + +## Client commands + +`runtimeProbeJar` builds the development probe. `packagedRuntimeProbeJar` builds +the installed probe. They must never be distributed as part of the mod. + +```text +python run_eclipse_profile.py first --probe --production-output +python run_eclipse_profile.py reload --probe --production-output +``` + +Merge `build/classes/java/main` and `build/resources/main` into that disposable +output first. Keep the editor's `bin/main` unchanged during qualification. + +On Windows, `run_packaged_client.py` launches the installed jar against the +unmodified obfuscated Minecraft client and Forge universal jar. Supply `--java`, +`--cache`, `--installer`, `--template`, `--output`, `--mirror` and `--phase first|reload`. +It verifies the Forge installer and Minecraft client hashes, stages verified +Windows natives in the disposable game directory, and isolates the universal jar from the server jar named +in its manifest. Every ordinary installed mod is copied into the disposable +directory with the packaged game classpath. Inspect the generated +client reports and screenshots separately from manual acceptance. +The matching Mojang asset index and every referenced object are checksum verified +in a disposable asset directory. The script copies matching cached objects and +downloads the rest from Mojang. A different branch's shared Gradle asset index is not +overwritten or assumed to match Minecraft 1.11.2. + +For the overlay check, run `--phase first` with vanilla textures, then run +`--phase reload --ore-host-pack` against the same disposable world. The probe +checks the baked block and inventory faces and records an ore display screenshot +in each run. Copy the first screenshot before reloading to compare both. +Use `--language de_DE`, `zh_CN` or `ru_RU` to check the actual localized +settings and selector values. + +## Multiplayer and original saves + +`check_multiplayer_mode.py` uses the same client inputs and a loopback-only +dedicated server. Supply `--server-jar` and `--client-jar` to test the installed +archives. The four default scenarios cover each pair of High and Low Fantasy. +The server log verifies that a rejected client never enters the world. + +`check_original_saves.py --java --template +--public-jars --candidate +--output ` verifies the SHA-256 of the original +public jars, creates a 1.11.2 beta4.58 fixture, and checks a separate +upgraded copy on first load and reload. Pass `--mirror` for the staged dependencies. It checks placed blocks, filled +buckets and item stacks with non-default count or damage. It stops on any Forge +prompt to remove missing blocks or items. The source worlds are fingerprinted. + +## Limits and separately owned defects + +Base Metals 2.5.1.111021 and OreSpawn 4.1.5.111021 are staged locally. +The suite tests OS1 and OS3 import and checks that existing native overrides +and saved world profiles survive migration. + +Modern Metals 1.11 owns its `General.contentMode` in `ModernMetals.cfg` and the +Forge configuration screen. `check_content_modes.py --java --template --output ` checks defaults, invalid +values, conflicting Base Metals settings, saved-world mode changes and Low Fantasy +with Tinkers, including fresh/reload behavior. It preserves its source fixture. +Core-mod inheritance is reserved for 1.12. See [CONTENT_MODE](../../../docs/CONTENT_MODE.md). + +The 1.10 regression matrix supplies the corresponding checks for 1.11. +The optional runtime tests cover Tinkers, Mantle, Mekanism and IC2. Other +integrations have source and API coverage only. The local issue matrix +records these limits and reports lacking exact reproduction evidence. Radiation, +new alloys/abilities and Construct's Armory are outside this modernization. + +Hosted CI/CodeQL, manual graphical acceptance, real-world migration, multiplayer +pack qualification and publication remain separate steps. No performance budget +is claimed from successful bounded generation alone. diff --git a/tools/validation/legacy111/audit_resources.py b/tools/validation/legacy111/audit_resources.py new file mode 100644 index 00000000..443aef4c --- /dev/null +++ b/tools/validation/legacy111/audit_resources.py @@ -0,0 +1,119 @@ +"""Validate resources, native provider schema, workflow pins and material assets.""" +import argparse +import json +import re +import zipfile +from pathlib import Path +from referencing import Registry, Resource +from jsonschema import Draft202012Validator +import yaml +from run_runtime_checks import ROOT, dependency + + +def language_entries(path): + entries = {} + for line in path.read_text(encoding='utf-8-sig').splitlines(): + if '=' not in line or line.startswith('#'): + continue + key, value = line.split('=', 1) + assert key not in entries, f'{path.name}: duplicate key {key}' + entries[key] = value + return entries + + +def audit_languages(directory, materials): + languages = {path.stem: language_entries(path) for path in directory.glob('*.lang')} + settings = { + 'config.modernmetals.title', 'config.modernmetals.description', + 'config.modernmetals.content_mode', 'config.modernmetals.content_mode.tooltip', + 'config.modernmetals.villager_trades', 'config.modernmetals.villager_trades.tooltip', + 'config.modernmetals.material.tooltip', + 'config.modernmetals.connection.mismatch', 'config.modernmetals.connection.protocol', + 'high_fantasy', 'low_fantasy', 'itemGroup.modernmetals.combat', + } + settings.update('config.modernmetals.enable_' + name for name in materials) + english = languages['en_us'] + restored_material = re.compile(r'\b(?:boron|beryllium|thorium|titanium)(?=\b|_)') + content = {key for key in english if restored_material.search(key)} + for locale in ('en_us', 'de_de', 'ru_ru', 'zh_cn'): + entries = languages.get(locale, {}) + missing = {key for key in settings | content if not entries.get(key)} + assert not missing, f'{locale}: missing translations {sorted(missing)}' + assert entries['config.modernmetals.connection.mismatch'].count('%s') == 2, locale + for locale in ('en_au', 'en_ca', 'en_gb', 'en_nz', 'en_pt'): + assert locale in languages, f'{locale}: missing regional English file' + +def main(): + parser=argparse.ArgumentParser(description=__doc__) + parser.add_argument('--mirror',type=Path) + args=parser.parse_args() + checked=0 + resources=ROOT/'build/resources/main' + for path in resources.rglob('*'): + if path.is_file() and (path.suffix in ('.json','.mcmeta') or path.name=='mcmod.info'): + json.loads(path.read_text(encoding='utf-8-sig')); checked+=1 + def references(value,key): + if isinstance(value,dict): + for name,child in value.items(): + if name==key and isinstance(child,str) and child.startswith('modernmetals:'):yield child.split(':',1)[1] + else:yield from references(child,key) + elif isinstance(value,list): + for child in value:yield from references(child,key) + assets=resources/'assets/modernmetals' + materials=re.findall(r'material\("([a-z]+)",', (ROOT/'src/main/java/zone/moddev/mc/modernmetals/util/Config.java').read_text()) + audit_languages(assets/'lang', materials) + for path in (assets/'blockstates').glob('*.json'): + for model in references(json.loads(path.read_text()),'model'): + assert (assets/'models/block'/(model+'.json')).is_file(),f'{path.name}: missing block model {model}' + for path in (assets/'models').rglob('*.json'): + # Historical silicon item placeholders belong to no registered material + # in this branch. Preserve those asset names without inventing content. + if path.parent.name=='item' and path.stem.startswith('silicon_') and 'silicon' not in materials:continue + for parent in references(json.loads(path.read_text()),'parent'): + assert (assets/'models'/(parent+'.json')).is_file(),f'{path.name}: missing parent {parent}' + ore_states=sorted((assets/'blockstates').glob('*_ore.json')) + assert ore_states, 'Processed ore assets are missing; run processResources first' + for state in ore_states: + name=state.stem + model=json.loads((assets/'models/block'/state.name).read_text()) + for value in model['textures'].values(): + if value.startswith('modernmetals:'): + texture=value.split(':',1)[1] + assert (assets/'textures'/(texture+'.png')).is_file(),f'{name}: missing texture {texture}' + if name!='thorium_ore': + assert model['parent']=='modernmetals:block/ore_overlay',name + assert model['textures']['base']=='minecraft:blocks/stone',name + assert model['textures']['overlay']==f'modernmetals:blocks/ore_overlays/{name}',name + assert not (assets/'textures/blocks'/(name+'.png')).exists(),f'{name}: stale flat texture' + specs=json.loads((ROOT/'gradle/dependencies.json').read_text()) + with zipfile.ZipFile(dependency(next(s for s in specs if s['id']=='orespawn'),args.mirror)) as jar: + schemas=[json.loads(jar.read(n)) for n in jar.namelist() if n.startswith('META-INF/orespawn/docs/schemas/') and n.endswith('.json')] + registry=Registry().with_resources((s['$id'],Resource.from_contents(s)) for s in schemas) + schema=next(s for s in schemas if s['$id'].endswith('/orespawn-provider.schema.json')) + provider=json.loads((ROOT/'build/reports/native-provider.json').read_text()) + Draft202012Validator(schema,registry=registry).validate(provider) + assert len(provider['ores'])==17 and not provider['ores']['modernmetals:legacy/titanium_ore']['enabled'] + for name in ('boron','beryllium','thorium','titanium'): + for relative in (f'blockstates/{name}_ore.json',f'models/item/{name}_ingot.json', + f'models/item/{name}_plate.json',f'models/block/{name}_plate.json',f'textures/blocks/{name}_plate.png'): + assert (ROOT/'src/main/resources/assets/modernmetals'/relative).is_file(),relative + language=dict(line.split('=',1) for line in (ROOT/'src/main/resources/assets/modernmetals/lang/en_US.lang').read_text().splitlines() if '=' in line and not line.startswith('#')) + assert len(materials)==21 + for key in ('config.modernmetals.content_mode','config.modernmetals.content_mode.tooltip','high_fantasy','low_fantasy'): + assert key in language,key + for name in materials: + assert 'config.modernmetals.enable_'+name in language,name + for relative in (f'models/item/{name}_plate.json',f'models/block/{name}_plate.json',f'textures/blocks/{name}_plate.png'): + assert (ROOT/'src/main/resources/assets/modernmetals'/relative).is_file(),relative + for path in (ROOT/'.github/workflows').glob('*.yml'): + workflow=yaml.load(path.read_text(),Loader=yaml.BaseLoader) + assert workflow.get('on') and workflow.get('jobs'),path + for job in workflow['jobs'].values(): + for step in job.get('steps',[]): + if 'uses' in step: assert re.fullmatch(r'[^@]+@[0-9a-f]{40}',step['uses']),step['uses'] + release=(ROOT/'.github/workflows/deploy-release.yml').read_text() + assert 'confirm_live_publication' in release and '240967(required)' in release and '245586(required)' in release + assert '243157(optional)' in release and '244357' in release + print(f'MODERN_METALS_RESOURCES_PASS json={checked} provider_schema=5 workflows=5') + +if __name__=='__main__':main() diff --git a/tools/validation/legacy111/check_content_modes.py b/tools/validation/legacy111/check_content_modes.py new file mode 100644 index 00000000..ab47b119 --- /dev/null +++ b/tools/validation/legacy111/check_content_modes.py @@ -0,0 +1,59 @@ +"""Check Modern Metals-owned fantasy configuration using installed release jars.""" +import argparse,json,shutil +from pathlib import Path +from run_runtime_checks import ROOT,dependency,prepare,run + +def main(): + parser=argparse.ArgumentParser(description=__doc__) + for name in ('java','template','output'):parser.add_argument('--'+name,type=Path,required=True) + parser.add_argument('--mirror', type=Path) + parser.add_argument('--scenario',choices=['modern-low']) + args=parser.parse_args() + args.output.mkdir(parents=True,exist_ok=False) + specs=json.loads((ROOT/'gradle/dependencies.json').read_text()) + jars=[dependency(s, args.mirror) for s in specs if s.get('runtime')] + jars += [ROOT/'build/libs/ModernMetals-2.5.1.111021.jar',ROOT/'build/test-mods/modernmetals-probe-packaged.jar'] + cases=[('default-with-base-low',None,'low_fantasy',False), + ('modern-low','low_fantasy','high_fantasy',True), + ('modern-high-with-base-low','high_fantasy','low_fantasy',False), + ('invalid-mode','invalid_value','low_fantasy',False)] + completed=[] + for name,modern,base,low in cases: + if args.scenario and name!=args.scenario:continue + target=args.output/name + prepare(target,args.template,jars) + (target/'config').mkdir() + (target/'config/BaseMetals.cfg').write_text('general {\n S:contentMode='+base+'\n}\n') + if modern is not None: + (target/'config/ModernMetals.cfg').write_text('general {\n S:contentMode='+modern+'\n}\n') + for phase in ('first','reload'): + run(args.java,target,phase,{'modern.lowFantasy':str(low).lower(),'modern.saved':'true'}) + text=(target/'config/ModernMetals.cfg').read_text() + assert 'S:contentMode='+('low_fantasy' if low else 'high_fantasy') in text + completed.append(name) + if not args.scenario: + source=args.output/'default-with-base-low'/'world' + # Copy only the disposable fixture; never run against its source directory. + from run_packaged_suite import fingerprint + before=fingerprint(source) + switched=args.output/'high-to-low-existing-world' + prepare(switched,args.template,jars,source) + (switched/'config').mkdir() + (switched/'config/ModernMetals.cfg').write_text('general {\n S:contentMode=low_fantasy\n}\n') + run(args.java,switched,'reload',{'modern.lowFantasy':'true','modern.saved':'true'}) + assert fingerprint(source)==before + completed.append(switched.name) + optional=args.output/'modern-low-tinkers' + prepare(optional,args.template,jars) + (optional/'config').mkdir() + (optional/'config/ModernMetals.cfg').write_text('general {\n S:contentMode=low_fantasy\n}\n') + for spec in specs: + if spec['id'] in ('tconstruct','mantle'): + jar=dependency(spec, args.mirror);shutil.copy2(jar,optional/'mods'/jar.name) + for phase in ('first','reload'): + run(args.java,optional,phase,{'modern.lowFantasy':'true','modern.saved':'true'}) + completed.append(optional.name) + (args.output/'qualification.json').write_text(json.dumps({'passed':True,'scenarios':completed},indent=2)+'\n') + print('MODERN_METALS_OWNED_CONTENT_MODE_PASS',len(completed),flush=True) + +if __name__=='__main__':main() diff --git a/tools/validation/legacy111/check_multiplayer_mode.py b/tools/validation/legacy111/check_multiplayer_mode.py new file mode 100644 index 00000000..0918d2a4 --- /dev/null +++ b/tools/validation/legacy111/check_multiplayer_mode.py @@ -0,0 +1,84 @@ +"""Exercise the fantasy mode handshake over a real loopback Forge connection.""" +import argparse +import json +import socket +import subprocess +import sys +import time +from pathlib import Path + +from run_runtime_checks import FORGE, ROOT, dependency, digest, prepare, write_json + + +def check(args, server_mode, client_mode): + output = args.output / f"{server_mode}-{client_mode}" + server = output / "server" + specs = json.loads((ROOT / "gradle/dependencies.json").read_text()) + jars = [dependency(spec, args.mirror) for spec in specs if spec.get("runtime")] + prepare(server, args.template, jars + [args.server_jar]) + with socket.socket() as reservation: + reservation.bind(("127.0.0.1", 0)) + port = reservation.getsockname()[1] + properties = server / "server.properties" + properties.write_text(properties.read_text().replace("server-port=0", f"server-port={port}")) + config = server / "config/ModernMetals.cfg" + config.parent.mkdir() + config.write_text(f"general {{\n S:contentMode={server_mode}_fantasy\n}}\n") + console = server / "console.log" + mismatch = server_mode != client_mode + rejection = args.incompatible_build or mismatch + with console.open("wb") as log, subprocess.Popen( + [str(args.java), "-Xmx2G", "-jar", FORGE, "nogui"], cwd=server, + stdin=subprocess.PIPE, stdout=log, stderr=subprocess.STDOUT + ) as process: + try: + deadline = time.monotonic() + 90 + while 'For help, type "help"' not in console.read_text(errors="replace"): + if process.poll() is not None or time.monotonic() >= deadline: + raise AssertionError(f"Dedicated server did not start: {console}") + time.sleep(0.25) + command = [sys.executable, str(Path(__file__).with_name("run_packaged_client.py")), + "--java", str(args.java), "--cache", str(args.cache), "--installer", str(args.installer), + "--template", str(args.template), "--output", str(output / "client"), + "--mod-jar", str(args.client_jar), "--remote-port", str(port)] + if args.mirror: + command += ["--mirror", str(args.mirror)] + if client_mode == "low": + command.append("--low-fantasy") + if args.incompatible_build: + command += ["--expected-disconnect", "Use the same Modern Metals build"] + elif mismatch: + command += ["--expected-disconnect", f"The server uses {server_mode.title()} Fantasy, " + f"but your game uses {client_mode.title()} Fantasy"] + subprocess.run(command, check=True, timeout=360) + finally: + if process.poll() is None: + try: + process.communicate(b"stop\n", timeout=45) + except subprocess.TimeoutExpired: + process.kill() + process.wait() + raise + assert process.returncode == 0, console + assert ("ModernCheck joined the game" in console.read_text(errors="replace")) == (not rejection), console + write_json(output / "receipt.json", { + "passed": True, "server_mode": server_mode, "client_mode": client_mode, + "server_sha256": digest(args.server_jar), "client_sha256": digest(args.client_jar), + "joined": not rejection, + }) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + for name in ("java", "cache", "installer", "template", "output", "server-jar", "client-jar"): + parser.add_argument("--" + name, required=True, type=lambda path: Path(path).resolve()) + parser.add_argument("--mirror", type=lambda path: Path(path).resolve()) + parser.add_argument("--scenario", action="append", choices=["high-high", "high-low", "low-high", "low-low"]) + parser.add_argument("--incompatible-build", action="store_true", help="Expect a missing mode advertisement.") + args = parser.parse_args() + for scenario in args.scenario or ["high-low", "low-high", "high-high", "low-low"]: + check(args, *scenario.split("-")) + + +if __name__ == "__main__": + main() diff --git a/tools/validation/legacy111/check_original_saves.py b/tools/validation/legacy111/check_original_saves.py new file mode 100644 index 00000000..febdd1d0 --- /dev/null +++ b/tools/validation/legacy111/check_original_saves.py @@ -0,0 +1,111 @@ +"""Create and upgrade disposable worlds using the public 1.11.2 beta4.58 release.""" +import argparse +import json +import subprocess +import time +from pathlib import Path + +from run_packaged_suite import fingerprint +from run_runtime_checks import FORGE, ROOT, dependency, digest, prepare, write_json + + +PUBLIC_JARS = { + "modern-metals-244357-2534044": "8F5011542650A03A7B25E4BCB410D676A5FB6AD705B91552E494A08A6C43087A", + "base-metals-240967-2533178": "ABDF704023100AD6BB70351CE72C31A07943AD5BCE8308155564194A25089CCC", + "mmd-orespawn-245586-2533159": "05CC48E59C7B20ED9B7F29AE014F5E2E5B2F16F289EF7D6F274F017B5E2A6F77", +} +BLOCKS = [ + ("0 100 0", "aluminum_ore"), + ("1 100 0", "chromium_ore"), + ("2 100 0", "tungsten_block"), + ("7 100 7", "aluminumbrass"), + ("15 100 7", "titanium"), +] +STACKS = [ + ("aluminum_ingot", 7, 0), + ("titanium_pickaxe", 1, 17), + ("plutonium_pickaxe", 1, 3), + ("aluminum_door", 2, 0), + ("tungsten_fishing_rod", 1, 4), + ("titanium_horse_armor", 1, 0), +] + + +def chest(): + items = [f'{{Slot:{slot}b,id:"modernmetals:{item}",Count:{count}b,Damage:{damage}s}}' + for slot, (item, count, damage) in enumerate(STACKS)] + items.append('{Slot:6b,id:"forge:bucketfilled",Count:1b,Damage:0s,tag:{FluidName:"aluminumbrass",Amount:1000}}') + return "{Items:[" + ",".join(items) + "]}" + + +def place(): + containers = ["fill 5 99 5 9 102 9 minecraft:stone 0 hollow", "fill 13 99 5 17 102 9 minecraft:stone 0 hollow"] + blocks = [f"setblock {pos} modernmetals:{block}" for pos, block in BLOCKS] + return containers + blocks + [f"setblock 0 100 2 minecraft:chest 0 replace {chest()}"] + + +def verify(): + blocks = [f"testforblock {pos} modernmetals:{block}" for pos, block in BLOCKS] + return blocks + [f"testforblock 0 100 2 minecraft:chest -1 {chest()}"] + + +def run(java, directory, phase, setup, checks): + console = directory / (phase + "-console.log") + with console.open("wb") as log, subprocess.Popen( + [str(java), "-Xmx2G", "-jar", FORGE, "nogui"], + cwd=directory, stdin=subprocess.PIPE, stdout=log, stderr=subprocess.STDOUT + ) as process: + try: + deadline = time.monotonic() + 100 + while 'For help, type "help"' not in (output := console.read_text(errors="replace")): + if "Run the command /fml confirm" in output: + raise AssertionError(f"Forge requested permission to remove old entries: {console}") + if process.poll() is not None or time.monotonic() >= deadline: + raise AssertionError(f"Server did not reach the world: {console}") + time.sleep(0.25) + commands = setup + checks + ["save-all", "stop"] + process.communicate(("\n".join(commands) + "\n").encode("ascii"), timeout=60) + assert process.returncode == 0, console + finally: + if process.poll() is None: + process.kill() + process.wait() + assert console.read_text(errors="replace").count("Successfully found the block") == len(checks), console + print(f"{directory.name} {phase} passed {len(checks)} block and chest checks", flush=True) + + +def check(args): + jars = [] + for name, expected in PUBLIC_JARS.items(): + jar = args.public_jars / (name + ".jar") + assert digest(jar).upper() == expected, f"Public jar checksum mismatch: {jar}" + jars.append(jar) + source = args.output / "beta4.58-source" + prepare(source, args.template, jars) + run(args.java, source, "capture", place(), verify()) + world = source / "world" + before = fingerprint(world) + specs = json.loads((ROOT / "gradle/dependencies.json").read_text()) + jars = [dependency(spec, args.mirror) for spec in specs if spec.get("runtime")] + target = args.output / "beta4.58-upgraded" + prepare(target, args.template, jars + [args.candidate], world) + try: + for phase in ("first", "reload"): + run(args.java, target, phase, [], verify()) + finally: + assert fingerprint(world) == before, "Original fixture was modified" + write_json(target / "receipt.json", {"passed": True, "candidate_sha256": digest(args.candidate), + "source_world_unchanged": True, "public_jars": PUBLIC_JARS}) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + for name in ("java", "template", "output", "public-jars", "candidate"): + parser.add_argument("--" + name, required=True, type=lambda path: Path(path).resolve()) + parser.add_argument("--mirror", type=lambda path: Path(path).resolve()) + args = parser.parse_args() + check(args) + + +if __name__ == "__main__": + main() diff --git a/tools/validation/legacy111/dependency_requirements.py b/tools/validation/legacy111/dependency_requirements.py new file mode 100644 index 00000000..2ecd4ec3 --- /dev/null +++ b/tools/validation/legacy111/dependency_requirements.py @@ -0,0 +1,44 @@ +"""Reject missing/old public dependencies before any disposable world is loaded.""" +import hashlib +import json +import subprocess +import urllib.request +from run_runtime_checks import FORGE, prepare, write_json + +OLD = { + 'base':('base-metals-240967','2533178','abdf704023100ad6bb70351ce72c31a07943ad5bce8308155564194a25089ccc'), + 'os':('mmd-orespawn-245586','2533159','05cc48e59c7b20ed9b7f29ae014f5e2e5b2f16f289ef7d6f274f017b5e2a6f77'), + 'os4':('mmd-orespawn-245586','8840593','51ff282d41e3c566db2541ccad53968030ea12e985a5cee7307162bbf560c99a') +} + +def verify(java,template,output,base,orespawn,modern): + fixtures=output/'dependency-fixtures';fixtures.mkdir() + old={} + for key,(artifact,version,expected) in OLD.items(): + target=fixtures/f'{artifact}-{version}.jar' + request=urllib.request.Request(f'https://www.cursemaven.com/curse/maven/{artifact}/{version}/{target.name}',headers={'User-Agent':'Mozilla/5.0'}) + with urllib.request.urlopen(request,timeout=45) as response:target.write_bytes(response.read()) + assert hashlib.sha256(target.read_bytes()).hexdigest()==expected,'Public fixture checksum mismatch' + old[key]=target + scenarios={ + 'missing-base':([orespawn,modern],'basemetals','2.5.1.111021'), + 'old-base':([old['base'],orespawn,modern],'basemetals','2.5.1.111021'), + 'missing-orespawn':([base,modern],'orespawn','4.1.5.111021'), + 'old-orespawn':([base,old['os'],modern],'orespawn','4.1.5.111021'), + 'old-orespawn4':([base,old['os4'],modern],'orespawn','4.1.5.111021') + } + result={} + for name,(jars,modid,version) in scenarios.items(): + target=output/name;prepare(target,template,jars) + log=target/'rejection-console.log' + with log.open('wb') as stream: + subprocess.run([str(java),'-Xmx1G','-jar',FORGE,'nogui'],cwd=target,stdout=stream,stderr=subprocess.STDOUT,timeout=90) + text=log.read_text(errors='replace') + requirement = f'Mod modernmetals (Modern Metals) requires [{modid}@[{version},' + assert requirement in text, log + assert 'Preparing level' not in text and not (target/'world/level.dat').exists(), ( + 'Incompatible dependencies reached world loading' + ) + result[name]={'rejected_before_world':True,'required_mod':modid,'minimum':version} + print(name,'PASS',flush=True) + write_json(output/'dependency-requirements.json',result) diff --git a/src/main/resources/assets/modernmetals/orespawn/modernmetals.json b/tools/validation/legacy111/fixtures/orespawn3-modernmetals.json old mode 100755 new mode 100644 similarity index 100% rename from src/main/resources/assets/modernmetals/orespawn/modernmetals.json rename to tools/validation/legacy111/fixtures/orespawn3-modernmetals.json diff --git a/tools/validation/legacy111/run_eclipse_profile.py b/tools/validation/legacy111/run_eclipse_profile.py new file mode 100644 index 00000000..887cf8ca --- /dev/null +++ b/tools/validation/legacy111/run_eclipse_profile.py @@ -0,0 +1,72 @@ +"""Run a generated Eclipse profile in an isolated directory, with a test-only probe.""" + +import argparse +import json +import os +import shlex +import shutil +import subprocess +import time +import xml.etree.ElementTree as ET +from pathlib import Path + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("profile", type=Path) + parser.add_argument("java", type=Path) + parser.add_argument("directory", type=Path) + parser.add_argument("phase", choices=["first", "reload"]) + parser.add_argument("--probe", type=Path, required=True) + parser.add_argument("--production-output", type=Path, + help="Use a disposable Eclipse build without touching the editor's bin directory") + args = parser.parse_args() + profile = args.profile.resolve() + root = ET.parse(profile).getroot() + strings = {node.get("key"): node.get("value") for node in root.findall("stringAttribute")} + environment = os.environ.copy() + for node in root.findall("mapAttribute/mapEntry"): + environment[node.get("key")] = node.get("value") + production_output = (args.production_output or profile.parent / "bin/main").resolve() + environment["MOD_CLASSES"] = str(production_output) + + archives = [] + for node in root.findall("listAttribute[@key='org.eclipse.jdt.launching.CLASSPATH']/listEntry"): + entry = ET.fromstring(node.get("value")) + path = entry.get("externalArchive") + if path: + archives.append(path) + elif entry.get("projectName"): + archives.append(str(production_output)) + + directory = args.directory.resolve() + (directory / "mods").mkdir(parents=True, exist_ok=True) + shutil.copy2(args.probe, directory / "mods" / args.probe.name) + (directory / "eula.txt").write_text("eula=true\n", encoding="utf-8") + if not (directory / "server.properties").exists(): + (directory / "server.properties").write_text("server-ip=127.0.0.1\nserver-port=0\nonline-mode=false\nlevel-type=FLAT\nview-distance=3\n", encoding="utf-8") + + command = [str(args.java), "-Xmx3G", "-Dmodern.phase=" + args.phase, "-Dmodern.saved=true"] + client = "Client" in profile.stem + if client: + command += ["-Dmodern.client=true", "-Dmodern.lowFantasy="+str(args.phase=="reload").lower()] + command += shlex.split(strings.get("org.eclipse.jdt.launching.VM_ARGUMENTS", "")) + command += ["-cp", os.pathsep.join(dict.fromkeys(archives)), strings["org.eclipse.jdt.launching.MAIN_TYPE"]] + command += shlex.split(strings["org.eclipse.jdt.launching.PROGRAM_ARGUMENTS"]) + started = time.time_ns() + with (directory / (args.phase + "-console.log")).open("w", encoding="utf-8") as log: + process = subprocess.run(command, cwd=directory, env=environment, stdout=log, + stderr=subprocess.STDOUT, timeout=300) + + report = directory / (args.phase + ("-client.json" if client else "-modern.json")) + if (process.returncode or not report.exists() or report.stat().st_mtime_ns < started + or not json.loads(report.read_text())["passed"]): + raise SystemExit(f"Launch failed; inspect {directory / (args.phase + '-console.log')}") + print(report.read_text(), flush=True) + if client: + world_report=json.loads((directory/(args.phase+'-modern.json')).read_text()) + if not world_report['passed']:raise SystemExit(str(world_report['failures'])) + + +if __name__ == "__main__": + main() diff --git a/tools/validation/legacy111/run_packaged_client.py b/tools/validation/legacy111/run_packaged_client.py new file mode 100644 index 00000000..9d53e6b2 --- /dev/null +++ b/tools/validation/legacy111/run_packaged_client.py @@ -0,0 +1,170 @@ +"""Run installed release jars on the obfuscated Forge client, in a disposable game directory.""" +import argparse,hashlib,json,os,shutil,struct,subprocess,time,zipfile,zlib +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path +from urllib.request import urlopen +from run_runtime_checks import ROOT,dependency,FORGE + +def install_ore_host_pack(game): + # A solid test colour makes the host/ore separation easy to see in a screenshot. + pack=game/'resourcepacks/ore-host-probe' + target=pack/'assets/minecraft/textures/blocks/stone.png' + target.parent.mkdir(parents=True,exist_ok=True) + (pack/'pack.mcmeta').write_text(json.dumps({'pack':{'pack_format':3,'description':'Ore host probe'}}),encoding='utf-8') + def chunk(name,data): + return struct.pack('>I',len(data))+name+data+struct.pack('>I',zlib.crc32(name+data)) + target.write_bytes(b'\x89PNG\r\n\x1a\n'+chunk(b'IHDR',struct.pack('>IIBBBBB',16,16,8,2,0,0,0)) + +chunk(b'IDAT',zlib.compress((b'\0'+bytes((57,180,142))*16)*16))+chunk(b'IEND',b'')) + +def configure_client(game,host_pack): + options=game/'options.txt' + lines=options.read_text(encoding='utf-8').splitlines() if options.is_file() else [] + # Do not let losing window focus pause the disposable world during screenshots. + if host_pack:install_ore_host_pack(game) + packs=['ore-host-probe'] if host_pack else [] + settings=['pauseOnLostFocus:false','resourcePacks:'+json.dumps(packs),'incompatibleResourcePacks:[]'] + keys=tuple(line.split(':',1)[0]+':' for line in settings) + options.write_text('\n'.join([line for line in lines if not line.startswith(keys)]+settings)+'\n',encoding='utf-8') + +def prepare_assets(output,cache,metadata): + """Use this version's verified index without overwriting a shared Gradle index.""" + assets=output/'assets' + index=assets/'indexes'/(metadata['id']+'.json') + index.parent.mkdir(parents=True,exist_ok=True) + if not index.is_file(): + with urlopen(metadata['url'],timeout=60) as response:index.write_bytes(response.read()) + assert hashlib.sha1(index.read_bytes()).hexdigest()==metadata['sha1'],'Minecraft asset index checksum mismatch' + objects=json.loads(index.read_text())['objects'] + def stage(spec): + sha=spec['hash'];relative=Path('objects')/sha[:2]/sha + target=assets/relative + if not target.is_file(): + target.parent.mkdir(parents=True,exist_ok=True) + cached=cache/'caches/minecraft/assets'/relative + if cached.is_file() and hashlib.sha1(cached.read_bytes()).hexdigest()==sha: + shutil.copy2(cached,target) + else: + with urlopen(f'https://resources.download.minecraft.net/{sha[:2]}/{sha}',timeout=60) as response: + target.write_bytes(response.read()) + assert target.stat().st_size==spec['size'] and hashlib.sha1(target.read_bytes()).hexdigest()==sha,sha + # Multiple names may share one content-addressed object. + unique={spec['hash']:spec for spec in objects.values()} + with ThreadPoolExecutor(max_workers=8) as pool:list(pool.map(stage,unique.values())) + print('MODERN_METALS_CLIENT_ASSETS_VERIFIED',metadata['id'],len(unique),flush=True) + return assets + +def windows_allowed(library): + allowed='rules' not in library + for rule in library.get('rules',[]): + if not rule.get('os') or rule['os'].get('name')=='windows': + allowed=rule['action']=='allow' + return allowed + +def prepare_natives(output,cache,libraries): + # An Eclipse launch is not required to populate a shared natives directory. + natives=output/'natives' + natives.mkdir(exist_ok=True) + for library in libraries: + classifier=library.get('natives',{}).get('windows') + if not classifier or not windows_allowed(library):continue + spec=library['downloads']['classifiers'][classifier.replace('${arch}','64')] + group,artifact,version=library['name'].split(':') + name=Path(spec['path']).name + archive=output/'engine'/name + if not archive.is_file(): + cached=next((cache/'caches/modules-2/files-2.1'/group/artifact/version).glob(f'*/{name}'),None) + if cached and hashlib.sha1(cached.read_bytes()).hexdigest()==spec['sha1']: + shutil.copy2(cached,archive) + else: + with urlopen(spec['url'],timeout=60) as response:archive.write_bytes(response.read()) + assert hashlib.sha1(archive.read_bytes()).hexdigest()==spec['sha1'],name + with zipfile.ZipFile(archive) as jar: + for member in jar.namelist(): + if Path(member).name==member and member.endswith('.dll'): + (natives/member).write_bytes(jar.read(member)) + assert (natives/'lwjgl64.dll').is_file() + return natives + +def main(): + parser=argparse.ArgumentParser(description=__doc__) + for name in ('java','cache','installer','template','output'):parser.add_argument('--'+name,type=Path,required=True) + parser.add_argument('--phase',choices=['first','reload'],default='first') + parser.add_argument('--ore-host-pack',action='store_true',help='Replace vanilla stone with a test texture in this disposable client.') + parser.add_argument('--language',choices=['de_DE','en_US','ru_RU','zh_CN']) + parser.add_argument('--remote-port',type=int,help='Connect to a disposable server on loopback.') + parser.add_argument('--expected-disconnect',help='Expected text in a pre-world disconnect.') + parser.add_argument('--low-fantasy',action='store_true',help='Start the remote client in Low Fantasy.') + parser.add_argument('--mod-jar',type=Path,default=ROOT/'build/libs/ModernMetals-2.5.1.111021.jar') + parser.add_argument('--mirror', type=Path) + parser.add_argument('--probe-jar', type=Path, default=ROOT/'build/test-mods/modernmetals-probe-packaged.jar') + args=parser.parse_args() + if args.phase=='first':args.output.mkdir(parents=True,exist_ok=False) + assert hashlib.sha256(args.installer.read_bytes()).hexdigest().upper()=='00AEA3DDF1E78BBB4D5D05E8C22FB3E849DE09B47D828E57DAD373A16BB8A26F' + with zipfile.ZipFile(args.installer) as jar:forge=json.loads(jar.read('install_profile.json'))['versionInfo'] + vanilla_root=args.cache/'caches/minecraftforge/forgegradle/mavenizer/caches/minecraft_tasks/1.11.2' + vanilla=json.loads((vanilla_root/'version.json').read_text()) + assert hashlib.sha1((vanilla_root/'client.jar').read_bytes()).hexdigest()==vanilla['downloads']['client']['sha1'] + libraries={lib['name']:lib for lib in vanilla['libraries']} + libraries.update({lib['name']:lib for lib in forge['libraries']}) + classpath=[] + for coordinate,lib in libraries.items(): + if lib.get('clientreq') is False or not windows_allowed(lib):continue + group,artifact,version,*classifier=coordinate.split(':') + if group=='net.minecraftforge' and artifact=='forge': + # The server distribution's manifest names its neighboring server jar. + # Isolate the unchanged universal jar so it cannot shadow client classes. + engine=args.output/'engine';engine.mkdir(exist_ok=True) + shutil.copy2(args.template/FORGE,engine/FORGE) + classpath.append(engine/FORGE);continue + if 'natives' in lib and not lib.get('downloads',{}).get('artifact'):continue + filename=artifact+'-'+version+('-'+classifier[0] if classifier else '')+'.jar' + paths=list((args.cache/'caches/modules-2/files-2.1'/group/artifact/version).glob('*/'+filename)) + if not paths: + path=args.template/'libraries'/group.replace('.','/')/artifact/version/filename + if path.is_file():paths=[path] + if not paths:raise FileNotFoundError(coordinate) + classpath.append(paths[0]) + classpath.append(vanilla_root/'client.jar') + if args.phase=='first': + (args.output/'mods').mkdir() + specs=json.loads((ROOT/'gradle/dependencies.json').read_text()) + jars=[dependency(s, args.mirror) for s in specs if s.get('runtime')] + jars+=[args.mod_jar,args.probe_jar] + for jar in jars:shutil.copy2(jar,args.output/'mods'/jar.name) + natives=prepare_natives(args.output,args.cache,vanilla['libraries']) + assets=prepare_assets(args.output,args.cache,vanilla['assetIndex']) + configure_client(args.output,args.ore_host_pack) + if args.language: + options=args.output/'options.txt' + lines=[line for line in options.read_text(encoding='utf-8').splitlines() if not line.startswith('lang:')] + options.write_text('\n'.join(lines+[f'lang:{args.language.lower()}'])+'\n',encoding='utf-8') + if args.remote_port and args.low_fantasy: + config=args.output/'config/ModernMetals.cfg' + config.parent.mkdir(exist_ok=True) + config.write_text('general {\n S:contentMode=low_fantasy\n}\n',encoding='utf-8') + remote=[] + if args.remote_port: + remote=[f'-Dmodern.remoteServer=127.0.0.1:{args.remote_port}', + f'-Dmodern.expectedDisconnect={args.expected_disconnect or ""}'] + command=[str(args.java),'-Xmx3G',f'-Djava.library.path={natives}',f'-Dmodern.phase={args.phase}', + '-Dmodern.saved=true','-Dmodern.client=true','-Dmodern.lowFantasy='+str(args.phase=='reload').lower(), + '-Dmodern.oreHostPack='+str(args.ore_host_pack).lower(), + '-Dmodern.language='+(args.language or 'en_US').lower(), + *remote, + '-cp',os.pathsep.join(map(str,classpath)),forge['mainClass'], + '--username','ModernCheck','--uuid','00000000000000000000000000000001','--accessToken','0', + '--version',forge['id'],'--gameDir',str(args.output),'--assetsDir',str(assets),'--assetIndex',vanilla['assetIndex']['id'], + '--userType','legacy','--tweakClass','net.minecraftforge.fml.common.launcher.FMLTweaker','--versionType','Forge'] + started=time.time_ns() + with (args.output/(args.phase+'-console.log')).open('wb') as log: + result=subprocess.run(command,cwd=args.output,stdout=log,stderr=subprocess.STDOUT,timeout=300) + for suffix in (('client',) if args.remote_port else ('client','modern')): + report=args.output/(args.phase+'-'+suffix+'.json') + assert report.is_file() and report.stat().st_mtime_ns>started,report + assert json.loads(report.read_text())['passed'],report + assert result.returncode==0 + console=(args.output/(args.phase+'-console.log')).read_text(errors='replace') + assert "Can't find the resource index file" not in console,'Minecraft assets were not loaded' + print('MODERN_METALS_PACKAGED_CLIENT_PASS',args.phase,flush=True) + +if __name__=='__main__':main() diff --git a/tools/validation/legacy111/run_packaged_suite.py b/tools/validation/legacy111/run_packaged_suite.py new file mode 100644 index 00000000..4e87ebf2 --- /dev/null +++ b/tools/validation/legacy111/run_packaged_suite.py @@ -0,0 +1,192 @@ +"""Qualify native generation, migrations, switches and saved worlds in disposable servers.""" +import argparse +import copy +import json +from pathlib import Path +from run_runtime_checks import ROOT, dependency, digest, prepare, run, write_json + +RULE='modernmetals:legacy/' +SELECTOR='orespawn:all_except_nether_end' +FIELDS=['Aluminum','AluminumBrass','Beryllium','Boron','Cadmium','Chromium','GalvanizedSteel', + 'Iridium','Magnesium','Manganese','Nichrome','Osmium','Plutonium','Rutile','StainlessSteel', + 'Tantalum','Thorium','Titanium','Tungsten','Uranium','Zirconium'] + +def fingerprint(directory): + return {str(p.relative_to(directory)):digest(p) for p in directory.rglob('*') if p.is_file()} + +def main(): + parser=argparse.ArgumentParser(description=__doc__) + for name in ('java','template','output'):parser.add_argument('--'+name,type=Path,required=True) + parser.add_argument('--mirror',type=Path) + args=parser.parse_args() + args.output.mkdir(parents=True,exist_ok=False) + specs=json.loads((ROOT/'gradle/dependencies.json').read_text()) + jars=[dependency(s,args.mirror) for s in specs if s.get('runtime')] + jars += [ROOT/'build/libs/ModernMetals-2.5.1.111021.jar',ROOT/'build/test-mods/modernmetals-probe-packaged.jar'] + native=json.loads((ROOT/'build/reports/native-provider.json').read_text()) + stock=json.loads((ROOT/'tools/validation/legacy111/fixtures/orespawn3-modernmetals.json').read_text()) + + def setup(name,world=None): + target=args.output/name + prepare(target,args.template,jars,world) + return target + def check(target,expected=None,properties=None,sample=False,saved=False): + properties=dict(properties or {}) + if expected: + write_json(target/'expected.json',expected) + properties['modern.expected']=str((target/'expected.json').resolve()) + if saved:properties['modern.saved']='true' + first=run(args.java,target,'first',{**properties,**({'modern.sample':'true'} if sample else {})}) + run(args.java,target,'reload',properties) + assert not first['profile'].get('terrain_dimensions'), 'Ore-only mods enabled terrain replacement' + return first + + fresh=setup('native-fresh') + (fresh/'config').mkdir(exist_ok=True) + (fresh/'config/BaseMetals.cfg').write_text('general {\n S:contentMode=low_fantasy\n}\n',encoding='utf-8') + report=check(fresh,{'ores':native['ores']},properties={'modern.lowFantasy':'false','modern.trades':'true'},sample=True,saved=True) + base_profile=report['profile'] + assert len([key for key in base_profile['ores'] if key.startswith('modernmetals:')])==17 + aluminum_group = base_profile['ore_material_groups']['orespawn:aluminum'] + assert {'oreAluminum', 'oreAluminium', 'oreBauxite'} <= set(aluminum_group['ore_dictionary_entries']) + aluminum_policy = base_profile['ore_source_policies'][f'orespawn:aluminum|{SELECTOR}'] + aluminum_source = RULE + 'aluminum_ore' + assert aluminum_policy['placement_sources']['orespawn:standard'] == aluminum_source + assert any(candidate['source_id'] == aluminum_source and candidate['loaded'] and candidate['material_declared'] + for candidate in aluminum_policy['candidates']) + before=fingerprint(fresh/'world') + + disabled=setup('materials-disabled') + config=disabled/'config/ModernMetals.cfg';config.parent.mkdir(parents=True,exist_ok=True) + config.write_text('metals {\n'+''.join(f' B:Enable{name}=false\n' for name in ('Boron','Beryllium','Thorium','Titanium'))+'}\n',encoding='utf-8') + expected={k:v for k,v in native['ores'].items() if not any(k.endswith('/'+x+'_ore') for x in ('boron','beryllium','thorium','titanium'))} + result=check(disabled,{'ores':expected},{'modern.disabled':'boron,beryllium,thorium,titanium'}) + assert len([k for k in result['profile']['ores'] if k.startswith('modernmetals:')])==13 + + all_disabled=setup('all-materials-disabled') + config=all_disabled/'config/ModernMetals.cfg';config.parent.mkdir(parents=True,exist_ok=True) + config.write_text('metals {\n'+''.join(f' B:Enable{name}=false\n' for name in FIELDS)+'}\n',encoding='utf-8') + check(all_disabled,properties={'modern.disabled':','.join(n.lower() for n in FIELDS),'modern.allDisabled':'true'}) + + titanium=setup('titanium-enabled') + override=copy.deepcopy(native);override['ores'][RULE+'titanium_ore']['enabled']=True + write_json(titanium/'config/modernmetals-orespawn.json',override) + check(titanium,{'ores':override['ores']},{'modern.titanium':'true'},sample=True,saved=True) + + no_ores=setup('ores-disabled') + override=copy.deepcopy(native) + for ore in override['ores'].values():ore['enabled']=False + write_json(no_ores/'config/modernmetals-orespawn.json',override) + result=check(no_ores,{'ores':override['ores']},{'modern.noOres':'true'},sample=True) + assert all(not counts for counts in result['oreSamples'].values()) + + custom=copy.deepcopy(stock) + custom['spawns']['aluminum_ore']['parameters'].update(size=5,variation=0,frequency=2.75,minHeight=10,maxHeight=50) + custom['spawns']['thorium_ore']['enabled']=False + custom['spawns']['boron_ore']['dimensions']=[0] + target=setup('os3-import',fresh/'world') + # An existing OS4 profile is authoritative; remove only this disposable copy's snapshot + # to exercise an old world which has not yet been imported by OreSpawn 4. + snapshot=target/'world/serverconfig/orespawn-worldgen.json' + assert snapshot.resolve().is_relative_to(args.output.resolve()) + snapshot.unlink() + legacy=target/'config/orespawn3/modernmetals.json';write_json(legacy,custom) + legacy_hash=digest(legacy) + expected={'ores':{RULE+'aluminum_ore':{'dimension_selectors':{SELECTOR:{'frequency':2.75,'min_y':10,'max_y':49,'quantity':5}}}, + RULE+'thorium_ore':{'enabled':False},RULE+'boron_ore':{'dimensions':{'minecraft:overworld':{'enabled':True}}}}} + check(target,expected,saved=True) + assert digest(legacy)==legacy_hash + override_path=target/'config/modernmetals-orespawn.json' + assert override_path.is_file() + stable={p:digest(p) for p in (legacy,override_path,target/'world/serverconfig/orespawn-worldgen.json')} + run(args.java,target,'later',{'modern.expected':str((target/'expected.json').resolve()),'modern.saved':'true'}) + assert all(digest(p)==value for p,value in stable.items()) + + os1=setup('os1-import') + legacy=os1/'config/orespawn/modernmetals.json' + write_json(legacy,{'dimensions':[{'dimension':'0','ores':[{'blockID':'modernmetals:aluminum_ore','size':4,'variation':0,'frequency':1.5,'minHeight':8,'maxHeight':40}]}]}) + legacy_hash=digest(legacy) + result=check(os1) + imported=[v for v in result['profile']['ores'].values() if v.get('block')=='modernmetals:aluminum_ore'] + frequencies = [ + placement.get('frequency') + for ore in imported + for section in ('dimensions', 'dimension_selectors') + for placement in ore.get(section, {}).values() + ] + assert len(imported) == 1 and 1.5 in frequencies, ( + f'Expected one OS1 aluminum rule at frequency 1.5, found {len(imported)} rules with {frequencies}' + ) + assert digest(legacy)==legacy_hash + + existing=setup('existing-os4',fresh/'world') + profile_path=existing/'world/serverconfig/orespawn-worldgen.json' + profile=json.loads(profile_path.read_text()) + profile['ores'][RULE+'aluminum_ore']['dimension_selectors'][SELECTOR]['frequency']=6.25 + write_json(profile_path,profile) + override=copy.deepcopy(native) + override['ores'][RULE+'aluminum_ore']['dimension_selectors'][SELECTOR]['frequency']=9.0 + write_json(existing/'config/modernmetals-orespawn.json',override) + write_json(existing/'config/orespawn3/modernmetals.json',custom) + hashes={p:digest(p) for p in (profile_path,existing/'config/modernmetals-orespawn.json')} + check(existing,{'ores':{RULE+'aluminum_ore':{'dimension_selectors':{SELECTOR:{'frequency':6.25}}}}},saved=True) + assert all(digest(p)==value for p,value in hashes.items()) + future=setup('existing-override-new-world') + write_json(future/'config/modernmetals-orespawn.json',override) + write_json(future/'config/orespawn3/modernmetals.json',custom) + check(future,{'ores':override['ores']}) + assert fingerprint(fresh/'world')==before,'A source fixture world changed' + + optional=setup('optional-integrations') + for spec in specs: + if spec['id'] in ('tconstruct','mantle','mekanism','mcmultipart','ic2'): + import shutil + jar=dependency(spec,args.mirror);shutil.copy2(jar,optional/'mods'/jar.name) + check(optional,saved=True) + low=setup('low-fantasy') + (low/'config').mkdir(exist_ok=True) + (low/'config/ModernMetals.cfg').write_text('general {\n S:contentMode=low_fantasy\n}\n',encoding='utf-8') + (low/'config/BaseMetals.cfg').write_text('general {\n S:contentMode=high_fantasy\n}\n',encoding='utf-8') + check(low,properties={'modern.lowFantasy':'true','modern.trades':'true'},saved=True) + for mode in ('high','low'): + trade_off=setup(f'{mode}-fantasy-trades-disabled') + config=trade_off/'config/ModernMetals.cfg' + config.parent.mkdir() + config.write_text(f'general {{\n S:contentMode={mode}_fantasy\n B:enableVillagerTrades=false\n}}\n',encoding='utf-8') + run(args.java,trade_off,'first',{'modern.lowFantasy':str(mode=='low').lower(),'modern.trades':'false'}) + switched=setup('high-to-low-existing-world',fresh/'world') + (switched/'config').mkdir(exist_ok=True) + (switched/'config/ModernMetals.cfg').write_text('general {\n S:contentMode=low_fantasy\n}\n',encoding='utf-8') + run(args.java,switched,'reload',{'modern.lowFantasy':'true','modern.saved':'true'}) + custom_dimension=setup('ordinary-custom-dimension') + check(custom_dimension,properties={'modern.customDimension':'true'},sample=True) + low_optional=setup('low-fantasy-tinkers') + (low_optional/'config').mkdir(exist_ok=True) + (low_optional/'config/ModernMetals.cfg').write_text('general {\n S:contentMode=low_fantasy\n}\n',encoding='utf-8') + for spec in specs: + if spec['id'] in ('tconstruct','mantle'): + jar=dependency(spec,args.mirror);shutil.copy2(jar,low_optional/'mods'/jar.name) + check(low_optional,properties={'modern.lowFantasy':'true'},saved=True) + disabled_integration=setup('tinkers-disabled') + (disabled_integration/'config').mkdir(exist_ok=True) + (disabled_integration/'config/ModernMetals.cfg').write_text('general {\n S:contentMode=low_fantasy\n}\n',encoding='utf-8') + (disabled_integration/'config/BaseMetals.cfg').write_text('"mod integration" {\n B:tinkers_construct_integration=false\n}\n',encoding='utf-8') + for spec in specs: + if spec['id'] in ('tconstruct','mantle'): + jar=dependency(spec,args.mirror);shutil.copy2(jar,disabled_integration/'mods'/jar.name) + check(disabled_integration,properties={'modern.lowFantasy':'true','modern.disabledIntegrationTest':'true'},saved=True) + disabled_materials_optional=setup('materials-disabled-with-tinkers') + (disabled_materials_optional/'config').mkdir(exist_ok=True) + (disabled_materials_optional/'config/ModernMetals.cfg').write_text('metals {\n'+''.join(f' B:Enable{name}=false\n' for name in ('Boron','Beryllium','Thorium','Titanium'))+'}\n',encoding='utf-8') + for spec in specs: + if spec['id'] in ('tconstruct','mantle'): + jar=dependency(spec,args.mirror);shutil.copy2(jar,disabled_materials_optional/'mods'/jar.name) + check(disabled_materials_optional,properties={'modern.disabled':'boron,beryllium,thorium,titanium'}) + from dependency_requirements import verify + verify(args.java,args.template,args.output,dependency(next(s for s in specs if s['id']=='basemetals'),args.mirror), + dependency(next(s for s in specs if s['id']=='orespawn'),args.mirror),ROOT/'build/libs/ModernMetals-2.5.1.111021.jar') + assert fingerprint(fresh/'world')==before,'A source fixture world changed' + write_json(args.output/'qualification.json',{'passed':True,'scenarios':18,'source_world_unchanged':True}) + print('MODERN_METALS_PACKAGED_SUITE_PASS',flush=True) + +if __name__=='__main__':main() diff --git a/tools/validation/legacy111/run_runtime_checks.py b/tools/validation/legacy111/run_runtime_checks.py new file mode 100644 index 00000000..ddf01336 --- /dev/null +++ b/tools/validation/legacy111/run_runtime_checks.py @@ -0,0 +1,94 @@ +"""Run the installed Modern Metals jar in disposable packaged Forge servers.""" +import argparse +import hashlib +import json +import re +import shutil +import subprocess +import time +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[3] +FORGE = 'forge-1.11.2-13.20.1.2588-universal.jar' + +def write_json(path, value): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(value, indent=2)+'\n', encoding='utf-8') + +def digest(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + +def dependency(spec, mirror=None): + group, artifact, version, *classifier = spec['coordinate'].split(':') + filename = f'{artifact}-{version}'+('-'+classifier[0] if classifier else '')+'.jar' + path = (mirror or ROOT/'.gradle/legacy-dependencies')/group.replace('.', '/')/artifact/version/filename + if digest(path).upper() != spec['sha256']: + raise AssertionError(f'Dependency checksum mismatch: {spec["id"]}') + return path + +def prepare(directory, template, jars, world=None): + directory.mkdir(parents=True, exist_ok=False) + for name in (FORGE, 'minecraft_server.1.11.2.jar'): + shutil.copy2(template/name, directory/name) + shutil.copytree(template/'libraries', directory/'libraries') + (directory/'mods').mkdir() + for jar in jars: + shutil.copy2(jar, directory/'mods'/jar.name) + (directory/'eula.txt').write_text('eula=true\n', encoding='utf-8') + (directory/'server.properties').write_text( + 'level-name=world\nserver-ip=127.0.0.1\nserver-port=0\nonline-mode=false\n' + 'level-seed=8675309\nview-distance=2\nmax-tick-time=-1\nspawn-protection=0\n' + 'generate-structures=false\nlevel-type=DEFAULT\n', encoding='utf-8') + if world: + if directory.resolve().is_relative_to(world.resolve()): + raise ValueError('Disposable target is inside the source world') + shutil.copytree(world, directory/'world') + +def run(java, directory, phase, properties=None, expect_failure=False): + properties = {'modern.phase':phase, **(properties or {})} + command = [str(java), '-Xms256M', '-Xmx2G']+[f'-D{k}={v}' for k,v in properties.items()] + command += ['-jar', FORGE, 'nogui'] + log = directory/f'{phase}-console.log' + started = time.time_ns() + with log.open('wb') as stream: + result = subprocess.run(command, cwd=directory, stdout=stream, stderr=subprocess.STDOUT, timeout=240) + report_path = directory/f'{phase}-modern.json' + if not report_path.is_file() or report_path.stat().st_mtime_ns < started: + raise AssertionError(f'No fresh runtime report: {log}') + report = json.loads(report_path.read_text(encoding='utf-8')) + if expect_failure: + if report['passed'] or not any('Missing material: boron' == x for x in report['failures']): + raise AssertionError(f'Missing-material defect was not reproduced: {report_path}') + elif result.returncode or not report['passed']: + raise AssertionError(f'Packaged checks failed: {report_path}; {log}') + text = log.read_text(errors='replace') + if not expect_failure and re.search(r"Couldn't load[^\r\n]*for modernmetals", text): + raise AssertionError(f'An optional integration failed: {log}') + print(directory.name, phase, 'expected failure' if expect_failure else 'PASS', flush=True) + return report + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + for name in ('java','template','output'): + parser.add_argument('--'+name, type=Path, required=True) + parser.add_argument('--baseline', action='store_true') + parser.add_argument('--optional', action='append', default=[]) + parser.add_argument('--mirror', type=Path) + args = parser.parse_args() + specs = json.loads((ROOT/'gradle/dependencies.json').read_text()) + selected = set(args.optional) + if 'tconstruct' in selected: + selected.add('mantle') + if 'mekanism' in selected: + selected.add('mcmultipart') + jars = [dependency(s,args.mirror) for s in specs if s.get('runtime') or s['id'] in selected] + jars += [ROOT/'build/libs/ModernMetals-2.5.1.111021.jar', ROOT/'build/test-mods/modernmetals-probe-packaged.jar'] + prepare(args.output, args.template, jars) + if args.baseline: + run(args.java,args.output,'first',{'modern.baseline':'true'},expect_failure=True) + else: + run(args.java,args.output,'first',{'modern.saved':'true'}) + run(args.java,args.output,'reload',{'modern.saved':'true'}) + +if __name__ == '__main__': + main() diff --git a/tools/validation/legacy111/runtime-probe/review/modern/ClientChecks.java b/tools/validation/legacy111/runtime-probe/review/modern/ClientChecks.java new file mode 100644 index 00000000..227a5cea --- /dev/null +++ b/tools/validation/legacy111/runtime-probe/review/modern/ClientChecks.java @@ -0,0 +1,280 @@ +package review.modern; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.mcmoddev.lib.data.Names; +import com.mcmoddev.lib.init.Materials; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.concurrent.Future; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiDisconnected; +import net.minecraft.client.gui.GuiMainMenu; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.multiplayer.GuiConnecting; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.client.renderer.block.model.IBakedModel; +import net.minecraft.item.ItemStack; +import net.minecraft.world.GameType; +import net.minecraft.world.WorldSettings; +import net.minecraft.world.WorldType; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.client.FMLClientHandler; +import net.minecraftforge.fml.client.IModGuiFactory; +import net.minecraftforge.fml.client.config.GuiConfig; +import net.minecraftforge.fml.client.config.GuiMessageDialog; +import net.minecraftforge.fml.client.config.GuiConfigEntries.BooleanEntry; +import net.minecraftforge.fml.client.config.GuiConfigEntries.CycleValueEntry; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import zone.moddev.mc.modernmetals.util.Config; +import zone.moddev.mc.modernmetals.content.ContentPolicy; + +/** Automated graphical acceptance probes, isolated from all release archives. */ +@Mod(modid="modernmetalsclientprobe",name="Modern Metals client checks",version="1", + acceptableRemoteVersions="*",dependencies="required-after:modernmetalsprobe") +public final class ClientChecks { + @Mod.EventHandler public void init(final FMLInitializationEvent event) { + if (!Boolean.getBoolean("modern.client")) return; + final String server = System.getProperty("modern.remoteServer"); + MinecraftForge.EVENT_BUS.register(server == null ? new Checks() : new RemoteCheck(server)); + } + private static void require(final boolean pass, final String detail) { + if (!pass) throw new AssertionError(detail); + } + + public static final class RemoteCheck { + private final String address; + private final long deadline = System.currentTimeMillis() + 90000; + private boolean connecting; + private boolean finished; + + RemoteCheck(final String address) { this.address = address; } + + @SubscribeEvent public void tick(final TickEvent.ClientTickEvent event) throws Exception { + if (event.phase != TickEvent.Phase.END || finished) return; + final Minecraft mc = Minecraft.getMinecraft(); + if (!connecting && mc.currentScreen instanceof GuiMainMenu) { + mc.displayGuiScreen(new GuiConnecting(mc.currentScreen, mc, new ServerData("Disposable Forge", address, false))); + connecting = true; + return; + } + final String expectedDisconnect = System.getProperty("modern.expectedDisconnect", ""); + final boolean expectRejection = !expectedDisconnect.isEmpty(); + if (mc.world != null && mc.player != null) { + finish(mc, !expectRejection, "Dedicated server world entered"); + } else if (connecting && mc.currentScreen instanceof GuiDisconnected) { + final String reason = disconnectReason((GuiDisconnected) mc.currentScreen); + finish(mc, expectRejection && reason.contains(expectedDisconnect), reason); + } else if (System.currentTimeMillis() >= deadline) { + finish(mc, false, "Dedicated server login timed out"); + } + } + + private static String disconnectReason(final GuiDisconnected screen) throws Exception { + for (final java.lang.reflect.Field field : GuiDisconnected.class.getDeclaredFields()) { + if (field.getType() == net.minecraft.util.text.ITextComponent.class) { + field.setAccessible(true); + return ((net.minecraft.util.text.ITextComponent) field.get(screen)).getUnformattedText(); + } + } + throw new AssertionError("Disconnect reason field missing"); + } + + private void finish(final Minecraft mc, final boolean pass, final String detail) throws Exception { + Checks.screenshot(mc, "dedicated-login.png"); + Checks.report(pass, detail); + finished = true; + mc.shutdown(); + } + } + public static final class Checks { + // Exercise the actual directory entry and the same action as its cog button. + private static GuiScreen openOreSpawnConfig(final Minecraft mc, final GuiScreen parent) + throws ReflectiveOperationException { + final Class directoryType = Class.forName("zone.moddev.mc.orespawn.client.OreSpawnModsScreen"); + final Constructor constructor = directoryType.getDeclaredConstructor(GuiScreen.class); + constructor.setAccessible(true); + final GuiScreen directory = (GuiScreen) constructor.newInstance(parent); + final Field entriesField = directoryType.getDeclaredField("entries"); + entriesField.setAccessible(true); + for (final Object entry : (Iterable) entriesField.get(directory)) { + final Field modId = entry.getClass().getDeclaredField("modId"); + modId.setAccessible(true); + if ("modernmetals".equals(modId.get(entry))) { + mc.displayGuiScreen(directory); + final Method open = directoryType.getDeclaredMethod("open", entry.getClass()); + open.setAccessible(true); + open.invoke(directory, entry); + return directory; + } + } + throw new AssertionError("Modern Metals is missing from the OreSpawn Mods directory"); + } + + private long started=System.currentTimeMillis(); + private int ticks; + private int state; + private GuiScreen parent; + private GuiScreen oreSpawnMods; + private GuiConfig screen; + private Future check; + private boolean finished; + @SubscribeEvent public void tick(final TickEvent.ClientTickEvent event) throws Exception { + if (event.phase!=TickEvent.Phase.END || finished) return; + final Minecraft mc=Minecraft.getMinecraft(); + try { + if (state==0 && mc.currentScreen instanceof GuiMainMenu && ++ticks>=20) { + parent=mc.currentScreen; + final IModGuiFactory factory=FMLClientHandler.instance().getGuiFactoryFor(Loader.instance().getIndexedModList().get("modernmetals")); + require(factory!=null && factory.hasConfigGui(),"Forge Mods config factory missing"); + screen=(GuiConfig)factory.createConfigGui(parent); + mc.displayGuiScreen(screen); state=1;ticks=0; + } else if(state==1 && ++ticks>=20) { + require(screen.parentScreen==parent && screen.configElements.size()==23,"Forge config parent or settings incorrect"); + require(screen.allRequireMcRestart,"Registry switches must require a restart"); + require(!screen.title.startsWith("config."),"Untranslated config title"); + final String locale = System.getProperty("modern.language", "en_us"); + require(locale.equals(mc.gameSettings.language), "Minecraft did not load language " + locale); + if (!"en_us".equals(locale)) { + require(!"Fantasy Level".equals(net.minecraft.client.resources.I18n.format("config.modernmetals.content_mode")), + "Fantasy selector fell back to English in " + locale); + require(!"High Fantasy".equals(net.minecraft.client.resources.I18n.format("high_fantasy")), + "Fantasy value fell back to English in " + locale); + } + for(final net.minecraftforge.fml.client.config.IConfigElement element:screen.configElements) + require(net.minecraft.client.resources.I18n.hasKey(element.getLanguageKey()),"Untranslated material label: "+element.getName()); + final String startupMode=Boolean.getBoolean("modern.lowFantasy")?"low_fantasy":"high_fantasy"; + require(mode(screen).getCurrentValue().equals(startupMode),"Wrong startup fantasy selection"); + require(ContentPolicy.active().lowFantasy()==Boolean.getBoolean("modern.lowFantasy"),"Wrong active fantasy mode"); + screenshot(mc,"forge-mods-config.png"); + mode(screen).valueButtonPressed(0); + final BooleanEntry entry=entry(screen,"EnableBoron"); + entry.valueButtonPressed(0); + entry(screen,"enableVillagerTrades").valueButtonPressed(0); + invoke(screen,"keyTyped",new Class[]{char.class,int.class},'\0',1); + require(mc.currentScreen==parent,"Escape did not return to Forge parent"); + require(Config.getConfiguration().get("Metals","EnableBoron",true).getBoolean(),"Escape saved a discarded switch"); + require(Config.getConfiguration().get("General","enableVillagerTrades",true).getBoolean(),"Escape saved discarded trade settings"); + require(Config.getConfiguration().getCategory("General").get("contentMode").getString().equals(startupMode),"Escape saved a discarded fantasy selection"); + oreSpawnMods = openOreSpawnConfig(mc, parent); + require(mc.currentScreen instanceof GuiConfig, "OreSpawn cog did not open the Modern Metals config"); + screen = (GuiConfig) mc.currentScreen; + state=2;ticks=0; + } else if(state==2 && ++ticks>=20) { + require(screen.parentScreen==oreSpawnMods && screen.configElements.size()==23,"OreSpawn config parent or options incorrect"); + screenshot(mc,"orespawn-child-mods-config.png"); + mode(screen).valueButtonPressed(0); + screen.entryList.scrollBy(10000); + require(screen.entryList.getAmountScrolled()>0,"Material list cannot scroll"); + entry(screen,"EnableBoron").valueButtonPressed(0); + entry(screen,"enableVillagerTrades").valueButtonPressed(0); + invoke(screen,"actionPerformed",new Class[]{GuiButton.class},new GuiButton(2000,0,0,"Done")); + require(mc.currentScreen instanceof GuiMessageDialog, "Missing Forge restart confirmation"); + invoke(mc.currentScreen, "actionPerformed", new Class[]{GuiButton.class}, new GuiButton(0, 0, 0, "OK")); + require(mc.currentScreen == oreSpawnMods, "Restart confirmation did not return to OreSpawn Mods"); + require(!Config.getConfiguration().get("Metals","EnableBoron",true).getBoolean(),"Done did not save the switch"); + require(Config.Options.enableBoron && Materials.hasMaterial("boron"),"GUI rebuilt live registries"); + require(!Config.getConfiguration().get("General","enableVillagerTrades",true).getBoolean() + && Config.Options.enableVillagerTrades,"Trade settings did not save for the next restart"); + final String pendingMode=Boolean.getBoolean("modern.lowFantasy")?"high_fantasy":"low_fantasy"; + require(Config.getConfiguration().getCategory("General").get("contentMode").getString().equals(pendingMode),"Done did not save the fantasy selection"); + require(ContentPolicy.active().lowFantasy()==Boolean.getBoolean("modern.lowFantasy"),"GUI applied fantasy recipes without a restart"); + require(new String(Files.readAllBytes(Paths.get("config/ModernMetals.cfg")),StandardCharsets.UTF_8).contains("B:EnableBoron=false"),"Done did not persist the file"); + require(new String(Files.readAllBytes(Paths.get("config/ModernMetals.cfg")),StandardCharsets.UTF_8).contains("S:contentMode="+pendingMode),"Done did not persist the fantasy setting"); + // Restore the fixture through the same save path before the reload test. + screen=new zone.moddev.mc.modernmetals.client.ModernMetalsConfigScreen(parent); + mc.displayGuiScreen(screen); + entry(screen,"EnableBoron").valueButtonPressed(0); + entry(screen,"enableVillagerTrades").valueButtonPressed(0); + invoke(screen,"actionPerformed",new Class[]{GuiButton.class},new GuiButton(2000,0,0,"Done")); + mc.displayGuiScreen(parent); + mc.launchIntegratedServer("modern-check","Modern Metals disposable checks",new WorldSettings(8675309L,GameType.CREATIVE,false,false,WorldType.DEFAULT)); + state=3; + } else if(state==3 && mc.world!=null && mc.player!=null && check==null) { + OreOverlayChecks.verify(mc); + for(final net.minecraft.block.Block block:net.minecraftforge.fml.common.registry.ForgeRegistries.BLOCKS.getValues()) { + if(!"modernmetals".equals(block.getRegistryName().getResourceDomain())) continue; + for(final net.minecraft.block.state.IBlockState variant:block.getBlockState().getValidStates()) + require(mc.getBlockRendererDispatcher().getBlockModelShapes().getModelForState(variant) + !=mc.getRenderItem().getItemModelMesher().getModelManager().getMissingModel(),"Missing baked block variant: "+variant); + } + for(final com.mcmoddev.lib.material.MMDMaterial material:Materials.getMaterialsByMod("modernmetals")) { + boolean tab=false; + for(final net.minecraft.creativetab.CreativeTabs value:material.getItem(Names.INGOT).getCreativeTabs()) + if(value!=null && value.getTabLabel().startsWith("modernmetals.")) tab=true; + require(tab,"Missing Modern Metals creative tab: "+material.getName()); + } + for(final String label:new String[]{"aluminum_brass_maker","galvanized_steel_maker","nichrome_maker","stainless_steel_maker","titanium_maker"}) { + require(net.minecraft.client.resources.I18n.hasKey("achievement."+label),"Missing achievement label: "+label); + require(net.minecraft.client.resources.I18n.hasKey("achievement."+label+".desc"),"Missing achievement description: "+label); + } + for(final com.mcmoddev.lib.material.MMDMaterial material:Materials.getMaterialsByMod("modernmetals")) { + final String name=material.getName(); + for(final Names form:new Names[]{Names.INGOT,Names.PLATE,Names.PICKAXE}) { + final ItemStack stack=Materials.getMaterialByName(name).hasItem(form) + ? Materials.getMaterialByName(name).getItemStack(form) + : Materials.getMaterialByName(name).getBlockItemStack(form); + require(stack!=null,"Missing model input: "+name+":"+form); + final IBakedModel model=mc.getRenderItem().getItemModelMesher().getItemModel(stack); + require(model!=mc.getRenderItem().getItemModelMesher().getModelManager().getMissingModel(),"Missing baked model: "+name+":"+form); + } + } + check=mc.getIntegratedServer().addScheduledTask(()->{ + try { + ModernMetalsProbe.run(mc.getIntegratedServer()); + OreOverlayChecks.placeDisplay(mc.getIntegratedServer()); + } + catch(Exception failure) {throw new RuntimeException(failure);} + }); + } else if(state==3 && check!=null && check.isDone()) { + check.get(); + mc.displayGuiScreen(null); + mc.gameSettings.hideGUI=true; + state=4; + ticks=0; + } else if(state==4 && ++ticks>=40) { + screenshot(mc,"ore-overlay-display.png"); + report(true,"Both config screens, fantasy restart, baked ore block/item overlays, stone host and integrated saved world"); + finished=true;mc.shutdown(); + } + if(System.currentTimeMillis()-started>240000) throw new AssertionError("Client check timed out, state="+state); + } catch(Throwable failure) {report(false,failure.toString());failure.printStackTrace();finished=true;mc.shutdown();} + } + private static BooleanEntry entry(final GuiConfig screen,final String name) { + for(int i=0;i[] types,final Object...args) throws Exception { + final Class owner = screen instanceof GuiMessageDialog ? GuiMessageDialog.class.getSuperclass() : GuiConfig.class; + Method method; + try {method=owner.getDeclaredMethod(name,types);} + catch(final NoSuchMethodException packaged) { + method=owner.getDeclaredMethod("keyTyped".equals(name)?"func_73869_a":"func_146284_a",types); + } + method.setAccessible(true);method.invoke(screen,args); + } + private static void screenshot(final Minecraft mc,final String name) { + net.minecraft.util.ScreenShotHelper.saveScreenshot(mc.mcDataDir,name,mc.displayWidth,mc.displayHeight,mc.getFramebuffer()); + } + private static void report(final boolean passed,final String detail) throws Exception { + final JsonObject report=new JsonObject();report.addProperty("passed",passed);report.addProperty("detail",detail); + Files.write(Paths.get(System.getProperty("modern.phase")+"-client.json"),(new GsonBuilder().setPrettyPrinting().create().toJson(report)+"\n").getBytes(StandardCharsets.UTF_8)); + System.out.println("MODERN_METALS_CLIENT_"+(passed?"PASS":"FAIL")+" "+detail); + } + } +} diff --git a/tools/validation/legacy111/runtime-probe/review/modern/ModernMetalsProbe.java b/tools/validation/legacy111/runtime-probe/review/modern/ModernMetalsProbe.java new file mode 100644 index 00000000..bb73b43e --- /dev/null +++ b/tools/validation/legacy111/runtime-probe/review/modern/ModernMetalsProbe.java @@ -0,0 +1,322 @@ +package review.modern; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.mcmoddev.lib.data.Names; +import com.mcmoddev.lib.init.Materials; +import com.mcmoddev.lib.material.MMDMaterial; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +import net.minecraft.inventory.Container; +import net.minecraft.inventory.InventoryCrafting; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.CraftingManager; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.MinecraftServer; +import net.minecraft.tileentity.TileEntityChest; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.WorldServer; +import net.minecraftforge.fluids.FluidRegistry; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.event.FMLServerStartedEvent; +import net.minecraftforge.fml.common.registry.ForgeRegistries; +import zone.moddev.mc.orespawn.api.OreSpawnApi; + +/** Inspects the installed jar, registered content, recipes, profile and saved data. */ +@Mod(modid = "modernmetalsprobe", name = "Modern Metals runtime checks", version = "1", + acceptableRemoteVersions = "*", dependencies = "required-after:modernmetals") +public final class ModernMetalsProbe { + private static final String[] MATERIALS = { "aluminum", "aluminumbrass", "beryllium", "boron", + "cadmium", "chromium", "galvanizedsteel", "iridium", "magnesium", "manganese", "nichrome", + "osmium", "plutonium", "rutile", "stainlesssteel", "tantalum", "thorium", "titanium", + "tungsten", "uranium", "zirconium" }; + + private static net.minecraft.world.DimensionType testDimension; + @Mod.EventHandler public void preInit(final net.minecraftforge.fml.common.event.FMLPreInitializationEvent event) { + if(Boolean.getBoolean("modern.disabledIntegrationTest")) OptionalIntegrationChecks.seedForeign(); + if(Boolean.getBoolean("modern.customDimension")) { + testDimension=net.minecraft.world.DimensionType.register("modernmetals_check","_modern_check",42,OrdinaryDimension.class,false); + net.minecraftforge.common.DimensionManager.registerDimension(42,testDimension); + } + } + public static final class OrdinaryDimension extends net.minecraft.world.WorldProviderSurface { + @Override public net.minecraft.world.DimensionType getDimensionType() {return testDimension;} + } + + @Mod.EventHandler + public void started(final FMLServerStartedEvent event) throws Exception { + if (System.getProperty("modern.phase") == null) return; + final MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance(); + if (server.isDedicatedServer()) { + run(server); + server.initiateShutdown(); + } + } + + public static void run(final MinecraftServer server) throws Exception { + final String phase = System.getProperty("modern.phase", "first"); + final JsonObject report = new JsonObject(); + final JsonArray failures = new JsonArray(); + final Set disabled = new HashSet<>(Arrays.asList(System.getProperty("modern.disabled", "").split(","))); + final JsonObject catalog = new JsonObject(); + check("2.5.1.111021".equals(Loader.instance().getIndexedModList().get("modernmetals").getVersion()), "mod version", failures); + for (final String name : MATERIALS) { + final MMDMaterial material = Materials.getMaterialByName(name); + if (disabled.contains(name)) { + check(material.isEmpty(), "Disabled material registered: " + name, failures); + continue; + } + check(!material.isEmpty(), "Missing material: " + name, failures); + if (material.isEmpty()) continue; + final JsonObject entry = new JsonObject(); + entry.addProperty("toolDurability", material.getToolDurability()); + entry.addProperty("fluid", material.getFluid() == null ? "" : material.getFluid().getName()); + for (final Names form : new Names[] {Names.INGOT, Names.NUGGET, Names.POWDER, Names.PICKAXE, Names.HELMET}) { + check(material.hasItem(form), "Missing " + name + ":" + form, failures); + } + check(material.hasBlock(Names.BLOCK), "Missing storage block: " + name, failures); + check(material.getFluid() != null && FluidRegistry.getFluid(material.getFluid().getName()) != null, + "Missing registered fluid: " + name, failures); + if(material.getFluid()!=null) { + check(material.getFluid()==FluidRegistry.getFluid(name),"Material retains an unregistered duplicate fluid: "+name,failures); + check(zone.moddev.mc.modernmetals.init.Fluids.getFluidByName(name)==material.getFluid() + && zone.moddev.mc.modernmetals.init.Fluids.getFluidRegistry().get(name)==material.getFluid() + && name.equals(zone.moddev.mc.modernmetals.init.Fluids.getNameOfFluid(material.getFluid())),"Legacy fluid query lost borrowed material: "+name,failures); + } + check(material.getFluidBlock() != null, "Missing fluid block: " + name, failures); + if (material.getFluid() != null) check(FluidRegistry.getBucketFluids().contains(material.getFluid()), "Missing bucket: " + name, failures); + if (material.hasBlock(Names.ORE)) { + final com.mcmoddev.lib.registry.recipe.ICrusherRecipe recipe = + com.mcmoddev.lib.registry.CrusherRecipeRegistry.getInstance() + .getRecipeForInputItem(material.getBlockItemStack(Names.ORE)); + check(recipe != null && ItemStack.areItemsEqual(recipe.getOutput(), material.getItemStack(Names.POWDER)), + "Base Metals crusher output missing or wrong: " + name, failures); + } + catalog.add(name, entry); + } + report.add("materials", catalog); + if (!Boolean.getBoolean("modern.baseline") && Materials.hasMaterial("aluminumbrass") + && !Loader.isModLoaded("tconstruct")) { + final net.minecraft.nbt.NBTTagCompound savedFluid = new net.minecraft.nbt.NBTTagCompound(); + savedFluid.setString("FluidName", "alubrass"); + savedFluid.setInteger("Amount", 1000); + final net.minecraftforge.fluids.FluidStack legacy = net.minecraftforge.fluids.FluidStack.loadFluidStackFromNBT(savedFluid); + check(legacy != null && legacy.amount == 1000 + && legacy.getFluid().getBlock() == Materials.getMaterialByName("aluminumbrass").getFluidBlock(), + "A saved 0.11.0 aluminum brass bucket no longer resolves", failures); + } + for(final String[] alias:new String[][]{{"chromium","Chrome"},{"chromium","Chromium"},{"tungsten","Wolfram"},{"tungsten","Tungsten"},{"aluminum","Aluminium"}}) { + if(!Materials.hasMaterial(alias[0])) continue; + boolean found=false; + for(final ItemStack stack:net.minecraftforge.oredict.OreDictionary.getOres("ingot"+alias[1])) + if(stack.getItem()==Materials.getMaterialByName(alias[0]).getItem(Names.INGOT)) found=true; + check(found,"Missing ingot alias: "+alias[1],failures); + } + final java.util.Set durability=new java.util.HashSet<>(); + for(final MMDMaterial material:Materials.getMaterialsByMod("modernmetals")) durability.add(material.getToolDurability()); + if(catalog.entrySet().size()>4) check(durability.size()>4,"Material statistics unexpectedly uniform",failures); + if (System.getProperty("modern.lowFantasy") != null) { + final boolean low=Boolean.getBoolean("modern.lowFantasy"); + check(zone.moddev.mc.modernmetals.content.ContentPolicy.active().lowFantasy()==low,"Active fantasy mode does not match the startup selection",failures); + for(final String name:new String[]{"plutonium","uranium","thorium","cadmium","rutile","titanium","stainlesssteel","galvanizedsteel"}) { + final MMDMaterial material=Materials.getMaterialByName(name); + if(material.isEmpty()) continue; + final boolean suitable=Arrays.asList("titanium","stainlesssteel","galvanizedsteel").contains(name); + boolean recipe=false; + for(final net.minecraft.item.crafting.IRecipe value:CraftingManager.getInstance().getRecipeList()) { + final ItemStack output=value.getRecipeOutput(); + if(output!=null && output.getItem()==material.getItem(Names.PICKAXE)) recipe=true; + } + check(recipe==(!low || suitable),"Fantasy mode pickaxe recipe incorrect: "+name,failures); + final boolean hasOre = material.hasOre() || name.equals("stainlesssteel") || name.equals("galvanizedsteel"); + check(material.hasItem(Names.PICKAXE) && material.hasBlock(Names.ORE)==hasOre,"Fantasy mode changed registry identities: "+name,failures); + } + if(low) { + for(final net.minecraft.item.crafting.IRecipe recipe:CraftingManager.getInstance().getRecipeList()) { + final ItemStack output=recipe.getRecipeOutput(); + if(output==null || output.getItem()==null || output.getItem().getRegistryName()==null) continue; + final ResourceLocation id=output.getItem().getRegistryName(); + if(!"modernmetals".equals(id.getResourceDomain())) continue; + final String path=id.getResourcePath(); + for(final String material:new String[]{"plutonium","uranium","thorium","boron","beryllium","cadmium","rutile","chromium","iridium","osmium"}) + for(final String form:new String[]{"gear","door","trapdoor","bars","stairs","slab","pressure_plate"}) + check(!path.equals(material+"_"+form),"Low Fantasy unrealistic product remains: "+path,failures); + } + } + } + if (System.getProperty("modern.trades") != null) checkTrades(server.getWorld(0), failures); + OptionalIntegrationChecks.run(failures); + final WorldServer world = server.getWorld(0); + for (final String[] alloy : new String[][] {{"aluminumbrass","aluminum","brass"}, + {"galvanizedsteel","steel","zinc"}, {"nichrome","nickel","chromium"}, + {"stainlesssteel","steel","chromium"}, {"titanium","rutile","magnesium"}}) { + if (!Materials.hasMaterial(alloy[0]) || !Materials.hasMaterial(alloy[1]) || !Materials.hasMaterial(alloy[2])) continue; + final InventoryCrafting grid = new InventoryCrafting(new Container() { + @Override public boolean canInteractWith(final net.minecraft.entity.player.EntityPlayer player) { return true; } + }, 2, 2); + grid.setInventorySlotContents(0, Materials.getMaterialByName(alloy[1]).getItemStack(Names.POWDER)); + grid.setInventorySlotContents(1, Materials.getMaterialByName(alloy[2]).getItemStack(Names.POWDER)); + final ItemStack result = CraftingManager.getInstance().findMatchingRecipe(grid, world); + check(result != null && result.getItem() == Materials.getMaterialByName(alloy[0]).getItem(Names.BLEND) + && result.getCount() == 2, "Unobtainable alloy blend: " + alloy[0], failures); + } + if (OreSpawnApi.getActiveProfile(server).isPresent()) { + final JsonObject profile = OreSpawnApi.getActiveProfile(server).get().toJson(); + report.add("profile", profile); + if (System.getProperty("modern.expected") != null) { + final JsonObject expected = new JsonParser().parse(new String(Files.readAllBytes( + Paths.get(System.getProperty("modern.expected"))), StandardCharsets.UTF_8)).getAsJsonObject(); + subset(expected, profile, "profile", failures); + } + if (Boolean.getBoolean("modern.sample") || "later".equals(phase)) sample(server, profile.getAsJsonObject("ores"), report, failures); + } + if (!Boolean.getBoolean("modern.baseline")) { + check(OreSpawnApi.isOreTakeoverActive("modernmetals") != Boolean.getBoolean("modern.allDisabled"), "Native provider status incorrect", failures); + try { Class.forName("com.mcmoddev.orespawn.ModernMetalsOreSpawn"); failures.add(new JsonPrimitive("Retired OS3 plugin packaged")); } + catch (final ClassNotFoundException expected) { } + } + if (Boolean.getBoolean("modern.saved")) saved(world, phase, failures); + report.add("failures", failures); + report.addProperty("passed", failures.size() == 0); + report.addProperty("phase", phase); + Files.write(Paths.get(phase + "-modern.json"), (new GsonBuilder().setPrettyPrinting().create().toJson(report) + "\n").getBytes(StandardCharsets.UTF_8)); + server.saveAllWorlds(false); + System.out.println("MODERN_METALS_RUNTIME_" + (failures.size() == 0 ? "PASS" : "FAIL") + " " + failures); + } + + private static void sample(final MinecraftServer server, final JsonObject ores, final JsonObject report, final JsonArray failures) { + final JsonObject result = new JsonObject(); + for (final int dimension : Boolean.getBoolean("modern.customDimension") ? new int[]{0,-1,1,42} : new int[]{0,-1,1}) { + if (net.minecraftforge.common.DimensionManager.getWorld(dimension) == null) + net.minecraftforge.common.DimensionManager.initDimension(dimension); + final WorldServer world = server.getWorld(dimension); + final int origin = "later".equals(System.getProperty("modern.phase")) ? 96 : 64; + for (int x=origin; x<=origin+6; x++) for (int z=origin; z<=origin+6; z++) world.getChunkFromChunkCoords(x,z); + final JsonObject counts = new JsonObject(); + final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); + for (int cx=origin+2;cx<=origin+4;cx++) for (int cz=origin+2;cz<=origin+4;cz++) { + final net.minecraft.world.chunk.Chunk chunk = world.getChunkFromChunkCoords(cx,cz); + for(int x=0;x<16;x++) for(int z=0;z<16;z++) for(int y=0;y<128;y++) { + pos.setPos(cx*16+x,y,cz*16+z); + final Block block = chunk.getBlockState(pos).getBlock(); + if (!"modernmetals".equals(block.getRegistryName().getResourceDomain()) || !block.getRegistryName().getResourcePath().endsWith("_ore")) continue; + final String name = block.getRegistryName().getResourcePath(); + check(dimension==0 || dimension==42,"Modern Metals ore generated in excluded dimension: "+dimension+":"+name,failures); + final JsonObject rule = ores.getAsJsonObject("modernmetals:legacy/"+name); + check(rule != null && rule.get("enabled").getAsBoolean(),"Disabled or undeclared ore generated: "+name,failures); + if (rule != null && rule.has("dimension_selectors")) { + final JsonObject placement=rule.getAsJsonObject("dimension_selectors").getAsJsonObject("orespawn:all_except_nether_end"); + if(placement!=null) check(y>=placement.get("min_y").getAsInt() && y<=placement.get("max_y").getAsInt(),"Ore outside height: "+name,failures); + } + counts.addProperty(name,counts.has(name)?counts.get(name).getAsInt()+1:1); + } + } + if((dimension==0 || dimension==42) && !Boolean.getBoolean("modern.noOres")) { + for(final String name:new String[]{"boron_ore","beryllium_ore","thorium_ore","rutile_ore"}) { + final JsonObject rule=ores.getAsJsonObject("modernmetals:legacy/"+name); + if(rule!=null && rule.get("enabled").getAsBoolean()) check(counts.has(name),"No generated sample: "+name,failures); + } + check(counts.has("titanium_ore")==Boolean.getBoolean("modern.titanium"),"Titanium placement does not match its switch",failures); + } + result.add(String.valueOf(dimension),counts); + } + report.add("oreSamples",result); + } + private static void subset(final JsonElement expected, final JsonElement actual, final String path, final JsonArray failures) { + if(actual==null) { check(false,"Missing "+path,failures); return; } + if(expected.isJsonObject()) { + if(!actual.isJsonObject()) { check(false,"Wrong type at "+path,failures); return; } + for(final java.util.Map.Entry entry:expected.getAsJsonObject().entrySet()) + subset(entry.getValue(),actual.getAsJsonObject().get(entry.getKey()),path+"/"+entry.getKey(),failures); + } else check(expected.equals(actual),"Changed "+path+": expected "+expected+", found "+actual,failures); + } + + private static void saved(final WorldServer world, final String phase, final JsonArray failures) { + final BlockPos pos = new BlockPos(8, 200, 8); + if ("first".equals(phase)) { + world.setBlockState(pos, Blocks.CHEST.getDefaultState(), 2); + final TileEntityChest chest = (TileEntityChest) world.getTileEntity(pos); + int slot = 0; + for (final String name : new String[] {"boron","beryllium","thorium","titanium"}) { + final MMDMaterial material = Materials.getMaterialByName(name); + if (material.isEmpty()) continue; + final ItemStack stack = material.getItemStack(Names.INGOT, 3); + stack.setStackDisplayName("Saved " + name); + stack.getTagCompound().setString("ModernMetalsCheck", name); + chest.setInventorySlotContents(slot++, stack); + } + if(Materials.hasMaterial("plutonium")) { + final ItemStack tool=Materials.getMaterialByName("plutonium").getItemStack(Names.PICKAXE); + tool.setItemDamage(13);tool.setStackDisplayName("Saved plutonium pickaxe"); + tool.getTagCompound().setString("ModernMetalsCheck","preserve equipment across content modes"); + chest.setInventorySlotContents(4,tool); + } + } + final TileEntityChest chest = (TileEntityChest) world.getTileEntity(pos); + check(chest != null, "Saved chest missing", failures); + if (chest == null) return; + int slot = 0; + for (final String name : new String[] {"boron","beryllium","thorium","titanium"}) { + final ItemStack stack = chest.getStackInSlot(slot++); + check(stack != null && stack.getCount() == 3 && ("modernmetals:" + name + "_ingot").equals(stack.getItem().getRegistryName().toString()) + && ("Saved " + name).equals(stack.getDisplayName()) && name.equals(stack.getTagCompound().getString("ModernMetalsCheck")), "Saved stack changed: " + name, failures); + } + if(Materials.hasMaterial("plutonium")) { + final ItemStack tool=chest.getStackInSlot(4); + check(tool!=null && tool.getItem()==Materials.getMaterialByName("plutonium").getItem(Names.PICKAXE) + && tool.getItemDamage()==13 && "Saved plutonium pickaxe".equals(tool.getDisplayName()) + && "preserve equipment across content modes".equals(tool.getTagCompound().getString("ModernMetalsCheck")),"Saved equipment changed in content mode",failures); + } + world.getChunkFromBlockCoords(pos).markDirty(); + } + private static void checkTrades(final WorldServer world, final JsonArray failures) { + final net.minecraft.entity.passive.EntityVillager merchant = new net.minecraft.entity.passive.EntityVillager(world); + final net.minecraftforge.fml.common.registry.VillagerRegistry.VillagerProfession smith = + net.minecraftforge.fml.common.registry.VillagerRegistry.instance().getRegistry() + .getValue(new ResourceLocation("minecraft:smith")); + final net.minecraft.village.MerchantRecipeList offers = new net.minecraft.village.MerchantRecipeList(); + final java.util.Random random = new java.util.Random(8675309L); + for (int career = 1; career <= 3; career++) { + for (int level = 0; level < 8; level++) { + final java.util.List trades = + smith.getCareer(career).getTrades(level); + if (trades != null) trades.forEach(trade -> trade.addMerchantRecipe(merchant, offers, random)); + } + } + int modern = 0; + int base = 0; + final zone.moddev.mc.modernmetals.content.ContentPolicy policy = + zone.moddev.mc.modernmetals.content.ContentPolicy.active(); + for (final net.minecraft.village.MerchantRecipe offer : offers) { + for (final ItemStack stack : new ItemStack[] { + offer.getItemToBuy(), offer.getSecondItemToBuy(), offer.getItemToSell() }) { + if (stack == null || stack.getItem().getRegistryName() == null) continue; + final String domain = stack.getItem().getRegistryName().getResourceDomain(); + if ("modernmetals".equals(domain)) { + modern++; + check(policy.allows(stack), "Fantasy mode allowed a restricted trade: " + stack, failures); + } else if ("basemetals".equals(domain)) { + base++; + } + } + } + check((modern > 0) == Boolean.getBoolean("modern.trades"), "Modern Metals trade switch ignored", failures); + check(base > 0, "Modern Metals settings removed Base Metals trades", failures); + } + + private static void check(final boolean pass, final String detail, final JsonArray failures) { if (!pass) failures.add(new JsonPrimitive(detail)); } +} diff --git a/tools/validation/legacy111/runtime-probe/review/modern/OptionalIntegrationChecks.java b/tools/validation/legacy111/runtime-probe/review/modern/OptionalIntegrationChecks.java new file mode 100644 index 00000000..775db3f2 --- /dev/null +++ b/tools/validation/legacy111/runtime-probe/review/modern/OptionalIntegrationChecks.java @@ -0,0 +1,121 @@ +package review.modern; + +import com.google.gson.JsonArray; +import com.google.gson.JsonPrimitive; +import com.mcmoddev.lib.data.Names; +import com.mcmoddev.lib.init.Materials; +import com.mcmoddev.lib.material.MMDMaterial; +import net.minecraftforge.fml.common.Loader; + +/** Optional API linkage stays in nested classes that are loaded only with that mod. */ +public final class OptionalIntegrationChecks { + private OptionalIntegrationChecks() { } + public static void run(final JsonArray failures) { + if (Loader.isModLoaded("tconstruct")) { + if(com.mcmoddev.lib.util.ConfigBase.Options.isModEnabled("tconstruct")) Tinkers.verify(failures); + else if(Boolean.getBoolean("modern.disabledIntegrationTest")) Tinkers.verifyForeign(failures); + } + if (Loader.isModLoaded("ic2")) IndustrialCraft.verify(failures); + if (Loader.isModLoaded("mekanism")) Mekanism.verify(failures); + } + public static void seedForeign() {Tinkers.seedForeign();} + private static void check(final boolean pass, final String message, final JsonArray failures) { + if (!pass) failures.add(new JsonPrimitive(message)); + } + private static final class Tinkers { + static void seedForeign() { + final slimeknights.tconstruct.library.materials.Material foreign=new slimeknights.tconstruct.library.materials.Material("plutonium",0x123456); + slimeknights.tconstruct.library.TinkerRegistry.addMaterial(foreign); + slimeknights.tconstruct.library.TinkerRegistry.addMaterialStats(foreign, + new slimeknights.tconstruct.library.materials.HeadMaterialStats(200,6F,2F,2), + new slimeknights.tconstruct.library.materials.BowMaterialStats(1F,1F,1F)); + } + static void verifyForeign(final JsonArray failures) { + final slimeknights.tconstruct.library.materials.Material foreign=slimeknights.tconstruct.library.TinkerRegistry.getMaterial("plutonium"); + check(foreign.hasStats("head") && foreign.hasStats("bow"),"Disabled integration modified another mod's Tinkers material",failures); + } + static void verify(final JsonArray failures) { + for (final MMDMaterial material : Materials.getMaterialsByMod("modernmetals")) { + final String name = material.getName(); + final slimeknights.tconstruct.library.materials.Material tc = slimeknights.tconstruct.library.TinkerRegistry.getMaterial(name); + check(tc != slimeknights.tconstruct.library.materials.Material.UNKNOWN, "Tinkers material missing: " + name, failures); + check(tc.matches(material.getItemStack(Names.INGOT)) != null, "Tinkers repair ingredient missing: " + name, failures); + final boolean allowed=zone.moddev.mc.modernmetals.content.ContentPolicy.active().allows(name,"pickaxe"); + check(slimeknights.tconstruct.tools.TinkerTools.pickHead.canUseMaterial(tc)==allowed,"Tinkers tool policy incorrect: "+name,failures); + if(allowed) { + final slimeknights.tconstruct.library.materials.Material wood=slimeknights.tconstruct.library.TinkerRegistry.getMaterial("wood"); + final net.minecraft.item.ItemStack tool=slimeknights.tconstruct.library.utils.ToolBuilder.tryBuildTool(stacks( + slimeknights.tconstruct.tools.TinkerTools.toolRod.getItemstackWithMaterial(wood), + slimeknights.tconstruct.tools.TinkerTools.pickHead.getItemstackWithMaterial(tc), + slimeknights.tconstruct.tools.TinkerTools.binding.getItemstackWithMaterial(wood),net.minecraft.item.ItemStack.EMPTY),"Modern Metals regression tool"); + check(tool!=null && !tool.isEmpty(),"Tinkers tool creation failed: "+name,failures); + if(tool!=null && !tool.isEmpty()) { + tool.setItemDamage(5); + final net.minecraft.item.ItemStack repaired=slimeknights.tconstruct.library.utils.ToolBuilder.tryRepairTool( + stacks(material.getItemStack(Names.INGOT)),tool,false); + check(repaired!=null && !repaired.isEmpty() && repaired.getItemDamage()<5,"Tinkers tool repair failed: "+name,failures); + } + } else check(!slimeknights.tconstruct.tools.TinkerTools.toolRod.canUseMaterial(tc),"Tinkers restricted handle bypass: "+name,failures); + if(zone.moddev.mc.modernmetals.content.ContentPolicy.active().lowFantasy()) + check(!slimeknights.tconstruct.tools.TinkerTools.bowLimb.canUseMaterial(tc),"Tinkers ranged policy bypass: "+name,failures); + final slimeknights.tconstruct.library.smeltery.MeltingRecipe melting = slimeknights.tconstruct.library.TinkerRegistry.getMelting(material.getItemStack(Names.INGOT)); + check(melting != null && melting.getResult() != null && melting.getResult().amount == 144, + "Tinkers ingot melting missing: " + name, failures); + final net.minecraftforge.fluids.Fluid fluid = net.minecraftforge.fluids.FluidRegistry.getFluid(name); + check(slimeknights.tconstruct.library.TinkerRegistry.getTableCasting( + slimeknights.tconstruct.smeltery.TinkerSmeltery.castIngot, fluid) != null, "Tinkers ingot casting missing: " + name, failures); + final net.minecraft.item.ItemStack headCast=slimeknights.tconstruct.library.tools.Pattern.setTagForPart( + new net.minecraft.item.ItemStack(slimeknights.tconstruct.smeltery.TinkerSmeltery.cast),slimeknights.tconstruct.tools.TinkerTools.pickHead); + check((slimeknights.tconstruct.library.TinkerRegistry.getTableCasting(headCast,fluid)!=null)==allowed, + "Tinkers part casting policy incorrect: "+name,failures); + if(zone.moddev.mc.modernmetals.content.ContentPolicy.active().lowFantasy() + && !zone.moddev.mc.modernmetals.content.ContentPolicy.active().allows(name,"gear")) + check(slimeknights.tconstruct.library.TinkerRegistry.getTableCasting(slimeknights.tconstruct.smeltery.TinkerSmeltery.castGear,fluid)==null, + "Tinkers restricted gear casting remains: "+name,failures); + } + boolean titanium = false; + for (final slimeknights.tconstruct.library.smeltery.AlloyRecipe recipe : slimeknights.tconstruct.library.TinkerRegistry.getAlloys()) { + check(recipe.getResult() != null && recipe.getResult().getFluid() != null && recipe.getResult().amount > 0, "Invalid alloy output", failures); + for (final net.minecraftforge.fluids.FluidStack input : recipe.getFluids()) + check(input != null && input.getFluid() != null && input.amount > 0, "Invalid alloy input", failures); + if (recipe.getResult() != null && recipe.getResult().getFluid() != null && "titanium".equals(recipe.getResult().getFluid().getName())) { + final java.util.Set names = new java.util.HashSet<>(); + for (final net.minecraftforge.fluids.FluidStack input : recipe.getFluids()) if (input != null && input.getFluid() != null) names.add(input.getFluid().getName()); + int inputs = 0; + for (final net.minecraftforge.fluids.FluidStack input : recipe.getFluids()) if (input != null) inputs += input.amount; + titanium |= names.contains("rutile") && names.contains("magnesium") && recipe.getResult().amount == inputs; + } + } + if(Materials.hasMaterial("titanium") && Materials.hasMaterial("rutile") && Materials.hasMaterial("magnesium")) + check(titanium, "Tinkers titanium alloy chain missing", failures); + } + } + private static final class IndustrialCraft { + static void verify(final JsonArray failures) { + for (final String name : new String[]{"boron","beryllium","thorium","titanium","chromium"}) { + if (!Materials.hasMaterial(name)) continue; + final MMDMaterial material = Materials.getMaterialByName(name); + check(ic2.api.recipe.Recipes.macerator.getOutputFor(material.getBlockItemStack(Names.ORE), false) != null, + "IC2 ore processing missing: " + name, failures); + } + } + } + private static final class Mekanism { + static void verify(final JsonArray failures) { + for (final String name : new String[]{"boron","beryllium","thorium","titanium","chromium"}) { + if (!Materials.hasMaterial(name)) continue; + final MMDMaterial material = Materials.getMaterialByName(name); + check(mekanism.api.gas.GasRegistry.getGas(name) != null, "Mekanism gas missing: " + name, failures); + check(mekanism.api.gas.GasRegistry.getGas("clean" + name) != null, "Mekanism clean gas missing: " + name, failures); + check(mekanism.common.recipe.RecipeHandler.Recipe.ENRICHMENT_CHAMBER.containsRecipe(material.getBlockItemStack(Names.ORE)), + "Mekanism enrichment missing: " + name, failures); + } + } + } + private static net.minecraft.util.NonNullList stacks(final net.minecraft.item.ItemStack... items) { + final net.minecraft.util.NonNullList list = net.minecraft.util.NonNullList.create(); + java.util.Collections.addAll(list, items); + return list; + } + +} diff --git a/tools/validation/legacy111/runtime-probe/review/modern/OreOverlayChecks.java b/tools/validation/legacy111/runtime-probe/review/modern/OreOverlayChecks.java new file mode 100644 index 00000000..ff847176 --- /dev/null +++ b/tools/validation/legacy111/runtime-probe/review/modern/OreOverlayChecks.java @@ -0,0 +1,104 @@ +package review.modern; + +import com.mcmoddev.lib.data.Names; +import com.mcmoddev.lib.init.Materials; +import java.util.List; +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.IBakedModel; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.init.Blocks; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.BlockRenderLayer; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.WorldServer; +import net.minecraftforge.fml.common.registry.ForgeRegistries; +import net.minecraftforge.oredict.OreDictionary; + +/** Checks the installed client's baked models, including the inventory forms. */ +final class OreOverlayChecks { + private static final String STONE = "minecraft:blocks/stone"; + private static final int TEST_STONE = 0xFF39B48E; + + static void verify(Minecraft minecraft) { + final IBakedModel missing = minecraft.getRenderItem().getItemModelMesher().getModelManager().getMissingModel(); + int overlaid = 0; + int fallback = 0; + for (com.mcmoddev.lib.material.MMDMaterial material : Materials.getMaterialsByMod("modernmetals")) { + if (!material.hasBlock(Names.ORE)) continue; + final Block ore = material.getBlock(Names.ORE); + final IBlockState state = ore.getDefaultState(); + final Item item = Item.getItemFromBlock(ore); + final String name = material.getName() + "_ore"; + require(ore.getRegistryName().equals(new ResourceLocation("modernmetals", name)), "Ore registry ID changed: " + name); + require(ore.getRegistryName().equals(item.getRegistryName()), "Ore item registry ID changed: " + name); + require(OreDictionary.getOres("ore" + material.getCapitalizedName()).stream().anyMatch(s -> s.getItem() == item), + "Ore Dictionary entry missing: " + name); + final boolean thorium = name.equals("thorium_ore"); + require(ore.getBlockLayer() == (thorium ? BlockRenderLayer.SOLID : BlockRenderLayer.CUTOUT_MIPPED), + "Wrong render layer: " + name); + final IBakedModel blockModel = minecraft.getBlockRendererDispatcher().getBlockModelShapes().getModelForState(state); + final IBakedModel itemModel = minecraft.getRenderItem().getItemModelMesher().getItemModel(new ItemStack(item)); + require(blockModel != missing && itemModel != missing, "Missing baked ore block or item: " + name); + verifyModel(blockModel, state, name, thorium); + verifyModel(itemModel, null, name, thorium); + if (thorium) fallback++; else overlaid++; + } + require(overlaid == 19 && fallback == 1, "Unexpected registered ore set: " + overlaid + " overlays, " + fallback + " fallback"); + final TextureAtlasSprite stone = minecraft.getTextureMapBlocks().getAtlasSprite(STONE); + require((stone.getFrameTextureData(0)[0][0] == TEST_STONE) == Boolean.getBoolean("modern.oreHostPack"), + "The expected stone resource pack was not applied"); + System.out.println("MODERN_METALS_ORE_OVERLAYS_PASS ores=" + overlaid + " fallback=" + fallback + + " hostPack=" + Boolean.getBoolean("modern.oreHostPack")); + } + + private static void verifyModel(IBakedModel model, IBlockState state, String name, boolean thorium) { + final String base = thorium ? "modernmetals:blocks/thorium_ore" : STONE; + final String overlay = "modernmetals:blocks/ore_overlays/" + name; + require(base.equals(model.getParticleTexture().getIconName()), "Wrong particles: " + name); + for (EnumFacing face : EnumFacing.values()) { + final List quads = model.getQuads(state, face, 0); + require(quads.size() == (thorium ? 1 : 2), "Wrong face count: " + name + " " + face); + verifyFace(quads, face, base, 0.0F); + if (!thorium) verifyFace(quads, face, overlay, 0.05F / 16.0F); + } + } + + private static void verifyFace(List quads, EnumFacing face, String texture, float offset) { + final BakedQuad quad = quads.stream().filter(q -> texture.equals(q.getSprite().getIconName())) + .findFirst().orElseThrow(() -> new AssertionError("Missing baked sprite: " + texture)); + final int axis = face.getFrontOffsetX() != 0 ? 0 : face.getFrontOffsetY() != 0 ? 1 : 2; + final float expected = face.getAxisDirection() == EnumFacing.AxisDirection.POSITIVE ? 1.0F + offset : -offset; + final int[] data = quad.getVertexData(); + for (int vertex = 0; vertex < 4; vertex++) { + require(Math.abs(Float.intBitsToFloat(data[vertex * (data.length / 4) + axis]) - expected) < 0.0001F, + "Wrong face depth for " + texture + " " + face); + } + } + + static void placeDisplay(MinecraftServer server) { + final WorldServer world = server.getWorld(0); + for (int x = 0; x < 5; x++) { + for (int z = -5; z <= 0; z++) { + world.setBlockState(new BlockPos(x, 199, z), Blocks.STONE.getDefaultState()); + } + } + final String[] ores = {"aluminum_ore", "boron_ore", "rutile_ore", "thorium_ore"}; + for (int x = 0; x < ores.length; x++) { + final Block ore = ForgeRegistries.BLOCKS.getValue(new ResourceLocation("modernmetals", ores[x])); + world.setBlockState(new BlockPos(x, 200, 0), ore.getDefaultState()); + } + world.setBlockState(new BlockPos(4, 200, 0), Blocks.STONE.getDefaultState()); + server.getPlayerList().getPlayers().get(0).connection.setPlayerLocation(2.5, 200, -4, 0, 13); + } + + private static void require(boolean condition, String detail) { + if (!condition) throw new AssertionError(detail); + } +} diff --git a/tools/validation/requirements.txt b/tools/validation/requirements.txt new file mode 100644 index 00000000..ad5d466f --- /dev/null +++ b/tools/validation/requirements.txt @@ -0,0 +1,2 @@ +jsonschema==4.23.0 +PyYAML==6.0.3 diff --git a/update.json b/update.json index 34cccc7c..2db1142b 100644 --- a/update.json +++ b/update.json @@ -1,7 +1,7 @@ { - "homepage": "http://minecraft.curseforge.com/projects/modern-metals", + "homepage": "https://www.curseforge.com/minecraft/mc-mods/modern-metals/files/all?version=1.11.2", "promos": { - "1.11.2-latest": "0.0.0", + "1.11.2-latest": "2.5.0-beta4", "1.11-latest": "0.0.0", "1.10.2-latest": "0.0.0", "1.10-latest": "0.0.0", @@ -13,7 +13,7 @@ "1.7.10-latest": "0.0.0", "1.7.2-latest": "0.0.0", "1.6.4-latest": "0.0.0", - "1.11.2-recommended": "0.0.0", + "1.11.2-recommended": "2.5.0-beta4", "1.11-recommended": "0.0.0", "1.10.2-recommended": "0.0.0", "1.10-recommended": "0.0.0", @@ -27,7 +27,7 @@ "1.6.4-recommended": "0.0.0" }, "1.11.2": { - "0.0.0": "No Release" + "2.5.0-beta4": "Modern Metals 2.5.0-beta4.58 for Minecraft 1.11.2" }, "1.11": { "0.0.0": "No Release"