diff --git a/.gitattributes b/.gitattributes index 0fc4431..3d582df 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,4 @@ *.ogg binary *.jar binary *.zip binary +ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.patch whitespace=-blank-at-eol,-blank-at-eof diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28d19ce..b55cf3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,16 @@ -name: baseSteam 1.18 CI +name: baseSteam 1.19 CI on: push: - branches: [master-1.18.2, feature/basesteam-1.18.2-port] + branches: [master-1.19.4, feature/basesteam-1.19.4-port] pull_request: - branches: [master-1.18.2] + branches: [master-1.19.4] permissions: contents: read concurrency: - group: basesteam-1.18-${{ github.workflow }}-${{ github.ref }} + group: basesteam-1.19-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: @@ -28,19 +28,19 @@ jobs: power_ref=$(sed -n 's/^poweradvantage_source_ref=//p' gradle.properties | tr -d '\r') power_hash=$(sed -n 's/^poweradvantage_deobf_sha256=//p' gradle.properties | tr -d '\r') if [[ ! "$power_ref" =~ ^[a-f0-9]{40}$ || ! "$power_hash" =~ ^[A-F0-9]{64}$ ]]; then - echo "::error::The exact basePower 1.18 dependency pin is pending." + echo "::error::The exact basePower 1.19 dependency pin is pending." exit 1 fi bm_ref=$(sed -n 's/^basemetals_source_commit=//p' gradle.properties | tr -d '\r') bm_hash=$(sed -n 's/^basemetals_deobf_sha256=//p' gradle.properties | tr -d '\r') if [[ ! "$bm_ref" =~ ^[a-f0-9]{40}$ || ! "$bm_hash" =~ ^[A-F0-9]{64}$ ]]; then - echo "::error::The exact Base Metals 1.18 dependency pin is pending." + echo "::error::The exact Base Metals 1.19 dependency pin is pending." exit 1 fi os_ref=$(sed -n 's/^orespawn_source_commit=//p' gradle.properties | tr -d '\r') os_hash=$(sed -n 's/^orespawn_deobf_sha256=//p' gradle.properties | tr -d '\r') if [[ ! "$os_ref" =~ ^[a-f0-9]{40}$ || ! "$os_hash" =~ ^[A-F0-9]{64}$ ]]; then - echo "::error::The exact OreSpawn 1.18 dependency pin is pending." + echo "::error::The exact OreSpawn 1.19 dependency pin is pending." exit 1 fi echo "ORESPAWN_SOURCE_REF=$os_ref" >> "$GITHUB_ENV" @@ -61,7 +61,7 @@ jobs: repository: MinecraftModDevelopmentMods/BaseMetals ref: ${{ env.BASEMETALS_SOURCE_REF }} path: .ci-dependencies/BaseMetals - - name: Check out basePower 1.18.2 + - name: Check out basePower 1.19.4 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: MinecraftModDevelopmentMods/basePower @@ -91,7 +91,7 @@ jobs: -Porg.gradle.java.installations.auto-download=false" >> "$GITHUB_ENV" - name: Set up Gradle uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 - - name: Build 1.18.2 dependency chain + - name: Build 1.19.4 dependency chain id: dependencies shell: bash run: | @@ -102,32 +102,49 @@ jobs: test "$(git -C "$orespawn" rev-parse HEAD)" = "$ORESPAWN_SOURCE_REF" test "$(git -C "$power" rev-parse HEAD)" = "$POWERADVANTAGE_SOURCE_REF" test "$(git -C "$basemetals" rev-parse HEAD)" = "$BASEMETALS_SOURCE_REF" + mavenizer="$orespawn/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar" + printf '%s %s\n' '341B3FB10ABA6EA7DC612FBE519DC290A1C81867AED5DD320ACD783688AC49DE' "$mavenizer" | sha256sum -c - chmod +x "$orespawn/gradlew" "$basemetals/gradlew" "$power/gradlew" (cd "$orespawn" && ./gradlew $MMD_GRADLE_TOOLCHAINS jar --no-daemon --stacktrace) (cd "$basemetals" && ./gradlew $MMD_GRADLE_TOOLCHAINS jar --no-daemon --stacktrace) - osjar="$orespawn/build/libs-dev/OreSpawn-4.1.0.118021-deobf.jar" - bmjar="$basemetals/build/libs-dev/BaseMetals-3.1.2.118021-deobf.jar" + osjar="$orespawn/build/libs-dev/OreSpawn-4.1.0.119041-deobf.jar" + bmjar="$basemetals/build/libs-dev/BaseMetals-3.1.2.119041-deobf.jar" (cd "$power" && ./gradlew $MMD_GRADLE_TOOLCHAINS jar unitBootstrapJar verifyReleaseDependencies \ + "-Pforge45MavenizerJar=$mavenizer" \ -PbaseMetalsDeobfJar="$bmjar" -PoreSpawnDeobfJar="$osjar" \ --no-daemon --stacktrace) - powerjar="$power/build/libs-dev/basePower-2.4.10.118021-deobf.jar" + powerjar="$power/build/libs-dev/basePower-2.4.11.119041-deobf.jar" bootstrap="$power/build/unit-bootstrap/forge-native-unit-bootstrap.jar" test -f "$osjar" && test -f "$bmjar" && test -f "$powerjar" && test -f "$bootstrap" - version="$(sed -n 's/^poweradvantage_version=//p' gradle.properties | tr -d '\r')" - [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.118021$ ]] - release="$RUNNER_TEMP/basePower-$version.jar" - curl --fail --silent --show-error --location --retry 3 --output "$release" \ - "https://maven.moddev.zone/releases/zone/moddev/mc/basepower/basePower/$version/basePower-$version.jar" - power_release_sha=$(sed -n 's/^poweradvantage_player_sha256=//p' gradle.properties | tr -d '\r') - printf '%s %s\n' "$power_release_sha" "$release" | sha256sum --check echo "orespawn=$osjar" >> "$GITHUB_OUTPUT" echo "basemetals=$bmjar" >> "$GITHUB_OUTPUT" echo "power=$powerjar" >> "$GITHUB_OUTPUT" echo "bootstrap=$bootstrap" >> "$GITHUB_OUTPUT" + echo "mavenizer=$mavenizer" >> "$GITHUB_OUTPUT" + - name: Download the released player jars + id: player + shell: bash + run: | + set -euo pipefail + directory="$RUNNER_TEMP/base-steam-published" + mkdir "$directory" + fetch() { + curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' \ + --output "$directory/$1.jar" "$2" + echo "$1=$directory/$1.jar" >> "$GITHUB_OUTPUT" + } + maven="https://maven.moddev.zone/releases/zone/moddev/mc" + fetch power "$maven/basepower/basePower/2.4.11.119041/basePower-2.4.11.119041.jar?ci=$GITHUB_RUN_ID" + fetch metals "$maven/basemetals/BaseMetals/3.1.2.119041/BaseMetals-3.1.2.119041.jar?ci=$GITHUB_RUN_ID" + fetch orespawn 'https://www.curseforge.com/api/v1/mods/245586/files/9171353/download' - name: Build and audit release artifacts run: >- - ./gradlew $MMD_GRADLE_TOOLCHAINS clean check build javadoc verifyReleaseDependencies verifyReleaseArtifacts + ./gradlew $MMD_GRADLE_TOOLCHAINS clean check build javadoc verifyReleaseDependencies verifyPlayerDependencies verifyReleaseArtifacts writeReleaseChecksums verifyEclipseProductionClasspath + "-Pforge45MavenizerJar=${{ steps.dependencies.outputs.mavenizer }}" + "-PpowerAdvantagePlayerJar=${{ steps.player.outputs.power }}" + "-PbaseMetalsPlayerJar=${{ steps.player.outputs.metals }}" + "-PoreSpawnPlayerJar=${{ steps.player.outputs.orespawn }}" -PforgeCapabilityBootstrapJar=${{ steps.dependencies.outputs.bootstrap }} -PpowerAdvantageDeobfJar=${{ steps.dependencies.outputs.power }} -PbaseMetalsDeobfJar=${{ steps.dependencies.outputs.basemetals }} @@ -136,20 +153,20 @@ jobs: - name: Upload audited release candidate uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: baseSteam-1.18.2-${{ github.sha }} + name: baseSteam-1.19.4-${{ github.sha }} if-no-files-found: error retention-days: 30 path: | - build/libs/baseSteam-2.2.11.118021.jar - build/libs/baseSteam-2.2.11.118021-sources.jar - build/libs/baseSteam-2.2.11.118021-javadoc.jar + build/libs/baseSteam-2.2.12.119041.jar + build/libs/baseSteam-2.2.12.119041-sources.jar + build/libs/baseSteam-2.2.12.119041-javadoc.jar build/release/SHA256SUMS CHANGELOG.txt - name: Upload diagnostics on failure if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: baseSteam-1.18.2-diagnostics-${{ github.sha }} + name: baseSteam-1.19.4-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 d17fa79..2d5d7dc 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -2,9 +2,9 @@ name: CodeQL on: push: - branches: [master-1.18.2, feature/basesteam-1.18.2-port] + branches: [master-1.19.4, feature/basesteam-1.19.4-port] pull_request: - branches: [master-1.18.2] + branches: [master-1.19.4] schedule: - cron: '53 7 * * 4' @@ -28,19 +28,19 @@ jobs: power_ref=$(sed -n 's/^poweradvantage_source_ref=//p' gradle.properties | tr -d '\r') power_hash=$(sed -n 's/^poweradvantage_deobf_sha256=//p' gradle.properties | tr -d '\r') if [[ ! "$power_ref" =~ ^[a-f0-9]{40}$ || ! "$power_hash" =~ ^[A-F0-9]{64}$ ]]; then - echo "::error::The exact basePower 1.18 dependency pin is pending." + echo "::error::The exact basePower 1.19 dependency pin is pending." exit 1 fi bm_ref=$(sed -n 's/^basemetals_source_commit=//p' gradle.properties | tr -d '\r') bm_hash=$(sed -n 's/^basemetals_deobf_sha256=//p' gradle.properties | tr -d '\r') if [[ ! "$bm_ref" =~ ^[a-f0-9]{40}$ || ! "$bm_hash" =~ ^[A-F0-9]{64}$ ]]; then - echo "::error::The exact Base Metals 1.18 dependency pin is pending." + echo "::error::The exact Base Metals 1.19 dependency pin is pending." exit 1 fi os_ref=$(sed -n 's/^orespawn_source_commit=//p' gradle.properties | tr -d '\r') os_hash=$(sed -n 's/^orespawn_deobf_sha256=//p' gradle.properties | tr -d '\r') if [[ ! "$os_ref" =~ ^[a-f0-9]{40}$ || ! "$os_hash" =~ ^[A-F0-9]{64}$ ]]; then - echo "::error::The exact OreSpawn 1.18 dependency pin is pending." + echo "::error::The exact OreSpawn 1.19 dependency pin is pending." exit 1 fi echo "ORESPAWN_SOURCE_REF=$os_ref" >> "$GITHUB_ENV" @@ -61,7 +61,7 @@ jobs: repository: MinecraftModDevelopmentMods/BaseMetals ref: ${{ env.BASEMETALS_SOURCE_REF }} path: .ci-dependencies/BaseMetals - - name: Check out basePower 1.18.2 + - name: Check out basePower 1.19.4 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: MinecraftModDevelopmentMods/basePower @@ -91,7 +91,7 @@ jobs: -Porg.gradle.java.installations.auto-download=false" >> "$GITHUB_ENV" - name: Set up Gradle uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 - - name: Build 1.18.2 dependency chain + - name: Build 1.19.4 dependency chain id: dependencies shell: bash run: | @@ -102,21 +102,25 @@ jobs: test "$(git -C "$orespawn" rev-parse HEAD)" = "$ORESPAWN_SOURCE_REF" test "$(git -C "$power" rev-parse HEAD)" = "$POWERADVANTAGE_SOURCE_REF" test "$(git -C "$basemetals" rev-parse HEAD)" = "$BASEMETALS_SOURCE_REF" + mavenizer="$orespawn/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar" + printf '%s %s\n' '341B3FB10ABA6EA7DC612FBE519DC290A1C81867AED5DD320ACD783688AC49DE' "$mavenizer" | sha256sum -c - chmod +x "$orespawn/gradlew" "$basemetals/gradlew" "$power/gradlew" (cd "$orespawn" && ./gradlew $MMD_GRADLE_TOOLCHAINS jar --no-daemon --stacktrace) (cd "$basemetals" && ./gradlew $MMD_GRADLE_TOOLCHAINS jar --no-daemon --stacktrace) - osjar="$orespawn/build/libs-dev/OreSpawn-4.1.0.118021-deobf.jar" - bmjar="$basemetals/build/libs-dev/BaseMetals-3.1.2.118021-deobf.jar" + osjar="$orespawn/build/libs-dev/OreSpawn-4.1.0.119041-deobf.jar" + bmjar="$basemetals/build/libs-dev/BaseMetals-3.1.2.119041-deobf.jar" (cd "$power" && ./gradlew $MMD_GRADLE_TOOLCHAINS jar unitBootstrapJar verifyReleaseDependencies \ + "-Pforge45MavenizerJar=$mavenizer" \ -PbaseMetalsDeobfJar="$bmjar" -PoreSpawnDeobfJar="$osjar" \ --no-daemon --stacktrace) - powerjar="$power/build/libs-dev/basePower-2.4.10.118021-deobf.jar" + powerjar="$power/build/libs-dev/basePower-2.4.11.119041-deobf.jar" bootstrap="$power/build/unit-bootstrap/forge-native-unit-bootstrap.jar" test -f "$osjar" && test -f "$bmjar" && test -f "$powerjar" && test -f "$bootstrap" echo "orespawn=$osjar" >> "$GITHUB_OUTPUT" echo "basemetals=$bmjar" >> "$GITHUB_OUTPUT" echo "power=$powerjar" >> "$GITHUB_OUTPUT" echo "bootstrap=$bootstrap" >> "$GITHUB_OUTPUT" + echo "mavenizer=$mavenizer" >> "$GITHUB_OUTPUT" - name: Initialize CodeQL uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 with: @@ -125,6 +129,7 @@ jobs: run: | chmod +x ./gradlew ./gradlew $MMD_GRADLE_TOOLCHAINS clean classes --rerun-tasks --no-build-cache --no-daemon --stacktrace \ + "-Pforge45MavenizerJar=${{ steps.dependencies.outputs.mavenizer }}" \ -PforgeCapabilityBootstrapJar=${{ steps.dependencies.outputs.bootstrap }} \ -PpowerAdvantageDeobfJar=${{ steps.dependencies.outputs.power }} \ -PbaseMetalsDeobfJar=${{ steps.dependencies.outputs.basemetals }} \ diff --git a/.github/workflows/validate-gradle-build.yml b/.github/workflows/validate-gradle-build.yml index 6252ede..81f747c 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.18.2 - - feature/basesteam-1.18.2-port + - master-1.19.4 + - feature/basesteam-1.19.4-port pull_request: branches: - - master-1.18.2 + - master-1.19.4 permissions: contents: read diff --git a/CHANGELOG.txt b/CHANGELOG.txt index b3e36b8..66be540 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,8 +1,15 @@ -baseSteam 2.2.11.118021 (Minecraft 1.18.2) +baseSteam 2.2.12.119041 (Minecraft 1.19.4) -- Bring baseSteam to Minecraft 1.18.2, with the same recipes and steam machines. -- Keep saved inventories, steam, drill state and musket wear when upgrading. -- Use the released basePower 2.4.10.118021 and Base Metals 3.1.2 save hook. +- Target Minecraft 1.19.4 / Forge 45.4.0 and Java 17 with basePower 2.4.11.119041. +- Adapt native registrations, rendering, damage, sound, fuel and recipe calls. +- Retain Steam save IDs, machine data and optional Base Metals recipes. +- Build against the released basePower 2.4.11.119041 and test with the Base Metals migration fix. + +baseSteam 2.2.11.118021 (local Minecraft 1.18.2 candidate) + +- Target Minecraft 1.18.2 / Forge 40.3.0 and Java 17 with basePower 2.4.10.118021. +- Use native block-entity saves and update packets and native musket item tags. +- Keep Steam registry names, legacy state keys, and recipe data unchanged. baseSteam 2.2.10.117011 (local Minecraft 1.17.1 candidate) @@ -11,7 +18,6 @@ baseSteam 2.2.10.117011 (local Minecraft 1.17.1 candidate) - Carry over Steam Advantage common settings without overwriting an existing baseSteam config. - Keep Minecraft 1.17.1 machine behavior, models, GUIs, drops and recipe unlocks unchanged. - Fix repeated Eclipse project refreshes without changing the game launch runtime. -- Build and test against the published basePower 2.4.9.117011 release. Steam Advantage 2.2.9.117011 (local 1.17.1 port candidate) diff --git a/README.md b/README.md index 6a045a2..1ddd8fe 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # baseSteam -**Minecraft 1.18.2 (Forge), version 2.2.11.118021.** +**Minecraft 1.19.4 (Forge), version 2.2.12.119041.** baseSteam, formerly Steam Advantage, adds boilers, steam-powered machines, pumps, storage, elevators and a blackpowder musket to basePower. Its items and @@ -11,8 +11,8 @@ blocks use the shared basePower creative tab. Back up your world and test the upgrade on a copy. Replace Steam Advantage and Power Advantage with baseSteam and basePower; do not install both sets of mods. The migration recognizes old Steam Advantage IDs and keeps the registry paths, -machine settings and saved data used by earlier baseSteam versions. The Works -rig checks copied legacy saves, running machinery and cold restarts. +machine settings and saved data used by earlier baseSteam versions. The +upgrade tests run existing boilers, pumps and drills after a full restart. Existing `basesteam-common.toml` settings take precedence. When that file is absent, baseSteam can copy `steamadvantage-common.toml` or import settings from an @@ -20,32 +20,34 @@ older `.cfg` file. The old config is left in place. ## Build Target -Minecraft 1.18.2, Forge 40.3.0, official 1.18.2 mappings and MCP -20220404.173914. ForgeGradle 7.0.34 and Gradle 9.6.1 run on Java 17. +Minecraft 1.19.4, Forge 45.4.0, official 1.19.4 mappings and MCP +20230314.122934. ForgeGradle 7.0.34 and Gradle 9.6.1 run on Java 17. Production compilation and launches use Temurin 17, class major 61. -The build-only Mavenizer fixture uses Java 25 and corrects the known Forge 40 -`Holder.Direct` decompile issue. Java 8 is only for older build utilities. +Mavenizer uses Java 25. The build requires the +[checksum-verified Forge 45 compatibility JAR](ci-fixtures/tools/README.md) +from the local OreSpawn 1.19.4 checkout or `-Pforge45MavenizerJar=`. +It is not tracked here. +Java 8 is only for older build utilities. The mod ID and resource namespace are `basesteam`; the Java implementation namespace is `zone.moddev.mc.basesteam`. The shared `cyano.poweradvantage.api` names remain in use. -basePower 2.4.10.118021 is required. Base Metals 3.1.2.118021 remains optional -in Forge and requires OreSpawn 4.1.0.118021 when installed. The CurseForge +basePower 2.4.11.119041 is required. Base Metals 3.1.2.119041 remains optional +in Forge and requires OreSpawn 4.1.0.119041 when installed. The CurseForge install metadata lists basePower and Base Metals. No dependency is embedded. ## Local Verification Use the checked-in Gradle wrapper and supply explicit paths to the development jars matching the pins in `gradle.properties`. Use a Steam-only Gradle user -home and the test-only Forge capability bootstrap. Player JARs are distinct -from development and published JARs. Before a packaged smoke, run +home and the test-only Forge capability bootstrap. Use the published player +jars for packaged tests, not the development jars. Before a packaged smoke, run `verifyPlayerDependencies` with explicit `powerAdvantagePlayerJar`, `baseMetalsPlayerJar` and `oreSpawnPlayerJar` project properties. -Use the source for the [basePower 2.4.10.118021 release](https://github.com/MinecraftModDevelopmentMods/basePower/releases/tag/2.4.10.118021) -to build the development jar and the test bootstrap. Use the published jar -for packaged testing; its checksum is pinned separately. +Use the released basePower 2.4.11.119041 and the exact dependencies in +`gradle.properties`. The 1.18.2 jars cannot substitute for these inputs. For example, from the Steam checkout in PowerShell: @@ -66,12 +68,12 @@ $env:GRADLE_USER_HOME = $env:BASESTEAM_GRADLE_USER_HOME ``` On other platforms use `./gradlew`. The artifact family is -`baseSteam-2.2.11.118021.jar`, plus sources and Javadoc. The development jar +`baseSteam-2.2.12.119041.jar`, plus sources and Javadoc. The development jar stays under `build/libs-dev`. `node tools/verify-native-port.js` and `node tools/generate-recipe-unlocks.js --check` -run without the dependency jars. [1.18.2 port status](docs/PORT118-STATUS.md) -tracks current checks. [1.17.1 port status](docs/PORT117-STATUS.md) is historical. +run without the dependency jars. [1.19.4 port status](docs/PORT119-STATUS.md) +tracks current checks. [1.18.2 port status](docs/PORT118-STATUS.md) is historical. ## Port Details @@ -79,7 +81,7 @@ Native block-entity factories preserve their actual constructor position and block state. Loading NBT does not replace that position, including when a drill moves along a track. Power's shared native ticker retains per-tick work before the eight-tick power update on both logical sides. The drill-bit helper has its -own native ticker. Minecraft 1.18.2 uses `saveAdditional` for native saves, +own native ticker. Minecraft 1.19.4 uses `saveAdditional` for native saves, and the drill-bit direction packet carries the native block-entity type. The port retains first-loaded-tick redstone sampling in the crusher and furnace, @@ -87,11 +89,13 @@ water-only boiler inputs, dirty-owner callbacks, finite energy and fluid accounting, persistence aliases, helper state and GUI caption rows. Native PoseStack and vertex consumers retain the drill geometry, UVs and animation. Musket terrain traversal stops before unloaded chunks; sound dispatch still -uses loaded server players. +uses loaded server players. High Explosive shots use the TNT drop policy, which +preserves full drops by default and honors the TNT drop-decay gamerule. Native pickaxe tags cover the metal machines and helpers without introducing new minimum tool tiers or changing drop requirements. Ordinary blocks retain -their self-drop tables; hidden helpers still have no item form or drops. +their self-drop tables. Hidden helper block items retain their registry IDs, +stay out of the creative tab, and do not drop from their blocks. Recipe ingredients, processing costs and quantities are unchanged. Recipe-book advancements use native singleton `items` predicates; the ignored legacy `item` field must not turn an ingredient discovery into an unrestricted predicate. diff --git a/build.gradle b/build.gradle index d0f6212..b050e77 100644 --- a/build.gradle +++ b/build.gradle @@ -14,21 +14,22 @@ plugins { id 'net.minecraftforge.gradle' version '7.0.34' } -def mavenizerFixture = file('ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-1.18.jar') -def mavenizerFixtureHash = '28A6697BDF6D9500EC0AF9C1C949B9F3ED7DDFDA649CB5CAC4BEEA2E9567570A' -if (!mavenizerFixture.isFile() || MessageDigest.getInstance('SHA-256').digest(mavenizerFixture.bytes) - .encodeHex().toString().toUpperCase(Locale.ROOT) != mavenizerFixtureHash) { - throw new GradleException('Missing or invalid Forge 40 Mavenizer compatibility fixture') -} -fgtools.configure('mavenizer') { - classpath.from(mavenizerFixture) - mainClass.set('net.minecraftforge.mcmaven.cli.Main') - javaLauncher.set(javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(25) - vendor = JvmVendorSpec.ADOPTIUM - }) +apply from: 'gradle/forge45-mavenizer.gradle' + +def mavenizerFixtureHashes = [ + (file('ci-fixtures/tools/minecraft-source-compatibility.json')): + '33A3217261EBB8F37D45F50BD2F8CE70B221C014A5C15F4C5F8F9091AD6D1EC8', + (file('ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.patch')): + 'D856190DEF5574716C5E4F4379912C451B1F5952B0B111576651A98EECF58544', + (file('ci-fixtures/tools/LICENSE-MAVENIZER.txt')): + '20C17D8B8C48A600800DFD14F95D5CB9FF47066A9641DDEAB48DC54AEC96E331' +] +mavenizerFixtureHashes.each { fixture, expected -> + if (!fixture.isFile() || MessageDigest.getInstance('SHA-256').digest(fixture.bytes) + .encodeHex().toString().toUpperCase(Locale.ROOT) != expected) { + throw new GradleException("Missing or invalid Forge 45 Mavenizer fixture: ${fixture.name}") + } } - group = project.mod_group version = project.mod_version base.archivesName = 'baseSteam' @@ -103,6 +104,10 @@ def normalizeArchiveLineEndings = { details -> } minecraft { + if (gradle.startParameter.offline) { + mavenizerArguments.add('--offline') + mavenizerArguments.add('--cache-only') + } mappings channel: project.mapping_channel, version: project.mapping_version String configuredRunDirectory = providers.gradleProperty('steamAdvantageRunDirectory') .getOrElse('run') @@ -134,7 +139,7 @@ sourceSets.main.resources { def forgeRunModClassesDirectory = layout.buildDirectory.dir('forge-run-mod-classes/main') def prepareForgeRunModClasses = tasks.register('prepareForgeRunModClasses', Sync) { group = 'ide' - description = 'Merges classes and processed resources for Forge 40 launches.' + description = 'Merges classes and processed resources for Forge 45 launches.' dependsOn tasks.named('classes') from sourceSets.main.output into forgeRunModClassesDirectory @@ -204,20 +209,37 @@ tasks.named('processResources', ProcessResources) { filesMatching(archiveTextPatterns, normalizeArchiveLineEndings) } +def committedPin = { String key, pattern -> + Properties pins = new Properties() + file('gradle.properties').withInputStream { pins.load(it) } + String pinned = pins.getProperty(key, '') + if (!(pinned ==~ pattern) || project.property(key) != pinned) { + throw new GradleException("Supply the exact coordinated ${key} in gradle.properties; overrides are not accepted.") + } + pinned +} def forgeCapabilityBootstrapJar = providers.gradleProperty('forgeCapabilityBootstrapJar').map { file(it) } tasks.withType(Test).configureEach { inputs.files(forgeCapabilityBootstrapJar) + inputs.property('powerBootstrapSha256', providers.gradleProperty('poweradvantage_test_bootstrap_sha256')) doFirst { File bootstrapJar = forgeCapabilityBootstrapJar.getOrNull() if (bootstrapJar == null || !bootstrapJar.isFile()) { throw new GradleException('Supply the verified basePower test bootstrap via -PforgeCapabilityBootstrapJar=') } + String expectedHash = committedPin('poweradvantage_test_bootstrap_sha256', /[A-F0-9]{64}/) + String actualHash = MessageDigest.getInstance('SHA-256').digest(bootstrapJar.bytes) + .encodeHex().toString().toUpperCase(Locale.ROOT) + if (actualHash != expectedHash) { + throw new GradleException("basePower test bootstrap SHA-256 is ${actualHash}, expected ${expectedHash}") + } new ZipFile(bootstrapJar).withCloseable { zip -> def manifest = new Manifest(zip.getInputStream(zip.getEntry('META-INF/MANIFEST.MF'))).mainAttributes if (manifest.getValue('Premain-Class') != 'zone.moddev.mc.advantageworks.unit.ForgeCapabilityBootstrap') { throw new GradleException('Unexpected native capability test bootstrap entry point') } } + logger.lifecycle("basePower test bootstrap: ${bootstrapJar.canonicalPath} SHA-256 ${actualHash}") jvmArgs "-javaagent:${bootstrapJar.canonicalPath}" } } @@ -340,9 +362,9 @@ def expectedReleaseFiles = providers.provider { def preparedReleaseDir = providers.gradleProperty('preparedReleaseDir') def verifyPackFormats = { pack -> - if (pack.pack_format != 9 || pack['forge:resource_pack_format'] != 8 - || pack['forge:data_pack_format'] != 9) { - throw new GradleException('Minecraft 1.18.2 requires resource pack format 8 and data pack format 9') + if (pack.pack_format != 13 || pack['forge:server_data_pack_format'] != 12 + || pack.containsKey('forge:resource_pack_format') || pack.containsKey('forge:data_pack_format')) { + throw new GradleException('Minecraft 1.19.4 requires resource pack format 13 and data pack format 12') } } @@ -351,15 +373,15 @@ tasks.register('verifyReleaseConfiguration') { description = 'Validates release identity, target metadata, and dependency policy.' doLast { verifyPackFormats(new JsonSlurper().parse(file('src/main/resources/pack.mcmeta')).pack) - if (project.mod_version != '2.2.11.118021') { + if (project.mod_version != '2.2.12.119041') { throw new GradleException("Unexpected baseSteam release version: ${project.mod_version}") } - if (project.minecraft_version != '1.18.2' || project.minecraft_version_range != '[1.18.2]' - || project.forge_version != '40.3.0' || project.forge_version_range != '[40.3.0,41)' - || project.loader_version_range != '[40,41)' + if (project.minecraft_version != '1.19.4' || project.minecraft_version_range != '[1.19.4]' + || project.forge_version != '45.4.0' || project.forge_version_range != '[45.4.0,46)' + || project.loader_version_range != '[45,46)' || project.mapping_channel != 'official' - || project.mapping_version != '1.18.2' - || project.mcp_version != '20220404.173914') { + || project.mapping_version != '1.19.4' + || project.mcp_version != '20230314.122934') { throw new GradleException('Unexpected Minecraft, Forge, or mappings target') } if (project.loader_name != 'forge' || project.loader_code != '1' @@ -379,27 +401,27 @@ tasks.register('verifyReleaseConfiguration') { } if (project.cf_requirements != 'basepower,base-metals' || project.cf_optionals != '' - || project.cf_versions != '1.18.2') { + || project.cf_versions != '1.19.4') { throw new GradleException('Unexpected CurseForge dependency or game-version metadata') } - if (project.basemetals_version != '3.1.2.118021' - || project.orespawn_version != '4.1.0.118021') { + if (project.basemetals_version != '3.1.2.119041' + || project.orespawn_version != '4.1.0.119041') { throw new GradleException('Unexpected development API versions') } - if (project.poweradvantage_version != '2.4.10.118021' + if (project.poweradvantage_version != '2.4.11.119041' || project.poweradvantage_source_repository != 'MinecraftModDevelopmentMods/basePower') { throw new GradleException('Unexpected basePower compile dependency identity') } String mainSource = file( 'src/main/java/zone/moddev/mc/basesteam/SteamAdvantage.java').getText('UTF-8') if (!mainSource.contains('MODID = "basesteam"') - || !mainSource.contains('VERSION = "2.2.11.118021"')) { + || !mainSource.contains('VERSION = "2.2.12.119041"')) { throw new GradleException('baseSteam source identity changed') } String metadata = file('src/main/resources/META-INF/mods.toml').getText('UTF-8').replace('\r\n', '\n') ['modId="basesteam"', 'displayName="baseSteam"', - 'modId="basepower"\nmandatory=true\nversionRange="[2.4.10.118021,3.0.0)"', - 'modId="basemetals"\nmandatory=false\nversionRange="[3.1.2.118021,4.0.0)"'].each { marker -> + 'modId="basepower"\nmandatory=true\nversionRange="[2.4.11.119041,3.0.0)"', + 'modId="basemetals"\nmandatory=false\nversionRange="[3.1.2.119041,4.0.0)"'].each { marker -> if (!metadata.contains(marker)) { throw new GradleException("Source mods.toml is missing ${marker}") } @@ -407,16 +429,6 @@ tasks.register('verifyReleaseConfiguration') { } } -def committedPin = { String key, pattern -> - Properties pins = new Properties() - file('gradle.properties').withInputStream { pins.load(it) } - String pinned = pins.getProperty(key, '') - if (!(pinned ==~ pattern) || project.property(key) != pinned) { - throw new GradleException("Supply the exact coordinated ${key} in gradle.properties; overrides are not accepted.") - } - pinned -} - def auditJar = { File jarFile, String property, String hashPin, String modId, String expectedVersion, List required -> String expectedHash = committedPin(hashPin, /[A-F0-9]{64}/) @@ -436,9 +448,9 @@ def auditJar = { File jarFile, String property, String hashPin, String modId, St } def manifest = new Manifest(zip.getInputStream(zip.getEntry('META-INF/MANIFEST.MF'))).mainAttributes if (manifest.getValue('Implementation-Version') != expectedVersion - || manifest.getValue('Built-On') != '1.18.2-40.3.0' + || manifest.getValue('Built-On') != "${project.minecraft_version}-${project.forge_version}" || manifest.getValue('Built-On-Java') != '17') { - throw new GradleException("${modId} must be ${expectedVersion} for Forge 40.3.0 and Java 17") + throw new GradleException("${modId} must be ${expectedVersion} for Forge ${project.forge_version} and Java 17") } String metadata = zip.getInputStream(zip.getEntry('META-INF/mods.toml')).getText('UTF-8') if (!metadata.readLines().any { it.startsWith("modId=\"${modId}\"") }) { @@ -448,7 +460,10 @@ def auditJar = { File jarFile, String property, String hashPin, String modId, St logger.lifecycle("${modId}: ${jarFile.canonicalPath} SHA-256 ${actualHash}") } -def baseMetalsApi = ['zone/moddev/mc/basemetals/recipe/CrushingRecipe.class'] +def baseMetalsApi = [ + 'zone/moddev/mc/basemetals/api/migration/Legacy112MigrationExtension.class', + 'zone/moddev/mc/basemetals/recipe/CrushingRecipe.class' +] def oreSpawnApi = ['zone/moddev/mc/orespawn/api/OreSpawnApi.class'] def powerApi = [ 'cyano/poweradvantage/api/PoweredEntity.class', @@ -503,7 +518,6 @@ tasks.register('verifyNativeDevelopmentDependencies') { description = 'Resolves Forge and audits the exact development JARs.' dependsOn tasks.named('verifyCompanionDevelopmentDependencies') doLast { - committedPin('poweradvantage_source_ref', /[a-f0-9]{40}/) auditJar(powerAdvantageDeobfJar, 'powerAdvantageDeobfJar', 'poweradvantage_deobf_sha256', 'basepower', project.poweradvantage_version, powerApi) @@ -537,6 +551,27 @@ tasks.register('verifyReleaseDependencies') { description = 'Audits native development dependencies with the final release source pin.' dependsOn tasks.named('verifyReleaseConfiguration') dependsOn tasks.named('verifyNativeDevelopmentDependencies') + doLast { committedPin('poweradvantage_source_ref', /[a-f0-9]{40}/) } +} + +def blocksAtlasPath = 'assets/minecraft/atlases/blocks.json' +def steamBlocksAtlas = [sources: [[type: 'single', resource: 'basesteam:entity/drill_bit']]] +def verifySteamBlocksAtlas = { Object atlas -> + if (atlas != steamBlocksAtlas) { + throw new GradleException("${blocksAtlasPath} must stitch only basesteam:entity/drill_bit as a single source") + } +} +def verifyPackagedAssetNamespaces = { ZipFile zip -> + String steamAssets = "assets/${project.mod_id}/" + def foreignAssets = zip.entries().findAll { + !it.directory && it.name.startsWith('assets/') && !it.name.startsWith(steamAssets) + }.collect { it.name } + if (foreignAssets != [blocksAtlasPath]) { + throw new GradleException("${zip.name} must contain only ${blocksAtlasPath} outside ${steamAssets}; found ${foreignAssets}") + } + zip.getInputStream(zip.getEntry(blocksAtlasPath)).withCloseable { input -> + verifySteamBlocksAtlas(new JsonSlurper().parse(input, 'UTF-8')) + } } tasks.register('verifyReleaseArtifacts') { @@ -576,6 +611,7 @@ tasks.register('verifyReleaseArtifacts') { File mainJar = new File(libs, expectedReleaseFiles.get()[0]) new ZipFile(mainJar).withCloseable { zip -> + verifyPackagedAssetNamespaces(zip) List names = zip.entries().collect { it.name } ['META-INF/mods.toml', 'pack.mcmeta', 'zone/moddev/mc/basesteam/SteamAdvantage.class', @@ -584,6 +620,8 @@ tasks.register('verifyReleaseArtifacts') { 'zone/moddev/mc/basesteam/machines/GeothermalBoilerTileEntity.class', 'zone/moddev/mc/basesteam/migration/SteamLegacyMigrations.class', 'assets/basesteam/lang/en_us.json', + 'data/basesteam/damage_type/musket.json', + 'data/basesteam/damage_type/steam_machine.json', 'data/basesteam/recipes/steam_pipe.json', 'data/basesteam/advancements/recipes/steam_pipe.json'].each { required -> if (!names.contains(required)) { @@ -601,7 +639,8 @@ tasks.register('verifyReleaseArtifacts') { def resourcePack = new JsonSlurper().parseText(zip.getInputStream(zip.getEntry('pack.mcmeta')) .getText(StandardCharsets.UTF_8.name())) verifyPackFormats(resourcePack.pack) - ['agent-notes/', '.codex/', 'src/test/', 'fernflower_abstract_parameter_names.txt', + ['agent-notes/', '.codex/', 'src/test/', 'zone/moddev/mc/basesteam/testing/', + 'fernflower_abstract_parameter_names.txt', 'cyano/poweradvantage/', 'zone/moddev/mc/basepower/', 'zone/moddev/mc/poweradvantage/', 'zone/moddev/mc/steamadvantage/', 'assets/steamadvantage/', 'data/steamadvantage/', 'zone/moddev/mc/basemetals/', 'cyano/basemetals/', 'com/mcmoddev/lib/', @@ -614,12 +653,16 @@ tasks.register('verifyReleaseArtifacts') { String metadata = zip.getInputStream(zip.getEntry('META-INF/mods.toml')) .getText(StandardCharsets.UTF_8.name()) if (!metadata.contains('modId="basesteam"') - || !metadata.contains("version=\"${project.mod_version}\"")) { + || !metadata.contains("version=\"${project.mod_version}\"") + || !metadata.contains("versionRange=\"${project.minecraft_version_range}\"") + || !metadata.contains("versionRange=\"${project.forge_version_range}\"") + || !metadata.contains("loaderVersion=\"${project.loader_version_range}\"")) { throw new GradleException('Packaged mods.toml identity or version is incorrect') } def manifest = new Manifest(zip.getInputStream(zip.getEntry('META-INF/MANIFEST.MF'))).mainAttributes if (manifest.getValue('Implementation-Version') != project.mod_version || manifest.getValue('Built-On-Java') != project.java_version + || manifest.getValue('Built-On') != "${project.minecraft_version}-${project.forge_version}" || manifest.getValue('Implementation-Timestamp') != null || manifest.getValue('Timestamp') != null) { throw new GradleException('Release manifest identity is incorrect or volatile') @@ -638,6 +681,9 @@ tasks.register('verifyReleaseArtifacts') { } } + new ZipFile(tasks.named('jar', Jar).get().archiveFile.get().asFile).withCloseable { zip -> + verifyPackagedAssetNamespaces(zip) + } new ZipFile(new File(libs, expectedReleaseFiles.get()[1])).withCloseable { sourceZip -> if (sourceZip.getEntry('zone/moddev/mc/basesteam/SteamAdvantage.java') == null || sourceZip.getEntry('zone/moddev/mc/basesteam/machines/SteamDrillTileEntity.java') == null) { @@ -713,6 +759,9 @@ tasks.register('verifyPreparedReleaseArtifacts') { if (actualChecksums != declaredChecksums) { throw new GradleException('Prepared release checksums do not match the immutable jars') } + new ZipFile(new File(prepared, expectedReleaseFiles.get()[0])).withCloseable { zip -> + verifyPackagedAssetNamespaces(zip) + } } } @@ -1037,7 +1086,7 @@ tasks.register('verifyEclipseProductionClasspath') { if (!arguments.contains('--main cpw.mods.bootstraplauncher.BootstrapLauncher') || !vmArguments.contains('--add-opens java.base/java.lang.invoke=ALL-UNNAMED') || contents.contains('net.minecraftforge.userdev.LaunchTesting')) { - throw new GradleException("${name} must retain the native Forge 40 bootstrap and Java 17 module access") + throw new GradleException("${name} must retain the native Forge 45 bootstrap and Java 17 module access") } if (launch.booleanAttribute.find { it.@key.text() == 'org.eclipse.jdt.launching.ATTR_EXCLUDE_TEST_CODE' @@ -1097,7 +1146,7 @@ tasks.register('verifyEclipseProductionClasspath') { || !arguments.contains("--fml.mcVersion ${project.minecraft_version}") || !arguments.contains("--fml.forgeVersion ${project.forge_version}") || !arguments.contains("--fml.mcpVersion ${project.mcp_version}")) { - throw new GradleException("${name} does not target the exact Forge 40 development runtime") + throw new GradleException("${name} does not target the exact Forge 45 development runtime") } } } @@ -1111,7 +1160,9 @@ tasks.register('verifyModelAssets') { File assetRoot = new File(resourceRoot, "assets/${project.mod_id}") File modelRoot = new File(assetRoot, 'models') File blockstateRoot = new File(assetRoot, 'blockstates') + File steamAtlas = new File(resourceRoot, blocksAtlasPath) inputs.dir(assetRoot) + inputs.files(fileTree(resourceRoot) { include blocksAtlasPath }) inputs.files(providers.gradleProperty('powerAdvantageDeobfJar').map { file(it) }) doLast { @@ -1119,7 +1170,24 @@ tasks.register('verifyModelAssets') { new JsonSlurper().parse(jsonFile) } + Set atlasPrefixes = ['block/', 'item/'] as Set + Set singleSprites = [] as Set + if (steamAtlas.isFile()) { + def atlas = new JsonSlurper().parse(steamAtlas) + verifySteamBlocksAtlas(atlas) + singleSprites.addAll(atlas.sources.collect { it.resource }) + } Set powerAdvantageAssets = new ZipFile(powerAdvantageDeobfJar).withCloseable { zip -> + def atlasEntry = zip.getEntry(blocksAtlasPath) + if (atlasEntry != null) { + zip.getInputStream(atlasEntry).withCloseable { input -> + def atlas = new JsonSlurper().parse(input, 'UTF-8') + // Power's directory sources preserve the existing texture IDs across namespaces. + atlas.sources.findAll { + it.type == 'directory' && it.source instanceof String && it.prefix == it.source + '/' + }.each { atlasPrefixes.add(it.prefix) } + } + } zip.entries().collect { it.name } as Set } def verifyReference = { String reference, String assetType, String sourcePath -> @@ -1138,6 +1206,10 @@ tasks.register('verifyModelAssets') { throw new GradleException( "${sourcePath} references missing ${assetType} asset ${reference}") } + if (assetType == 'textures' && !singleSprites.contains(reference) + && !atlasPrefixes.any { path.startsWith(it) }) { + throw new GradleException("${sourcePath} references unstitched blocks atlas texture ${reference}") + } } fileTree(modelRoot) { include '**/*.json' }.files.sort().each { File modelFile -> diff --git a/ci-fixtures/tools/README.md b/ci-fixtures/tools/README.md index 3408522..78bad5d 100644 --- a/ci-fixtures/tools/README.md +++ b/ci-fixtures/tools/README.md @@ -1,4 +1,28 @@ -# Forge 1.18.2 Mavenizer compatibility fixture +# Forge Mavenizer compatibility fixtures + +## Forge 45 (Minecraft 1.19.4) + +The source patch and target-rule manifest match the Base Metals 1.19.4 +fixture. The binary derivative is not tracked here. The build requires the +verified JAR from the local OreSpawn 1.19.4 checkout, or an explicit +`-Pforge45MavenizerJar=`. CI gets it from the pinned OreSpawn +checkout. There is no stock Mavenizer fallback. +The derivative is based on MinecraftForge MinecraftMavenizer 0.5.21 at +`6968241ce7a0a902cdc1c534b976e8373a423091`. Its LGPL-2.1 license is in +`LICENSE-MAVENIZER.txt`. + +Forge 45 decompilation emits eight redundant record accessors in +`Holder.Direct`, `OptionInstance` and `MemoryCondition`. The target rules +remove those accessors, which javac regenerates. Missing or ambiguous rule +matches stop the build. The fixture also forwards +`ORESPAWN_MAVENIZER_OFFLINE=true` as the native Mavenizer offline flag. +The checked-in source patch rebuilds against the upstream commit with +Temurin 25.0.3+9 and the upstream Gradle 9.1.0 wrapper. + +The derivative is build-only; no fixture bytes ship in baseSteam artifacts. +JAR SHA-256: `341B3FB10ABA6EA7DC612FBE519DC290A1C81867AED5DD320ACD783688AC49DE`. + +## Forge 40 (historical, Minecraft 1.18.2) `minecraft-mavenizer-0.5.21-orespawn-1.18.jar` is a build-only derivative of MinecraftForge MinecraftMavenizer `0.5.21`, source commit diff --git a/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.patch b/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.patch new file mode 100644 index 0000000..28951fb --- /dev/null +++ b/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.patch @@ -0,0 +1,425 @@ +From 10279f8ab1adadb87b56f36c85dbda3cf64e62b3 Mon Sep 17 00:00:00 2001 +From: OreSpawn Build Fixture +Date: Fri, 28 Aug 2026 15:21:10 +0100 +Subject: [PATCH] Add target-aware source compatibility rules + +--- + build.gradle | 2 +- + .../net/minecraftforge/mcmaven/cli/Main.java | 18 +- + .../minecraftforge/mcmaven/cli/MavenTask.java | 3 + + .../mcmaven/impl/util/ProcessUtils.java | 2 + + .../impl/util/SourceCompatibilityPatcher.java | 241 ++++++++++++++++++ + .../minecraft-source-compatibility.json | 58 +++++ + 6 files changed, 322 insertions(+), 2 deletions(-) + create mode 100644 src/main/java/net/minecraftforge/mcmaven/impl/util/SourceCompatibilityPatcher.java + create mode 100644 src/main/resources/META-INF/orespawn/minecraft-source-compatibility.json + +diff --git a/build.gradle b/build.gradle +index 439c211..9d99d81 100644 +--- a/build.gradle ++++ b/build.gradle +@@ -17,7 +17,7 @@ plugins { + gradleutils.displayName = 'Minecraft Mavenizer' + description = 'A pure-blooded Java tool to generate a maven repository for Minecraft artifacts.' + group = 'net.minecraftforge' +-version = gitversion.tagOffset ++version = '0.5.21' + + println "Version: $version" + +diff --git a/src/main/java/net/minecraftforge/mcmaven/cli/Main.java b/src/main/java/net/minecraftforge/mcmaven/cli/Main.java +index eeda8a0..ad0fe93 100644 +--- a/src/main/java/net/minecraftforge/mcmaven/cli/Main.java ++++ b/src/main/java/net/minecraftforge/mcmaven/cli/Main.java +@@ -12,6 +12,7 @@ import static net.minecraftforge.mcmaven.impl.Mavenizer.LOGGER; + + import java.time.Duration; + import java.util.ArrayList; ++import java.util.Arrays; + + public class Main { + private static final String DISPLAY_NAME = "Minecraft Mavenizer"; +@@ -20,6 +21,8 @@ public class Main { + try { + LOGGER.capture(); + LOGGER.info(JarVersionInfo.of(DISPLAY_NAME, Main.class).implementation()); ++ LOGGER.info("OreSpawn Mavenizer compatibility runtime: Java " + Runtime.version() ++ + " (" + System.getProperty("java.vendor") + ")"); + run(args); + } catch (Throwable e) { + LOGGER.release(); +@@ -36,7 +39,8 @@ public class Main { + LOGGER.getInfo().printf(", took %d:%02d.%03d%n", time.toMinutesPart(), time.toSecondsPart(), time.toMillisPart()); + } + +- private static void run(String[] args) throws Exception { ++ private static void run(String[] suppliedArgs) throws Exception { ++ var args = withOfflinePropagation(suppliedArgs); + var parser = new OptionParser(); + parser.allowsUnrecognizedOptions(); + var tasks = Tasks.values(); +@@ -88,4 +92,16 @@ public class Main { + Tasks.MAVEN.callback.run(args, false); + } + } ++ ++ private static String[] withOfflinePropagation(String[] suppliedArgs) { ++ if (!"true".equalsIgnoreCase(System.getenv("ORESPAWN_MAVENIZER_OFFLINE")) ++ || Arrays.asList(suppliedArgs).contains("--offline")) { ++ return suppliedArgs; ++ } ++ ++ var propagated = Arrays.copyOf(suppliedArgs, suppliedArgs.length + 1); ++ propagated[suppliedArgs.length] = "--offline"; ++ LOGGER.info("OreSpawn Mavenizer compatibility: propagated Gradle offline mode"); ++ return propagated; ++ } + } +diff --git a/src/main/java/net/minecraftforge/mcmaven/cli/MavenTask.java b/src/main/java/net/minecraftforge/mcmaven/cli/MavenTask.java +index 825820e..39bb9b3 100644 +--- a/src/main/java/net/minecraftforge/mcmaven/cli/MavenTask.java ++++ b/src/main/java/net/minecraftforge/mcmaven/cli/MavenTask.java +@@ -21,6 +21,7 @@ import net.minecraftforge.mcmaven.impl.cache.Cache; + import net.minecraftforge.mcmaven.impl.mappings.Mappings; + import net.minecraftforge.mcmaven.impl.util.Artifact; + import net.minecraftforge.mcmaven.impl.util.Constants; ++import net.minecraftforge.mcmaven.impl.util.SourceCompatibilityPatcher; + + import static net.minecraftforge.mcmaven.impl.Mavenizer.LOGGER; + +@@ -194,6 +195,8 @@ class MavenTask { + if (artifact.getVersion() == null) + artifact = artifact.withVersion(options.valueOf(versionO)); + ++ SourceCompatibilityPatcher.selectTarget(artifact); ++ + var mappings = getMappings(options, mappingsO, parchmentO); + + var foreignRepositories = new HashMap(); +diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/ProcessUtils.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/ProcessUtils.java +index 9fa2888..d8c14fd 100644 +--- a/src/main/java/net/minecraftforge/mcmaven/impl/util/ProcessUtils.java ++++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/ProcessUtils.java +@@ -305,6 +305,8 @@ public final class ProcessUtils { + throw new RuntimeException("Failed to extract source jar: " + sourcesJar.getAbsolutePath(), e); + } + ++ SourceCompatibilityPatcher.apply(sourcesOutput.toPath(), outputJar.toPath()); ++ + // track source files + var sourcePath = new StringBuilder(); + var nonSourceFiles = new ArrayList(); +diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/SourceCompatibilityPatcher.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/SourceCompatibilityPatcher.java +new file mode 100644 +index 0000000..2cb07e0 +--- /dev/null ++++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/SourceCompatibilityPatcher.java +@@ -0,0 +1,241 @@ ++/* ++ * Copyright (c) Forge Development LLC and contributors ++ * SPDX-License-Identifier: LGPL-2.1-only ++ */ ++package net.minecraftforge.mcmaven.impl.util; ++ ++import com.google.gson.Gson; ++import java.io.IOException; ++import java.io.InputStream; ++import java.nio.charset.StandardCharsets; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.nio.file.StandardCopyOption; ++import java.security.MessageDigest; ++import java.security.NoSuchAlgorithmException; ++import java.util.HashSet; ++import java.util.LinkedHashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.Objects; ++import java.util.stream.Collectors; ++ ++import static net.minecraftforge.mcmaven.impl.Mavenizer.LOGGER; ++ ++/** Applies exact, target-scoped source compatibility rules before recompilation. */ ++public final class SourceCompatibilityPatcher { ++ public static final String MANIFEST_RESOURCE = "META-INF/orespawn/minecraft-source-compatibility.json"; ++ private static final Gson GSON = new Gson(); ++ private static volatile String selectedArtifact; ++ ++ private SourceCompatibilityPatcher() {} ++ ++ public static void selectTarget(Artifact artifact) { ++ selectedArtifact = artifact.getDescriptor(); ++ } ++ ++ public static void apply(Path sourceRoot, Path outputJar) { ++ var artifact = selectedArtifact; ++ if (artifact == null) ++ throw new IllegalStateException("No Mavenizer target artifact was selected before source recompilation"); ++ ++ var manifestBytes = readManifest(); ++ var manifestHash = sha256(manifestBytes); ++ var manifest = GSON.fromJson(new String(manifestBytes, StandardCharsets.UTF_8), RuleManifest.class); ++ if (manifest == null || manifest.schema != 1 || manifest.targets == null) ++ throw new IllegalStateException("Unsupported or incomplete OreSpawn Mavenizer compatibility manifest"); ++ ++ var target = manifest.targets.get(artifact); ++ if (target == null) { ++ LOGGER.info("OreSpawn Mavenizer compatibility: no rules for " + artifact); ++ return; ++ } ++ ++ validateTarget(target); ++ var sourceByFile = new LinkedHashMap(); ++ var present = 0; ++ var absent = 0; ++ for (var rule : target.rules) { ++ var file = sourceRoot.resolve(rule.file).normalize(); ++ if (!file.startsWith(sourceRoot.normalize())) ++ throw new IllegalStateException("Compatibility rule escapes source root: " + rule.id); ++ if (!Files.isRegularFile(file)) ++ throw new IllegalStateException("Compatibility source file is missing for " + rule.id + ": " + rule.file); ++ ++ var source = sourceByFile.computeIfAbsent(file, ++ ignored -> readUtf8(file).replace("\r\n", "\n")); ++ requireCount(source, rule.recordDeclaration, 1, "record declaration", rule); ++ var accessorCount = countInRecord(source, rule); ++ if (accessorCount == 1) ++ present++; ++ else if (accessorCount == 0) ++ absent++; ++ else ++ throw new IllegalStateException("Ambiguous accessor match for " + rule.id + ": expected at most 1, found " + accessorCount); ++ } ++ ++ if (present != 0 && absent != 0) ++ throw new IllegalStateException("Partial compatibility state for " + artifact + ": " + present + " present, " + absent + " absent"); ++ ++ if (present == target.expectedApplications) { ++ for (var rule : target.rules) { ++ var file = sourceRoot.resolve(rule.file).normalize(); ++ var patched = replaceInRecord(sourceByFile.get(file), rule); ++ sourceByFile.put(file, patched); ++ } ++ sourceByFile.forEach(SourceCompatibilityPatcher::writeUtf8); ++ writeMarker(outputJar, artifact, manifestHash, target.rules); ++ LOGGER.info("OreSpawn Mavenizer compatibility: applied " + present + " rule(s) for " + artifact ++ + " [" + ruleIds(target.rules) + "] manifest SHA-256 " + manifestHash); ++ } else if (absent == target.expectedApplications) { ++ writeMarker(outputJar, artifact, manifestHash, target.rules); ++ LOGGER.info("OreSpawn Mavenizer compatibility: verified " + absent + " rule(s) already applied for " + artifact ++ + " [" + ruleIds(target.rules) + "] manifest SHA-256 " + manifestHash); ++ } else { ++ throw new IllegalStateException("Compatibility manifest expected " + target.expectedApplications ++ + " applications for " + artifact + " but defines " + target.rules.size()); ++ } ++ } ++ ++ private static void validateTarget(TargetRuleSet target) { ++ if (target.expectedApplications <= 0 || target.rules == null ++ || target.rules.size() != target.expectedApplications) { ++ throw new IllegalStateException("Compatibility target has an invalid expected application count"); ++ } ++ var ids = new HashSet(); ++ for (var rule : target.rules) { ++ Objects.requireNonNull(rule.id, "Compatibility rule id"); ++ Objects.requireNonNull(rule.file, "Compatibility rule file"); ++ Objects.requireNonNull(rule.recordDeclaration, "Compatibility record declaration"); ++ Objects.requireNonNull(rule.accessor, "Compatibility accessor"); ++ if (!ids.add(rule.id)) ++ throw new IllegalStateException("Duplicate compatibility rule id: " + rule.id); ++ } ++ } ++ ++ private static byte[] readManifest() { ++ try (InputStream in = SourceCompatibilityPatcher.class.getClassLoader().getResourceAsStream(MANIFEST_RESOURCE)) { ++ if (in == null) ++ throw new IllegalStateException("Missing embedded compatibility manifest: " + MANIFEST_RESOURCE); ++ return in.readAllBytes(); ++ } catch (IOException e) { ++ throw new IllegalStateException("Could not read embedded compatibility manifest", e); ++ } ++ } ++ ++ private static String readUtf8(Path file) { ++ try { ++ return Files.readString(file, StandardCharsets.UTF_8); ++ } catch (IOException e) { ++ throw new IllegalStateException("Could not read compatibility source " + file, e); ++ } ++ } ++ ++ private static void writeUtf8(Path file, String content) { ++ try { ++ Files.writeString(file, content, StandardCharsets.UTF_8); ++ } catch (IOException e) { ++ throw new IllegalStateException("Could not write compatibility source " + file, e); ++ } ++ } ++ ++ private static void writeMarker(Path outputJar, String artifact, String manifestHash, List rules) { ++ var marker = Path.of(outputJar.toString() + ".orespawn-compatibility"); ++ var temp = Path.of(marker.toString() + ".tmp"); ++ var content = "artifact=" + artifact + "\nmanifestSha256=" + manifestHash + "\nrules=" + ruleIds(rules) + "\n"; ++ try { ++ Files.writeString(temp, content, StandardCharsets.UTF_8); ++ try { ++ Files.move(temp, marker, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); ++ } catch (java.nio.file.AtomicMoveNotSupportedException ignored) { ++ Files.move(temp, marker, StandardCopyOption.REPLACE_EXISTING); ++ } ++ } catch (IOException e) { ++ throw new IllegalStateException("Could not write compatibility marker " + marker, e); ++ } ++ } ++ ++ private static String ruleIds(List rules) { ++ return rules.stream().map(rule -> rule.id).collect(Collectors.joining(",")); ++ } ++ ++ private static void requireCount(String source, String needle, int expected, String description, Rule rule) { ++ var actual = count(source, needle); ++ if (actual != expected) ++ throw new IllegalStateException("Invalid " + description + " match for " + rule.id ++ + ": expected " + expected + ", found " + actual); ++ } ++ ++ private static int count(String source, String needle) { ++ var count = 0; ++ var from = 0; ++ while ((from = source.indexOf(needle, from)) >= 0) { ++ count++; ++ from += needle.length(); ++ } ++ return count; ++ } ++ ++ private static int countInRecord(String source, Rule rule) { ++ var bounds = recordBounds(source, rule); ++ return count(source.substring(bounds[0], bounds[1]), rule.accessor); ++ } ++ ++ private static String replaceInRecord(String source, Rule rule) { ++ var bounds = recordBounds(source, rule); ++ var body = source.substring(bounds[0], bounds[1]); ++ var count = count(body, rule.accessor); ++ if (count != 1) ++ throw new IllegalStateException("Accessor state changed while applying " + rule.id ++ + ": expected 1, found " + count); ++ var patchedBody = body.replace(rule.accessor, ""); ++ if (count(patchedBody, rule.accessor) != 0) ++ throw new IllegalStateException("Accessor remained after applying " + rule.id); ++ return source.substring(0, bounds[0]) + patchedBody + source.substring(bounds[1]); ++ } ++ ++ private static int[] recordBounds(String source, Rule rule) { ++ var declaration = source.indexOf(rule.recordDeclaration); ++ var open = source.indexOf('{', declaration + rule.recordDeclaration.length()); ++ if (declaration < 0 || open < 0) ++ throw new IllegalStateException("Could not locate record body for " + rule.id); ++ var depth = 0; ++ for (var index = open; index < source.length(); index++) { ++ var character = source.charAt(index); ++ if (character == '{') ++ depth++; ++ else if (character == '}' && --depth == 0) ++ return new int[] {open, index + 1}; ++ } ++ throw new IllegalStateException("Unterminated record body for " + rule.id); ++ } ++ ++ private static String sha256(byte[] bytes) { ++ try { ++ var digest = MessageDigest.getInstance("SHA-256").digest(bytes); ++ var result = new StringBuilder(digest.length * 2); ++ for (byte value : digest) ++ result.append(String.format("%02X", value)); ++ return result.toString(); ++ } catch (NoSuchAlgorithmException e) { ++ throw new IllegalStateException("SHA-256 is unavailable", e); ++ } ++ } ++ ++ private static final class RuleManifest { ++ int schema; ++ Map targets; ++ } ++ ++ private static final class TargetRuleSet { ++ int expectedApplications; ++ List rules; ++ } ++ ++ private static final class Rule { ++ String id; ++ String file; ++ String recordDeclaration; ++ String accessor; ++ } ++} +diff --git a/src/main/resources/META-INF/orespawn/minecraft-source-compatibility.json b/src/main/resources/META-INF/orespawn/minecraft-source-compatibility.json +new file mode 100644 +index 0000000..462d571 +--- /dev/null ++++ b/src/main/resources/META-INF/orespawn/minecraft-source-compatibility.json +@@ -0,0 +1,58 @@ ++{ ++ "schema": 1, ++ "targets": { ++ "net.minecraftforge:forge:1.19.4-45.4.0": { ++ "expectedApplications": 8, ++ "rules": [ ++ { ++ "id": "forge-1.19.4-holder-direct-value", ++ "file": "net/minecraft/core/Holder.java", ++ "recordDeclaration": "public static record Direct(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", ++ "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", ++ "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", ++ "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", ++ "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", ++ "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", ++ "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", ++ "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" ++ } ++ ] ++ } ++ } ++} +-- +2.55.0.windows.3 + diff --git a/ci-fixtures/tools/minecraft-source-compatibility.json b/ci-fixtures/tools/minecraft-source-compatibility.json new file mode 100644 index 0000000..462d571 --- /dev/null +++ b/ci-fixtures/tools/minecraft-source-compatibility.json @@ -0,0 +1,58 @@ +{ + "schema": 1, + "targets": { + "net.minecraftforge:forge:1.19.4-45.4.0": { + "expectedApplications": 8, + "rules": [ + { + "id": "forge-1.19.4-holder-direct-value", + "file": "net/minecraft/core/Holder.java", + "recordDeclaration": "public static record Direct(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", + "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", + "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", + "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", + "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", + "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", + "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", + "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/PORT118-STATUS.md b/docs/PORT118-STATUS.md index a7c8241..479c312 100644 --- a/docs/PORT118-STATUS.md +++ b/docs/PORT118-STATUS.md @@ -1,11 +1,9 @@ -# baseSteam 1.18.2 port notes +# baseSteam 1.18.2 local port -The release branch uses the shared Base Metals 3.1.2 migration hook and the -published basePower 2.4.10.118021 build. +The current local candidate uses the shared Base Metals 3.1.2 migration hook. See [gradle.properties](../gradle.properties) for the exact Power, Base Metals -and OreSpawn pins. - -The first locally tested Steam player jar had SHA-256 +and OreSpawn pins. The release and Eclipse audits passed with these inputs. +The current Steam player JAR has SHA-256 `2E7131FD22F9486905212F64323F5E11EEFBE0A538499E92477373D0C5D2A2CC`. The notes below record earlier port milestones and their original inputs. @@ -14,8 +12,8 @@ The notes below record earlier port milestones and their original inputs. - Branch: `feature/basesteam-1.18.2-port`; candidate `2.2.11.118021`. - Target: Forge `40.3.0`, official `1.18.2`, MCP `20220404.173914`, Temurin `17.0.1+12`, ForgeGradle `7.0.34`, Gradle `9.6.1`. -- The original port was tested locally on disposable worlds; the notes below - describe that candidate, not the later publication build. +- Scope: local-only. No push, publication, original world, PA117 edit, or + sibling project build. ## Port changes diff --git a/docs/PORT119-STATUS.md b/docs/PORT119-STATUS.md new file mode 100644 index 0000000..98fea19 --- /dev/null +++ b/docs/PORT119-STATUS.md @@ -0,0 +1,147 @@ +# baseSteam 1.19.4 port + +- Source: accepted 1.18.2 `d10c56cf021e6bd701e973cda90358495844a45f`, + cloned locally with `--no-hardlinks` onto + `feature/basesteam-1.19.4-port`. +- Target: baseSteam `2.2.12.119041`, basePower `2.4.11.119041`, + Minecraft `1.19.4`, Forge `45.4.0`, official mappings, + MCP `20230314.122934`, Temurin 17, ForgeGradle `7.0.34`, and Gradle `9.6.1`. +- The release uses basePower `2.4.11.119041` and the published Base Metals + migration fix. Earlier checkpoints below describe the local port before + those dependency releases. All upgrade tests use copies. + +## Tooling + +The [official Forge listing](https://files.minecraftforge.net/net/minecraftforge/forge/index_1.19.4.html) +identifies 45.4.0 as the recommended 1.19.4 Forge. ForgeGradle 7 configuration +and its Java 25 Mavenizer follow the local Forge 45.4.0 builds. The required +[compatibility derivative](../ci-fixtures/tools/README.md) is an external +build-only input from the pinned OreSpawn checkout, checked by SHA-256. +Java 25 is not a production toolchain. Use a separate Steam Gradle user home or the +agreed warm cache with distinct project state. + +CI and CodeQL build the pinned basePower and Base Metals sources with their own +Forge 45 setup. Steam supplies the verified OreSpawn Mavenizer jar to basePower +without applying a second Gradle init script. +The dependency commits and artifact checksums are in `gradle.properties`. + +## Compatibility + +RegisterEvent preserves the block, item, enchantment and block-entity paths. +Player-facing items contribute through `ItemGroups.show`; the drill-bit and +pump-pipe block items stay hidden. NBT and legacy migration keys, recipes, +textures, and loot tables are unchanged. Sound dispatch retains its loaded-player, +range and spectator checks. Native damage types retain the old death-message keys. +The musket uses the TNT interaction to preserve full drops by default; it +honors the TNT drop-decay gamerule. Base Metals' crusher adapter is only +linked when the optional mod is loaded; the vanilla fallback and recipe +condition order remain in place. + +## Inputs and evidence + +The Base Metals 3.1.2 migration-hook development and player JARs are from +`_verification/base-119-port-20261109/Base Metals hook build`. OreSpawn is +from `D:/MinecraftMineralogy/OS119/OreSpawn`. The exact pins are in +[gradle.properties](../gradle.properties); the Base Metals and OreSpawn +inputs are not claims of new public releases. + +Local receipts are under the clone's ignored `evidence/port119/`: + +- `mapped-preflight-a`: six-suite exploratory run retained with three failures: + missing Forge network-event transformation and two asset assertions without Power. +- `mapped-independent-b`: the native damage codec requires Minecraft bootstrap; + the initial standalone attempt failed and is retained. +- `event-bootstrap-probe*`: successive failed native bootstrap experiments. + The initial transform attempted to transform Event twice, the next missed a + directly derived network event, and the third exposed a test item without a + Forge registry delegate. +- `mapped-native-tests-d`: 22 JUnit tests passed across six suites with the + test-only network-event agent and no Power JAR. This covered fuel, damage type + codec, loot and pack metadata, optional recipe conditions, recipe unlocks, + and sound dispatch. It did not execute machines or load a world. +- `provisional-compile-a`, `b` and `c`: direct Java 17 compile against mapped + Forge SHA-256 `B05295524E3277ADB61C348E3C4FFB3101C7EF147F6136AC86470BD6BD5A006B`, + the pinned Base Metals and OreSpawn development JARs, and provisional Power + SHA-256 `22210403D5CEAD59354ABBEC796433283ADB68254A92A344C23DE829438822E5`. + The first two receipts retained 16 production and four test compilation + errors respectively; the third compiled 64 production and 32 test files. + No tests, packages or launches used the provisional Power input. + +The source audit verified 181 JSON/pack files, 12 block-entity factories, +17 harvest entries, and 24 recipe unlocks. + +## Full local audits at Power 1cf39fd + +Power source `1cf39fd78ad891da26c32893ee0de675f61ec243` was the verified +local checkpoint for this build. All development, player and test-bootstrap +inputs were copied and hashed before the build, and neither Gradle run read +the mutable parent build paths. The Power bootstrap already includes the +native network event transform, so the Steam-only experimental agent is not +part of this suite or the release artifacts. + +`evidence/port119/local-final-a/full-a.log` and `test-results-a/` retain the +first run: 207 tests, four failures. The plain JUnit harness had not registered +Forge's vanilla fluid types or initialized the cached fluid states needed by +the native pump fixtures. The fixes run the actual Forge test registry phases +and initialize the fixture states; production code and assertions were not +relaxed. + +`full-b.log` passed the full Gradle build, release configuration and +dependencies, package contents and checksums, explicit player dependency +audit, and Eclipse production classpath and launch configuration audit. +`test-results-b/` records 207 tests in 31 suites with no failures, errors or +skips. `steam-source-b.json` inventories the source before these handoff notes. + +The exact output set is frozen in `local-final-a/artifacts/` and recorded in +`artifact-receipt.json`. The player JAR SHA-256 is +`8DDFAF1E924EDB47616CA4CAC26C2FB4E49385DE40D4E1638FB82626036023C4`; +the development JAR SHA-256 is +`419921119001F2421C2B30C80C75AAA0C0F8680369064DC6A650ACE8673E28F2`. + +This evidence is for the pinned local Power checkpoint only. It does not +qualify a later Power repair, the combined suite, world upgrade, Eclipse launch +execution or manual acceptance. Steam/Electric 1.18 publication and +reconciliation against merged source are not claimed. + +## Rebuild at repaired Power c999531 + +Power checkpoint `c9995315e2aee9d9bffec8eb8b1191ad8a0faeb7` was pinned +with deobf SHA-256 +`512FDEC9275B33533EF773C110E6229E0F07268775FAD4B5942A15B68A9EB87D` +and player SHA-256 +`E1689C1147F6F6F93F6516D9188A279093D6CCC2FF1FBEB506468B876BA03E60`. +The bootstrap and companion pins are unchanged. The build read only the +verified frozen inputs listed in `evidence/port119/repaired-power-c999531-a/inputs.json`. + +All 36 Gradle tasks executed in the offline rebuild, including the full +suite, dependency and package audits, and the Eclipse production classpath +and launch configuration audit. All 207 tests in 31 suites passed. +The four JARs and checksum file match the preceding checkpoint byte for byte, +but the rebuild and tests have separate receipts. The new evidence directory +contains `full-a.log`, `test-results/`, `artifact-receipt.json` and the Steam +manifest entry `combined-input.json`. The frozen player JAR is under +`artifacts/` with SHA-256 +`8DDFAF1E924EDB47616CA4CAC26C2FB4E49385DE40D4E1638FB82626036023C4`. + +This is the Steam handoff for the combined test. No combined run, hosted +reconciliation, Eclipse launch, or manual acceptance is claimed here. + +## Drill-bit atlas repair + +The combined Forge 45 client reported a missing baked sprite for +`basesteam:entity/drill_bit`. Steam now contributes that single sprite to +`assets/minecraft/atlases/blocks.json`. The historical texture path and PNG +are unchanged, and the existing Power directory sources remain separate. + +The model audit now rejects unstitched Steam and Power textures referenced by +Steam models. The package audit allows only this exact atlas file outside +Steam's asset namespace and verifies that it contains only the drill-bit +source. The expected pre-fix failure is retained in +`evidence/port119/drill-bit-atlas-a/atlas-red.log`. + +The full rebuild against the same frozen Power `c999531` pins passed all 36 +Gradle tasks and 207 tests in 31 suites. The old/new player and deobf archives +each gained only the atlas JSON; all 73 classes and every other payload entry +are byte-identical. The comparison and refreshed combined manifest entry are +in `jar-payload-delta.json` and `combined-input.json` in that evidence directory. +The graphical and affected combined integration reruns are pending. diff --git a/gradle.properties b/gradle.properties index 7c6a358..c73cf76 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,15 +5,15 @@ org.gradle.caching=true org.gradle.parallel=false net.minecraftforge.gradle.merge-source-sets=false -minecraft_version=1.18.2 -minecraft_version_range=[1.18.2] -forge_version=40.3.0 -forge_installer_sha256=9434C29790504DCD11CE97CB30DD8891B4B18848982357BC1B5ABFA79FB95103 -forge_version_range=[40.3.0,41) -loader_version_range=[40,41) +minecraft_version=1.19.4 +minecraft_version_range=[1.19.4] +forge_version=45.4.0 +forge_installer_sha256=6643BB8C21F409CC528FF656DAF60C206D304CC77BF529BBBAB4B336004DFC53 +forge_version_range=[45.4.0,46) +loader_version_range=[45,46) mapping_channel=official -mapping_version=1.18.2 -mcp_version=20220404.173914 +mapping_version=1.19.4 +mcp_version=20230314.122934 loader_name=forge loader_code=1 @@ -26,33 +26,36 @@ curseforge_project_id=1781373 mod_id=basesteam mod_name=baseSteam mod_license=GPL-3.0-only -mod_version=2.2.11.118021 +mod_version=2.2.12.119041 mod_group=zone.moddev.mc.basesteam mod_authors=DrCyano mod_description=Steam-powered machines and infrastructure for basePower. cf_requirements=basepower,base-metals cf_optionals= -cf_versions=1.18.2 +cf_versions=1.19.4 -poweradvantage_version=2.4.10.118021 +poweradvantage_version=2.4.11.119041 poweradvantage_source_repository=MinecraftModDevelopmentMods/basePower -poweradvantage_source_ref=bcf5105ef5c10ba36c26c8ff941742fecc3e11a1 -poweradvantage_deobf_sha256=767BABC0D6E1536DCB937B9F1DC565AF348B3CAF92D3207671EF1E1C8D8EF5D4 -# The player jar is the published build. The development jar is rebuilt from its source. -poweradvantage_player_sha256=8CA46D5BCBFFDDAEB4F40137B5BF71E5AF7DD36515E5BFEA751FC5C02249823B +# Development jar rebuilt from the released basePower source. +poweradvantage_source_ref=4af5c7d5d490162cddc7f1e4fc72cc602b42bb32 +poweradvantage_deobf_sha256=512FDEC9275B33533EF773C110E6229E0F07268775FAD4B5942A15B68A9EB87D +# Player jar is the exact published build, not the development jar. +poweradvantage_player_sha256=E1689C1147F6F6F93F6516D9188A279093D6CCC2FF1FBEB506468B876BA03E60 +poweradvantage_test_bootstrap_sha256=A93CD83EC42B09B730CB7715C10C6E53D47B22B557EFE68EDC95241D53BD4EB3 +poweradvantage_test_bootstrap_source_sha256=0FB05554D6446BD5561BF9DE47B9F173E6F6C28E39A2BC877D359BB363CB1ABE -basemetals_version=3.1.2.118021 -basemetals_source_commit=3488ed58403722ac3ff203cff7680dfc16e0fd3f -basemetals_deobf_sha256=E4F9E2CDE355275218C79954D3792D000615790C7CBAE5FCEC59636A8C4CA57D -basemetals_player_sha256=5595C2CE7ADCDF1599E4327F1130121148A5CACD928CBC2190312D61F7CA37A9 +basemetals_version=3.1.2.119041 +basemetals_source_commit=5791a793aa3a1ca1aa3fcb36e6d4a2d865e7d2d3 +basemetals_deobf_sha256=0AA993D6BADA54B11548EB5BE150CE0939232291F2D215B7DBAF149857976B2F +basemetals_player_sha256=991EA843EEDF5FA1E42E9984A07573C7D03D74230482230440E44EDCA895FA39 -orespawn_version=4.1.0.118021 -orespawn_source_commit=d31f52f296739f34a0c13f554abf3e1ac071ae25 -orespawn_deobf_sha256=34B25B2116FD91B0050C80393F8356A5CA256E707BF443E4882BE5CFAD1335B6 -orespawn_player_sha256=BD8D84A96DAFB7668E95D61B9D72C62AEF3440675473E41244C182C64653356F +orespawn_version=4.1.0.119041 +orespawn_source_commit=444d86ef48db2bf54422901b067c51d5832197fc +orespawn_deobf_sha256=BE6973F68F1C310DFF8CDF89C12B34C62D68CAEA326C0F1528E486EE3ABC1000 +orespawn_player_sha256=1B2ECAA546E7FFB35863AC1D9FF92504471EE857045DCD01A068C5CA7F53CFFD # Published runtime bytes are distinct from development API jars. -basemetals_public_sha256=5595C2CE7ADCDF1599E4327F1130121148A5CACD928CBC2190312D61F7CA37A9 -orespawn_public_sha256=BD8D84A96DAFB7668E95D61B9D72C62AEF3440675473E41244C182C64653356F +basemetals_public_sha256=991EA843EEDF5FA1E42E9984A07573C7D03D74230482230440E44EDCA895FA39 +orespawn_public_sha256=1B2ECAA546E7FFB35863AC1D9FF92504471EE857045DCD01A068C5CA7F53CFFD diff --git a/gradle/forge45-mavenizer.gradle b/gradle/forge45-mavenizer.gradle new file mode 100644 index 0000000..f99e82a --- /dev/null +++ b/gradle/forge45-mavenizer.gradle @@ -0,0 +1,25 @@ +import java.security.MessageDigest +import org.gradle.jvm.toolchain.JvmVendorSpec + +// OreSpawn's Forge 45 fixture fixes eight decompiled record accessors. +def tool = 'ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar' +def defaultJar = "../../../../MinecraftMineralogy/OS119/OreSpawn/${tool}" +def fixture = file(providers.gradleProperty('forge45MavenizerJar').getOrElse(defaultJar)) +def expected = '341B3FB10ABA6EA7DC612FBE519DC290A1C81867AED5DD320ACD783688AC49DE' +if (!fixture.isFile()) { + throw new GradleException("Forge 45 requires OreSpawn's Mavenizer fixture: ${fixture}. " + + "Use -Pforge45MavenizerJar= for another checkout layout.") +} +def actual = MessageDigest.getInstance('SHA-256').digest(fixture.bytes).encodeHex().toString().toUpperCase(Locale.ROOT) +if (actual != expected) { + throw new GradleException("Forge 45 Mavenizer fixture SHA-256 mismatch: expected ${expected}, got ${actual}") +} + +fgtools.configure('mavenizer') { + classpath.from(fixture) + mainClass.set('net.minecraftforge.mcmaven.cli.Main') + javaLauncher.set(javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(25) + vendor = JvmVendorSpec.ADOPTIUM + }) +} diff --git a/src/main/java/zone/moddev/mc/basesteam/SteamAdvantage.java b/src/main/java/zone/moddev/mc/basesteam/SteamAdvantage.java index 567795f..5f44166 100644 --- a/src/main/java/zone/moddev/mc/basesteam/SteamAdvantage.java +++ b/src/main/java/zone/moddev/mc/basesteam/SteamAdvantage.java @@ -28,12 +28,12 @@ import zone.moddev.mc.basesteam.migration.SteamConfigMigration; import zone.moddev.mc.basesteam.recipe.RecipeConditions; -/** Forge 1.18.2 entry point for baseSteam. */ +/** Forge 1.19.4 entry point for baseSteam. */ @Mod(SteamAdvantage.MODID) public final class SteamAdvantage { public static final String MODID = "basesteam"; public static final String NAME = "baseSteam"; - public static final String VERSION = "2.2.11.118021"; + public static final String VERSION = "2.2.12.119041"; public static final Logger LOGGER = LogManager.getLogger(MODID); public static float MUSKET_DAMAGE = 20.0F; diff --git a/src/main/java/zone/moddev/mc/basesteam/blocks/DrillBitBlock.java b/src/main/java/zone/moddev/mc/basesteam/blocks/DrillBitBlock.java index 600bbdb..623e66e 100644 --- a/src/main/java/zone/moddev/mc/basesteam/blocks/DrillBitBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/blocks/DrillBitBlock.java @@ -42,7 +42,7 @@ public net.minecraft.world.level.block.entity.BlockEntit @Override public void entityInside(BlockState state, Level world, BlockPos pos, Entity victim) { - victim.hurt(Power.machine_damage, 2.0F); + if (!world.isClientSide) victim.hurt(Power.machineDamage(world), 2.0F); } @Override diff --git a/src/main/java/zone/moddev/mc/basesteam/compat/BaseMetalsCompat.java b/src/main/java/zone/moddev/mc/basesteam/compat/BaseMetalsCompat.java index 7ca5a48..5febe11 100644 --- a/src/main/java/zone/moddev/mc/basesteam/compat/BaseMetalsCompat.java +++ b/src/main/java/zone/moddev/mc/basesteam/compat/BaseMetalsCompat.java @@ -5,7 +5,6 @@ import net.minecraft.world.SimpleContainer; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.level.Level; import net.minecraftforge.fml.ModList; import zone.moddev.mc.basemetals.recipe.CrushingRecipe; @@ -28,15 +27,24 @@ public static boolean hasCrusherRecipe(Level world, ItemStack input) { public static ItemStack getCrusherRecipeOutput(Level world, ItemStack input) { if (world == null || input == null || input.isEmpty()) return null; if (isLoaded()) { + ItemStack result = LoadedCrushingRecipes.find(world, input); + if (result != null) return result; + } + return getVanillaCrusherOutput(input); + } + + // Resolve the optional recipe type only after Forge confirms Base Metals is installed. + private static final class LoadedCrushingRecipes { + private static ItemStack find(Level world, ItemStack input) { SimpleContainer inventory = new SimpleContainer(1); inventory.setItem(0, input.copy()); - for (Recipe candidate : world.getRecipeManager().getRecipes()) { - if (candidate instanceof CrushingRecipe && candidate.matches(inventory, world)) { - return candidate.getResultItem().copy(); + for (var candidate : world.getRecipeManager().getRecipes()) { + if (candidate instanceof CrushingRecipe crushing && crushing.matches(inventory, world)) { + return crushing.getResultItem(world.registryAccess()).copy(); } } + return null; } - return getVanillaCrusherOutput(input); } public static ItemStack getCrusherRecipeOutput(Level world, BlockState input) { diff --git a/src/main/java/zone/moddev/mc/basesteam/graphics/DrillBitRenderer.java b/src/main/java/zone/moddev/mc/basesteam/graphics/DrillBitRenderer.java index c161489..76114aa 100644 --- a/src/main/java/zone/moddev/mc/basesteam/graphics/DrillBitRenderer.java +++ b/src/main/java/zone/moddev/mc/basesteam/graphics/DrillBitRenderer.java @@ -4,7 +4,7 @@ import com.mojang.blaze3d.vertex.VertexConsumer; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderType; -import com.mojang.math.Vector3f; +import com.mojang.math.Axis; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider.Context; import net.minecraft.core.Direction; @@ -35,10 +35,10 @@ static void renderGeometry(Direction.Axis axis, float rotation, PoseStack matric try { matrices.translate(0.5D, 0.5D, 0.5D); if (axis != Direction.Axis.Y) { - matrices.mulPose(Vector3f.XP.rotationDegrees(90.0F)); - if (axis != Direction.Axis.Z) matrices.mulPose(Vector3f.ZP.rotationDegrees(90.0F)); + matrices.mulPose(Axis.XP.rotationDegrees(90.0F)); + if (axis != Direction.Axis.Z) matrices.mulPose(Axis.ZP.rotationDegrees(90.0F)); } - matrices.mulPose(Vector3f.YP.rotationDegrees(rotation)); + matrices.mulPose(Axis.YP.rotationDegrees(rotation)); PoseStack.Pose transform = matrices.last(); final float radius = 0.25F; // Retain the original six quads and UV layout; native buffers supply lighting and overlay. diff --git a/src/main/java/zone/moddev/mc/basesteam/init/Blocks.java b/src/main/java/zone/moddev/mc/basesteam/init/Blocks.java index 6acd434..69b49b9 100644 --- a/src/main/java/zone/moddev/mc/basesteam/init/Blocks.java +++ b/src/main/java/zone/moddev/mc/basesteam/init/Blocks.java @@ -8,9 +8,11 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.item.Item; import net.minecraft.world.item.BlockItem; -import net.minecraftforge.event.RegistryEvent; +import net.minecraft.resources.ResourceLocation; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegisterEvent; import zone.moddev.mc.basepower.blocks.BlockPowerSwitch; import zone.moddev.mc.basepower.init.ItemGroups; import zone.moddev.mc.basesteam.SteamAdvantage; @@ -60,7 +62,6 @@ private Blocks() {} /** Constructs blocks during the Forge block registry event. */ public static synchronized void init() { if (initialized) return; - ItemGroups.init(); steam_pipe = add(new SteamPipeBlock(), "steam_pipe"); steam_track = add(new SteamTrackBlock(), "steam_track"); steam_boiler_coal = (GUIBlock) add(new CoalBoilerBlock(), "steam_boiler_coal"); @@ -86,27 +87,24 @@ public static synchronized void init() { } private static Block add(Block block, String id) { - block.setRegistryName(SteamAdvantage.MODID, id); ALL_BLOCKS.put(id, block); return block; } @SubscribeEvent - public static void registerBlocks(RegistryEvent.Register event) { + public static void registerBlocks(RegisterEvent event) { + if (!ForgeRegistries.Keys.BLOCKS.equals(event.getRegistryKey())) return; init(); - event.getRegistry().registerAll(ALL_BLOCKS.values().toArray(new Block[0])); + event.register(ForgeRegistries.Keys.BLOCKS, registry -> + ALL_BLOCKS.forEach((id, block) -> registry.register(new ResourceLocation(SteamAdvantage.MODID, id), block))); } @SubscribeEvent - public static void registerBlockItems(RegistryEvent.Register event) { - for (Map.Entry entry : ALL_BLOCKS.entrySet()) { - Item.Properties properties = new Item.Properties(); - if (!"pump_pipe_steam".equals(entry.getKey()) && !"drillbit".equals(entry.getKey())) { - properties.tab(ItemGroups.tab_powerAdvantage); - } - BlockItem item = new BlockItem(entry.getValue(), properties); - item.setRegistryName(SteamAdvantage.MODID, entry.getKey()); - event.getRegistry().register(item); - } + public static void registerBlockItems(RegisterEvent event) { + event.register(ForgeRegistries.Keys.ITEMS, registry -> ALL_BLOCKS.forEach((id, block) -> { + BlockItem item = new BlockItem(block, new Item.Properties()); + registry.register(new ResourceLocation(SteamAdvantage.MODID, id), item); + if (!"pump_pipe_steam".equals(id) && !"drillbit".equals(id)) ItemGroups.show(item); + })); } } diff --git a/src/main/java/zone/moddev/mc/basesteam/init/Enchantments.java b/src/main/java/zone/moddev/mc/basesteam/init/Enchantments.java index e631bf5..30d5cc6 100644 --- a/src/main/java/zone/moddev/mc/basesteam/init/Enchantments.java +++ b/src/main/java/zone/moddev/mc/basesteam/init/Enchantments.java @@ -7,7 +7,8 @@ import zone.moddev.mc.basesteam.enchantments.RecoilEnchantment; import net.minecraft.world.item.enchantment.Enchantment; import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.event.RegistryEvent; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegisterEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.eventbus.api.SubscribeEvent; @@ -24,9 +25,15 @@ public static void init(){ } @SubscribeEvent - public static void registerEnchantments(RegistryEvent.Register event) { + public static void registerEnchantments(RegisterEvent event) { + if (!ForgeRegistries.Keys.ENCHANTMENTS.equals(event.getRegistryKey())) return; init(); - event.getRegistry().registerAll(high_explosive, powderless, rapid_reload, recoil); + event.register(ForgeRegistries.Keys.ENCHANTMENTS, registry -> { + registry.register(new ResourceLocation(SteamAdvantage.MODID, "high_explosive"), high_explosive); + registry.register(new ResourceLocation(SteamAdvantage.MODID, "powderless"), powderless); + registry.register(new ResourceLocation(SteamAdvantage.MODID, "rapid_reload"), rapid_reload); + registry.register(new ResourceLocation(SteamAdvantage.MODID, "recoil"), recoil); + }); } private static void createEnchantments() { @@ -36,9 +43,5 @@ private static void createEnchantments() { rapid_reload = new RapidReloadEnchantment(); recoil = new RecoilEnchantment(); - high_explosive.setRegistryName(new ResourceLocation(SteamAdvantage.MODID, "high_explosive")); - powderless.setRegistryName(new ResourceLocation(SteamAdvantage.MODID, "powderless")); - rapid_reload.setRegistryName(new ResourceLocation(SteamAdvantage.MODID, "rapid_reload")); - recoil.setRegistryName(new ResourceLocation(SteamAdvantage.MODID, "recoil")); } } diff --git a/src/main/java/zone/moddev/mc/basesteam/init/Entities.java b/src/main/java/zone/moddev/mc/basesteam/init/Entities.java index 5d7004d..d547663 100644 --- a/src/main/java/zone/moddev/mc/basesteam/init/Entities.java +++ b/src/main/java/zone/moddev/mc/basesteam/init/Entities.java @@ -4,9 +4,11 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.Block; -import net.minecraftforge.event.RegistryEvent; +import net.minecraft.resources.ResourceLocation; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegisterEvent; import zone.moddev.mc.basesteam.SteamAdvantage; import zone.moddev.mc.basesteam.blocks.DrillBitTileEntity; import zone.moddev.mc.basesteam.machines.BlastFurnaceTileEntity; @@ -41,7 +43,8 @@ private Entities() {} public static void init() {} @SubscribeEvent - public static void register(RegistryEvent.Register> event) { + public static void register(RegisterEvent event) { + if (!ForgeRegistries.Keys.BLOCK_ENTITY_TYPES.equals(event.getRegistryKey())) return; STEAM_BOILER_COAL = register(event, "steam_boiler_coal", CoalBoilerTileEntity::new, Blocks.steam_boiler_coal); STEAM_BOILER_ELECTRIC = register(event, "steam_boiler_electric", ElectricBoilerTileEntity::new, Blocks.steam_boiler_electric); STEAM_BOILER_GEOTHERMAL = register(event, "steam_boiler_geothermal", GeothermalBoilerTileEntity::new, Blocks.steam_boiler_geothermal); @@ -57,10 +60,9 @@ public static void register(RegistryEvent.Register> event) { } private static BlockEntityType register( - RegistryEvent.Register> event, String id, BlockEntityType.BlockEntitySupplier factory, Block block) { + RegisterEvent event, String id, BlockEntityType.BlockEntitySupplier factory, Block block) { BlockEntityType type = BlockEntityType.Builder.of(factory, block).build(null); - type.setRegistryName(SteamAdvantage.MODID, id); - event.getRegistry().register(type); + event.register(ForgeRegistries.Keys.BLOCK_ENTITY_TYPES, new ResourceLocation(SteamAdvantage.MODID, id), () -> type); return type; } } diff --git a/src/main/java/zone/moddev/mc/basesteam/init/Items.java b/src/main/java/zone/moddev/mc/basesteam/init/Items.java index 2b0180f..8ab01cf 100644 --- a/src/main/java/zone/moddev/mc/basesteam/init/Items.java +++ b/src/main/java/zone/moddev/mc/basesteam/init/Items.java @@ -4,10 +4,12 @@ import java.util.Map; import java.util.Set; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; -import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegisterEvent; import zone.moddev.mc.basepower.init.ItemGroups; import zone.moddev.mc.basesteam.SteamAdvantage; import zone.moddev.mc.basesteam.items.MusketItem; @@ -26,28 +28,26 @@ private Items() {} /** Constructs items during the Forge item registry event. */ public static synchronized void init() { if (initialized) return; - ItemGroups.init(); - steam_governor = add("steam_governor", new Item(properties())); - steam_drill_bit = add("steam_drill_bit", new Item(properties())); - blackpowder_cartridge = add("blackpowder_cartridge", new Item(properties())); - blackpowder_musket = add("musket", new MusketItem(properties().stacksTo(1))); + steam_governor = add("steam_governor", new Item(new Item.Properties())); + steam_drill_bit = add("steam_drill_bit", new Item(new Item.Properties())); + blackpowder_cartridge = add("blackpowder_cartridge", new Item(new Item.Properties())); + blackpowder_musket = add("musket", new MusketItem(new Item.Properties().stacksTo(1))); initialized = true; } - private static Item.Properties properties() { - return new Item.Properties().tab(ItemGroups.tab_powerAdvantage); - } - private static Item add(String id, Item item) { - item.setRegistryName(SteamAdvantage.MODID, id); allItems.put(id, item); return item; } @SubscribeEvent - public static void register(RegistryEvent.Register event) { + public static void register(RegisterEvent event) { + if (!ForgeRegistries.Keys.ITEMS.equals(event.getRegistryKey())) return; init(); - event.getRegistry().registerAll(allItems.values().toArray(new Item[0])); + event.register(ForgeRegistries.Keys.ITEMS, registry -> allItems.forEach((id, item) -> { + registry.register(new ResourceLocation(SteamAdvantage.MODID, id), item); + ItemGroups.show(item); + })); } public static Item getItemByName(String id) { return allItems.get(id); } diff --git a/src/main/java/zone/moddev/mc/basesteam/init/Power.java b/src/main/java/zone/moddev/mc/basesteam/init/Power.java index a27a2d3..7767438 100644 --- a/src/main/java/zone/moddev/mc/basesteam/init/Power.java +++ b/src/main/java/zone/moddev/mc/basesteam/init/Power.java @@ -1,7 +1,13 @@ package zone.moddev.mc.basesteam.init; import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.damagesource.DamageType; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; import cyano.poweradvantage.api.ConduitType; +import zone.moddev.mc.basesteam.SteamAdvantage; public abstract class Power { @@ -10,6 +16,22 @@ public abstract class Power { public static float ENERGY_LOST_PER_TICK = 0.0625f; - public static final DamageSource machine_damage = new DamageSource("steam_machine"); - public static final DamageSource musket_damage = new DamageSource("musket"); + private static final ResourceKey MACHINE_DAMAGE = damageType("steam_machine"); + private static final ResourceKey MUSKET_DAMAGE = damageType("musket"); + + public static DamageSource machineDamage(Level level) { + return damageSource(level, MACHINE_DAMAGE); + } + + public static DamageSource musketDamage(Level level) { + return damageSource(level, MUSKET_DAMAGE); + } + + private static ResourceKey damageType(String id) { + return ResourceKey.create(Registries.DAMAGE_TYPE, new ResourceLocation(SteamAdvantage.MODID, id)); + } + + private static DamageSource damageSource(Level level, ResourceKey type) { + return new DamageSource(level.registryAccess().registryOrThrow(Registries.DAMAGE_TYPE).getHolderOrThrow(type)); + } } diff --git a/src/main/java/zone/moddev/mc/basesteam/init/Villages.java b/src/main/java/zone/moddev/mc/basesteam/init/Villages.java index af6dbcf..9799d9b 100644 --- a/src/main/java/zone/moddev/mc/basesteam/init/Villages.java +++ b/src/main/java/zone/moddev/mc/basesteam/init/Villages.java @@ -1,7 +1,7 @@ package zone.moddev.mc.basesteam.init; import java.util.List; -import java.util.Random; +import net.minecraft.util.RandomSource; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.entity.npc.VillagerProfession; import net.minecraft.world.entity.npc.VillagerTrades; @@ -50,7 +50,7 @@ private static VillagerTrades.ItemListing sell(Item item, int min, int max) { }; } - private static int randomPrice(Random random, int min, int max) { + private static int randomPrice(RandomSource random, int min, int max) { return min + random.nextInt(max - min + 1); } diff --git a/src/main/java/zone/moddev/mc/basesteam/items/MusketItem.java b/src/main/java/zone/moddev/mc/basesteam/items/MusketItem.java index 0929ceb..3f1f8c7 100644 --- a/src/main/java/zone/moddev/mc/basesteam/items/MusketItem.java +++ b/src/main/java/zone/moddev/mc/basesteam/items/MusketItem.java @@ -46,9 +46,7 @@ import net.minecraft.world.level.ClipContext; import net.minecraft.world.phys.Vec3; import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.TranslatableComponent; import net.minecraft.world.level.Level; -import net.minecraft.world.level.Explosion; import net.minecraft.server.level.ServerLevel; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; @@ -168,14 +166,16 @@ protected void fire(ItemStack srcItemStack, LivingEntity playerEntity, return; } final float explodeFactor = 0.75f; + // TNT keeps the old BREAK behavior by default; BLOCK now decays drops by default. + final Level.ExplosionInteraction explosionInteraction = Level.ExplosionInteraction.TNT; if(rayTrace instanceof EntityHitResult){ Entity e = ((EntityHitResult) rayTrace).getEntity(); - e.hurt(Power.musket_damage, getShotDamage()); + e.hurt(Power.musketDamage(world), getShotDamage()); if(EnchantmentHelper.getItemEnchantmentLevel(Enchantments.high_explosive, srcItemStack) > 0){ int lvl = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.high_explosive, srcItemStack); world.explode(playerEntity, e.getX(), e.getY()+0.5, e.getZ(), explodeFactor * lvl, - Explosion.BlockInteraction.BREAK); + explosionInteraction); AABB fireArea = new AABB(e.getX()-lvl, e.getY()-lvl, e.getZ()-lvl, e.getX()+lvl, e.getY()+lvl, e.getZ()+lvl); List collateralDamage = world.getEntitiesOfClass(LivingEntity.class, fireArea); @@ -208,7 +208,7 @@ protected void fire(ItemStack srcItemStack, LivingEntity playerEntity, int lvl = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.high_explosive, srcItemStack); world.explode(playerEntity, rayTrace.getLocation().x, rayTrace.getLocation().y, rayTrace.getLocation().z, explodeFactor * lvl, - Explosion.BlockInteraction.BREAK); + explosionInteraction); AABB fireArea = new AABB(rayTrace.getLocation().x-lvl, rayTrace.getLocation().y-lvl, rayTrace.getLocation().z-lvl, rayTrace.getLocation().x+lvl, rayTrace.getLocation().y+lvl, rayTrace.getLocation().z+lvl); List collateralDamage = world.getEntitiesOfClass(LivingEntity.class, fireArea); @@ -356,7 +356,7 @@ public static HitResult rayTraceBlocksAndEntities(Level w, double maxRange, Livi Vec3 rayDirection = source.getLookAngle(); Vec3 loadedEnd = loadedRayEnd(rayOrigin, rayOrigin.add(rayDirection.scale(maxRange)), w::hasChunkAt); if(loadedEnd.equals(rayOrigin)) return BlockHitResult.miss(rayOrigin, - net.minecraft.core.Direction.getNearest(rayDirection.x, rayDirection.y, rayDirection.z), new BlockPos(rayOrigin)); + net.minecraft.core.Direction.getNearest(rayDirection.x, rayDirection.y, rayDirection.z), BlockPos.containing(rayOrigin)); BlockHitResult terrainHit = w.clip(new ClipContext(rayOrigin, loadedEnd, ClipContext.Block.COLLIDER, ClipContext.Fluid.SOURCE_ONLY, source)); Vec3 rayEnd = terrainHit.getLocation(); @@ -382,7 +382,7 @@ public static HitResult rayTraceBlocksAndEntities(Level w, double maxRange, Livi } static Vec3 loadedRayEnd(Vec3 origin, Vec3 end, java.util.function.Predicate loaded) { - if(!loaded.test(new BlockPos(origin))) return origin; + if(!loaded.test(BlockPos.containing(origin))) return origin; // Reuse native voxel traversal so even a grazing diagonal cannot enter an unloaded chunk. Vec3 boundary = net.minecraft.world.level.BlockGetter.traverseBlocks(origin, end, loaded, (test, pos) -> test.test(pos) ? null : new AABB(pos).clip(origin, end).orElse(origin), @@ -446,12 +446,12 @@ private static double min(double a, double b){ @Override public void appendHoverText(ItemStack stack, Level world, List list, net.minecraft.world.item.TooltipFlag flag){ super.appendHoverText(stack,world,list,flag); - list.add(new TranslatableComponent("tooltip.musket.damage", (int)MusketItem.getShotDamage())); + list.add(Component.translatable("tooltip.musket.damage", (int)MusketItem.getShotDamage())); if(isLoaded(stack)){ - list.add(new TranslatableComponent("tooltip.musket.loaded")); + list.add(Component.translatable("tooltip.musket.loaded")); } else { - list.add(new TranslatableComponent("tooltip.musket.unloaded")); - list.add(new TranslatableComponent("tooltip.musket.ammo", new TranslatableComponent("item.basesteam.blackpowder_cartridge"))); + list.add(Component.translatable("tooltip.musket.unloaded")); + list.add(Component.translatable("tooltip.musket.ammo", Component.translatable("item.basesteam.blackpowder_cartridge"))); } } diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/BlastFurnaceTileEntity.java b/src/main/java/zone/moddev/mc/basesteam/machines/BlastFurnaceTileEntity.java index c5d8f2f..2df620e 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/BlastFurnaceTileEntity.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/BlastFurnaceTileEntity.java @@ -7,7 +7,6 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.SimpleContainer; import net.minecraft.world.item.crafting.Recipe; -import net.minecraft.network.chat.TextComponent; import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; @@ -208,8 +207,8 @@ private int getFuelBurnTime(ItemStack item) { } private void decrementFuel() { - if(inventory[0].getCount() == 1 && inventory[0].getItem().getContainerItem(inventory[0]) != null){ - inventory[0] = inventory[0].getItem().getContainerItem(inventory[0]); + if(inventory[0].getCount() == 1){ + inventory[0] = inventory[0].getCraftingRemainingItem(); } else { this.removeItem(0, 1); } @@ -337,7 +336,7 @@ private ItemStack getSmeltingResult(ItemStack input) { SimpleContainer inventory = new SimpleContainer(1); inventory.setItem(0, input); return getLevel().getRecipeManager().getRecipeFor(RecipeType.SMELTING, inventory, getLevel()) - .map(recipe -> recipe.assemble(inventory)).orElse(ItemStack.EMPTY); + .map(recipe -> recipe.assemble(inventory, getLevel().registryAccess())).orElse(ItemStack.EMPTY); } diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/CoalBoilerTileEntity.java b/src/main/java/zone/moddev/mc/basesteam/machines/CoalBoilerTileEntity.java index c39e207..487ec0b 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/CoalBoilerTileEntity.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/CoalBoilerTileEntity.java @@ -115,8 +115,8 @@ private int getFuelBurnTime() { private void decrementFuel() { - if(inventory[0].getCount() == 1 && inventory[0].getItem().getContainerItem(inventory[0]) != null){ - inventory[0] = inventory[0].getItem().getContainerItem(inventory[0]); + if(inventory[0].getCount() == 1){ + inventory[0] = inventory[0].getCraftingRemainingItem(); } else { this.removeItem(0, 1); } diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/GeothermalBoilerTileEntity.java b/src/main/java/zone/moddev/mc/basesteam/machines/GeothermalBoilerTileEntity.java index b34abd6..facaf4d 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/GeothermalBoilerTileEntity.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/GeothermalBoilerTileEntity.java @@ -123,7 +123,7 @@ static float thermalEnergy(BlockState bs){ if(b == net.minecraft.world.level.block.Blocks.FIRE){ return 200; } else if(bs.getFluidState().getType().isSame(net.minecraft.world.level.material.Fluids.LAVA)) { - return (bs.getFluidState().getType().getAttributes().getTemperature() - 372) * 0.5f; + return (bs.getFluidState().getType().getFluidType().getTemperature() - 372) * 0.5f; } else { return 0; } diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/OilBoilerTileEntity.java b/src/main/java/zone/moddev/mc/basesteam/machines/OilBoilerTileEntity.java index 02aba53..a01b369 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/OilBoilerTileEntity.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/OilBoilerTileEntity.java @@ -19,7 +19,6 @@ import net.minecraft.core.Direction; import net.minecraft.sounds.SoundSource; import net.minecraft.resources.ResourceLocation; -import net.minecraft.core.Registry; import net.minecraft.world.level.material.Fluid; import cyano.poweradvantage.api.fluid.FluidStack; import cyano.poweradvantage.api.fluid.FluidTank; diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/SteamDrillTileEntity.java b/src/main/java/zone/moddev/mc/basesteam/machines/SteamDrillTileEntity.java index c9698fc..1065e17 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/SteamDrillTileEntity.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/SteamDrillTileEntity.java @@ -133,7 +133,7 @@ private boolean canStack(ItemStack newItem, ItemStack slot) { if(slot == null || newItem == null) return true; return ItemStack.isSame(newItem, slot) && ItemStack.tagMatches(newItem, slot) - && (newItem.getCount() + slot.getCount() <= slot.getItem().getItemStackLimit(slot)); + && (newItem.getCount() + slot.getCount() <= slot.getMaxStackSize()); } public ItemStack addItem(ItemStack in){ @@ -144,13 +144,14 @@ public ItemStack addItem(ItemStack in){ } else if(ItemStack.isSame(in, inventory[i]) && ItemStack.tagMatches(in, inventory[i])){ // are stackable - if(inventory[i].getCount() >= inventory[i].getItem().getItemStackLimit(inventory[i])){ + int maxStackSize = inventory[i].getMaxStackSize(); + if(inventory[i].getCount() >= maxStackSize){ continue; - } else if(in.getCount() + inventory[i].getCount() <= inventory[i].getItem().getItemStackLimit(inventory[i])){ + } else if(in.getCount() + inventory[i].getCount() <= maxStackSize){ inventory[i].grow(in.getCount()); return null; } else { - int delta = inventory[i].getItem().getItemStackLimit(inventory[i]) - inventory[i].getCount(); + int delta = maxStackSize - inventory[i].getCount(); inventory[i].grow(delta); in.shrink(delta); } diff --git a/src/main/java/zone/moddev/mc/basesteam/recipe/RecipeConditions.java b/src/main/java/zone/moddev/mc/basesteam/recipe/RecipeConditions.java index 22d3568..e1ce67c 100644 --- a/src/main/java/zone/moddev/mc/basesteam/recipe/RecipeConditions.java +++ b/src/main/java/zone/moddev/mc/basesteam/recipe/RecipeConditions.java @@ -41,12 +41,12 @@ private static final class ModeCondition implements ICondition { private final RecipeMode mode; private ModeCondition(RecipeMode mode) { this.mode = mode; } @Override public ResourceLocation getID() { return ID; } - @Override public boolean test() { return PowerAdvantage.recipeMode == mode; } + @Override public boolean test(IContext context) { return PowerAdvantage.recipeMode == mode; } } private static final class MusketCondition implements ICondition { private static final ResourceLocation ID = new ResourceLocation(SteamAdvantage.MODID, "musket_enabled"); @Override public ResourceLocation getID() { return ID; } - @Override public boolean test() { return SteamAdvantage.MUSKET_ENABLE; } + @Override public boolean test(IContext context) { return SteamAdvantage.MUSKET_ENABLE; } } } diff --git a/src/main/java/zone/moddev/mc/basesteam/util/SoundHelper.java b/src/main/java/zone/moddev/mc/basesteam/util/SoundHelper.java index 7359457..4161405 100644 --- a/src/main/java/zone/moddev/mc/basesteam/util/SoundHelper.java +++ b/src/main/java/zone/moddev/mc/basesteam/util/SoundHelper.java @@ -1,8 +1,10 @@ package zone.moddev.mc.basesteam.util; +import java.util.concurrent.ThreadLocalRandom; import net.minecraft.world.entity.player.Player; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.network.protocol.game.ClientboundCustomSoundPacket; +import net.minecraft.network.protocol.game.ClientboundSoundPacket; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.entity.EntitySelector; import net.minecraft.sounds.SoundSource; @@ -30,8 +32,9 @@ public static void playSoundAtPosition(double x, double y, double z, SoundEvent if(!(candidate instanceof ServerPlayer) || !EntitySelector.NO_SPECTATORS.test(candidate) || !area.intersects(candidate.getBoundingBox())) continue; ServerPlayer player = (ServerPlayer) candidate; - if(player.connection != null) player.connection.send(new ClientboundCustomSoundPacket(sound.getRegistryName(), soundType, - new Vec3(x, y, z), volume, pitch)); + if(player.connection != null) player.connection.send(new ClientboundSoundPacket( + BuiltInRegistries.SOUND_EVENT.wrapAsHolder(sound), soundType, x, y, z, volume, pitch, + ThreadLocalRandom.current().nextLong())); } } @@ -45,8 +48,9 @@ public static void playBigSoundAtPosition(double x, double y, double z, SoundEve if(!(candidate instanceof ServerPlayer) || !EntitySelector.NO_SPECTATORS.test(candidate) || !area.intersects(candidate.getBoundingBox())) continue; ServerPlayer player = (ServerPlayer) candidate; - if(player.connection != null) player.connection.send(new ClientboundCustomSoundPacket(sound.getRegistryName(), soundType, - new Vec3(x, y, z), volume, pitch)); + if(player.connection != null) player.connection.send(new ClientboundSoundPacket( + BuiltInRegistries.SOUND_EVENT.wrapAsHolder(sound), soundType, x, y, z, volume, pitch, + ThreadLocalRandom.current().nextLong())); } } } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index e03ba03..717642a 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -14,14 +14,14 @@ description='''Steam-powered machines and infrastructure for basePower.''' [[dependencies.basesteam]] modId="basepower" mandatory=true -versionRange="[2.4.10.118021,3.0.0)" +versionRange="[2.4.11.119041,3.0.0)" ordering="AFTER" side="BOTH" [[dependencies.basesteam]] modId="basemetals" mandatory=false -versionRange="[3.1.2.118021,4.0.0)" +versionRange="[3.1.2.119041,4.0.0)" ordering="AFTER" side="BOTH" diff --git a/src/main/resources/assets/minecraft/atlases/blocks.json b/src/main/resources/assets/minecraft/atlases/blocks.json new file mode 100644 index 0000000..d69f990 --- /dev/null +++ b/src/main/resources/assets/minecraft/atlases/blocks.json @@ -0,0 +1,8 @@ +{ + "sources": [ + { + "type": "single", + "resource": "basesteam:entity/drill_bit" + } + ] +} diff --git a/src/main/resources/data/basesteam/damage_type/musket.json b/src/main/resources/data/basesteam/damage_type/musket.json new file mode 100644 index 0000000..e461c04 --- /dev/null +++ b/src/main/resources/data/basesteam/damage_type/musket.json @@ -0,0 +1,5 @@ +{ + "message_id": "musket", + "scaling": "never", + "exhaustion": 0.1 +} diff --git a/src/main/resources/data/basesteam/damage_type/steam_machine.json b/src/main/resources/data/basesteam/damage_type/steam_machine.json new file mode 100644 index 0000000..3340c32 --- /dev/null +++ b/src/main/resources/data/basesteam/damage_type/steam_machine.json @@ -0,0 +1,5 @@ +{ + "message_id": "steam_machine", + "scaling": "never", + "exhaustion": 0.1 +} diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index 9ecce85..0d628d2 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,8 +1,7 @@ { "pack":{ - "pack_format":9, - "forge:resource_pack_format":8, - "forge:data_pack_format":9, + "pack_format":13, + "forge:server_data_pack_format":12, "description":"baseSteam" } } diff --git a/src/test/java/zone/moddev/mc/basesteam/items/MusketLoadedChunkBoundaryTest.java b/src/test/java/zone/moddev/mc/basesteam/items/MusketLoadedChunkBoundaryTest.java index cc98808..2319d83 100644 --- a/src/test/java/zone/moddev/mc/basesteam/items/MusketLoadedChunkBoundaryTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/items/MusketLoadedChunkBoundaryTest.java @@ -15,7 +15,7 @@ public class MusketLoadedChunkBoundaryTest { pos -> pos.getX() >= 0 && pos.getX() < 16 && pos.getZ() >= 0 && pos.getZ() < 16 && pos.getY() >= 64 && pos.getY() < 96); assertTrue(direction.toString(), origin.distanceToSqr(actual) < origin.distanceToSqr(end)); - BlockPos stopped = new BlockPos(actual); + BlockPos stopped = BlockPos.containing(actual); assertTrue(stopped.getX() >= 0 && stopped.getX() < 16); assertTrue(stopped.getZ() >= 0 && stopped.getZ() < 16); assertTrue(stopped.getY() >= 64 && stopped.getY() < 96); diff --git a/src/test/java/zone/moddev/mc/basesteam/machines/CoalBoilerFillPersistenceTest.java b/src/test/java/zone/moddev/mc/basesteam/machines/CoalBoilerFillPersistenceTest.java index 5efb20e..16bd084 100644 --- a/src/test/java/zone/moddev/mc/basesteam/machines/CoalBoilerFillPersistenceTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/machines/CoalBoilerFillPersistenceTest.java @@ -215,6 +215,6 @@ private static SaveWorld world(boolean remote) { } } private static final class SaveWorld extends net.minecraft.server.level.ServerLevel { - private SaveWorld() { super(null, null, null, null, null, null, null, null, false, 0L, java.util.Collections.emptyList(), false); } + private SaveWorld() { super(null, null, null, null, null, null, null, false, 0L, java.util.Collections.emptyList(), false); } } } diff --git a/src/test/java/zone/moddev/mc/basesteam/machines/GeothermalNativeTemperatureTest.java b/src/test/java/zone/moddev/mc/basesteam/machines/GeothermalNativeTemperatureTest.java index 8146052..73e6d42 100644 --- a/src/test/java/zone/moddev/mc/basesteam/machines/GeothermalNativeTemperatureTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/machines/GeothermalNativeTemperatureTest.java @@ -9,10 +9,13 @@ import static org.junit.Assert.*; public class GeothermalNativeTemperatureTest { - @BeforeClass public static void bootstrap() { NativeTestBootstrap.start(); } + @BeforeClass public static void bootstrap() throws ReflectiveOperationException { + NativeTestBootstrap.registerVanillaFluidTypes(); + Blocks.LAVA.getStateDefinition().getPossibleStates().forEach(state -> state.initCache()); + } @Test public void nativeLavaTemperaturePreservesTheExistingHeatInput() { - assertEquals(1300, Fluids.LAVA.getAttributes().getTemperature()); + assertEquals(1300, Fluids.LAVA.getFluidType().getTemperature()); assertEquals(464, GeothermalBoilerTileEntity.thermalEnergy(Blocks.LAVA.defaultBlockState()), 0); assertEquals(464, GeothermalBoilerTileEntity.thermalEnergy(Blocks.LAVA.defaultBlockState() .setValue(BlockStateProperties.LEVEL, 7)), 0); diff --git a/src/test/java/zone/moddev/mc/basesteam/machines/RockCrusherReloadRedstoneTest.java b/src/test/java/zone/moddev/mc/basesteam/machines/RockCrusherReloadRedstoneTest.java index 5fbc624..8bee790 100644 --- a/src/test/java/zone/moddev/mc/basesteam/machines/RockCrusherReloadRedstoneTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/machines/RockCrusherReloadRedstoneTest.java @@ -83,7 +83,7 @@ private static final class ControlSample extends RuntimeException {} private static final class ControlWorld extends ServerLevel { boolean powered, stopAtFirstSample; int samples; - private ControlWorld() { super(null, null, null, null, null, null, null, null, false, 0L, java.util.Collections.emptyList(), false); } + private ControlWorld() { super(null, null, null, null, null, null, null, false, 0L, java.util.Collections.emptyList(), false); } @Override public boolean hasNeighborSignal(BlockPos pos) { samples++; if (stopAtFirstSample) throw new ControlSample(); diff --git a/src/test/java/zone/moddev/mc/basesteam/machines/SteamMachinePersistenceTest.java b/src/test/java/zone/moddev/mc/basesteam/machines/SteamMachinePersistenceTest.java index 68b2c28..387e515 100644 --- a/src/test/java/zone/moddev/mc/basesteam/machines/SteamMachinePersistenceTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/machines/SteamMachinePersistenceTest.java @@ -131,13 +131,12 @@ private static CompoundTag water(int amount) { private static BlockEntityType type(String id, BlockEntityType.BlockEntitySupplier factory) { ForgeRegistry> registry = - (ForgeRegistry>) ForgeRegistries.BLOCK_ENTITIES; + (ForgeRegistry>) ForgeRegistries.BLOCK_ENTITY_TYPES; boolean locked = registry.isLocked(); registry.unfreeze(); try { BlockEntityType type = BlockEntityType.Builder.of(factory, Blocks.STONE).build(null); - type.setRegistryName(new ResourceLocation("basesteam", id)); - registry.register(type); + registry.register(new ResourceLocation("basesteam", id), type); return type; } finally { if (locked) registry.freeze(); diff --git a/src/test/java/zone/moddev/mc/basesteam/machines/SteamOperationalPumpSourceTest.java b/src/test/java/zone/moddev/mc/basesteam/machines/SteamOperationalPumpSourceTest.java index f366d43..75116c0 100644 --- a/src/test/java/zone/moddev/mc/basesteam/machines/SteamOperationalPumpSourceTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/machines/SteamOperationalPumpSourceTest.java @@ -47,6 +47,7 @@ public class SteamOperationalPumpSourceTest { for (Block block : new Block[]{Blocks.WATER, Blocks.LAVA}) { BlockState source = block.defaultBlockState(); PickupWorld world = new PickupWorld(source); + assertTrue(source.getFluidState().isSource()); assertSame(source.getFluidState().getType(), SteamPumpTileEntity.pickupSource(world.world, SOURCE, source)); assertEquals(1, world.writes); assertEquals(Blocks.AIR.defaultBlockState(), world.state); @@ -58,8 +59,8 @@ public class SteamOperationalPumpSourceTest { @Override public FluidState getFluidState(BlockState state) { return Fluids.WATER.defaultFluidState(); } }; BlockState source = host.defaultBlockState(); - assertTrue(source.getFluidState().isSource()); PickupWorld world = new PickupWorld(source); + assertTrue(source.getFluidState().isSource()); assertSame(Fluids.EMPTY, SteamPumpTileEntity.pickupSource(world.world, SOURCE, source)); assertSame(source, world.state); assertEquals(0, world.writes); @@ -68,12 +69,16 @@ public class SteamOperationalPumpSourceTest { @Test public void flowingAndRefusedSourcesCannotProduceABucket() { BlockState flowing = Blocks.WATER.defaultBlockState().setValue(BlockStateProperties.LEVEL, 1); PickupWorld world = new PickupWorld(flowing); + assertFalse(flowing.getFluidState().isEmpty()); + assertFalse(flowing.getFluidState().isSource()); assertSame(Fluids.EMPTY, SteamPumpTileEntity.pickupSource(world.world, SOURCE, flowing)); assertEquals(0, world.writes); RefusingSource host = new RefusingSource(); BlockState refused = host.defaultBlockState(); PickupWorld refusedWorld = new PickupWorld(refused); + assertTrue(refused.getFluidState().isSource()); assertSame(Fluids.EMPTY, SteamPumpTileEntity.pickupSource(refusedWorld.world, SOURCE, refused)); + assertEquals(1, host.pickups); assertSame(refused, refusedWorld.state); assertEquals(0, refusedWorld.writes); } @@ -124,9 +129,13 @@ private static MethodNode method(ClassNode node, String name) { } private static final class RefusingSource extends Block implements BucketPickup { + private int pickups; private RefusingSource() { super(Block.Properties.of(Material.STONE)); } @Override public FluidState getFluidState(BlockState state) { return Fluids.WATER.defaultFluidState(); } - @Override public net.minecraft.world.item.ItemStack pickupBlock(LevelAccessor world, BlockPos pos, BlockState state) { return net.minecraft.world.item.ItemStack.EMPTY; } + @Override public net.minecraft.world.item.ItemStack pickupBlock(LevelAccessor world, BlockPos pos, BlockState state) { + pickups++; + return net.minecraft.world.item.ItemStack.EMPTY; + } @Override public java.util.Optional getPickupSound() { return java.util.Optional.empty(); } } @@ -136,12 +145,15 @@ private static final class PickupWorld { private final LevelAccessor world; private PickupWorld(BlockState initial) { + // Forge initializes BlockState's cached fluid in the post-registration bake. + initial.initCache(); state = initial; world = (LevelAccessor) Proxy.newProxyInstance(LevelAccessor.class.getClassLoader(), new Class[]{LevelAccessor.class}, (proxy, method, arguments) -> { if (method.getName().equals("setBlock")) { assertEquals(SOURCE, arguments[0]); state = (BlockState) arguments[1]; + state.initCache(); writes++; return true; } diff --git a/src/test/java/zone/moddev/mc/basesteam/machines/WaterBoilerTankNotificationTest.java b/src/test/java/zone/moddev/mc/basesteam/machines/WaterBoilerTankNotificationTest.java index b37a83c..71c2b02 100644 --- a/src/test/java/zone/moddev/mc/basesteam/machines/WaterBoilerTankNotificationTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/machines/WaterBoilerTankNotificationTest.java @@ -161,6 +161,6 @@ private static Level world(boolean client) { } catch (ReflectiveOperationException failure) { throw new AssertionError(failure); } } private static final class SaveWorld extends net.minecraft.server.level.ServerLevel { - private SaveWorld() { super(null, null, null, null, null, null, null, null, false, 0L, java.util.Collections.emptyList(), false); } + private SaveWorld() { super(null, null, null, null, null, null, null, false, 0L, java.util.Collections.emptyList(), false); } } } diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/BuildWorkflowContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/BuildWorkflowContractTest.java index 159a505..4a2c615 100644 --- a/src/test/java/zone/moddev/mc/basesteam/resources/BuildWorkflowContractTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/resources/BuildWorkflowContractTest.java @@ -8,12 +8,14 @@ public class BuildWorkflowContractTest { @Test public void everyHostedBuildUsesPinnedToolchainProjectProperties() throws Exception { + assertTrue(Files.isRegularFile(Paths.get("gradle/forge45-mavenizer.gradle"))); java.util.Properties pins = new java.util.Properties(); try (java.io.Reader reader = Files.newBufferedReader(Paths.get("gradle.properties"), StandardCharsets.UTF_8)) { pins.load(reader); } for (String key : new String[] {"poweradvantage_source_ref", "poweradvantage_deobf_sha256", - "poweradvantage_player_sha256", "basemetals_source_commit", "basemetals_deobf_sha256", + "poweradvantage_player_sha256", "poweradvantage_test_bootstrap_sha256", + "poweradvantage_test_bootstrap_source_sha256", "basemetals_source_commit", "basemetals_deobf_sha256", "basemetals_player_sha256", "orespawn_source_commit", "orespawn_deobf_sha256", "orespawn_player_sha256"}) { assertTrue(key, pins.getProperty(key, "").matches( @@ -28,14 +30,14 @@ public class BuildWorkflowContractTest { assertTrue(name, source.contains("basePower-" + pins.getProperty("poweradvantage_version") + "-deobf.jar")); assertTrue(name, source.contains("ref: ${{ env.ORESPAWN_SOURCE_REF }}")); assertTrue(name, source.contains("ref: ${{ env.POWERADVANTAGE_SOURCE_REF }}")); - assertTrue(name, source.contains("The exact basePower 1.18 dependency pin is pending.")); - assertTrue(name, source.contains("The exact Base Metals 1.18 dependency pin is pending.")); - assertTrue(name, source.contains("The exact OreSpawn 1.18 dependency pin is pending.")); + assertTrue(name, source.contains("The exact basePower 1.19 dependency pin is pending.")); + assertTrue(name, source.contains("The exact Base Metals 1.19 dependency pin is pending.")); + assertTrue(name, source.contains("The exact OreSpawn 1.19 dependency pin is pending.")); assertTrue(name, source.contains("^[a-f0-9]{40}$")); assertTrue(name, source.contains("^[A-F0-9]{64}$")); int pinCheck = source.indexOf("Validate coordinated dependency pins"); assertTrue(name, pinCheck >= 0 && pinCheck < source.indexOf("Check out pinned Base Metals") - && pinCheck < source.indexOf("Check out basePower 1.18.2")); + && pinCheck < source.indexOf("Check out basePower 1.19.4")); assertFalse(name, source.contains("-Ppoweradvantage_source_ref=")); assertTrue(name, source.contains("-Porg.gradle.java.installations.paths=")); assertTrue(name, source.contains("-Porg.gradle.java.installations.auto-detect=false")); @@ -43,6 +45,22 @@ public class BuildWorkflowContractTest { assertFalse(name, source.contains("-Dorg.gradle.java.installations.")); assertTrue(name, source.contains("jar unitBootstrapJar verifyReleaseDependencies")); assertTrue(name, source.contains("-PforgeCapabilityBootstrapJar=${{ steps.dependencies.outputs.bootstrap }}")); + assertTrue(name, source.contains("mavenizer=\"$orespawn/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar\"")); + assertTrue(name, source.contains("341B3FB10ABA6EA7DC612FBE519DC290A1C81867AED5DD320ACD783688AC49DE")); + int toolCheck = source.indexOf("| sha256sum -c -"); + assertTrue(name, toolCheck >= 0 && toolCheck < source.indexOf("(cd \"$basemetals\"")); + assertFalse(name, source.contains("forge45-mavenizer.init.gradle") || source.contains("-Pforge45MavenizerScript")); + assertTrue(name, source.contains( + "(cd \"$basemetals\" && ./gradlew $MMD_GRADLE_TOOLCHAINS jar --no-daemon --stacktrace)")); + String toolArg = "\"-Pforge45MavenizerJar=$mavenizer\""; + int powerStart = source.indexOf("(cd \"$power\""); + int powerEnd = source.indexOf("\n powerjar=", powerStart); + assertTrue(name, powerStart >= 0 && powerEnd > powerStart); + String powerBuild = source.substring(powerStart, powerEnd); + assertTrue(name, powerBuild.contains(toolArg)); + assertFalse(name, powerBuild.contains("-I ") || powerBuild.contains("--init-script")); + assertEquals(name, 1L, source.lines().filter(line -> line.contains(toolArg)).count()); + assertTrue(name, source.contains("\"-Pforge45MavenizerJar=${{ steps.dependencies.outputs.mavenizer }}\"")); for (String line : source.split("\\R")) { if (line.contains("./gradlew ") && !line.contains("chmod")) { assertTrue(name + ": " + line, line.contains("./gradlew $MMD_GRADLE_TOOLCHAINS ")); diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/NativeDamageTypeContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/NativeDamageTypeContractTest.java new file mode 100644 index 0000000..b1e6d3c --- /dev/null +++ b/src/test/java/zone/moddev/mc/basesteam/resources/NativeDamageTypeContractTest.java @@ -0,0 +1,30 @@ +package zone.moddev.mc.basesteam.resources; + +import com.google.gson.JsonParser; +import com.mojang.serialization.JsonOps; +import java.io.Reader; +import java.nio.file.Files; +import java.nio.file.Path; +import net.minecraft.world.damagesource.DamageScaling; +import net.minecraft.world.damagesource.DamageType; +import org.junit.BeforeClass; +import org.junit.Test; +import zone.moddev.mc.basesteam.testing.NativeTestBootstrap; +import static org.junit.Assert.*; + +public class NativeDamageTypeContractTest { + @BeforeClass public static void bootstrap() { NativeTestBootstrap.start(); } + + @Test public void nativeDamageTypesRetainLegacyMessagesScalingAndExhaustion() throws Exception { + for (String id : new String[] { "steam_machine", "musket" }) { + Path path = Path.of("src/main/resources/data/basesteam/damage_type", id + ".json"); + try (Reader reader = Files.newBufferedReader(path)) { + DamageType type = DamageType.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)) + .result().orElseThrow(() -> new AssertionError("Invalid native damage type " + id)); + assertEquals(id, type.msgId()); + assertEquals(DamageScaling.NEVER, type.scaling()); + assertEquals(0.1F, type.exhaustion(), 0.0F); + } + } + } +} diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/NativeFuelContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/NativeFuelContractTest.java index 942002b..a083efc 100644 --- a/src/test/java/zone/moddev/mc/basesteam/resources/NativeFuelContractTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/resources/NativeFuelContractTest.java @@ -7,14 +7,17 @@ import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.server.packs.PackType; import net.minecraft.server.packs.resources.MultiPackResourceManager; -import net.minecraft.server.packs.VanillaPackResources; +import net.minecraft.server.packs.repository.ServerPacksSource; import net.minecraft.tags.ItemTags; import net.minecraft.tags.TagKey; import net.minecraft.tags.TagLoader; import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; import net.minecraft.core.Holder; -import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; import net.minecraftforge.common.ForgeHooks; +import net.minecraftforge.registries.ForgeRegistries; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -31,22 +34,23 @@ public class NativeFuelContractTest { NativeTestBootstrap.start(); // Standalone tests need the actual vanilla item tags before the native fuel map. try (MultiPackResourceManager resources = new MultiPackResourceManager( - PackType.SERVER_DATA, List.of(new VanillaPackResources(null, "minecraft")))) { + PackType.SERVER_DATA, List.of(ServerPacksSource.createVanillaPackSource()))) { TagLoader> reader = new TagLoader<>( - id -> Registry.ITEM.getHolder(ResourceKey.create(Registry.ITEM_REGISTRY, id)), "tags/items"); + id -> BuiltInRegistries.ITEM.getHolder(ResourceKey.create(Registries.ITEM, id)), "tags/items"); Map, List>> loaded = new HashMap<>(); - reader.loadAndBuild(resources).forEach((id, tag) -> loaded.put(ItemTags.create(id), tag.getValues())); + reader.loadAndBuild(resources).forEach((id, tag) -> loaded.put(ItemTags.create(id), List.copyOf(tag))); assertTrue(loaded.containsKey(ItemTags.NON_FLAMMABLE_WOOD)); previousTags = new HashMap<>(); - Registry.ITEM.getTags().forEach(pair -> previousTags.put(pair.getFirst(), pair.getSecond().stream().toList())); - Registry.ITEM.bindTags(loaded); + BuiltInRegistries.ITEM.getTags().forEach(pair -> + previousTags.put(pair.getFirst(), pair.getSecond().stream().toList())); + BuiltInRegistries.ITEM.bindTags(loaded); } ForgeHooks.updateBurns(); } @AfterClass public static void restoreNativeTags() { if (previousTags != null) { - Registry.ITEM.bindTags(previousTags); + BuiltInRegistries.ITEM.bindTags(previousTags); ForgeHooks.updateBurns(); } } @@ -66,6 +70,7 @@ public class NativeFuelContractTest { return 237; } }; + ForgeRegistries.ITEMS.register(new ResourceLocation("basesteam_test", "fuel_override"), item); assertEquals(237, FurnaceFuelHelper.getBurnTime(new ItemStack(item))); } } diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/NativeLootContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/NativeLootContractTest.java index 1970c55..3227ff9 100644 --- a/src/test/java/zone/moddev/mc/basesteam/resources/NativeLootContractTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/resources/NativeLootContractTest.java @@ -39,11 +39,12 @@ public class NativeLootContractTest { } } - @Test public void packUsesNative118ResourceAndDataFormats() throws Exception { + @Test public void packUsesNative119ResourceAndDataFormats() throws Exception { JsonObject pack = read(ROOT.resolve("pack.mcmeta")).getAsJsonObject("pack"); - assertEquals(9, pack.get("pack_format").getAsInt()); - assertEquals(8, pack.get("forge:resource_pack_format").getAsInt()); - assertEquals(9, pack.get("forge:data_pack_format").getAsInt()); + assertEquals(13, pack.get("pack_format").getAsInt()); + assertEquals(12, pack.get("forge:server_data_pack_format").getAsInt()); + assertFalse(pack.has("forge:resource_pack_format")); + assertFalse(pack.has("forge:data_pack_format")); } private static JsonObject read(Path path) throws Exception { diff --git a/src/test/java/zone/moddev/mc/basesteam/testing/NativeTestBootstrap.java b/src/test/java/zone/moddev/mc/basesteam/testing/NativeTestBootstrap.java index 589f515..7684189 100644 --- a/src/test/java/zone/moddev/mc/basesteam/testing/NativeTestBootstrap.java +++ b/src/test/java/zone/moddev/mc/basesteam/testing/NativeTestBootstrap.java @@ -1,16 +1,59 @@ package zone.moddev.mc.basesteam.testing; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; import net.minecraft.SharedConstants; import net.minecraft.server.Bootstrap; +import net.minecraftforge.common.ForgeMod; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.ForgeRegistry; import net.minecraftforge.registries.GameData; +import net.minecraftforge.registries.NewRegistryEvent; +import net.minecraftforge.registries.RegisterEvent; public final class NativeTestBootstrap { private NativeTestBootstrap() {} public static void start() { - SharedConstants.tryDetectVersion(); - Bootstrap.bootStrap(); + NativeRegistries.ensureStarted(); // Standalone fixtures construct blocks and items outside Forge's mod registration phase. GameData.unfreezeData(); } + + public static synchronized void registerVanillaFluidTypes() throws ReflectiveOperationException { + start(); + if (ForgeMod.LAVA_TYPE.isPresent()) return; + + // Plain JUnit has no mod loader to dispatch these two Forge registry phases. + if (ForgeRegistries.FLUID_TYPES.get() == null) { + var event = new NewRegistryEvent(); + var fluidTypes = ForgeRegistries.class.getDeclaredField("DEFERRED_FLUID_TYPES"); + fluidTypes.setAccessible(true); + var create = DeferredRegister.class.getDeclaredMethod("createRegistry", NewRegistryEvent.class); + create.setAccessible(true); + create.invoke(fluidTypes.get(null), event); + var fill = NewRegistryEvent.class.getDeclaredMethod("fill"); + fill.setAccessible(true); + fill.invoke(event); + } + + var vanillaTypes = ForgeMod.class.getDeclaredField("VANILLA_FLUID_TYPES"); + vanillaTypes.setAccessible(true); + var addEntries = DeferredRegister.class.getDeclaredMethod("addEntries", RegisterEvent.class); + addEntries.setAccessible(true); + var event = RegisterEvent.class.getDeclaredConstructor(ResourceKey.class, ForgeRegistry.class, Registry.class); + event.setAccessible(true); + addEntries.invoke(vanillaTypes.get(null), event.newInstance( + ForgeRegistries.Keys.FLUID_TYPES, ForgeRegistries.FLUID_TYPES.get(), null)); + } + + private static final class NativeRegistries { + static { + SharedConstants.tryDetectVersion(); + Bootstrap.bootStrap(); + } + + private static void ensureStarted() {} + } } diff --git a/src/test/java/zone/moddev/mc/basesteam/util/SoundHelperLoadedPlayersTest.java b/src/test/java/zone/moddev/mc/basesteam/util/SoundHelperLoadedPlayersTest.java index 3a874e9..746ccd1 100644 --- a/src/test/java/zone/moddev/mc/basesteam/util/SoundHelperLoadedPlayersTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/util/SoundHelperLoadedPlayersTest.java @@ -56,7 +56,7 @@ public class SoundHelperLoadedPlayersTest { assertTrue(method.name + " rejects a false native spectator predicate", guard instanceof JumpInsnNode && guard.getOpcode() == Opcodes.IFEQ); } - if (call.owner.equals("net/minecraft/network/protocol/game/ClientboundCustomSoundPacket") && call.name.equals("")) { + if (call.owner.equals("net/minecraft/network/protocol/game/ClientboundSoundPacket") && call.name.equals("")) { packet = method.instructions.indexOf(instruction); } } @@ -95,7 +95,7 @@ private static void verifyDispatch(String name, double radius) throws Exception if (call.name.equals("getBoundingBox")) bounds++; if (call.owner.equals("net/minecraft/world/phys/AABB") && call.name.equals("intersects") && call.desc.equals("(Lnet/minecraft/world/phys/AABB;)Z")) intersections++; - if (call.owner.equals("net/minecraft/network/protocol/game/ClientboundCustomSoundPacket") && call.name.equals("")) soundPackets++; + if (call.owner.equals("net/minecraft/network/protocol/game/ClientboundSoundPacket") && call.name.equals("")) soundPackets++; } else if (instruction instanceof LdcInsnNode) { Object value = ((LdcInsnNode) instruction).cst; if (value instanceof Double && ((Double) value).doubleValue() == radius) correctRadius = true; diff --git a/tools/verify-native-port.js b/tools/verify-native-port.js index f11f964..def553a 100644 --- a/tools/verify-native-port.js +++ b/tools/verify-native-port.js @@ -23,11 +23,11 @@ for (const name of files(resources).filter(name => /\.(json|mcmeta)$/.test(name) json.set(name, JSON.parse(text)); } assert.deepEqual(json.get(`${resources}/pack.mcmeta`).pack, { - pack_format: 9, 'forge:resource_pack_format': 8, 'forge:data_pack_format': 9, description: 'baseSteam' + pack_format: 13, 'forge:server_data_pack_format': 12, description: 'baseSteam' }); const metadata = read(`${resources}/META-INF/mods.toml`); assert.match(metadata, /modId="basesteam"/); -assert.match(metadata, /modId="basepower"\r?\nmandatory=true\r?\nversionRange="\[2\.4\.10\.118021,3\.0\.0\)"/); +assert.match(metadata, /modId="basepower"\r?\nmandatory=true\r?\nversionRange="\[2\.4\.11\.119041,3\.0\.0\)"/); assert.match(metadata, /modId="basemetals"\r?\nmandatory=false/); assert.doesNotMatch(metadata, /dependencies\.steamadvantage|modId="(?:steamadvantage|poweradvantage)"/); @@ -67,7 +67,15 @@ for (const name of files(machines).filter(name => name.endsWith('Block.java'))) for (const file of ['Blocks', 'Items']) { const text = read(`${java}/init/${file}.java`); assert(text.includes('import zone.moddev.mc.basepower.init.ItemGroups;')); - assert(text.includes('.tab(ItemGroups.tab_powerAdvantage)')); + assert(text.includes('ItemGroups.show(item)')); + assert(!text.includes('.tab(')); +} +assert.match(read(`${java}/init/Blocks.java`), + /if \(!"pump_pipe_steam"\.equals\(id\) && !"drillbit"\.equals\(id\)\) ItemGroups\.show\(item\)/); +for (const id of ['steam_machine', 'musket']) { + assert.deepEqual(json.get(`${data}/damage_type/${id}.json`), + { message_id: id, scaling: 'never', exhaustion: 0.1 }); + assert.equal(typeof json.get(`${assets}/lang/en_us.json`)[`death.attack.${id}`], 'string'); } const migration = read(`${java}/migration/SteamLegacyMigrations.java`); assert.match(migration, /LEGACY_MODID = "steamadvantage"/); @@ -87,5 +95,5 @@ for (const id of pickaxe.values.map(value => value.split(':')[1]).filter(id => ! } const tests = files('src/test/java').filter(name => name.endsWith('.java')); const testCount = tests.reduce((sum, name) => sum + (read(name).match(/@Test\b/g) || []).length, 0); -console.log(`BASESTEAM_NATIVE118_SOURCE_PASS json=${json.size} java=${source.length} factories=${ids.length} harvest=${pickaxe.values.length} testMethods=${testCount}`); +console.log(`BASESTEAM_NATIVE119_SOURCE_PASS json=${json.size} java=${source.length} factories=${ids.length} harvest=${pickaxe.values.length} testMethods=${testCount}`); console.log('Source checks only. Compilation, executed tests, packaged runtime and manual acceptance are separate.');