diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c14a1fcb6..c477dd19f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,19 +1,19 @@ -name: Base Metals 1.15.2 CI +name: Base Metals 1.16.5 CI on: push: branches: - - master-1.15.2 + - master-1.16.5 - 'feature/**' pull_request: branches: - - master-1.15.2 + - master-1.16.5 permissions: contents: read concurrency: - group: basemetals-1.15-${{ github.workflow }}-${{ github.ref }} + group: basemetals-1.16-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: @@ -141,9 +141,9 @@ jobs: ./gradlew "${gradle_args[@]}" first="$RUNNER_TEMP/basemetals-first" mkdir -p "$first" - cp build/libs/BaseMetals-3.0.1.115021.jar "$first/" - cp build/libs/BaseMetals-3.0.1.115021-sources.jar "$first/" - cp build/libs/BaseMetals-3.0.1.115021-javadoc.jar "$first/" + cp build/libs/BaseMetals-3.1.0.116051.jar "$first/" + cp build/libs/BaseMetals-3.1.0.116051-sources.jar "$first/" + cp build/libs/BaseMetals-3.1.0.116051-javadoc.jar "$first/" cp build/release/SHA256SUMS "$first/" - name: Rebuild and prove byte-identical archives @@ -159,9 +159,9 @@ jobs: -Dorg.gradle.java.installations.auto-download=false ) ./gradlew "${gradle_args[@]}" - cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.0.1.115021.jar" build/libs/BaseMetals-3.0.1.115021.jar - cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.0.1.115021-sources.jar" build/libs/BaseMetals-3.0.1.115021-sources.jar - cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.0.1.115021-javadoc.jar" build/libs/BaseMetals-3.0.1.115021-javadoc.jar + cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.1.0.116051.jar" build/libs/BaseMetals-3.1.0.116051.jar + cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.1.0.116051-sources.jar" build/libs/BaseMetals-3.1.0.116051-sources.jar + cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.1.0.116051-javadoc.jar" build/libs/BaseMetals-3.1.0.116051-javadoc.jar cmp "$RUNNER_TEMP/basemetals-first/SHA256SUMS" build/release/SHA256SUMS - name: Run exact-OreSpawn dedicated-server probe @@ -181,13 +181,13 @@ jobs: - name: Upload audited release candidate uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: BaseMetals-1.15.2-${{ github.sha }} + name: BaseMetals-1.16.5-${{ github.sha }} if-no-files-found: error retention-days: 30 path: | - build/libs/BaseMetals-3.0.1.115021.jar - build/libs/BaseMetals-3.0.1.115021-sources.jar - build/libs/BaseMetals-3.0.1.115021-javadoc.jar + build/libs/BaseMetals-3.1.0.116051.jar + build/libs/BaseMetals-3.1.0.116051-sources.jar + build/libs/BaseMetals-3.1.0.116051-javadoc.jar build/release/SHA256SUMS CHANGELOG.txt @@ -195,7 +195,7 @@ jobs: if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: BaseMetals-1.15.2-diagnostics-${{ github.sha }} + name: BaseMetals-1.16.5-diagnostics-${{ github.sha }} if-no-files-found: ignore retention-days: 14 path: | diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c3fa8e69f..2efbdcb33 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -3,11 +3,11 @@ name: CodeQL on: push: branches: - - master-1.15.2 + - master-1.16.5 - 'feature/**' pull_request: branches: - - master-1.15.2 + - master-1.16.5 schedule: - cron: '37 7 * * 4' diff --git a/.github/workflows/validate-gradle-build.yml b/.github/workflows/validate-gradle-build.yml index 7d4748476..fc66575a5 100644 --- a/.github/workflows/validate-gradle-build.yml +++ b/.github/workflows/validate-gradle-build.yml @@ -3,11 +3,11 @@ name: Validate Gradle Wrapper on: push: branches: - - master-1.15.2 + - master-1.16.5 - 'feature/**' pull_request: branches: - - master-1.15.2 + - master-1.16.5 permissions: contents: read diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 491f2d76d..79aa51b5f 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,27 @@ +Base Metals 3.1.0.116051 + +- Bring the 1.15.2 release to Minecraft 1.16.5 and Forge 36.2.34. +- Keep the same materials, equipment, recipes, High/Low Fantasy settings and + material-based recipe discovery. +- Use the native 1.16 configuration screen, with the same Mods-list button + and OreSpawn cog. Keep the content-mode check when joining a server. +- Keep Kiri's ore overlays, vanilla host textures and automatic model updates. +- Increase the ore-overlay gap to reduce flickering when viewed from a distance. +- Use Forge's fluid-bucket models so filled buckets show the fluid's texture + and colour. Give all 36 buckets clear names in all 18 language files, such as + "Bucket of Molten Nickel" instead of "Nickel Bucket". +- Move to version 3.1 to distinguish these rendering improvements from the + 3.0.1 releases for Minecraft 1.13–1.15. +- Generate the new low and tall wall sides, and convert older connected walls. +- Crush Nether gold ore into two gold powders and Ancient Debris into two + vanilla Netherite scraps. Gilded Blackstone is not included. +- Keep old-world upgrades and custom anvils working on Forge 36, without losing + saved item data, migration backups or protection for existing terrain. +- Keep ForgeGradle 7, Java 8 game compatibility, reproducible archives and + isolated test launches. +- Use the published OreSpawn 4.1.0.116051 release from Curse Maven, with its + download and checksum pinned. The eleven ore rules are unchanged. + Base Metals 3.0.1.115021 - Bring Base Metals to Minecraft 1.15.2 and Forge 31.2.57. diff --git a/README.md b/README.md index 44e6053af..5338feb11 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Discord](https://img.shields.io/badge/Discord-MMD-green.svg?style=flat&logo=Discord)](https://discord.moddev.zone) [![CurseForge downloads](https://cf.way2muchnoise.eu/full_base-metals_downloads.svg)](https://www.curseforge.com/minecraft/mc-mods/base-metals) [![Supported Minecraft versions](https://cf.way2muchnoise.eu/versions/Minecraft_base-metals_all.svg)](https://www.curseforge.com/minecraft/mc-mods/base-metals) -[![Build, test, and audit](https://github.com/MinecraftModDevelopmentMods/BaseMetals/actions/workflows/ci.yml/badge.svg?branch=master-1.15.2)](https://github.com/MinecraftModDevelopmentMods/BaseMetals/actions/workflows/ci.yml?query=branch%3Amaster-1.15.2) +[![Build, test, and audit](https://github.com/MinecraftModDevelopmentMods/BaseMetals/actions/workflows/ci.yml/badge.svg?branch=master-1.16.5)](https://github.com/MinecraftModDevelopmentMods/BaseMetals/actions/workflows/ci.yml?query=branch%3Amaster-1.16.5) # Base Metals @@ -12,7 +12,7 @@ shields, bows, crossbows, arrows, and bolts. The mod also includes Crack Hammers, scythes, metal anvils, the human detector, villager trades, advancements, and extra loot to find in chests. -This is Base Metals `3.0.1.115021` for Minecraft 1.15.2. It brings the 1.14.4 +This is Base Metals `3.1.0.116051` for Minecraft 1.16.5. It brings the 1.15.2 release forward while keeping the same blocks, items and recipes. MMDLib and Additional Loot Tables are no longer needed. OreSpawn places the ores, and Base Metals supplies the metals and their recipes. @@ -22,15 +22,19 @@ overlays, so resource packs can change their surroundings without making the ores look out of place. Smith trades and village chest loot use the Armorer, Weaponsmith and Toolsmith professions introduced in Minecraft 1.14. +The 3.1 release reduces ore-overlay flickering and uses Forge's fluid-bucket +icons, with names that clearly describe their contents. These improvements +are not yet included in the 3.0.1 builds for Minecraft 1.13–1.15. + ## Requirements -- Minecraft `1.15.2` -- Forge `31.2.57` +- Minecraft `1.16.5` +- Forge `36.2.34` - Java 8 -- OreSpawn `[4.1.0.115021,5.0.0)` on both client and server +- OreSpawn `[4.1.0.116051,5.0.0)` on both client and server -Put `BaseMetals-3.0.1.115021.jar` and OreSpawn `4.1.0.115021` (or a later -compatible OreSpawn 4 build for 1.15.2) in the `mods` directory. MMDLib and +Put `BaseMetals-3.1.0.116051.jar` and OreSpawn `4.1.0.116051` (or a later +compatible OreSpawn 4 build for 1.16.5) in the `mods` directory. MMDLib and Additional Loot Tables are not dependencies. Mineralogy is optional. ## Materials and gameplay @@ -72,6 +76,8 @@ Notable mechanics include: not regenerate. - Molten-metal buckets let you place and collect fluids. Base Metals does not include a smeltery; other mods may provide ways to produce these fluids. +- Crack Hammers turn Nether gold ore into two gold powders, and Ancient Debris + into two Netherite scraps. Gilded Blackstone is not a crushing ingredient. Open the configuration from the main menu's **Mods → Base Metals → Config** button, or from the Base Metals cog in OreSpawn's world-creation settings. @@ -122,14 +128,14 @@ items in containers are protected too. Always perform the first upgrade on a copy of the world and keep the untouched original. Read [Migration](docs/MIGRATION.md) before opening an old save. A -world already upgraded to Minecraft 1.16 or later must not be opened in this -older version. Worlds from the Base Metals 1.13.2 and 1.14.4 ports can be +world already upgraded to Minecraft 1.17 or later must not be opened in this +older version. Worlds from the Base Metals 1.13.2, 1.14.4 and 1.15.2 ports can be upgraded normally. ## Compatibility Forge item, block, and fluid tags are Base Metals' public compatibility API. -The 1.15.2 line deliberately contains no version-specific Mekanism, Thermal, +The 1.16.5 line deliberately contains no version-specific Mekanism, Thermal, Tinkers' Construct, Ender IO, IC2, or Thaumcraft plugin code. Compatible mods can consume the common tags without linking to Base Metals internals. See [Compatibility](docs/COMPATIBILITY.md) and [Supported versions](docs/VERSIONS.md). @@ -175,7 +181,7 @@ Generate and verify Eclipse launches with: ``` Release candidates can also be exercised as packaged mods in prepared official -Forge 31 client and server runtimes with `packagedRuntimeIntegrationTest`; pass +Forge 36 client and server runtimes with `packagedRuntimeIntegrationTest`; pass their directories through the `packagedForgeClientRuntime` and `packagedForgeServerRuntime` Gradle properties. These probes load the exact release JAR and OreSpawn dependency, create real worlds, validate client models @@ -187,24 +193,24 @@ messages in disposable multiplayer profiles. `packagedMineralogyIntegrationTest` checks ore placement alongside Mineralogy. It uses the server runtime above and a `mineralogyTestJar` property pointing to -the qualified Mineralogy 1.15.2 JAR. Mineralogy remains optional for players. +the qualified Mineralogy 1.16.5 JAR. Mineralogy remains optional for players. CI checks a fresh Forge setup, unit tests, generated files, a dedicated server with the pinned OreSpawn release, release-JAR contents, checksums, reproducible builds, CodeQL, wrapper validation, and Eclipse classpath isolation. Test probes and historical fixtures are excluded from the published JAR. -Builds use the published [OreSpawn `4.1.0.115021` release](https://www.curseforge.com/minecraft/mc-mods/mmd-orespawn/files/9073591) -through Curse Maven. No sibling checkout or local candidate repository is -needed. The build verifies the download's checksum, Minecraft version and -configuration-screen API. +Development and packaged tests use the published +[OreSpawn `4.1.0.116051` release](https://www.curseforge.com/minecraft/mc-mods/mmd-orespawn/files/9080646) +through Curse Maven: `curse.maven:mmd-orespawn-245586:9080646`. The build checks +its checksum, Minecraft version and configuration-screen API. -CI stages that same download with `gradle/stage-orespawn-release.sh` and passes -the temporary repository through `orespawnVerificationRepository`. Local -candidate builds remain guarded against publication. +CI stages the same verified download with `gradle/stage-orespawn-release.sh` +and passes its temporary repository through `orespawnVerificationRepository`. +Normal development does not need a local OreSpawn checkout or repository. Release artifacts use Maven coordinate -`zone.moddev.mc.basemetals:BaseMetals:3.0.1.115021`. +`zone.moddev.mc.basemetals:BaseMetals:3.1.0.116051`. Report defects through the [Base Metals issue tracker](https://github.com/MinecraftModDevelopmentMods/BaseMetals/issues). diff --git a/build.gradle b/build.gradle index d5f146f3f..d7656d455 100644 --- a/build.gradle +++ b/build.gradle @@ -123,7 +123,7 @@ def mainModClasses = "${mod_id}%%${forgeMainOutputPath}${File.pathSeparator}${mo def forgeProbeOutputPath = integrationTestModClassesDirectory.get().asFile.absolutePath def probeModClasses = mainModClasses + "${File.pathSeparator}basemetalsprobe%%${forgeProbeOutputPath}" + "${File.pathSeparator}basemetalsprobe%%${forgeProbeOutputPath}" -def forge31RunTargets = [client: 'fmluserdevclient', server: 'fmluserdevserver', +def forge36RunTargets = [client: 'fmluserdevclient', server: 'fmluserdevserver', data: 'fmluserdevdata', runtimeProbeServer: 'fmluserdevserver', legacyUpgradeServer: 'fmluserdevserver'] minecraft { @@ -136,7 +136,7 @@ minecraft { systemProperty 'forge.logging.markers', 'REGISTRIES' systemProperty 'forge.logging.console.level', 'info' mods { basemetals { source sourceSets.main } } - environment 'target', forge31RunTargets[name] + environment 'target', forge36RunTargets[name] environment 'MCP_MAPPINGS', "${mapping_channel}_${mapping_version}" environment 'MCP_VERSION', mcp_version environment 'FORGE_VERSION', forge_version @@ -182,7 +182,7 @@ def prepareIntegrationTestModClasses = tasks.register('prepareIntegrationTestMod } tasks.withType(JavaExec).configureEach { JavaExec runTask -> - def runName = forge31RunTargets.keySet().find { runTask.name == 'run' + it.capitalize() } + def runName = forge36RunTargets.keySet().find { runTask.name == 'run' + it.capitalize() } if (runName != null) { runTask.dependsOn prepareForgeRunModClasses if (runTask.name in ['runRuntimeProbeServer', 'runLegacyUpgradeServer']) { diff --git a/docs/COMPATIBILITY.md b/docs/COMPATIBILITY.md index bd7b8a154..718c02a9a 100644 --- a/docs/COMPATIBILITY.md +++ b/docs/COMPATIBILITY.md @@ -8,14 +8,14 @@ ores, crystals, shards, clumps, dirty dusts, and molten fluids. Adamantite, Adamantium, Adamant, Quicksilver, and steel-sprocket names are forwarding tags. Copper-consuming recipes accept `forge:ingots/copper`, except Base Metals' own compacting and decompacting recipes, whose output must remain -the Base Metals item. On Minecraft 1.15.2 the copper ore, ingot, and storage +the Base Metals item. On Minecraft 1.16.5 the copper ore, ingot, and storage tags contain Base Metals entries; other mods can add their equivalents through normal tag merging. This version has no mod-specific plugins for Mekanism, Thermal Expansion, Tinkers' Construct, Ender IO, IC2, Thaumcraft, Dense Ores, VeinMiner, or Constructs Armory. Their version-specific 1.18 data and APIs are not valid on Forge -31. Generic processors and tools can interoperate through Forge tags. +36. Generic processors and tools can interoperate through Forge tags. Mineralogy is optional. When installed, OreSpawn can place the same ores in Mineralogy's rocks. Base Metals does not need Mineralogy to run. diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md index ff9d2e212..a324bcdd7 100644 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -6,7 +6,7 @@ blocks cannot be reconstructed by Mojang's vanilla fixer alone, so Base Metals installs a narrowly scoped migration hook before Forge reads an old world. Always upgrade a copy and retain the original 1.10/1.12 world. Install Base -Metals `3.0.1.115021`, OreSpawn `4.1.0.115021`, and any other 1.15.2 mods needed +Metals `3.1.0.116051`, OreSpawn `4.1.0.116051`, and any other 1.16.5 mods needed by that world. Do not carry MMDLib, Additional Loot Tables, the OreSpawn 3 plugin, or Base Metals' old fallback generator into the new instance. @@ -37,8 +37,8 @@ nugget, and the MMDLib Vanilla Bits block/item/fluid names. The migration supports the original Cyano Base Metals 2.4 line and the later MMD Base Metals releases for Minecraft 1.10.2 and 1.12.2. It is not a downgrade -path: do not open a world already saved by Minecraft 1.16 or newer, including -the Base Metals 1.18.2 port, in Minecraft 1.15.2. +path: do not open a world already saved by Minecraft 1.17 or newer, including +the Base Metals 1.18.2 port, in Minecraft 1.16.5. ## Mod settings @@ -60,16 +60,19 @@ migration remains OreSpawn's responsibility. Keep a copy of the old OreSpawn configuration alongside the untouched source world when validating an upgrade. The generated manifests at `data/basemetals/registry_manifest_1_12.json` and -`data/basemetals/registry_manifest.json` document the historical registry -baseline and its 1.15.2 projection. +`data/basemetals/registry_manifest.json` list the old registered names and their +1.16.5 counterparts. -## Moving from Minecraft 1.13.2 or 1.14.4 +## Moving from Minecraft 1.13.2, 1.14.4 or 1.15.2 -Use a copy of your 1.13.2 or 1.14.4 world, with the 1.15.2 builds of Base Metals and +Use a copy of your 1.13.2, 1.14.4 or 1.15.2 world, with the 1.16.5 builds of Base Metals and OreSpawn. Keep `basemetals-common.toml` and your OreSpawn settings: this port uses the same configuration keys, registered names and ore-rule IDs. Existing items, recipe-book progress and terrain should remain unchanged. Once Minecraft -has saved the copy in 1.15.2, do not reopen it in the older game. +has saved the copy in 1.16.5, do not reopen it in the older game. + +Base Metals converts the old boolean wall connections to 1.16's low and absent +connections. Existing tall connections and wall posts are left as they are. Forge 1.13 saved a profession registry that no longer exists in 1.14. Base Metals removes that obsolete index when it contains only vanilla professions; @@ -81,3 +84,8 @@ before Base Metals changes it. The old vanilla sign and dye names are mapped to oak signs and the matching 1.14 dyes. Other missing registry entries still need the relevant mod's migration support; Base Metals does not silently discard them. + +On the first upgrade, Forge may also warn about vanilla registry entries that +Minecraft 1.16 removed or renamed, such as zombie pigmen, their spawn egg and +sounds. These warnings are separate from Base Metals' migration. Keep the +backup and check any missing entries from other mods before continuing. diff --git a/docs/VERSIONS.md b/docs/VERSIONS.md index 03a7f0bb8..8f864c7c3 100644 --- a/docs/VERSIONS.md +++ b/docs/VERSIONS.md @@ -1,19 +1,20 @@ # Supported versions -Base Metals `3.0.1.115021` targets Minecraft `1.15.2`, Forge `31.2.57`, and +Base Metals `3.1.0.116051` targets Minecraft `1.16.5`, Forge `36.2.34`, and Java 8. The Gradle build itself runs on Java `17.0.1+12` and uses ForgeGradle `7.0.34` with Gradle `9.6.1`; those build-time runtimes do not change the Java 8 requirement of the released mod. -Builds and tests use the published [OreSpawn `4.1.0.115021` release](https://www.curseforge.com/minecraft/mc-mods/mmd-orespawn/files/9073591), -CurseForge project `245586`, file `9073591`, with -SHA-256 `F7C9A110E834EC3F73D55C20E8F52EEA1CF48C4E3ACCFBE8FCEFB60E78ABE387`. -At runtime, Base Metals requires OreSpawn `[4.1.0.115021,5.0.0)` for Minecraft 1.15.2. +Builds and tests use the published +[OreSpawn `4.1.0.116051` release](https://www.curseforge.com/minecraft/mc-mods/mmd-orespawn/files/9080646) with +SHA-256 `CDF59D83F191C5FBCD8921228DB577066A63A8FEB25CA79DB4EAEEA32D32DB04`. +At runtime, Base Metals requires OreSpawn `[4.1.0.116051,5.0.0)` for Minecraft 1.16.5. The configuration cog uses OreSpawn's public 4.1 client API. -The exact build dependency is `curse.maven:mmd-orespawn-245586:9073591`. -CI stages the same checksum-verified file in a temporary Maven repository. +The development coordinate is `curse.maven:mmd-orespawn-245586:9080646`: +CurseForge project `245586`, file `9080646`. CI downloads and checks the same +file before staging it in a temporary Maven repository. The schema-3 provider supports the OreSpawn versions in the runtime range above. Release artifacts use Maven coordinate -`zone.moddev.mc.basemetals:BaseMetals:3.0.1.115021`. +`zone.moddev.mc.basemetals:BaseMetals:3.1.0.116051`. diff --git a/docs/WORLDGEN.md b/docs/WORLDGEN.md index 3e3575190..19c3569ef 100644 --- a/docs/WORLDGEN.md +++ b/docs/WORLDGEN.md @@ -1,8 +1,8 @@ # OreSpawn world generation -Base Metals requires OreSpawn `[4.1.0.115021,5.0.0)` and packages provider +Base Metals requires OreSpawn `[4.1.0.116051,5.0.0)` and packages provider schema 3, revision 1 at `data/basemetals/orespawn/provider.json`. Builds and -tests use the published OreSpawn `4.1.0.115021` release. +tests use the published OreSpawn `4.1.0.116051` release, CurseForge file `9080646`. The provider declares ores only: it has no rocks, geomes, biome rules, terrain dimensions, formations, fluid deposits, vanilla suppression, or strata @@ -24,7 +24,7 @@ and `retrogen:false`. | Nickel | all except Nether/End | 32..95 | uniform | 1 | same | | Platinum | all except Nether/End | 1..31 | uniform | 0.125 | same | -Minecraft 1.15.2 has no vanilla copper ore, so Base Metals copper generates +Minecraft 1.16.5 has no vanilla copper ore, so Base Metals copper generates by default. Antimony and Bismuth ores are available in creative mode and old saves, but do not generate unless you add rules for them in OreSpawn. diff --git a/gradle.properties b/gradle.properties index 9e2dfec32..7075a3e34 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,10 +7,10 @@ org.gradle.parallel=false net.minecraftforge.gradle.merge-source-sets=false # Minecraft, Forge and mappings must describe the same game version. -minecraft_version=1.15.2 -minecraft_version_range=[1.15.2] -forge_version=31.2.57 -forge_installer_sha256=84FD49A743978421B26F62AC78222289FE892A46F996CF11CB5E05B7441A7773 +minecraft_version=1.16.5 +minecraft_version_range=[1.16.5] +forge_version=36.2.34 +forge_installer_sha256=53B73DBBCC486F38726E76CE923B13381044984C83C762847C7193340F97130F # Target and Java versions used by release checks and publishing. loader_name=forge @@ -20,27 +20,27 @@ java_toolchain_version=8.0.502+7 gradle_java_version=17 curseforge_project_id=240967 -# Exact OreSpawn release used by development, CI and release checks. +# Exact OreSpawn release used for development, CI and packaged tests. orespawn_dependency_mode=release -orespawn_version=4.1.0.115021 +orespawn_version=4.1.0.116051 orespawn_curse_project_id=245586 -orespawn_curse_file_id=9073591 -orespawn_sha256=F7C9A110E834EC3F73D55C20E8F52EEA1CF48C4E3ACCFBE8FCEFB60E78ABE387 +orespawn_curse_file_id=9080646 +orespawn_sha256=CDF59D83F191C5FBCD8921228DB577066A63A8FEB25CA79DB4EAEEA32D32DB04 # Versions accepted when players load the mod. -forge_version_range=[31,) -loader_version_range=[31,) +forge_version_range=[36,) +loader_version_range=[36,) -# MCP mappings for Forge 31. -mapping_channel=snapshot -mapping_version=20200514-1.15.1 -mcp_version=20200515.085601 +# MCP mappings for Forge 36. +mapping_channel=official +mapping_version=1.16.5 +mcp_version=20210115.111550 # Mod identity. Keep mod_id unchanged so existing saves still load. mod_id=basemetals mod_name=Base Metals mod_license=LGPL-2.1 -mod_version=3.0.1.115021 +mod_version=3.1.0.116051 # Maven coordinates and credits. mod_group=zone.moddev.mc.basemetals diff --git a/gradle/README.md b/gradle/README.md index fbeada3d3..f6b255f2c 100644 --- a/gradle/README.md +++ b/gradle/README.md @@ -17,14 +17,15 @@ Use Java 17 to run Gradle. ForgeGradle uses Java 25 for its Mavenizer; Minecraft and the mod run on Java 8. Development accepts newer Java 8 updates, while release auditing requires the pinned compiler in `gradle.properties`. -Development resolves OreSpawn `4.1.0.115021` from Curse Maven, using file -`9073591`. `verifyReleaseDependencies` checks its checksum, mod metadata and -configuration-screen API. No local candidate setup is needed. +The build resolves the published OreSpawn `4.1.0.116051` release through +`curse.maven:mmd-orespawn-245586:9080646`. `verifyReleaseDependencies` checks +its checksum, mod metadata and configuration-screen API. No local OreSpawn +checkout is needed. -CI runs `stage-orespawn-release.sh ` to download and checksum that +CI uses `stage-orespawn-release.sh ` to download and verify the same file, then passes `-PorespawnVerificationRepository=` to -Gradle. The mirror does not change the dependency's coordinates or checksum. -The optional local-candidate path still blocks publication. +Gradle. Local candidate support remains available for future ports, but +candidate builds cannot be published. Run `check build` for the normal build, then `verifyReleaseArtifacts` and `writeReleaseChecksums` when preparing a release candidate. Packaged checks diff --git a/gradle/ide/eclipse.gradle b/gradle/ide/eclipse.gradle index 452337b00..c5bd43771 100644 --- a/gradle/ide/eclipse.gradle +++ b/gradle/ide/eclipse.gradle @@ -75,7 +75,7 @@ def quoteEclipseLaunchPathArguments = { String contents -> contents } -def syncForge31EclipseLaunches = tasks.register('syncForge31EclipseLaunches') { +def syncForge36EclipseLaunches = tasks.register('syncForge36EclipseLaunches') { group = 'ide' dependsOn tasks.named('processResources') doLast { @@ -126,13 +126,13 @@ def syncForge31EclipseLaunches = tasks.register('syncForge31EclipseLaunches') { } } } -tasks.matching { it.name == 'genEclipseRuns' }.configureEach { finalizedBy syncForge31EclipseLaunches } -tasks.matching { it.name == 'eclipse' }.configureEach { finalizedBy syncForge31EclipseLaunches } +tasks.matching { it.name == 'genEclipseRuns' }.configureEach { finalizedBy syncForge36EclipseLaunches } +tasks.matching { it.name == 'eclipse' }.configureEach { finalizedBy syncForge36EclipseLaunches } tasks.register('isolateEclipseProductionRuns') { group = 'ide' dependsOn tasks.named('genEclipseRuns') - dependsOn syncForge31EclipseLaunches + dependsOn syncForge36EclipseLaunches dependsOn configureEclipseBuildship } @@ -198,7 +198,7 @@ tasks.register('verifyEclipseProductionClasspath') { || !contents.contains("MOD_CLASSES\" value=\"${expectedModClasses}\"") || !contents.contains('MAIN_TYPE" value="net.minecraftforge.launcher.Main"') || !contents.contains('--main net.minecraftforge.userdev.LaunchTesting')) { - throw new GradleException("${name} is not an isolated Forge 31 launch: ${leaked}") + throw new GradleException("${name} is not an isolated Forge 36 launch: ${leaked}") } } List allLaunches = fileTree(projectDir) { include '*.launch' }.files.sort { it.name } diff --git a/gradle/release/artifacts.gradle b/gradle/release/artifacts.gradle index 66b5f06d6..04b3220f1 100644 --- a/gradle/release/artifacts.gradle +++ b/gradle/release/artifacts.gradle @@ -69,7 +69,7 @@ tasks.register('verifyJava8Toolchain') { doLast { def metadata = tasks.named('compileJava', JavaCompile).get().javaCompiler.get().metadata if (metadata.languageVersion.asInt() != 8) { - throw new GradleException("Minecraft 1.15.2 requires a Java 8 JDK, found " + + throw new GradleException("Minecraft 1.16.5 requires a Java 8 JDK, found " + "${metadata.vendor} ${metadata.javaRuntimeVersion} at ${metadata.installationPath}") } logger.lifecycle("Java 8 development toolchain: ${metadata.vendor} ${metadata.javaRuntimeVersion}") @@ -131,21 +131,21 @@ tasks.register('verifyReleaseConfiguration') { loaderCode: project.loader_code.toString(), javaVersion: project.java_version.toString(), gradleJavaVersion: project.gradle_java_version.toString(), curseForgeProjectId: project.curseforge_project_id.toString()] - def expected = [modVersion: '3.0.1.115021', modGroup: expectedMavenGroup, - minecraftVersion: '1.15.2', forgeVersion: '31.2.57', - forgeInstallerSha256: '84FD49A743978421B26F62AC78222289FE892A46F996CF11CB5E05B7441A7773', - mappingChannel: 'snapshot', mappingVersion: '20200514-1.15.1', - mcpVersion: '20200515.085601', loaderName: 'forge', loaderCode: '1', + def expected = [modVersion: '3.1.0.116051', modGroup: expectedMavenGroup, + minecraftVersion: '1.16.5', forgeVersion: '36.2.34', + forgeInstallerSha256: '53B73DBBCC486F38726E76CE923B13381044984C83C762847C7193340F97130F', + mappingChannel: 'official', mappingVersion: '1.16.5', + mcpVersion: '20210115.111550', loaderName: 'forge', loaderCode: '1', javaVersion: '8', gradleJavaVersion: '17', curseForgeProjectId: '240967'] - if (actual != expected) throw new GradleException("Unexpected Base Metals 1.15.2 identity: ${actual}") + if (actual != expected) throw new GradleException("Unexpected Base Metals 1.16.5 identity: ${actual}") } } tasks.register('verifyReleaseDependencies') { group = 'verification' doLast { - def expected = [version: '4.1.0.115021', curseProjectId: '245586', curseFileId: '9073591', - sha256: 'F7C9A110E834EC3F73D55C20E8F52EEA1CF48C4E3ACCFBE8FCEFB60E78ABE387'] + def expected = [version: '4.1.0.116051', curseProjectId: '245586', curseFileId: '9080646', + sha256: 'CDF59D83F191C5FBCD8921228DB577066A63A8FEB25CA79DB4EAEEA32D32DB04'] if (configuredOreSpawnIdentity != expected) { throw new GradleException("Unexpected OreSpawn identity: ${configuredOreSpawnIdentity}") } @@ -160,11 +160,11 @@ tasks.register('verifyReleaseDependencies') { if (metadataEntry == null) throw new GradleException('OreSpawn mod metadata is missing') String metadata = zip.getInputStream(metadataEntry).getText('UTF-8') if (!metadata.contains('modId="orespawn"') - || !metadata.contains('version="4.1.0.115021"') - || !metadata.contains('versionRange="[1.15.2]"') + || !metadata.contains('version="4.1.0.116051"') + || !metadata.contains('versionRange="[1.16.5,1.17)"') || zip.getEntry('zone/moddev/mc/orespawn/api/OreSpawnApi.class') == null || zip.getEntry('zone/moddev/mc/orespawn/api/client/WorldSettingsExtensionRegistry.class') == null) { - throw new GradleException('Expected Minecraft 1.15.2 OreSpawn 4.1 and its configuration-screen API') + throw new GradleException('Expected Minecraft 1.16.5 OreSpawn 4.1 and its configuration-screen API') } } } @@ -243,7 +243,7 @@ tasks.register('verifyReleaseArtifacts') { new ZipFile(main).withCloseable { zip -> Set names = zip.entries().toList().collect { it.name } as Set ['META-INF/mods.toml', 'META-INF/MANIFEST.MF', 'META-INF/coremods.json', - 'coremods/basemetals_115_compatibility.js', 'pack.mcmeta', + 'coremods/basemetals_116_compatibility.js', 'pack.mcmeta', 'data/basemetals/orespawn/provider.json', 'data/basemetals/recipes/stone_crackhammer.json', 'data/minecraft/tags/blocks/anvil.json', 'assets/basemetals/lang/en_us.json', 'META-INF/basemetals/docs/VERSIONS.md'].each { required -> @@ -252,14 +252,21 @@ tasks.register('verifyReleaseArtifacts') { Manifest manifest = new Manifest(zip.getInputStream(zip.getEntry('META-INF/MANIFEST.MF'))) if (manifest.mainAttributes.getValue('Maven-Artifact') != expectedMavenCoordinate || manifest.mainAttributes.getValue('Build-Target') != - 'minecraft-1.15.2-forge-31.2.57-java-8') { + 'minecraft-1.16.5-forge-36.2.34-java-8') { throw new GradleException('Release manifest contains the wrong identity') } String modsToml = zip.getInputStream(zip.getEntry('META-INF/mods.toml')).getText('UTF-8') - if (modsToml.contains('${') || !modsToml.contains('version="3.0.1.115021"') - || !modsToml.contains('versionRange="[4.1.0.115021,5.0.0)"')) { + if (modsToml.contains('${') || !modsToml.contains('version="3.1.0.116051"') + || !modsToml.contains('versionRange="[4.1.0.116051,5.0.0)"') + || !modsToml.contains('loaderVersion="[36,)"') + || !modsToml.contains('versionRange="[36,)"') + || !modsToml.contains('versionRange="[1.16.5]"')) { throw new GradleException('Release mods.toml contains unresolved or incompatible metadata') } + def pack = new JsonSlurper().parse(zip.getInputStream(zip.getEntry('pack.mcmeta'))) + if (pack.pack.pack_format != 6) { + throw new GradleException('Release resources must use Minecraft 1.16 pack format 6') + } def provider = new JsonSlurper().parse(zip.getInputStream( zip.getEntry('data/basemetals/orespawn/provider.json'))) if (provider.schema_version != 3 || provider.provider_revision != 1 diff --git a/gradle/stage-orespawn-candidate.ps1 b/gradle/stage-orespawn-candidate.ps1 index 15b8eca38..3565baf20 100644 --- a/gradle/stage-orespawn-candidate.ps1 +++ b/gradle/stage-orespawn-candidate.ps1 @@ -4,8 +4,8 @@ param( ) $ErrorActionPreference = 'Stop' -$candidateVersion = '4.1.0.115021' -$candidateSha256 = 'F7C9A110E834EC3F73D55C20E8F52EEA1CF48C4E3ACCFBE8FCEFB60E78ABE387' +$candidateVersion = '4.1.0.116051' +$candidateSha256 = 'CDF59D83F191C5FBCD8921228DB577066A63A8FEB25CA79DB4EAEEA32D32DB04' if ((Get-FileHash -LiteralPath $CandidateJar -Algorithm SHA256).Hash -ne $candidateSha256) { throw 'The OreSpawn candidate differs from the qualified build.' @@ -18,9 +18,9 @@ try { if ($null -eq $metadata) { throw 'OreSpawn mod metadata is missing.' } $reader = [IO.StreamReader]::new($metadata.Open()) try { $text = $reader.ReadToEnd() } finally { $reader.Dispose() } - if (!$text.Contains('version="4.1.0.115021"') -or !$text.Contains('versionRange="[1.15.2]"') -or + if (!$text.Contains('version="4.1.0.116051"') -or !$text.Contains('versionRange="[1.16.5,1.17)"') -or $null -eq $archive.GetEntry('zone/moddev/mc/orespawn/api/client/WorldSettingsExtensionRegistry.class')) { - throw 'Expected the OreSpawn 1.15.2 candidate with its configuration-screen API.' + throw 'Expected the OreSpawn 1.16.5 candidate with its configuration-screen API.' } } finally { $archive.Dispose() } diff --git a/gradle/stage-orespawn-release.sh b/gradle/stage-orespawn-release.sh index 5395ab2a9..77f599ddc 100644 --- a/gradle/stage-orespawn-release.sh +++ b/gradle/stage-orespawn-release.sh @@ -5,7 +5,7 @@ repository="${1:?Usage: stage-orespawn-release.sh }" value() { sed -n "s/^$1=//p" gradle.properties; } if [[ "$(value orespawn_dependency_mode)" == "candidate" ]]; then - echo 'Publication is blocked: pin the public OreSpawn 1.15.2 release first.' >&2 + echo 'Publication is blocked: pin the public OreSpawn 1.16.5 release first.' >&2 exit 1 fi diff --git a/gradle/verification/development.gradle b/gradle/verification/development.gradle index e963c2a8a..6e7c97cc8 100644 --- a/gradle/verification/development.gradle +++ b/gradle/verification/development.gradle @@ -22,6 +22,7 @@ def prepareIntegrationTestRun = tasks.register('prepareIntegrationTestRun') { new File(runDirectory, 'eula.txt').setText('eula=true\n', 'UTF-8') new File(runDirectory, 'server.properties').setText('''\ level-name=basemetals-runtime-probe +level-seed=8675309 online-mode=false spawn-protection=0 server-port=0 @@ -132,8 +133,8 @@ def verifyLegacyUpgradeRun = tasks.register('verifyLegacyUpgradeRun') { if (fixtureManifest.isFile()) { boolean from110 = new File(runDirectory, 'world/BASEMETALS_1_10_FIXTURE_COMPLETE.txt').isFile() - String marker = from110 ? 'BASEMETALS_1_10_TO_1_15_UPGRADE PASS' - : 'BASEMETALS_1_12_TO_1_15_UPGRADE PASS' + String marker = from110 ? 'BASEMETALS_1_10_TO_1_16_UPGRADE PASS' + : 'BASEMETALS_1_12_TO_1_16_UPGRADE PASS' if (!text.contains(marker)) { throw new GradleException("Complete ${from110 ? '1.10' : '1.12'} fixture verification did not report PASS") } diff --git a/gradle/verification/generated-data.gradle b/gradle/verification/generated-data.gradle index 51cfdcf1d..f15ff566c 100644 --- a/gradle/verification/generated-data.gradle +++ b/gradle/verification/generated-data.gradle @@ -8,8 +8,8 @@ def sha256 = project.ext.baseMetalsBuild.sha256 def verifyGeneratedData = tasks.register('verifyGeneratedData', Exec) { group = 'verification' - description = 'Regenerates and proves that the committed Minecraft 1.15.2 resource projection is current.' - commandLine 'node', 'tools/generate_115_resources.js' + description = 'Regenerates and proves that the committed Minecraft 1.16.5 resource projection is current.' + commandLine 'node', 'tools/generate_116_resources.js' def snapshot = { Map result = new TreeMap<>() [file('src/generated/resources'), diff --git a/gradle/verification/packaged-forge.gradle b/gradle/verification/packaged-forge.gradle index 3c9f9f510..e38c7dc61 100644 --- a/gradle/verification/packaged-forge.gradle +++ b/gradle/verification/packaged-forge.gradle @@ -16,6 +16,7 @@ def packagedServerProbeDeobfJar = tasks.register('packagedServerProbeDeobfJar', } def packagedServerProbeJar = renamer.classes(packagedServerProbeDeobfJar) { map.from minecraft.dependency.toSrgFile + libraries.from sourceSets.main.output, sourceSets.main.compileClasspath output = layout.buildDirectory.file('packaged-probes/basemetalsprobe.jar') } @@ -45,6 +46,7 @@ def generateOreOverlayClientFixtures = tasks.register('generateOreOverlayClientF } def packagedClientProbeJar = renamer.classes(clientIntegrationTestModJar) { map.from minecraft.dependency.toSrgFile + libraries.from sourceSets.main.output, sourceSets.main.compileClasspath output = layout.buildDirectory.file('client-integration-fixture/basemetalsclientprobe.jar') } @@ -52,7 +54,7 @@ def packagedForgeServerRuntime = providers.gradleProperty('packagedForgeServerRu def packagedForgeClientRuntime = providers.gradleProperty('packagedForgeClientRuntime') def requireRuntimeDirectory = { Provider configuredPath, String propertyName -> if (!configuredPath.isPresent()) { - throw new GradleException("Pass -P${propertyName}=") + throw new GradleException("Pass -P${propertyName}=") } File runtime = file(configuredPath.get()).canonicalFile if (!runtime.isDirectory()) { @@ -109,7 +111,7 @@ def preparePackagedServerIntegration = tasks.register('preparePackagedServerInte } new File(runDirectory, 'server.properties').setText('''\ level-name=basemetals-packaged-server-smoke -level-seed=0 +level-seed=8675309 online-mode=false spawn-protection=0 server-port=0 @@ -141,7 +143,7 @@ def packagedServerProcess = tasks.register('packagedServerProcess', Exec) { } tasks.register('packagedServerIntegrationTest') { group = 'verification' - description = 'Runs the exact release jars in an official Forge 31 dedicated-server runtime.' + description = 'Runs the exact release jars in an official Forge 36 dedicated-server runtime.' dependsOn packagedServerProcess doLast { assertPackagedRuntimePassed(packagedServerRunDirectory.get().asFile, @@ -187,7 +189,7 @@ def preparePackagedClientIntegration = tasks.register('preparePackagedClientInte File overlayPack = new File(runDirectory, 'resourcepacks/basemetals-ore-overlay-fixture') copy { from oreOverlayClientFixtures; into overlayPack } new File(overlayPack, 'pack.mcmeta').setText( - '{"pack":{"pack_format":5,"description":"Base Metals ore overlay test fixture"}}\n', 'UTF-8') + '{"pack":{"pack_format":6,"description":"Base Metals ore overlay test fixture"}}\n', 'UTF-8') new File(runDirectory, 'options.txt').setText( 'fullscreen:false\nlang:en_us\nresourcePacks:["file/basemetals-ore-overlay-fixture"]\n', 'UTF-8') } @@ -270,7 +272,7 @@ def packagedClientProcess = tasks.register('packagedClientProcess', Exec) { } tasks.register('packagedClientIntegrationTest') { group = 'verification' - description = 'Loads and renders the exact release jars in an official Forge 31 client runtime.' + description = 'Loads and renders the exact release jars in an official Forge 36 client runtime.' dependsOn packagedClientProcess doLast { File runDirectory = packagedClientRunDirectory.get().asFile @@ -286,11 +288,44 @@ tasks.register('packagedClientIntegrationTest') { } } +def packagedClientReloadProcess = tasks.register('packagedClientReloadProcess', Exec) { + group = 'verification' + timeout = java.time.Duration.ofMinutes(3) + dependsOn tasks.named('packagedClientIntegrationTest') + doFirst { + File runDirectory = packagedClientRunDirectory.get().asFile + if (!new File(runDirectory, 'saves/basemetals-client-smoke-world/level.dat').isFile()) { + throw new GradleException('Packaged client did not save its disposable world') + } + delete new File(runDirectory, 'client-smoke-pass.properties') + List arguments = new ArrayList<>(packagedClientProcess.get().commandLine) + arguments.add(1, '-Dbasemetalsclientprobe.reload=true') + workingDir runDirectory + commandLine arguments + } +} + +tasks.register('packagedClientReloadTest') { + group = 'verification' + description = 'Reopens the packaged-client world and checks saved arrow and bolt identities.' + dependsOn packagedClientReloadProcess + doLast { + File runDirectory = packagedClientRunDirectory.get().asFile + Properties values = new Properties() + new File(runDirectory, 'client-smoke-pass.properties').withInputStream { values.load(it) } + if (values.getProperty('integrated_world_reloaded') != 'true') { + throw new GradleException('Packaged client did not complete its reload') + } + assertPackagedRuntimePassed(runDirectory, 'BASEMETALS_CLIENT_RELOAD_PROBE PASS', 'packaged client reload') + assertPackagedRuntimePassed(runDirectory, 'BASEMETALS_CLIENT_PROBE PASS', 'packaged client reload') + } +} + tasks.register('packagedRuntimeIntegrationTest') { group = 'verification' - description = 'Runs the exact Base Metals and OreSpawn jars in official Forge 31 server and client runtimes.' + description = 'Runs the exact Base Metals and OreSpawn jars in official Forge 36 server and client runtimes.' dependsOn tasks.named('packagedServerReloadTest') - dependsOn tasks.named('packagedClientIntegrationTest') + dependsOn tasks.named('packagedClientReloadTest') } tasks.register('packagedMineralogyIntegrationTest', Exec) { @@ -300,11 +335,11 @@ tasks.register('packagedMineralogyIntegrationTest', Exec) { timeout = java.time.Duration.ofMinutes(5) doFirst { String configuredJar = providers.gradleProperty('mineralogyTestJar').orNull - if (configuredJar == null) throw new GradleException('Pass -PmineralogyTestJar=') + if (configuredJar == null) throw new GradleException('Pass -PmineralogyTestJar=') File mineralogy = file(configuredJar) if (!mineralogy.isFile() || sha256(mineralogy) != - 'ADACF365CCAFF7091980AA92ED1FCC14892AA6CD4B10E82336C8512525341DFA') { - throw new GradleException('Expected the qualified Mineralogy 6.0.1.115021 artifact') + '6A22C97BC74338327C62B7A9DF42499BBCB6E6DF5C06BE33EECF2662E007BE1C') { + throw new GradleException('Expected the qualified Mineralogy 6.1.0.116051 artifact') } File runDirectory = prepareBuildOwnedRunDirectory( @@ -314,7 +349,7 @@ tasks.register('packagedMineralogyIntegrationTest', Exec) { into new File(runDirectory, 'mods') } new File(runDirectory, 'server.properties').setText( - 'level-name=world\nlevel-seed=0\nonline-mode=false\nserver-port=0\nview-distance=4\nmax-tick-time=-1\n', 'UTF-8') + 'level-name=world\nlevel-seed=8675309\nonline-mode=false\nserver-port=0\nview-distance=4\nmax-tick-time=-1\n', 'UTF-8') new File(runDirectory, 'eula.txt').setText('eula=true\n', 'UTF-8') File runtime = requireRuntimeDirectory(packagedForgeServerRuntime, 'packagedForgeServerRuntime') workingDir runDirectory diff --git a/src/clientIntegrationTest/java/zone/moddev/mc/basemetals/testmod/BaseMetalsClientProbe.java b/src/clientIntegrationTest/java/zone/moddev/mc/basemetals/testmod/BaseMetalsClientProbe.java index a0c434903..f0c66779c 100644 --- a/src/clientIntegrationTest/java/zone/moddev/mc/basemetals/testmod/BaseMetalsClientProbe.java +++ b/src/clientIntegrationTest/java/zone/moddev/mc/basemetals/testmod/BaseMetalsClientProbe.java @@ -33,7 +33,13 @@ import net.minecraft.util.ResourceLocation; import net.minecraft.world.GameType; import net.minecraft.world.WorldSettings; -import net.minecraft.world.WorldType; +import net.minecraft.world.Difficulty; +import net.minecraft.world.GameRules; +import net.minecraft.world.gen.settings.DimensionGeneratorSettings; +import net.minecraft.util.datafix.codec.DatapackCodec; +import net.minecraft.util.registry.DynamicRegistries; +import net.minecraft.util.registry.Registry; +import com.mojang.blaze3d.matrix.MatrixStack; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.ForgeHooksClient; import net.minecraftforge.client.event.RenderWorldLastEvent; @@ -63,6 +69,7 @@ public final class BaseMetalsClientProbe { static final String MODID = "basemetalsclientprobe"; private static final String WORLD_DIRECTORY = "basemetals-client-smoke-world"; + private boolean pendingCreativeBucketScreenshot; private static final Logger LOGGER = LogManager.getLogger(MODID); private static volatile BaseMetalsClientProbe instance; @@ -76,6 +83,26 @@ public BaseMetalsClientProbe() { LOGGER.info("Client probe loaded; enabled={}", Boolean.getBoolean("basemetalsclientprobe.enabled")); } + @SubscribeEvent + public static void onScreenRendered(net.minecraftforge.client.event.GuiScreenEvent.DrawScreenEvent.Post event) { + BaseMetalsClientProbe probe = instance; + if (probe == null || !probe.pendingCreativeBucketScreenshot + || !(event.getGui() instanceof net.minecraft.client.gui.screen.inventory.CreativeScreen)) return; + + Minecraft minecraft = Minecraft.getInstance(); + try (net.minecraft.client.renderer.texture.NativeImage screenshot = net.minecraft.util.ScreenShotHelper + .takeScreenshot(minecraft.getWindow().getWidth(), minecraft.getWindow().getHeight(), + minecraft.getMainRenderTarget())) { + screenshot.writeToFile(new File("creative-buckets.png")); + LOGGER.info("BASEMETALS_CREATIVE_BUCKET_SCREENSHOT saved=creative-buckets.png"); + } catch (IOException failure) { + fail(minecraft, failure.toString()); + } finally { + probe.pendingCreativeBucketScreenshot = false; + minecraft.setScreen(null); + } + } + @SubscribeEvent public static void onWorldRendered(RenderWorldLastEvent event) { BaseMetalsClientProbe probe = instance; @@ -90,7 +117,7 @@ public static void onProjectileRendered(net.minecraftforge.client.event.RenderNa BaseMetalsClientProbe probe = instance; if (probe != null && probe.state == 4 && event.getEntity() instanceof zone.moddev.mc.basemetals.entity.MaterialProjectile) { - probe.renderedProjectiles.add(event.getEntity().getEntityId()); + probe.renderedProjectiles.add(event.getEntity().getId()); } } @@ -105,25 +132,25 @@ public static void onClientTick(TickEvent.ClientTickEvent event) { private void tick() { Minecraft minecraft = Minecraft.getInstance(); if (stateTicks == 0) LOGGER.info("Client probe state={} screen={}", state, - minecraft.currentScreen == null ? "none" : minecraft.currentScreen.getClass().getName()); + minecraft.screen == null ? "none" : minecraft.screen.getClass().getName()); if (++stateTicks > 3600) fail(minecraft, "Timed out in client probe state " + state); try { - if (state == 0 && minecraft.currentScreen instanceof MainMenuScreen) { + if (state == 0 && minecraft.screen instanceof MainMenuScreen) { if (Boolean.getBoolean("basemetalsclientprobe.login")) { require(BaseMetalsConfig.activeMode().serializedName().equals( System.getProperty("basemetalsclientprobe.expectedMode")), "startup mode differs from profile"); - minecraft.displayGuiScreen(new net.minecraft.client.gui.screen.ConnectingScreen( - minecraft.currentScreen, minecraft, "127.0.0.1", + minecraft.setScreen(new net.minecraft.client.gui.screen.ConnectingScreen( + minecraft.screen, minecraft, "127.0.0.1", Integer.getInteger("basemetalsclientprobe.port", 25565))); nextState(3); return; } validateClientContent(minecraft); - minecraft.launchIntegratedServer(WORLD_DIRECTORY, "Base Metals Client Smoke", - new WorldSettings(0L, GameType.CREATIVE, false, false, WorldType.DEFAULT)); + createWorld(minecraft); nextState(1); - } else if (state == 1 && minecraft.world != null && minecraft.player != null + } else if (state == 1 && minecraft.level != null && minecraft.player != null && renderedFrames >= 8 && stateTicks >= 100) { + validateCreativeBucketScreen(minecraft); validateCreativeCrossbows(minecraft); spawnProjectileRenderChecks(minecraft); nextState(4); @@ -131,7 +158,7 @@ private void tick() { LOGGER.info("BASEMETALS_PROJECTILE_RENDER_PROBE PASS rendered={}", renderedProjectiles.size()); writeMarker(); LOGGER.info("BASEMETALS_CLIENT_PROBE PASS frames={}", Integer.valueOf(renderedFrames)); - minecraft.shutdown(); + minecraft.stop(); nextState(2); } else if (state == 3) { checkLogin(minecraft); @@ -141,13 +168,49 @@ private void tick() { } } + private static void createWorld(Minecraft minecraft) { + if (Boolean.getBoolean("basemetalsclientprobe.reload")) { + minecraft.loadLevel(WORLD_DIRECTORY); + return; + } + + DynamicRegistries.Impl registries = DynamicRegistries.builtin(); + DimensionGeneratorSettings generator = DimensionGeneratorSettings.makeDefault( + registries.registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY), + registries.registryOrThrow(Registry.BIOME_REGISTRY), + registries.registryOrThrow(Registry.NOISE_GENERATOR_SETTINGS_REGISTRY)) + .withSeed(false, java.util.OptionalLong.of(0L)); + WorldSettings settings = new WorldSettings("Base Metals Client Smoke", GameType.CREATIVE, + false, Difficulty.NORMAL, true, new GameRules(), DatapackCodec.DEFAULT); + + minecraft.createLevel(WORLD_DIRECTORY, settings, registries, generator); + } + private static void spawnProjectileRenderChecks(Minecraft minecraft) { - java.util.UUID playerId = minecraft.player.getUniqueID(); - minecraft.getIntegratedServer().execute(() -> { - net.minecraft.entity.player.ServerPlayerEntity player = minecraft.getIntegratedServer() - .getPlayerList().getPlayerByUUID(playerId); - net.minecraft.world.server.ServerWorld world = player.getServerWorld(); - net.minecraft.util.math.Vec3d look = player.getLookVec(); + java.util.UUID playerId = minecraft.player.getUUID(); + minecraft.getSingleplayerServer().execute(() -> { + net.minecraft.entity.player.ServerPlayerEntity player = minecraft.getSingleplayerServer() + .getPlayerList().getPlayer(playerId); + net.minecraft.world.server.ServerWorld world = player.getLevel(); + if (Boolean.getBoolean("basemetalsclientprobe.reload")) { + java.util.Set savedAmmo = new java.util.HashSet<>(); + for (net.minecraft.entity.Entity entity : world.getAllEntities()) { + if (entity instanceof zone.moddev.mc.basemetals.entity.MaterialProjectile) { + savedAmmo.add(((zone.moddev.mc.basemetals.entity.MaterialProjectile) entity) + .getAmmunition().getItem().getRegistryName().toString()); + } + } + for (String material : new String[] {"gold", "steel", "adamantine"}) { + for (String form : new String[] {"arrow", "bolt"}) { + require(savedAmmo.contains("basemetals:" + material + "_" + form), + "projectile identity lost on client-world reload: " + material + " " + form); + } + } + LOGGER.info("BASEMETALS_CLIENT_RELOAD_PROBE PASS ammunition={}", savedAmmo.size()); + return; + } + + net.minecraft.util.math.vector.Vector3d look = player.getLookAngle(); int index = 0; for (String material : new String[] {"gold", "steel", "adamantine"}) { @@ -159,18 +222,18 @@ private static void spawnProjectileRenderChecks(Minecraft minecraft) { : zone.moddev.mc.basemetals.entity.ModEntities.CUSTOM_ARROW.get(), world, player, new ItemStack(ModContent.item(name).get())); net.minecraft.entity.Entity entity = projectile; - entity.setPosition(player.getPosX() + look.x * 3 + (index++ - 2.5) * 0.25, - player.getPosY() + player.getEyeHeight(), player.getPosZ() + look.z * 3); + entity.setPos(player.getX() + look.x * 3 + (index++ - 2.5) * 0.25, + player.getY() + player.getEyeHeight(), player.getZ() + look.z * 3); entity.setNoGravity(true); - world.addEntity(projectile); + world.addFreshEntity(projectile); } } }); } private static void validateCreativeCrossbows(Minecraft minecraft) { - require(minecraft.world.isRemote, "crossbow probe must run on the client"); - require(minecraft.player.abilities.isCreativeMode, "crossbow probe needs a Creative player"); + require(minecraft.level.isClientSide, "crossbow probe must run on the client"); + require(minecraft.player.abilities.instabuild, "crossbow probe needs a Creative player"); int shots = 0; for (String name : ModContent.itemsById().keySet()) { @@ -181,67 +244,67 @@ private static void validateCreativeCrossbows(Minecraft minecraft) { new ItemStack(ModContent.item(boltName).get())}; for (ItemStack supply : ammunition) { - minecraft.player.inventory.clear(); + minecraft.player.inventory.clearContent(); ItemStack launcher = new ItemStack(ModContent.item(name).get()); ItemStack original = supply.copy(); - minecraft.player.setHeldItem(Hand.MAIN_HAND, launcher); - minecraft.player.setHeldItem(Hand.OFF_HAND, supply); + minecraft.player.setItemInHand(Hand.MAIN_HAND, launcher); + minecraft.player.setItemInHand(Hand.OFF_HAND, supply); BowItem crossbow = (BowItem) launcher.getItem(); - crossbow.onPlayerStoppedUsing(launcher, minecraft.world, minecraft.player, + crossbow.releaseUsing(launcher, minecraft.level, minecraft.player, crossbow.getUseDuration(launcher) - 20); - require(ItemStack.areItemStacksEqual(original, supply), + require(ItemStack.matches(original, supply), "Creative crossbow consumed client ammunition: " + name); - require(launcher.getDamage() == 0, "Creative crossbow wore out on the client: " + name); + require(launcher.getDamageValue() == 0, "Creative crossbow wore out on the client: " + name); shots++; } } - minecraft.player.inventory.clear(); + minecraft.player.inventory.clearContent(); require(shots == 108, "all 27 crossbows need four client ammunition cases"); LOGGER.info("BASEMETALS_CLIENT_CROSSBOWS PASS shots={}", Integer.valueOf(shots)); } private void checkLogin(Minecraft minecraft) throws IOException { boolean rejected = Boolean.getBoolean("basemetalsclientprobe.expectReject"); - if (minecraft.world != null && minecraft.player != null && stateTicks > 60) { + if (minecraft.level != null && minecraft.player != null && stateTicks > 60) { require(!rejected, "mismatched modes entered gameplay"); - net.minecraft.item.crafting.IRecipe bow = minecraft.world.getRecipeManager().getRecipe(new ResourceLocation("basemetals", "tin_bow")).orElse(null); + net.minecraft.item.crafting.IRecipe bow = minecraft.level.getRecipeManager().byKey(new ResourceLocation("basemetals", "tin_bow")).orElse(null); require(bow != null, "recipe IDs changed during mode synchronization"); - require(bow.isDynamic() == (BaseMetalsConfig.activeMode() == ContentMode.LOW_FANTASY), + require(bow.isSpecial() == (BaseMetalsConfig.activeMode() == ContentMode.LOW_FANTASY), "client recipe policy did not match the server"); if (Boolean.getBoolean("basemetalsclientprobe.modeSwitch")) { - require(minecraft.player.getRecipeBook().isUnlocked(bow) + require(minecraft.player.getRecipeBook().contains(bow) == (BaseMetalsConfig.activeMode() == ContentMode.HIGH_FANTASY), "earned recipe-book unlock did not survive the mode change"); for (String name : new String[] {"steel_bow", "adamantine_bow", "adamantine_crossbow", "adamantine_gear", "adamantine_arrow", "adamantine_rod", "adamantine_pickaxe"}) { - net.minecraft.item.crafting.IRecipe recipe = minecraft.world.getRecipeManager() - .getRecipe(new ResourceLocation("basemetals", name)).orElse(null); - require(minecraft.player.getRecipeBook().isUnlocked(recipe) != recipe.isDynamic(), + net.minecraft.item.crafting.IRecipe recipe = minecraft.level.getRecipeManager() + .byKey(new ResourceLocation("basemetals", name)).orElse(null); + require(minecraft.player.getRecipeBook().contains(recipe) != recipe.isSpecial(), "material discovery did not synchronize to the client: " + name); } - require(!minecraft.player.getRecipeBook().isUnlocked(minecraft.world.getRecipeManager() - .getRecipe(new ResourceLocation("basemetals", "gold_bow")).orElse(null)), + require(!minecraft.player.getRecipeBook().contains(minecraft.level.getRecipeManager() + .byKey(new ResourceLocation("basemetals", "gold_bow")).orElse(null)), "mode change unlocked an unearned recipe"); } - require(minecraft.player.inventory.getStackInSlot(0).getItem() == ModContent.item("tin_bow").get(), + require(minecraft.player.inventory.getItem(0).getItem() == ModContent.item("tin_bow").get(), "existing restricted item did not synchronize"); finishLogin(minecraft, "connected"); - } else if (minecraft.currentScreen instanceof net.minecraft.client.gui.screen.DisconnectedScreen) { + } else if (minecraft.screen instanceof net.minecraft.client.gui.screen.DisconnectedScreen) { StringBuilder message = new StringBuilder(); try { - for (java.lang.reflect.Field field : minecraft.currentScreen.getClass().getDeclaredFields()) { + for (java.lang.reflect.Field field : minecraft.screen.getClass().getDeclaredFields()) { if (net.minecraft.util.text.ITextComponent.class.isAssignableFrom(field.getType())) { field.setAccessible(true); - message.append(((net.minecraft.util.text.ITextComponent) field.get(minecraft.currentScreen)).getString()); + message.append(((net.minecraft.util.text.ITextComponent) field.get(minecraft.screen)).getString()); } } } catch (ReflectiveOperationException failure) { throw new IllegalStateException(failure); } - require(rejected && minecraft.world == null, "unexpected disconnect: " + message); + require(rejected && minecraft.level == null, "unexpected disconnect: " + message); require(message.toString().contains("Base Metals") && message.toString().contains("High Fantasy") && message.toString().contains("Low Fantasy") && message.toString().contains("Config"), "disconnect did not explain the mode mismatch: " + message); @@ -258,7 +321,7 @@ private void finishLogin(Minecraft minecraft, String result) throws IOException marker.store(output, "Base Metals content-mode login test"); } LOGGER.info("BASEMETALS_MODE_LOGIN_CLIENT PASS result={} mode={}", result, BaseMetalsConfig.activeMode()); - minecraft.shutdown(); + minecraft.stop(); nextState(2); } @@ -278,14 +341,15 @@ private static void validateClientContent(Minecraft minecraft) throws IOExceptio require(ModContent.fluids().size() == 36, "fluid catalogue size"); require(ForgeRegistries.ITEMS.containsKey(new ResourceLocation("basemetals", "mercury_bucket")), "mercury bucket registration"); + validateCreativeBuckets(minecraft); for (FluidContent fluid : ModContent.fluids().values()) { - require(fluid.bucket().get().getGroup() == ModTabs.ITEMS, "bucket creative group"); - require(RenderTypeLookup.canRenderInLayer(fluid.block().get().getDefaultState(), - RenderType.getTranslucent()), "molten block render layer"); - require(RenderTypeLookup.canRenderInLayer(fluid.source().get().getDefaultState(), - RenderType.getTranslucent()), "molten source render layer"); - require(RenderTypeLookup.canRenderInLayer(fluid.flowing().get().getDefaultState(), - RenderType.getTranslucent()), "molten flowing render layer"); + require(fluid.bucket().get().getItemCategory() == ModTabs.ITEMS, "bucket creative group"); + require(RenderTypeLookup.canRenderInLayer(fluid.block().get().defaultBlockState(), + RenderType.translucent()), "molten block render layer"); + require(RenderTypeLookup.canRenderInLayer(fluid.source().get().defaultFluidState(), + RenderType.translucent()), "molten source render layer"); + require(RenderTypeLookup.canRenderInLayer(fluid.flowing().get().defaultFluidState(), + RenderType.translucent()), "molten flowing render layer"); validateBucketModel(minecraft, fluid.bucket().get().getRegistryName().getPath()); } for (net.minecraft.block.Block block : ForgeRegistries.BLOCKS) { @@ -293,11 +357,12 @@ private static void validateClientContent(Minecraft minecraft) throws IOExceptio if (block instanceof net.minecraft.block.DoorBlock || block instanceof net.minecraft.block.TrapDoorBlock || block instanceof net.minecraft.block.PaneBlock) { - require(RenderTypeLookup.canRenderInLayer(block.getDefaultState(), - RenderType.getCutoutMipped()), "transparent block render layer: " + block); + require(RenderTypeLookup.canRenderInLayer(block.defaultBlockState(), + RenderType.cutoutMipped()), "transparent block render layer: " + block); } } validateOreModels(minecraft); + validateWallModels(minecraft); minecraft.getResourceManager().getResource( new ResourceLocation("basemetals", "textures/item/adamantine_sword.png")); minecraft.getResourceManager().getResource( @@ -305,38 +370,38 @@ private static void validateClientContent(Minecraft minecraft) throws IOExceptio } private static void validateConfigScreen(Minecraft minecraft) { - Screen parent = minecraft.currentScreen; + Screen parent = minecraft.screen; boolean originalEffects = BaseMetalsConfig.SPECIAL_EFFECTS.get(); String originalMode = BaseMetalsConfig.CONTENT_MODE.get(); ContentMode activeMode = BaseMetalsConfig.activeMode(); ModListScreen modsScreen = new ModListScreen(parent); - minecraft.displayGuiScreen(modsScreen); + minecraft.setScreen(modsScreen); int baseMetalsIndex = -1; for (int index = 0; index < ModList.get().getMods().size(); index++) { if ("basemetals".equals(ModList.get().getMods().get(index).getModId())) baseMetalsIndex = index; } require(baseMetalsIndex >= 0, "Base Metals is missing from the Mods list"); selectMod(modsScreen, "basemetals"); - ForgeHooksClient.drawScreen(modsScreen, 0, 0, 0.0F); + ForgeHooksClient.drawScreen(modsScreen, new MatrixStack(), 0, 0, 0.0F); Button configButton = button(modsScreen, 20); require(configButton.active, "Base Metals Config button is disabled in the Mods list"); require(modsScreen.mouseClicked(configButton.x + 1, configButton.y + 1, 0), "Mods-list Config button did not accept a mouse click"); - Screen forgeScreen = minecraft.currentScreen; + Screen forgeScreen = minecraft.screen; require(forgeScreen instanceof BaseMetalsConfigScreen, "Mods-list button opened the wrong screen"); require(forgeScreen.children().size() == 9, "config screen must expose five settings and four actions"); click(forgeScreen, 20); click(forgeScreen, 10); click(forgeScreen, 1); - require(minecraft.currentScreen == modsScreen, "Cancel did not return to the Mods list"); + require(minecraft.screen == modsScreen, "Cancel did not return to the Mods list"); require(originalMode.equals(BaseMetalsConfig.CONTENT_MODE.get()) && originalEffects == BaseMetalsConfig.SPECIAL_EFFECTS.get(), "Cancel changed the loaded config"); - ForgeHooksClient.drawScreen(modsScreen, 0, 0, 0.0F); + ForgeHooksClient.drawScreen(modsScreen, new MatrixStack(), 0, 0, 0.0F); require(button(modsScreen, 20).active, "Config button was disabled on returning from the settings"); modsScreen.setSelected(null); - ForgeHooksClient.drawScreen(modsScreen, 0, 0, 0.0F); + ForgeHooksClient.drawScreen(modsScreen, new MatrixStack(), 0, 0, 0.0F); require(!button(modsScreen, 20).active, "Config button stayed enabled with no mod selected"); int forgeIndex = -1; for (int index = 0; index < ModList.get().getMods().size(); index++) { @@ -344,33 +409,33 @@ private static void validateConfigScreen(Minecraft minecraft) { } require(forgeIndex >= 0, "Forge is missing from the Mods list"); selectMod(modsScreen, "forge"); - ForgeHooksClient.drawScreen(modsScreen, 0, 0, 0.0F); + ForgeHooksClient.drawScreen(modsScreen, new MatrixStack(), 0, 0, 0.0F); require(!button(modsScreen, 20).active, "Base Metals enabled the Config button for Forge"); click(modsScreen, 6); - require(minecraft.currentScreen == parent, "Mods-list Done did not return to the main menu"); + require(minecraft.screen == parent, "Mods-list Done did not return to the main menu"); Screen oreSpawnScreen = WorldSettingsExtensionRegistry.extensions().stream() .filter(extension -> "basemetals:configuration".equals(extension.id().toString())) .findFirst().get().createScreen(parent); require(oreSpawnScreen instanceof BaseMetalsConfigScreen, "OreSpawn factory uses the wrong screen"); - minecraft.displayGuiScreen(oreSpawnScreen); + minecraft.setScreen(oreSpawnScreen); click(oreSpawnScreen, 10); click(oreSpawnScreen, 20); click(oreSpawnScreen, 2); - require(button(oreSpawnScreen, 20).getMessage().contains(I18n.format(ContentMode.HIGH_FANTASY.translationKey())), + require(button(oreSpawnScreen, 20).getMessage().getString().contains(I18n.get(ContentMode.HIGH_FANTASY.translationKey())), "Defaults did not restore High Fantasy"); click(oreSpawnScreen, 3); - require(button(oreSpawnScreen, 10).getMessage().endsWith(I18n.format(originalEffects ? "options.on" : "options.off")), + require(button(oreSpawnScreen, 10).getMessage().getString().endsWith(I18n.get(originalEffects ? "options.on" : "options.off")), "Undo did not restore the original boolean"); click(oreSpawnScreen, 20); click(oreSpawnScreen, 0); - require(minecraft.currentScreen instanceof ConfirmScreen, "Mode change has no restart confirmation"); + require(minecraft.screen instanceof ConfirmScreen, "Mode change has no restart confirmation"); confirmation(minecraft, false); - require(minecraft.currentScreen == oreSpawnScreen, "Declining confirmation lost pending edits"); + require(minecraft.screen == oreSpawnScreen, "Declining confirmation lost pending edits"); require(BaseMetalsConfig.CONTENT_MODE.get().equals(originalMode), "Declining confirmation saved the mode"); click(oreSpawnScreen, 0); confirmation(minecraft, true); - require(minecraft.currentScreen == parent, "Done did not return to the OreSpawn parent"); + require(minecraft.screen == parent, "Done did not return to the OreSpawn parent"); require(!BaseMetalsConfig.CONTENT_MODE.get().equals(originalMode), "Done did not save the string property"); require(BaseMetalsConfig.activeMode() == activeMode, "GUI changes altered the startup-latched mode"); @@ -378,7 +443,7 @@ private static void validateConfigScreen(Minecraft minecraft) { BaseMetalsConfig.set(BaseMetalsConfig.CONTENT_MODE, originalMode); BaseMetalsConfig.set(BaseMetalsConfig.SPECIAL_EFFECTS, originalEffects); BaseMetalsConfig.save(); - require(!I18n.format("config.basemetals.title").startsWith("config."), "GUI title is untranslated"); + require(!I18n.get("config.basemetals.title").startsWith("config."), "GUI title is untranslated"); LOGGER.info("BASEMETALS_CONFIG_GUI_PROBE PASS entries=5 factories=2 mods_button=true cancel=true undo=true defaults=true save=true"); } @@ -398,7 +463,7 @@ private static void selectMod(ModListScreen screen, String modId) { private static void confirmation(Minecraft minecraft, boolean accepted) { List