From 6760b5443f87757ddeda8b99d2a84dbefc22bdf3 Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Wed, 21 Oct 2026 19:27:51 +0100 Subject: [PATCH] Bring Base Metals to Minecraft 1.20.1 Keep the existing features, update optional integrations and prevent an intermittent Forge login stall. Refresh the build, release checks and Eclipse launches for Forge 47. --- .github/workflows/ci.yml | 40 ++-- .github/workflows/codeql-analysis.yml | 4 +- .github/workflows/validate-gradle-build.yml | 4 +- .gitignore | 4 +- CHANGELOG.txt | 11 ++ README.md | 40 ++-- build.gradle | 10 +- ci-fixtures/tools/README.md | 6 +- ...craft-mavenizer-0.5.21-orespawn-compat.jar | Bin 2567344 -> 2567353 bytes .../tools/minecraft-source-compatibility.json | 18 +- docs/COMPATIBILITY.md | 31 ++- docs/MIGRATION.md | 4 +- docs/VERSIONS.md | 14 +- docs/WORLDGEN.md | 6 +- docs/schemas/orespawn-4.1.0/README.md | 4 +- gradle.properties | 22 +-- gradle/README.md | 21 +- gradle/dependencies.gradle | 95 ++++++++-- gradle/ide/eclipse.gradle | 43 +++-- gradle/optional-mods.json | 58 ++++++ gradle/prepare-forge-client-runtime.py | 8 +- gradle/release/artifacts.gradle | 28 +-- gradle/verification/packaged-runtime.gradle | 36 ++-- .../mc/basemetals/data/GeneratedData.java | 47 ++--- .../content/BaseMetalsGameTests.java | 10 +- .../enderio/adamantine/ingot_sag_milling.json | 10 +- .../enderio/adamantine/ore_sag_milling.json | 12 +- .../compat/enderio/alloys/aquarium.json | 4 +- .../recipes/compat/enderio/alloys/brass.json | 4 +- .../recipes/compat/enderio/alloys/bronze.json | 4 +- .../compat/enderio/alloys/cupronickel.json | 4 +- .../compat/enderio/alloys/electrum.json | 4 +- .../recipes/compat/enderio/alloys/invar.json | 4 +- .../compat/enderio/alloys/mithril.json | 4 +- .../recipes/compat/enderio/alloys/pewter.json | 4 +- .../recipes/compat/enderio/alloys/steel.json | 4 +- .../enderio/antimony/ingot_sag_milling.json | 10 +- .../enderio/antimony/ore_sag_milling.json | 12 +- .../enderio/aquarium/ingot_sag_milling.json | 10 +- .../enderio/bismuth/ingot_sag_milling.json | 10 +- .../enderio/bismuth/ore_sag_milling.json | 12 +- .../enderio/brass/ingot_sag_milling.json | 10 +- .../enderio/bronze/ingot_sag_milling.json | 10 +- .../enderio/coldiron/ingot_sag_milling.json | 10 +- .../enderio/coldiron/ore_sag_milling.json | 12 +- .../enderio/copper/ingot_sag_milling.json | 10 +- .../enderio/copper/ore_sag_milling.json | 12 +- .../cupronickel/ingot_sag_milling.json | 10 +- .../enderio/electrum/ingot_sag_milling.json | 10 +- .../enderio/invar/ingot_sag_milling.json | 10 +- .../enderio/lead/ingot_sag_milling.json | 10 +- .../compat/enderio/lead/ore_sag_milling.json | 12 +- .../enderio/mercury/ingot_sag_milling.json | 10 +- .../enderio/mercury/ore_sag_milling.json | 12 +- .../enderio/mithril/ingot_sag_milling.json | 10 +- .../enderio/nickel/ingot_sag_milling.json | 10 +- .../enderio/nickel/ore_sag_milling.json | 12 +- .../enderio/pewter/ingot_sag_milling.json | 10 +- .../enderio/platinum/ingot_sag_milling.json | 10 +- .../enderio/platinum/ore_sag_milling.json | 12 +- .../enderio/silver/ingot_sag_milling.json | 10 +- .../enderio/silver/ore_sag_milling.json | 12 +- .../enderio/starsteel/ingot_sag_milling.json | 10 +- .../enderio/starsteel/ore_sag_milling.json | 12 +- .../enderio/steel/ingot_sag_milling.json | 10 +- .../compat/enderio/tin/ingot_sag_milling.json | 10 +- .../compat/enderio/tin/ore_sag_milling.json | 12 +- .../enderio/zinc/ingot_sag_milling.json | 10 +- .../compat/enderio/zinc/ore_sag_milling.json | 12 +- .../data/basemetals/registry_manifest.json | 2 +- .../tinkering/materials/stats/adamantine.json | 1 - .../tinkering/materials/stats/antimony.json | 1 - .../tinkering/materials/stats/aquarium.json | 1 - .../tinkering/materials/stats/bismuth.json | 1 - .../tinkering/materials/stats/brass.json | 1 - .../tinkering/materials/stats/bronze.json | 1 - .../tinkering/materials/stats/coldiron.json | 1 - .../tinkering/materials/stats/copper.json | 1 - .../materials/stats/cupronickel.json | 1 - .../tinkering/materials/stats/electrum.json | 1 - .../tinkering/materials/stats/invar.json | 1 - .../tinkering/materials/stats/lead.json | 1 - .../tinkering/materials/stats/mithril.json | 1 - .../tinkering/materials/stats/nickel.json | 1 - .../tinkering/materials/stats/pewter.json | 1 - .../tinkering/materials/stats/platinum.json | 1 - .../tinkering/materials/stats/silver.json | 1 - .../tinkering/materials/stats/starsteel.json | 1 - .../tinkering/materials/stats/steel.json | 1 - .../tinkering/materials/stats/tin.json | 1 - .../tinkering/materials/stats/zinc.json | 1 - .../smoke/IntegratedWorldSmoke.java | 18 +- .../zone/moddev/mc/basemetals/BaseMetals.java | 10 +- .../mc/basemetals/BaseMetalsEvents.java | 4 +- .../zone/moddev/mc/basemetals/ModTabs.java | 30 +-- .../client/BaseMetalsConfigScreen.java | 12 +- .../mc/basemetals/compat/MekanismCompat.java | 6 +- .../mc/basemetals/content/ModContent.java | 22 ++- .../mc/basemetals/content/ScytheItem.java | 6 +- .../loot/AppendLootTableModifier.java | 2 +- .../network/ContentModeNetwork.java | 5 +- src/main/resources/META-INF/coremods.json | 3 +- src/main/resources/META-INF/mods.toml | 10 +- .../coremods/connection_read_interest.js | 90 +++++++++ .../tags/blocks/scythe_harvestable.json | 2 +- src/main/resources/pack.mcmeta | 4 +- .../mc/basemetals/smoke/RemoteLoginSmoke.java | 6 +- .../packaged-resources/META-INF/mods.toml | 4 +- .../basemetals/smoke/PackagedServerSmoke.java | 11 +- .../packaged-resources/META-INF/mods.toml | 4 +- src/smoke/datapack/pack.mcmeta | 2 +- .../data/IntegrationContentPolicyTest.java | 4 +- .../data/Minecraft1201ResourceTest.java | 65 +++++++ .../data/ResourceIntegrityTest.java | 2 +- .../network/ConnectionReadInterestTest.java | 179 ++++++++++++++++++ tools/validation/README.md | 2 +- tools/validation/test_content_modes.js | 12 +- tools/validation/test_legacy_worlds.js | 7 +- 118 files changed, 1088 insertions(+), 433 deletions(-) create mode 100644 gradle/optional-mods.json create mode 100644 src/main/resources/coremods/connection_read_interest.js rename src/{generated => main}/resources/data/basemetals/tags/blocks/scythe_harvestable.json (99%) create mode 100644 src/test/java/zone/moddev/mc/basemetals/data/Minecraft1201ResourceTest.java create mode 100644 src/test/java/zone/moddev/mc/basemetals/network/ConnectionReadInterestTest.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc0b3a4c7..0d8ad250b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,19 +1,19 @@ -name: Base Metals 1.19.4 CI +name: Base Metals 1.20.1 CI on: push: branches: - - master-1.19.4 + - master-1.20.1 - 'feature/**' pull_request: branches: - - master-1.19.4 + - master-1.20.1 permissions: contents: read concurrency: - group: basemetals-1.19.4-${{ github.workflow }}-${{ github.ref }} + group: basemetals-1.20.1-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: @@ -50,9 +50,9 @@ jobs: --no-daemon --stacktrace first="$RUNNER_TEMP/basemetals-first" mkdir -p "$first" - cp build/libs/BaseMetals-3.1.0.119041.jar "$first/" - cp build/libs/BaseMetals-3.1.0.119041-sources.jar "$first/" - cp build/libs/BaseMetals-3.1.0.119041-javadoc.jar "$first/" + cp build/libs/BaseMetals-3.1.0.120011.jar "$first/" + cp build/libs/BaseMetals-3.1.0.120011-sources.jar "$first/" + cp build/libs/BaseMetals-3.1.0.120011-javadoc.jar "$first/" cp build/release/SHA256SUMS "$first/" - name: Rebuild and prove byte-identical archives @@ -60,9 +60,9 @@ jobs: ./gradlew clean assemble verifyReleaseArtifacts writeReleaseChecksums \ -PorespawnVerificationRepository="${{ steps.orespawn.outputs.repository }}" \ --no-daemon --stacktrace - cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.1.0.119041.jar" build/libs/BaseMetals-3.1.0.119041.jar - cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.1.0.119041-sources.jar" build/libs/BaseMetals-3.1.0.119041-sources.jar - cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.1.0.119041-javadoc.jar" build/libs/BaseMetals-3.1.0.119041-javadoc.jar + cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.1.0.120011.jar" build/libs/BaseMetals-3.1.0.120011.jar + cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.1.0.120011-sources.jar" build/libs/BaseMetals-3.1.0.120011-sources.jar + cmp "$RUNNER_TEMP/basemetals-first/BaseMetals-3.1.0.120011-javadoc.jar" build/libs/BaseMetals-3.1.0.120011-javadoc.jar cmp "$RUNNER_TEMP/basemetals-first/SHA256SUMS" build/release/SHA256SUMS - name: Run all required GameTests @@ -74,14 +74,14 @@ jobs: - name: Install official Forge packaged runtimes id: forge-runtimes run: | - server_runtime="$RUNNER_TEMP/forge-1.19.4-45.4.0-server" - client_runtime="$RUNNER_TEMP/forge-1.19.4-45.4.0-client" - installer="$RUNNER_TEMP/forge-1.19.4-45.4.0-installer.jar" + server_runtime="$RUNNER_TEMP/forge-1.20.1-47.4.10-server" + client_runtime="$RUNNER_TEMP/forge-1.20.1-47.4.10-client" + installer="$RUNNER_TEMP/forge-1.20.1-47.4.10-installer.jar" mkdir -p "$server_runtime" "$client_runtime" curl --fail --location --retry 3 \ --output "$installer" \ - https://maven.minecraftforge.net/net/minecraftforge/forge/1.19.4-45.4.0/forge-1.19.4-45.4.0-installer.jar - echo "6643bb8c21f409cc528ff656daf60c206d304cc77bf529bbbab4b336004dfc53 $installer" | sha256sum --check --strict + https://maven.minecraftforge.net/net/minecraftforge/forge/1.20.1-47.4.10/forge-1.20.1-47.4.10-installer.jar + echo "1912760B4CB6B803D8A826DE603C9076B1DA71EC2765E9A1F8C1CA78F65278E3 $installer" | sha256sum --check --strict ( cd "$server_runtime" java -jar "$installer" --installServer @@ -124,13 +124,13 @@ jobs: - name: Upload audited release candidate uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: BaseMetals-1.19.4-${{ github.sha }} + name: BaseMetals-1.20.1-${{ github.sha }} if-no-files-found: error retention-days: 30 path: | - build/libs/BaseMetals-3.1.0.119041.jar - build/libs/BaseMetals-3.1.0.119041-sources.jar - build/libs/BaseMetals-3.1.0.119041-javadoc.jar + build/libs/BaseMetals-3.1.0.120011.jar + build/libs/BaseMetals-3.1.0.120011-sources.jar + build/libs/BaseMetals-3.1.0.120011-javadoc.jar build/release/SHA256SUMS CHANGELOG.txt @@ -138,7 +138,7 @@ jobs: if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: BaseMetals-1.19.4-diagnostics-${{ github.sha }} + name: BaseMetals-1.20.1-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 7b035dabb..a1a0092f8 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -3,11 +3,11 @@ name: CodeQL on: push: branches: - - master-1.19.4 + - master-1.20.1 - 'feature/**' pull_request: branches: - - master-1.19.4 + - master-1.20.1 permissions: actions: read diff --git a/.github/workflows/validate-gradle-build.yml b/.github/workflows/validate-gradle-build.yml index 2d52c08f6..63abc33fb 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.19.4 + - master-1.20.1 - 'feature/**' pull_request: branches: - - master-1.19.4 + - master-1.20.1 permissions: contents: read diff --git a/.gitignore b/.gitignore index b2568b2de..378855845 100644 --- a/.gitignore +++ b/.gitignore @@ -55,8 +55,8 @@ classes !/libs/README.txt .DS_Store -Thumbs.db -Desktop.ini +[Tt]humbs.db +[Dd]esktop.ini # python __pycache__/ diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 08ccd1e44..f6b1a1958 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,14 @@ +Base Metals 3.1.0.120011 + +- Port the 1.19.4 version to Minecraft 1.20.1 and Forge 47.4.10. +- Keep raw metals, Kiri's ore overlays, filled buckets, High/Low Fantasy modes and the earlier fixes. +- Update creative tabs, configuration drawing, loot and networking for the newer APIs. +- Fix an intermittent Forge login stall by keeping socket read changes on Netty's event loop. +- Use the published OreSpawn 4.1.0.120011 release, with the same ore rules and copper defaults. +- Update optional integrations for their 1.20.1 APIs and keep them in a separate development profile. +- Preserve existing blocks, items, configuration keys and old-world conversion backups. +- Build sources and Javadocs as UTF-8 on every platform. + Base Metals 3.1.0.119041 - Port the 1.18 version to Minecraft 1.19.4 and Forge 45.4.0. diff --git a/README.md b/README.md index 45f7142a9..1a4aeadf5 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.19.4)](https://github.com/MinecraftModDevelopmentMods/BaseMetals/actions/workflows/ci.yml?query=branch%3Amaster-1.19.4) +[![Build, test, and audit](https://github.com/MinecraftModDevelopmentMods/BaseMetals/actions/workflows/ci.yml/badge.svg?branch=master-1.20.1)](https://github.com/MinecraftModDevelopmentMods/BaseMetals/actions/workflows/ci.yml?query=branch%3Amaster-1.20.1) # Base Metals @@ -12,21 +12,21 @@ shields, bows, crossbows, arrows, and bolts. The mod also includes Crack Hammers, scythes, metal anvils, the human detector, villager trades, advancements, and structure loot. -This is the Minecraft 1.19.4 Forge version, `3.1.0.119041`. It brings over the -fixes and features from 1.18, including raw metals, ore texture +This is the Minecraft 1.20.1 Forge version, `3.1.0.120011`. It keeps the +fixes and features from 1.19.4, including raw metals, ore texture overlays and High/Low Fantasy modes. Existing Base Metals items and blocks keep their registry IDs. OreSpawn handles ore generation; MMDLib and Additional Loot Tables are no longer needed. ## Requirements -- Minecraft `1.19.4` -- Forge `45.4.0` or newer in the Minecraft 1.19.4 Forge 45 line -- Java 17 -- OreSpawn `[4.1.0.119041,5.0.0)` on both client and server +- Minecraft `1.20.1` +- Forge `47.4.10` or newer in the Minecraft 1.20.1 Forge 47 line +- A current Java 17 runtime +- OreSpawn `[4.1.0.120011,5.0.0)` on both client and server -This version uses OreSpawn `4.1.0.119041`. Put -`BaseMetals-3.1.0.119041.jar` and a compatible OreSpawn JAR in the `mods` +This version uses OreSpawn `4.1.0.120011`. Put +`BaseMetals-3.1.0.120011.jar` and a compatible OreSpawn JAR in the `mods` directory. MMDLib and Additional Loot Tables must not be carried forward from an older installation. Mineralogy is optional. @@ -134,10 +134,13 @@ original. Read [Migration](docs/MIGRATION.md) before starting an upgraded world. Forge item, block, and fluid tags are Base Metals' public compatibility API. -The integration data from 1.18 is retained, but Mekanism, Thermal Expansion, -Tinkers' Construct and Ender IO do not currently publish Forge 1.19.4 builds. -Those integrations are not qualified for this version. Use mods built for -Minecraft 1.19.4; jars for 1.19.2 or 1.20.1 cannot be substituted. +Integration data is included for Mekanism, Thermal Expansion, Tinkers' Construct +and Ender IO. These mods are optional. Use their Minecraft 1.20.1 builds; jars +for other Minecraft versions cannot be substituted. + +The normal development client loads only Base Metals and OreSpawn. Use the +separate optional-mod client profile when working on integrations. Its exact +dependencies and checksums are listed in `gradle/optional-mods.json`. See [Compatibility](docs/COMPATIBILITY.md) for the supported processing chains, tag aliases, copper rules, and intentionally retired legacy integrations. See @@ -147,8 +150,9 @@ contracts. ## Building and contributing Base Metals uses ForgeGradle `7.0.34`, Gradle `9.6.1`, official Minecraft -mappings, and the Java 17 toolchain. A normal command-line build resolves the -qualified OreSpawn release from CurseMaven: +mappings, and the Java 17 toolchain. Initial Minecraft setup also uses Java 25 +for Mavenizer; Minecraft and the mod still run on Java 17. A normal command-line +build resolves the published OreSpawn release from CurseMaven: ```text ./gradlew clean check build javadoc @@ -162,7 +166,9 @@ Generate and verify Eclipse launches with: Use **runClient** for normal testing with Base Metals and OreSpawn only. **runClientOptionalMods** uses a separate `run-optional-mods` folder for -compatibility testing. No optional mods are added automatically on 1.19.4. +compatibility testing with Mekanism, Mantle, Tinkers' Construct and CoFH Core. +Thermal and Ender IO are tested with packaged jars instead: their embedded +dependencies cannot be remapped by the development launcher. Both profiles are available in Eclipse and through Gradle tasks. Use the Gradle **generateResources** task when changing recipes, models or @@ -177,7 +183,7 @@ artifacts. GameTests, smoke probes, migration fixtures, and reference sources are isolated from the published JAR. Release artifacts use Maven coordinate -`zone.moddev.mc.basemetals:BaseMetals:3.1.0.119041`. +`zone.moddev.mc.basemetals:BaseMetals:3.1.0.120011`. Report defects and compatibility problems through the [Base Metals issue tracker](https://github.com/MinecraftModDevelopmentMods/BaseMetals/issues). diff --git a/build.gradle b/build.gradle index 65354a4c7..169c10011 100644 --- a/build.gradle +++ b/build.gradle @@ -10,11 +10,11 @@ plugins { ext.mavenizerCompatibilityJar = layout.projectDirectory.file( 'ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar') ext.mavenizerCompatibilitySha256 = - '341B3FB10ABA6EA7DC612FBE519DC290A1C81867AED5DD320ACD783688AC49DE' + '416DCA4797BC55826F0DDCF03FDC405F1990FC15C2133FA27A1F143BF7976177' ext.mavenizerFixtureChecksums = [ 'minecraft-mavenizer-0.5.21-orespawn-compat.jar': mavenizerCompatibilitySha256, - 'minecraft-source-compatibility.json': '33A3217261EBB8F37D45F50BD2F8CE70B221C014A5C15F4C5F8F9091AD6D1EC8', + 'minecraft-source-compatibility.json': '8945511DFF53B8F1A91EE44B14AF9EC7DB34C373617EA37A608C6958BFEEB22E', 'minecraft-mavenizer-0.5.21-orespawn-compat.patch': 'D856190DEF5574716C5E4F4379912C451B1F5952B0B111576651A98EECF58544', 'LICENSE-MAVENIZER.txt': '20C17D8B8C48A600800DFD14F95D5CB9FF47066A9641DDEAB48DC54AEC96E331' ] @@ -28,7 +28,7 @@ mavenizerFixtureChecksums.each { name, expected -> } // OreSpawn's sealed Mavenizer fixture fixes duplicate record accessors in -// Forge 45's decompiled sources. It is build tooling, not part of the mod. +// Forge 47's decompiled sources. It is build tooling, not part of the mod. fgtools.configure('mavenizer') { classpath.from(mavenizerCompatibilityJar) mainClass.set('net.minecraftforge.mcmaven.cli.Main') @@ -93,6 +93,8 @@ 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) } @@ -236,6 +238,8 @@ minecraft { if (providers.gradleProperty('integratedWorldSmoke').isPresent()) { workingDir.set(layout.projectDirectory.dir('run-integrated-smoke')) systemProperty 'basemetals.integratedWorldSmoke', 'true' + systemProperty 'basemetals.expectedSmokeMods', 'minecraft,forge,basemetals,orespawn' + systemProperty 'basemetals.allowExtraSmokeMods', providers.gradleProperty('compatSmoke').isPresent().toString() systemProperty 'basemetals.integratedWorldSmokeName', providers.gradleProperty('integratedWorldSmokeName').getOrElse('basemetals-integrated-smoke') mods { diff --git a/ci-fixtures/tools/README.md b/ci-fixtures/tools/README.md index 284a2e36e..0dea853bb 100644 --- a/ci-fixtures/tools/README.md +++ b/ci-fixtures/tools/README.md @@ -1,10 +1,10 @@ -# Forge 1.19.4 Mavenizer compatibility fixture +# Forge 1.20.1 Mavenizer compatibility fixture `minecraft-mavenizer-0.5.21-orespawn-compat.jar` is a build-only derivative of MinecraftForge MinecraftMavenizer `0.5.21`, source commit `6968241ce7a0a902cdc1c534b976e8373a423091`. -Forge 45's decompiler emits eight duplicate record accessors in Holder.Direct, +Forge 47's decompiler emits eight duplicate record accessors in Holder.Direct, OptionInstance and MemoryCondition. The target-specific rules in `minecraft-source-compatibility.json` remove only those accessors; Java generates the same public methods automatically. Missing or ambiguous source @@ -22,4 +22,4 @@ It is checksum-verified by the build and excluded from every Base Metals artifac MinecraftMavenizer remains licensed LGPL-2.1-only; see `LICENSE-MAVENIZER.txt`. SHA-256: -`341B3FB10ABA6EA7DC612FBE519DC290A1C81867AED5DD320ACD783688AC49DE` +`416DCA4797BC55826F0DDCF03FDC405F1990FC15C2133FA27A1F143BF7976177` diff --git a/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar b/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar index cc781cdb8e3f5ff5670a33a1e8aa73aa4cbfc4e1..2c068aaec21bdadeeea2ed3bb41182f17ec7cb30 100644 GIT binary patch delta 43079 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 p(T value) implements Holder", "accessor": "\n public T value() {\n return this.value;\n }\n" }, { - "id": "forge-1.19.4-option-alt-enum-value-setter", + "id": "forge-1.20.1-option-alt-enum-value-setter", "file": "net/minecraft/client/OptionInstance.java", "recordDeclaration": "public static record AltEnum(List values, List altValues, BooleanSupplier altCondition, OptionInstance.CycleableValueSet.ValueSetter valueSetter, Codec codec)", "accessor": "\n public OptionInstance.CycleableValueSet.ValueSetter valueSetter() {\n return this.valueSetter;\n }\n" }, { - "id": "forge-1.19.4-option-alt-enum-codec", + "id": "forge-1.20.1-option-alt-enum-codec", "file": "net/minecraft/client/OptionInstance.java", "recordDeclaration": "public static record AltEnum(List values, List altValues, BooleanSupplier altCondition, OptionInstance.CycleableValueSet.ValueSetter valueSetter, Codec codec)", "accessor": "\n public Codec codec() {\n return this.codec;\n }\n" }, { - "id": "forge-1.19.4-option-enum-codec", + "id": "forge-1.20.1-option-enum-codec", "file": "net/minecraft/client/OptionInstance.java", "recordDeclaration": "public static record Enum(List values, Codec codec)", "accessor": "\n public Codec codec() {\n return this.codec;\n }\n" }, { - "id": "forge-1.19.4-option-lazy-enum-codec", + "id": "forge-1.20.1-option-lazy-enum-codec", "file": "net/minecraft/client/OptionInstance.java", "recordDeclaration": "public static record LazyEnum(Supplier> values, Function> validateValue, Codec codec)", "accessor": "\n public Codec codec() {\n return this.codec;\n }\n" }, { - "id": "forge-1.19.4-memory-absent-memory", + "id": "forge-1.20.1-memory-absent-memory", "file": "net/minecraft/world/entity/ai/behavior/declarative/MemoryCondition.java", "recordDeclaration": "public static record Absent(MemoryModuleType memory)", "accessor": "\n public MemoryModuleType memory() {\n return this.memory;\n }\n" }, { - "id": "forge-1.19.4-memory-present-memory", + "id": "forge-1.20.1-memory-present-memory", "file": "net/minecraft/world/entity/ai/behavior/declarative/MemoryCondition.java", "recordDeclaration": "public static record Present(MemoryModuleType memory)", "accessor": "\n public MemoryModuleType memory() {\n return this.memory;\n }\n" }, { - "id": "forge-1.19.4-memory-registered-memory", + "id": "forge-1.20.1-memory-registered-memory", "file": "net/minecraft/world/entity/ai/behavior/declarative/MemoryCondition.java", "recordDeclaration": "public static record Registered(MemoryModuleType memory)", "accessor": "\n public MemoryModuleType memory() {\n return this.memory;\n }\n" diff --git a/docs/COMPATIBILITY.md b/docs/COMPATIBILITY.md index 522ffd630..f21a8083d 100644 --- a/docs/COMPATIBILITY.md +++ b/docs/COMPATIBILITY.md @@ -12,20 +12,26 @@ must remain the Base Metals item. Copper ore and storage tags contain both vanilla and Base Metals entries; Base Metals copper ore smelts to the Base Metals ingot. -The integration data below is carried over from 1.18. These mods do not publish Forge -1.19.4 builds, so their integrations are not qualified on this branch. Do not -load a jar for another Minecraft version to test them. Their APIs are -compile-only dependencies and never enter the normal development runtime. - -- Mekanism 10.2: dirty and clean slurries plus the complete ore-processing +These integrations use Minecraft 1.20.1 builds. Their APIs are compile-only +dependencies and never enter the normal development runtime. The separate +optional-mod profile uses the exact releases and checksums in +`gradle/optional-mods.json`; don't substitute jars for another Minecraft version. +Thermal and Ender IO use packaged-runtime tests rather than the IDE profile, +because their embedded dependencies retain production names after remapping. + +- Mekanism 10.4: dirty and clean slurries plus the complete ore-processing sequence for Adamantine, Antimony, Bismuth, Cold Iron, Nickel, Platinum, Starsteel, and Zinc. The slurry objects are registered only when Mekanism is loaded and use its public API. -- Thermal Expansion 9: pulverizer, furnace, crucible, press, and induction +- Thermal Expansion 11: pulverizer, furnace, crucible, press, and induction smelter recipes. -- Tinkers' Construct 3.7: common tags, melting, casting, alloying, and +- Tinkers' Construct 3.11: common tags, melting, casting, alloying, and data-defined material stats. No removed foreign traits are reimplemented. -- Ender IO 6 alpha: SAG Mill and Alloy Smelter recipes. +- Ender IO 6.2 beta: SAG Mill and Alloy Smelter recipes. + +CoFH Core 11.0.2.56 logs a `MultiPlayerGameModeMixin` warning on dedicated +servers. This also occurs with CoFH Core alone and does not stop the server +loading. IC2, Thaumcraft, Dense Ores, VeinMiner, and Constructs Armory do not have direct integrations in this version. Their generic tools and ore processors can still interoperate @@ -34,6 +40,13 @@ through Forge tags. Mineralogy is entirely optional. Base Metals never links to its Java classes; OreSpawn's rock-family host matching is the only integration point. +## Multiplayer login + +Forge 47 can occasionally stop reading from a connection during login. Base +Metals keeps the protocol's read-pause and read-resume changes on Netty's event +loop to prevent that race. This does not skip any login checks or change the +connection timeout. + ## Low Fantasy Base Metals filters its own crafting, processing, trades and chest additions. diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md index 7174ee9ff..52888c58f 100644 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -2,7 +2,7 @@ Always upgrade a copy and retain the original legacy world. Install Base Metals 3.1.0 and OreSpawn 4.1.0, but do not carry MMDLib, Additional Loot Tables, the -OreSpawn 3 plugin, or the old native fallback generator into the 1.19.4 instance. +OreSpawn 3 plugin, or the old native fallback generator into the 1.20.1 instance. Minecraft's data fixer cannot identify third-party numeric block IDs from before the 1.13 flattening. Base Metals snapshots those positions, runs @@ -58,7 +58,7 @@ upgrade start. Missing mappings also forward: - historical `mmdlib:*` Vanilla Bits blocks, items, and fluids to matching `basemetals:*` IDs. -## Moving from 1.13 through 1.18 +## Moving from 1.13 through 1.19 These worlds already use named block states, so they do not need the numeric ID conversion. Older wall connections are converted to the current wall diff --git a/docs/VERSIONS.md b/docs/VERSIONS.md index 1b839d3d7..cc5f657a8 100644 --- a/docs/VERSIONS.md +++ b/docs/VERSIONS.md @@ -1,16 +1,16 @@ # Supported versions -Base Metals `3.1.0.119041` targets Minecraft 1.19.4, Forge 45.4.0 and Java 17. -OreSpawn `4.1.0.119041` is the exact build and test dependency. Clients and -servers need OreSpawn `[4.1.0.119041,5.0.0)`. +Base Metals `3.1.0.120011` targets Minecraft 1.20.1, Forge 47.4.10 and Java 17. +OreSpawn `4.1.0.120011` is the exact build and test dependency. Clients and +servers need OreSpawn `[4.1.0.120011,5.0.0)`. -The pinned CurseForge artifact is project 245586, file 9171353: -`curse.maven:mmd-orespawn-245586:9171353`. +The pinned CurseForge artifact is project 245586, file 9175609: +`curse.maven:mmd-orespawn-245586:9175609`. Its SHA-256 is -`1B2ECAA546E7FFB35863AC1D9FF92504471EE857045DCD01A068C5CA7F53CFFD`. +`E6BDE47C1BB0BF4D08396F1D1FD5B1D2D3F8A5DF09905F279D39E2601D7FE4B0`. Release artifacts use Maven coordinate -`zone.moddev.mc.basemetals:BaseMetals:3.1.0.119041`. +`zone.moddev.mc.basemetals:BaseMetals:3.1.0.120011`. The provider remains schema 3, revision 1. OreSpawn 4.1 is needed for the configuration-screen API; the provider's eleven rule IDs and 1.18 distributions diff --git a/docs/WORLDGEN.md b/docs/WORLDGEN.md index 8802c1480..88570e01c 100644 --- a/docs/WORLDGEN.md +++ b/docs/WORLDGEN.md @@ -1,7 +1,9 @@ # OreSpawn world generation -Base Metals requires OreSpawn `[4.0.1,5.0.0)` and packages provider schema 3, -revision 1 at `data/basemetals/orespawn/provider.json`. Development, CI, and release qualification use the exact OreSpawn `4.1.0.119041` artifact; the provider is also checked against the vendored 4.1.0 public schema snapshot. +Base Metals requires OreSpawn `[4.1.0.120011,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.120011` release. The provider is checked against the +included 4.1.0 schema. The provider declares ores only: it has no rocks, geomes, biome rules, terrain dimensions, formations, fluid deposits, vanilla suppression, or strata diff --git a/docs/schemas/orespawn-4.1.0/README.md b/docs/schemas/orespawn-4.1.0/README.md index 599fd3319..75bf7d2ca 100644 --- a/docs/schemas/orespawn-4.1.0/README.md +++ b/docs/schemas/orespawn-4.1.0/README.md @@ -1,9 +1,9 @@ # OreSpawn 4.1.0 schemas -These public schemas match OreSpawn 4.1.0.119041. The Base Metals provider +These public schemas match OreSpawn 4.1.0.120011. The Base Metals provider still uses schema 3 and revision 1; its ore rules have not changed. The build validates the provider against this snapshot. OreSpawn 4.1 is also needed for the Base Metals configuration button in its world-creation screen. -Source: [OreSpawn 4.1.0.119041](https://github.com/MinecraftModDevelopmentMods/OreSpawn/releases/tag/4.1.0.119041). +Source: [OreSpawn 4.1.0.120011](https://github.com/MinecraftModDevelopmentMods/OreSpawn/releases/tag/4.1.0.120011). diff --git a/gradle.properties b/gradle.properties index f459a833e..68bb25f73 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,14 +11,14 @@ net.minecraftforge.gradle.merge-source-sets=false ## Environment Properties # The Minecraft version must agree with the Forge version to get a valid artifact -minecraft_version=1.19.4 +minecraft_version=1.20.1 # The Minecraft version range can use any release version of Minecraft as bounds. # Snapshots, pre-releases, and release candidates are not guaranteed to sort properly # as they do not follow standard versioning conventions. -minecraft_version_range=[1.19.4,1.20) +minecraft_version_range=[1.20.1,1.20.2) # The Forge version must agree with the Minecraft version to get a valid artifact -forge_version=45.4.0 -forge_installer_sha256=6643BB8C21F409CC528FF656DAF60C206D304CC77BF529BBBAB4B336004DFC53 +forge_version=47.4.10 +forge_installer_sha256=1912760B4CB6B803D8A826DE603C9076B1DA71EC2765E9A1F8C1CA78F65278E3 # Release metadata consumed by the generic dispatcher. loader_name=forge loader_code=1 @@ -29,14 +29,14 @@ curseforge_project_id=240967 # Exact released OreSpawn used for development and qualification. Runtime # metadata intentionally remains compatible with supported OreSpawn 4 builds. -orespawn_version=4.1.0.119041 +orespawn_version=4.1.0.120011 orespawn_curse_project_id=245586 -orespawn_curse_file_id=9171353 -orespawn_sha256=1B2ECAA546E7FFB35863AC1D9FF92504471EE857045DCD01A068C5CA7F53CFFD +orespawn_curse_file_id=9175609 +orespawn_sha256=E6BDE47C1BB0BF4D08396F1D1FD5B1D2D3F8A5DF09905F279D39E2601D7FE4B0 # The Forge version range can use any version of Forge as bounds or match the loader version range -forge_version_range=[45,) +forge_version_range=[47,) # The loader version range can only use the major version of Forge/FML as bounds -loader_version_range=[45,) +loader_version_range=[47,) # The mapping channel to use for mappings. # The default set of supported mapping channels are ["official", "snapshot", "snapshot_nodoc", "stable", "stable_nodoc"]. # Additional mapping channels can be registered through the "channelProviders" extension in a Gradle plugin. @@ -54,7 +54,7 @@ loader_version_range=[45,) mapping_channel=official # The mapping version to query from the mapping channel. # This must match the format required by the mapping channel. -mapping_version=1.19.4 +mapping_version=1.20.1 ## Mod Properties @@ -67,7 +67,7 @@ mod_name=Base Metals # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=LGPL-2.1 # The mod version. See https://semver.org/ -mod_version=3.1.0.119041 +mod_version=3.1.0.120011 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/gradle/README.md b/gradle/README.md index dc413da19..1c562f1d1 100644 --- a/gradle/README.md +++ b/gradle/README.md @@ -23,12 +23,23 @@ Use `generateResources` after changing them to update the committed resources. source files. Neither the providers nor test probes enter ordinary game launches or public jars. +The scythe's harvestable-block tag lives in `src/main/resources`. Its explicit +block list preserves the older harvesting behaviour, while vanilla crop, +flower, sapling and leaf tags include suitable new plants. + Normal client and server launches use an explicit runtime classpath, not Buildship's full dependency container. Compile-only integration APIs therefore remain available in the editor without loading extra mods into the game. -`runClientOptionalMods` uses a separate folder for optional-mod testing. No -optional mods are added automatically: the inherited integration mods do not -publish builds for Minecraft 1.19.4. The `compatSmoke` and `packagedCompatSmoke` -switches reject those unsupported jars rather than loading another Minecraft -version's mods. +`runClientOptionalMods` uses a separate folder and loads the pinned 1.20.1 +integration mods marked `development` in `gradle/optional-mods.json`. Their downloaded jars +are checked by `verifyOptionalRuntimeDependencies`. Ordinary client and server +launches still load only essential mods. + +Use `compatSmoke` for development-runtime integration tests or +`packagedCompatSmoke` for the packaged server tests. Neither switch is needed +for ordinary development. + +Thermal and Ender IO are covered by packaged client/server tests. Their embedded +dependencies still use SRG names after development remapping, so they are not +included in the IDE profile. diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 58f3ac647..a48d5ee3d 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -1,8 +1,22 @@ +import groovy.json.JsonSlurper +import java.security.MessageDigest + ext.orespawnModule = "mmd-orespawn-${project.orespawn_curse_project_id}" def orespawnVerificationRepository = project.findProperty('orespawnVerificationRepository') configurations.create('packagedCompatibilityMods') -def mantleDevelopment = renamer.dependency('slimeknights.mantle:Mantle:1.19.2-1.10.49') -def tinkersDevelopment = renamer.dependency('slimeknights.tconstruct:TConstruct:1.19.2-3.8.5.63') +configurations.create('optionalModsRelease') +ext.optionalModArtifacts = new JsonSlurper().parse(file('gradle/optional-mods.json')) +ext.optionalDevelopmentArtifacts = optionalModArtifacts.findAll { it.development } +def optionalDevelopmentDependencies = optionalDevelopmentArtifacts.collectEntries { artifact -> + def dependency = renamer.dependency("curse.maven:${artifact.module}:${artifact.file}") { + // CoFH uses the valid *() wildcard, which Renamer cannot parse. + // Keep its AT file intact; Forge remaps SRG access rules when loading it. + if (artifact.modId == 'cofh_core') { + accessTransformers.set(false) + } + } + [(artifact.modId): dependency] +} repositories { minecraft.mavenizer(it) maven fg.forgeMaven @@ -28,6 +42,17 @@ repositories { } } + exclusiveContent { + forRepository { + maven { + name = 'OptionalModsCurseMaven' + url = uri('https://www.cursemaven.com') + metadataSources { artifact() } + } + } + filter { optionalModArtifacts.each { includeModule('curse.maven', it.module) } } + } + maven { url = uri('https://modmaven.dev/') } maven { url = uri('https://dvs1.progwml6.com/files/maven/') } maven { url = uri('https://maven.covers1624.net/') } @@ -38,22 +63,30 @@ dependencies { implementation minecraft.dependency("net.minecraftforge:forge:${minecraft_version}-${forge_version}") // OreSpawn remains an ordinary hard mod dependency in metadata and is - // never shaded into Base Metals. Use the published 1.19.4 release in + // never shaded into Base Metals. Use the published 1.20.1 release in // development so clean checkouts exercise the same artifact as players. implementation renamer.dependency("curse.maven:mmd-orespawn-${orespawn_curse_project_id}:${orespawn_curse_file_id}") orespawnRelease "curse.maven:mmd-orespawn-${orespawn_curse_project_id}:${orespawn_curse_file_id}" // API only: Mekanism remains optional at runtime. Its public slurry // registry is required to provide the eight historical processing chains. - compileOnly 'mekanism:Mekanism:1.19.2-10.3.9.13:api' - compileOnly mantleDevelopment - compileOnly tinkersDevelopment - - // These mods have no published Forge 1.19.4 build. Keep their APIs compile-only; - // loading jars for another Minecraft version would break the development client. - if (providers.gradleProperty('compatSmoke').isPresent() - || providers.gradleProperty('packagedCompatSmoke').isPresent()) { - throw new GradleException('Optional-mod runtime qualification needs a published Forge 1.19.4 build') + compileOnly 'mekanism:Mekanism:1.20.1-10.4.16.80:api' + compileOnly optionalDevelopmentDependencies.mantle + compileOnly optionalDevelopmentDependencies.tconstruct + + // Only the separate optional-mod profile and explicit compatibility tests load these mods. + optionalModArtifacts.each { artifact -> + String coordinate = "curse.maven:${artifact.module}:${artifact.file}" + optionalModsRelease coordinate + if (artifact.development) { + optionalModsRuntimeOnly optionalDevelopmentDependencies[artifact.modId] + if (providers.gradleProperty('compatSmoke').isPresent()) { + runtimeOnly optionalDevelopmentDependencies[artifact.modId] + } + } + if (providers.gradleProperty('packagedCompatSmoke').isPresent()) { + packagedCompatibilityMods coordinate + } } testImplementation platform('org.junit:junit-bom:5.10.2') @@ -64,6 +97,44 @@ dependencies { renamer.mappings.from minecraft.dependency.toSrgFile +// Optional mods ship SRG mixin references; development runs use official names. +def developmentMixinMappings = renamer.convert('optionalMixinMappings') { + // Mixin 0.8 reads classic SRG entries, not TSRG, from this file. + map.from minecraft.dependency.toSrgFile + format.set('srg') + reverse.set(true) + output.set(layout.buildDirectory.file('mappings/optional-refmaps.srg')) +} +minecraft.runs.configureEach { + systemProperties(developmentMixinMappings.flatMap { it.output }.map { mappings -> + ['mixin.env.remapRefMap': 'true', 'mixin.env.refMapRemappingFile': mappings.asFile.absolutePath] + }) +} + configurations.named('testRuntimeClasspath') { exclude group: 'curse.maven', module: orespawnModule } + +tasks.register('verifyOptionalRuntimeDependencies') { + group = 'verification' + description = 'Checks the exact published jars used by the optional-mod profile.' + inputs.file 'gradle/optional-mods.json' + doLast { + def resolved = configurations.optionalModsRelease.resolvedConfiguration.resolvedArtifacts + optionalModArtifacts.each { expected -> + File artifact = resolved.find { + it.moduleVersion.id.name == expected.module && it.moduleVersion.id.version == expected.file + }?.file + if (artifact == null || MessageDigest.getInstance('SHA-256').digest(artifact.bytes) + .encodeHex().toString().toUpperCase(Locale.ROOT) != expected.sha256) { + throw new GradleException("Missing or altered optional dependency: ${expected.module}:${expected.file}") + } + } + } +} + +tasks.named('check') { dependsOn tasks.named('verifyOptionalRuntimeDependencies') } + +tasks.matching { it.name == 'runOptionalModsClient' }.configureEach { + dependsOn tasks.named('verifyOptionalRuntimeDependencies') +} diff --git a/gradle/ide/eclipse.gradle b/gradle/ide/eclipse.gradle index b192cb21b..a65641253 100644 --- a/gradle/ide/eclipse.gradle +++ b/gradle/ide/eclipse.gradle @@ -44,9 +44,12 @@ def verifyDevelopmentRunProfiles = tasks.register('verifyDevelopmentRunProfiles' throw new GradleException("${name} must not load Buildship's compile-only dependencies") } boolean optional = name == 'runClientOptionalMods.launch' - if (launch =~ /(?i)(TConstruct|Mantle|Mekanism|cofh_core|thermal_|EnderIO)/) { + if (!optional && launch =~ /(?i)(tconstruct|tinkers-construct|mantle|mekanism|cofh[_-]core|thermal[_-]|ender[-_]?io)/) { throw new GradleException("${name} includes an optional integration dependency") } + if (optional && optionalDevelopmentArtifacts.any { !launch.contains("${it.module}-${it.file}") }) { + throw new GradleException("${name} is missing a pinned optional dependency") + } if (!launch.contains('mmd-orespawn-')) { throw new GradleException("${name} is missing OreSpawn") } @@ -60,27 +63,30 @@ def verifyDevelopmentRunProfiles = tasks.register('verifyDevelopmentRunProfiles' } ['runClient', 'runServer'].each { name -> if (tasks.named(name, JavaExec).get().classpath.files.any { - it.name =~ /(?i)(TConstruct|Mantle|Mekanism|cofh_core|thermal_|EnderIO)/ + it.name =~ /(?i)(tconstruct|tinkers-construct|mantle|mekanism|cofh[_-]core|thermal[_-]|ender[-_]?io)/ }) { throw new GradleException("${name} includes an optional integration dependency") } } def optionalClient = tasks.named('runOptionalModsClient', JavaExec).get() if (optionalClient.sourceSetName.get() != 'optionalMods' - || optionalClient.classpath.files.any { it.name =~ /(?i)(TConstruct|Mantle|Mekanism|cofh_core|thermal_|EnderIO)/ }) { - throw new GradleException('The optional client must not load APIs for another Minecraft version') + || optionalDevelopmentArtifacts.any { expected -> + !optionalClient.classpath.files.any { it.name.contains("${expected.module}-${expected.file}") } + }) { + throw new GradleException('The optional client must load the pinned 1.20.1 dependencies') } } } -def syncForge45EclipseLaunches = tasks.register('syncForge45EclipseLaunches') { +def syncForge47EclipseLaunches = tasks.register('syncForge47EclipseLaunches') { group = 'ide' - description = 'Normalizes Forge 45 Eclipse launches to the production-only merged output.' + description = 'Normalizes Forge 47 Eclipse launches to the production-only merged output.' dependsOn tasks.named('processResources') doLast { String mainOutput = new File(projectDir, 'bin/main').absolutePath String modClasses = "${mod_id}%%${mainOutput}" - String eclipseGradleJavaHome = System.getenv('JAVA_HOME') ?: System.getProperty('java.home') + String eclipseGradleJavaHome = new File( + System.getenv('JAVA_HOME') ?: System.getProperty('java.home')).canonicalPath def escapePreference = { String value -> value.replace('\\', '\\\\').replace(':', '\\:') } File buildshipPreferences = file('.settings/org.eclipse.buildship.core.prefs') if (!buildshipPreferences.parentFile.isDirectory() && !buildshipPreferences.parentFile.mkdirs()) { @@ -168,6 +174,11 @@ def syncForge45EclipseLaunches = tasks.register('syncForge45EclipseLaunches') { } // Buildship also exposes compile-only APIs. Use the selected runtime instead. def launchXml = new XmlParser(false, false).parseText(text) + def vmArguments = launchXml.stringAttribute.find { + it.@key == 'org.eclipse.jdt.launching.VM_ARGUMENTS' + } + vmArguments.@value = vmArguments.@value.toString().replaceAll( + /(? if (!launch.contains("--${flag} "")) { @@ -288,6 +299,10 @@ def verifyEclipseProductionClasspath = tasks.register('verifyEclipseProductionCl || !launch.contains('minecraft_classpath.txt" -DignoreList=')) { throw new GradleException("Eclipse launch does not quote -DlegacyClassPath.file: ${name}") } + if (!launch.contains('"-Dmixin.env.refMapRemappingFile=') + || !launch.contains('optional-refmaps.srg"')) { + throw new GradleException("Eclipse launch does not quote its mixin mapping path: ${name}") + } } File processed = layout.buildDirectory.dir('resources/main').get().asFile ['META-INF/mods.toml', 'data/basemetals/orespawn/provider.json', @@ -300,26 +315,26 @@ def verifyEclipseProductionClasspath = tasks.register('verifyEclipseProductionCl } } String metadata = new File(projectDir, 'bin/main/META-INF/mods.toml').getText('UTF-8') - if (metadata.contains('${') || !metadata.contains('version="3.1.0.119041"')) { + if (metadata.contains('${') || !metadata.contains('version="3.1.0.120011"')) { throw new GradleException('Eclipse output contains unresolved or invalid Base Metals version metadata') } } } verifyDevelopmentRunProfiles.configure { - dependsOn syncForge45EclipseLaunches + dependsOn syncForge47EclipseLaunches } tasks.matching { it.name == 'genEclipseRuns' }.configureEach { - finalizedBy syncForge45EclipseLaunches + finalizedBy syncForge47EclipseLaunches } tasks.register('isolateEclipseProductionRuns') { group = 'ide' dependsOn tasks.named('genEclipseRuns') - dependsOn syncForge45EclipseLaunches + dependsOn syncForge47EclipseLaunches } tasks.matching { it.name == 'eclipse' }.configureEach { - finalizedBy syncForge45EclipseLaunches + finalizedBy syncForge47EclipseLaunches } diff --git a/gradle/optional-mods.json b/gradle/optional-mods.json new file mode 100644 index 000000000..38f007c19 --- /dev/null +++ b/gradle/optional-mods.json @@ -0,0 +1,58 @@ +[ + { + "modId": "mekanism", + "development": true, + "module": "mekanism-268560", + "file": "6552911", + "version": "10.4.16.80", + "sha256": "3B0D191FA4A45E662F9725991D76A30192F30186ABB2BA694B873B5F7B9358D2" + }, + { + "modId": "mantle", + "development": true, + "module": "mantle-74924", + "file": "7563777", + "version": "1.11.104", + "sha256": "6052E47C3981064BD5213728EAA3C5418289F6F85B07A1CD4DD1505239248838" + }, + { + "modId": "tconstruct", + "development": true, + "module": "tinkers-construct-74072", + "file": "7449219", + "version": "3.11.2.166", + "sha256": "653B49D73481A1325BA78ADC7273DEE6C765A51BAFDF264A7510576D6EF91C43" + }, + { + "modId": "cofh_core", + "development": true, + "module": "cofh-core-69162", + "file": "5374122", + "version": "11.0.2.56", + "sha256": "D7CD3EC8083E294E17CB50FE607B416C1DC83CE885E53BA3995D86E028F790E0" + }, + { + "modId": "thermal_foundation", + "development": false, + "module": "thermal-foundation-222880", + "file": "5443583", + "version": "11.0.6.70", + "sha256": "8CB610ABB3C835B47F9B4EC19BE6A54104D1F61230B67A9D5BFD097C5AC45823" + }, + { + "modId": "thermal_expansion", + "development": false, + "module": "thermal-expansion-69163", + "file": "5372749", + "version": "11.0.1.29", + "sha256": "F44D9E2476DEB0CE2700C3837AA6E99C6A6BC95BC2D89D69EBE34B6C3A612633" + }, + { + "modId": "enderio", + "development": false, + "module": "ender-io-64578", + "file": "7726916", + "version": "6.2.18-beta", + "sha256": "9D1471E6D4562D0A50F0564BC46573B87FD738BD440FE19397D31847DA20F172" + } +] diff --git a/gradle/prepare-forge-client-runtime.py b/gradle/prepare-forge-client-runtime.py index f5ffbf1fd..2ee3ffeec 100644 --- a/gradle/prepare-forge-client-runtime.py +++ b/gradle/prepare-forge-client-runtime.py @@ -18,12 +18,12 @@ from pathlib import Path from typing import Any -MINECRAFT_VERSION = "1.19.4" -FORGE_VERSION = "45.4.0" -FORGE_INSTALLER_SHA256 = "6643bb8c21f409cc528ff656daf60c206d304cc77bf529bbbab4b336004dfc53" +MINECRAFT_VERSION = "1.20.1" +FORGE_VERSION = "47.4.10" +FORGE_INSTALLER_SHA256 = "1912760b4cb6b803d8a826de603c9076b1da71ec2765e9a1f8c1ca78f65278e3" VERSION_MANIFEST_URL = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json" ASSET_OBJECT_URL = "https://resources.download.minecraft.net/{prefix}/{digest}" -USER_AGENT = "BaseMetals-release-validation/3.1.0.119041" +USER_AGENT = "BaseMetals-release-validation/3.1.0.120011" def digest_file(path: Path, algorithm: str) -> str: diff --git a/gradle/release/artifacts.gradle b/gradle/release/artifacts.gradle index 32aae58b7..6cf36ba1e 100644 --- a/gradle/release/artifacts.gradle +++ b/gradle/release/artifacts.gradle @@ -194,7 +194,7 @@ tasks.register('verifyMavenCoordinates') { tasks.register('verifyMavenizerCompatibilityFixture') { group = 'verification' - description = 'Verifies the checksum-sealed Forge 1.19.4 Mavenizer compatibility fixture.' + description = 'Verifies the checksum-sealed Forge 1.20.1 Mavenizer compatibility fixture.' inputs.file project.ext.mavenizerCompatibilityJar doLast { File fixture = project.ext.mavenizerCompatibilityJar.asFile @@ -208,23 +208,23 @@ tasks.register('verifyMavenizerCompatibilityFixture') { tasks.register('verifyReleaseConfiguration') { group = 'verification' - description = 'Verifies the exact Base Metals 1.19.4 release identity.' + description = 'Verifies the exact Base Metals 1.20.1 release identity.' dependsOn tasks.named('verifyMavenizerCompatibilityFixture') dependsOn tasks.named('verifyJava17Toolchain') doLast { - if (configuredReleaseIdentity.modVersion != '3.1.0.119041' + if (configuredReleaseIdentity.modVersion != '3.1.0.120011' || configuredReleaseIdentity.modGroup != expectedMavenGroup - || configuredReleaseIdentity.minecraftVersion != '1.19.4' - || configuredReleaseIdentity.forgeVersion != '45.4.0' - || configuredReleaseIdentity.forgeInstallerSha256 != '6643BB8C21F409CC528FF656DAF60C206D304CC77BF529BBBAB4B336004DFC53' + || configuredReleaseIdentity.minecraftVersion != '1.20.1' + || configuredReleaseIdentity.forgeVersion != '47.4.10' + || configuredReleaseIdentity.forgeInstallerSha256 != '1912760B4CB6B803D8A826DE603C9076B1DA71EC2765E9A1F8C1CA78F65278E3' || configuredReleaseIdentity.mappingChannel != 'official' - || configuredReleaseIdentity.mappingVersion != '1.19.4' + || configuredReleaseIdentity.mappingVersion != '1.20.1' || configuredReleaseIdentity.loaderName != 'forge' || configuredReleaseIdentity.loaderCode != '1' || configuredReleaseIdentity.javaVersion != '17' || configuredReleaseIdentity.gradleJavaVersion != '17' || configuredReleaseIdentity.curseForgeProjectId != '240967') { - throw new GradleException('Unexpected Base Metals 1.19.4 release identity') + throw new GradleException('Unexpected Base Metals 1.20.1 release identity') } } } @@ -233,10 +233,10 @@ tasks.register('verifyReleaseDependencies') { group = 'verification' description = 'Resolves and verifies the exact released OreSpawn qualification dependency.' doLast { - if (configuredOreSpawnIdentity.version != '4.1.0.119041' + if (configuredOreSpawnIdentity.version != '4.1.0.120011' || configuredOreSpawnIdentity.curseProjectId != '245586' - || configuredOreSpawnIdentity.curseFileId != '9171353' - || configuredOreSpawnIdentity.sha256 != '1B2ECAA546E7FFB35863AC1D9FF92504471EE857045DCD01A068C5CA7F53CFFD') { + || configuredOreSpawnIdentity.curseFileId != '9175609' + || configuredOreSpawnIdentity.sha256 != 'E6BDE47C1BB0BF4D08396F1D1FD5B1D2D3F8A5DF09905F279D39E2601D7FE4B0') { throw new GradleException('Unexpected OreSpawn release coordinates') } File artifact = configurations.orespawnRelease.resolvedConfiguration.resolvedArtifacts @@ -325,14 +325,14 @@ tasks.register('verifyReleaseArtifacts') { if (manifest.mainAttributes.getValue('Maven-Artifact') != expectedMavenCoordinate) { throw new GradleException('Release manifest contains the wrong Maven coordinate') } - String expectedBuildTarget = 'minecraft-1.19.4-forge-45.4.0-java-17' + String expectedBuildTarget = 'minecraft-1.20.1-forge-47.4.10-java-17' if (manifest.mainAttributes.getValue('Build-Target') != expectedBuildTarget) { throw new GradleException('Release manifest contains the wrong stable build target') } String modsToml = zip.getInputStream(zip.getEntry('META-INF/mods.toml')).getText('UTF-8') if (modsToml.contains('${') - || !modsToml.contains('version="3.1.0.119041"') - || !modsToml.contains('versionRange="[4.1.0.119041,5.0.0)"')) { + || !modsToml.contains('version="3.1.0.120011"') + || !modsToml.contains('versionRange="[4.1.0.120011,5.0.0)"')) { throw new GradleException('Release mods.toml contains unresolved or incompatible metadata') } def provider = new JsonSlurper().parse(zip.getInputStream( diff --git a/gradle/verification/packaged-runtime.gradle b/gradle/verification/packaged-runtime.gradle index f442264be..cfa893b3a 100644 --- a/gradle/verification/packaged-runtime.gradle +++ b/gradle/verification/packaged-runtime.gradle @@ -19,7 +19,7 @@ def packagedClientRunDirectory = layout.buildDirectory.dir('packaged-client-run' 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()) if (!runtime.isDirectory()) throw new GradleException("${propertyName} does not name a directory: ${runtime}") @@ -42,6 +42,9 @@ def preparePackagedServerSmoke = tasks.register('preparePackagedServerSmoke') { dependsOn releaseJar dependsOn packagedServerSmokeProbe dependsOn tasks.named('verifyReleaseDependencies') + if (providers.gradleProperty('packagedCompatSmoke').isPresent()) { + dependsOn tasks.named('verifyOptionalRuntimeDependencies') + } doLast { File runDirectory = packagedServerRunDirectory.get().asFile delete runDirectory @@ -81,8 +84,8 @@ def packagedServerStartupSmoke = tasks.register('packagedServerStartupSmoke', Ex doFirst { File runtime = requireRuntimeDirectory(packagedForgeServerRuntime, 'packagedForgeServerRuntime') File libraries = new File(runtime, 'libraries') - File forgeDirectory = new File(libraries, 'net/minecraftforge/forge/1.19.4-45.4.0') - File launcher = new File(forgeDirectory, 'forge-1.19.4-45.4.0-server.jar') + File forgeDirectory = new File(libraries, 'net/minecraftforge/forge/1.20.1-47.4.10') + File launcher = new File(forgeDirectory, 'forge-1.20.1-47.4.10-server.jar') File sourceArguments = new File(forgeDirectory, System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows') ? 'win_args.txt' : 'unix_args.txt') @@ -106,9 +109,9 @@ def packagedServerStartupSmoke = tasks.register('packagedServerStartupSmoke', Ex if (!marker.isFile()) throw new GradleException('Packaged dedicated-server completion marker is missing') Properties values = new Properties() marker.withInputStream { values.load(it) } - if (values.getProperty('basemetals') != '3.1.0.119041' - || values.getProperty('orespawn') != '4.1.0.119041' - || values.getProperty('forge') != '45.4.0') { + if (values.getProperty('basemetals') != '3.1.0.120011' + || values.getProperty('orespawn') != '4.1.0.120011' + || values.getProperty('forge') != '47.4.10') { throw new GradleException("Packaged dedicated-server versions are wrong: ${values}") } verifyPackagedLog(runDirectory, 'BASEMETALS_PACKAGED_SERVER_SMOKE PASS', @@ -122,6 +125,9 @@ def preparePackagedClientSmoke = tasks.register('preparePackagedClientSmoke') { dependsOn releaseJar dependsOn packagedClientSmokeProbe dependsOn tasks.named('verifyReleaseDependencies') + if (providers.gradleProperty('packagedCompatSmoke').isPresent()) { + dependsOn tasks.named('verifyOptionalRuntimeDependencies') + } doLast { File runDirectory = packagedClientRunDirectory.get().asFile delete runDirectory @@ -147,11 +153,11 @@ def preparePackagedClientSmoke = tasks.register('preparePackagedClientSmoke') { File hostPack = new File(runDirectory, 'resourcepacks/basemetals-host-test') File texture = new File(hostPack, 'assets/minecraft/textures/block/stone.png') texture.parentFile.mkdirs() - new java.util.zip.ZipFile(new File(runtime, 'versions/1.19.4/1.19.4.jar')).withCloseable { zip -> + new java.util.zip.ZipFile(new File(runtime, 'versions/1.20.1/1.20.1.jar')).withCloseable { zip -> texture.bytes = zip.getInputStream(zip.getEntry('assets/minecraft/textures/block/dirt.png')).bytes } new File(hostPack, 'pack.mcmeta').setText( - '{"pack":{"pack_format":13,"description":"Ore host replacement test"}}\n', 'UTF-8') + '{"pack":{"pack_format":15,"description":"Ore host replacement test"}}\n', 'UTF-8') new File(runDirectory, 'options.txt').append( 'resourcePacks:["vanilla","mod_resources","file/basemetals-host-test"]\n', 'UTF-8') } @@ -171,13 +177,13 @@ def packagedClientStartupSmoke = tasks.register('packagedClientStartupSmoke', Ex dependsOn preparePackagedClientSmoke doFirst { File runtime = requireRuntimeDirectory(packagedForgeClientRuntime, 'packagedForgeClientRuntime') - String forgeVersionId = ['1.19.4-forge-45.4.0', 'forge-45.4.0'].find { candidate -> + String forgeVersionId = ['1.20.1-forge-47.4.10', 'forge-47.4.10'].find { candidate -> new File(runtime, "versions/${candidate}/${candidate}.json").isFile() } - if (forgeVersionId == null) throw new GradleException('Official client runtime has no Forge 45.4.0 profile') + if (forgeVersionId == null) throw new GradleException('Official client runtime has no Forge 47.4.10 profile') File forgeJsonFile = new File(runtime, "versions/${forgeVersionId}/${forgeVersionId}.json") - File baseJsonFile = new File(runtime, 'versions/1.19.4/1.19.4.json') - File baseJar = new File(runtime, 'versions/1.19.4/1.19.4.jar') + File baseJsonFile = new File(runtime, 'versions/1.20.1/1.20.1.json') + File baseJar = new File(runtime, 'versions/1.20.1/1.20.1.jar') File nativesDirectory = new File(runtime, "natives/${forgeVersionId}") [forgeJsonFile, baseJsonFile, baseJar, new File(runtime, 'libraries'), new File(runtime, 'assets'), nativesDirectory].each { @@ -254,6 +260,12 @@ def packagedClientStartupSmoke = tasks.register('packagedClientStartupSmoke', Ex '-Dminecraft.launcher.brand=basemetals-validation', '-Dminecraft.launcher.version=1' ] + List expectedMods = ['minecraft', 'forge', 'basemetals', 'orespawn', 'basemetals_client_smoke'] + if (providers.gradleProperty('packagedCompatSmoke').isPresent()) { + expectedMods.addAll(optionalModArtifacts.collect { it.modId }) + command.add('-Dbasemetals.allowExtraSmokeMods=true') + } + command.add("-Dbasemetals.expectedSmokeMods=${expectedMods.join(',')}") command.addAll(forgeJvmArguments) command.addAll(['-cp', classpathFiles.collect { it.absolutePath }.join(File.pathSeparator), (String) forgeJson.mainClass]) diff --git a/src/dataGenerator/java/zone/moddev/mc/basemetals/data/GeneratedData.java b/src/dataGenerator/java/zone/moddev/mc/basemetals/data/GeneratedData.java index 872b68a1c..03b7a2a65 100644 --- a/src/dataGenerator/java/zone/moddev/mc/basemetals/data/GeneratedData.java +++ b/src/dataGenerator/java/zone/moddev/mc/basemetals/data/GeneratedData.java @@ -166,7 +166,6 @@ private void writeTags(CachedOutput cache) throws IOException { // AnvilBlock.damage is hard-coded to the three vanilla blocks in 1.18. // Tagging a custom anvil makes any damaging fall delete it immediately. // BaseMetalAnvilBlock supplies the menu and durability handling itself. - tag(cache, "basemetals", "blocks/scythe_harvestable", scytheHarvestableBlocks()); tag(cache, "basemetals", "blocks/crackhammer_crushable", crackhammerCrushableBlocks()); writeHarvestTierTags(cache); tag(cache, "forge", "blocks/netherrack", List.of("minecraft:netherrack")); @@ -319,31 +318,6 @@ private void writeVanillaBitsTags(CachedOutput cache) throws IOException { tag(cache, "forge", "blocks/storage_blocks/charcoal", List.of("basemetals:charcoal_block")); } - private static List scytheHarvestableBlocks() { - LinkedHashSet values = new LinkedHashSet<>(List.of( - "#minecraft:leaves", "#minecraft:saplings", "#minecraft:flowers", "#minecraft:crops")); - java.util.Set materials = java.util.Set.of( - net.minecraft.world.level.material.Material.WEB, - net.minecraft.world.level.material.Material.LEAVES, - net.minecraft.world.level.material.Material.PLANT, - net.minecraft.world.level.material.Material.WATER_PLANT, - net.minecraft.world.level.material.Material.REPLACEABLE_PLANT, - net.minecraft.world.level.material.Material.REPLACEABLE_FIREPROOF_PLANT, - net.minecraft.world.level.material.Material.REPLACEABLE_WATER_PLANT, - net.minecraft.world.level.material.Material.VEGETABLE, - net.minecraft.world.level.material.Material.CACTUS, - net.minecraft.world.level.material.Material.BAMBOO, - net.minecraft.world.level.material.Material.BAMBOO_SAPLING, - net.minecraft.world.level.material.Material.MOSS); - net.minecraftforge.registries.ForgeRegistries.BLOCKS.getValues().stream() - .filter(block -> materials.contains(block.defaultBlockState().getMaterial())) - .map(net.minecraftforge.registries.ForgeRegistries.BLOCKS::getKey) - .filter(java.util.Objects::nonNull) - .map(ResourceLocation::toString) - .forEach(values::add); - return List.copyOf(values); - } - private static List crackhammerCrushableBlocks() { LinkedHashSet values = new LinkedHashSet<>(); MaterialCatalogue.ALL.stream().filter(MaterialDefinition::hasOre) @@ -1155,7 +1129,7 @@ private void writeEnderIoRecipes(CachedOutput cache) throws IOException { } } for (Map.Entry> alloy : ALLOYS.entrySet()) { - JsonObject root = object("type", "enderio_machines:alloy_smelting", "energy", 6400, + JsonObject root = object("type", "enderio:alloy_smelting", "energy", 6400, "experience", 0.3F); JsonArray inputs = new JsonArray(); for (AlloyComponent component : alloy.getValue()) { @@ -1165,15 +1139,19 @@ private void writeEnderIoRecipes(CachedOutput cache) throws IOException { root.add("inputs", inputs); root.add("result", result("basemetals:" + alloy.getKey() + "_ingot", ALLOY_OUTPUTS.get(alloy.getKey()))); - saveConditionalRecipe(cache, root, "compat/enderio/alloys/" + alloy.getKey(), "enderio_machines"); + saveConditionalRecipe(cache, root, "compat/enderio/alloys/" + alloy.getKey(), "enderio"); } } private void enderSag(CachedOutput cache, String id, String input, String output, int count) throws IOException { - JsonObject root = object("type", "enderio_machines:sagmilling", "energy", 2400); + JsonObject root = object("type", "enderio:sag_milling", "energy", 2400); root.add("input", ingredient(input)); - JsonArray outputs = new JsonArray(); outputs.add(result(output, count)); root.add("outputs", outputs); - saveConditionalRecipe(cache, root, id, "enderio_machines"); + JsonObject product = object("chance", 1.0F, "optional", false); + product.add("item", result(output, count)); + JsonArray outputs = new JsonArray(); + outputs.add(product); + root.add("outputs", outputs); + saveConditionalRecipe(cache, root, id, "enderio"); } private void writeTinkersRecipes(CachedOutput cache) throws IOException { @@ -1264,7 +1242,6 @@ private void writeTinkersMaterials(CachedOutput cache) throws IOException { save(cache, definition, "data/basemetals/tinkering/materials/definition/" + name + ".json"); JsonObject stats = new JsonObject(); - stats.add("tconstruct:extra", new JsonObject()); stats.add("tconstruct:handle", object("durability", 1.0F, "miningSpeed", 1.0F, "attackSpeed", 1.0F, "attackDamage", 1.0F)); stats.add("tconstruct:head", object("durability", material.toolDurability(), @@ -1338,7 +1315,9 @@ private static void collectItemOutputs(com.google.gson.JsonElement value, java.u if (value.isJsonArray()) { value.getAsJsonArray().forEach(entry -> collectItemOutputs(entry, outputs)); } else if (value.isJsonObject() && value.getAsJsonObject().has("item")) { - outputs.add(value.getAsJsonObject().get("item").getAsString()); + var item = value.getAsJsonObject().get("item"); + if (item.isJsonPrimitive()) outputs.add(item.getAsString()); + else collectItemOutputs(item, outputs); } } @@ -1559,7 +1538,7 @@ private void writeManifest(CachedOutput cache) throws IOException { JsonObject root = new JsonObject(); root.addProperty("format", 1); - root.addProperty("source", "Base Metals 1.19.4 catalogue"); + root.addProperty("source", "Base Metals 1.20.1 catalogue"); root.add("blocks", array(blocks)); root.add("items", array(items)); root.add("fluids", array(ModContent.fluids().keySet().stream().flatMap(id -> diff --git a/src/gametest/java/zone/moddev/mc/basemetals/content/BaseMetalsGameTests.java b/src/gametest/java/zone/moddev/mc/basemetals/content/BaseMetalsGameTests.java index db880374e..990948c3a 100644 --- a/src/gametest/java/zone/moddev/mc/basemetals/content/BaseMetalsGameTests.java +++ b/src/gametest/java/zone/moddev/mc/basemetals/content/BaseMetalsGameTests.java @@ -570,13 +570,13 @@ public static void smithsRetainTheLegacyMercuryIngotSale(GameTestHelper helper) @GameTest(template = EMPTY) public static void creativeTabsRetainTheirLegacyStarsteelIcons(GameTestHelper helper) { - require(helper, ModTabs.BLOCKS.getIconItem().is(ModContent.item("starsteel_block").get()), + require(helper, ModTabs.BLOCKS.get().getIconItem().is(ModContent.item("starsteel_block").get()), "Blocks tab did not retain the Starsteel block icon"); - require(helper, ModTabs.ITEMS.getIconItem().is(ModContent.item("starsteel_gear").get()), + require(helper, ModTabs.ITEMS.get().getIconItem().is(ModContent.item("starsteel_gear").get()), "Items tab did not retain the Starsteel gear icon"); - require(helper, ModTabs.TOOLS.getIconItem().is(ModContent.item("starsteel_pickaxe").get()), + require(helper, ModTabs.TOOLS.get().getIconItem().is(ModContent.item("starsteel_pickaxe").get()), "Tools tab did not retain the Starsteel pickaxe icon"); - require(helper, ModTabs.COMBAT.getIconItem().is(ModContent.item("starsteel_sword").get()), + require(helper, ModTabs.COMBAT.get().getIconItem().is(ModContent.item("starsteel_sword").get()), "Combat tab did not retain the Starsteel sword icon"); helper.succeed(); } @@ -968,7 +968,7 @@ public static void projectilesRetainAmmunitionAcrossSaveAndSpawnData(GameTestHel } private static CraftingContainer craftingContainer(int width, int height) { - return new CraftingContainer(new AbstractContainerMenu(null, -1) { + return new net.minecraft.world.inventory.TransientCraftingContainer(new AbstractContainerMenu(null, -1) { @Override public ItemStack quickMoveStack(Player player, int slot) { return ItemStack.EMPTY; } @Override diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/adamantine/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/adamantine/ingot_sag_milling.json index af0dd4644..362af6ac8 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/adamantine/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/adamantine/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:adamantine_powder" + "chance": 1.0, + "item": { + "item": "basemetals:adamantine_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/adamantine/ore_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/adamantine/ore_sag_milling.json index 81c09e872..4ba291339 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/adamantine/ore_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/adamantine/ore_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,8 +16,12 @@ }, "outputs": [ { - "count": 2, - "item": "basemetals:adamantine_powder" + "chance": 1.0, + "item": { + "count": 2, + "item": "basemetals:adamantine_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/aquarium.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/aquarium.json index 77927a675..84a5c2d6e 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/aquarium.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/aquarium.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:alloy_smelting", + "type": "enderio:alloy_smelting", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/brass.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/brass.json index 347ab16e6..8ce3f1cb9 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/brass.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/brass.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:alloy_smelting", + "type": "enderio:alloy_smelting", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/bronze.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/bronze.json index 8816bf80e..21ea2ced0 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/bronze.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/bronze.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:alloy_smelting", + "type": "enderio:alloy_smelting", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/cupronickel.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/cupronickel.json index cb9d2458c..e04885375 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/cupronickel.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/cupronickel.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:alloy_smelting", + "type": "enderio:alloy_smelting", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/electrum.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/electrum.json index 5194ea974..f40a1778e 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/electrum.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/electrum.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:alloy_smelting", + "type": "enderio:alloy_smelting", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/invar.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/invar.json index dbf04a280..da8fb8664 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/invar.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/invar.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:alloy_smelting", + "type": "enderio:alloy_smelting", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/mithril.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/mithril.json index 9f1bea848..5a9db2911 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/mithril.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/mithril.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:alloy_smelting", + "type": "enderio:alloy_smelting", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/pewter.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/pewter.json index 5dcaeca94..cf3c3cdee 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/pewter.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/pewter.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:alloy_smelting", + "type": "enderio:alloy_smelting", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/steel.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/steel.json index ae87c5667..b5de5143c 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/steel.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/alloys/steel.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:alloy_smelting", + "type": "enderio:alloy_smelting", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/antimony/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/antimony/ingot_sag_milling.json index 3e6663bf9..9fc06f2e4 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/antimony/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/antimony/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:antimony_powder" + "chance": 1.0, + "item": { + "item": "basemetals:antimony_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/antimony/ore_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/antimony/ore_sag_milling.json index 45d63fdb5..82cffae71 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/antimony/ore_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/antimony/ore_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,8 +16,12 @@ }, "outputs": [ { - "count": 2, - "item": "basemetals:antimony_powder" + "chance": 1.0, + "item": { + "count": 2, + "item": "basemetals:antimony_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/aquarium/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/aquarium/ingot_sag_milling.json index 1fe106950..cf65fc68e 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/aquarium/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/aquarium/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:aquarium_powder" + "chance": 1.0, + "item": { + "item": "basemetals:aquarium_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/bismuth/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/bismuth/ingot_sag_milling.json index 3457e4c32..c586be490 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/bismuth/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/bismuth/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:bismuth_powder" + "chance": 1.0, + "item": { + "item": "basemetals:bismuth_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/bismuth/ore_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/bismuth/ore_sag_milling.json index 3afd0a392..d2fab668b 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/bismuth/ore_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/bismuth/ore_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,8 +16,12 @@ }, "outputs": [ { - "count": 2, - "item": "basemetals:bismuth_powder" + "chance": 1.0, + "item": { + "count": 2, + "item": "basemetals:bismuth_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/brass/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/brass/ingot_sag_milling.json index 6b2c623b6..764e011be 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/brass/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/brass/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:brass_powder" + "chance": 1.0, + "item": { + "item": "basemetals:brass_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/bronze/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/bronze/ingot_sag_milling.json index de7d5e492..4ab0a8d83 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/bronze/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/bronze/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:bronze_powder" + "chance": 1.0, + "item": { + "item": "basemetals:bronze_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/coldiron/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/coldiron/ingot_sag_milling.json index 3efd42be6..8e9e18fb2 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/coldiron/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/coldiron/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:coldiron_powder" + "chance": 1.0, + "item": { + "item": "basemetals:coldiron_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/coldiron/ore_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/coldiron/ore_sag_milling.json index 1022af5d3..a838c34f0 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/coldiron/ore_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/coldiron/ore_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,8 +16,12 @@ }, "outputs": [ { - "count": 2, - "item": "basemetals:coldiron_powder" + "chance": 1.0, + "item": { + "count": 2, + "item": "basemetals:coldiron_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/copper/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/copper/ingot_sag_milling.json index 34e6610cb..ea5982026 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/copper/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/copper/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:copper_powder" + "chance": 1.0, + "item": { + "item": "basemetals:copper_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/copper/ore_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/copper/ore_sag_milling.json index 0239f794a..22aa4df8f 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/copper/ore_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/copper/ore_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,8 +16,12 @@ }, "outputs": [ { - "count": 2, - "item": "basemetals:copper_powder" + "chance": 1.0, + "item": { + "count": 2, + "item": "basemetals:copper_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/cupronickel/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/cupronickel/ingot_sag_milling.json index b278a4c61..60d53542f 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/cupronickel/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/cupronickel/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:cupronickel_powder" + "chance": 1.0, + "item": { + "item": "basemetals:cupronickel_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/electrum/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/electrum/ingot_sag_milling.json index 0c07a8241..4a2905f23 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/electrum/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/electrum/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:electrum_powder" + "chance": 1.0, + "item": { + "item": "basemetals:electrum_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/invar/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/invar/ingot_sag_milling.json index e9ce0328b..ffd2b05d7 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/invar/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/invar/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:invar_powder" + "chance": 1.0, + "item": { + "item": "basemetals:invar_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/lead/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/lead/ingot_sag_milling.json index 22b143912..ccad6ee2b 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/lead/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/lead/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:lead_powder" + "chance": 1.0, + "item": { + "item": "basemetals:lead_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/lead/ore_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/lead/ore_sag_milling.json index 0c1a033fb..92080b2fd 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/lead/ore_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/lead/ore_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,8 +16,12 @@ }, "outputs": [ { - "count": 2, - "item": "basemetals:lead_powder" + "chance": 1.0, + "item": { + "count": 2, + "item": "basemetals:lead_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/mercury/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/mercury/ingot_sag_milling.json index 54d106b70..09a7a096b 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/mercury/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/mercury/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:mercury_powder" + "chance": 1.0, + "item": { + "item": "basemetals:mercury_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/mercury/ore_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/mercury/ore_sag_milling.json index 679bdddda..8be452fbb 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/mercury/ore_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/mercury/ore_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,8 +16,12 @@ }, "outputs": [ { - "count": 2, - "item": "basemetals:mercury_powder" + "chance": 1.0, + "item": { + "count": 2, + "item": "basemetals:mercury_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/mithril/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/mithril/ingot_sag_milling.json index 768f26ec6..24551333e 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/mithril/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/mithril/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:mithril_powder" + "chance": 1.0, + "item": { + "item": "basemetals:mithril_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/nickel/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/nickel/ingot_sag_milling.json index ca59727b5..48690c7b9 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/nickel/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/nickel/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:nickel_powder" + "chance": 1.0, + "item": { + "item": "basemetals:nickel_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/nickel/ore_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/nickel/ore_sag_milling.json index 4a2c5f5ab..6c6bb77e1 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/nickel/ore_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/nickel/ore_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,8 +16,12 @@ }, "outputs": [ { - "count": 2, - "item": "basemetals:nickel_powder" + "chance": 1.0, + "item": { + "count": 2, + "item": "basemetals:nickel_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/pewter/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/pewter/ingot_sag_milling.json index 4c262cb27..ad8f8acae 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/pewter/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/pewter/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:pewter_powder" + "chance": 1.0, + "item": { + "item": "basemetals:pewter_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/platinum/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/platinum/ingot_sag_milling.json index 680971fd9..a76c0a528 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/platinum/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/platinum/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:platinum_powder" + "chance": 1.0, + "item": { + "item": "basemetals:platinum_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/platinum/ore_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/platinum/ore_sag_milling.json index 5ea1a63f7..496814b16 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/platinum/ore_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/platinum/ore_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,8 +16,12 @@ }, "outputs": [ { - "count": 2, - "item": "basemetals:platinum_powder" + "chance": 1.0, + "item": { + "count": 2, + "item": "basemetals:platinum_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/silver/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/silver/ingot_sag_milling.json index f7274aeef..6f8e8ab1e 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/silver/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/silver/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:silver_powder" + "chance": 1.0, + "item": { + "item": "basemetals:silver_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/silver/ore_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/silver/ore_sag_milling.json index 2d8d07475..cb71646e7 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/silver/ore_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/silver/ore_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,8 +16,12 @@ }, "outputs": [ { - "count": 2, - "item": "basemetals:silver_powder" + "chance": 1.0, + "item": { + "count": 2, + "item": "basemetals:silver_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/starsteel/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/starsteel/ingot_sag_milling.json index 3abc71e0e..42aac3f4e 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/starsteel/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/starsteel/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:starsteel_powder" + "chance": 1.0, + "item": { + "item": "basemetals:starsteel_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/starsteel/ore_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/starsteel/ore_sag_milling.json index 5e0a35a45..8b7680149 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/starsteel/ore_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/starsteel/ore_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,8 +16,12 @@ }, "outputs": [ { - "count": 2, - "item": "basemetals:starsteel_powder" + "chance": 1.0, + "item": { + "count": 2, + "item": "basemetals:starsteel_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/steel/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/steel/ingot_sag_milling.json index a642a9313..6eece306e 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/steel/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/steel/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:steel_powder" + "chance": 1.0, + "item": { + "item": "basemetals:steel_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/tin/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/tin/ingot_sag_milling.json index a54f10b86..252740879 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/tin/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/tin/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:tin_powder" + "chance": 1.0, + "item": { + "item": "basemetals:tin_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/tin/ore_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/tin/ore_sag_milling.json index a358a5e93..7d99735dc 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/tin/ore_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/tin/ore_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,8 +16,12 @@ }, "outputs": [ { - "count": 2, - "item": "basemetals:tin_powder" + "chance": 1.0, + "item": { + "count": 2, + "item": "basemetals:tin_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/zinc/ingot_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/zinc/ingot_sag_milling.json index 0f455fcb0..930ef4a5d 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/zinc/ingot_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/zinc/ingot_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,7 +16,11 @@ }, "outputs": [ { - "item": "basemetals:zinc_powder" + "chance": 1.0, + "item": { + "item": "basemetals:zinc_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/recipes/compat/enderio/zinc/ore_sag_milling.json b/src/generated/resources/data/basemetals/recipes/compat/enderio/zinc/ore_sag_milling.json index 1c31d073f..b1c587609 100644 --- a/src/generated/resources/data/basemetals/recipes/compat/enderio/zinc/ore_sag_milling.json +++ b/src/generated/resources/data/basemetals/recipes/compat/enderio/zinc/ore_sag_milling.json @@ -1,9 +1,9 @@ { - "type": "enderio_machines:sagmilling", + "type": "enderio:sag_milling", "conditions": [ { "type": "forge:mod_loaded", - "modid": "enderio_machines" + "modid": "enderio" }, { "type": "basemetals:content_mode", @@ -16,8 +16,12 @@ }, "outputs": [ { - "count": 2, - "item": "basemetals:zinc_powder" + "chance": 1.0, + "item": { + "count": 2, + "item": "basemetals:zinc_powder" + }, + "optional": false } ] } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/registry_manifest.json b/src/generated/resources/data/basemetals/registry_manifest.json index 558debd3e..11b620935 100644 --- a/src/generated/resources/data/basemetals/registry_manifest.json +++ b/src/generated/resources/data/basemetals/registry_manifest.json @@ -1735,5 +1735,5 @@ "basemetals:wood", "basemetals:stone" ], - "source": "Base Metals 1.19.4 catalogue" + "source": "Base Metals 1.20.1 catalogue" } \ No newline at end of file diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/adamantine.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/adamantine.json index c76fdf11f..9e1c6fd30 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/adamantine.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/adamantine.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/antimony.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/antimony.json index 7106ebb19..f84b25119 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/antimony.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/antimony.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/aquarium.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/aquarium.json index a5912058e..c2ccaa9b3 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/aquarium.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/aquarium.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/bismuth.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/bismuth.json index 7106ebb19..f84b25119 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/bismuth.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/bismuth.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/brass.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/brass.json index 6d9a3e893..8c173c1ef 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/brass.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/brass.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/bronze.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/bronze.json index 1282f825a..440c69eeb 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/bronze.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/bronze.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/coldiron.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/coldiron.json index c63bc269c..e5aa51e62 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/coldiron.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/coldiron.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/copper.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/copper.json index e151e73c1..a51afc7b9 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/copper.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/copper.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/cupronickel.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/cupronickel.json index 37ae5e0ed..5bd4b905e 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/cupronickel.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/cupronickel.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/electrum.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/electrum.json index 3e4efa08e..99ebff2bb 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/electrum.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/electrum.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/invar.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/invar.json index f84b0cde0..1268f311e 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/invar.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/invar.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/lead.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/lead.json index 7106ebb19..f84b25119 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/lead.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/lead.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/mithril.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/mithril.json index ef6dd3d7c..dc2e19448 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/mithril.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/mithril.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/nickel.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/nickel.json index e151e73c1..a51afc7b9 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/nickel.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/nickel.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/pewter.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/pewter.json index 7106ebb19..f84b25119 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/pewter.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/pewter.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/platinum.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/platinum.json index 615740c82..948f7e40c 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/platinum.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/platinum.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/silver.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/silver.json index 3e4efa08e..99ebff2bb 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/silver.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/silver.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/starsteel.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/starsteel.json index 0c878b72f..779dcf6e9 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/starsteel.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/starsteel.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/steel.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/steel.json index 30cce1c0c..8db152bfd 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/steel.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/steel.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/tin.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/tin.json index 318b16af6..ab36a8966 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/tin.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/tin.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/generated/resources/data/basemetals/tinkering/materials/stats/zinc.json b/src/generated/resources/data/basemetals/tinkering/materials/stats/zinc.json index 7106ebb19..f84b25119 100644 --- a/src/generated/resources/data/basemetals/tinkering/materials/stats/zinc.json +++ b/src/generated/resources/data/basemetals/tinkering/materials/stats/zinc.json @@ -1,6 +1,5 @@ { "stats": { - "tconstruct:extra": {}, "tconstruct:grip": { "accuracy": 0.0, "durability": 1.0, diff --git a/src/integratedSmoke/java/zone/moddev/mc/basemetals/smoke/IntegratedWorldSmoke.java b/src/integratedSmoke/java/zone/moddev/mc/basemetals/smoke/IntegratedWorldSmoke.java index b85098600..093bf61e5 100644 --- a/src/integratedSmoke/java/zone/moddev/mc/basemetals/smoke/IntegratedWorldSmoke.java +++ b/src/integratedSmoke/java/zone/moddev/mc/basemetals/smoke/IntegratedWorldSmoke.java @@ -76,7 +76,8 @@ public static void onClientTick(TickEvent.ClientTickEvent event) { var expected = new java.util.TreeSet<>(java.util.List.of(expectedMods.split(","))); var actual = new java.util.TreeSet<>(net.minecraftforge.fml.ModList.get().getMods().stream() .map(net.minecraftforge.forgespi.language.IModInfo::getModId).toList()); - if (!actual.equals(expected)) { + boolean allowDependencies = Boolean.getBoolean("basemetals.allowExtraSmokeMods"); + if (allowDependencies ? !actual.containsAll(expected) : !actual.equals(expected)) { throw new IllegalStateException("Client loaded " + actual + "; expected " + expected); } BaseMetals.LOGGER.info("BASEMETALS_CLIENT_PROFILE PASS mods={}", actual); @@ -86,16 +87,12 @@ public static void onClientTick(TickEvent.ClientTickEvent event) { verifyBlockRenderLayers(minecraft); renderLayersChecked = true; } - if (!creativeBucketsChecked) { - verifyCreativeBuckets(minecraft); - creativeBucketsChecked = true; - } if (!creationStarted && elapsedTicks % 200 == 0) { BaseMetals.LOGGER.info("BASEMETALS_INTEGRATED_SMOKE waiting for title screen current={} overlay={}", minecraft.screen == null ? "none" : minecraft.screen.getClass().getName(), minecraft.getOverlay() == null ? "none" : minecraft.getOverlay().getClass().getName()); } - if (!creationStarted && minecraft.screen instanceof TitleScreen) { + if (!creationStarted && minecraft.screen instanceof TitleScreen && minecraft.getOverlay() == null) { ConfigScreenSmoke.verify(minecraft, minecraft.screen); verifyOreOverlayModels(minecraft); verifyBarsModels(minecraft); @@ -129,6 +126,11 @@ public static void onClientTick(TickEvent.ClientTickEvent event) { && minecraft.level != null && minecraft.player != null) { ServerLevel serverLevel = minecraft.getSingleplayerServer().getLevel(minecraft.level.dimension()); if (serverLevel != null && serverLevel.getServer().isRunning()) { + if (!creativeBucketsChecked) { + // Other mods may use server settings while building creative tooltips. + verifyCreativeBuckets(minecraft); + creativeBucketsChecked = true; + } if (!projectileRenderersChecked) { verifyProjectileRenderers(minecraft); projectileRenderersChecked = true; @@ -321,10 +323,10 @@ private static void verifyCreativeBuckets(Minecraft minecraft) { net.minecraft.world.item.CreativeModeTabs.tryRebuildTabContents( net.minecraft.world.flag.FeatureFlags.VANILLA_SET, true, RegistryAccess.fromRegistryOfRegistries(net.minecraft.core.registries.BuiltInRegistries.REGISTRY)); - itemsTab.addAll(ModTabs.ITEMS.getDisplayItems()); + itemsTab.addAll(ModTabs.ITEMS.get().getDisplayItems()); searchTab.addAll(net.minecraft.world.item.CreativeModeTabs.searchTab().getDisplayItems()); var shown = new java.util.HashSet(); - for (var tab : java.util.List.of(ModTabs.BLOCKS, ModTabs.ITEMS, ModTabs.TOOLS, ModTabs.COMBAT)) { + for (var tab : java.util.List.of(ModTabs.BLOCKS.get(), ModTabs.ITEMS.get(), ModTabs.TOOLS.get(), ModTabs.COMBAT.get())) { tab.getDisplayItems().forEach(stack -> shown.add(stack.getItem())); } var indexed = minecraft.getSearchTree(SearchRegistry.CREATIVE_NAMES).search("basemetals:") diff --git a/src/main/java/zone/moddev/mc/basemetals/BaseMetals.java b/src/main/java/zone/moddev/mc/basemetals/BaseMetals.java index 3acfee5e5..20427d5ba 100644 --- a/src/main/java/zone/moddev/mc/basemetals/BaseMetals.java +++ b/src/main/java/zone/moddev/mc/basemetals/BaseMetals.java @@ -26,12 +26,14 @@ public final class BaseMetals { public BaseMetals() { IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus(); ModContent.register(modBus); - modBus.addListener(ModTabs::register); + ModTabs.register(modBus); modBus.addListener(BaseMetalsConfig::onConfigLoading); modBus.addListener((net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent event) -> { - zone.moddev.mc.basemetals.network.ContentModeNetwork.register(); - event.enqueueWork(() -> net.minecraftforge.common.crafting.CraftingHelper.register( - new zone.moddev.mc.basemetals.recipe.ContentRecipeCondition.Serializer())); + event.enqueueWork(() -> { + zone.moddev.mc.basemetals.network.ContentModeNetwork.register(); + net.minecraftforge.common.crafting.CraftingHelper.register( + new zone.moddev.mc.basemetals.recipe.ContentRecipeCondition.Serializer()); + }); }); CrushingRecipe.TYPES.register(modBus); CrushingRecipe.SERIALIZERS.register(modBus); diff --git a/src/main/java/zone/moddev/mc/basemetals/BaseMetalsEvents.java b/src/main/java/zone/moddev/mc/basemetals/BaseMetalsEvents.java index 78fabd1d0..522bd08c8 100644 --- a/src/main/java/zone/moddev/mc/basemetals/BaseMetalsEvents.java +++ b/src/main/java/zone/moddev/mc/basemetals/BaseMetalsEvents.java @@ -112,7 +112,7 @@ public void onBreak(BlockEvent.BreakEvent event) { @SubscribeEvent public void onPlayerTick(TickEvent.PlayerTickEvent event) { Player player = event.player; - if (event.phase != TickEvent.Phase.END || player.level.isClientSide) return; + if (event.phase != TickEvent.Phase.END || player.level().isClientSide) return; if (BaseMetalsConfig.STARSTEEL_REGENERATION.get() && player.tickCount % 200 == 0) { repairStarsteel(player.getMainHandItem()); repairStarsteel(player.getOffhandItem()); @@ -179,7 +179,7 @@ private static void add(Player player, net.minecraft.world.effect.MobEffect effe @SubscribeEvent public void onLivingHurt(LivingHurtEvent event) { - if (!BaseMetalsConfig.SPECIAL_EFFECTS.get() || event.getEntity().level.isClientSide + if (!BaseMetalsConfig.SPECIAL_EFFECTS.get() || event.getEntity().level().isClientSide || !(event.getSource().getEntity() instanceof LivingEntity attacker)) return; ItemStack held = attacker.getMainHandItem(); if (!(held.getItem() instanceof MaterialBacked backed) || !(held.getItem() instanceof TieredItem)) return; diff --git a/src/main/java/zone/moddev/mc/basemetals/ModTabs.java b/src/main/java/zone/moddev/mc/basemetals/ModTabs.java index f301aeb78..bb4ffaef1 100644 --- a/src/main/java/zone/moddev/mc/basemetals/ModTabs.java +++ b/src/main/java/zone/moddev/mc/basemetals/ModTabs.java @@ -1,12 +1,14 @@ package zone.moddev.mc.basemetals; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; import net.minecraft.world.item.Items; -import net.minecraftforge.event.CreativeModeTabEvent; +import net.minecraft.core.registries.Registries; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.RegistryObject; import zone.moddev.mc.basemetals.content.BaseMetalAmmoItem; import zone.moddev.mc.basemetals.content.CrackhammerItem; import zone.moddev.mc.basemetals.content.MaterialItems; @@ -14,23 +16,23 @@ import zone.moddev.mc.basemetals.content.ScytheItem; public final class ModTabs { - public static CreativeModeTab BLOCKS; - public static CreativeModeTab ITEMS; - public static CreativeModeTab TOOLS; - public static CreativeModeTab COMBAT; + private static final DeferredRegister TABS = + DeferredRegister.create(Registries.CREATIVE_MODE_TAB, BaseMetals.MOD_ID); + + public static final RegistryObject BLOCKS = tab("blocks", "starsteel_block", Items.IRON_BLOCK); + public static final RegistryObject ITEMS = tab("items", "starsteel_gear", Items.IRON_INGOT); + public static final RegistryObject TOOLS = tab("tools", "starsteel_pickaxe", Items.IRON_PICKAXE); + public static final RegistryObject COMBAT = tab("combat", "starsteel_sword", Items.IRON_SWORD); private ModTabs() {} - public static void register(CreativeModeTabEvent.Register event) { - BLOCKS = tab(event, "blocks", "starsteel_block", Items.IRON_BLOCK); - ITEMS = tab(event, "items", "starsteel_gear", Items.IRON_INGOT); - TOOLS = tab(event, "tools", "starsteel_pickaxe", Items.IRON_PICKAXE); - COMBAT = tab(event, "combat", "starsteel_sword", Items.IRON_SWORD); + public static void register(IEventBus bus) { + TABS.register(bus); } - private static CreativeModeTab tab(CreativeModeTabEvent.Register event, String category, + private static RegistryObject tab(String category, String iconId, Item fallback) { - return event.registerCreativeModeTab(new ResourceLocation(BaseMetals.MOD_ID, category), builder -> builder + return TABS.register(category, () -> CreativeModeTab.builder() .title(Component.translatable("itemGroup.basemetals." + category)) .icon(() -> ModContent.itemsById().containsKey(iconId) ? ModContent.item(iconId).get().getDefaultInstance() : fallback.getDefaultInstance()) @@ -39,7 +41,7 @@ private static CreativeModeTab tab(CreativeModeTabEvent.Register event, String c if (!ModContent.fluids().containsKey(id) && category.equals(category(entry.get()))) { output.accept(entry.get()); } - }))); + })).build()); } public static String category(Item item) { diff --git a/src/main/java/zone/moddev/mc/basemetals/client/BaseMetalsConfigScreen.java b/src/main/java/zone/moddev/mc/basemetals/client/BaseMetalsConfigScreen.java index c99f35252..5d8dd941f 100644 --- a/src/main/java/zone/moddev/mc/basemetals/client/BaseMetalsConfigScreen.java +++ b/src/main/java/zone/moddev/mc/basemetals/client/BaseMetalsConfigScreen.java @@ -2,7 +2,7 @@ import java.util.ArrayList; import java.util.List; -import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.gui.GuiGraphics; import net.minecraft.util.FormattedCharSequence; @@ -119,10 +119,10 @@ public void onClose() { } @Override - public void render(PoseStack matrices, int mouseX, int mouseY, float partialTicks) { - renderBackground(matrices); - drawCenteredString(matrices, font, title, width / 2, 12, 0xFFFFFF); - super.render(matrices, mouseX, mouseY, partialTicks); + public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) { + renderBackground(graphics); + graphics.drawCenteredString(font, title, width / 2, 12, 0xFFFFFF); + super.render(graphics, mouseX, mouseY, partialTicks); for (net.minecraft.client.gui.components.events.GuiEventListener child : children()) { if (child instanceof OptionButton && ((OptionButton) child).isHoveredOrFocused()) { @@ -134,7 +134,7 @@ public void render(PoseStack matrices, int mouseX, int mouseY, float partialTick help.addAll(font.split(net.minecraft.network.chat.Component.translatable(edits.mode().translationKey() + ".description"), 280)); help.addAll(font.split(net.minecraft.network.chat.Component.translatable(PREFIX + "restart"), 280)); } - renderTooltip(matrices, help, mouseX, mouseY); + graphics.renderTooltip(font, help, mouseX, mouseY); } } } diff --git a/src/main/java/zone/moddev/mc/basemetals/compat/MekanismCompat.java b/src/main/java/zone/moddev/mc/basemetals/compat/MekanismCompat.java index 5213471ef..b0fb23c9c 100644 --- a/src/main/java/zone/moddev/mc/basemetals/compat/MekanismCompat.java +++ b/src/main/java/zone/moddev/mc/basemetals/compat/MekanismCompat.java @@ -22,7 +22,7 @@ public final class MekanismCompat { "nickel", "platinum", "starsteel", "zinc"); private static final DeferredRegister SLURRIES = DeferredRegister.create( - MekanismAPI.slurryRegistryName(), BaseMetals.MOD_ID); + MekanismAPI.SLURRY_REGISTRY_NAME, BaseMetals.MOD_ID); private static final Map> DIRTY = new LinkedHashMap<>(); private static final Map> CLEAN = new LinkedHashMap<>(); @@ -31,9 +31,9 @@ public final class MekanismCompat { int colour = MaterialCatalogue.get(name).colour(); ResourceLocation oreTag = new ResourceLocation("forge", "ores/" + name); DIRTY.put(name, SLURRIES.register("dirty_" + name, - () -> new Slurry(SlurryBuilder.dirty().color(colour).ore(oreTag)))); + () -> new Slurry(SlurryBuilder.dirty().tint(colour).ore(oreTag)))); CLEAN.put(name, SLURRIES.register("clean_" + name, - () -> new Slurry(SlurryBuilder.clean().color(colour).ore(oreTag)))); + () -> new Slurry(SlurryBuilder.clean().tint(colour).ore(oreTag)))); } } diff --git a/src/main/java/zone/moddev/mc/basemetals/content/ModContent.java b/src/main/java/zone/moddev/mc/basemetals/content/ModContent.java index 8806ced3c..cc7eb01f0 100644 --- a/src/main/java/zone/moddev/mc/basemetals/content/ModContent.java +++ b/src/main/java/zone/moddev/mc/basemetals/content/ModContent.java @@ -31,7 +31,8 @@ import net.minecraft.world.level.block.WallBlock; import net.minecraft.world.level.block.LiquidBlock; import net.minecraft.world.level.block.state.BlockBehaviour; -import net.minecraft.world.level.material.Material; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.level.material.PushReaction; import net.minecraft.world.level.material.Fluid; import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundEvents; @@ -68,12 +69,12 @@ public final class ModContent { static { MaterialCatalogue.ALL.forEach(ModContent::registerMaterial); - registerAnvil("stone_anvil", BlockBehaviour.Properties.of(Material.STONE) + registerAnvil("stone_anvil", BlockBehaviour.Properties.of().mapColor(MapColor.STONE) .sound(SoundType.STONE).strength(5.0F, 10.0F)); registerAnvil("steel_anvil", metalProperties(MaterialCatalogue.get("steel"))); registerAnvil("adamantine_anvil", metalProperties(MaterialCatalogue.get("adamantine"))); HUMAN_DETECTOR = registerBlock("human_detector", - () -> new HumanDetectorBlock(BlockBehaviour.Properties.of(Material.METAL) + () -> new HumanDetectorBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL) .sound(SoundType.METAL).strength(5.0F).noCollission()), true); registerVanillaBits(); MaterialCatalogue.ALL.forEach(material -> registerFluid(material.name(), material.colour(), @@ -253,7 +254,7 @@ private static RegistryObject registerArmor(MaterialDefinition material, E private static void registerVanillaBits() { Map vanilla = vanillaDefinitions(); RegistryObject charcoalBlock = registerBlock("charcoal_block", - () -> new Block(BlockBehaviour.Properties.of(Material.STONE) + () -> new Block(BlockBehaviour.Properties.of().mapColor(MapColor.STONE) .sound(SoundType.SAND).requiresCorrectToolForDrops().strength(5.0F)), false); registerItem("charcoal_block", () -> new MaterialItems.BurnableBlock( charcoalBlock.get(), 16000, new Item.Properties())); @@ -323,9 +324,9 @@ private static BlockBehaviour.Properties vanillaDecorativeProperties(String name || name.equals("obsidian") || name.equals("quartz"); boolean lockedGemDoor = gem && !name.equals("quartz") && (form.equals("door") || form.equals("trapdoor")); - Material blockMaterial = name.equals("gold") || name.equals("iron") || lockedGemDoor - ? Material.METAL : Material.STONE; - return BlockBehaviour.Properties.of(blockMaterial) + MapColor mapColour = name.equals("gold") || name.equals("iron") || lockedGemDoor + ? MapColor.METAL : MapColor.STONE; + return BlockBehaviour.Properties.of().mapColor(mapColour) .sound(gem ? SoundType.GLASS : SoundType.METAL) .requiresCorrectToolForDrops() .strength(material.blockHardness(), material.blastResistance()); @@ -432,7 +433,8 @@ private static void registerFluid(String name, int colour, boolean mercury) { source[0] = FLUIDS.register(name, () -> new ForgeFlowingFluid.Source(properties)); flowing[0] = FLUIDS.register("flowing_" + name, () -> new ForgeFlowingFluid.Flowing(properties)); block[0] = BLOCKS.register(name, () -> new MoltenMetalBlock(() -> source[0].get(), - BlockBehaviour.Properties.of(Material.LAVA).noCollission().strength(100.0F).noLootTable(), mercury)); + BlockBehaviour.Properties.of().mapColor(MapColor.FIRE).liquid().replaceable() + .pushReaction(PushReaction.DESTROY).noCollission().strength(100.0F).noLootTable(), mercury)); BLOCKS_BY_ID.put(name, block[0]); // Forge 1.12 registered an ItemBlock under every molten block ID. Keep // those hidden inventory IDs so old stacks can survive a world upgrade. @@ -484,13 +486,13 @@ private static RegistryObject registerItem(String id, Supplier doApply(ObjectArrayList generatedLoot, LootContext context) { - context.getLootTable(table).getRandomItemsRaw(context, item -> { + context.getResolver().getLootTable(table).getRandomItemsRaw(context, item -> { if (zone.moddev.mc.basemetals.config.ContentPolicy.active().allows(net.minecraftforge.registries.ForgeRegistries.ITEMS.getKey(item.getItem()).toString())) { generatedLoot.add(item); } diff --git a/src/main/java/zone/moddev/mc/basemetals/network/ContentModeNetwork.java b/src/main/java/zone/moddev/mc/basemetals/network/ContentModeNetwork.java index 5a1b5ff90..a71b30ded 100644 --- a/src/main/java/zone/moddev/mc/basemetals/network/ContentModeNetwork.java +++ b/src/main/java/zone/moddev/mc/basemetals/network/ContentModeNetwork.java @@ -27,20 +27,21 @@ public static void register() { channel = NetworkRegistry.newSimpleChannel(new ResourceLocation("basemetals", "content_mode"), () -> "1", "1"::equals, "1"::equals); + // Forge updates login state on the main thread, including acknowledgements. channel.messageBuilder(ModeMessage.class, 0) .encoder((message, buffer) -> buffer.writeUtf(message.mode.serializedName())) .decoder(ModeMessage::read) .loginIndex(message -> message.loginIndex, (message, index) -> message.loginIndex = index) .buildLoginPacketList(local -> Collections.singletonList( Pair.of("Base Metals content mode", new ModeMessage(BaseMetalsConfig.activeMode())))) - .consumer(ContentModeNetwork::receive) + .consumerMainThread(ContentModeNetwork::receive) .add(); channel.messageBuilder(Acknowledgement.class, 1) .encoder((message, buffer) -> {}) .decoder(buffer -> new Acknowledgement()) .loginIndex(message -> message.loginIndex, (message, index) -> message.loginIndex = index) - .consumer(HandshakeHandler.indexFirst((handshake, message, context) -> + .consumerMainThread(HandshakeHandler.indexFirst((handshake, message, context) -> context.get().setPacketHandled(true))) .add(); } diff --git a/src/main/resources/META-INF/coremods.json b/src/main/resources/META-INF/coremods.json index 2bf71a9de..1e41b9927 100644 --- a/src/main/resources/META-INF/coremods.json +++ b/src/main/resources/META-INF/coremods.json @@ -1,3 +1,4 @@ { - "basemetals_118_compatibility": "coremods/basemetals_118_compatibility.js" + "basemetals_118_compatibility": "coremods/basemetals_118_compatibility.js", + "basemetals_connection_read_interest": "coremods/connection_read_interest.js" } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 94dece867..75dbe8254 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -14,7 +14,7 @@ description='''${mod_description}''' [[dependencies.${mod_id}]] modId="orespawn" mandatory=true -versionRange="[4.1.0.119041,5.0.0)" +versionRange="[4.1.0.120011,5.0.0)" ordering="AFTER" side="BOTH" @@ -42,7 +42,7 @@ side="BOTH" [[dependencies.${mod_id}]] modId="thermal_expansion" mandatory=false -versionRange="[9,10)" +versionRange="[11,12)" ordering="AFTER" side="BOTH" @@ -54,11 +54,9 @@ ordering="AFTER" side="BOTH" [[dependencies.${mod_id}]] -modId="enderio_machines" +modId="enderio" mandatory=false -# Maven qualifier ordering places commit-suffixed 6.0.0 alpha builds below -# the bare 6.0.0-alpha token. This range admits the intended major-6 alpha -# line while still rejecting every other Ender IO major version. +# Ender IO 6 uses prerelease version suffixes, including its supported beta builds. versionRange="(5,7)" ordering="AFTER" side="BOTH" diff --git a/src/main/resources/coremods/connection_read_interest.js b/src/main/resources/coremods/connection_read_interest.js new file mode 100644 index 000000000..1b784a7fa --- /dev/null +++ b/src/main/resources/coremods/connection_read_interest.js @@ -0,0 +1,90 @@ +var Opcodes = Java.type('org.objectweb.asm.Opcodes'); +var InsnList = Java.type('org.objectweb.asm.tree.InsnList'); +var FieldInsnNode = Java.type('org.objectweb.asm.tree.FieldInsnNode'); +var MethodInsnNode = Java.type('org.objectweb.asm.tree.MethodInsnNode'); +var VarInsnNode = Java.type('org.objectweb.asm.tree.VarInsnNode'); +var ASMAPI = Java.type('net.minecraftforge.coremod.api.ASMAPI'); + +function initializeCoreMod() { + return { + 'basemetals_connection_read_interest': { + 'target': { 'type': 'CLASS', 'name': 'net.minecraft.network.Connection' }, + 'transformer': serializeReadChanges + } + }; +} + +function serializeReadChanges(node) { + var packet = 'Lnet/minecraft/network/protocol/Packet;'; + var listener = 'Lnet/minecraft/network/PacketSendListener;'; + var protocol = 'Lnet/minecraft/network/ConnectionProtocol;'; + var sender = findMethod(node, ASMAPI.mapMethod('m_129520_'), '(' + packet + listener + ')V'); + var writer = findMethod(node, ASMAPI.mapMethod('m_243087_'), '(' + packet + listener + protocol + protocol + ')V'); + var pause = findCall(sender, 'io/netty/channel/ChannelConfig', 'setAutoRead'); + + if (pause === null && findCall(writer, 'io/netty/channel/ChannelConfig', 'setAutoRead') !== null) { + return node; + } + if (pause === null || findCall(writer, 'io/netty/channel/ChannelConfig', 'setAutoRead') !== null) { + throw new Error('Base Metals found an unexpected Connection read-pause layout'); + } + + var value = previousCode(pause); + var config = previousCode(value); + var channel = previousCode(config); + var self = previousCode(channel); + var discard = pause.getNext(); + var changeProtocol = findCall(writer, node.name, ASMAPI.mapMethod('m_129498_')); + var newProtocol = changeProtocol === null ? null : previousCode(changeProtocol); + var receiver = newProtocol === null ? null : previousCode(newProtocol); + + if (value === null || value.getOpcode() !== Opcodes.ICONST_0 + || !(config instanceof MethodInsnNode) || config.name !== 'config' + || !(channel instanceof FieldInsnNode) || channel.desc !== 'Lio/netty/channel/Channel;' + || !(self instanceof VarInsnNode) || self.getOpcode() !== Opcodes.ALOAD || self.var !== 0 + || discard === null || discard.getOpcode() !== Opcodes.POP + || !(receiver instanceof VarInsnNode) || receiver.getOpcode() !== Opcodes.ALOAD || receiver.var !== 0 + || !(newProtocol instanceof VarInsnNode) || newProtocol.var !== 3) { + throw new Error('Base Metals could not move Connection read changes onto the event loop'); + } + + // Forge resumes reads on the event loop. Pause them there too, so a delayed + // off-thread pause cannot erase the newer socket read request during login. + var instructions = new InsnList(); + var instruction = self; + var end = discard.getNext(); + while (instruction !== end) { + var next = instruction.getNext(); + sender.instructions.remove(instruction); + instructions.add(instruction); + instruction = next; + } + writer.instructions.insertBefore(receiver, instructions); + return node; +} + +function findMethod(node, name, descriptor) { + for (var i = 0; i < node.methods.size(); ++i) { + var method = node.methods.get(i); + if (method.name === name && method.desc === descriptor) return method; + } + throw new Error('Base Metals could not find Connection.' + name + descriptor); +} + +function findCall(method, owner, name) { + var found = null; + for (var instruction = method.instructions.getFirst(); instruction !== null; instruction = instruction.getNext()) { + if (instruction instanceof MethodInsnNode && instruction.owner === owner && instruction.name === name) { + if (found !== null) throw new Error('Base Metals found duplicate calls to ' + owner + '.' + name); + found = instruction; + } + } + return found; +} + +function previousCode(instruction) { + if (instruction === null) return null; + var previous = instruction.getPrevious(); + while (previous !== null && previous.getOpcode() < 0) previous = previous.getPrevious(); + return previous; +} diff --git a/src/generated/resources/data/basemetals/tags/blocks/scythe_harvestable.json b/src/main/resources/data/basemetals/tags/blocks/scythe_harvestable.json similarity index 99% rename from src/generated/resources/data/basemetals/tags/blocks/scythe_harvestable.json rename to src/main/resources/data/basemetals/tags/blocks/scythe_harvestable.json index 9e2de2ada..9239ca7ad 100644 --- a/src/generated/resources/data/basemetals/tags/blocks/scythe_harvestable.json +++ b/src/main/resources/data/basemetals/tags/blocks/scythe_harvestable.json @@ -116,4 +116,4 @@ "minecraft:small_dripleaf", "minecraft:hanging_roots" ] -} \ No newline at end of file +} diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index 041016fa7..c3bc90957 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,7 +1,7 @@ { "pack": { "description": "${mod_id} resources", - "pack_format": 13, - "forge:server_data_pack_format": 12 + "pack_format": 15, + "forge:server_data_pack_format": 15 } } diff --git a/src/packagedClientSmoke/java/zone/moddev/mc/basemetals/smoke/RemoteLoginSmoke.java b/src/packagedClientSmoke/java/zone/moddev/mc/basemetals/smoke/RemoteLoginSmoke.java index faa488501..b3f33515e 100644 --- a/src/packagedClientSmoke/java/zone/moddev/mc/basemetals/smoke/RemoteLoginSmoke.java +++ b/src/packagedClientSmoke/java/zone/moddev/mc/basemetals/smoke/RemoteLoginSmoke.java @@ -18,6 +18,7 @@ public final class RemoteLoginSmoke { private static boolean connecting; private static boolean completed; private static int ticks; + private static int titleTicks; private static int joinedTicks; private RemoteLoginSmoke() {} @@ -30,10 +31,13 @@ public static void tick(TickEvent.ClientTickEvent event) { + (minecraft.screen == null ? "gameplay" : minecraft.screen.getClass().getName())); boolean reject = Boolean.getBoolean("basemetals.expectModeReject"); if (!connecting && minecraft.screen instanceof TitleScreen) { + // Wait until the loading overlay is gone, as a player would have to. + if (minecraft.getOverlay() != null || ++titleTicks < 20) return; + connecting = true; String address = System.getProperty("basemetals.remoteAddress"); ConnectScreen.startConnecting(minecraft.screen, minecraft, ServerAddress.parseString(address), - new net.minecraft.client.multiplayer.ServerData("Base Metals smoke", address, false)); + new net.minecraft.client.multiplayer.ServerData("Base Metals smoke", address, false), false); } if (minecraft.player != null && minecraft.level != null) { if (reject) throw new IllegalStateException("MODE_LOGIN_SMOKE FAIL: mismatched modes entered gameplay"); diff --git a/src/packagedClientSmoke/packaged-resources/META-INF/mods.toml b/src/packagedClientSmoke/packaged-resources/META-INF/mods.toml index c9508b8a1..3eb7a3092 100644 --- a/src/packagedClientSmoke/packaged-resources/META-INF/mods.toml +++ b/src/packagedClientSmoke/packaged-resources/META-INF/mods.toml @@ -10,13 +10,13 @@ displayName="Base Metals packaged client smoke" [[dependencies.basemetals_client_smoke]] modId="basemetals" mandatory=true -versionRange="[3.1.0.119041]" +versionRange="[3.1.0.120011]" ordering="AFTER" side="CLIENT" [[dependencies.basemetals_client_smoke]] modId="orespawn" mandatory=true -versionRange="[4.1.0.119041]" +versionRange="[4.1.0.120011]" ordering="AFTER" side="CLIENT" diff --git a/src/packagedServerSmoke/java/zone/moddev/mc/basemetals/smoke/PackagedServerSmoke.java b/src/packagedServerSmoke/java/zone/moddev/mc/basemetals/smoke/PackagedServerSmoke.java index 5b5e21ec2..8cf478fed 100644 --- a/src/packagedServerSmoke/java/zone/moddev/mc/basemetals/smoke/PackagedServerSmoke.java +++ b/src/packagedServerSmoke/java/zone/moddev/mc/basemetals/smoke/PackagedServerSmoke.java @@ -38,7 +38,7 @@ public PackagedServerSmoke() { public void onServerStarted(ServerStartedEvent event) throws IOException { String baseMetals = version("basemetals"); String oreSpawn = version("orespawn"); - if (!"3.1.0.119041".equals(baseMetals) || !"4.1.0.119041".equals(oreSpawn)) { + if (!"3.1.0.120011".equals(baseMetals) || !"4.1.0.120011".equals(oreSpawn)) { throw new IllegalStateException("PACKAGED_SERVER_SMOKE FAIL versions basemetals=" + baseMetals + " orespawn=" + oreSpawn); } @@ -160,11 +160,12 @@ private static void verifyOreSample(net.minecraft.server.MinecraftServer server) private static void verifyTinkers() { var mode = zone.moddev.mc.basemetals.config.BaseMetalsConfig.activeMode(); var registry = slimeknights.tconstruct.library.materials.MaterialRegistry.getInstance(); - for (String material : new String[] {"adamantine", "steel", "pewter", "silver"}) { + for (String material : zone.moddev.mc.basemetals.material.MaterialCatalogue.BY_NAME.keySet()) { var id = new slimeknights.tconstruct.library.materials.definition.MaterialId("basemetals:" + material); - for (String stat : new String[] {"head", "limb", "grip"}) { - boolean expected = zone.moddev.mc.basemetals.compat.TinkersContentPolicy.allows(mode, material, - "tconstruct:" + stat); + for (String stat : new String[] {"head", "handle", "limb", "grip"}) { + boolean expected = zone.moddev.mc.basemetals.material.MaterialCatalogue.get(material).hasEquipment() + && zone.moddev.mc.basemetals.compat.TinkersContentPolicy.allows(mode, material, + "tconstruct:" + stat); if ("steel".equals(material) && Boolean.getBoolean("basemetals.tinkersOverrideSmoke")) expected = true; boolean present = registry.getMaterialStats(id, new slimeknights.tconstruct.library.materials.stats.MaterialStatsId("tconstruct:" + stat)).isPresent(); diff --git a/src/packagedServerSmoke/packaged-resources/META-INF/mods.toml b/src/packagedServerSmoke/packaged-resources/META-INF/mods.toml index d0f206c87..f6e6d50c8 100644 --- a/src/packagedServerSmoke/packaged-resources/META-INF/mods.toml +++ b/src/packagedServerSmoke/packaged-resources/META-INF/mods.toml @@ -10,13 +10,13 @@ displayName="Base Metals packaged server smoke" [[dependencies.basemetals_server_smoke]] modId="basemetals" mandatory=true -versionRange="[3.1.0.119041]" +versionRange="[3.1.0.120011]" ordering="AFTER" side="SERVER" [[dependencies.basemetals_server_smoke]] modId="orespawn" mandatory=true -versionRange="[4.1.0.119041]" +versionRange="[4.1.0.120011]" ordering="AFTER" side="SERVER" diff --git a/src/smoke/datapack/pack.mcmeta b/src/smoke/datapack/pack.mcmeta index e7963e966..53bc7859e 100644 --- a/src/smoke/datapack/pack.mcmeta +++ b/src/smoke/datapack/pack.mcmeta @@ -1,6 +1,6 @@ { "pack": { - "pack_format": 12, + "pack_format": 15, "description": "Base Metals custom ordinary dimension smoke fixture" } } diff --git a/src/test/java/zone/moddev/mc/basemetals/data/IntegrationContentPolicyTest.java b/src/test/java/zone/moddev/mc/basemetals/data/IntegrationContentPolicyTest.java index 01ad8723a..4d9630884 100644 --- a/src/test/java/zone/moddev/mc/basemetals/data/IntegrationContentPolicyTest.java +++ b/src/test/java/zone/moddev/mc/basemetals/data/IntegrationContentPolicyTest.java @@ -41,7 +41,9 @@ private static void collect(JsonElement value, Set outputs) { if (value == null) return; if (value.isJsonArray()) value.getAsJsonArray().forEach(entry -> collect(entry, outputs)); else if (value.isJsonObject() && value.getAsJsonObject().has("item")) { - outputs.add(value.getAsJsonObject().get("item").getAsString()); + var item = value.getAsJsonObject().get("item"); + if (item.isJsonPrimitive()) outputs.add(item.getAsString()); + else collect(item, outputs); } } } diff --git a/src/test/java/zone/moddev/mc/basemetals/data/Minecraft1201ResourceTest.java b/src/test/java/zone/moddev/mc/basemetals/data/Minecraft1201ResourceTest.java new file mode 100644 index 000000000..6968e79d0 --- /dev/null +++ b/src/test/java/zone/moddev/mc/basemetals/data/Minecraft1201ResourceTest.java @@ -0,0 +1,65 @@ +package zone.moddev.mc.basemetals.data; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.junit.jupiter.api.Test; + +class Minecraft1201ResourceTest { + @Test + void resourceAndDataPacksUseThe1201Format() throws Exception { + var pack = read("src/main/resources/pack.mcmeta").getAsJsonObject("pack"); + assertEquals(15, pack.get("pack_format").getAsInt()); + assertEquals(15, pack.get("forge:server_data_pack_format").getAsInt()); + } + + @Test + void scythesKeepTheirPlantAndCobwebHarvestingTag() throws Exception { + var values = read("src/main/resources/data/basemetals/tags/blocks/scythe_harvestable.json") + .getAsJsonArray("values"); + var blocks = new HashSet(); + values.forEach(value -> blocks.add(value.getAsString())); + + for (String block : new String[] {"#minecraft:leaves", "#minecraft:flowers", "#minecraft:crops", + "minecraft:cobweb", "minecraft:melon", "minecraft:pumpkin", "minecraft:moss_block"}) { + assertTrue(blocks.contains(block), block); + } + + assertFalse(blocks.contains("minecraft:stone")); + assertFalse(Files.exists(Path.of( + "src/generated/resources/data/basemetals/tags/blocks/scythe_harvestable.json"))); + } + + @Test + void tinkersStatsUseRegistered1201Types() throws Exception { + var stats = read("src/generated/resources/data/basemetals/tinkering/materials/stats/steel.json") + .getAsJsonObject("stats"); + assertFalse(stats.has("tconstruct:extra")); + for (String type : new String[] {"head", "handle", "limb", "grip"}) { + assertTrue(stats.has("tconstruct:" + type), type); + } + } + + @Test + void enderIoUsesItsNativeRecipeAndOutputNames() throws Exception { + var recipe = read("src/generated/resources/data/basemetals/recipes/compat/enderio/tin/ore_sag_milling.json"); + assertEquals("enderio:sag_milling", recipe.get("type").getAsString()); + + var output = recipe.getAsJsonArray("outputs").get(0).getAsJsonObject(); + assertEquals(1.0F, output.get("chance").getAsFloat()); + assertFalse(output.get("optional").getAsBoolean()); + assertEquals("basemetals:tin_powder", output.getAsJsonObject("item").get("item").getAsString()); + assertEquals(2, output.getAsJsonObject("item").get("count").getAsInt()); + } + + private static JsonObject read(String path) throws Exception { + return JsonParser.parseString(Files.readString(Path.of(path))).getAsJsonObject(); + } +} diff --git a/src/test/java/zone/moddev/mc/basemetals/data/ResourceIntegrityTest.java b/src/test/java/zone/moddev/mc/basemetals/data/ResourceIntegrityTest.java index 88a8c27f9..7f59fd053 100644 --- a/src/test/java/zone/moddev/mc/basemetals/data/ResourceIntegrityTest.java +++ b/src/test/java/zone/moddev/mc/basemetals/data/ResourceIntegrityTest.java @@ -322,7 +322,7 @@ void crushingAliasesAndOptionalProcessingContractsAreGenerated() throws Exceptio assertConditionalRecipe("compat/mekanism/adamantine/ore_to_dirty_slurry", "mekanism"); assertConditionalRecipe("compat/thermal/alloys/bronze", "thermal_expansion"); assertConditionalRecipe("compat/tconstruct/alloys/bronze", "tconstruct"); - assertConditionalRecipe("compat/enderio/alloys/bronze", "enderio_machines"); + assertConditionalRecipe("compat/enderio/alloys/bronze", "enderio"); } @Test diff --git a/src/test/java/zone/moddev/mc/basemetals/network/ConnectionReadInterestTest.java b/src/test/java/zone/moddev/mc/basemetals/network/ConnectionReadInterestTest.java new file mode 100644 index 000000000..b09d09aa1 --- /dev/null +++ b/src/test/java/zone/moddev/mc/basemetals/network/ConnectionReadInterestTest.java @@ -0,0 +1,179 @@ +package zone.moddev.mc.basemetals.network; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.nio.channels.SelectionKey; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.DefaultSocketChannelConfig; +import io.netty.channel.socket.nio.NioSocketChannel; +import org.junit.jupiter.api.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +class ConnectionReadInterestTest { + private static final Path PATCH = Path.of("src/main/resources/coremods/connection_read_interest.js"); + + @Test + void protocolReadChangesStayOnTheEventLoop() throws Exception { + ClassNode connection = connectionClass(); + assertEquals(1, autoReadCalls(method(connection, "sendPacket"))); + assertEquals(0, autoReadCalls(method(connection, "doSendPacket"))); + + transform(connection); + + assertEquals(0, autoReadCalls(method(connection, "sendPacket"))); + assertEquals(1, autoReadCalls(method(connection, "doSendPacket"))); + assertTrue(Files.readString(Path.of("src/main/resources/META-INF/coremods.json")) + .contains("coremods/connection_read_interest.js")); + } + + @Test + void applyingThePatchTwiceDoesNotDuplicateTheReadChange() throws Exception { + ClassNode connection = connectionClass(); + transform(connection); + transform(connection); + + assertEquals(0, autoReadCalls(method(connection, "sendPacket"))); + assertEquals(1, autoReadCalls(method(connection, "doSendPacket"))); + } + + @Test + void anUnexpectedMinecraftMethodFailsClearly() throws Exception { + ClassNode connection = connectionClass(); + method(connection, "doSendPacket").desc = "()V"; + assertThrows(ScriptException.class, () -> transform(connection)); + } + + @Test + void serializingAutoReadChangesPreventsTheLostReadInterest() throws Exception { + NioEventLoopGroup group = new NioEventLoopGroup(1); + DelayedReadChannel channel = new DelayedReadChannel(); + + try (ServerSocket listener = new ServerSocket(0)) { + new Bootstrap().group(group).channelFactory(() -> channel) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel socket) {} + }).connect(new InetSocketAddress("127.0.0.1", listener.getLocalPort())).sync(); + + try (var peer = listener.accept()) { + channel.eventLoop().submit(() -> {}).sync(); + assertTrue((channel.readInterest() & SelectionKey.OP_READ) != 0); + + // Pause the off-thread disable after the flag changes, but before Netty clears OP_READ. + channel.delayClear = true; + Thread sender = new Thread(() -> channel.config().setAutoRead(false)); + sender.start(); + assertTrue(channel.clearing.await(5, TimeUnit.SECONDS)); + channel.eventLoop().submit(() -> channel.config().setAutoRead(true)).sync(); + channel.releaseClear.countDown(); + sender.join(5000); + channel.eventLoop().submit(() -> {}).sync(); + + assertTrue(channel.config().isAutoRead()); + assertEquals(0, channel.readInterest() & SelectionKey.OP_READ, + "The original off-thread disable can erase the newer read request"); + + channel.delayClear = false; + channel.eventLoop().submit(() -> { + channel.config().setAutoRead(false); + channel.config().setAutoRead(true); + }).sync(); + + assertTrue((channel.readInterest() & SelectionKey.OP_READ) != 0, + "Changing both flags on the event loop must leave reads enabled"); + } + } finally { + channel.releaseClear.countDown(); + channel.close().sync(); + group.shutdownGracefully(0, 5, TimeUnit.SECONDS).sync(); + } + } + + private static ClassNode connectionClass() throws Exception { + try (var bytes = ConnectionReadInterestTest.class.getClassLoader() + .getResourceAsStream("net/minecraft/network/Connection.class")) { + assertNotNull(bytes); + ClassNode node = new ClassNode(); + new ClassReader(bytes).accept(node, 0); + return node; + } + } + + private static MethodNode method(ClassNode node, String name) { + return node.methods.stream().filter(method -> method.name.equals(name)).findFirst().orElseThrow(); + } + + private static long autoReadCalls(MethodNode method) { + long count = 0; + for (var instruction : method.instructions) { + if (instruction instanceof MethodInsnNode call && call.name.equals("setAutoRead")) { + ++count; + } + } + return count; + } + + private static void transform(ClassNode node) throws Exception { + ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); + assertNotNull(engine); + String script = Files.readString(PATCH).replace( + "Java.type('net.minecraftforge.coremod.api.ASMAPI')", + "({mapMethod: function(name) { return ({m_129520_: 'sendPacket', " + + "m_243087_: 'doSendPacket', m_129498_: 'setProtocol'})[name] || name; }})"); + engine.eval(script); + engine.put("connection", node); + engine.eval("initializeCoreMod().basemetals_connection_read_interest.transformer(connection)"); + } + + private static final class DelayedReadChannel extends NioSocketChannel { + private final CountDownLatch clearing = new CountDownLatch(1); + private final CountDownLatch releaseClear = new CountDownLatch(1); + private volatile boolean delayClear; + private final DefaultSocketChannelConfig config = new DefaultSocketChannelConfig(this, javaChannel().socket()) { + @Override + protected void autoReadCleared() { + if (delayClear) { + clearing.countDown(); + try { + if (!releaseClear.await(5, TimeUnit.SECONDS)) { + throw new IllegalStateException("Timed out arranging the read-interest race"); + } + } catch (InterruptedException interrupted) { + Thread.currentThread().interrupt(); + throw new IllegalStateException(interrupted); + } + } + clearReadPending(); + } + }; + + @Override + public DefaultSocketChannelConfig config() { + return config; + } + + int readInterest() { + return selectionKey().interestOps(); + } + } +} diff --git a/tools/validation/README.md b/tools/validation/README.md index 1370028d9..43e3d5418 100644 --- a/tools/validation/README.md +++ b/tools/validation/README.md @@ -1,6 +1,6 @@ # World upgrade checks -`test_legacy_worlds.js` starts the packaged Forge 1.19.4 server against a fresh +`test_legacy_worlds.js` starts the packaged Forge 1.20.1 server against a fresh copy of each supplied world, then starts it again to check the saved result. It refuses to overwrite an existing test directory and fingerprints every source world before and after the test. diff --git a/tools/validation/test_content_modes.js b/tools/validation/test_content_modes.js index 220abe511..ae3d8505d 100644 --- a/tools/validation/test_content_modes.js +++ b/tools/validation/test_content_modes.js @@ -29,7 +29,7 @@ function hash(file) { function prepare(directory, mode, probe) { fs.mkdirSync(path.join(directory, 'mods'), { recursive: true }); - // A fresh 1.19 client otherwise stops at the accessibility onboarding screen. + // Skip first-run accessibility setup in the automated client. fs.writeFileSync(path.join(directory, 'options.txt'), 'fullscreen:false\nonboardAccessibility:false\n'); for (const artifact of [spec.modJar, spec.oreSpawn, probe]) { const file = typeof artifact === 'string' ? artifact : artifact.path; @@ -94,7 +94,7 @@ function verifyConfig(directory, initial) { (async () => { const results = []; - const client = clientLaunch(spec.clientRuntime, '1.19.4', '45.4.0'); + const client = clientLaunch(spec.clientRuntime, '1.20.1', '47.4.10'); const selected = spec.cases ? cases.filter(([name]) => spec.cases.includes(name)) : cases; if (!selected.length || (spec.cases && selected.length !== spec.cases.length)) { throw new Error('The requested mode-test case is unknown or duplicated.'); @@ -110,16 +110,18 @@ function verifyConfig(directory, initial) { fs.writeFileSync(path.join(serverDir, 'eula.txt'), 'eula=true\n'); fs.writeFileSync(path.join(serverDir, 'server.properties'), `online-mode=false\nserver-ip=127.0.0.1\nserver-port=${port}\nview-distance=3\n` - + 'simulation-distance=3\nlevel-type=flat\ngenerate-structures=false\nmax-tick-time=-1\n'); + + 'simulation-distance=3\nlevel-type=minecraft:flat\n' + + 'generator-settings={"layers":[{"block":"minecraft:bedrock","height":1},{"block":"minecraft:dirt","height":2},{"block":"minecraft:grass_block","height":1}],"biome":"minecraft:plains"}\n' + + 'generate-structures=false\nmax-tick-time=-1\n'); const server = launch(serverDir, ['-Xms256m', '-Xmx2g', '-Dbasemetals.awaitLoginSmoke=true', - ...serverArguments(spec.serverRuntime, serverDir, '1.19.4', '45.4.0')], 'server.log'); + ...serverArguments(spec.serverRuntime, serverDir, '1.20.1', '47.4.10')], 'server.log'); let player; try { await waitFor(server, 'BASEMETALS_PACKAGED_SERVER_SMOKE PASS', 240000); player = launch(clientDir, ['-Xms512m', '-Xmx2g', '-Dbasemetals.remoteLoginSmoke=true', `-Dbasemetals.remoteAddress=127.0.0.1:${port}`, `-Dbasemetals.expectModeReject=${!!reject}`, ...client.args, ...client.metadata.arguments.game, - '--username', 'BMValidation', '--version', '1.19.4', '--gameDir', clientDir, + '--username', 'BMValidation', '--version', '1.20.1', '--gameDir', clientDir, '--assetsDir', path.join(spec.clientRuntime, 'assets'), '--assetIndex', client.vanilla.assetIndex.id, '--uuid', '00000000000000000000000000000118', '--accessToken', '0', '--userType', 'legacy'], 'client.log'); await waitFor(player, 'BASEMETALS_MODE_LOGIN_CLIENT PASS result=' + (reject ? 'rejected' : 'connected'), 240000); diff --git a/tools/validation/test_legacy_worlds.js b/tools/validation/test_legacy_worlds.js index 7c30a1658..b279fa91a 100644 --- a/tools/validation/test_legacy_worlds.js +++ b/tools/validation/test_legacy_worlds.js @@ -71,6 +71,10 @@ function checkUpgradeLog(log) { 'minecraft:zombie_pigman', 'minecraft:zombie_pigman_spawn_egg', 'minecraft:golem_last_seen_time', 'minecraft:opened_doors', 'minecraft:nitwit', 'minecraft:unemployed', + 'minecraft:pottery_shard_archer', 'minecraft:pottery_shard_arms_up', + 'minecraft:pottery_shard_prize', 'minecraft:pottery_shard_skull', + 'minecraft:item.brush.brush_sand_completed', 'minecraft:item.brush.brushing', + 'minecraft:music.overworld.jungle_and_forest', 'minecraft:entity.zombie_pigman.ambient', 'minecraft:entity.zombie_pigman.angry', 'minecraft:entity.zombie_pigman.death', 'minecraft:entity.zombie_pigman.hurt', 'minecraft:music.nether', 'minecraft:item.sweet_berries.pick_from_bush' @@ -123,7 +127,7 @@ async function run(directory, launcher, label, marker, properties = []) { const output = fs.createWriteStream(path.join(directory, label + '.log')); const executable = phase === 'capture' ? spec.java8 : spec.java17; const launchArguments = phase === 'capture' ? ['-jar', launcher, 'nogui'] - : serverArguments(spec.serverRuntime, directory, '1.19.4', '45.4.0'); + : serverArguments(spec.serverRuntime, directory, '1.20.1', '47.4.10'); const child = spawn(executable, ['-Xms256m', '-Xmx2g', ...properties, ...launchArguments], { cwd: directory, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] }); child.stdout.pipe(output, { end: false }); @@ -203,6 +207,5 @@ async function run(directory, launcher, label, marker, properties = []) { if (!profile.fresh) results.push(profile.id + ' source_sha256=' + before + ' unchanged=true'); } fs.writeFileSync(path.join(root, phase + '-results.txt'), results.join('\n') + '\n'); - fs.writeFileSync(path.join(root, phase + '-results.txt'), results.join('\n') + '\n'); } })().catch(error => { console.error(error); process.exitCode = 1; });