diff --git a/.github/scripts/forge45-dependencies.sh b/.github/scripts/forge45-dependencies.sh new file mode 100644 index 0000000..d8347a9 --- /dev/null +++ b/.github/scripts/forge45-dependencies.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "${GITHUB_WORKSPACE:?}" + +value() { + sed -n "s/^$1=//p" gradle.properties | tr -d '\r' +} + +fail() { + echo "$*" >&2 + exit 1 +} + +check_hash() { + local file="$1" expected="$2" actual + [[ -f "$file" ]] || fail "Missing pinned input: $file" + read -r actual _ < <(sha256sum < "$file") + [[ "${actual^^}" == "$expected" ]] || fail "SHA-256 mismatch for $file: expected $expected, got ${actual^^}" +} + +[[ "$(value minecraft_version)" == '1.19.4' && "$(value forge_version)" == '45.4.0' ]] || + fail 'This dependency build is only for Minecraft 1.19.4 / Forge 45.4.0' +[[ "$(value poweradvantage_pin_state)" == 'PUBLISHED_RELEASE' ]] || fail 'The released basePower is not pinned' + +ore_ref="$(value orespawn_source_commit)" +bm_ref="$(value basemetals_source_commit)" +power_ref="$(value poweradvantage_source_ref)" +for ref in "$ore_ref" "$bm_ref" "$power_ref"; do + [[ "$ref" =~ ^[a-f0-9]{40}$ ]] || fail "Invalid dependency source pin: $ref" +done +for key in orespawn_deobf_sha256 orespawn_runtime_sha256 basemetals_deobf_sha256 basemetals_runtime_sha256 \ + poweradvantage_deobf_sha256 poweradvantage_runtime_sha256 poweradvantage_unit_bootstrap_sha256 \ + poweradvantage_unit_bootstrap_jar_sha256 forge_installer_sha256; do + [[ "$(value "$key")" =~ ^[A-F0-9]{64}$ ]] || fail "Invalid dependency checksum: $key" +done + +case "${1:-}" in + pins) + printf 'orespawn_ref=%s\nbasemetals_ref=%s\npower_ref=%s\n' "$ore_ref" "$bm_ref" "$power_ref" >> "$GITHUB_OUTPUT" + exit 0 + ;; + build) ;; + *) fail 'Expected pins or build' ;; +esac + +orespawn="$GITHUB_WORKSPACE/.ci-dependencies/OreSpawn" +basemetals="$GITHUB_WORKSPACE/.ci-dependencies/BaseMetals" +power="$GITHUB_WORKSPACE/.ci-dependencies/PowerAdvantage" +test "$(git -C "$orespawn" rev-parse HEAD)" = "$ore_ref" +test "$(git -C "$basemetals" rev-parse HEAD)" = "$bm_ref" +test "$(git -C "$power" rev-parse HEAD)" = "$power_ref" + +mavenizer="$orespawn/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar" +check_hash "$mavenizer" '341B3FB10ABA6EA7DC612FBE519DC290A1C81867AED5DD320ACD783688AC49DE' + +read -r -a toolchains <<< "${MMD_GRADLE_TOOLCHAINS:?}" +ore_version="$(value orespawn_version)" +bm_version="$(value basemetals_version)" +power_version="$(value poweradvantage_version)" +osjar="$orespawn/build/libs-dev/OreSpawn-$ore_version-deobf.jar" +osplayer="$orespawn/build/libs/OreSpawn-$ore_version.jar" +bmjar="$basemetals/build/libs-dev/BaseMetals-$bm_version-deobf.jar" +bmplayer="$basemetals/build/libs/BaseMetals-$bm_version.jar" +powerjar="$power/build/libs-dev/basePower-$power_version-deobf.jar" +powerplayer="$power/build/libs/basePower-$power_version.jar" +bootstrap="$power/test-rig/unit-bootstrap/ForgeCapabilityBootstrap.java" +agent="$power/build/unit-bootstrap/forge-native-unit-bootstrap.jar" +installer="${RUNNER_TEMP:?}/forge-1.19.4-45.4.0-installer.jar" + +(cd "$orespawn" && ./gradlew "${toolchains[@]}" assemble --no-daemon --stacktrace) +check_hash "$osjar" "$(value orespawn_deobf_sha256)" +check_hash "$osplayer" "$(value orespawn_runtime_sha256)" +# The pinned Base Metals build already configures its own verified fixture. +(cd "$basemetals" && ./gradlew "${toolchains[@]}" assemble --no-daemon --stacktrace) +git -C "$basemetals" diff --exit-code HEAD -- +check_hash "$bmjar" "$(value basemetals_deobf_sha256)" +check_hash "$bmplayer" "$(value basemetals_runtime_sha256)" +check_hash "$bootstrap" "$(value poweradvantage_unit_bootstrap_sha256)" +(cd "$power" && ./gradlew "${toolchains[@]}" assemble unitBootstrapJar verifyReleaseDependencies --no-daemon --stacktrace \ + "-Pforge45MavenizerJar=$mavenizer" "-PbaseMetalsDeobfJar=$bmjar" "-PoreSpawnDeobfJar=$osjar") +git -C "$power" diff --exit-code HEAD -- +check_hash "$powerjar" "$(value poweradvantage_deobf_sha256)" +check_hash "$powerplayer" "$(value poweradvantage_runtime_sha256)" +check_hash "$agent" "$(value poweradvantage_unit_bootstrap_jar_sha256)" +curl --fail --silent --show-error --location --proto '=https' \ + 'https://maven.minecraftforge.net/net/minecraftforge/forge/1.19.4-45.4.0/forge-1.19.4-45.4.0-installer.jar' \ + --output "$installer" +check_hash "$installer" "$(value forge_installer_sha256)" + +# Check public player bytes separately from the source-built development jars. +published="$RUNNER_TEMP/base-electric-published" +mkdir -p "$published" +fetch_public() { + curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --output "$1" "$2" + check_hash "$1" "$3" +} +maven='https://maven.moddev.zone/releases/zone/moddev/mc' +powerplayer="$published/basePower-$power_version.jar" +bmplayer="$published/BaseMetals-$bm_version.jar" +osplayer="$published/OreSpawn-$ore_version.jar" +fetch_public "$powerplayer" "$maven/basepower/basePower/$power_version/basePower-$power_version.jar?ci=$GITHUB_RUN_ID" \ + "$(value poweradvantage_runtime_sha256)" +fetch_public "$bmplayer" "$maven/basemetals/BaseMetals/$bm_version/BaseMetals-$bm_version.jar?ci=$GITHUB_RUN_ID" \ + "$(value basemetals_runtime_sha256)" +fetch_public "$osplayer" 'https://www.curseforge.com/api/v1/mods/245586/files/9171353/download' \ + "$(value orespawn_runtime_sha256)" + +printf 'mavenizer=%s\norespawn=%s\norespawn_player=%s\nbasemetals=%s\nbasemetals_player=%s\npower=%s\npower_player=%s\nbootstrap=%s\nagent=%s\ninstaller=%s\n' \ + "$mavenizer" "$osjar" "$osplayer" "$bmjar" "$bmplayer" "$powerjar" "$powerplayer" "$bootstrap" "$agent" "$installer" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29f66e4..be602f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,16 @@ -name: baseElectric 1.18 CI +name: baseElectric 1.19.4 CI on: push: - branches: [master-1.18.2, 'feature/**'] + branches: [master-1.19.4, 'feature/**'] pull_request: - branches: [master-1.18.2] + branches: [master-1.19.4] permissions: contents: read concurrency: - group: baseelectric-1.18-${{ github.workflow }}-${{ github.ref }} + group: baseelectric-1.19.4-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: @@ -22,31 +22,15 @@ jobs: - name: Check out baseElectric uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Use canonical LF dependency inputs - # Apply before every dependency checkout, including OreSpawn. run: git config --global core.autocrlf false - - name: Require coordinated 1.18.2 dependency pins + - name: Require coordinated 1.19.4 dependency pins id: pins - shell: bash - run: | - set -euo pipefail - power_ref="$(sed -n 's/^poweradvantage_source_ref=//p' gradle.properties | tr -d '\r')" - power_sha="$(sed -n 's/^poweradvantage_deobf_sha256=//p' gradle.properties | tr -d '\r')" - pin_state="$(sed -n 's/^poweradvantage_pin_state=//p' gradle.properties | tr -d '\r')" - bm_ref="$(sed -n 's/^basemetals_source_commit=//p' gradle.properties | tr -d '\r')" - bm_sha="$(sed -n 's/^basemetals_deobf_sha256=//p' gradle.properties | tr -d '\r')" - bootstrap_sha="$(sed -n 's/^poweradvantage_unit_bootstrap_sha256=//p' gradle.properties | tr -d '\r')" - if [[ "$pin_state" != "PUBLISHED_RELEASE" || ! "$power_ref" =~ ^[a-f0-9]{40}$ || ! "$bm_ref" =~ ^[a-f0-9]{40}$ || - ! "$power_sha" =~ ^[A-F0-9]{64}$ || ! "$bm_sha" =~ ^[A-F0-9]{64}$ || ! "$bootstrap_sha" =~ ^[A-F0-9]{64}$ ]]; then - echo "The coordinated basePower and Base Metals 1.18.2 pins are pending" >&2 - exit 1 - fi - echo "power_ref=$power_ref" >> "$GITHUB_OUTPUT" - echo "basemetals_ref=$bm_ref" >> "$GITHUB_OUTPUT" + run: bash .github/scripts/forge45-dependencies.sh pins - name: Check out pinned OreSpawn uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: MinecraftModDevelopmentMods/OreSpawn - ref: d31f52f296739f34a0c13f554abf3e1ac071ae25 + ref: ${{ steps.pins.outputs.orespawn_ref }} path: .ci-dependencies/OreSpawn - name: Check out pinned Base Metals uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 @@ -54,7 +38,7 @@ jobs: repository: MinecraftModDevelopmentMods/BaseMetals ref: ${{ steps.pins.outputs.basemetals_ref }} path: .ci-dependencies/BaseMetals - - name: Check out basePower 1.18.2 + - name: Check out pinned basePower uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: MinecraftModDevelopmentMods/basePower @@ -70,13 +54,12 @@ jobs: with: distribution: temurin java-version: '8.0.502+7' - - name: Install Java 17 for Gradle + - name: Install Java 17 for Gradle and production uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: temurin java-version: '17.0.1+12' - name: Use pinned build toolchains - # These are Gradle project properties, not JVM system properties. shell: bash run: >- echo "MMD_GRADLE_TOOLCHAINS=-Porg.gradle.java.installations.paths=$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64 @@ -84,94 +67,47 @@ jobs: -Porg.gradle.java.installations.auto-download=false" >> "$GITHUB_ENV" - name: Set up Gradle uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 - - name: Stage the pinned Forge 40 Mavenizer compatibility fixture - id: mavenizer - shell: bash - run: | - set -euo pipefail - fixture="$RUNNER_TEMP/minecraft-mavenizer-0.5.21-orespawn-1.18.jar" - curl --fail --silent --show-error --location --retry 3 --output "$fixture" \ - 'https://raw.githubusercontent.com/MinecraftModDevelopmentMods/IronAgeFurniture/8283a09d4c75dc01a5b62a912be722654e960de8/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-1.18.jar' - printf '%s %s\n' '28A6697BDF6D9500EC0AF9C1C949B9F3ED7DDFDA649CB5CAC4BEEA2E9567570A' "$fixture" | sha256sum --check - echo "fixture=$fixture" >> "$GITHUB_OUTPUT" - - name: Build 1.18.2 dependency chain + - name: Build 1.19.4 dependency chain id: dependencies + run: bash .github/scripts/forge45-dependencies.sh build + - name: Build and audit release artifacts shell: bash + env: + MAVENIZER: ${{ steps.dependencies.outputs.mavenizer }} + POWER_DEV: ${{ steps.dependencies.outputs.power }} + POWER_PLAYER: ${{ steps.dependencies.outputs.power_player }} + POWER_BOOTSTRAP: ${{ steps.dependencies.outputs.bootstrap }} + POWER_AGENT: ${{ steps.dependencies.outputs.agent }} + METALS_DEV: ${{ steps.dependencies.outputs.basemetals }} + METALS_PLAYER: ${{ steps.dependencies.outputs.basemetals_player }} + ORESPAWN_DEV: ${{ steps.dependencies.outputs.orespawn }} + ORESPAWN_PLAYER: ${{ steps.dependencies.outputs.orespawn_player }} + FORGE_INSTALLER: ${{ steps.dependencies.outputs.installer }} run: | - set -euo pipefail - orespawn="$GITHUB_WORKSPACE/.ci-dependencies/OreSpawn" - basemetals="$GITHUB_WORKSPACE/.ci-dependencies/BaseMetals" - power="$GITHUB_WORKSPACE/.ci-dependencies/PowerAdvantage" - test "$(git -C "$power" rev-parse HEAD)" = "${{ steps.pins.outputs.power_ref }}" - test "$(git -C "$basemetals" rev-parse HEAD)" = "${{ steps.pins.outputs.basemetals_ref }}" - 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" - (cd "$power" && ./gradlew $MMD_GRADLE_TOOLCHAINS jar verifyReleaseDependencies \ - -PbaseMetalsDeobfJar="$bmjar" -PoreSpawnDeobfJar="$osjar" \ - --no-daemon --stacktrace) - powerjar="$power/build/libs-dev/basePower-2.4.10.118021-deobf.jar" - test -f "$osjar" && test -f "$bmjar" && test -f "$powerjar" - release="$RUNNER_TEMP/basePower-2.4.10.118021.jar" - curl --fail --silent --show-error --location --retry 3 --output "$release" \ - 'https://maven.moddev.zone/releases/zone/moddev/mc/basepower/basePower/2.4.10.118021/basePower-2.4.10.118021.jar' - power_release_sha=$(sed -n 's/^poweradvantage_runtime_sha256=//p' gradle.properties | tr -d '\r') - printf '%s %s\n' "$power_release_sha" "$release" | sha256sum --check - pin() { sed -n "s/^$1=//p" gradle.properties | tr -d '\r'; } - fetch_runtime() { - local key="$1" url="$2" destination="$3" expected="$4" - [[ "$expected" =~ ^[A-F0-9]{64}$ ]] - curl --fail --silent --show-error --location --retry 3 --output "$destination" "$url" - printf '%s %s\n' "$expected" "$destination" | sha256sum --check - echo "$key=$destination" >> "$GITHUB_OUTPUT" - } - bm_version="$(pin basemetals_version)" - os_version="$(pin orespawn_version)" - forge_target="$(pin minecraft_version)-$(pin forge_version)" - maven='https://maven.moddev.zone/releases/zone/moddev/mc' - fetch_runtime basemetals_runtime "$maven/basemetals/BaseMetals/$bm_version/BaseMetals-$bm_version.jar" \ - "$RUNNER_TEMP/BaseMetals-$bm_version.jar" "$(pin basemetals_runtime_sha256)" - fetch_runtime orespawn_runtime "$maven/orespawn/OreSpawn/$os_version/OreSpawn-$os_version.jar" \ - "$RUNNER_TEMP/OreSpawn-$os_version.jar" "$(pin orespawn_runtime_sha256)" - fetch_runtime forge_installer "https://maven.minecraftforge.net/net/minecraftforge/forge/$forge_target/forge-$forge_target-installer.jar" \ - "$RUNNER_TEMP/forge-$forge_target-installer.jar" "$(pin forge_installer_sha256)" - echo "orespawn=$osjar" >> "$GITHUB_OUTPUT" - echo "basemetals=$bmjar" >> "$GITHUB_OUTPUT" - echo "power=$powerjar" >> "$GITHUB_OUTPUT" - echo "power_runtime=$release" >> "$GITHUB_OUTPUT" - - name: Build and audit release artifacts - run: >- - ./gradlew $MMD_GRADLE_TOOLCHAINS clean check build javadoc verifyReleaseDependencies verifyReleaseArtifacts - writeReleaseChecksums verifyEclipseProductionClasspath verifyLocalRuntimeInputs - -PpowerAdvantageDeobfJar=${{ steps.dependencies.outputs.power }} - -PmavenizerCompatibilityJar=${{ steps.mavenizer.outputs.fixture }} - -PpowerAdvantageUnitBootstrapSource=${{ github.workspace }}/.ci-dependencies/PowerAdvantage/test-rig/unit-bootstrap/ForgeCapabilityBootstrap.java - -PbaseMetalsDeobfJar=${{ steps.dependencies.outputs.basemetals }} - -PoreSpawnDeobfJar=${{ steps.dependencies.outputs.orespawn }} - -PpowerAdvantageRuntimeJar=${{ steps.dependencies.outputs.power_runtime }} - -PbaseMetalsRuntimeJar=${{ steps.dependencies.outputs.basemetals_runtime }} - -PoreSpawnRuntimeJar=${{ steps.dependencies.outputs.orespawn_runtime }} - -PforgeInstallerJar=${{ steps.dependencies.outputs.forge_installer }} - --no-daemon --stacktrace + ./gradlew $MMD_GRADLE_TOOLCHAINS build writeReleaseChecksums verifyEclipseProductionClasspath \ + "-Pforge45MavenizerJar=$MAVENIZER" "-PpowerAdvantageDeobfJar=$POWER_DEV" \ + "-PpowerAdvantageRuntimeJar=$POWER_PLAYER" "-PpowerAdvantageUnitBootstrapSource=$POWER_BOOTSTRAP" \ + "-PpowerAdvantageUnitBootstrapJar=$POWER_AGENT" "-PbaseMetalsDeobfJar=$METALS_DEV" \ + "-PbaseMetalsRuntimeJar=$METALS_PLAYER" "-PoreSpawnDeobfJar=$ORESPAWN_DEV" \ + "-PoreSpawnRuntimeJar=$ORESPAWN_PLAYER" "-PforgeInstallerJar=$FORGE_INSTALLER" \ + --no-build-cache --no-daemon --stacktrace - name: Upload audited release candidate uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: baseElectric-1.18.2-${{ github.sha }} + name: baseElectric-1.19.4-${{ github.sha }} if-no-files-found: error retention-days: 30 path: | - build/libs/baseElectric-2.2.11.118021.jar - build/libs/baseElectric-2.2.11.118021-sources.jar - build/libs/baseElectric-2.2.11.118021-javadoc.jar + build/libs/baseElectric-2.2.12.119041.jar + build/libs/baseElectric-2.2.12.119041-sources.jar + build/libs/baseElectric-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: baseElectric-1.18.2-diagnostics-${{ github.sha }} + name: baseElectric-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 f5937c4..865a732 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/**'] + branches: [master-1.19.4, 'feature/**'] pull_request: - branches: [master-1.18.2] + branches: [master-1.19.4] schedule: - cron: '59 7 * * 4' @@ -17,36 +17,20 @@ jobs: analyze: name: Analyze Java runs-on: ubuntu-latest - timeout-minutes: 90 + timeout-minutes: 105 steps: - name: Check out baseElectric uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Use canonical LF dependency inputs - # Apply before every dependency checkout, including OreSpawn. run: git config --global core.autocrlf false - - name: Require coordinated 1.18.2 dependency pins + - name: Require coordinated 1.19.4 dependency pins id: pins - shell: bash - run: | - set -euo pipefail - power_ref="$(sed -n 's/^poweradvantage_source_ref=//p' gradle.properties | tr -d '\r')" - power_sha="$(sed -n 's/^poweradvantage_deobf_sha256=//p' gradle.properties | tr -d '\r')" - pin_state="$(sed -n 's/^poweradvantage_pin_state=//p' gradle.properties | tr -d '\r')" - bm_ref="$(sed -n 's/^basemetals_source_commit=//p' gradle.properties | tr -d '\r')" - bm_sha="$(sed -n 's/^basemetals_deobf_sha256=//p' gradle.properties | tr -d '\r')" - bootstrap_sha="$(sed -n 's/^poweradvantage_unit_bootstrap_sha256=//p' gradle.properties | tr -d '\r')" - if [[ "$pin_state" != "PUBLISHED_RELEASE" || ! "$power_ref" =~ ^[a-f0-9]{40}$ || ! "$bm_ref" =~ ^[a-f0-9]{40}$ || - ! "$power_sha" =~ ^[A-F0-9]{64}$ || ! "$bm_sha" =~ ^[A-F0-9]{64}$ || ! "$bootstrap_sha" =~ ^[A-F0-9]{64}$ ]]; then - echo "The coordinated basePower and Base Metals 1.18.2 pins are pending" >&2 - exit 1 - fi - echo "power_ref=$power_ref" >> "$GITHUB_OUTPUT" - echo "basemetals_ref=$bm_ref" >> "$GITHUB_OUTPUT" + run: bash .github/scripts/forge45-dependencies.sh pins - name: Check out pinned OreSpawn uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: MinecraftModDevelopmentMods/OreSpawn - ref: d31f52f296739f34a0c13f554abf3e1ac071ae25 + ref: ${{ steps.pins.outputs.orespawn_ref }} path: .ci-dependencies/OreSpawn - name: Check out pinned Base Metals uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 @@ -54,7 +38,7 @@ jobs: repository: MinecraftModDevelopmentMods/BaseMetals ref: ${{ steps.pins.outputs.basemetals_ref }} path: .ci-dependencies/BaseMetals - - name: Check out basePower 1.18.2 + - name: Check out pinned basePower uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: repository: MinecraftModDevelopmentMods/basePower @@ -70,13 +54,12 @@ jobs: with: distribution: temurin java-version: '8.0.502+7' - - name: Install Java 17 for Gradle + - name: Install Java 17 for Gradle and production uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: temurin java-version: '17.0.1+12' - name: Use pinned build toolchains - # These are Gradle project properties, not JVM system properties. shell: bash run: >- echo "MMD_GRADLE_TOOLCHAINS=-Porg.gradle.java.installations.paths=$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64 @@ -84,51 +67,24 @@ jobs: -Porg.gradle.java.installations.auto-download=false" >> "$GITHUB_ENV" - name: Set up Gradle uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 - - name: Stage the pinned Forge 40 Mavenizer compatibility fixture - id: mavenizer - shell: bash - run: | - set -euo pipefail - fixture="$RUNNER_TEMP/minecraft-mavenizer-0.5.21-orespawn-1.18.jar" - curl --fail --silent --show-error --location --retry 3 --output "$fixture" \ - 'https://raw.githubusercontent.com/MinecraftModDevelopmentMods/IronAgeFurniture/8283a09d4c75dc01a5b62a912be722654e960de8/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-1.18.jar' - printf '%s %s\n' '28A6697BDF6D9500EC0AF9C1C949B9F3ED7DDFDA649CB5CAC4BEEA2E9567570A' "$fixture" | sha256sum --check - echo "fixture=$fixture" >> "$GITHUB_OUTPUT" - - name: Build 1.18.2 dependency chain + - name: Build 1.19.4 dependency chain id: dependencies - shell: bash - run: | - set -euo pipefail - orespawn="$GITHUB_WORKSPACE/.ci-dependencies/OreSpawn" - basemetals="$GITHUB_WORKSPACE/.ci-dependencies/BaseMetals" - power="$GITHUB_WORKSPACE/.ci-dependencies/PowerAdvantage" - test "$(git -C "$power" rev-parse HEAD)" = "${{ steps.pins.outputs.power_ref }}" - test "$(git -C "$basemetals" rev-parse HEAD)" = "${{ steps.pins.outputs.basemetals_ref }}" - 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" - (cd "$power" && ./gradlew $MMD_GRADLE_TOOLCHAINS jar verifyReleaseDependencies \ - -PbaseMetalsDeobfJar="$bmjar" -PoreSpawnDeobfJar="$osjar" \ - --no-daemon --stacktrace) - powerjar="$power/build/libs-dev/basePower-2.4.10.118021-deobf.jar" - test -f "$osjar" && test -f "$bmjar" && test -f "$powerjar" - echo "orespawn=$osjar" >> "$GITHUB_OUTPUT" - echo "basemetals=$bmjar" >> "$GITHUB_OUTPUT" - echo "power=$powerjar" >> "$GITHUB_OUTPUT" + run: bash .github/scripts/forge45-dependencies.sh build - name: Initialize CodeQL uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 with: languages: java-kotlin build-mode: manual - name: Compile baseElectric production code + shell: bash + env: + MAVENIZER: ${{ steps.dependencies.outputs.mavenizer }} + POWER_DEV: ${{ steps.dependencies.outputs.power }} + METALS_DEV: ${{ steps.dependencies.outputs.basemetals }} + ORESPAWN_DEV: ${{ steps.dependencies.outputs.orespawn }} run: | - chmod +x ./gradlew - ./gradlew $MMD_GRADLE_TOOLCHAINS clean classes --rerun-tasks --no-build-cache --no-daemon --stacktrace \ - -PpowerAdvantageDeobfJar=${{ steps.dependencies.outputs.power }} \ - -PmavenizerCompatibilityJar=${{ steps.mavenizer.outputs.fixture }} \ - -PbaseMetalsDeobfJar=${{ steps.dependencies.outputs.basemetals }} \ - -PoreSpawnDeobfJar=${{ steps.dependencies.outputs.orespawn }} + ./gradlew $MMD_GRADLE_TOOLCHAINS classes verifyReleaseDependencies --rerun-tasks --no-build-cache --no-daemon --stacktrace \ + "-Pforge45MavenizerJar=$MAVENIZER" "-PpowerAdvantageDeobfJar=$POWER_DEV" \ + "-PbaseMetalsDeobfJar=$METALS_DEV" "-PoreSpawnDeobfJar=$ORESPAWN_DEV" - name: Analyze uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 diff --git a/.github/workflows/validate-gradle-build.yml b/.github/workflows/validate-gradle-build.yml index afe21ad..2d52c08 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 + - master-1.19.4 - 'feature/**' pull_request: branches: - - master-1.18.2 + - master-1.19.4 permissions: contents: read diff --git a/.gitignore b/.gitignore index cb99cf1..52c3f36 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ # Gradle, Forge, and generated dependency output /build/ /.gradle/ +/.gradle-user-home/ /eclipse/ /run/ /run-data/ diff --git a/CHANGELOG.txt b/CHANGELOG.txt index acd64e6..43766f4 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,15 +1,16 @@ -baseElectric 2.2.11.118021 (Minecraft 1.18.2) +baseElectric 2.2.12.119041 (Minecraft 1.19.4) -- Bring baseElectric to Minecraft 1.18.2, with the same machines and recipes. -- Keep saved power, inventories, drill facings and hydroturbines when upgrading. -- Use the released basePower 2.4.10.118021 and Base Metals 3.1.2 save hook. +- Target Forge 45.4.0, official mappings, Java 17 and ForgeGradle 7. +- Register the same Electric content in the shared basePower creative tab. +- Retain machine data, recipe unlocks, ore drops and optional Base Metals recipes. +- Use native registry-aware crafting, sound, damage and renderer APIs. +- Pin the released basePower 2.4.11.119041 and the Base Metals migration fix. baseElectric 2.2.10.117011 (Minecraft 1.17.1) - Continue Electric Advantage as baseElectric, with its blocks and items in the shared basePower creative tab. - Recognize older Electric Advantage blocks, items, machines and hydroturbines when upgrading. - Keep the native 1.17.1 machine controls, drops and recipe discoveries. -- Build and test against the published basePower 2.4.9.117011 release. Electric Advantage 2.2.9.117011 - Minecraft 1.17.1 local port candidate diff --git a/README.md b/README.md index a846bb3..a3b4d75 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,17 @@ baseElectric continues DrCyano's Electric Advantage. It adds electric machines, generation, storage, automation and defence to basePower, and its blocks and items appear in the shared basePower creative tab. -This is **baseElectric 2.2.11.118021 for Minecraft 1.18.2**. -Back up your world before upgrading. The port retains the mappings for +This is the **Minecraft 1.19.4 port, 2.2.12.119041**. It retains the mappings for older Electric Advantage saves, as well as saves that already use baseElectric IDs. ## Target -- Forge `40.3.0`, official `1.18.2` mappings, MCP `20220404.173914`. +- Forge `45.4.0`, official `1.19.4` mappings, MCP `20230314.122934`. - ForgeGradle `7.0.34`, Renamer `1.1.5`, Gradle `9.6.1`. - Java 17 runs Gradle; Java 25 runs Mavenizer. Production and tests use Java 17, class major `61`. The Eclipse generator also needs Java 8 for ForgeGradle's Slime Launcher tooling; the game launches use Java 17. -- Player artifact: `build/libs/baseElectric-2.2.11.118021.jar`. +- Player artifact path: `build/libs/baseElectric-2.2.12.119041.jar`. The separate deobfuscated API artifact stays under `build/libs-dev`. basePower is required. Base Metals remains optional in Forge metadata; @@ -24,19 +23,20 @@ basePower, Base Metals and OreSpawn are separate dependencies, never embedded. ## Current State -The port keeps the 96-test suite, registry paths, machine NBT, loot and recipes. -See [the 1.18.2 port notes](docs/NATIVE118.md) for the exact inputs, evidence and -remaining gates. Results in the [1.17.1 notes](docs/NATIVE117.md) and -[1.16.5 notes](docs/NATIVE116.md) are historical. +The port keeps the 96-test inventory, registry paths, machine NBT, loot and +recipes. See [the 1.19.4 port notes](docs/NATIVE119.md) for exact inputs and +verification status. Earlier port notes, including +[1.18.2](docs/NATIVE118.md), are historical. ## Dependencies -The [basePower 2.4.10.118021 release](https://github.com/MinecraftModDevelopmentMods/basePower/releases/tag/2.4.10.118021) -provides the player jar. The build pins its source, development jar and test -bootstrap separately. Base Metals -`3.1.2.118021`, OreSpawn `4.1.0.118021` and the Forge installer also have -explicit hashes in `gradle.properties`. Hosted checks build the development -dependencies and verify the published basePower jar from MMD Maven. +The build pins the released basePower `2.4.11.119041` source and player jar, +along with its separate development jar and test agent. Base Metals +`3.1.2.119041`, OreSpawn `4.1.0.119041` and the Forge installer have +explicit hashes in `gradle.properties`. The pinned basePower and Base Metals +builds configure their own verified Forge 45 tool. The dependency source +checkouts stay unchanged. Releases are made +through the guarded workflow, using the verified CI artifacts. Run from this repository with the explicit input arguments below: @@ -44,24 +44,28 @@ Run from this repository with the explicit input arguments below: ./gradlew --no-build-cache --rerun-tasks build verifyLocalRuntimeInputs writeReleaseChecksums prepareEclipse verifyEclipseProductionClasspath ``` -On Windows, use `gradlew.bat` instead of `./gradlew`. Set `JAVA_HOME` to Java 17 -for Gradle and production, with Java 25 for Mavenizer and Java 8 for Eclipse -launch generation. The checksum-verified IAF118 Mavenizer fixture fixes a -decompiler error in Forge 40's `Holder.Direct` record. +On Windows, use `gradlew.bat` instead of `./gradlew`. Set `GRADLE_USER_HOME` to +an isolated directory (the ignored `.gradle-user-home` was used locally). +Set `JAVA_HOME` to Java 17 for Gradle and production, with Java 25 for +Mavenizer and Java 8 for Eclipse launch generation. The checksum-verified +OreSpawn Mavenizer fixture fixes duplicate decompiled record accessors in +Forge 45.4.0. The default is the sibling `MinecraftMineralogy/OS119/OreSpawn` +checkout; use `-Pforge45MavenizerJar` for another checkout layout. Append these arguments using immutable local inputs. In particular, the build does not discover a basePower jar or bootstrap source in a sibling checkout: ```text --PpowerAdvantageDeobfJar="/path/to/basePower-2.4.10.118021-deobf.jar" --PpowerAdvantageRuntimeJar="/path/to/basePower-2.4.10.118021.jar" --PbaseMetalsDeobfJar="/path/to/BaseMetals-3.1.2.118021-deobf.jar" --PbaseMetalsRuntimeJar="/path/to/BaseMetals-3.1.2.118021.jar" --PoreSpawnDeobfJar="/path/to/OreSpawn-4.1.0.118021-deobf.jar" --PoreSpawnRuntimeJar="/path/to/OreSpawn-4.1.0.118021.jar" +-PpowerAdvantageDeobfJar="/path/to/basePower-2.4.11.119041-deobf.jar" +-PpowerAdvantageRuntimeJar="/path/to/basePower-2.4.11.119041.jar" +-PbaseMetalsDeobfJar="/path/to/BaseMetals-3.1.2.119041-deobf.jar" +-PbaseMetalsRuntimeJar="/path/to/BaseMetals-3.1.2.119041.jar" +-PoreSpawnDeobfJar="/path/to/OreSpawn-4.1.0.119041-deobf.jar" +-PoreSpawnRuntimeJar="/path/to/OreSpawn-4.1.0.119041.jar" -PpowerAdvantageUnitBootstrapSource="/path/to/ForgeCapabilityBootstrap.java" --PmavenizerCompatibilityJar="/path/to/minecraft-mavenizer-0.5.21-orespawn-1.18.jar" --PforgeInstallerJar="/path/to/forge-1.18.2-40.3.0-installer.jar" +-PpowerAdvantageUnitBootstrapJar="/path/to/forge-native-unit-bootstrap.jar" +-Pforge45MavenizerJar="/path/to/minecraft-mavenizer-0.5.21-orespawn-compat.jar" +-PforgeInstallerJar="/path/to/forge-1.19.4-45.4.0-installer.jar" ``` The build checks these inputs against the pinned hashes; do not substitute jars @@ -70,23 +74,23 @@ from another Minecraft version. If Gradle cannot find the installed JDKs, pass For a prepared offline cache, also set `ORESPAWN_MAVENIZER_OFFLINE=true` and use Gradle's `--offline` flag. -Tests use basePower's hash-checked, build-only Forge capability bootstrap source -from `-PpowerAdvantageUnitBootstrapSource`. Electric's build-only wrapper also -runs Forge's event-bus plugin for the native tier/harvest checks. Neither replaces -capability tokens or events, and neither is included in production jars. +Tests use the exact basePower agent supplied by +`-PpowerAdvantageUnitBootstrapJar` and verify its checkpoint source via +`-PpowerAdvantageUnitBootstrapSource`. The build-only agent runs Forge's +native capability and event transforms and is not included in production jars. Independent ECJ verification uses `tools/verification/Verify-ElectricEcj.ps1` after Gradle has produced a fresh classpath, unit bootstrap jar and complete test inventory. Static checks need no Gradle or game launch: ```text -node tools/verify-native-port.js --report build/baseelectric-118-static-unique.json +node tools/verify-native-port.js --report build/port-evidence/static-119-unique.json node tools/generate-recipe-unlocks.js --check ``` -Use a new report name on each run. The audit checks the historical native -baselines and the exact port delta from the PA117 clone point. It does not prove compilation, native ticking, -shader compilation or migration safety. +Use a new report name on each run. The audit checks preservation against the +accepted 1.18.2 clone point and historical migration contracts. It does not +prove compilation, native ticking, shader compilation or migration safety. ## Compatibility @@ -103,7 +107,9 @@ The crusher continues to consume the real Base Metals recipe implementation; no replacement raw-metal conversion or balance change is introduced. Eclipse imports use the Gradle wrapper, processed resources and production-only -launches. Generate metadata through `verifyEclipseProductionClasspath`; do not +launches. The Eclipse and Gradle project name is `ElectricAdvantage`, matching +the checkout folder; the mod and archives keep the baseElectric identity. +Generate metadata through `verifyEclipseProductionClasspath`; do not hand-edit generated classpaths or disable restricted-reference tests. Repeated refreshes replace stale runtime entries while keeping the native Forge launcher and excluding compile-only libraries from production launches. diff --git a/build.gradle b/build.gradle index 13b51e6..beccb40 100644 --- a/build.gradle +++ b/build.gradle @@ -61,18 +61,9 @@ def pinnedFile = { String property, String expected -> input } -// The IAF118/OreSpawn fixture fixes the Forge 40 Holder record decompilation. -// Its build-tool JVM is separate from the Java 17 production/test JVM. -fgtools.configure('mavenizer') { - classpath.from(providers.provider { - pinnedFile('mavenizerCompatibilityJar', '28A6697BDF6D9500EC0AF9C1C949B9F3ED7DDFDA649CB5CAC4BEEA2E9567570A') - }) - mainClass.set('net.minecraftforge.mcmaven.cli.Main') - javaLauncher.set(javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(25) - vendor = JvmVendorSpec.ADOPTIUM - }) -} +ext.forge45MavenizerDefault = + '../../../../MinecraftMineralogy/OS119/OreSpawn/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar' +apply from: 'gradle/forge45-mavenizer.gradle' java { toolchain { @@ -96,26 +87,26 @@ tasks.withType(JavaCompile).configureEach { def unitBootstrapSource = providers.provider { pinnedFile('powerAdvantageUnitBootstrapSource', project.poweradvantage_unit_bootstrap_sha256) } -def compileUnitBootstrap = tasks.register('compileUnitBootstrap', JavaCompile) { - source unitBootstrapSource, file('tools/verification/ForgeNativeTestBootstrap.java') - classpath = files() - destinationDirectory = layout.buildDirectory.dir('unit-bootstrap/classes') - inputs.property('approvedSourceSha256', project.poweradvantage_unit_bootstrap_sha256) - doFirst { - unitBootstrapSource.get() - } +def unitBootstrapInput = providers.provider { + pinnedFile('powerAdvantageUnitBootstrapJar', project.poweradvantage_unit_bootstrap_jar_sha256) } -def unitBootstrapJar = tasks.register('unitBootstrapJar', Jar) { - dependsOn compileUnitBootstrap - archiveFileName = 'forge-native-unit-bootstrap.jar' - destinationDirectory = layout.buildDirectory.dir('unit-bootstrap') - from compileUnitBootstrap.flatMap { it.destinationDirectory } - manifest.attributes('Premain-Class': 'zone.moddev.mc.baseelectric.verification.ForgeNativeTestBootstrap') +def unitBootstrapOutput = layout.buildDirectory.file('unit-bootstrap/forge-native-unit-bootstrap.jar') +def unitBootstrapJar = tasks.register('unitBootstrapJar', Copy) { + inputs.file(unitBootstrapSource) + from(unitBootstrapInput) { rename { 'forge-native-unit-bootstrap.jar' } } + into layout.buildDirectory.dir('unit-bootstrap') } tasks.named('test', Test) { dependsOn unitBootstrapJar - doFirst { jvmArgs "-javaagent:${unitBootstrapJar.get().archiveFile.get().asFile}" } + inputs.file(unitBootstrapOutput) + doFirst { + File agent = unitBootstrapOutput.get().asFile + if (sha256Of(agent) != project.poweradvantage_unit_bootstrap_jar_sha256) { + throw new GradleException('The copied unit agent does not match the basePower checkpoint.') + } + jvmArgs "-javaagent:${agent}" + } javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(17) vendor = JvmVendorSpec.ADOPTIUM @@ -129,6 +120,7 @@ tasks.named('test', Test) { tasks.withType(Javadoc).configureEach { failOnError = false + title = "${base.archivesName.get()} ${project.version} API" options.encoding = 'UTF-8' options.addStringOption('Xdoclint:none', '-quiet') options.addBooleanOption('notimestamp', true) @@ -188,7 +180,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 1.18.2 launches.' + description = 'Merges classes and processed resources for Forge 1.19.4 launches.' dependsOn tasks.named('classes') from sourceSets.main.output into forgeRunModClassesDirectory @@ -203,35 +195,22 @@ repositories { minecraft.mavenizer(it) maven fg.forgeMaven maven fg.minecraftLibsMaven - exclusiveContent { - forRepository { - maven { - name = 'Sponge' - url = 'https://repo.spongepowered.org/repository/maven-public' - } - } - filter { includeGroupAndSubgroups('org.spongepowered') } - } mavenCentral() - maven { - name = 'MinecraftLibraries' - url = 'https://libraries.minecraft.net/' - } } def requirePowerPin = { if (project.poweradvantage_pin_state != 'PUBLISHED_RELEASE' - || project.poweradvantage_source_ref != 'bcf5105ef5c10ba36c26c8ff941742fecc3e11a1' - || project.poweradvantage_deobf_sha256 != '767BABC0D6E1536DCB937B9F1DC565AF348B3CAF92D3207671EF1E1C8D8EF5D4' - || project.poweradvantage_runtime_sha256 != '8CA46D5BCBFFDDAEB4F40137B5BF71E5AF7DD36515E5BFEA751FC5C02249823B' - || project.poweradvantage_unit_bootstrap_sha256 != '025F16FCA9FD5CFC585931E7C8BCFAB22FC1DF933BE7DAAD490B8E4FC824B76E') { - throw new GradleException('The published basePower 2.4.10.118021 source and artifact pins do not match.') + || project.poweradvantage_source_ref != '4af5c7d5d490162cddc7f1e4fc72cc602b42bb32' + || project.poweradvantage_deobf_sha256 != '512FDEC9275B33533EF773C110E6229E0F07268775FAD4B5942A15B68A9EB87D' + || project.poweradvantage_runtime_sha256 != 'E1689C1147F6F6F93F6516D9188A279093D6CCC2FF1FBEB506468B876BA03E60' + || project.poweradvantage_unit_bootstrap_sha256 != '0FB05554D6446BD5561BF9DE47B9F173E6F6C28E39A2BC877D359BB363CB1ABE' + || project.poweradvantage_unit_bootstrap_jar_sha256 != 'A93CD83EC42B09B730CB7715C10C6E53D47B22B557EFE68EDC95241D53BD4EB3') { + throw new GradleException('The owner-frozen basePower 2.4.11.119041 source and artifact pins are required.') } } // Resolve and check only the dependencies required by the selected tasks. def powerAdvantageDeobfJar = providers.provider { - requirePowerPin() pinnedFile('powerAdvantageDeobfJar', project.poweradvantage_deobf_sha256) } def baseMetalsDeobfJar = providers.provider { @@ -289,10 +268,13 @@ def powerPinManifest = [ 'API-Pin-State' : project.poweradvantage_pin_state, 'basePower-Source-Ref' : project.poweradvantage_source_ref, 'basePower-Deobf-SHA256': project.poweradvantage_deobf_sha256, - 'basePower-Player-SHA256': project.poweradvantage_runtime_sha256 + 'basePower-Player-SHA256': project.poweradvantage_runtime_sha256, + 'basePower-Unit-Source-SHA256': project.poweradvantage_unit_bootstrap_sha256, + 'basePower-Unit-Agent-SHA256': project.poweradvantage_unit_bootstrap_jar_sha256 ] tasks.named('jar', Jar) { + doFirst { requirePowerPin() } archiveClassifier = 'deobf' destinationDirectory = layout.buildDirectory.dir('libs-dev') exclude 'fernflower_abstract_parameter_names.txt' @@ -398,20 +380,20 @@ tasks.register('verifyReleaseConfiguration') { description = 'Validates release identity, target metadata, and dependency policy.' doLast { def pack = new JsonSlurper().parse(file('src/main/resources/pack.mcmeta')).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 Forge resource pack format 8 and data pack format 9') + if (pack.pack_format != 13 || pack['forge:server_data_pack_format'] != 12) { + throw new GradleException('Minecraft 1.19.4 requires Forge resource pack format 13 and data pack format 12') } - if (project.mod_version != '2.2.11.118021') { + if (project.mod_version != '2.2.12.119041') { throw new GradleException("Unexpected baseElectric 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,)' + 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,)' || 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' @@ -431,34 +413,33 @@ 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.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') } - requirePowerPin() - if (project.basemetals_version != '3.1.2.118021' - || project.orespawn_version != '4.1.0.118021' - || project.basemetals_source_commit != '3488ed58403722ac3ff203cff7680dfc16e0fd3f' - || project.basemetals_deobf_sha256 != 'E4F9E2CDE355275218C79954D3792D000615790C7CBAE5FCEC59636A8C4CA57D' - || project.basemetals_runtime_sha256 != '5595C2CE7ADCDF1599E4327F1130121148A5CACD928CBC2190312D61F7CA37A9' - || project.orespawn_source_commit != 'd31f52f296739f34a0c13f554abf3e1ac071ae25' - || project.orespawn_deobf_sha256 != '34B25B2116FD91B0050C80393F8356A5CA256E707BF443E4882BE5CFAD1335B6' - || project.orespawn_runtime_sha256 != 'BD8D84A96DAFB7668E95D61B9D72C62AEF3440675473E41244C182C64653356F' - || project.forge_installer_sha256 != '9434C29790504DCD11CE97CB30DD8891B4B18848982357BC1B5ABFA79FB95103') { + if (project.basemetals_version != '3.1.2.119041' + || project.orespawn_version != '4.1.0.119041' + || project.basemetals_source_commit != '5791a793aa3a1ca1aa3fcb36e6d4a2d865e7d2d3' + || project.basemetals_deobf_sha256 != '0AA993D6BADA54B11548EB5BE150CE0939232291F2D215B7DBAF149857976B2F' + || project.basemetals_runtime_sha256 != '991EA843EEDF5FA1E42E9984A07573C7D03D74230482230440E44EDCA895FA39' + || project.orespawn_source_commit != '444d86ef48db2bf54422901b067c51d5832197fc' + || project.orespawn_deobf_sha256 != 'BE6973F68F1C310DFF8CDF89C12B34C62D68CAEA326C0F1528E486EE3ABC1000' + || project.orespawn_runtime_sha256 != '1B2ECAA546E7FFB35863AC1D9FF92504471EE857045DCD01A068C5CA7F53CFFD' + || project.forge_installer_sha256 != '6643BB8C21F409CC528FF656DAF60C206D304CC77BF529BBBAB4B336004DFC53') { throw new GradleException('Unexpected native dependency pins') } String mainSource = file( 'src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java').getText('UTF-8') if (!mainSource.contains('MODID = "baseelectric"') - || !mainSource.contains('VERSION = "2.2.11.118021"')) { + || !mainSource.contains('VERSION = "2.2.12.119041"')) { throw new GradleException('baseElectric source identity changed') } String metadata = file('src/main/resources/META-INF/mods.toml').getText('UTF-8').replace('\r\n', '\n') ['modId="baseelectric"', 'displayName="baseElectric"', 'modId="basepower"', 'modId="basemetals"', - 'versionRange="[2.4.10.118021,3.0.0)"', 'versionRange="[3.1.2.118021,4.0.0)"'].each { marker -> + 'versionRange="[2.4.11.119041,3.0.0)"', 'versionRange="[3.1.2.119041,4.0.0)"'].each { marker -> if (!metadata.contains(marker)) { throw new GradleException("Source mods.toml is missing ${marker}") } @@ -477,7 +458,7 @@ def auditModInput = { File jarFile, String modId, String version, List r def manifest = new Manifest(zip.getInputStream(zip.getEntry('META-INF/MANIFEST.MF'))).mainAttributes String metadata = zip.getInputStream(zip.getEntry('META-INF/mods.toml')).getText('UTF-8') if (manifest.getValue('Implementation-Version') != version - || 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' || !metadata.contains("modId=\"${modId}\"") || !metadata.contains("version=\"${version}\"")) { @@ -489,10 +470,12 @@ def auditModInput = { File jarFile, String modId, String version, List r tasks.register('verifyLocalNativeInputs') { group = 'verification' - description = 'Verifies explicit Base Metals and OreSpawn 1.18.2 development and player JARs.' + description = 'Verifies explicit Base Metals and OreSpawn 1.19.4 development and player JARs.' dependsOn tasks.named('verifyReleaseConfiguration') doLast { - auditModInput(baseMetalsDeobfJar.get(), 'basemetals', project.basemetals_version, []) + auditModInput(baseMetalsDeobfJar.get(), 'basemetals', project.basemetals_version, + ['zone/moddev/mc/basemetals/api/migration/Legacy112MigrationExtension.class', + 'zone/moddev/mc/basemetals/recipe/CrushingRecipe.class']) auditModInput(pinnedFile('baseMetalsRuntimeJar', project.basemetals_runtime_sha256), 'basemetals', project.basemetals_version, []) auditModInput(oreSpawnDeobfJar.get(), 'orespawn', project.orespawn_version, []) @@ -509,6 +492,8 @@ tasks.register('verifyLocalRuntimeInputs') { requirePowerPin() auditModInput(pinnedFile('powerAdvantageRuntimeJar', project.poweradvantage_runtime_sha256), 'basepower', project.poweradvantage_version, []) + logger.lifecycle("Verified basePower unit source: ${project.poweradvantage_unit_bootstrap_sha256} ${unitBootstrapSource.get()}") + logger.lifecycle("Verified basePower unit agent: ${project.poweradvantage_unit_bootstrap_jar_sha256} ${unitBootstrapInput.get()}") File installer = pinnedFile('forgeInstallerJar', project.forge_installer_sha256) logger.lifecycle("Verified Forge installer: ${project.forge_installer_sha256} ${installer}") } @@ -519,6 +504,7 @@ tasks.register('verifyReleaseDependencies') { description = 'Resolves Forge and audits the exact required basePower development jar.' dependsOn tasks.named('verifyReleaseConfiguration') doLast { + requirePowerPin() File powerJar = powerAdvantageDeobfJar.get() File metalsJar = baseMetalsDeobfJar.get() File oreJar = oreSpawnDeobfJar.get() @@ -529,7 +515,9 @@ tasks.register('verifyReleaseDependencies') { 'cyano/poweradvantage/api/migration/LegacyMigrationApi.class', 'zone/moddev/mc/basepower/conduitnetwork/ConduitRegistry.class', 'zone/moddev/mc/basepower/init/Fluids.class']) - auditModInput(metalsJar, 'basemetals', project.basemetals_version, []) + auditModInput(metalsJar, 'basemetals', project.basemetals_version, + ['zone/moddev/mc/basemetals/api/migration/Legacy112MigrationExtension.class', + 'zone/moddev/mc/basemetals/recipe/CrushingRecipe.class']) auditModInput(oreJar, 'orespawn', project.orespawn_version, []) Set compileFiles = configurations.compileClasspath.files.collect { it.canonicalFile } as Set @@ -554,6 +542,22 @@ tasks.register('verifyReleaseDependencies') { } } +def blocksAtlasPath = 'assets/minecraft/atlases/blocks.json' +def electricBlocksAtlas = [sources: [[type: 'single', resource: 'baseelectric:entity/laser_turret']]] +def verifyPackagedBlocksAtlas = { ZipFile zip -> + def overrides = zip.entries().findAll { + !it.isDirectory() && it.name.startsWith('assets/minecraft/') + }.collect { it.name } + if (overrides != [blocksAtlasPath]) { + throw new GradleException("Expected only ${blocksAtlasPath} in the Minecraft asset namespace, found ${overrides}") + } + zip.getInputStream(zip.getEntry(blocksAtlasPath)).withCloseable { input -> + if (new JsonSlurper().parse(input, 'UTF-8') != electricBlocksAtlas) { + throw new GradleException('Packaged blocks atlas must add only the turret item sprite') + } + } +} + tasks.register('verifyReleaseArtifacts') { group = 'verification' description = 'Audits the exact three distributable jars and release-critical contents.' @@ -592,6 +596,7 @@ tasks.register('verifyReleaseArtifacts') { File mainJar = new File(libs, expectedReleaseFiles.get()[0]) new ZipFile(mainJar).withCloseable { zip -> + verifyPackagedBlocksAtlas(zip) List names = zip.entries().collect { it.name } ['META-INF/mods.toml', 'pack.mcmeta', 'zone/moddev/mc/baseelectric/ElectricAdvantage.class', @@ -605,6 +610,8 @@ tasks.register('verifyReleaseArtifacts') { 'assets/baseelectric/lang/en_us.json', 'data/baseelectric/loot_tables/blocks/electric_drill.json', 'data/baseelectric/loot_tables/blocks/electric_conduit.json', + 'data/baseelectric/damage_type/laser.json', + 'data/baseelectric/damage_type/propeller.json', 'data/baseelectric/recipes/electric_conduit_plastic_ingot.json', 'data/baseelectric/advancements/recipes/integrated_circuit_copper_normal.json', 'data/forge/tags/items/ores/lithium.json'].each { required -> @@ -626,12 +633,14 @@ tasks.register('verifyReleaseArtifacts') { String metadata = zip.getInputStream(zip.getEntry('META-INF/mods.toml')) .getText(StandardCharsets.UTF_8.name()) if (!metadata.contains('modId="baseelectric"') - || !metadata.contains("version=\"${project.mod_version}\"")) { + || !metadata.contains("version=\"${project.mod_version}\"") + || !metadata.contains('modId="basemetals"\nmandatory=false')) { 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') @@ -654,6 +663,7 @@ tasks.register('verifyReleaseArtifacts') { } new ZipFile(tasks.named('jar', Jar).get().archiveFile.get().asFile).withCloseable { zip -> + verifyPackagedBlocksAtlas(zip) def manifest = new Manifest(zip.getInputStream(zip.getEntry('META-INF/MANIFEST.MF'))).mainAttributes if (powerPinManifest.any { key, value -> manifest.getValue(key) != value }) { throw new GradleException('Development JAR does not record the exact basePower pins') @@ -792,9 +802,9 @@ publishing { tasks.register('validateMavenReleaseCredentials') { group = 'publishing' - description = 'Rejects remote publication from this local port.' + description = 'Keeps remote publication in the guarded release workflow.' doLast { - throw new GradleException('Remote Maven publication is disabled on the local 1.18.2 port.') + throw new GradleException('Use the guarded release workflow to publish the verified 1.19.4 jar.') } } @@ -819,6 +829,7 @@ def obsoleteForgeGradle2EclipseLaunches = [ eclipse { project { + name = rootProject.name buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder' natures 'org.eclipse.buildship.core.gradleprojectnature' } @@ -914,6 +925,7 @@ tasks.register('isolateEclipseProductionRuns') { } // ForgeGradle's legacy launch generator can leave this token unresolved, // so write the target version into every generated launch explicitly. + def parser = new groovy.xml.XmlParser() fileTree(project.projectDir) { include 'run*.launch' }.files.each { File launch -> @@ -921,10 +933,21 @@ tasks.register('isolateEclipseProductionRuns') { contents = contents.replace( 'key="MC_VERSION" value="${MC_VERSION}"', "key=\"MC_VERSION\" value=\"${project.minecraft_version}\"") + def launchXml = parser.parseText(contents) + launchXml.stringAttribute.findAll { it.@key == 'org.eclipse.jdt.launching.PROJECT_ATTR' }.each { + it.@value = project.name + } + // A reused launch may still name the old workspace project. + launchXml.listAttribute.find { it.@key == 'org.eclipse.jdt.launching.CLASSPATH' } + ?.listEntry?.each { entry -> + def runtime = parser.parseText(entry.@value) + if (runtime.@projectName == 'baseElectric') { + runtime.@projectName = project.name + entry.@value = groovy.xml.XmlUtil.serialize(runtime) + } + } if (launch.name in ['runClient.launch', 'runServer.launch', 'runData.launch']) { // Eclipse compiles into bin/main, not Gradle's merged run output. - def parser = new groovy.xml.XmlParser() - def launchXml = parser.parseText(contents) def modClasses = launchXml.mapAttribute.find { it.@key == 'org.eclipse.debug.core.environmentVariables' }.mapEntry.findAll { it.@key == 'MOD_CLASSES' } @@ -942,21 +965,8 @@ tasks.register('isolateEclipseProductionRuns') { throw new GradleException("${launch.name} is missing its program arguments") } programArguments.@value = quoteEclipseLaunchPaths(launch.name, programArguments.@value) - // A launch reused across the rename may still point at the old Eclipse project. - launchXml.stringAttribute.findAll { it.@key == 'org.eclipse.jdt.launching.PROJECT_ATTR' }.each { - it.@value = project.name - } - launchXml.listAttribute.find { it.@key == 'org.eclipse.jdt.launching.CLASSPATH' } - ?.listEntry?.each { entry -> - def runtime = parser.parseText(entry.@value) - if (runtime.@projectName == 'ElectricAdvantage') { - runtime.@projectName = project.name - entry.@value = groovy.xml.XmlUtil.serialize(runtime) - } - } - contents = groovy.xml.XmlUtil.serialize(launchXml) } - launch.setText(contents.replace('\r\n', '\n'), 'UTF-8') + launch.setText(groovy.xml.XmlUtil.serialize(launchXml).replace('\r\n', '\n'), 'UTF-8') } ['runClient.launch', 'runServer.launch'].each { name -> File launch = file(name) @@ -982,7 +992,6 @@ tasks.register('isolateEclipseProductionRuns') { }.findAll { File runtimeFile -> !mainOutputs.contains(runtimeFile) } - def parser = new groovy.xml.XmlParser() def launchXml = parser.parseText(contents) def classpath = launchXml.listAttribute.find { it.@key == 'org.eclipse.jdt.launching.CLASSPATH' } if (classpath == null) { @@ -1025,11 +1034,29 @@ tasks.register('verifyEclipseProductionClasspath') { != gradle.gradleUserHomeDir.canonicalFile) { throw new GradleException('Eclipse Buildship does not use this wrapper and Gradle home') } - String projectMetadata = file('.project').getText('UTF-8') - if (!projectMetadata.contains('org.eclipse.buildship.core.gradleprojectnature') - || !projectMetadata.contains('org.eclipse.buildship.core.gradleprojectbuilder')) { + def eclipseProject = new groovy.xml.XmlSlurper().parse(file('.project')) + if (project.name != 'ElectricAdvantage' || eclipseProject.name.text() != project.name) { + throw new GradleException('The Eclipse and Gradle project names must both be ElectricAdvantage') + } + if (!eclipseProject.natures.nature.any { it.text() == 'org.eclipse.buildship.core.gradleprojectnature' } + || !eclipseProject.buildSpec.buildCommand.name.any { it.text() == 'org.eclipse.buildship.core.gradleprojectbuilder' }) { throw new GradleException('Eclipse project metadata is missing its Buildship nature or builder') } + ['Client', 'Server', 'Data', 'TestClient', 'TestServer', 'TestData'].each { run -> + String name = "run${run}.launch" + def launch = new groovy.xml.XmlSlurper().parse(file(name)) + def projectAttributes = launch.stringAttribute.findAll { + it.@key.text() == 'org.eclipse.jdt.launching.PROJECT_ATTR' + }.collect { it.@value.text() } + def classpathProjects = launch.listAttribute.find { + it.@key.text() == 'org.eclipse.jdt.launching.CLASSPATH' + }.listEntry.collect { + new groovy.xml.XmlSlurper().parseText(it.@value.text()).@projectName.text() + }.findAll { it } + if (projectAttributes != [project.name] || classpathProjects != [project.name]) { + throw new GradleException("${name} must use the ${project.name} Eclipse project and classpath entry") + } + } File eclipseModMetadata = file('bin/main/META-INF/mods.toml') String eclipseMetadata = eclipseModMetadata.getText('UTF-8') if (!eclipseMetadata.contains("modId=\"${project.mod_id}\"") @@ -1085,21 +1112,12 @@ tasks.register('verifyEclipseProductionClasspath') { || contents.contains('src/test') || contents.contains('bin/test')) { throw new GradleException("${name} does not isolate production code") } - if (launch.stringAttribute.find { - it.@key.text() == 'org.eclipse.jdt.launching.PROJECT_ATTR' - }.@value.text() != project.name) { - throw new GradleException("${name} targets the wrong Eclipse project") - } def runtimeEntries = launch.listAttribute.find { it.@key.text() == 'org.eclipse.jdt.launching.CLASSPATH' }.listEntry.collect { new groovy.xml.XmlSlurper().parseText(it.@value.text()) } if (runtimeEntries.any { it.@containerPath.text() == 'org.eclipse.buildship.core.gradleclasspathcontainer' }) { throw new GradleException("${name} exposes compileOnly dependencies at runtime") } - if (!runtimeEntries.any { it.@projectName.text() == project.name } - || runtimeEntries.any { it.@projectName.text() == 'ElectricAdvantage' }) { - throw new GradleException("${name} has an incorrect Eclipse project classpath entry") - } List runtimeArchives = runtimeEntries.findAll { it.@externalArchive.text() }.collect { file(it.@externalArchive.text()).canonicalFile } @@ -1141,7 +1159,7 @@ tasks.register('verifyEclipseProductionClasspath') { || !programArguments.contains("--fml.forgeVersion ${project.forge_version}") || !programArguments.contains("--fml.mcpVersion ${project.mcp_version}") || !vmArguments.contains('--add-opens java.base/java.lang.invoke=ALL-UNNAMED')) { - throw new GradleException("${name} does not define the native Forge 40 bootstrap and literal target versions") + throw new GradleException("${name} does not define the native Forge 45 bootstrap and literal target versions") } } } @@ -1155,8 +1173,10 @@ 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 atlasFile = new File(resourceRoot, blocksAtlasPath) inputs.dir(assetRoot) inputs.dir(new File(resourceRoot, 'data')) + inputs.files(atlasFile) inputs.file(powerAdvantageDeobfJar) doLast { @@ -1167,9 +1187,19 @@ tasks.register('verifyModelAssets') { new JsonSlurper().parse(jsonFile) } + def localAtlas = atlasFile.isFile() ? new JsonSlurper().parse(atlasFile) : [sources: []] + // Vanilla's generic directories also cover mod namespaces. Power adds the legacy directories. + List atlasSources = [[type: 'directory', source: 'block', prefix: 'block/'], + [type: 'directory', source: 'item', prefix: 'item/']] Set powerAdvantageAssets = new ZipFile(powerAdvantageDeobfJar.get()).withCloseable { zip -> + def entry = zip.getEntry(blocksAtlasPath) + if (entry == null) throw new GradleException('Pinned basePower is missing its blocks atlas') + zip.getInputStream(entry).withCloseable { input -> + atlasSources.addAll(new JsonSlurper().parse(input, 'UTF-8').sources) + } zip.entries().collect { it.name } as Set } + atlasSources.addAll(localAtlas.sources) def verifyReference = { String reference, String assetType, String sourcePath -> if (reference.startsWith('#')) return int separator = reference.indexOf(':') @@ -1186,6 +1216,14 @@ tasks.register('verifyModelAssets') { throw new GradleException( "${sourcePath} references missing ${assetType} asset ${reference}") } + if (assetType == 'textures' && !atlasSources.any { source -> + if (source.type == 'single') return (source.sprite ?: source.resource) == reference + if (source.type != 'directory') return false + String folder = source.source + '/' + path.startsWith(folder) && (source.prefix + path.substring(folder.length())) == path + }) { + throw new GradleException("${sourcePath} references unstitched blocks-atlas sprite ${reference}") + } } fileTree(modelRoot) { include '**/*.json' }.files.sort().each { File modelFile -> @@ -1198,6 +1236,18 @@ tasks.register('verifyModelAssets') { verifyReference(texture, 'textures', modelFile.path) } } + if (model.elements instanceof List) { + model.elements.each { element -> + if (element.faces instanceof Map) { + element.faces.values().each { face -> + if (face.texture instanceof String) verifyReference(face.texture, 'textures', modelFile.path) + } + } + } + } + } + if (localAtlas != electricBlocksAtlas) { + throw new GradleException('Electric blocks atlas must add only the turret item sprite') } Closure verifyBlockstateModels @@ -1246,6 +1296,7 @@ tasks.register('verifyRepositoryHygiene') { || normalized.startsWith('.cursor/') || normalized.startsWith('.ci-dependencies/') || normalized.startsWith('.settings/') + || normalized.startsWith('.gradle-user-home/') || normalized.startsWith('build/') || normalized.startsWith('bin/') || normalized.startsWith('run/') diff --git a/docs/NATIVE118.md b/docs/NATIVE118.md index e70fad4..9ab8892 100644 --- a/docs/NATIVE118.md +++ b/docs/NATIVE118.md @@ -1,17 +1,15 @@ -# baseElectric 1.18.2 port notes +# baseElectric 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 Electric player jar had SHA-256 +and OreSpawn pins. The release and Eclipse audits passed with these inputs. +The current Electric player JAR has SHA-256 `C5C1A6DEAE795F8AF741DBE35B0D8941F7FA4BBC708758BDFE056B4AEBADCBC1`. The notes below record earlier port milestones and their original inputs. `feature/baseelectric-1.18.2-port` was cloned from the clean PA117 `feature/baseelectric-1.17.1-identity-migration` tip -`cf3d4b0e2d1591b2ae8e5d638002ca016e46eac4`. +`cf3d4b0e2d1591b2ae8e5d638002ca016e46eac4`. This checkout has no Git remote. The candidate is `2.2.11.118021`, for Forge `40.3.0`, official `1.18.2`, MCP `20220404.173914`, Java 17 and ForgeGradle `7.0.34`. diff --git a/docs/NATIVE119.md b/docs/NATIVE119.md new file mode 100644 index 0000000..8c6c40a --- /dev/null +++ b/docs/NATIVE119.md @@ -0,0 +1,87 @@ +# baseElectric 1.19.4 port + +Branch: `feature/baseelectric-1.19.4-port`. The isolated, no-hardlinks +checkout starts at accepted 1.18.2 Electric commit +`4914e5c8a0d8d878f0938b3a9dc59d9f35f7c85e`. Release builds use the published +basePower and Base Metals sources and artifacts recorded in `gradle.properties`. + +## Target and inputs + +- [Forge 1.19.4-45.4.0](https://files.minecraftforge.net/net/minecraftforge/forge/index_1.19.4.html) + and [ForgeGradle 7](https://github.com/MinecraftForge/ForgeGradle/blob/FG_7.0/docs/Overview.md). + Local Base Metals and OreSpawn establish ForgeGradle `7.0.34`, Gradle + `9.6.1`, Renamer `1.1.5`, official 1.19.4 and MCP `20230314.122934`. + Java 17 runs the game, compiles the mod, and runs Gradle; Java 25 is + Mavenizer-only and Java 8 is for the Slime Launcher generation tool. +- Use the checksum-bound Mavenizer compatibility JAR from + `D:/MinecraftMineralogy/OS119/OreSpawn/ci-fixtures/tools/`. It fixes the + eight duplicate record accessors on this exact target and is never shipped. + The local Forge installer SHA-1 matches the official download listing. +- Base Metals `3.1.2.119041` is the published migration-hook build from + `5791a793aa3a1ca1aa3fcb36e6d4a2d865e7d2d3`. It preserves the old + machine data when both mods are installed. +- OreSpawn `4.1.0.119041` JARs are in + `D:/MinecraftMineralogy/OS119/OreSpawn/build/libs-dev` and `build/libs`. +- The released basePower `2.4.11.119041` comes from + `4af5c7d5d490162cddc7f1e4fc72cc602b42bb32`. The development jar, player + jar, unit agent and agent source are checked separately. Electric uses + the canonical Forge 45 unit agent without an extra event transformer. + +The exact local dependency digests are in [gradle.properties](../gradle.properties). +Do not substitute 1.18 artifacts. No sibling build, shared Gradle write, or +world migration is part of Electric's local build. + +## Port changes + +Forge 45 registration retains all registry paths and legacy aliases. Visible +items call basePower's shared `ItemGroups.show` at registration; the pump +pipe and evil turret remain hidden. Machine recipes use the level's native +registry access. Base Metals crushing stays optional with the vanilla fallback +unchanged. Machine and generator sound keep the original recipient cube, +without chunk queries, using Forge 45's native sound packet. Laser and +propeller use native damage types with the same message IDs. The renderer +uses Minecraft's JOML-backed transforms; the existing shader and textures +remain unchanged. BasePower supplies the shared legacy atlas folder support. + +## Evidence + +Keep each run in a fresh directory under `build/port-evidence`. These +receipts predate the pinned checkpoint: + +| Receipt | Scope | +| --- | --- | +| `provisional-power-compile-b`, `provisional-test-compile-b` | Java 17 compiled 86 production and 28 test sources against the compile-only Power input. The source/classpath hashes and compiler output are retained. | +| `gradle-preflight-e` | Isolated warm-cache Gradle passed production compilation, resource processing, target, repository, recipe-guard and Base Metals/OreSpawn input audits. | +| `gradle-provisional-test-compile-a` | Gradle compiled test sources; did not execute tests. | +| `junit-no-power-a` | Eight loot, pack metadata, shader resource, recipe and historical workflow assertions passed with no Power JAR on the classpath. | +| `static-119-c.json` | Fourteen preservation contracts and changed-path hashes against accepted 1.18.2. The separate unlock generator checked all 50 recipe unlocks. | + +Initial compile and cache failures remain under distinct paths. The successful +offline compile used a copied Gradle user home and Mavenizer cache, not a cold +Minecraft preparation. No game launched in this preflight. + +### Superseded local checkpoint 1cf39fd + +Evidence root: `build/port-evidence/power-checkpoint-1cf39fd`. + +- `test-a` preserves the first 96-test run: four entity tests lacked Forge's + fluid-type registry and five pickup tests lacked initialized fluid state + caches. Only the worldless fixtures changed; no assertions were skipped. +- `lifecycle-b` passed all 96 tests in 27 suites, target and dependency + audits, release archives and checksums, and the generated Java 17 Eclipse + production classpath and launches. +- `ecj-a` independently compiled 86 main and 28 test sources and passed all + 96 tests with the pinned agent and no skips, ignores or assumptions. The + restricted-JDK negative control failed as intended. +- `artifacts` contains the audited player, development, sources and Javadoc + JARs. `archives.json` records all four hashes; the player SHA-256 is + `11F26C99A8AA0EFF8ACAB33019EB31DAD7869A17063C302E475B46217FEA6E01`. +- `smoke-with-metals-a` preserves the first packaged failure: the live item + selector found no entity after loot spawned inside the ore block. + `smoke-with-metals-b` and `smoke-without-metals-a` passed drill NBT, + live sulfur loot, damage-type registration, save and shutdown checks. + +The independent Gradle/ECJ, package and dedicated-server checks must use exact +checkpoint pins. The smoke runner supports separate runs with Base Metals and +without it; both keep required OreSpawn. A successful smoke is not +combined-suite, client/GPU, original-world migration or manual acceptance. diff --git a/gradle.properties b/gradle.properties index 8143b78..33d565f 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,) +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,) 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,30 +26,31 @@ curseforge_project_id=1783006 mod_id=baseelectric mod_name=baseElectric mod_license=GPL-3.0-only -mod_version=2.2.11.118021 +mod_version=2.2.12.119041 mod_group=zone.moddev.mc.baseelectric mod_authors=DrCyano mod_description=Electric-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_pin_state=PUBLISHED_RELEASE poweradvantage_source_repository=MinecraftModDevelopmentMods/basePower -# Owner-frozen local source checkpoint; development and player JARs are distinct. -poweradvantage_source_ref=bcf5105ef5c10ba36c26c8ff941742fecc3e11a1 -poweradvantage_deobf_sha256=767BABC0D6E1536DCB937B9F1DC565AF348B3CAF92D3207671EF1E1C8D8EF5D4 -poweradvantage_runtime_sha256=8CA46D5BCBFFDDAEB4F40137B5BF71E5AF7DD36515E5BFEA751FC5C02249823B -poweradvantage_unit_bootstrap_sha256=025F16FCA9FD5CFC585931E7C8BCFAB22FC1DF933BE7DAAD490B8E4FC824B76E +# Published source. Development, player and unit-agent artifacts are distinct. +poweradvantage_source_ref=4af5c7d5d490162cddc7f1e4fc72cc602b42bb32 +poweradvantage_deobf_sha256=512FDEC9275B33533EF773C110E6229E0F07268775FAD4B5942A15B68A9EB87D +poweradvantage_runtime_sha256=E1689C1147F6F6F93F6516D9188A279093D6CCC2FF1FBEB506468B876BA03E60 +poweradvantage_unit_bootstrap_sha256=0FB05554D6446BD5561BF9DE47B9F173E6F6C28E39A2BC877D359BB363CB1ABE +poweradvantage_unit_bootstrap_jar_sha256=A93CD83EC42B09B730CB7715C10C6E53D47B22B557EFE68EDC95241D53BD4EB3 -basemetals_version=3.1.2.118021 -basemetals_source_commit=3488ed58403722ac3ff203cff7680dfc16e0fd3f -basemetals_deobf_sha256=E4F9E2CDE355275218C79954D3792D000615790C7CBAE5FCEC59636A8C4CA57D -basemetals_runtime_sha256=5595C2CE7ADCDF1599E4327F1130121148A5CACD928CBC2190312D61F7CA37A9 +basemetals_version=3.1.2.119041 +basemetals_source_commit=5791a793aa3a1ca1aa3fcb36e6d4a2d865e7d2d3 +basemetals_deobf_sha256=0AA993D6BADA54B11548EB5BE150CE0939232291F2D215B7DBAF149857976B2F +basemetals_runtime_sha256=991EA843EEDF5FA1E42E9984A07573C7D03D74230482230440E44EDCA895FA39 -orespawn_version=4.1.0.118021 -orespawn_source_commit=d31f52f296739f34a0c13f554abf3e1ac071ae25 -orespawn_deobf_sha256=34B25B2116FD91B0050C80393F8356A5CA256E707BF443E4882BE5CFAD1335B6 -orespawn_runtime_sha256=BD8D84A96DAFB7668E95D61B9D72C62AEF3440675473E41244C182C64653356F +orespawn_version=4.1.0.119041 +orespawn_source_commit=444d86ef48db2bf54422901b067c51d5832197fc +orespawn_deobf_sha256=BE6973F68F1C310DFF8CDF89C12B34C62D68CAEA326C0F1528E486EE3ABC1000 +orespawn_runtime_sha256=1B2ECAA546E7FFB35863AC1D9FF92504471EE857045DCD01A068C5CA7F53CFFD diff --git a/gradle/forge45-mavenizer.gradle b/gradle/forge45-mavenizer.gradle new file mode 100644 index 0000000..62bacb7 --- /dev/null +++ b/gradle/forge45-mavenizer.gradle @@ -0,0 +1,27 @@ +import java.security.MessageDigest +import org.gradle.jvm.toolchain.JvmVendorSpec + +// OreSpawn's audited Forge 45 fixture fixes eight decompiled record accessors. +def fixturePath = providers.gradleProperty('forge45MavenizerJar').orNull +if (fixturePath == null) fixturePath = project.findProperty('forge45MavenizerDefault') +if (!fixturePath || fixturePath.isBlank()) { + throw new GradleException('Set -Pforge45MavenizerJar to the pinned OreSpawn Forge 45 Mavenizer fixture.') +} +File fixture = file(fixturePath) +if (!fixture.isFile()) { + throw new GradleException("Forge 45 Mavenizer fixture not found: ${fixture}. Use -Pforge45MavenizerJar=.") +} +def expected = '341B3FB10ABA6EA7DC612FBE519DC290A1C81867AED5DD320ACD783688AC49DE' +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/settings.gradle b/settings.gradle index cea1b34..23ff1c3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,4 +2,4 @@ plugins { id('org.gradle.toolchains.foojay-resolver-convention') version '1.0.0' } -rootProject.name = 'baseElectric' +rootProject.name = 'ElectricAdvantage' diff --git a/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java b/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java index ef0b287..9652e0e 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java +++ b/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java @@ -28,13 +28,13 @@ import zone.moddev.mc.baseelectric.recipe.RecipeConditions; import zone.moddev.mc.baseelectric.migration.ElectricLegacyMigrations; -/** Forge 1.18.2 entry point for baseElectric. */ +/** Forge 1.19.4 entry point for baseElectric. */ @Mod(ElectricAdvantage.MODID) public final class ElectricAdvantage { public static final String MODID = "baseelectric"; public static final String LEGACY_MODID = "electricadvantage"; public static final String NAME = "baseElectric"; - 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 ElectricAdvantage INSTANCE; diff --git a/src/main/java/zone/moddev/mc/baseelectric/blocks/Ore.java b/src/main/java/zone/moddev/mc/baseelectric/blocks/Ore.java index 44d82d0..7ff1a0e 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/blocks/Ore.java +++ b/src/main/java/zone/moddev/mc/baseelectric/blocks/Ore.java @@ -1,6 +1,6 @@ package zone.moddev.mc.baseelectric.blocks; -import java.util.Random; +import net.minecraft.util.RandomSource; import java.util.Collections; import java.util.List; import java.util.function.Supplier; @@ -55,7 +55,7 @@ public List getDrops(BlockState state, LootContext.Builder context) { && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0)) { return Collections.singletonList(new ItemStack(this)); } - Random random = context.getLevel().random; + RandomSource random = context.getLevel().random; ItemStack result = dropItem.copy(); result.setCount(dropItem.getCount() + (dropRange > 1 ? random.nextInt(dropRange) : 0)); return Collections.singletonList(result); diff --git a/src/main/java/zone/moddev/mc/baseelectric/blocks/PumpPipeBlock.java b/src/main/java/zone/moddev/mc/baseelectric/blocks/PumpPipeBlock.java index 50b0862..da6e575 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/blocks/PumpPipeBlock.java +++ b/src/main/java/zone/moddev/mc/baseelectric/blocks/PumpPipeBlock.java @@ -17,7 +17,7 @@ /** Hidden pump intake column. */ public class PumpPipeBlock extends Block { private static final VoxelShape SHAPE = Shapes.box(0.25D, 0.0D, 0.25D, 0.75D, 1.0D, 0.75D); - public PumpPipeBlock() { super(Properties.of(Material.METAL).strength(5.0F, 10.0F).dynamicShape().noOcclusion().isRedstoneConductor((state, world, pos) -> false).noDrops()); } + public PumpPipeBlock() { super(Properties.of(Material.METAL).strength(5.0F, 10.0F).dynamicShape().noOcclusion().isRedstoneConductor((state, world, pos) -> false).noLootTable()); } @Override public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { return SHAPE; } @Override public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { return SHAPE; } @Override public java.util.List getDrops(BlockState state, LootContext.Builder context) { return java.util.Collections.emptyList(); } diff --git a/src/main/java/zone/moddev/mc/baseelectric/compat/BaseMetalsCompat.java b/src/main/java/zone/moddev/mc/baseelectric/compat/BaseMetalsCompat.java index eee7871..8aaebae 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/compat/BaseMetalsCompat.java +++ b/src/main/java/zone/moddev/mc/baseelectric/compat/BaseMetalsCompat.java @@ -7,7 +7,6 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Recipe; import net.minecraft.resources.ResourceLocation; -import net.minecraft.network.chat.TextComponent; import net.minecraft.world.level.Level; import net.minecraftforge.fml.ModList; import net.minecraftforge.registries.ForgeRegistries; @@ -38,7 +37,7 @@ public static ItemStack getCrusherRecipeOutput(Level world, ItemStack input) { inventory.setItem(0, input.copy()); for (Recipe candidate : world.getRecipeManager().getRecipes()) { if (candidate instanceof CrushingRecipe && ((CrushingRecipe) candidate).matches(inventory, world)) { - return candidate.getResultItem().copy(); + return candidate.getResultItem(world.registryAccess()).copy(); } } } @@ -50,7 +49,7 @@ public static ItemStack getCrusherRecipeOutput(Level world, BlockState input) { } private static ItemStack getVanillaCrusherOutput(ItemStack input) { - String key = String.valueOf(input.getItem().getRegistryName()); + String key = String.valueOf(ForgeRegistries.ITEMS.getKey(input.getItem())); if ("minecraft:stone".equals(key) || "minecraft:stone_bricks".equals(key)) { return stack("minecraft:cobblestone", 1); } diff --git a/src/main/java/zone/moddev/mc/baseelectric/entities/HydroturbineEntity.java b/src/main/java/zone/moddev/mc/baseelectric/entities/HydroturbineEntity.java index 48b478c..567fa98 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/entities/HydroturbineEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/entities/HydroturbineEntity.java @@ -8,6 +8,7 @@ import net.minecraft.world.level.material.FluidState; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraftforge.network.NetworkHooks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.core.BlockPos; @@ -51,7 +52,7 @@ protected void defineSynchedData() { } @Override - public Packet getAddEntityPacket() { + public Packet getAddEntityPacket() { return NetworkHooks.getEntitySpawningPacket(this); } @@ -100,7 +101,7 @@ public void tick(){ // slice-n-dice unfortunate souls caught in the blades List victims = getCommandSenderWorld().getEntitiesOfClass(LivingEntity.class, this.getBoundingBox()); for(Entity e : victims){ - e.hurt(Power.propeller_damage, PROPELLER_DAMAGE); + e.hurt(Power.damageSource(getCommandSenderWorld(), Power.PROPELLER_DAMAGE), PROPELLER_DAMAGE); } } } diff --git a/src/main/java/zone/moddev/mc/baseelectric/graphics/ClientRegistration.java b/src/main/java/zone/moddev/mc/baseelectric/graphics/ClientRegistration.java index 9f819ae..d7a3a7f 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/graphics/ClientRegistration.java +++ b/src/main/java/zone/moddev/mc/baseelectric/graphics/ClientRegistration.java @@ -33,7 +33,7 @@ public static void registerRenderers(EntityRenderersEvent.RegisterRenderers even @SubscribeEvent public static void registerShaders(RegisterShadersEvent event) throws IOException { - event.registerShader(new ShaderInstance(event.getResourceManager(), ElectricRenderTypes.UNSHADED, + event.registerShader(new ShaderInstance(event.getResourceProvider(), ElectricRenderTypes.UNSHADED, DefaultVertexFormat.NEW_ENTITY), ElectricRenderTypes::setUnshadedShader); } } diff --git a/src/main/java/zone/moddev/mc/baseelectric/graphics/HydroturbineRenderer.java b/src/main/java/zone/moddev/mc/baseelectric/graphics/HydroturbineRenderer.java index 4be0abc..88a4380 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/graphics/HydroturbineRenderer.java +++ b/src/main/java/zone/moddev/mc/baseelectric/graphics/HydroturbineRenderer.java @@ -5,7 +5,7 @@ import com.mojang.blaze3d.vertex.PoseStack; 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.texture.OverlayTexture; import net.minecraft.client.renderer.entity.EntityRenderer; import net.minecraft.client.renderer.entity.EntityRendererProvider; @@ -40,7 +40,7 @@ public void render(final HydroturbineEntity entity, final float entityYaw, final final float shaftMaxV = 1.0f; matrices.translate(0f, 0.5f, 0f); - matrices.mulPose(Vector3f.YP.rotationDegrees(e.getYRot())); + matrices.mulPose(Axis.YP.rotationDegrees(e.getYRot())); final BufferedQuads worldRenderer = new BufferedQuads(matrices, buffers.getBuffer(RenderType.entitySolid(texture)), packedLight, OverlayTexture.NO_OVERLAY); diff --git a/src/main/java/zone/moddev/mc/baseelectric/graphics/LaserDrillRenderer.java b/src/main/java/zone/moddev/mc/baseelectric/graphics/LaserDrillRenderer.java index 6aae6af..e154ccf 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/graphics/LaserDrillRenderer.java +++ b/src/main/java/zone/moddev/mc/baseelectric/graphics/LaserDrillRenderer.java @@ -5,7 +5,7 @@ import com.mojang.blaze3d.vertex.PoseStack; 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; import net.minecraft.core.Direction; @@ -76,8 +76,8 @@ public void render(final ElectricDrillTileEntity e, final float partialTick, fin matrices.pushPose(); matrices.translate(0.5f, 0.5f, 0.5f); - matrices.mulPose(Vector3f.YP.rotationDegrees(rotY)); - matrices.mulPose(Vector3f.XP.rotationDegrees(rotX)); + matrices.mulPose(Axis.YP.rotationDegrees(rotY)); + matrices.mulPose(Axis.XP.rotationDegrees(rotX)); final BufferedQuads worldRenderer = new BufferedQuads(matrices, buffers.getBuffer(beamType), 15 << 20 | 15 << 4, packedOverlay); diff --git a/src/main/java/zone/moddev/mc/baseelectric/graphics/LaserTurretRenderer.java b/src/main/java/zone/moddev/mc/baseelectric/graphics/LaserTurretRenderer.java index bffb31b..ea57220 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/graphics/LaserTurretRenderer.java +++ b/src/main/java/zone/moddev/mc/baseelectric/graphics/LaserTurretRenderer.java @@ -5,7 +5,7 @@ import com.mojang.blaze3d.vertex.PoseStack; 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; import net.minecraft.resources.ResourceLocation; @@ -58,8 +58,8 @@ public void render(final LaserTurretTileEntity e, final float partialTick, final matrices.pushPose(); matrices.translate(0.5f, 0.75f, 0.5f); - matrices.mulPose(Vector3f.YP.rotationDegrees(yaw)); - matrices.mulPose(Vector3f.XP.rotationDegrees(pitch)); + matrices.mulPose(Axis.YP.rotationDegrees(yaw)); + matrices.mulPose(Axis.XP.rotationDegrees(pitch)); BufferedQuads worldRenderer = new BufferedQuads(matrices, buffers.getBuffer(renderType), packedLight, packedOverlay); diff --git a/src/main/java/zone/moddev/mc/baseelectric/init/Blocks.java b/src/main/java/zone/moddev/mc/baseelectric/init/Blocks.java index ca3b6b9..63547b1 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/init/Blocks.java +++ b/src/main/java/zone/moddev/mc/baseelectric/init/Blocks.java @@ -4,13 +4,15 @@ import java.util.Map; import cyano.poweradvantage.api.GUIBlock; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.block.Block; import net.minecraft.world.item.Item; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.ItemStack; -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.baseelectric.ElectricAdvantage; import zone.moddev.mc.baseelectric.blocks.ElectricConduitBlock; import zone.moddev.mc.baseelectric.blocks.ElectricScaffoldBlock; @@ -68,11 +70,10 @@ public final class Blocks { private Blocks() {} /** Kept for callers of the legacy bootstrap; Forge owns block construction. */ - public static void init() { ItemGroups.init(); } + public static void init() {} private static synchronized void constructBlocks() { if (initialized) return; - init(); electric_conduit = add(new ElectricConduitBlock(), "electric_conduit"); lithium_ore = add(new Ore(1), "li_ore"); sulfur_ore = add(Ore.withDeferredDrop(() -> new ItemStack(Items.sulfur_powder, 1), 0, 3), "sulfur_ore"); @@ -100,25 +101,24 @@ private static synchronized void constructBlocks() { } private static Block add(Block block, String id) { - block.setRegistryName(ElectricAdvantage.MODID, id); ALL_BLOCKS.put(id, block); return block; } - @SubscribeEvent public static void registerBlocks(RegistryEvent.Register event) { - constructBlocks(); - event.getRegistry().registerAll(ALL_BLOCKS.values().toArray(new Block[0])); + @SubscribeEvent public static void registerBlocks(RegisterEvent event) { + event.register(ForgeRegistries.Keys.BLOCKS, registry -> { + constructBlocks(); + ALL_BLOCKS.forEach((id, block) -> registry.register(new ResourceLocation(ElectricAdvantage.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_electric".equals(entry.getKey()) && !"laser_turret_evil".equals(entry.getKey())) { - properties.tab(ItemGroups.tab_powerAdvantage); + @SubscribeEvent 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()); + if (!"pump_pipe_electric".equals(id) && !"laser_turret_evil".equals(id)) { + ItemGroups.show(item); } - BlockItem item = new BlockItem(entry.getValue(), properties); - item.setRegistryName(ElectricAdvantage.MODID, entry.getKey()); - event.getRegistry().register(item); - } + registry.register(new ResourceLocation(ElectricAdvantage.MODID, id), item); + })); } } diff --git a/src/main/java/zone/moddev/mc/baseelectric/init/Entities.java b/src/main/java/zone/moddev/mc/baseelectric/init/Entities.java index d017eef..f29ae0e 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/init/Entities.java +++ b/src/main/java/zone/moddev/mc/baseelectric/init/Entities.java @@ -1,13 +1,15 @@ package zone.moddev.mc.baseelectric.init; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.MobCategory; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; -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.baseelectric.ElectricAdvantage; import zone.moddev.mc.baseelectric.entities.HydroturbineEntity; import zone.moddev.mc.baseelectric.machines.ElectricBatteryArrayTileEntity; @@ -51,7 +53,11 @@ public final class Entities { private Entities() {} public static void init() {} - @SubscribeEvent public static void registerTiles(RegistryEvent.Register> event) { + @SubscribeEvent public static void registerTiles(RegisterEvent event) { + event.register(ForgeRegistries.Keys.BLOCK_ENTITY_TYPES, Entities::registerTiles); + } + + private static void registerTiles(RegisterEvent.RegisterHelper> event) { ELECTRIC_FURNACE = tile(event, "electric_furnace", ElectricFurnaceTileEntity::new, Blocks.arc_furnace); ELECTRIC_CRUSHER = tile(event, "electric_crusher", ElectricCrusherTileEntity::new, Blocks.rock_crusher); ELECTRIC_DRILL = tile(event, "electric_drill", ElectricDrillTileEntity::new, Blocks.laser_drill); @@ -70,22 +76,22 @@ public static void init() {} PLASTIC_REFINERY = tile(event, "plastic_refinery", PlasticRefineryTileEntity::new, Blocks.plastic_refinery); } - @SubscribeEvent public static void registerEntities(RegistryEvent.Register> event) { - HYDROTURBINE = EntityType.Builder.of( - HydroturbineEntity::new, MobCategory.MISC) - .sized(0.9375F, 0.9375F) - .setTrackingRange(64).setUpdateInterval(1).setShouldReceiveVelocityUpdates(true) - .build(ElectricAdvantage.MODID + ":hydroturbine"); - HYDROTURBINE.setRegistryName(ElectricAdvantage.MODID, "hydroturbine"); - event.getRegistry().register(HYDROTURBINE); + @SubscribeEvent public static void registerEntities(RegisterEvent event) { + event.register(ForgeRegistries.Keys.ENTITY_TYPES, registry -> { + ResourceLocation id = new ResourceLocation(ElectricAdvantage.MODID, "hydroturbine"); + HYDROTURBINE = EntityType.Builder.of(HydroturbineEntity::new, MobCategory.MISC) + .sized(0.9375F, 0.9375F) + .setTrackingRange(64).setUpdateInterval(1).setShouldReceiveVelocityUpdates(true) + .build(id.toString()); + registry.register(id, HYDROTURBINE); + }); } private static BlockEntityType tile( - RegistryEvent.Register> event, String id, BlockEntityType.BlockEntitySupplier factory, + RegisterEvent.RegisterHelper> event, String id, BlockEntityType.BlockEntitySupplier factory, net.minecraft.world.level.block.Block... blocks) { BlockEntityType type = BlockEntityType.Builder.of(factory, blocks).build(null); - type.setRegistryName(ElectricAdvantage.MODID, id); - event.getRegistry().register(type); + event.register(new ResourceLocation(ElectricAdvantage.MODID, id), type); return type; } diff --git a/src/main/java/zone/moddev/mc/baseelectric/init/Items.java b/src/main/java/zone/moddev/mc/baseelectric/init/Items.java index 6ce1d0d..8dcdfcb 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/init/Items.java +++ b/src/main/java/zone/moddev/mc/baseelectric/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.baseelectric.ElectricAdvantage; import zone.moddev.mc.baseelectric.items.BatteryItem; import zone.moddev.mc.basepower.init.ItemGroups; @@ -38,11 +40,10 @@ public final class Items { private Items() {} /** Kept for callers of the legacy bootstrap; Forge owns item construction. */ - public static void init() { ItemGroups.init(); } + public static void init() {} private static synchronized void constructItems() { if (initialized) return; - init(); blank_circuit_board = add("blank_circuit_board", new Item(properties())); control_circuit = add("control_circuit", new Item(properties())); integrated_circuit = add("integrated_circuit", new Item(properties())); @@ -63,16 +64,18 @@ private static synchronized void constructItems() { initialized = true; } - private static Item.Properties properties() { return new Item.Properties().tab(ItemGroups.tab_powerAdvantage); } + private static Item.Properties properties() { return new Item.Properties(); } private static Item add(String id, Item item) { - item.setRegistryName(ElectricAdvantage.MODID, id); allItems.put(id, item); return item; } - @SubscribeEvent public static void register(RegistryEvent.Register event) { - constructItems(); - event.getRegistry().registerAll(allItems.values().toArray(new Item[0])); + @SubscribeEvent public static void register(RegisterEvent event) { + event.register(ForgeRegistries.Keys.ITEMS, registry -> { + constructItems(); + allItems.forEach((id, item) -> + registry.register(new ResourceLocation(ElectricAdvantage.MODID, id), ItemGroups.show(item))); + }); } public static Item getItemByName(String id) { return allItems.get(id); } diff --git a/src/main/java/zone/moddev/mc/baseelectric/init/Power.java b/src/main/java/zone/moddev/mc/baseelectric/init/Power.java index fbf6952..a358e40 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/init/Power.java +++ b/src/main/java/zone/moddev/mc/baseelectric/init/Power.java @@ -1,7 +1,13 @@ package zone.moddev.mc.baseelectric.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.baseelectric.ElectricAdvantage; public abstract class Power { @@ -22,6 +28,14 @@ public abstract class Power { // made from lead and sulfur public static final float LEAD_ACID_BATTERY_CAPACITY = 1000f; - public static final DamageSource laser_damage = new DamageSource("laser"); - public static final DamageSource propeller_damage = new DamageSource("propeller"); + public static final ResourceKey LASER_DAMAGE = damageType("laser"); + public static final ResourceKey PROPELLER_DAMAGE = damageType("propeller"); + + private static ResourceKey damageType(String id) { + return ResourceKey.create(Registries.DAMAGE_TYPE, new ResourceLocation(ElectricAdvantage.MODID, id)); + } + + public 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/baseelectric/items/BatteryItem.java b/src/main/java/zone/moddev/mc/baseelectric/items/BatteryItem.java index a33776d..4081eab 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/items/BatteryItem.java +++ b/src/main/java/zone/moddev/mc/baseelectric/items/BatteryItem.java @@ -8,8 +8,6 @@ import net.minecraft.world.item.Item; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.TextComponent; -import net.minecraft.network.chat.TranslatableComponent; import net.minecraft.world.level.Level; import java.text.NumberFormat; @@ -100,10 +98,10 @@ public void appendHoverText(ItemStack stack, Level world, List list, float max = getMaxEnergy(stack); if(max <= 0) return; // not a battery! float e = getEnergy(stack); - list.add(new TextComponent(nf.format(100 * e / max) + "% ") - .append(new TranslatableComponent("tooltip.battery.charge"))); - list.add(new TextComponent("(" + nf.format(max) + "kJ ") - .append(new TranslatableComponent("tooltip.battery.capacity")) + list.add(Component.literal(nf.format(100 * e / max) + "% ") + .append(Component.translatable("tooltip.battery.charge"))); + list.add(Component.literal("(" + nf.format(max) + "kJ ") + .append(Component.translatable("tooltip.battery.capacity")) .append(")")); } } diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricDrillTileEntity.java b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricDrillTileEntity.java index 3dbeb20..50570a1 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricDrillTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricDrillTileEntity.java @@ -290,7 +290,7 @@ public void onDataFieldUpdate() { List victims = getLevel().getEntitiesOfClass(LivingEntity.class, beamArea); for(Entity e : victims){ e.setSecondsOnFire(3); - e.hurt(Power.laser_damage, 2f); + e.hurt(Power.damageSource(getLevel(), Power.LASER_DAMAGE), 2f); } } } diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricGeneratorTileEntity.java b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricGeneratorTileEntity.java index 77b0198..6698dfa 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricGeneratorTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricGeneratorTileEntity.java @@ -3,16 +3,11 @@ import zone.moddev.mc.baseelectric.init.Power; import cyano.poweradvantage.api.ConduitType; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.entity.player.Player; -import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.protocol.game.ClientboundCustomSoundPacket; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.core.Direction; -import net.minecraft.sounds.SoundSource; import net.minecraft.sounds.SoundEvent; -import net.minecraft.world.phys.AABB; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.Level; @@ -79,19 +74,7 @@ protected void setActive(boolean active){ } protected void playSoundEffect(double x, double y, double z, SoundEvent sound, float volume, float pitch){ - if(getLevel().isClientSide) return; - final double range = 16; - AABB bounds = new AABB( - x - range, y - range, z - range, - x + range, y + range, z + range); - // A world-wide entity query can load chunks just to broadcast a sound. - for(Player candidate : getLevel().players()){ - if(!(candidate instanceof ServerPlayer)) continue; - ServerPlayer player = (ServerPlayer)candidate; - if(player.connection == null || player.isSpectator() || !player.getBoundingBox().intersects(bounds)) continue; - player.connection.send(new ClientboundCustomSoundPacket(sound.getRegistryName(), SoundSource.BLOCKS, - new net.minecraft.world.phys.Vec3(x, y, z), volume, pitch)); - } + ElectricOperationalSound.broadcast(getLevel(), x, y, z, sound, volume, pitch); } public boolean isActive(){ diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricMachineTileEntity.java b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricMachineTileEntity.java index e1a5177..c6c9ac0 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricMachineTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricMachineTileEntity.java @@ -3,21 +3,15 @@ import zone.moddev.mc.baseelectric.init.Power; import cyano.poweradvantage.api.ConduitType; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.entity.player.Player; -import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.SimpleContainer; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Recipe; import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.protocol.game.ClientboundCustomSoundPacket; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.core.Direction; -import net.minecraft.sounds.SoundSource; import net.minecraft.sounds.SoundEvent; -import net.minecraft.world.phys.AABB; import net.minecraft.core.BlockPos; -import net.minecraft.network.chat.TextComponent; import net.minecraft.world.level.Level; import net.minecraft.world.item.crafting.RecipeType; import zone.moddev.mc.baseelectric.ElectricAdvantage; @@ -227,7 +221,7 @@ protected 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); } @Override @@ -347,19 +341,7 @@ public ItemStack insertItemToInputSlots(ItemStack itemStack){ } protected void playSoundEffect(double x, double y, double z, SoundEvent sound, float volume, float pitch){ - if(getLevel().isClientSide) return; - final double range = 16; - AABB bounds = new AABB( - x - range, y - range, z - range, - x + range, y + range, z + range); - // A world-wide entity query can load chunks just to broadcast a sound. - for(Player candidate : getLevel().players()){ - if(!(candidate instanceof ServerPlayer)) continue; - ServerPlayer player = (ServerPlayer)candidate; - if(player.connection == null || player.isSpectator() || !player.getBoundingBox().intersects(bounds)) continue; - player.connection.send(new ClientboundCustomSoundPacket(sound.getRegistryName(), SoundSource.BLOCKS, - new net.minecraft.world.phys.Vec3(x, y, z), volume, pitch)); - } + ElectricOperationalSound.broadcast(getLevel(), x, y, z, sound, volume, pitch); } public ItemStack insertItemToOutputSlots(ItemStack itemStack){ diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalSound.java b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalSound.java new file mode 100644 index 0000000..b702cbe --- /dev/null +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalSound.java @@ -0,0 +1,33 @@ +package zone.moddev.mc.baseelectric.machines; + +import java.util.concurrent.ThreadLocalRandom; + +import net.minecraft.core.Holder; +import net.minecraft.network.protocol.game.ClientboundSoundPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.AABB; + +final class ElectricOperationalSound { + private ElectricOperationalSound() {} + + static void broadcast(Level level, double x, double y, double z, SoundEvent sound, float volume, float pitch) { + if (level.isClientSide) return; + final double range = 16; + AABB bounds = new AABB(x - range, y - range, z - range, x + range, y + range, z + range); + ClientboundSoundPacket packet = null; + // Keep the historical recipient cube without a chunk-backed entity query. + for (Player candidate : level.players()) { + if (!(candidate instanceof ServerPlayer player) || player.connection == null + || player.isSpectator() || !player.getBoundingBox().intersects(bounds)) continue; + if (packet == null) { + packet = new ClientboundSoundPacket(Holder.direct(sound), SoundSource.BLOCKS, + x, y, z, volume, pitch, ThreadLocalRandom.current().nextLong()); + } + player.connection.send(packet); + } + } +} diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberControllerTileEntity.java b/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberControllerTileEntity.java index 7959bc9..4dd324e 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberControllerTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberControllerTileEntity.java @@ -16,13 +16,13 @@ import cyano.poweradvantage.api.fluid.FluidStack; import cyano.poweradvantage.api.fluid.FluidTank; import cyano.poweradvantage.api.fluid.FluidTankInfo; -import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; import net.minecraft.tags.TagKey; import net.minecraft.world.item.Item; import net.minecraft.resources.ResourceLocation; public class GrowthChamberControllerTileEntity extends cyano.poweradvantage.api.simple.TileEntitySimplePowerMachine implements LegacyFluidHandler{ - private static final TagKey DIRT_TAG = TagKey.create(Registry.ITEM_REGISTRY, new ResourceLocation("forge", "dirt")); + private static final TagKey DIRT_TAG = TagKey.create(Registries.ITEM, new ResourceLocation("forge", "dirt")); static final float ELECTRICITY_PER_UNIT = 8f; diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/LaserTurretTileEntity.java b/src/main/java/zone/moddev/mc/baseelectric/machines/LaserTurretTileEntity.java index c9af390..51162b2 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/LaserTurretTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/LaserTurretTileEntity.java @@ -86,7 +86,7 @@ public class LaserTurretTileEntity extends ElectricMachineTileEntity{ public LaserTurretTileEntity(net.minecraft.core.BlockPos pos, net.minecraft.world.level.block.state.BlockState state){ super(zone.moddev.mc.baseelectric.init.Entities.LASER_TURRET, pos, state, "tile.baseelectric.laser_turret.name",MAX_BUFFER,0,0,0); - SoundEvent soundEvent = ElectricAdvantage.INSTANCE == null ? SoundEvents.NOTE_BLOCK_BASS + SoundEvent soundEvent = ElectricAdvantage.INSTANCE == null ? SoundEvents.NOTE_BLOCK_BASS.value() : ForgeRegistries.SOUND_EVENTS.getValue(new ResourceLocation(ElectricAdvantage.INSTANCE.LASER_SOUND)); if(soundEvent == null){ LASER_SOUND = SoundEvents.ENDERMAN_TELEPORT; @@ -274,7 +274,7 @@ && rayIntersectsBoundingBox(origin,dir,v.getBoundingBox())){ private void hurtVictim(Entity e){ e.setSecondsOnFire(BURN_TIME); if(e instanceof LivingEntity){ - ((LivingEntity)e).hurt(Power.laser_damage, ATTACK_DAMAGE); + ((LivingEntity)e).hurt(Power.damageSource(getLevel(), Power.LASER_DAMAGE), ATTACK_DAMAGE); BlockPos bp = e.blockPosition(); if(getLevel().isEmptyBlock(bp) && !getLevel().isEmptyBlock(bp.below())){ getLevel().setBlockAndUpdate(bp, Blocks.FIRE.defaultBlockState()); diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/PlasticRefineryTileEntity.java b/src/main/java/zone/moddev/mc/baseelectric/machines/PlasticRefineryTileEntity.java index 59a072d..60108c5 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/PlasticRefineryTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/PlasticRefineryTileEntity.java @@ -18,16 +18,15 @@ import cyano.poweradvantage.api.fluid.FluidStack; import cyano.poweradvantage.api.fluid.FluidTank; import cyano.poweradvantage.api.fluid.FluidTankInfo; -import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; import net.minecraft.tags.TagKey; import net.minecraft.world.item.Item; import net.minecraft.resources.ResourceLocation; -import net.minecraft.core.Registry; import java.util.List; public class PlasticRefineryTileEntity extends ElectricMachineTileEntity implements LegacyFluidHandler{ - private static final TagKey PLASTICS_TAG = TagKey.create(Registry.ITEM_REGISTRY, new ResourceLocation("forge", "plastics")); + private static final TagKey PLASTICS_TAG = TagKey.create(Registries.ITEM, new ResourceLocation("forge", "plastics")); public static final float ENERGY_PER_TICK = 12f; diff --git a/src/main/java/zone/moddev/mc/baseelectric/recipe/RecipeConditions.java b/src/main/java/zone/moddev/mc/baseelectric/recipe/RecipeConditions.java index a50c854..391822b 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/recipe/RecipeConditions.java +++ b/src/main/java/zone/moddev/mc/baseelectric/recipe/RecipeConditions.java @@ -37,7 +37,7 @@ static final class ModeCondition implements ICondition { inverted ? "not_recipe_mode" : "recipe_mode"); } - @Override public boolean test() { return test(IContext.EMPTY); } + public boolean test() { return test(IContext.EMPTY); } @Override public boolean test(IContext context) { return (PowerAdvantage.recipeMode == mode) != inverted; diff --git a/src/main/java/zone/moddev/mc/baseelectric/util/crafting/ItemMatcher.java b/src/main/java/zone/moddev/mc/baseelectric/util/crafting/ItemMatcher.java index ab86722..e7a2565 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/util/crafting/ItemMatcher.java +++ b/src/main/java/zone/moddev/mc/baseelectric/util/crafting/ItemMatcher.java @@ -10,7 +10,8 @@ import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.core.Holder; -import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; import net.minecraft.tags.TagKey; import net.minecraft.resources.ResourceLocation; @@ -20,7 +21,7 @@ public final class ItemMatcher { public ItemMatcher(String legacyName) { this.choices = new ArrayList<>(); - for (Holder item : Registry.ITEM.getTagOrEmpty(TagKey.create(Registry.ITEM_REGISTRY, tagId(legacyName)))) { + for (Holder item : BuiltInRegistries.ITEM.getTagOrEmpty(TagKey.create(Registries.ITEM, tagId(legacyName)))) { choices.add(new ItemStack(item.value())); } } diff --git a/src/main/java/zone/moddev/mc/baseelectric/util/crafting/RecipeDeconstructor.java b/src/main/java/zone/moddev/mc/baseelectric/util/crafting/RecipeDeconstructor.java index b136089..70ffec2 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/util/crafting/RecipeDeconstructor.java +++ b/src/main/java/zone/moddev/mc/baseelectric/util/crafting/RecipeDeconstructor.java @@ -38,7 +38,7 @@ private void initializeRecipeCache(Level world) { if (cachedRecipeCount == recipes.size() && !recipeCache.isEmpty()) return; recipeCache.clear(); for (Recipe recipe : recipes) { - ItemStack output = recipe.getResultItem(); + ItemStack output = recipe.getResultItem(world.registryAccess()); if (output == null || output.isEmpty()) continue; recipeCache.computeIfAbsent(new ItemRecord(output), ignored -> new ArrayList<>()).add(recipe); } @@ -93,7 +93,7 @@ private SerializedInventory attemptToCraft(Level world, ItemStack target, } } if (!failed) { - output.set(recipe.getResultItem().copy()); + output.set(recipe.getResultItem(world.registryAccess()).copy()); return candidate; } } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 314a5d4..22c5d78 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -30,13 +30,13 @@ side="BOTH" [[dependencies.baseelectric]] 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.baseelectric]] 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..d413b81 --- /dev/null +++ b/src/main/resources/assets/minecraft/atlases/blocks.json @@ -0,0 +1,5 @@ +{ + "sources": [ + {"type":"single","resource":"baseelectric:entity/laser_turret"} + ] +} diff --git a/src/main/resources/data/baseelectric/damage_type/laser.json b/src/main/resources/data/baseelectric/damage_type/laser.json new file mode 100644 index 0000000..2bc0353 --- /dev/null +++ b/src/main/resources/data/baseelectric/damage_type/laser.json @@ -0,0 +1,5 @@ +{ + "message_id": "laser", + "scaling": "never", + "exhaustion": 0.1 +} diff --git a/src/main/resources/data/baseelectric/damage_type/propeller.json b/src/main/resources/data/baseelectric/damage_type/propeller.json new file mode 100644 index 0000000..ad1da40 --- /dev/null +++ b/src/main/resources/data/baseelectric/damage_type/propeller.json @@ -0,0 +1,5 @@ +{ + "message_id": "propeller", + "scaling": "never", + "exhaustion": 0.1 +} diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index 1a55219..f71b9ae 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":"baseElectric" } } diff --git a/src/test/java/zone/moddev/mc/baseelectric/entities/ElectricOperationalHydroturbineAnchorTest.java b/src/test/java/zone/moddev/mc/baseelectric/entities/ElectricOperationalHydroturbineAnchorTest.java index 5de01f3..a58ccca 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/entities/ElectricOperationalHydroturbineAnchorTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/entities/ElectricOperationalHydroturbineAnchorTest.java @@ -2,7 +2,7 @@ import java.lang.reflect.Field; import java.util.Collections; -import java.util.Random; +import net.minecraft.util.RandomSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.MobCategory; import net.minecraft.world.entity.EntityType; @@ -10,6 +10,7 @@ import net.minecraft.nbt.DoubleTag; import net.minecraft.nbt.ListTag; import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.world.phys.Vec3; import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import net.minecraft.world.level.Level; @@ -20,7 +21,9 @@ import static org.junit.Assert.*; public class ElectricOperationalHydroturbineAnchorTest { - @BeforeClass public static void bootstrap() { NativeTestBootstrap.initialize(); } + @BeforeClass public static void bootstrap() throws ReflectiveOperationException { + NativeTestBootstrap.registerVanillaFluidTypes(); + } @Test public void nativeEntityLoadStillClampsOverspeedMotionAndPreservesItsBoundary() throws Exception { ControlEntity control = new ControlEntity(controlType(), world()); @@ -92,13 +95,13 @@ private static Level world() throws Exception { .invoke(field.get(null), NoTickWorld.class); Field random = Level.class.getField("random"); random.setAccessible(true); - random.set(world, new Random(0)); + random.set(world, RandomSource.create(0)); return world; } private static final class NoTickWorld extends ServerLevel { private NoTickWorld() { - super(null, null, null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); + super(null, null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); } } @@ -107,6 +110,6 @@ private static final class ControlEntity extends Entity { @Override protected void defineSynchedData() {} @Override protected void readAdditionalSaveData(CompoundTag tag) {} @Override protected void addAdditionalSaveData(CompoundTag tag) {} - @Override public Packet getAddEntityPacket() { return null; } + @Override public Packet getAddEntityPacket() { return null; } } } diff --git a/src/test/java/zone/moddev/mc/baseelectric/graphics/RendererBufferContractTest.java b/src/test/java/zone/moddev/mc/baseelectric/graphics/RendererBufferContractTest.java index 36230b8..e15449a 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/graphics/RendererBufferContractTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/graphics/RendererBufferContractTest.java @@ -6,9 +6,9 @@ import java.util.ArrayList; import java.util.List; import net.minecraft.client.renderer.MultiBufferSource; -import com.mojang.math.Matrix4f; +import org.joml.Matrix4f; import net.minecraft.client.renderer.RenderType; -import com.mojang.math.Vector3f; +import com.mojang.math.Axis; import net.minecraft.core.Direction; import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; import org.junit.BeforeClass; @@ -31,7 +31,7 @@ public class RendererBufferContractTest { @Test public void quadNormalsUseWindingAndTheNativeNormalMatrix() { PoseStack matrices = new PoseStack(); matrices.translate(3, 4, 5); - matrices.mulPose(Vector3f.YP.rotationDegrees(90)); + matrices.mulPose(Axis.YP.rotationDegrees(90)); RecordingBuffer buffer = new RecordingBuffer(); BufferedQuads quads = new BufferedQuads(matrices, buffer, 0x123456, 0x789abc); quad(quads, false); @@ -59,7 +59,7 @@ public class RendererBufferContractTest { PoseStack matrices = new PoseStack(); matrices.translate(11, -5, 7); PoseStack.Pose before = matrices.last(); - Matrix4f matrix = before.pose().copy(); + Matrix4f matrix = new Matrix4f(before.pose()); RecordingBuffer buffer = new RecordingBuffer(); renderer.render(drill, 0.5F, matrices, buffer, 0, 17); assertSame(before, matrices.last()); @@ -100,7 +100,7 @@ public class RendererBufferContractTest { set(LaserTurretTileEntity.class, turret, "laserAttack", charge); PoseStack matrices = new PoseStack(); PoseStack.Pose before = matrices.last(); - Matrix4f matrix = before.pose().copy(); + Matrix4f matrix = new Matrix4f(before.pose()); RecordingBuffer buffer = new RecordingBuffer(); renderer.render(turret, 0.5F, matrices, buffer, 0x340012, 23); boolean beam = charge == 1 || charge == 10; diff --git a/src/test/java/zone/moddev/mc/baseelectric/init/VillagesTest.java b/src/test/java/zone/moddev/mc/baseelectric/init/VillagesTest.java index 649c8c7..5ca5156 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/init/VillagesTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/init/VillagesTest.java @@ -1,6 +1,7 @@ package zone.moddev.mc.baseelectric.init; -import java.util.Random; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.LegacyRandomSource; import net.minecraft.world.item.trading.MerchantOffer; import net.minecraft.nbt.CompoundTag; import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; @@ -28,7 +29,7 @@ public class VillagesTest { } private static void assertOffer(int minimum, int maximum, boolean upper, int emeralds, int goods) { - Random random = new Random(0L) { + RandomSource random = new LegacyRandomSource(0L) { @Override public int nextInt(int bound) { return upper ? bound - 1 : 0; } }; MerchantOffer offer = Villages.sell(net.minecraft.world.item.Items.APPLE, minimum, maximum) diff --git a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalNativeDefectsTest.java b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalNativeDefectsTest.java index beb7e4e..2278feb 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalNativeDefectsTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalNativeDefectsTest.java @@ -109,7 +109,7 @@ private static final class LightWorld extends ServerLevel { BlockPos surface; int brightness, skyReads, lightReads; private LightWorld() { - super(null, null, null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); + super(null, null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); } @Override public boolean canSeeSky(BlockPos pos) { assertEquals("Sky access must be sampled above the solid panel", surface, pos); @@ -127,7 +127,7 @@ private static final class BeamWorld extends ServerLevel { BlockPos turret; int traces; private BeamWorld() { - super(null, null, null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); + super(null, null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); } @Override public BlockHitResult clip(ClipContext context) { traces++; @@ -135,7 +135,7 @@ private BeamWorld() { context.getBlockShape(new LaserTurretBlock(false).defaultBlockState(), this, turret).isEmpty()); assertFalse("An immediately adjacent solid block must still stop the beam", context.getBlockShape(Blocks.STONE.defaultBlockState(), this, turret.east()).isEmpty()); - return BlockHitResult.miss(context.getTo(), Direction.EAST, new BlockPos(context.getTo())); + return BlockHitResult.miss(context.getTo(), Direction.EAST, BlockPos.containing(context.getTo())); } } diff --git a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalPumpPickupTest.java b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalPumpPickupTest.java index 56d4e5c..6db3037 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalPumpPickupTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalPumpPickupTest.java @@ -41,7 +41,13 @@ public class ElectricOperationalPumpPickupTest { private static final float RESERVE = 8000; private static final float PICKUP_COST = 1000 + 32; - @BeforeClass public static void bootstrap() { NativeTestBootstrap.initialize(); } + @BeforeClass public static void bootstrap() { + NativeTestBootstrap.initialize(); + // Forge normally initializes fluid caches after posting its registry events. + for (Block block : new Block[]{Blocks.WATER, Blocks.LAVA, Blocks.SEAGRASS, Blocks.OAK_SLAB, Blocks.OAK_STAIRS}) { + block.getStateDefinition().getPossibleStates().forEach(BlockState::initCache); + } + } @Test public void waterloggedSlabKeepsItsBlockAndSlabType() throws Exception { BlockState wet = Blocks.OAK_SLAB.defaultBlockState() @@ -174,7 +180,7 @@ private static final class PumpWorld extends ServerLevel { Map blocks; int writes; private PumpWorld() { - super(null, null, null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); + super(null, null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); } @Override public boolean hasChunkAt(BlockPos pos) { return true; } @Override public BlockState getBlockState(BlockPos pos) { @@ -195,6 +201,7 @@ private static final class RefusingSource extends Block implements BucketPickup RefusingSource(boolean returnNull) { super(Block.Properties.of(Material.STONE)); this.returnNull = returnNull; + defaultBlockState().initCache(); } @Override public FluidState getFluidState(BlockState state) { return Fluids.WATER.getSource(false); } @Override public net.minecraft.world.item.ItemStack pickupBlock(LevelAccessor world, BlockPos pos, BlockState state) { diff --git a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalRedstoneCacheTest.java b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalRedstoneCacheTest.java index e1e93aa..6c5e1ff 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalRedstoneCacheTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalRedstoneCacheTest.java @@ -204,7 +204,7 @@ private static final class SignalWorld extends ServerLevel { long time; List queryTimes; private SignalWorld() { - super(null, null, null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); + super(null, null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); } @Override public boolean hasNeighborSignal(BlockPos position) { assertEquals(this.position, position); diff --git a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalSoundBroadcastTest.java b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalSoundBroadcastTest.java index eacea42..bd5f8d7 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalSoundBroadcastTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalSoundBroadcastTest.java @@ -9,7 +9,7 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.network.protocol.Packet; import net.minecraft.server.network.ServerGamePacketListenerImpl; -import net.minecraft.network.protocol.game.ClientboundCustomSoundPacket; +import net.minecraft.network.protocol.game.ClientboundSoundPacket; import net.minecraft.sounds.SoundEvents; import net.minecraft.world.phys.AABB; import zone.moddev.mc.baseelectric.verification.NativeTestBootstrap; @@ -83,7 +83,16 @@ private static void assertRecipients(QueryRejectingWorld world) { if (player.connection == null) continue; RecordingConnection connection = (RecordingConnection)player.connection; assertEquals("Player " + i, i < 3 ? 1 : 0, connection.sent); - if (i < 3) assertTrue(connection.last instanceof ClientboundCustomSoundPacket); + if (i < 3) { + assertTrue(connection.last instanceof ClientboundSoundPacket); + ClientboundSoundPacket packet = (ClientboundSoundPacket) connection.last; + assertSame(SoundEvents.ANVIL_LAND, packet.getSound().value()); + assertEquals(100, packet.getX(), 0.0); + assertEquals(80, packet.getY(), 0.0); + assertEquals(-20, packet.getZ(), 0.0); + assertEquals(0.25f, packet.getVolume(), 0.0f); + assertEquals(0.75f, packet.getPitch(), 0.0f); + } } } @@ -99,7 +108,7 @@ private static final class QueryRejectingWorld extends ServerLevel { int playerListReads; private QueryRejectingWorld() { - super(null, null, null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); + super(null, null, null, null, null, null, null, false, 0L, Collections.emptyList(), false); } @Override public List players() { diff --git a/src/test/java/zone/moddev/mc/baseelectric/resources/BlockLootResourceTest.java b/src/test/java/zone/moddev/mc/baseelectric/resources/BlockLootResourceTest.java index 86e9d32..39dbfd9 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/resources/BlockLootResourceTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/resources/BlockLootResourceTest.java @@ -33,11 +33,10 @@ public class BlockLootResourceTest { assertFalse(Files.exists(root.resolve("pump_pipe_electric.json"))); } - @Test public void resourcePackDeclaresTheNativeOneEighteenFormats() throws Exception { + @Test public void resourcePackDeclaresTheNativeOneNineteenFourFormats() throws Exception { JsonObject pack = read(Paths.get("src/main/resources/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()); } private static JsonObject read(Path path) throws Exception { diff --git a/src/test/java/zone/moddev/mc/baseelectric/resources/BuildWorkflowContractTest.java b/src/test/java/zone/moddev/mc/baseelectric/resources/BuildWorkflowContractTest.java index f0aaead..d7dd98d 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/resources/BuildWorkflowContractTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/resources/BuildWorkflowContractTest.java @@ -16,7 +16,6 @@ public class BuildWorkflowContractTest { assertTrue(workflow + " missing pinned JDK " + version, pin >= 0 && pin < setup); } assertTrue(source.contains("-Porg.gradle.java.installations.paths=$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64")); - assertFalse(source.contains("JAVA_HOME_16_X64")); assertTrue(source.contains("-Porg.gradle.java.installations.auto-detect=false")); assertTrue(source.contains("-Porg.gradle.java.installations.auto-download=false")); assertFalse(source.contains("-Dorg.gradle.java.installations.")); @@ -26,6 +25,7 @@ public class BuildWorkflowContractTest { } } assertTrue(source.indexOf("- name: Use pinned build toolchains") < setup); + assertFalse(source.contains("JAVA_HOME_16_X64")); assertFalse(source.contains("distribution: microsoft")); } } @@ -34,35 +34,40 @@ public class BuildWorkflowContractTest { for (String workflow : new String[] {"ci.yml", "codeql-analysis.yml"}) { String source = new String(Files.readAllBytes(Paths.get(".github/workflows", workflow)), StandardCharsets.UTF_8); int policy = source.indexOf("git config --global core.autocrlf false"); - int pins = source.indexOf("- name: Require coordinated 1.18.2 dependency pins"); + int pins = source.indexOf("bash .github/scripts/forge45-dependencies.sh pins"); + int dependencyBuild = source.indexOf("bash .github/scripts/forge45-dependencies.sh build"); assertTrue(policy > 0); assertTrue(policy < pins); for (String repository : new String[] {"OreSpawn", "BaseMetals", "basePower"}) { assertTrue(pins < source.indexOf("repository: MinecraftModDevelopmentMods/" + repository)); } - assertTrue(source.contains("ref: d31f52f296739f34a0c13f554abf3e1ac071ae25")); - assertTrue(source.contains("BaseMetals-3.1.2.118021-deobf.jar")); - assertTrue(source.contains("basePower-2.4.10.118021-deobf.jar")); + assertTrue(source.contains("ref: ${{ steps.pins.outputs.orespawn_ref }}")); assertTrue(source.contains("ref: ${{ steps.pins.outputs.basemetals_ref }}")); assertTrue(source.contains("ref: ${{ steps.pins.outputs.power_ref }}")); - assertTrue(source.contains("\"$pin_state\" != \"PUBLISHED_RELEASE\"")); - assertTrue(source.contains("! \"$power_ref\" =~ ^[a-f0-9]{40}$")); - assertTrue(source.contains("! \"$bm_ref\" =~ ^[a-f0-9]{40}$")); - for (String hash : new String[] {"power_sha", "bm_sha", "bootstrap_sha"}) { - assertTrue(source.contains("! \"$" + hash + "\" =~ ^[A-F0-9]{64}$")); - } - assertTrue(source.contains("git -C \"$power\" rev-parse HEAD")); - assertTrue(source.contains("git -C \"$basemetals\" rev-parse HEAD")); - assertFalse(source.contains("5b9abe578b053d2ef59b1962cf9e04f6880c91b9")); - assertFalse(source.contains("799B59B06EB2AC3FC4682AA64BF42C3A2A2C1D89D7FFFE9C72745DEC7DFBE0D8")); - assertFalse(source.contains("dacfaa3af2e3310d5cb6b081da79f09260ae103d")); - assertFalse(source.contains("098d4625abc8a849bd1a11dcf697b3fee0cade7b")); - assertFalse(source.contains("78E58DD2AB04E2B9F3267E5735C64AAE7E93EAC97571CB04485038862C853ED3")); - assertFalse(source.contains("ce90c24c302d0ff3565db2db0c6c94a3b5f21f83")); - assertFalse(source.contains("8BBB03DD7E4AF08A7390A161ED5AA94E292BEE56EF60E550B5512E967E0E9174")); - assertTrue(source.contains("branches: [master-1.18.2")); - assertTrue(source.contains("-PmavenizerCompatibilityJar=${{ steps.mavenizer.outputs.fixture }}")); - assertFalse(source.contains("master-1.14.4")); + assertTrue(dependencyBuild > source.indexOf("ref: ${{ steps.pins.outputs.power_ref }}")); + assertTrue(source.indexOf("\"-Pforge45MavenizerJar=$MAVENIZER\"") > dependencyBuild); + assertTrue(source.contains("MAVENIZER: ${{ steps.dependencies.outputs.mavenizer }}")); + assertTrue(source.contains("branches: [master-1.19.4")); + assertFalse(source.contains("1.17.1")); + } + String script = Files.readString(Paths.get(".github/scripts/forge45-dependencies.sh")); + assertTrue(script.contains("$(value poweradvantage_pin_state)") && script.contains("'PUBLISHED_RELEASE'")); + assertTrue(script.contains("=~ ^[a-f0-9]{40}$") && script.contains("=~ ^[A-F0-9]{64}$")); + for (String dependency : new String[] {"orespawn", "basemetals", "power"}) { + assertTrue(script.contains("git -C \"$" + dependency + "\" rev-parse HEAD")); } + assertTrue(script.contains("$orespawn/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar")); + assertTrue(script.contains("check_hash \"$mavenizer\" '341B3FB10ABA6EA7DC612FBE519DC290A1C81867AED5DD320ACD783688AC49DE'")); + assertFalse(script.contains("forge45-mavenizer.init.gradle") || script.contains("-Pforge45MavenizerScript")); + int metalsBuild = script.indexOf("(cd \"$basemetals\" && ./gradlew"); + int powerBuild = script.indexOf("(cd \"$power\" && ./gradlew"); + assertTrue(metalsBuild > 0 && powerBuild > metalsBuild); + String metals = script.substring(metalsBuild, powerBuild); + assertFalse(metals.contains("-I ") || metals.contains("-Pforge45Mavenizer")); + String power = script.substring(powerBuild); + assertFalse(power.contains("-I ") || power.contains("--init-script")); + assertTrue(power.contains("\"-Pforge45MavenizerJar=$mavenizer\"")); + assertTrue(script.contains("git -C \"$basemetals\" diff --exit-code HEAD --")); + assertTrue(script.contains("git -C \"$power\" diff --exit-code HEAD --")); } } diff --git a/src/test/java/zone/moddev/mc/baseelectric/resources/RecipeUnlockContractTest.java b/src/test/java/zone/moddev/mc/baseelectric/resources/RecipeUnlockContractTest.java index e364c65..31ce615 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/resources/RecipeUnlockContractTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/resources/RecipeUnlockContractTest.java @@ -143,7 +143,7 @@ private static String ingredientKey(JsonObject predicate) { private static String unlockPredicateKey(JsonObject predicate) { assertEquals("Unlock predicates must constrain exactly one item or tag", 1, predicate.size()); - assertFalse("Native 1.18 ignores the singular item key in advancement predicates", predicate.has("item")); + assertFalse("The native parser ignores the singular item key in advancement predicates", predicate.has("item")); if (predicate.has("tag")) { assertTrue("A tag predicate must be a string", predicate.get("tag").isJsonPrimitive() && predicate.getAsJsonPrimitive("tag").isString()); diff --git a/src/test/java/zone/moddev/mc/baseelectric/verification/NativeTestBootstrap.java b/src/test/java/zone/moddev/mc/baseelectric/verification/NativeTestBootstrap.java index ab41388..600b1da 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/verification/NativeTestBootstrap.java +++ b/src/test/java/zone/moddev/mc/baseelectric/verification/NativeTestBootstrap.java @@ -1,8 +1,19 @@ package zone.moddev.mc.baseelectric.verification; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import net.minecraft.SharedConstants; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; 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; /** Opens the native Forge registration phase for worldless test fixtures. */ public final class NativeTestBootstrap { @@ -18,4 +29,30 @@ public static synchronized void initialize() { GameData.unfreezeData(); ready = true; } + + public static synchronized void registerVanillaFluidTypes() throws ReflectiveOperationException { + initialize(); + if (ForgeRegistries.FLUID_TYPES.get() != null) return; + + // Run the same narrow Forge registry phases as Power's worldless entity fixtures. + NewRegistryEvent event = new NewRegistryEvent(); + Field types = ForgeRegistries.class.getDeclaredField("DEFERRED_FLUID_TYPES"); + types.setAccessible(true); + Method create = DeferredRegister.class.getDeclaredMethod("createRegistry", NewRegistryEvent.class); + create.setAccessible(true); + create.invoke(types.get(null), event); + Method fill = NewRegistryEvent.class.getDeclaredMethod("fill"); + fill.setAccessible(true); + fill.invoke(event); + + Field vanilla = ForgeMod.class.getDeclaredField("VANILLA_FLUID_TYPES"); + vanilla.setAccessible(true); + Method register = DeferredRegister.class.getDeclaredMethod("addEntries", RegisterEvent.class); + register.setAccessible(true); + Constructor registerEvent = RegisterEvent.class.getDeclaredConstructor( + ResourceKey.class, ForgeRegistry.class, Registry.class); + registerEvent.setAccessible(true); + register.invoke(vanilla.get(null), registerEvent.newInstance( + ForgeRegistries.Keys.FLUID_TYPES, ForgeRegistries.FLUID_TYPES.get(), null)); + } } diff --git a/tools/verification/ForgeNativeTestBootstrap.java b/tools/verification/ForgeNativeTestBootstrap.java deleted file mode 100644 index e4bd9c3..0000000 --- a/tools/verification/ForgeNativeTestBootstrap.java +++ /dev/null @@ -1,43 +0,0 @@ -package zone.moddev.mc.baseelectric.verification; - -import java.lang.instrument.ClassFileTransformer; -import java.lang.instrument.Instrumentation; -import java.security.ProtectionDomain; - -/** Applies Forge's real event-bus plugin for native tier/harvest tests on a plain JVM. */ -public final class ForgeNativeTestBootstrap { - private ForgeNativeTestBootstrap() {} - - public static void premain(String options, Instrumentation instrumentation) { - zone.moddev.mc.advantageworks.unit.ForgeCapabilityBootstrap.premain(options, instrumentation); - instrumentation.addTransformer(new ClassFileTransformer() { - @Override public byte[] transform(ClassLoader loader, String name, Class type, - ProtectionDomain domain, byte[] bytes) { - if (loader == null || name == null || !(name.equals("net/minecraftforge/network/NetworkEvent") - || name.startsWith("net/minecraftforge/network/NetworkEvent$"))) return null; - try { - Class readerType = Class.forName("org.objectweb.asm.ClassReader", true, loader); - Object reader = readerType.getConstructor(byte[].class).newInstance((Object) bytes); - Class nodeType = Class.forName("org.objectweb.asm.tree.ClassNode", true, loader); - Object node = nodeType.getConstructor().newInstance(); - Class visitorType = Class.forName("org.objectweb.asm.ClassVisitor", true, loader); - readerType.getMethod("accept", visitorType, int.class).invoke(reader, node, 0); - Class asmType = Class.forName("org.objectweb.asm.Type", true, loader); - Object classType = asmType.getMethod("getObjectType", String.class).invoke(null, name); - Class phaseType = Class.forName("cpw.mods.modlauncher.serviceapi.ILaunchPluginService$Phase", true, loader); - Object phase = phaseType.getField("AFTER").get(null); - Class pluginType = Class.forName("net.minecraftforge.eventbus.service.ModLauncherService", true, loader); - int flags = (Integer) pluginType.getMethod("processClassWithFlags", phaseType, nodeType, asmType, String.class) - .invoke(pluginType.getConstructor().newInstance(), phase, node, classType, "classloading"); - if (flags == 0) throw new IllegalStateException("Native event-bus plugin did not transform " + name); - Class writerType = Class.forName("org.objectweb.asm.ClassWriter", true, loader); - Object writer = writerType.getConstructor(int.class).newInstance(flags & 3); - nodeType.getMethod("accept", visitorType).invoke(node, writer); - return (byte[]) writerType.getMethod("toByteArray").invoke(writer); - } catch (ReflectiveOperationException failure) { - throw new IllegalStateException("Cannot run Forge's native event-bus transform for " + name, failure); - } - } - }); - } -} diff --git a/tools/verification/Invoke-ElectricPackagedSmoke.ps1 b/tools/verification/Invoke-ElectricPackagedSmoke.ps1 index 447bd82..03815b6 100644 --- a/tools/verification/Invoke-ElectricPackagedSmoke.ps1 +++ b/tools/verification/Invoke-ElectricPackagedSmoke.ps1 @@ -1,3 +1,4 @@ +[CmdletBinding(DefaultParameterSetName = 'WithBaseMetals')] param( [Parameter(Mandatory)][string]$Evidence, [Parameter(Mandatory)][string]$ServerSeed, @@ -7,7 +8,8 @@ param( [Parameter(Mandatory)][ValidatePattern('^[A-F0-9]{64}$')][string]$ElectricSha256, [Parameter(Mandatory)][string]$PowerJar, [Parameter(Mandatory)][ValidatePattern('^[A-F0-9]{64}$')][string]$PowerSha256, - [Parameter(Mandatory)][string]$BaseMetalsJar, + [Parameter(Mandatory, ParameterSetName = 'WithBaseMetals')][string]$BaseMetalsJar, + [Parameter(Mandatory, ParameterSetName = 'WithoutBaseMetals')][switch]$WithoutBaseMetals, [Parameter(Mandatory)][string]$OreSpawnJar ) @@ -24,9 +26,12 @@ $null = New-Item -ItemType Directory -Path $output $pins = ConvertFrom-StringData (Get-Content -LiteralPath (Join-Path $root 'gradle.properties') -Raw) $seed = (Resolve-Path -LiteralPath $ServerSeed).Path $javaPath = (Resolve-Path -LiteralPath $Java).Path -$winArgs = 'libraries/net/minecraftforge/forge/1.18.2-40.3.0/win_args.txt' +$target = "$($pins.minecraft_version)-$($pins.forge_version)" +$forgeDir = "libraries/net/minecraftforge/forge/$target" +$winArgs = "$forgeDir/win_args.txt" $receipt = [ordered]@{ - status = 'FAIL'; scope = 'Packaged, loopback-only Forge 1.18.2 dedicated server smoke; no client or migration proof' + status = 'FAIL'; scope = 'Packaged, loopback-only Forge 1.19.4 dedicated server smoke; no client or migration proof' + baseMetalsIncluded = !$WithoutBaseMetals seed = $seed; java = $javaPath; inputs = @(); checks = [ordered]@{}; exitCode = $null; error = $null } $process = $null @@ -43,10 +48,10 @@ function Wait-ForLine([string]$Pattern, [int]$Seconds) { $line = $read.Result if ($null -eq $line) { throw "Server output ended before $Pattern" } $stdout.WriteLine($line) - if ($line -match $Pattern) { return $line } - if ($line -match 'Failed to start the minecraft server|Failed to complete lifecycle event|Failed to load datapacks') { - throw "Server reported a startup failure: $line" + if ($line -match '/(?:ERROR|FATAL)\]|Failed to start the minecraft server|Failed to complete lifecycle event|Failed to load datapacks') { + throw "Server reported an error: $line" } + if ($line -match $Pattern) { return $line } } throw "Timed out waiting for $Pattern" } @@ -58,6 +63,9 @@ function Send-Command([string]$Command) { } try { + if ($target -cne '1.19.4-45.4.0' -or $pins.mod_version -cne '2.2.12.119041') { + throw 'This smoke runner is bound to baseElectric 2.2.12.119041 on Forge 45.4.0.' + } if ($pins.poweradvantage_pin_state -ne 'PUBLISHED_RELEASE' -or $PowerSha256 -ne $pins.poweradvantage_runtime_sha256) { throw 'Packaged smoke requires the exact source-pinned basePower player JAR.' } @@ -69,12 +77,12 @@ try { $receipt.javaRelease = (Get-FileHash -LiteralPath $javaRelease).Hash $receipt.javaSha256 = (Get-FileHash -LiteralPath $javaPath).Hash $argsText = Get-Content -LiteralPath (Join-Path $seed $winArgs) -Raw - foreach ($marker in @('--launchTarget forgeserver', '--fml.forgeVersion 40.3.0', - '--fml.mcVersion 1.18.2', '--fml.mcpVersion 20220404.173914')) { + foreach ($marker in @('--launchTarget forgeserver', "--fml.forgeVersion $($pins.forge_version)", + "--fml.mcVersion $($pins.minecraft_version)", "--fml.mcpVersion $($pins.mcp_version)")) { if (!$argsText.Contains($marker)) { throw "Unexpected Forge seed arguments: missing $marker" } } $receipt.seedArgumentsSha256 = (Get-FileHash -LiteralPath (Join-Path $seed $winArgs)).Hash - $forgeUniversal = Join-Path $seed 'libraries/net/minecraftforge/forge/1.18.2-40.3.0/forge-1.18.2-40.3.0-universal.jar' + $forgeUniversal = Join-Path $seed "$forgeDir/forge-$target-universal.jar" $receipt.forgeUniversalSha256 = (Get-FileHash -LiteralPath $forgeUniversal).Hash if ((Get-Content -LiteralPath $EulaFile -Raw) -notmatch '(?m)^eula=true\s*$') { throw 'Pass the existing accepted EULA from the local smoke fixture.' @@ -82,11 +90,15 @@ try { Copy-Item -LiteralPath (Join-Path $seed 'libraries') -Destination $output -Recurse Copy-Item -LiteralPath $EulaFile -Destination (Join-Path $output 'eula.txt') $modDir = (New-Item -ItemType Directory -Path (Join-Path $output 'mods')).FullName - foreach ($input in @( - @{ path = $ElectricJar; hash = $ElectricSha256; name = 'baseElectric-2.2.11.118021.jar' }, - @{ path = $PowerJar; hash = $PowerSha256; name = 'basePower-2.4.10.118021.jar' }, - @{ path = $BaseMetalsJar; hash = $pins.basemetals_runtime_sha256; name = 'BaseMetals-3.1.2.118021.jar' }, - @{ path = $OreSpawnJar; hash = $pins.orespawn_runtime_sha256; name = 'OreSpawn-4.1.0.118021.jar' })) { + $modInputs = @( + @{ path = $ElectricJar; hash = $ElectricSha256; name = "baseElectric-$($pins.mod_version).jar" }, + @{ path = $PowerJar; hash = $PowerSha256; name = "basePower-$($pins.poweradvantage_version).jar" }, + @{ path = $OreSpawnJar; hash = $pins.orespawn_runtime_sha256; name = "OreSpawn-$($pins.orespawn_version).jar" } + ) + if (!$WithoutBaseMetals) { + $modInputs += @{ path = $BaseMetalsJar; hash = $pins.basemetals_runtime_sha256; name = "BaseMetals-$($pins.basemetals_version).jar" } + } + foreach ($input in $modInputs) { $actual = Get-FileHash -LiteralPath $input.path if ($actual.Hash -ne $input.hash -or [IO.Path]::GetFileName($actual.Path) -cne $input.name) { throw "Wrong packaged input: $($actual.Path) SHA-256 $($actual.Hash)" @@ -98,7 +110,7 @@ try { } [IO.File]::WriteAllLines((Join-Path $output 'server.properties'), @( 'server-ip=127.0.0.1', 'server-port=0', 'enable-rcon=false', 'enable-query=false', - 'level-name=electric-smoke', 'level-seed=118021', 'generate-structures=false', + 'level-name=electric-smoke', 'level-seed=119041', 'generate-structures=false', 'view-distance=3', 'simulation-distance=3', 'spawn-protection=0' )) $start = [Diagnostics.ProcessStartInfo]::new($javaPath) @@ -115,7 +127,7 @@ try { $stdout.AutoFlush = $true $process = [Diagnostics.Process]::Start($start) $stderr = $process.StandardError.ReadToEndAsync() - $receipt.checks.commonSetup = Wait-ForLine 'baseElectric 2\.2\.11\.118021 common setup complete' 120 + $receipt.checks.commonSetup = Wait-ForLine "baseElectric $([regex]::Escape($pins.mod_version)) common setup complete" 120 $receipt.checks.ready = Wait-ForLine 'Done \([^)]+\)! For help' 180 Send-Command 'forceload add 0 0' @@ -131,21 +143,46 @@ try { $receipt.checks.placeDrill = Wait-ForLine 'Changed the block at 0, 300, 0' 20 Send-Command 'data get block 0 300 0' $receipt.checks.drillNbt = Wait-ForLine 'block data:.*baseelectric:electric_drill' 20 + foreach ($key in @('Energy', 'Items', 'progress')) { + if ($receipt.checks.drillNbt -cnotmatch "[{,]\s*${key}:") { + throw "Drill did not serialize the expected $key tag." + } + } Send-Command 'setblock 1 300 0 baseelectric:sulfur_ore' $receipt.checks.placeSulfur = Wait-ForLine 'Changed the block at 1, 300, 0' 20 - Send-Command 'loot spawn 1 300 0 mine 1 300 0 minecraft:wooden_pickaxe' + Send-Command 'setblock 2 299 0 minecraft:stone' + $receipt.checks.sulfurLanding = Wait-ForLine 'Changed the block at 2, 299, 0' 20 + Send-Command 'loot spawn 2 301 0 mine 1 300 0 minecraft:wooden_pickaxe' $receipt.checks.sulfurLoot = Wait-ForLine 'Dropped [1-4] \[Powdered Sulfur\] from loot table baseelectric:blocks/sulfur_ore' 20 - Send-Command 'data get entity @e[type=minecraft:item,sort=nearest,limit=1,x=1,y=300,z=0,distance=..5] Item' - $receipt.checks.sulfurItem = Wait-ForLine 'entity data:.*baseelectric:sulfur_powder' 20 + for ($attempt = 0; $attempt -lt 5; $attempt++) { + Send-Command 'data get entity @e[type=minecraft:item,sort=nearest,limit=1,x=2,y=301,z=0,distance=..5] Item' + $result = Wait-ForLine 'entity data:|No entity was found' 10 + if ($result -match 'entity data:') { $receipt.checks.sulfurItem = $result; break } + Start-Sleep -Milliseconds 200 + } $expectedCount = [regex]::Match($receipt.checks.sulfurLoot, 'Dropped ([1-4]) ').Groups[1].Value - if ($receipt.checks.sulfurItem -notmatch "Count: ${expectedCount}b") { - throw 'The live item stack count does not match the sulfur loot response.' + if (!$receipt.checks.Contains('sulfurItem') -or $receipt.checks.sulfurItem -notmatch 'baseelectric:sulfur_powder' -or + $receipt.checks.sulfurItem -notmatch "Count: ${expectedCount}b") { + throw 'No live sulfur item stack matched the sulfur loot response.' + } + foreach ($type in @('laser', 'propeller')) { + $tag = "electric_${type}_probe" + Send-Command ('summon minecraft:pig 0 301 0 {NoAI:1b,NoGravity:1b,Tags:["' + $tag + '"]}') + $receipt.checks["${type}Target"] = Wait-ForLine 'Summoned new Pig' 20 + $probe = "@e[type=minecraft:pig,tag=$tag,limit=1]" + Send-Command "damage $probe 1 baseelectric:$type" + $receipt.checks[$type] = Wait-ForLine 'Applied 1(\.0)? damage to Pig' 20 } Send-Command 'save-all flush' $receipt.checks.save = Wait-ForLine 'Saved the game' 30 Send-Command 'stop' $receipt.checks.shutdown = Wait-ForLine 'All dimensions are saved' 45 if (!$process.WaitForExit(30000) -or $process.ExitCode -ne 0) { throw 'Server did not stop cleanly.' } + foreach ($input in $receipt.inputs) { + if ((Get-FileHash -LiteralPath $input.copy).Hash -cne $input.sha256) { + throw "Runtime input changed: $($input.copy)" + } + } $receipt.status = 'PASS' } catch { $receipt.error = $_.ToString() diff --git a/tools/verification/Verify-ElectricEcj.ps1 b/tools/verification/Verify-ElectricEcj.ps1 index 030848c..90d8294 100644 --- a/tools/verification/Verify-ElectricEcj.ps1 +++ b/tools/verification/Verify-ElectricEcj.ps1 @@ -61,30 +61,35 @@ function Invoke-Ecj([string]$Name, [string[]]$Sources, [string[]]$Libraries) { } $report = [ordered]@{ status = 'RUNNING'; project = $ProjectRoot; evidence = $Evidence - scope = 'Pinned local baseElectric 1.18.2 candidate; fresh ECJ main/test/helper outputs and worldless Java 17 JUnit; no Gradle or Minecraft launch' + scope = 'Pinned local baseElectric 1.19.4 candidate; fresh ECJ main/test/helper outputs and worldless Java 17 JUnit; no Gradle or Minecraft launch' compiler = $null; toolchains = $null; libraries = @(); sources = @(); gradleInventory = @() compilations = [ordered]@{}; junit = $null; negativeControl = $null; apiPinState = $null; capabilityBootstrap = $null } try { $properties = ConvertFrom-StringData -StringData (Get-Content -LiteralPath (Join-Path $ProjectRoot 'gradle.properties') -Raw) $report.apiPinState = $properties.poweradvantage_pin_state - if ($properties.mod_id -ne 'baseelectric' -or $properties.mod_version -ne '2.2.11.118021' -or - $properties.poweradvantage_version -ne '2.4.10.118021' -or $properties.basemetals_version -ne '3.1.2.118021' -or - $properties.orespawn_version -ne '4.1.0.118021' -or + if ($properties.mod_id -ne 'baseelectric' -or $properties.mod_version -ne '2.2.12.119041' -or + $properties.minecraft_version -ne '1.19.4' -or $properties.forge_version -ne '45.4.0' -or + $properties.poweradvantage_version -ne '2.4.11.119041' -or $properties.basemetals_version -ne '3.1.2.119041' -or + $properties.orespawn_version -ne '4.1.0.119041' -or $properties.poweradvantage_pin_state -ne 'PUBLISHED_RELEASE' -or $properties.poweradvantage_source_ref -cnotmatch '^[a-f0-9]{40}$' -or $properties.poweradvantage_runtime_sha256 -cnotmatch '^[A-F0-9]{64}$' -or $properties.basemetals_source_commit -cnotmatch '^[a-f0-9]{40}$' -or $properties.poweradvantage_deobf_sha256 -cnotmatch '^[A-F0-9]{64}$' -or $properties.poweradvantage_unit_bootstrap_sha256 -cnotmatch '^[A-F0-9]{64}$' -or + $properties.poweradvantage_unit_bootstrap_jar_sha256 -cnotmatch '^[A-F0-9]{64}$' -or $properties.basemetals_deobf_sha256 -cnotmatch '^[A-F0-9]{64}$' -or $properties.orespawn_deobf_sha256 -cnotmatch '^[A-F0-9]{64}$') { - throw 'This verifier requires the source-pinned baseElectric 1.18.2 inputs' + throw 'This verifier requires the source-pinned baseElectric 1.19.4 inputs' } $report.compiler = Get-Pin $Compiler if ($report.compiler.sha256 -ne 'D0D43F8E2D7003E5EFED612E2CBB5F01870043397D8F1BBE536FD9128F4FCBF7') { throw 'ECJ compiler pin mismatch' } $java17 = Join-Path $Java17Home 'bin/java.exe' $capabilityBootstrap = Join-Path $ProjectRoot 'build/unit-bootstrap/forge-native-unit-bootstrap.jar' $report.capabilityBootstrap = Get-Pin $capabilityBootstrap + if ($report.capabilityBootstrap.sha256 -cne $properties.poweradvantage_unit_bootstrap_jar_sha256) { + throw 'The Eclipse-compiler run requires the same pinned basePower native unit agent as Gradle' + } $report.toolchains = [ordered]@{ java = (Get-Pin $java17) java17Release = (Get-Pin (Join-Path $Java17Home 'release')) systemModules = (Get-Pin (Join-Path $Java17Home 'lib/modules')) } @@ -139,7 +144,7 @@ try { $expected += [int]$inventory.testsuite.tests $report.gradleInventory += Get-Pin $xml.FullName } - if ($classes.Count -ne 27 -or $expected -ne 96 -or $sources.test.Count -ne 28 -or $sources.main.Count -ne 85) { throw 'Unexpected Electric 1.18.2 source/test inventory' } + if ($classes.Count -ne 27 -or $expected -ne 96 -or $sources.test.Count -ne 28 -or $sources.main.Count -ne 86) { throw 'Unexpected Electric 1.19.4 source/test inventory' } $main = Invoke-Ecj 'main' $sources.main $mainLibraries $test = Invoke-Ecj 'test' $sources.test (@($main) + $testLibraries) $helperSource = Join-Path $PSScriptRoot 'AuditedJUnit.java' diff --git a/tools/verify-native-port.js b/tools/verify-native-port.js index 7cfce78..400f6bb 100644 --- a/tools/verify-native-port.js +++ b/tools/verify-native-port.js @@ -7,7 +7,7 @@ const { createHash } = require('node:crypto'); const root = path.resolve(__dirname, '..'); const baseline = 'c5c8c9ea1d6f2010b8cff61e4bfae86f4c96c978'; const nativeBaseline = 'ce6b075f1d0e417e46e1742b9a2ca856b24dc67f'; -const portBase = 'cf3d4b0e2d1591b2ae8e5d638002ca016e46eac4'; +const portBase = '4914e5c8a0d8d878f0938b3a9dc59d9f35f7c85e'; const oldJavaRoot = 'src/main/java/zone/moddev/mc/electricadvantage/'; const javaRoot = 'src/main/java/zone/moddev/mc/baseelectric/'; const migratePath = name => name.startsWith('src/test/resources/') @@ -34,9 +34,9 @@ function tokens(source) { return source.match(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\/\/[^\n]*|\/\*[\s\S]*?\*\/|[A-Za-z_$][A-Za-z0-9_$]*|(?:0[xX][\da-fA-F_]+|(?:\d[\d_]*\.?[\d_]*|\.\d[\d_]*)(?:[eE][+-]?[\d_]+)?)[dDfFlL]?/g) || []; } const strings = source => tokens(source).filter(t => t.startsWith('"') - && !['"2.2.8.116051"', '"2.2.9.117011"', '"2.2.10.117011"', '"2.2.11.118021"'].includes(t)).sort(); + && !['"2.2.11.118021"', '"2.2.12.119041"'].includes(t)).sort(); const numbers = source => tokens(source).filter(t => /^\d|^\.\d/.test(t)); -const production = tracked.filter(n => n.startsWith(oldJavaRoot) && n.endsWith('.java')); +const production = portBaseFiles.filter(n => n.startsWith(javaRoot) && n.endsWith('.java')); const entry = javaRoot + 'ElectricAdvantage.java'; const migration = javaRoot + 'migration/ElectricLegacyMigrations.java'; @@ -64,58 +64,46 @@ function assertNativeUnlock(source, actual) { return expected.converted; } -check('Exact native 1.18.2 build target', () => { - for (const [key, value] of Object.entries({minecraft_version:'1.18.2', forge_version:'40.3.0', - mapping_channel:'official', mapping_version:'1.18.2', mcp_version:'20220404.173914', - mod_id:'baseelectric', mod_name:'baseElectric', mod_version:'2.2.11.118021', +check('Exact native 1.19.4 build target', () => { + for (const [key, value] of Object.entries({minecraft_version:'1.19.4', forge_version:'45.4.0', + mapping_channel:'official', mapping_version:'1.19.4', mcp_version:'20230314.122934', + mod_id:'baseelectric', mod_name:'baseElectric', mod_version:'2.2.12.119041', mod_group:'zone.moddev.mc.baseelectric', curseforge_project_id:'1783006', cf_requirements:'basepower,base-metals', java_version:'17', java_toolchain_version:'17.0.1+12', gradle_java_version:'17', - poweradvantage_version:'2.4.10.118021', basemetals_version:'3.1.2.118021', orespawn_version:'4.1.0.118021'})) { + poweradvantage_version:'2.4.11.119041', basemetals_version:'3.1.2.119041', orespawn_version:'4.1.0.119041'})) { assert.equal(properties[key], value, key); } assert.deepEqual(JSON.parse(read('src/main/resources/pack.mcmeta')).pack, - {pack_format:9, 'forge:resource_pack_format':8, 'forge:data_pack_format':9, description:'baseElectric'}); + {pack_format:13, 'forge:server_data_pack_format':12, description:'baseElectric'}); assert(read('build.gradle').includes('major != 61')); const metadata = read('src/main/resources/META-INF/mods.toml'); - assert.match(metadata, /modId="basepower"\s+mandatory=true\s+versionRange="\[2\.4\.10\.118021,3\.0\.0\)"/); - assert.match(metadata, /modId="basemetals"\s+mandatory=false\s+versionRange="\[3\.1\.2\.118021,4\.0\.0\)"/); + assert.match(metadata, /modId="basepower"\s+mandatory=true\s+versionRange="\[2\.4\.11\.119041,3\.0\.0\)"/); + assert.match(metadata, /modId="basemetals"\s+mandatory=false\s+versionRange="\[3\.1\.2\.119041,4\.0\.0\)"/); assert(metadata.includes('modId="baseelectric"') && metadata.includes('displayName="baseElectric"')); }); -check('The exact published basePower, Base Metals and OreSpawn pins are retained', () => { +check('Published Power and companion pins are exact', () => { assert.equal(properties.poweradvantage_source_repository, 'MinecraftModDevelopmentMods/basePower'); - assert.equal(properties.poweradvantage_pin_state, 'PUBLISHED_RELEASE'); const build = read('build.gradle'); assert.match(build, /poweradvantage_pin_state != 'PUBLISHED_RELEASE'/); - assert.match(build, /requirePowerPin\(\)\s+pinnedFile\('powerAdvantageDeobfJar', project.poweradvantage_deobf_sha256\)/); - assert(!build.includes('PA117') && !build.includes('117011-deobf.jar')); + assert.match(build, /pinnedFile\('powerAdvantageDeobfJar', project.poweradvantage_deobf_sha256\)/); + assert(!build.includes('118021') && !build.includes('117011')); + assert.equal(properties.poweradvantage_pin_state, 'PUBLISHED_RELEASE'); for (const [key, value] of Object.entries({ - poweradvantage_source_ref:'bcf5105ef5c10ba36c26c8ff941742fecc3e11a1', - poweradvantage_deobf_sha256:'767BABC0D6E1536DCB937B9F1DC565AF348B3CAF92D3207671EF1E1C8D8EF5D4', - poweradvantage_runtime_sha256:'8CA46D5BCBFFDDAEB4F40137B5BF71E5AF7DD36515E5BFEA751FC5C02249823B', - poweradvantage_unit_bootstrap_sha256:'025F16FCA9FD5CFC585931E7C8BCFAB22FC1DF933BE7DAAD490B8E4FC824B76E', - basemetals_source_commit:'3488ed58403722ac3ff203cff7680dfc16e0fd3f', - basemetals_deobf_sha256:'E4F9E2CDE355275218C79954D3792D000615790C7CBAE5FCEC59636A8C4CA57D', - basemetals_runtime_sha256:'5595C2CE7ADCDF1599E4327F1130121148A5CACD928CBC2190312D61F7CA37A9', - orespawn_source_commit:'d31f52f296739f34a0c13f554abf3e1ac071ae25', - orespawn_deobf_sha256:'34B25B2116FD91B0050C80393F8356A5CA256E707BF443E4882BE5CFAD1335B6', - orespawn_runtime_sha256:'BD8D84A96DAFB7668E95D61B9D72C62AEF3440675473E41244C182C64653356F', - forge_installer_sha256:'9434C29790504DCD11CE97CB30DD8891B4B18848982357BC1B5ABFA79FB95103'})) { + poweradvantage_source_ref:'4af5c7d5d490162cddc7f1e4fc72cc602b42bb32', + poweradvantage_deobf_sha256:'512FDEC9275B33533EF773C110E6229E0F07268775FAD4B5942A15B68A9EB87D', + poweradvantage_runtime_sha256:'E1689C1147F6F6F93F6516D9188A279093D6CCC2FF1FBEB506468B876BA03E60', + poweradvantage_unit_bootstrap_sha256:'0FB05554D6446BD5561BF9DE47B9F173E6F6C28E39A2BC877D359BB363CB1ABE', + poweradvantage_unit_bootstrap_jar_sha256:'A93CD83EC42B09B730CB7715C10C6E53D47B22B557EFE68EDC95241D53BD4EB3', + basemetals_source_commit:'5791a793aa3a1ca1aa3fcb36e6d4a2d865e7d2d3', + basemetals_deobf_sha256:'0AA993D6BADA54B11548EB5BE150CE0939232291F2D215B7DBAF149857976B2F', + basemetals_runtime_sha256:'991EA843EEDF5FA1E42E9984A07573C7D03D74230482230440E44EDCA895FA39', + orespawn_source_commit:'444d86ef48db2bf54422901b067c51d5832197fc', + orespawn_deobf_sha256:'BE6973F68F1C310DFF8CDF89C12B34C62D68CAEA326C0F1528E486EE3ABC1000', + orespawn_runtime_sha256:'1B2ECAA546E7FFB35863AC1D9FF92504471EE857045DCD01A068C5CA7F53CFFD', + forge_installer_sha256:'6643BB8C21F409CC528FF656DAF60C206D304CC77BF529BBBAB4B336004DFC53'})) { assert.equal(properties[key], value, key); } }); -check('Hosted release checks receive the verified player jars and Forge installer', () => { - const workflow = read('.github/workflows/ci.yml'); - assert(workflow.includes('verifyEclipseProductionClasspath verifyLocalRuntimeInputs')); - for (const [property, output] of [ - ['powerAdvantageRuntimeJar', 'power_runtime'], - ['baseMetalsRuntimeJar', 'basemetals_runtime'], - ['oreSpawnRuntimeJar', 'orespawn_runtime'], - ['forgeInstallerJar', 'forge_installer'] - ]) assert(workflow.includes('-P' + property + '=${{ steps.dependencies.outputs.' + output + ' }}')); - for (const key of ['basemetals_runtime_sha256', 'orespawn_runtime_sha256', 'forge_installer_sha256']) { - assert(workflow.includes('$(pin ' + key + ')')); - } -}); check('Resources retained except namespace moves, exact advancement conversion and the native fog shader', () => { let changedFiles = 0, changedPredicates = 0; for (const name of tracked.filter(n => n.startsWith('src/main/resources/') @@ -158,47 +146,48 @@ check('Advancement compatibility allowance rejects unrelated changes and old tri }); check('Registry, asset, provider and NBT string identities retained', () => { for (const name of production) { - const target = migratePath(name); - if (target === entry || target === migration) continue; // Checked against the native baseline below. - const actual = strings(read(target)); - if (name.endsWith('/ElectricRenderTypes.java')) { - assert.equal(actual.splice(actual.indexOf('"unshaded"'), 1)[0], '"unshaded"'); + const before = git('show', `${portBase}:${name}`).toString(); + const after = read(name); + const expected = strings(before); + if (name.endsWith('/init/Entities.java')) { + assert.equal(expected.splice(expected.indexOf('":hydroturbine"'), 1)[0], '":hydroturbine"'); + assert(after.includes('new ResourceLocation(ElectricAdvantage.MODID, "hydroturbine")')); + assert(after.includes('registry.register(id, HYDROTURBINE)')); } - assert.deepEqual(actual, strings(migrateText(git('show', `${baseline}:${name}`).toString())), target); + assert.deepEqual(strings(after), expected, name); } }); check('Machine balance and renderer test tolerances retained', () => { - const renderer = 'src/test/java/zone/moddev/mc/electricadvantage/graphics/RendererBufferContractTest.java'; + const renderer = 'src/test/java/zone/moddev/mc/baseelectric/graphics/RendererBufferContractTest.java'; for (const name of [...production, renderer]) { - let before = git('show', `${baseline}:${name}`).toString(); - if (name.endsWith('/LaserTurretTileEntity.java')) { - assert(before.includes('this.getBlockPos(), 0, nbtTag')); - before = before.replace('this.getBlockPos(), 0, nbtTag', 'this.getBlockPos(), nbtTag'); + let before = git('show', `${portBase}:${name}`).toString(); + if (/\/Electric(Machine|Generator)TileEntity\.java$/.test(name)) { + assert(before.includes('final double range = 16;')); + before = before.replace('final double range = 16;', 'final double range = range;'); } - const expected = numbers(before); - if (name.endsWith('/ElectricRenderTypes.java')) { - assert.equal(expected.splice(expected.indexOf('7'), 1)[0], '7'); // Native enum replaces GL_QUADS. + if (name.endsWith('/init/Blocks.java') || name.endsWith('/init/Items.java')) { + before = before.replace(/\.toArray\(new (?:Block|Item)\[0\]\)/, ''); } - assert.deepEqual(numbers(read(migratePath(name))), expected, name); + const expected = numbers(before); + assert.deepEqual(numbers(read(name)), expected, name); } + const sound = read(javaRoot + 'machines/ElectricOperationalSound.java'); + assert.match(sound, /final double range = 16;/); + assert.match(sound, /player\.isSpectator\(\) \|\| !player\.getBoundingBox\(\)\.intersects\(bounds\)/); }); check('Inherited regression tests remain present with no skip or assume bypass', () => { let count = 0; - for (const name of tracked.filter(n => n.startsWith('src/test/') && n.endsWith('.java'))) { - const before = git('show', `${baseline}:${name}`).toString(); - const after = read(migratePath(name)); + for (const name of portBaseFiles.filter(n => n.startsWith('src/test/') && n.endsWith('.java'))) { + const before = git('show', `${portBase}:${name}`).toString(); + const after = read(name); const methods = s => [...s.matchAll(/@Test\s+public void (\w+)\(/g)].map(m => m[1]); - const expected = methods(before).map(n => n.replace('resourcePackDeclaresTheNativeOneSixteenFormat', - 'resourcePackDeclaresTheNativeOneEighteenFormats') - .replace('publishedOneSeventeenRecipesKeepRawAndOreOutputsDistinct', 'nativeCrushingRecipesKeepRawAndOreOutputsDistinct')); + const expected = methods(before).map(n => n.replace('resourcePackDeclaresTheNativeOneEighteenFormats', + 'resourcePackDeclaresTheNativeOneNineteenFourFormats')); count += expected.length; - if (name.endsWith('/RecipeUnlockContractTest.java')) { - expected.splice(2, 0, 'nativeParserRestrictsItemArraysButSilentlyIgnoresTheLegacySingularKey'); - } assert.deepEqual(methods(after), expected, name); assert(!/@Ignore|Assume\.|assumeTrue\(|assumeFalse\(/.test(after), name); } - assert.equal(count, 89); + assert.equal(count, 96); }); check('Native constructors, factories and tag-only persistence', () => { const directory = path.join(root, javaRoot, 'machines'); @@ -247,37 +236,31 @@ check('Shared capabilities, legacy cyano API and first-tick interlocks retained' const source = read(javaRoot + `machines/${name}.java`); assert(source.includes('!redstoneInitialized && getLevel() != null && !getLevel().isClientSide')); } - assert(!production.some(n => /invalidateCaps\(|reviveCaps\(|LazyOptional/.test(read(migratePath(n))))); + assert(!production.some(n => /invalidateCaps\(|reviveCaps\(|LazyOptional/.test(read(n)))); assert(read(javaRoot + 'ElectricAdvantage.java').includes('event.enqueueWork(RecipeConditions::register)')); assert(read(javaRoot + 'machines/PhotovoltaicGeneratorTileEntity.java').includes('BlockPos lightPos = pos.above()')); }); -check('Only the reviewed 1.18.2 source, shader, metadata and fixture deltas are present', () => { +check('Accepted resource bytes and source paths are retained with explicit 1.19.4 additions', () => { const actualFiles = fs.readdirSync(path.join(root, 'src'), {recursive:true, withFileTypes:true}) .filter(file => file.isFile()).map(file => path.relative(root, path.join(file.parentPath, file.name)) .split(path.sep).join('/')).sort(); - const expectedFiles = portBaseFiles.map(name => name.replace('/Native117ConstructionTest.java', '/Native118ConstructionTest.java')); - expectedFiles.push('src/test/java/zone/moddev/mc/baseelectric/verification/NativeTestBootstrap.java'); + const damageRoot = 'src/main/resources/data/baseelectric/damage_type/'; + const blocksAtlas = 'src/main/resources/assets/minecraft/atlases/blocks.json'; + const expectedFiles = [...portBaseFiles, javaRoot + 'machines/ElectricOperationalSound.java', + damageRoot + 'laser.json', damageRoot + 'propeller.json', blocksAtlas]; assert.deepEqual(actualFiles, expectedFiles.sort(), 'Source path inventory'); - const sourceChanges = [...new Set([ - ...git('diff', '--no-renames', '--name-only', portBase, '--', 'src').toString().trim().split('\n'), - ...git('ls-files', '--others', '--exclude-standard', '--', 'src').toString().trim().split('\n') - ])].filter(Boolean).sort(); - const workflowTest = 'src/test/java/zone/moddev/mc/baseelectric/resources/BuildWorkflowContractTest.java'; - assert(sourceChanges.includes(workflowTest)); - assert.equal(sha256(read(workflowTest)), 'fc2ac98237925da76ab6ccd3b1b6c5f0f8839f8eeb2f77b9a30cdc5f554543eb', - 'Reviewed 1.18 hosted workflow test'); - const fingerprints = sourceChanges.filter(file => file !== workflowTest).map(file => { - const source = path.join(root, file); - if (!fs.existsSync(source)) return [file, null]; - let bytes = fs.readFileSync(source); - if (file === 'src/main/resources/META-INF/mods.toml') { - // The live minimum is checked above. Compare the rest to the original review. - bytes = Buffer.from(bytes.toString().replace('3.1.2.118021', '3.1.1.118021')); - } - return [file, sha256(bytes)]; - }); - assert.equal(sha256(JSON.stringify(fingerprints)), 'c684a77312129f3b90b367b6f0bf9b6a81402992e8bb2440b5795e99f9c60898', - 'Exact reviewed source delta from the PA117 clone point'); + const resourceExceptions = ['src/main/resources/pack.mcmeta', 'src/main/resources/META-INF/mods.toml']; + for (const name of portBaseFiles.filter(n => /^src\/(main|test)\/resources\//.test(n) + && !resourceExceptions.includes(n))) { + const expected = git('show', `${portBase}:${name}`); + assert.equal(sha256(fs.readFileSync(path.join(root, name))), sha256(expected), name); + } + for (const id of ['laser', 'propeller']) { + assert.deepEqual(JSON.parse(read(damageRoot + id + '.json')), + {message_id:id, scaling:'never', exhaustion:0.1}); + } + assert.deepEqual(JSON.parse(read(blocksAtlas)), + {sources:[{type:'single', resource:'baseelectric:entity/laser_turret'}]}); }); check('Original and baseElectric aliases retain the native registry paths and state decoding', () => { const old = git('show', `${nativeBaseline}:${oldJavaRoot}migration/ElectricLegacyMigrations.java`).toString(); @@ -302,12 +285,15 @@ check('Original and baseElectric aliases retain the native registry paths and st for (const name of ['Blocks', 'Items']) { const registration = read(javaRoot + `init/${name}.java`); assert(registration.includes('import zone.moddev.mc.basepower.init.ItemGroups;')); - assert(registration.includes('tab(ItemGroups.tab_powerAdvantage)')); + assert(registration.includes('ItemGroups.show(item)')); + assert(registration.includes('net.minecraftforge.registries.RegisterEvent')); } + assert.match(read(javaRoot + 'init/Blocks.java'), + /if \(!"pump_pipe_electric"\.equals\(id\) && !"laser_turret_evil"\.equals\(id\)\) \{\s+ItemGroups\.show\(item\);/); }); const changedFiles = [...new Set([ - ...git('diff', '--name-only', baseline).toString().trim().split('\n'), + ...git('diff', '--name-only', portBase).toString().trim().split('\n'), ...git('ls-files', '--others', '--exclude-standard').toString().trim().split('\n') ])].filter(Boolean).sort(); const testFiles = fs.readdirSync(path.join(root, 'src/test/java'), {recursive:true, withFileTypes:true})