diff --git a/.github/scripts/forge45-dependencies.sh b/.github/scripts/forge47-dependencies.sh similarity index 72% rename from .github/scripts/forge45-dependencies.sh rename to .github/scripts/forge47-dependencies.sh index d8347a9..b26aabc 100644 --- a/.github/scripts/forge45-dependencies.sh +++ b/.github/scripts/forge47-dependencies.sh @@ -19,9 +19,9 @@ check_hash() { [[ "${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' +[[ "$(value minecraft_version)" == '1.20.1' && "$(value forge_version)" == '47.4.10' ]] || + fail 'This dependency build is only for Minecraft 1.20.1 / Forge 47.4.10' +[[ "$(value poweradvantage_pin_state)" == 'MERGED_SOURCE' ]] || fail 'basePower merged source is not pinned' ore_ref="$(value orespawn_source_commit)" bm_ref="$(value basemetals_source_commit)" @@ -52,7 +52,7 @@ 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' +check_hash "$mavenizer" '416DCA4797BC55826F0DDCF03FDC405F1990FC15C2133FA27A1F143BF7976177' read -r -a toolchains <<< "${MMD_GRADLE_TOOLCHAINS:?}" ore_version="$(value orespawn_version)" @@ -66,7 +66,7 @@ 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" +installer="${RUNNER_TEMP:?}/forge-1.20.1-47.4.10-installer.jar" (cd "$orespawn" && ./gradlew "${toolchains[@]}" assemble --no-daemon --stacktrace) check_hash "$osjar" "$(value orespawn_deobf_sha256)" @@ -78,33 +78,15 @@ 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") + "-Pforge47MavenizerJar=$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' \ + 'https://maven.minecraftforge.net/net/minecraftforge/forge/1.20.1-47.4.10/forge-1.20.1-47.4.10-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/verify-published-pins.sh b/.github/verify-published-pins.sh new file mode 100644 index 0000000..206980c --- /dev/null +++ b/.github/verify-published-pins.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +value() { sed -n "s/^$1=//p" gradle.properties | tr -d '\r'; } + +require_merged() { + local repo="$1" ref="$2" status + [[ "$ref" =~ ^[a-f0-9]{40}$ ]] + status="$(gh api "repos/MinecraftModDevelopmentMods/$repo/compare/$ref...master-1.20.1" --jq '.status')" + case "$status" in + identical|ahead) ;; + *) echo "::error::$repo $ref is not on upstream master-1.20.1 ($status)."; exit 1 ;; + esac +} + +file_id="$(value poweradvantage_public_file_id)" +expected="$(value poweradvantage_runtime_sha256)" +if [[ ! "$file_id" =~ ^[1-9][0-9]*$ || ! "$expected" =~ ^[A-F0-9]{64}$ ]]; then + echo '::error::A verified public basePower 1.20.1 file ID and player checksum are required.' + exit 1 +fi +require_merged basePower "$(value poweradvantage_source_ref)" +require_merged BaseMetals "$(value basemetals_source_commit)" + +player="${RUNNER_TEMP:?}/basePower-public-player.jar" +curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' \ + --output "$player" "https://www.curseforge.com/api/v1/mods/1780140/files/$file_id/download" +read -r actual _ < <(sha256sum < "$player") +if [[ "${actual^^}" != "$expected" ]]; then + echo "::error::Public basePower file $file_id SHA-256 is ${actual^^}, expected $expected." + exit 1 +fi +printf 'power_player=%s\n' "$player" >> "${GITHUB_OUTPUT:?}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be602f6..258f36d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,16 @@ -name: baseElectric 1.19.4 CI +name: baseElectric 1.20.1 CI on: push: - branches: [master-1.19.4, 'feature/**'] + branches: [master-1.20.1, 'feature/**'] pull_request: - branches: [master-1.19.4] + branches: [master-1.20.1] permissions: contents: read concurrency: - group: baseelectric-1.19.4-${{ github.workflow }}-${{ github.ref }} + group: baseelectric-1.20.1-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: @@ -23,9 +23,14 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Use canonical LF dependency inputs run: git config --global core.autocrlf false - - name: Require coordinated 1.19.4 dependency pins + - name: Verify merged dependency pins and public basePower bytes + id: public-pins + env: + GH_TOKEN: ${{ github.token }} + run: bash .github/verify-published-pins.sh + - name: Require coordinated 1.20.1 dependency pins id: pins - run: bash .github/scripts/forge45-dependencies.sh pins + run: bash .github/scripts/forge47-dependencies.sh pins - name: Check out pinned OreSpawn uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: @@ -67,15 +72,15 @@ jobs: -Porg.gradle.java.installations.auto-download=false" >> "$GITHUB_ENV" - name: Set up Gradle uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 - - name: Build 1.19.4 dependency chain + - name: Build 1.20.1 dependency chain id: dependencies - run: bash .github/scripts/forge45-dependencies.sh build + run: bash .github/scripts/forge47-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_PLAYER: ${{ steps.public-pins.outputs.power_player }} POWER_BOOTSTRAP: ${{ steps.dependencies.outputs.bootstrap }} POWER_AGENT: ${{ steps.dependencies.outputs.agent }} METALS_DEV: ${{ steps.dependencies.outputs.basemetals }} @@ -85,7 +90,7 @@ jobs: FORGE_INSTALLER: ${{ steps.dependencies.outputs.installer }} run: | ./gradlew $MMD_GRADLE_TOOLCHAINS build writeReleaseChecksums verifyEclipseProductionClasspath \ - "-Pforge45MavenizerJar=$MAVENIZER" "-PpowerAdvantageDeobfJar=$POWER_DEV" \ + "-Pforge47MavenizerJar=$MAVENIZER" "-PpowerAdvantageDeobfJar=$POWER_DEV" \ "-PpowerAdvantageRuntimeJar=$POWER_PLAYER" "-PpowerAdvantageUnitBootstrapSource=$POWER_BOOTSTRAP" \ "-PpowerAdvantageUnitBootstrapJar=$POWER_AGENT" "-PbaseMetalsDeobfJar=$METALS_DEV" \ "-PbaseMetalsRuntimeJar=$METALS_PLAYER" "-PoreSpawnDeobfJar=$ORESPAWN_DEV" \ @@ -94,20 +99,20 @@ jobs: - name: Upload audited release candidate uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: baseElectric-1.19.4-${{ github.sha }} + name: baseElectric-1.20.1-${{ github.sha }} if-no-files-found: error retention-days: 30 path: | - 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/libs/baseElectric-2.2.13.120011.jar + build/libs/baseElectric-2.2.13.120011-sources.jar + build/libs/baseElectric-2.2.13.120011-javadoc.jar build/release/SHA256SUMS CHANGELOG.txt - name: Upload diagnostics on failure if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: baseElectric-1.19.4-diagnostics-${{ github.sha }} + name: baseElectric-1.20.1-diagnostics-${{ github.sha }} if-no-files-found: ignore retention-days: 14 path: | diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 865a732..033e10b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -2,9 +2,9 @@ name: CodeQL on: push: - branches: [master-1.19.4, 'feature/**'] + branches: [master-1.20.1, 'feature/**'] pull_request: - branches: [master-1.19.4] + branches: [master-1.20.1] schedule: - cron: '59 7 * * 4' @@ -23,9 +23,14 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Use canonical LF dependency inputs run: git config --global core.autocrlf false - - name: Require coordinated 1.19.4 dependency pins + - name: Verify merged dependency pins and public basePower bytes + id: public-pins + env: + GH_TOKEN: ${{ github.token }} + run: bash .github/verify-published-pins.sh + - name: Require coordinated 1.20.1 dependency pins id: pins - run: bash .github/scripts/forge45-dependencies.sh pins + run: bash .github/scripts/forge47-dependencies.sh pins - name: Check out pinned OreSpawn uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: @@ -67,9 +72,9 @@ jobs: -Porg.gradle.java.installations.auto-download=false" >> "$GITHUB_ENV" - name: Set up Gradle uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 - - name: Build 1.19.4 dependency chain + - name: Build 1.20.1 dependency chain id: dependencies - run: bash .github/scripts/forge45-dependencies.sh build + run: bash .github/scripts/forge47-dependencies.sh build - name: Initialize CodeQL uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 with: @@ -84,7 +89,7 @@ jobs: ORESPAWN_DEV: ${{ steps.dependencies.outputs.orespawn }} run: | ./gradlew $MMD_GRADLE_TOOLCHAINS classes verifyReleaseDependencies --rerun-tasks --no-build-cache --no-daemon --stacktrace \ - "-Pforge45MavenizerJar=$MAVENIZER" "-PpowerAdvantageDeobfJar=$POWER_DEV" \ + "-Pforge47MavenizerJar=$MAVENIZER" "-PpowerAdvantageDeobfJar=$POWER_DEV" \ "-PbaseMetalsDeobfJar=$METALS_DEV" "-PoreSpawnDeobfJar=$ORESPAWN_DEV" - name: Analyze uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 43766f4..104d8ca 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,10 +1,15 @@ -baseElectric 2.2.12.119041 (Minecraft 1.19.4) +baseElectric 2.2.13.120011 (Minecraft 1.20.1 / Forge) + +- Update to Forge 47.4.10 with Java 17. +- Keep machine save data, the shared creative tab, and older Electric Advantage world mappings. +- Keep the original ore tool levels, drops, machine block properties and drill track appearance on the new Forge APIs. + +baseElectric 2.2.12.119041 (Minecraft 1.19.4 / Forge) - 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) diff --git a/README.md b/README.md index a3b4d75..f71ee28 100644 --- a/README.md +++ b/README.md @@ -1,122 +1,67 @@ # baseElectric 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. +generation, storage, automation and defence to basePower. Its equipment appears +in the shared basePower creative tab. -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. +This branch targets Minecraft **1.20.1** with Forge **47.4.10**, official mappings, +ForgeGradle **7.0.34**, and Java **17**. The version is **2.2.13.120011**. +Registry names, machine save data, and legacy Electric Advantage migration +mappings are unchanged. -## Target +Back up your world and try the upgrade on a copy. Replace Electric Advantage +and Power Advantage with baseElectric and basePower; do not install both sets. -- 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 path: `build/libs/baseElectric-2.2.12.119041.jar`. - The separate deobfuscated API artifact stays under `build/libs-dev`. +## Building locally -basePower is required. Base Metals remains optional in Forge metadata; -the existing required CurseForge installation relationship and ordering are unchanged. -basePower, Base Metals and OreSpawn are separate dependencies, never embedded. +Use Java 17 for Gradle, the game and Eclipse Buildship. ForgeGradle uses Java 25 +for Mavenizer and Java 8 for its Slime Launcher tooling. If necessary, supply +`-Porg.gradle.java.installations.paths=,,`. +Use an isolated Gradle user home for clean-room checks. -## Current State - -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 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: +Pass the following input paths to the Gradle wrapper. Each input is checked against +the version and SHA-256 recorded in `gradle.properties`. +Hosted release checks also require the merged basePower source and a matching +public player download. ```text -./gradlew --no-build-cache --rerun-tasks build verifyLocalRuntimeInputs writeReleaseChecksums prepareEclipse verifyEclipseProductionClasspath -``` - -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.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" +-PpowerAdvantageDeobfJar="/path/to/basePower-2.4.12.120011-deobf.jar" +-PpowerAdvantageRuntimeJar="/path/to/basePower-2.4.12.120011.jar" -PpowerAdvantageUnitBootstrapSource="/path/to/ForgeCapabilityBootstrap.java" -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" +-PbaseMetalsDeobfJar="/path/to/BaseMetals-3.1.2.120011-deobf.jar" +-PbaseMetalsRuntimeJar="/path/to/BaseMetals-3.1.2.120011.jar" +-PforgeInstallerJar="/path/to/forge-1.20.1-47.4.10-installer.jar" ``` -The build checks these inputs against the pinned hashes; do not substitute jars -from another Minecraft version. If Gradle cannot find the installed JDKs, pass -`-Porg.gradle.java.installations.paths=,,`. -For a prepared offline cache, also set `ORESPAWN_MAVENIZER_OFFLINE=true` and use -Gradle's `--offline` flag. - -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: +By default the OreSpawn development and player jars, and its audited Forge 47 +Mavenizer fixture, come from the sibling `MinecraftMineralogy/OS1201/OreSpawn` +checkout. Use `-PoreSpawnDeobfJar`, `-PoreSpawnRuntimeJar` and +`-Pforge47MavenizerJar` to point to the exact same inputs in another layout. +For a prepared offline cache, use `--offline` and +`ORESPAWN_MAVENIZER_OFFLINE=true`. ```text -node tools/verify-native-port.js --report build/port-evidence/static-119-unique.json +./gradlew clean check build javadoc verifyLocalRuntimeInputs writeReleaseChecksums verifyEclipseProductionClasspath --no-build-cache +node tools/verify-native-port.js node tools/generate-recipe-unlocks.js --check ``` -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 - -The mod ID remains `baseelectric` and the implementation package is -`zone.moddev.mc.baseelectric`. The old namespace maps to the new one through -basePower's migration API. Registry paths after the namespace, machine NBT keys, -and the legacy `cyano.poweradvantage.api` integration are retained. -Native constructors carry each machine's real position and block state, including -moving drills. Numeric machine constants and existing recipes are unchanged. +Use `gradlew.bat` on Windows. The player, sources and Javadoc jars go to +`build/libs`; the deobfuscated jar goes to `build/libs-dev`. The test agent is +used only by the unit tests. basePower, Base Metals and OreSpawn remain separate +mods. Base Metals is optional in Forge metadata and remains required on +CurseForge. -All 46 crafting variants and four smelting recipes retain their recipe-book -unlocks. The eight Base Metals crushing recipes remain non-crafting entries. -The crusher continues to consume the real Base Metals recipe implementation; -no replacement raw-metal conversion or balance change is introduced. +## Eclipse and testing -Eclipse imports use the Gradle wrapper, processed resources and production-only -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. -Use Java 17 for the generated client and server launches, not Java 8. -Java 17 is for Gradle and Buildship; Java 25 is for Mavenizer. +Import this checkout as an existing Gradle project named `ElectricAdvantage`. +Use the generated `runClient.launch` or `runServer.launch` after +`verifyEclipseProductionClasspath`. The build checks package paths against +Gradle and Eclipse source roots and keeps test classes out of production runs. +`tools/verification/Verify-ElectricEcj.ps1` independently compiles and runs the +test inventory using Eclipse's compiler. -Test only disposable copies of worlds. Preserve originals, backups, failed -receipts and version-specific checkouts. Manual GUI interaction and captions, -hydroturbine animation, sound, multiplayer, weapons and player/entity behavior -remain separate acceptance gates. +The [1.19.4 notes](docs/NATIVE119.md) and [1.18.2 notes](docs/NATIVE118.md) are +historical. To check a world upgrade, test only a disposable copy. A build and +resource audit do not substitute for a migration, gameplay or client check. diff --git a/build.gradle b/build.gradle index beccb40..5e3d715 100644 --- a/build.gradle +++ b/build.gradle @@ -46,11 +46,12 @@ def sha256Of = { File input -> MessageDigest.getInstance('SHA-256').digest(input.bytes).encodeHex().toString().toUpperCase() } -def pinnedFile = { String property, String expected -> +def pinnedFile = { String property, String expected, String fallback = null -> if (!(expected ==~ /[A-F0-9]{64}/)) { throw new GradleException("No approved SHA-256 for -P${property}.") } - File input = providers.gradleProperty(property).map { file(it) }.orNull + String path = providers.gradleProperty(property).getOrElse(fallback ?: '') + File input = path ? file(path) : null if (input == null || !input.isFile()) { throw new GradleException("Pass the exact local input with -P${property}= (received ${input}).") } @@ -61,9 +62,9 @@ def pinnedFile = { String property, String expected -> input } -ext.forge45MavenizerDefault = - '../../../../MinecraftMineralogy/OS119/OreSpawn/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar' -apply from: 'gradle/forge45-mavenizer.gradle' +ext.forge47MavenizerDefault = + '../../../../MinecraftMineralogy/OS1201/OreSpawn/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar' +apply from: 'gradle/forge47-mavenizer.gradle' java { toolchain { @@ -112,8 +113,8 @@ tasks.named('test', Test) { vendor = JvmVendorSpec.ADOPTIUM } afterSuite { descriptor, result -> - if (!descriptor.parent && (result.testCount != 96 || result.skippedTestCount != 0)) { - throw new GradleException("Expected 96 tests with no skips, got ${result.testCount} tests and ${result.skippedTestCount} skips") + if (!descriptor.parent && (result.testCount != 99 || result.skippedTestCount != 0)) { + throw new GradleException("Expected 99 tests with no skips, got ${result.testCount} tests and ${result.skippedTestCount} skips") } } } @@ -180,7 +181,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.19.4 launches.' + description = 'Merges classes and processed resources for Forge 1.20.1 launches.' dependsOn tasks.named('classes') from sourceSets.main.output into forgeRunModClassesDirectory @@ -198,26 +199,49 @@ repositories { mavenCentral() } +def committedPins = new Properties() +file('gradle.properties').withInputStream { committedPins.load(it) } +def requireCommittedPins = { String prefix -> + committedPins.stringPropertyNames().findAll { key -> + key.startsWith(prefix) && (key.endsWith('_sha256') || key.endsWith('_ref') + || key.endsWith('_commit') || key.endsWith('_pin_state') || key.endsWith('_version')) + }.each { key -> + if (project.property(key) != committedPins.getProperty(key)) { + throw new GradleException("Cannot override the recorded release pin ${key}.") + } + } +} def requirePowerPin = { - if (project.poweradvantage_pin_state != 'PUBLISHED_RELEASE' - || 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.') + requireCommittedPins('poweradvantage_') + if (project.poweradvantage_pin_state != 'MERGED_SOURCE' + || !(project.poweradvantage_source_ref ==~ /[a-f0-9]{40}/) + || ![project.poweradvantage_deobf_sha256, project.poweradvantage_runtime_sha256] + .every { it ==~ /[A-F0-9]{64}/ }) { + throw new GradleException('The owner-approved basePower 2.4.12.120011 source and artifact pins are required.') + } +} +def requireCompletePowerPin = { + requirePowerPin() + if (![project.poweradvantage_unit_bootstrap_sha256, project.poweradvantage_unit_bootstrap_jar_sha256] + .every { it ==~ /[A-F0-9]{64}/ }) { + throw new GradleException('The exact basePower unit bootstrap source and jar pins are required for release.') } } // 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 { + requireCommittedPins('basemetals_') pinnedFile('baseMetalsDeobfJar', project.basemetals_deobf_sha256) } +def oreSpawnProject = '../../../../MinecraftMineralogy/OS1201/OreSpawn' def oreSpawnDeobfJar = providers.provider { - pinnedFile('oreSpawnDeobfJar', project.orespawn_deobf_sha256) + requireCommittedPins('orespawn_') + pinnedFile('oreSpawnDeobfJar', project.orespawn_deobf_sha256, + "${oreSpawnProject}/build/libs-dev/OreSpawn-${project.orespawn_version}-deobf.jar") } dependencies { @@ -274,7 +298,7 @@ def powerPinManifest = [ ] tasks.named('jar', Jar) { - doFirst { requirePowerPin() } + doFirst { requireCompletePowerPin() } archiveClassifier = 'deobf' destinationDirectory = layout.buildDirectory.dir('libs-dev') exclude 'fernflower_abstract_parameter_names.txt' @@ -379,21 +403,24 @@ tasks.register('verifyReleaseConfiguration') { group = 'verification' description = 'Validates release identity, target metadata, and dependency policy.' doLast { + requireCompletePowerPin() + requireCommittedPins('basemetals_') + requireCommittedPins('orespawn_') def pack = new JsonSlurper().parse(file('src/main/resources/pack.mcmeta')).pack - 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 (pack.pack_format != 15) { + throw new GradleException('Minecraft 1.20.1 requires pack format 15') } - if (project.mod_version != '2.2.12.119041') { + if (project.mod_version != '2.2.13.120011') { throw new GradleException("Unexpected baseElectric release version: ${project.mod_version}") } - 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,)' + if (project.minecraft_version != '1.20.1' + || project.minecraft_version_range != '[1.20.1]' + || project.forge_version != '47.4.10' + || project.forge_version_range != '[47.4.10,48)' + || project.loader_version_range != '[47,)' || project.mapping_channel != 'official' - || project.mapping_version != '1.19.4' - || project.mcp_version != '20230314.122934') { + || project.mapping_version != '1.20.1' + || project.mcp_version != '20230612.114412') { throw new GradleException('Unexpected Minecraft, Forge, or mappings target') } if (project.loader_name != 'forge' || project.loader_code != '1' @@ -413,33 +440,33 @@ tasks.register('verifyReleaseConfiguration') { } if (project.cf_requirements != 'basepower,base-metals' || project.cf_optionals != '' - || project.cf_versions != '1.19.4') { + || project.cf_versions != '1.20.1') { throw new GradleException('Unexpected CurseForge dependency or game-version metadata') } - if (project.poweradvantage_version != '2.4.11.119041' + if (project.poweradvantage_version != '2.4.12.120011' || project.poweradvantage_source_repository != 'MinecraftModDevelopmentMods/basePower') { throw new GradleException('Unexpected basePower compile dependency identity') } - 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') { + if (project.basemetals_version != '3.1.2.120011' + || project.orespawn_version != '4.1.0.120011' + || !(project.basemetals_source_commit ==~ /[a-f0-9]{40}/) + || !(project.basemetals_deobf_sha256 ==~ /[A-F0-9]{64}/) + || !(project.basemetals_runtime_sha256 ==~ /[A-F0-9]{64}/) + || project.orespawn_source_commit != '9d9db6cd315c9fce542808aa2551c94bf9267e93' + || project.orespawn_deobf_sha256 != 'A0B096894DC48308D59455F7EC67AB0BBF7EB02354D903024A9E9E876EB07CBB' + || project.orespawn_runtime_sha256 != 'E6BDE47C1BB0BF4D08396F1D1FD5B1D2D3F8A5DF09905F279D39E2601D7FE4B0' + || project.forge_installer_sha256 != '1912760B4CB6B803D8A826DE603C9076B1DA71EC2765E9A1F8C1CA78F65278E3') { 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.12.119041"')) { + || !mainSource.contains('VERSION = "2.2.13.120011"')) { 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.11.119041,3.0.0)"', 'versionRange="[3.1.2.119041,4.0.0)"'].each { marker -> + 'versionRange="[2.4.12.120011,3.0.0)"', 'versionRange="[3.1.2.120011,4.0.0)"'].each { marker -> if (!metadata.contains(marker)) { throw new GradleException("Source mods.toml is missing ${marker}") } @@ -470,7 +497,7 @@ def auditModInput = { File jarFile, String modId, String version, List r tasks.register('verifyLocalNativeInputs') { group = 'verification' - description = 'Verifies explicit Base Metals and OreSpawn 1.19.4 development and player JARs.' + description = 'Verifies explicit Base Metals and OreSpawn 1.20.1 development and player JARs.' dependsOn tasks.named('verifyReleaseConfiguration') doLast { auditModInput(baseMetalsDeobfJar.get(), 'basemetals', project.basemetals_version, @@ -479,7 +506,8 @@ tasks.register('verifyLocalNativeInputs') { auditModInput(pinnedFile('baseMetalsRuntimeJar', project.basemetals_runtime_sha256), 'basemetals', project.basemetals_version, []) auditModInput(oreSpawnDeobfJar.get(), 'orespawn', project.orespawn_version, []) - auditModInput(pinnedFile('oreSpawnRuntimeJar', project.orespawn_runtime_sha256), + auditModInput(pinnedFile('oreSpawnRuntimeJar', project.orespawn_runtime_sha256, + "${oreSpawnProject}/build/libs/OreSpawn-${project.orespawn_version}.jar"), 'orespawn', project.orespawn_version, []) } } @@ -802,9 +830,9 @@ publishing { tasks.register('validateMavenReleaseCredentials') { group = 'publishing' - description = 'Keeps remote publication in the guarded release workflow.' + description = 'Rejects remote publication from this local port.' doLast { - throw new GradleException('Use the guarded release workflow to publish the verified 1.19.4 jar.') + throw new GradleException('Remote Maven publication is disabled on the local 1.20.1 port.') } } @@ -958,6 +986,17 @@ tasks.register('isolateEclipseProductionRuns') { String eclipseModClasses = "${project.mod_id}%%${mainOutput}" + "${File.pathSeparator}${project.mod_id}%%${mainOutput}" modClasses[0].@value = eclipseModClasses + def vmArguments = launchXml.stringAttribute.find { it.@key == 'org.eclipse.jdt.launching.VM_ARGUMENTS' } + if (vmArguments != null) { + // ForgeGradle leaves these path arguments bare even when a directory contains spaces. + vmArguments.@value = (vmArguments.@value as String) + .replaceAll(/(? + "\"${argument}\"" + } + .replaceAll(/(? + "-p \"${argument}\"" + } + } def programArguments = launchXml.stringAttribute.find { it.@key == 'org.eclipse.jdt.launching.PROGRAM_ARGUMENTS' } @@ -1020,6 +1059,31 @@ tasks.register('isolateEclipseProductionRuns') { } } +def verifyJavaPackages = { Iterable roots -> + roots.findAll { it.isDirectory() }.each { root -> + fileTree(root).matching { include '**/*.java' }.each { source -> + def declaration = source.getText('UTF-8') =~ /(?m)^\s*package\s+([\w.]+)\s*;/ + if (!declaration.find()) { + throw new GradleException("No Java package declared in ${relativePath(source)}") + } + String declared = declaration.group(1) + String expected = root.toPath().relativize(source.parentFile.toPath()).join('.') + if (declared != expected) { + throw new GradleException("${relativePath(source)} declares ${declared}, " + + "but source root ${relativePath(root)} expects '${expected}'.") + } + } + } +} + +tasks.register('verifyJavaSourceLayout') { + group = 'verification' + description = 'Checks package paths that Eclipse must resolve through each Java source root.' + doLast { + verifyJavaPackages(sourceSets.collectMany { it.java.srcDirs }.unique()) + } +} + tasks.register('verifyEclipseProductionClasspath') { group = 'verification' description = 'Verifies the generated Eclipse project and production launches.' @@ -1064,6 +1128,15 @@ tasks.register('verifyEclipseProductionClasspath') { throw new GradleException('Eclipse output contains incorrect mod metadata') } def eclipseClasspath = new groovy.xml.XmlSlurper().parse(file('.classpath')) + def eclipseSourceRoots = eclipseClasspath.classpathentry.findAll { + it.@kind.text() == 'src' && !it.@path.text().startsWith('/') + }.collect { file(it.@path.text()).canonicalFile } + def expectedJavaRoots = sourceSets.collectMany { it.java.srcDirs } + .findAll { it.isDirectory() }.collect { it.canonicalFile } + if (!eclipseSourceRoots.containsAll(expectedJavaRoots)) { + throw new GradleException('Eclipse classpath omits a configured Java source root') + } + verifyJavaPackages(eclipseSourceRoots) def productionJre = eclipseClasspath.classpathentry.findAll { it.@kind.text() == 'con' && it.@path.text().startsWith('org.eclipse.jdt.launching.JRE_CONTAINER/') } @@ -1153,13 +1226,18 @@ tasks.register('verifyEclipseProductionClasspath') { String vmArguments = launch.stringAttribute.find { it.@key.text() == 'org.eclipse.jdt.launching.VM_ARGUMENTS' }.@value.text() + def legacyFile = vmArguments =~ /(?:^|\s)"-DlegacyClassPath\.file=([^"]+)"(?=\s|$)/ + if (!legacyFile.find() || !file(legacyFile.group(1)).isFile() + || !(vmArguments =~ /(?:^|\s)-p\s+"[^"]+"/).find()) { + throw new GradleException("${name} must quote the Forge classpath and module paths") + } if (contents.contains('${MC_VERSION}') || !programArguments.contains('--main cpw.mods.bootstraplauncher.BootstrapLauncher') || !programArguments.contains("--fml.mcVersion ${project.minecraft_version}") || !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 45 bootstrap and literal target versions") + throw new GradleException("${name} does not define the native Forge 47 bootstrap and literal target versions") } } } @@ -1361,6 +1439,7 @@ tasks.register('verifyRecipeTagGuards') { } tasks.named('check') { + dependsOn tasks.named('verifyJavaSourceLayout') dependsOn tasks.named('verifyReleaseConfiguration') dependsOn tasks.named('verifyModelAssets') dependsOn tasks.named('verifyRepositoryHygiene') diff --git a/docs/NATIVE119.md b/docs/NATIVE119.md index 8c6c40a..525fc15 100644 --- a/docs/NATIVE119.md +++ b/docs/NATIVE119.md @@ -1,9 +1,8 @@ -# baseElectric 1.19.4 port +# baseElectric 1.19.4 local 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`. +`4914e5c8a0d8d878f0938b3a9dc59d9f35f7c85e`. No Git remote is configured. ## Target and inputs @@ -17,15 +16,21 @@ basePower and Base Metals sources and artifacts recorded in `gradle.properties`. `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. +- Base Metals `3.1.2.119041` is the owner-approved migration-hook build + from `a9fdd6842f2a2e1e2e5b9a0abf9178d5675df044`. Development and player JARs + are under `D:/PowerAdvantage/_verification/base-119-port-20261109/Base Metals hook build/build/` + in `libs-dev` and `libs` respectively. The original Base Metals 1.19 + checkout supplied the read-only Forge reference setup. - 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 local basePower `2.4.11.119041` audit uses owner-verified checkpoint + `c9995315e2aee9d9bffec8eb8b1191ad8a0faeb7`. The development JAR, player + JAR, unit-agent JAR and the agent's source are hash-checked and snapshotted in + `build/port-evidence/power-checkpoint-c9995315/inputs` before building. + The bootstrap source also matched the checkpoint's Git blob. Electric uses the canonical Forge 45 unit agent without an extra event transformer. + This checkpoint includes Power's modern-upgrade repair. Electric's combined + suite and reconciliation against merged source remain separate. The exact local dependency digests are in [gradle.properties](../gradle.properties). Do not substitute 1.18 artifacts. No sibling build, shared Gradle write, or diff --git a/gradle.properties b/gradle.properties index 33d565f..321e7ae 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.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,) +minecraft_version=1.20.1 +minecraft_version_range=[1.20.1] +forge_version=47.4.10 +forge_installer_sha256=1912760B4CB6B803D8A826DE603C9076B1DA71EC2765E9A1F8C1CA78F65278E3 +forge_version_range=[47.4.10,48) +loader_version_range=[47,) mapping_channel=official -mapping_version=1.19.4 -mcp_version=20230314.122934 +mapping_version=1.20.1 +mcp_version=20230612.114412 loader_name=forge loader_code=1 @@ -26,31 +26,32 @@ curseforge_project_id=1783006 mod_id=baseelectric mod_name=baseElectric mod_license=GPL-3.0-only -mod_version=2.2.12.119041 +mod_version=2.2.13.120011 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.19.4 +cf_versions=1.20.1 -poweradvantage_version=2.4.11.119041 -poweradvantage_pin_state=PUBLISHED_RELEASE +poweradvantage_version=2.4.12.120011 +poweradvantage_pin_state=MERGED_SOURCE poweradvantage_source_repository=MinecraftModDevelopmentMods/basePower -# Published source. Development, player and unit-agent artifacts are distinct. -poweradvantage_source_ref=4af5c7d5d490162cddc7f1e4fc72cc602b42bb32 -poweradvantage_deobf_sha256=512FDEC9275B33533EF773C110E6229E0F07268775FAD4B5942A15B68A9EB87D -poweradvantage_runtime_sha256=E1689C1147F6F6F93F6516D9188A279093D6CCC2FF1FBEB506468B876BA03E60 +# Build against the merged source and verify the matching public player jar. +poweradvantage_source_ref=6943b5be9478d846801ed5e7f7422df3e8f6e86a +poweradvantage_public_file_id=9540451 +poweradvantage_deobf_sha256=21A2A3007BA076D219E6836707EF365E2031823A09D5E3DC2A92DD88BD67EA01 +poweradvantage_runtime_sha256=E6835ECB04D3F2D5F54731694F7B8D87F06B4A0A9DDB18AA5EDD70EE0E1B099A poweradvantage_unit_bootstrap_sha256=0FB05554D6446BD5561BF9DE47B9F173E6F6C28E39A2BC877D359BB363CB1ABE poweradvantage_unit_bootstrap_jar_sha256=A93CD83EC42B09B730CB7715C10C6E53D47B22B557EFE68EDC95241D53BD4EB3 -basemetals_version=3.1.2.119041 -basemetals_source_commit=5791a793aa3a1ca1aa3fcb36e6d4a2d865e7d2d3 -basemetals_deobf_sha256=0AA993D6BADA54B11548EB5BE150CE0939232291F2D215B7DBAF149857976B2F -basemetals_runtime_sha256=991EA843EEDF5FA1E42E9984A07573C7D03D74230482230440E44EDCA895FA39 +basemetals_version=3.1.2.120011 +basemetals_source_commit=b4795d413b3d356406c351c5e7e29406789f3f30 +basemetals_deobf_sha256=A67EB0CB74971E53D528C4D4F1275AE007106D7F8F71DFE4A16FB50411BDCC28 +basemetals_runtime_sha256=30FC6777300E0E48FBB63E3250E86122DAFA38CCA5AD5878CB795C72B7CCCCDE -orespawn_version=4.1.0.119041 -orespawn_source_commit=444d86ef48db2bf54422901b067c51d5832197fc -orespawn_deobf_sha256=BE6973F68F1C310DFF8CDF89C12B34C62D68CAEA326C0F1528E486EE3ABC1000 -orespawn_runtime_sha256=1B2ECAA546E7FFB35863AC1D9FF92504471EE857045DCD01A068C5CA7F53CFFD +orespawn_version=4.1.0.120011 +orespawn_source_commit=9d9db6cd315c9fce542808aa2551c94bf9267e93 +orespawn_deobf_sha256=A0B096894DC48308D59455F7EC67AB0BBF7EB02354D903024A9E9E876EB07CBB +orespawn_runtime_sha256=E6BDE47C1BB0BF4D08396F1D1FD5B1D2D3F8A5DF09905F279D39E2601D7FE4B0 diff --git a/gradle/forge45-mavenizer.gradle b/gradle/forge47-mavenizer.gradle similarity index 57% rename from gradle/forge45-mavenizer.gradle rename to gradle/forge47-mavenizer.gradle index 62bacb7..fe80eb5 100644 --- a/gradle/forge45-mavenizer.gradle +++ b/gradle/forge47-mavenizer.gradle @@ -1,20 +1,20 @@ 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') +// OreSpawn's audited fixture repairs the Forge 47 decompiled sources. +def fixturePath = providers.gradleProperty('forge47MavenizerJar').orNull +if (fixturePath == null) fixturePath = project.findProperty('forge47MavenizerDefault') if (!fixturePath || fixturePath.isBlank()) { - throw new GradleException('Set -Pforge45MavenizerJar to the pinned OreSpawn Forge 45 Mavenizer fixture.') + throw new GradleException('Set -Pforge47MavenizerJar to the pinned OreSpawn Forge 47 Mavenizer fixture.') } File fixture = file(fixturePath) if (!fixture.isFile()) { - throw new GradleException("Forge 45 Mavenizer fixture not found: ${fixture}. Use -Pforge45MavenizerJar=.") + throw new GradleException("Forge 47 Mavenizer fixture not found: ${fixture}. Use -Pforge47MavenizerJar=.") } -def expected = '341B3FB10ABA6EA7DC612FBE519DC290A1C81867AED5DD320ACD783688AC49DE' +def expected = '416DCA4797BC55826F0DDCF03FDC405F1990FC15C2133FA27A1F143BF7976177' 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}") + throw new GradleException("Forge 47 Mavenizer fixture SHA-256 mismatch: expected ${expected}, got ${actual}") } fgtools.configure('mavenizer') { diff --git a/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java b/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java index 9652e0e..23ad8ae 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java +++ b/src/main/java/zone/moddev/mc/baseelectric/ElectricAdvantage.java @@ -7,9 +7,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.fml.DistExecutor; -import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; @@ -28,33 +25,29 @@ import zone.moddev.mc.baseelectric.recipe.RecipeConditions; import zone.moddev.mc.baseelectric.migration.ElectricLegacyMigrations; -/** Forge 1.19.4 entry point for baseElectric. */ +/** Forge 1.20.1 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.12.119041"; + public static final String VERSION = "2.2.13.120011"; public static final Logger LOGGER = LogManager.getLogger(MODID); public static ElectricAdvantage INSTANCE; public final Set PLASTIC_FLUID_MATERIALS = new HashSet<>(); public String LASER_SOUND = "minecraft:block.note_block.bass"; - public ElectricAdvantage() { + public ElectricAdvantage(FMLJavaModLoadingContext context) { INSTANCE = this; ElectricLegacyMigrations.register(); migrateLegacyOreSpawnConfiguration(); migrateLegacyConfiguration(); - ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ElectricAdvantageConfig.SPEC); - FMLJavaModLoadingContext.get().getModEventBus().addListener(ElectricAdvantageConfig::onConfigLoading); - FMLJavaModLoadingContext.get().getModEventBus().addListener(this::commonSetup); - FMLJavaModLoadingContext.get().getModEventBus().addListener(this::enqueueInterModCommunication); - - DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> - FMLJavaModLoadingContext.get().getModEventBus().addListener( - zone.moddev.mc.baseelectric.graphics.ClientRegistration::onClientSetup)); + context.registerConfig(ModConfig.Type.COMMON, ElectricAdvantageConfig.SPEC); + context.getModEventBus().addListener(ElectricAdvantageConfig::onConfigLoading); + context.getModEventBus().addListener(this::commonSetup); + context.getModEventBus().addListener(this::enqueueInterModCommunication); } private static void migrateLegacyOreSpawnConfiguration() { diff --git a/src/main/java/zone/moddev/mc/baseelectric/blocks/ElectricConduitBlock.java b/src/main/java/zone/moddev/mc/baseelectric/blocks/ElectricConduitBlock.java index 563ac5f..520060d 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/blocks/ElectricConduitBlock.java +++ b/src/main/java/zone/moddev/mc/baseelectric/blocks/ElectricConduitBlock.java @@ -1,11 +1,13 @@ package zone.moddev.mc.baseelectric.blocks; import zone.moddev.mc.baseelectric.init.Power; -import net.minecraft.world.level.material.Material; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.level.material.PushReaction; public class ElectricConduitBlock extends cyano.poweradvantage.api.simple.BlockSimplePowerConduit{ public ElectricConduitBlock() { - super(Material.PISTON, 0.75f, 2f/16f, Power.ELECTRIC_POWER); + super(Properties.of().mapColor(MapColor.STONE).pushReaction(PushReaction.BLOCK) + .forceSolidOn().strength(0.75f), 2f/16f, Power.ELECTRIC_POWER); } } diff --git a/src/main/java/zone/moddev/mc/baseelectric/blocks/ElectricScaffoldBlock.java b/src/main/java/zone/moddev/mc/baseelectric/blocks/ElectricScaffoldBlock.java index 724437a..44f245b 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/blocks/ElectricScaffoldBlock.java +++ b/src/main/java/zone/moddev/mc/baseelectric/blocks/ElectricScaffoldBlock.java @@ -1,7 +1,8 @@ package zone.moddev.mc.baseelectric.blocks; import cyano.poweradvantage.api.simple.BlockSimplePowerConduit; -import net.minecraft.world.level.material.Material; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.level.material.PushReaction; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.core.BlockPos; import net.minecraft.world.phys.shapes.VoxelShape; @@ -13,7 +14,8 @@ /** Full-block electric track/scaffold with conduit behavior. */ public class ElectricScaffoldBlock extends BlockSimplePowerConduit { public ElectricScaffoldBlock() { - super(Properties.of(Material.PISTON).strength(0.75F).noOcclusion() + super(Properties.of().mapColor(MapColor.STONE).pushReaction(PushReaction.BLOCK) + .forceSolidOn().strength(0.75F).noOcclusion() .isRedstoneConductor((state, world, pos) -> false), 2.0F / 16.0F, Power.ELECTRIC_POWER); } 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 7ff1a0e..4b24d05 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/blocks/Ore.java +++ b/src/main/java/zone/moddev/mc/baseelectric/blocks/Ore.java @@ -7,12 +7,12 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.material.Material; +import net.minecraft.world.level.material.MapColor; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.Enchantments; -import net.minecraft.world.level.storage.loot.LootContext; +import net.minecraft.world.level.storage.loot.LootParams; import net.minecraft.world.level.storage.loot.parameters.LootContextParams; /** Legacy ElectricAdvantage ore block retaining its original drops and harvest level. */ @@ -34,21 +34,21 @@ private static Supplier fixedDrop(ItemStack oreDrop) { } private Ore(Supplier drop, int pickLevel, int variation) { - super(Properties.of(Material.STONE).requiresCorrectToolForDrops().sound(SoundType.STONE) + super(Properties.of().mapColor(MapColor.STONE).requiresCorrectToolForDrops().sound(SoundType.STONE) .strength(1.5F + pickLevel, 5.0F * (1 + pickLevel))); this.drop = drop; dropRange = variation + 1; } public Ore(int pickLevel) { - super(Properties.of(Material.STONE).requiresCorrectToolForDrops().sound(SoundType.STONE) + super(Properties.of().mapColor(MapColor.STONE).requiresCorrectToolForDrops().sound(SoundType.STONE) .strength(1.5F + pickLevel, 5.0F * (1 + pickLevel))); drop = () -> ItemStack.EMPTY; dropRange = 0; } @Override - public List getDrops(BlockState state, LootContext.Builder context) { + public List getDrops(BlockState state, LootParams.Builder context) { ItemStack tool = context.getOptionalParameter(LootContextParams.TOOL); ItemStack dropItem = drop.get(); if (dropItem.isEmpty() || (tool != null 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 da6e575..ddbf0a6 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/blocks/PumpPipeBlock.java +++ b/src/main/java/zone/moddev/mc/baseelectric/blocks/PumpPipeBlock.java @@ -1,15 +1,12 @@ package zone.moddev.mc.baseelectric.blocks; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.material.Material; +import net.minecraft.world.level.material.MapColor; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.item.ItemStack; -import net.minecraft.core.NonNullList; import net.minecraft.core.BlockPos; import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.level.storage.loot.LootContext; import net.minecraft.world.level.Explosion; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; @@ -17,10 +14,12 @@ /** 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).noLootTable()); } + public PumpPipeBlock() { + super(Properties.of().mapColor(MapColor.METAL).forceSolidOn().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(); } @Override public void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean moving) { 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 d7a3a7f..5f3c7fa 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/graphics/ClientRegistration.java +++ b/src/main/java/zone/moddev/mc/baseelectric/graphics/ClientRegistration.java @@ -9,10 +9,6 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import zone.moddev.mc.baseelectric.ElectricAdvantage; -import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.ItemBlockRenderTypes; -import zone.moddev.mc.baseelectric.init.Blocks; import zone.moddev.mc.baseelectric.init.Entities; /** Client-only renderer registration. */ @@ -20,10 +16,6 @@ public final class ClientRegistration { private ClientRegistration() {} - public static void onClientSetup(FMLClientSetupEvent event) { - event.enqueueWork(() -> ItemBlockRenderTypes.setRenderLayer(Blocks.electric_track, RenderType.cutout())); - } - @SubscribeEvent public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) { event.registerBlockEntityRenderer(Entities.ELECTRIC_DRILL, LaserDrillRenderer::new); diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricDrillBlock.java b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricDrillBlock.java index 788b1ab..6ce1326 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricDrillBlock.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricDrillBlock.java @@ -6,7 +6,6 @@ import cyano.poweradvantage.api.PowerConnectorContext; import cyano.poweradvantage.api.PoweredEntity; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.material.Material; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.Container; @@ -33,7 +32,7 @@ public class ElectricDrillBlock extends GUIBlock implements ITypedConduit { private final ConduitType[] types = {Power.ELECTRIC_POWER}; public ElectricDrillBlock() { - super(Block.Properties.of(Material.PISTON).strength(0.75F).dynamicShape().noOcclusion()); + super(machineProperties(0.75F)); registerDefaultState(getStateDefinition().any() .setValue(ACTIVE, false) .setValue(FACING, Direction.DOWN)); 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 50570a1..776aac5 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricDrillTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricDrillTileEntity.java @@ -223,7 +223,7 @@ public void onDataFieldUpdate() { break; } - if(getLevel().isEmptyBlock(n) || getLevel().getBlockState(n).getMaterial().isReplaceable()){ + if(getLevel().isEmptyBlock(n) || getLevel().getBlockState(n).canBeReplaced()){ // this block is not worth mining, replace it n = n.relative(f); if(n.getY() == 0 || n.getY() == 255){ @@ -457,8 +457,7 @@ private void inventoryTransfer(BlockPos adj, Direction otherFace) { inv.setChanged(); this.setChanged(); return; - } else if(ItemStack.isSame(theirItem, inventory[mySlot]) - && ItemStack.tagMatches(theirItem, inventory[mySlot]) + } else if(ItemStack.isSameItemSameTags(theirItem, inventory[mySlot]) && theirItem.getCount() < theirItem.getMaxStackSize() && theirItem.getCount() < inv.getMaxStackSize()){ theirItem.grow(1); diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricGeneratorBlock.java b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricGeneratorBlock.java index 78c65cc..e00141d 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricGeneratorBlock.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricGeneratorBlock.java @@ -3,7 +3,6 @@ import cyano.poweradvantage.api.ConduitType; import cyano.poweradvantage.api.simple.BlockSimplePowerMachine; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.material.Material; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.Container; import net.minecraft.world.Containers; @@ -20,14 +19,9 @@ public abstract class ElectricGeneratorBlock extends BlockSimplePowerMachine { public static final BooleanProperty ACTIVE = BooleanProperty.create("active"); public ElectricGeneratorBlock() { - this(Material.PISTON); + this(machineProperties(0.75f)); } - public ElectricGeneratorBlock(Material material) { - super(material, 0.75f, Power.ELECTRIC_POWER); - initializeDefaultState(); - } - protected ElectricGeneratorBlock(Block.Properties properties) { super(properties, Power.ELECTRIC_POWER); initializeDefaultState(); diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricMachineBlock.java b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricMachineBlock.java index f8b8f6d..a0bbff0 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricMachineBlock.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricMachineBlock.java @@ -3,7 +3,6 @@ import cyano.poweradvantage.api.ConduitType; import cyano.poweradvantage.api.simple.BlockSimplePowerMachine; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.material.Material; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.Container; import net.minecraft.world.Containers; @@ -21,16 +20,15 @@ public abstract class ElectricMachineBlock extends BlockSimplePowerMachine { public static final BooleanProperty POWERED = BooleanProperty.create("powered"); public ElectricMachineBlock() { - this(Material.PISTON); + this(machineProperties(0.75f)); } - public ElectricMachineBlock(Material material, ConduitType type) { - super(material, 0.75f, type); - initializeDefaultState(); + protected ElectricMachineBlock(Block.Properties properties) { + this(properties, Power.ELECTRIC_POWER); } - protected ElectricMachineBlock(Block.Properties properties) { - super(properties, Power.ELECTRIC_POWER); + protected ElectricMachineBlock(Block.Properties properties, ConduitType type) { + super(properties, type); initializeDefaultState(); } @@ -41,10 +39,6 @@ private void initializeDefaultState() { .setValue(FACING, Direction.NORTH)); } - public ElectricMachineBlock(Material material) { - this(material, Power.ELECTRIC_POWER); - } - @Override protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(ACTIVE, FACING, POWERED); 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 c6c9ac0..689ddde 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricMachineTileEntity.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/ElectricMachineTileEntity.java @@ -205,7 +205,7 @@ protected static ItemStack copyStackOrNull(ItemStack stack) { protected static boolean areItemsEqualNullable(ItemStack left, ItemStack right) { if(isEmptyStack(left)) return isEmptyStack(right); if(isEmptyStack(right)) return false; - return ItemStack.isSame(left, right); + return ItemStack.isSameItem(left, right); } protected static boolean areItemStacksEqualNullable(ItemStack left, ItemStack right) { @@ -264,7 +264,7 @@ public boolean hasSpaceForItemInOutputSlots(ItemStack stack){ int stackLimit = Math.min(dest.getMaxStackSize(), this.getMaxStackSize()); if(dest.getCount() >= stackLimit) continue; int combinedStackSize = item.getCount() + dest.getCount(); - if(ItemStack.isSame(item, dest) ){ + if(ItemStack.isSameItem(item, dest) ){ if(combinedStackSize <= stackLimit){ return true; } else if (item.getCount() <= stackLimit) { @@ -284,7 +284,7 @@ public boolean hasSpaceForItemInInputSlots(ItemStack stack){ int stackLimit = Math.min(dest.getMaxStackSize(), this.getMaxStackSize()); if(dest.getCount() >= stackLimit) continue; int combinedStackSize = item.getCount() + dest.getCount(); - if(ItemStack.isSame(item, dest) ){ + if(ItemStack.isSameItem(item, dest) ){ if(combinedStackSize <= stackLimit){ return true; } else if (item.getCount() <= stackLimit) { @@ -324,7 +324,7 @@ public ItemStack insertItemToInputSlots(ItemStack itemStack){ // empty slot this.setInputSlot(slot,itemStack); return null; - } else if(ItemStack.isSame(itemStack, slotContent)){ + } else if(ItemStack.isSameItem(itemStack, slotContent)){ int stackLimit = Math.min(slotContent.getMaxStackSize(), this.getMaxStackSize()); // found slot with same item if(slotContent.getCount() < stackLimit){ @@ -354,7 +354,7 @@ public ItemStack insertItemToOutputSlots(ItemStack itemStack){ // empty slot this.setOutputSlot(slot,itemStack); return null; - } else if(ItemStack.isSame(itemStack, slotContent)){ + } else if(ItemStack.isSameItem(itemStack, slotContent)){ int stackLimit = Math.min(slotContent.getMaxStackSize(), this.getMaxStackSize()); // found slot with same item if(slotContent.getCount() < stackLimit){ diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberBlock.java b/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberBlock.java index 751a379..dd429f6 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberBlock.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberBlock.java @@ -1,13 +1,12 @@ package zone.moddev.mc.baseelectric.machines; import zone.moddev.mc.baseelectric.init.Power; -import net.minecraft.world.level.material.Material; import net.minecraft.world.level.Level; public class GrowthChamberBlock extends ElectricMachineBlock{ public GrowthChamberBlock(){ - super(Material.PISTON,Power.GROWTHCHAMBER_POWER); + super(machineProperties(0.75f), Power.GROWTHCHAMBER_POWER); } @Override diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberControllerBlock.java b/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberControllerBlock.java index d0951ee..34367c4 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberControllerBlock.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/GrowthChamberControllerBlock.java @@ -5,7 +5,6 @@ import cyano.poweradvantage.api.PowerConnectorContext; import cyano.poweradvantage.api.PoweredEntity; import zone.moddev.mc.basepower.init.Fluids; -import net.minecraft.world.level.material.Material; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.core.BlockPos; import net.minecraft.world.level.Level; @@ -21,7 +20,8 @@ public class GrowthChamberControllerBlock extends cyano.poweradvantage.api.simpl public GrowthChamberControllerBlock() { - super(Material.PISTON, 0.75f, Power.GROWTHCHAMBER_POWER, Power.ELECTRIC_POWER, Fluids.fluidConduit_general); + super(machineProperties(0.75f), + Power.GROWTHCHAMBER_POWER, Power.ELECTRIC_POWER, Fluids.fluidConduit_general); } @Override diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/HydroelectricGeneratorBlock.java b/src/main/java/zone/moddev/mc/baseelectric/machines/HydroelectricGeneratorBlock.java index 723e50c..0b03e54 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/HydroelectricGeneratorBlock.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/HydroelectricGeneratorBlock.java @@ -19,6 +19,6 @@ public ElectricGeneratorTileEntity newBlockEntity(net.minecraft.core.BlockPos po public boolean canSurvive(BlockState state, LevelReader worldIn, BlockPos pos) { return super.canSurvive(state, worldIn, pos) - && !worldIn.getBlockState(pos.below()).getMaterial().isSolid(); + && !worldIn.getBlockState(pos.below()).blocksMotion(); } } diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/LaserTurretBlock.java b/src/main/java/zone/moddev/mc/baseelectric/machines/LaserTurretBlock.java index fcf8d08..8b37e85 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/LaserTurretBlock.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/LaserTurretBlock.java @@ -1,7 +1,6 @@ package zone.moddev.mc.baseelectric.machines; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.material.Material; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; @@ -24,8 +23,7 @@ public class LaserTurretBlock extends ElectricMachineBlock { // Evil laser turrets target players and not hostile mobs public final boolean evil; public LaserTurretBlock(boolean isEvil) { - super(Block.Properties.of(Material.PISTON) - .strength(5.0F, 100.0F).dynamicShape().noOcclusion()); + super(machineProperties(5.0F).strength(5.0F, 100.0F)); this.evil = isEvil; } diff --git a/src/main/java/zone/moddev/mc/baseelectric/machines/PhotovoltaicGeneratorBlock.java b/src/main/java/zone/moddev/mc/baseelectric/machines/PhotovoltaicGeneratorBlock.java index d99f095..f6d2f1c 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/machines/PhotovoltaicGeneratorBlock.java +++ b/src/main/java/zone/moddev/mc/baseelectric/machines/PhotovoltaicGeneratorBlock.java @@ -1,15 +1,15 @@ package zone.moddev.mc.baseelectric.machines; import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.material.Material; +import net.minecraft.world.level.material.MapColor; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.Level; public class PhotovoltaicGeneratorBlock extends ElectricGeneratorBlock{ public PhotovoltaicGeneratorBlock(){ - super(net.minecraft.world.level.block.Block.Properties.of(Material.STONE) - .strength(0.75F).sound(SoundType.GLASS).dynamicShape() + super(net.minecraft.world.level.block.Block.Properties.of().mapColor(MapColor.STONE) + .forceSolidOn().strength(0.75F).sound(SoundType.GLASS).dynamicShape() .isRedstoneConductor((state, world, pos) -> true)); } 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 e7a2565..e24d97c 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 @@ -55,7 +55,7 @@ public boolean matches(ItemRecord record) { public boolean matches(ItemStack candidate) { if (candidate == null || candidate.isEmpty()) return false; for (ItemStack choice : choices) { - if (ItemStack.isSame(choice, candidate)) return true; + if (ItemStack.isSameItem(choice, candidate)) return true; } return false; } diff --git a/src/main/java/zone/moddev/mc/baseelectric/util/crafting/ItemRecord.java b/src/main/java/zone/moddev/mc/baseelectric/util/crafting/ItemRecord.java index c9e3a6c..0890725 100644 --- a/src/main/java/zone/moddev/mc/baseelectric/util/crafting/ItemRecord.java +++ b/src/main/java/zone/moddev/mc/baseelectric/util/crafting/ItemRecord.java @@ -22,6 +22,6 @@ public ItemStack getItem() { @Override public boolean equals(Object other) { return other == this || other instanceof ItemRecord - && ItemStack.isSame(item, ((ItemRecord) other).item); + && ItemStack.isSameItem(item, ((ItemRecord) other).item); } } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 22c5d78..111623b 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.11.119041,3.0.0)" +versionRange="[2.4.12.120011,3.0.0)" ordering="AFTER" side="BOTH" [[dependencies.baseelectric]] modId="basemetals" mandatory=false -versionRange="[3.1.2.119041,4.0.0)" +versionRange="[3.1.2.120011,4.0.0)" ordering="AFTER" side="BOTH" diff --git a/src/main/resources/assets/baseelectric/models/block/electric_track_0.json b/src/main/resources/assets/baseelectric/models/block/electric_track_0.json index ee1f638..0689fdc 100644 --- a/src/main/resources/assets/baseelectric/models/block/electric_track_0.json +++ b/src/main/resources/assets/baseelectric/models/block/electric_track_0.json @@ -1,5 +1,6 @@ { "parent": "basepower:block/steel_frame", + "render_type": "minecraft:cutout", "textures": { "texture": "baseelectric:blocks/electric_conduit", "particle": "baseelectric:materials/machine_case", diff --git a/src/main/resources/assets/baseelectric/models/block/electric_track_densuw.json b/src/main/resources/assets/baseelectric/models/block/electric_track_densuw.json index 01fa1f2..80cc081 100644 --- a/src/main/resources/assets/baseelectric/models/block/electric_track_densuw.json +++ b/src/main/resources/assets/baseelectric/models/block/electric_track_densuw.json @@ -1,5 +1,6 @@ { "parent": "basepower:block/steel_frame", + "render_type": "minecraft:cutout", "textures": { "texture": "baseelectric:blocks/electric_conduit", "particle": "baseelectric:materials/machine_case", diff --git a/src/main/resources/assets/baseelectric/models/block/electric_track_desuw.json b/src/main/resources/assets/baseelectric/models/block/electric_track_desuw.json index e02dc5a..e1d8293 100644 --- a/src/main/resources/assets/baseelectric/models/block/electric_track_desuw.json +++ b/src/main/resources/assets/baseelectric/models/block/electric_track_desuw.json @@ -1,5 +1,6 @@ { "parent": "basepower:block/steel_frame", + "render_type": "minecraft:cutout", "textures": { "texture": "baseelectric:blocks/electric_conduit", "particle": "baseelectric:materials/machine_case", diff --git a/src/main/resources/assets/baseelectric/models/block/electric_track_dnu.json b/src/main/resources/assets/baseelectric/models/block/electric_track_dnu.json index c0e6adb..1e9ed3a 100644 --- a/src/main/resources/assets/baseelectric/models/block/electric_track_dnu.json +++ b/src/main/resources/assets/baseelectric/models/block/electric_track_dnu.json @@ -1,5 +1,6 @@ { "parent": "basepower:block/steel_frame", + "render_type": "minecraft:cutout", "textures": { "texture": "baseelectric:blocks/electric_conduit", "particle": "baseelectric:materials/machine_case", diff --git a/src/main/resources/assets/baseelectric/models/block/electric_track_ensw.json b/src/main/resources/assets/baseelectric/models/block/electric_track_ensw.json index e6f32ab..8943d93 100644 --- a/src/main/resources/assets/baseelectric/models/block/electric_track_ensw.json +++ b/src/main/resources/assets/baseelectric/models/block/electric_track_ensw.json @@ -1,5 +1,6 @@ { "parent": "basepower:block/steel_frame", + "render_type": "minecraft:cutout", "textures": { "texture": "baseelectric:blocks/electric_conduit", "particle": "baseelectric:materials/machine_case", diff --git a/src/main/resources/assets/baseelectric/models/block/electric_track_n.json b/src/main/resources/assets/baseelectric/models/block/electric_track_n.json index e8e811c..21430aa 100644 --- a/src/main/resources/assets/baseelectric/models/block/electric_track_n.json +++ b/src/main/resources/assets/baseelectric/models/block/electric_track_n.json @@ -1,5 +1,6 @@ { "parent": "basepower:block/steel_frame", + "render_type": "minecraft:cutout", "textures": { "texture": "baseelectric:blocks/electric_conduit", "particle": "baseelectric:materials/machine_case", diff --git a/src/main/resources/assets/baseelectric/models/block/electric_track_ns.json b/src/main/resources/assets/baseelectric/models/block/electric_track_ns.json index 4fc026e..6cd9a2b 100644 --- a/src/main/resources/assets/baseelectric/models/block/electric_track_ns.json +++ b/src/main/resources/assets/baseelectric/models/block/electric_track_ns.json @@ -1,5 +1,6 @@ { "parent": "basepower:block/steel_frame", + "render_type": "minecraft:cutout", "textures": { "texture": "baseelectric:blocks/electric_conduit", "particle": "baseelectric:materials/machine_case", diff --git a/src/main/resources/assets/baseelectric/models/block/electric_track_nsuw.json b/src/main/resources/assets/baseelectric/models/block/electric_track_nsuw.json index 7f084f6..abe7f16 100644 --- a/src/main/resources/assets/baseelectric/models/block/electric_track_nsuw.json +++ b/src/main/resources/assets/baseelectric/models/block/electric_track_nsuw.json @@ -1,5 +1,6 @@ { "parent": "basepower:block/steel_frame", + "render_type": "minecraft:cutout", "textures": { "texture": "baseelectric:blocks/electric_conduit", "particle": "baseelectric:materials/machine_case", diff --git a/src/main/resources/assets/baseelectric/models/block/electric_track_nsw.json b/src/main/resources/assets/baseelectric/models/block/electric_track_nsw.json index a796d3a..6672584 100644 --- a/src/main/resources/assets/baseelectric/models/block/electric_track_nsw.json +++ b/src/main/resources/assets/baseelectric/models/block/electric_track_nsw.json @@ -1,5 +1,6 @@ { "parent": "basepower:block/steel_frame", + "render_type": "minecraft:cutout", "textures": { "texture": "baseelectric:blocks/electric_conduit", "particle": "baseelectric:materials/machine_case", diff --git a/src/main/resources/assets/baseelectric/models/block/electric_track_nuw.json b/src/main/resources/assets/baseelectric/models/block/electric_track_nuw.json index 82adb35..c0d9a8a 100644 --- a/src/main/resources/assets/baseelectric/models/block/electric_track_nuw.json +++ b/src/main/resources/assets/baseelectric/models/block/electric_track_nuw.json @@ -1,5 +1,6 @@ { "parent": "basepower:block/steel_frame", + "render_type": "minecraft:cutout", "textures": { "texture": "baseelectric:blocks/electric_conduit", "particle": "baseelectric:materials/machine_case", diff --git a/src/main/resources/assets/baseelectric/models/block/electric_track_nw.json b/src/main/resources/assets/baseelectric/models/block/electric_track_nw.json index 831016b..7abb988 100644 --- a/src/main/resources/assets/baseelectric/models/block/electric_track_nw.json +++ b/src/main/resources/assets/baseelectric/models/block/electric_track_nw.json @@ -1,5 +1,6 @@ { "parent": "basepower:block/steel_frame", + "render_type": "minecraft:cutout", "textures": { "texture": "baseelectric:blocks/electric_conduit", "particle": "baseelectric:materials/machine_case", diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index f71b9ae..baeea15 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,7 +1,6 @@ { "pack":{ - "pack_format":13, - "forge:server_data_pack_format":12, + "pack_format":15, "description":"baseElectric" } } diff --git a/src/test/java/zone/moddev/mc/baseelectric/blocks/NativeBlockContractTest.java b/src/test/java/zone/moddev/mc/baseelectric/blocks/NativeBlockContractTest.java index 7649712..00c1790 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/blocks/NativeBlockContractTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/blocks/NativeBlockContractTest.java @@ -14,10 +14,17 @@ import net.minecraft.world.level.Explosion; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.Level; +import net.minecraft.world.level.EmptyBlockGetter; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.level.material.PushReaction; +import net.minecraft.world.level.storage.loot.LootParams; import org.junit.BeforeClass; import org.junit.Test; import zone.moddev.mc.baseelectric.init.Power; import zone.moddev.mc.baseelectric.machines.ElectricDrillBlock; +import zone.moddev.mc.baseelectric.machines.ElectricFurnaceBlock; +import zone.moddev.mc.baseelectric.machines.GrowthChamberBlock; +import zone.moddev.mc.baseelectric.machines.GrowthChamberControllerBlock; import zone.moddev.mc.baseelectric.machines.LaserTurretBlock; import zone.moddev.mc.baseelectric.machines.PhotovoltaicGeneratorBlock; import static org.junit.Assert.*; @@ -76,9 +83,34 @@ public class NativeBlockContractTest { } @Test public void hiddenPumpPipeRemainsNonSolidAndSolarPanelRemainsSolid() { - assertFalse(new PumpPipeBlock().defaultBlockState().canOcclude()); + BlockState pipe = new PumpPipeBlock().defaultBlockState(); + pipe.initCache(); + assertFalse(pipe.canOcclude()); + assertTrue("The metal pipe was solid independently of its occlusion shape", pipe.blocksMotion()); + assertSame(PushReaction.NORMAL, pipe.getPistonPushReaction()); PhotovoltaicGeneratorBlock panel = new PhotovoltaicGeneratorBlock(); + panel.defaultBlockState().initCache(); assertTrue("The 1.14 photovoltaic block explicitly overrides isSolid to true", panel.defaultBlockState().canOcclude()); + assertTrue(panel.defaultBlockState().blocksMotion()); assertTrue(panel.defaultBlockState().isRedstoneConductor(net.minecraft.world.level.EmptyBlockGetter.INSTANCE, BlockPos.ZERO)); } + + @Test public void removedPistonMaterialKeepsItsMapColorAndBlocksPistons() { + for (var block : java.util.List.of(new ElectricConduitBlock(), new ElectricScaffoldBlock(), + new ElectricDrillBlock(), new ElectricFurnaceBlock(), new GrowthChamberBlock(), + new GrowthChamberControllerBlock(), new LaserTurretBlock(false))) { + BlockState state = block.defaultBlockState(); + // Forge fills this cache for registered blocks after the registry events. + state.initCache(); + String name = block.getClass().getSimpleName(); + assertSame(name, PushReaction.BLOCK, state.getPistonPushReaction()); + assertTrue(name, state.blocksMotion()); + assertSame(name, MapColor.STONE, state.getMapColor(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)); + } + } + + @Test public void hiddenPumpPipeUsesNativeNoLootTable() { + BlockState state = new PumpPipeBlock().defaultBlockState(); + assertTrue(state.getDrops(new LootParams.Builder(null)).isEmpty()); + } } 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 a58ccca..26e1fe4 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/entities/ElectricOperationalHydroturbineAnchorTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/entities/ElectricOperationalHydroturbineAnchorTest.java @@ -101,7 +101,7 @@ private static Level world() throws Exception { private static final class NoTickWorld extends ServerLevel { private NoTickWorld() { - super(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, null); } } 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 2278feb..6d5757b 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, false, 0L, Collections.emptyList(), false); + super(null, null, null, null, null, null, null, false, 0L, Collections.emptyList(), false, null); } @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, false, 0L, Collections.emptyList(), false); + super(null, null, null, null, null, null, null, false, 0L, Collections.emptyList(), false, null); } @Override public BlockHitResult clip(ClipContext context) { traces++; 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 6db3037..1c77a88 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalPumpPickupTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalPumpPickupTest.java @@ -12,7 +12,7 @@ import net.minecraft.world.level.block.BucketPickup; import net.minecraft.world.level.block.SlabBlock; import net.minecraft.world.level.block.StairBlock; -import net.minecraft.world.level.material.Material; +import net.minecraft.world.level.material.MapColor; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.material.Fluids; @@ -180,7 +180,7 @@ private static final class PumpWorld extends ServerLevel { Map blocks; int writes; private PumpWorld() { - super(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, null); } @Override public boolean hasChunkAt(BlockPos pos) { return true; } @Override public BlockState getBlockState(BlockPos pos) { @@ -199,7 +199,7 @@ private static final class RefusingSource extends Block implements BucketPickup final boolean returnNull; int calls; RefusingSource(boolean returnNull) { - super(Block.Properties.of(Material.STONE)); + super(Block.Properties.of().mapColor(MapColor.STONE)); this.returnNull = returnNull; defaultBlockState().initCache(); } 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 6c5e1ff..6a75ca0 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, false, 0L, Collections.emptyList(), false); + super(null, null, null, null, null, null, null, false, 0L, Collections.emptyList(), false, null); } @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 bd5f8d7..b6565fd 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalSoundBroadcastTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/machines/ElectricOperationalSoundBroadcastTest.java @@ -108,7 +108,7 @@ private static final class QueryRejectingWorld extends ServerLevel { int playerListReads; private QueryRejectingWorld() { - super(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, null); } @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 39dbfd9..a0737bb 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/resources/BlockLootResourceTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/resources/BlockLootResourceTest.java @@ -33,10 +33,23 @@ public class BlockLootResourceTest { assertFalse(Files.exists(root.resolve("pump_pipe_electric.json"))); } - @Test public void resourcePackDeclaresTheNativeOneNineteenFourFormats() throws Exception { + @Test public void resourcePackDeclaresTheNativeOneTwentyOneFormat() throws Exception { JsonObject pack = read(Paths.get("src/main/resources/pack.mcmeta")).getAsJsonObject("pack"); - assertEquals(13, pack.get("pack_format").getAsInt()); - assertEquals(12, pack.get("forge:server_data_pack_format").getAsInt()); + assertEquals(15, pack.get("pack_format").getAsInt()); + assertFalse(pack.has("forge:server_data_pack_format")); + } + + @Test public void eachTrackStateUsesTheNativeCutoutRenderLayer() throws Exception { + Path assets = Paths.get("src/main/resources/assets/baseelectric"); + var variants = read(assets.resolve("blockstates/electric_track.json")).getAsJsonObject("variants"); + assertEquals(64, variants.size()); + for (var variant : variants.entrySet()) { + String model = variant.getValue().getAsJsonObject().get("model").getAsString(); + assertTrue(variant.getKey(), model.startsWith("baseelectric:block/electric_track_")); + JsonObject modelJson = read(assets.resolve("models/" + model.substring("baseelectric:".length()) + ".json")); + assertTrue(model + " does not declare a render layer", modelJson.has("render_type")); + assertEquals(model, "minecraft:cutout", modelJson.get("render_type").getAsString()); + } } 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 d7dd98d..3c3a8a4 100644 --- a/src/test/java/zone/moddev/mc/baseelectric/resources/BuildWorkflowContractTest.java +++ b/src/test/java/zone/moddev/mc/baseelectric/resources/BuildWorkflowContractTest.java @@ -34,10 +34,11 @@ 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("bash .github/scripts/forge45-dependencies.sh pins"); - int dependencyBuild = source.indexOf("bash .github/scripts/forge45-dependencies.sh build"); + int published = source.indexOf("bash .github/verify-published-pins.sh"); + int pins = source.indexOf("bash .github/scripts/forge47-dependencies.sh pins"); + int dependencyBuild = source.indexOf("bash .github/scripts/forge47-dependencies.sh build"); assertTrue(policy > 0); - assertTrue(policy < pins); + assertTrue(policy < published && published < pins); for (String repository : new String[] {"OreSpawn", "BaseMetals", "basePower"}) { assertTrue(pins < source.indexOf("repository: MinecraftModDevelopmentMods/" + repository)); } @@ -45,28 +46,30 @@ public class BuildWorkflowContractTest { assertTrue(source.contains("ref: ${{ steps.pins.outputs.basemetals_ref }}")); assertTrue(source.contains("ref: ${{ steps.pins.outputs.power_ref }}")); assertTrue(dependencyBuild > source.indexOf("ref: ${{ steps.pins.outputs.power_ref }}")); - assertTrue(source.indexOf("\"-Pforge45MavenizerJar=$MAVENIZER\"") > dependencyBuild); + assertTrue(source.indexOf("\"-Pforge47MavenizerJar=$MAVENIZER\"") > dependencyBuild); assertTrue(source.contains("MAVENIZER: ${{ steps.dependencies.outputs.mavenizer }}")); - assertTrue(source.contains("branches: [master-1.19.4")); + assertTrue(source.contains("branches: [master-1.20.1")); assertFalse(source.contains("1.17.1")); + if (workflow.equals("ci.yml")) { + assertTrue(source.contains("POWER_PLAYER: ${{ steps.public-pins.outputs.power_player }}")); + } } - String script = Files.readString(Paths.get(".github/scripts/forge45-dependencies.sh")); - assertTrue(script.contains("$(value poweradvantage_pin_state)") && script.contains("'PUBLISHED_RELEASE'")); + String script = Files.readString(Paths.get(".github/scripts/forge47-dependencies.sh")); + assertTrue(script.contains("$(value poweradvantage_pin_state)") && script.contains("'MERGED_SOURCE'")); 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")); + assertTrue(script.contains("check_hash \"$mavenizer\" '416DCA4797BC55826F0DDCF03FDC405F1990FC15C2133FA27A1F143BF7976177'")); 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")); + assertFalse(metals.contains("-I ") || metals.contains("-Pforge47Mavenizer")); String power = script.substring(powerBuild); - assertFalse(power.contains("-I ") || power.contains("--init-script")); - assertTrue(power.contains("\"-Pforge45MavenizerJar=$mavenizer\"")); + assertFalse(power.contains("-I ") || power.contains("MavenizerScript")); + assertTrue(power.contains("\"-Pforge47MavenizerJar=$mavenizer\"")); assertTrue(script.contains("git -C \"$basemetals\" diff --exit-code HEAD --")); assertTrue(script.contains("git -C \"$power\" diff --exit-code HEAD --")); } diff --git a/tools/verification/Invoke-ElectricPackagedSmoke.ps1 b/tools/verification/Invoke-ElectricPackagedSmoke.ps1 index 03815b6..01323de 100644 --- a/tools/verification/Invoke-ElectricPackagedSmoke.ps1 +++ b/tools/verification/Invoke-ElectricPackagedSmoke.ps1 @@ -30,7 +30,7 @@ $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.19.4 dedicated server smoke; no client or migration proof' + status = 'FAIL'; scope = 'Packaged, loopback-only Forge 1.20.1 dedicated server smoke; no client or migration proof' baseMetalsIncluded = !$WithoutBaseMetals seed = $seed; java = $javaPath; inputs = @(); checks = [ordered]@{}; exitCode = $null; error = $null } @@ -63,10 +63,10 @@ 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 ($target -cne '1.20.1-47.4.10' -or $pins.mod_version -cne '2.2.13.120011') { + throw 'This smoke runner is bound to baseElectric 2.2.13.120011 on Forge 47.4.10.' } - if ($pins.poweradvantage_pin_state -ne 'PUBLISHED_RELEASE' -or $PowerSha256 -ne $pins.poweradvantage_runtime_sha256) { + if ($pins.poweradvantage_pin_state -ne 'MERGED_SOURCE' -or $PowerSha256 -ne $pins.poweradvantage_runtime_sha256) { throw 'Packaged smoke requires the exact source-pinned basePower player JAR.' } $receipt.powerSourceRef = $pins.poweradvantage_source_ref @@ -110,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=119041', 'generate-structures=false', + 'level-name=electric-smoke', 'level-seed=120011', 'generate-structures=false', 'view-distance=3', 'simulation-distance=3', 'spawn-protection=0' )) $start = [Diagnostics.ProcessStartInfo]::new($javaPath) diff --git a/tools/verification/Verify-ElectricEcj.ps1 b/tools/verification/Verify-ElectricEcj.ps1 index 90d8294..abff69f 100644 --- a/tools/verification/Verify-ElectricEcj.ps1 +++ b/tools/verification/Verify-ElectricEcj.ps1 @@ -61,17 +61,17 @@ function Invoke-Ecj([string]$Name, [string[]]$Sources, [string[]]$Libraries) { } $report = [ordered]@{ status = 'RUNNING'; project = $ProjectRoot; evidence = $Evidence - scope = 'Pinned local baseElectric 1.19.4 candidate; fresh ECJ main/test/helper outputs and worldless Java 17 JUnit; no Gradle or Minecraft launch' + scope = 'Pinned local baseElectric 1.20.1 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.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 + if ($properties.mod_id -ne 'baseelectric' -or $properties.mod_version -ne '2.2.13.120011' -or + $properties.minecraft_version -ne '1.20.1' -or $properties.forge_version -ne '47.4.10' -or + $properties.poweradvantage_version -ne '2.4.12.120011' -or $properties.basemetals_version -ne '3.1.2.120011' -or + $properties.orespawn_version -ne '4.1.0.120011' -or + $properties.poweradvantage_pin_state -ne 'MERGED_SOURCE' -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 @@ -80,7 +80,7 @@ try { $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.19.4 inputs' + throw 'This verifier requires the source-pinned baseElectric 1.20.1 inputs' } $report.compiler = Get-Pin $Compiler if ($report.compiler.sha256 -ne 'D0D43F8E2D7003E5EFED612E2CBB5F01870043397D8F1BBE536FD9128F4FCBF7') { throw 'ECJ compiler pin mismatch' } @@ -144,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 86) { throw 'Unexpected Electric 1.19.4 source/test inventory' } + if ($classes.Count -ne 27 -or $expected -ne 99 -or $sources.test.Count -ne 28 -or $sources.main.Count -ne 86) { throw 'Unexpected Electric 1.20.1 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 400f6bb..4c73fd7 100644 --- a/tools/verify-native-port.js +++ b/tools/verify-native-port.js @@ -5,318 +5,113 @@ const { execFileSync } = require('node:child_process'); const { createHash } = require('node:crypto'); const root = path.resolve(__dirname, '..'); -const baseline = 'c5c8c9ea1d6f2010b8cff61e4bfae86f4c96c978'; -const nativeBaseline = 'ce6b075f1d0e417e46e1742b9a2ca856b24dc67f'; -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/') - ? name : name.replaceAll('/electricadvantage/', '/baseelectric/'); -// Some historical blobs predate the checkout's canonical LF policy. -const migrateText = source => source.replaceAll('\r\n', '\n').replaceAll('electricadvantage', 'baseelectric') - .replaceAll('zone.moddev.mc.poweradvantage', 'zone.moddev.mc.basepower') - .replaceAll('poweradvantage:', 'basepower:'); -const read = name => fs.readFileSync(path.join(root, name), 'utf8'); +const baseline = '013737d4cc41947e8b6fa42f4bf926a1fa5ab998'; const git = (...args) => execFileSync('git', args, { cwd: root }); -const tracked = git('ls-tree', '-r', '--name-only', baseline).toString().trim().split('\n'); -const portBaseFiles = git('ls-tree', '-r', '--name-only', portBase, 'src').toString().trim().split('\n'); -const sha256 = value => createHash('sha256').update(value).digest('hex'); -const results = []; -const properties = Object.fromEntries(read('gradle.properties').split(/\r?\n/) - .filter(line => line && !line.startsWith('#')).map(line => { - const i = line.indexOf('='); return [line.slice(0, i), line.slice(i + 1)]; - })); +const old = file => git('show', baseline + ':' + file); +const read = file => fs.readFileSync(path.join(root, file)); +const json = file => JSON.parse(read(file)); +const sha256 = data => createHash('sha256').update(data).digest('hex'); +const tracked = git('ls-tree', '-r', '--name-only', baseline, 'src').toString().trim().split('\n'); +const javaRoot = 'src/main/java/zone/moddev/mc/baseelectric/'; +const assets = 'src/main/resources/assets/baseelectric/'; +const track = file => file.startsWith(assets + 'models/block/electric_track_') && file.endsWith('.json'); +const checks = []; function check(name, run) { - try { run(); results.push({ name, passed: true }); } - catch (error) { results.push({ name, passed: false, error: error.message }); } -} -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) || []; + try { run(); checks.push({name, pass: true}); } + catch (error) { checks.push({name, pass: false, error: error.message}); } } -const strings = source => tokens(source).filter(t => t.startsWith('"') - && !['"2.2.11.118021"', '"2.2.12.119041"'].includes(t)).sort(); -const numbers = source => tokens(source).filter(t => /^\d|^\.\d/.test(t)); -const production = portBaseFiles.filter(n => n.startsWith(javaRoot) && n.endsWith('.java')); -const entry = javaRoot + 'ElectricAdvantage.java'; -const migration = javaRoot + 'migration/ElectricLegacyMigrations.java'; -function nativeUnlockFromLegacy(source) { - const document = JSON.parse(source); - let converted = 0; - for (const criterion of Object.values(document.criteria || {})) { - if (criterion.trigger !== 'minecraft:inventory_changed') continue; - for (const predicate of criterion.conditions?.items || []) { - if (!Object.hasOwn(predicate, 'item')) continue; - assert.deepEqual(Object.keys(predicate), ['item']); - assert.equal(typeof predicate.item, 'string'); - const id = predicate.item; - delete predicate.item; - predicate.items = [id]; - converted++; - } - } - return {document, converted}; -} +check('Exact Forge 47 and release identity', () => { + const props = Object.fromEntries(read('gradle.properties').toString().split(/\r?\n/) + .filter(line => /^[a-z][a-z_\d]*=/.test(line)).map(line => line.split(/=(.*)/s).slice(0, 2))); + for (const [key, value] of Object.entries({ + minecraft_version: '1.20.1', forge_version: '47.4.10', mapping_channel: 'official', + mapping_version: '1.20.1', mcp_version: '20230612.114412', java_version: '17', + mod_id: 'baseelectric', mod_name: 'baseElectric', mod_version: '2.2.13.120011', + poweradvantage_version: '2.4.12.120011', basemetals_version: '3.1.2.120011', + orespawn_version: '4.1.0.120011', curseforge_project_id: '1783006', + cf_requirements: 'basepower,base-metals' + })) assert.equal(props[key], value, key); + assert.deepEqual(json('src/main/resources/pack.mcmeta').pack, {pack_format:15, description:'baseElectric'}); + const mods = read('src/main/resources/META-INF/mods.toml').toString(); + assert.match(mods, /modId="basepower"\s+mandatory=true\s+versionRange="\[2\.4\.12\.120011,3\.0\.0\)"/); + assert.match(mods, /modId="basemetals"\s+mandatory=false\s+versionRange="\[3\.1\.2\.120011,4\.0\.0\)"/); +}); -function assertNativeUnlock(source, actual) { - const expected = nativeUnlockFromLegacy(source); - assert.deepEqual(JSON.parse(actual), expected.document); - return expected.converted; +function checkTrackModel(file, model) { + assert.equal(model.render_type, 'minecraft:cutout', file + ' lacks its native cutout layer'); + const { render_type, ...unchanged } = model; + assert.deepEqual(unchanged, JSON.parse(old(file)), file + ' changed beyond the render layer'); } -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.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: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\.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('All 64 track states declare cutout without changing any geometry', () => { + const { variants } = json(assets + 'blockstates/electric_track.json'); + assert.equal(Object.keys(variants).length, 64); + const models = new Set(Object.values(variants).map(variant => variant.model)); + for (const id of models) { + assert.match(id, /^baseelectric:block\/electric_track_/); + const file = assets + 'models/' + id.split(':')[1] + '.json'; + checkTrackModel(file, json(file)); + } + for (const file of tracked.filter(track)) checkTrackModel(file, json(file)); }); -check('Published Power and companion pins are exact', () => { - assert.equal(properties.poweradvantage_source_repository, 'MinecraftModDevelopmentMods/basePower'); - const build = read('build.gradle'); - assert.match(build, /poweradvantage_pin_state != 'PUBLISHED_RELEASE'/); - 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:'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('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/') - && !['src/main/resources/pack.mcmeta', 'src/main/resources/META-INF/mods.toml'].includes(n))) { - const before = git('show', `${baseline}:${name}`); - const target = migratePath(name); - if (/\/shaders\/core\/unshaded\.(vsh|json)$/.test(target)) continue; // Exact 1.18 delta audited below. - const expected = /\.(json|fsh|vsh|mcmeta)$/.test(name) - ? Buffer.from(migrateText(before.toString())) : before; - if (name.startsWith('src/main/resources/data/electricadvantage/advancements/recipes/')) { - const converted = nativeUnlockFromLegacy(expected.toString()).converted; - if (converted) { - assert.equal(assertNativeUnlock(expected.toString(), read(target)), converted, target); - changedFiles++; - changedPredicates += converted; - continue; - } - } - assert.deepEqual(fs.readFileSync(path.join(root, target)), expected, target); - } - assert.equal(changedFiles, 19); - assert.equal(changedPredicates, 21); -}); -check('Advancement compatibility allowance rejects unrelated changes and old triggers', () => { - const before = JSON.stringify({criteria:{has_input:{trigger:'minecraft:inventory_changed', - conditions:{items:[{item:'minecraft:apple'}]}}}, rewards:{recipes:['electricadvantage:example']}, - requirements:[['has_input']]}); - const native = nativeUnlockFromLegacy(before).document; - assert.equal(assertNativeUnlock(before, JSON.stringify(native)), 1); - assert.throws(() => assertNativeUnlock(before, before)); - for (const mutate of [ - document => document.criteria.has_input.conditions.items[0].items.push('minecraft:dirt'), - document => document.rewards.recipes.push('electricadvantage:another'), - document => document.requirements.push(['has_input']) - ]) { - const altered = JSON.parse(JSON.stringify(native)); - mutate(altered); - assert.throws(() => assertNativeUnlock(before, JSON.stringify(altered))); - } -}); -check('Registry, asset, provider and NBT string identities retained', () => { - for (const name of production) { - 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(strings(after), expected, name); - } -}); -check('Machine balance and renderer test tolerances retained', () => { - const renderer = 'src/test/java/zone/moddev/mc/baseelectric/graphics/RendererBufferContractTest.java'; - for (const name of [...production, renderer]) { - 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;'); - } - if (name.endsWith('/init/Blocks.java') || name.endsWith('/init/Items.java')) { - before = before.replace(/\.toArray\(new (?:Block|Item)\[0\]\)/, ''); - } - 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 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('resourcePackDeclaresTheNativeOneEighteenFormats', - 'resourcePackDeclaresTheNativeOneNineteenFourFormats')); - count += expected.length; - assert.deepEqual(methods(after), expected, name); - assert(!/@Ignore|Assume\.|assumeTrue\(|assumeFalse\(/.test(after), name); - } - assert.equal(count, 96); -}); -check('Native constructors, factories and tag-only persistence', () => { - const directory = path.join(root, javaRoot, 'machines'); - for (const name of fs.readdirSync(directory).filter(n => n.endsWith('TileEntity.java'))) { - const source = fs.readFileSync(path.join(directory, name), 'utf8'); - assert(!/public \w+TileEntity\(\s*\)/.test(source), name); - assert(!/void load\(BlockState|super\.load\(state,|setPosition\(/.test(source), name); - assert.match(source, /super\([^;]*pos, state,/, name); - } - const entities = read(javaRoot + 'init/Entities.java'); - assert(entities.includes('BlockEntityType.BlockEntitySupplier')); - assert(entities.includes('.setTrackingRange(64).setUpdateInterval(1).setShouldReceiveVelocityUpdates(true)')); - const move = read(javaRoot + 'machines/ElectricDrillTileEntity.java'); - assert(move.includes('te.load(dataTransfer)')); - assert(move.includes('te.subtractEnergy(ENERGY_COST_MOVE, getEnergyType())')); - assert(read(javaRoot + 'machines/LaserTurretTileEntity.java').includes('handleUpdateTag(CompoundTag tag)')); -}); -check('Native helper ticker, bucket pickup, renderer context and shader', () => { - assert(read(javaRoot + 'machines/LEDBlock.java').includes('BlockEntityTicker getTicker')); - assert(read(javaRoot + 'machines/ElectricPumpTileEntity.java').includes('.pickupBlock(w, fluidSource, blockstate)')); - for (const name of ['LaserDrillRenderer', 'LaserTurretRenderer']) { - const source = read(javaRoot + `graphics/${name}.java`); - assert(source.includes('implements BlockEntityRenderer<')); - assert(source.includes('BlockEntityRendererProvider.Context')); - } - const render = read(javaRoot + 'graphics/ElectricRenderTypes.java'); - assert(render.includes('DefaultVertexFormat.NEW_ENTITY')); - assert(render.includes('VertexFormat.Mode.QUADS')); - assert(render.includes('new ShaderStateShard(() -> unshadedShader)')); - const shader = read('src/main/resources/assets/baseelectric/shaders/core/unshaded.vsh'); - assert(shader.includes('vertexColor = Color;')); - assert(shader.includes('lightMapColor = texelFetch(Sampler2, UV2 / 16, 0);')); - assert(shader.includes('overlayColor = texelFetch(Sampler1, UV1, 0);')); - const registration = read(javaRoot + 'graphics/ClientRegistration.java'); - assert(registration.includes('EntityRenderersEvent.RegisterRenderers')); -}); -check('Native harvest tags preserve lithium and sulfur tool tiers', () => { - const tag = name => JSON.parse(read(`src/main/resources/data/minecraft/tags/blocks/${name}.json`)); - assert.deepEqual(tag('mineable/pickaxe'), {replace:false, values:['baseelectric:li_ore','baseelectric:sulfur_ore']}); - assert.deepEqual(tag('needs_stone_tool'), {replace:false, values:['baseelectric:li_ore']}); - assert.equal((read(javaRoot + 'blocks/Ore.java').match(/requiresCorrectToolForDrops\(\)/g) || []).length, 2); -}); -check('Shared capabilities, legacy cyano API and first-tick interlocks retained', () => { - assert(read(javaRoot + 'util/LegacyFluidHandler.java').includes('extends IAdvantageFluidHandler')); - for (const name of ['ElectricMachineTileEntity', 'ElectricGeneratorTileEntity']) { - const source = read(javaRoot + `machines/${name}.java`); - assert(source.includes('!redstoneInitialized && getLevel() != null && !getLevel().isClientSide')); - } - 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('Track audit rejects the old asset and a geometry mutation', () => { + const file = assets + 'models/block/electric_track_ns.json'; + assert.throws(() => checkTrackModel(file, JSON.parse(old(file))), /lacks its native cutout layer/); + const mutated = json(file); + mutated.elements[0].from[0]++; + assert.throws(() => checkTrackModel(file, mutated), /changed beyond the render layer/); }); -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)) + +check('Every other shipped resource and test fixture retains its accepted bytes', () => { + const resources = tracked.filter(file => /^src\/(main|test)\/resources\//.test(file)); + const exceptions = new Set(['src/main/resources/pack.mcmeta', 'src/main/resources/META-INF/mods.toml']); + for (const file of resources.filter(file => !exceptions.has(file) && !track(file))) { + assert.equal(sha256(read(file)), sha256(old(file)), file); + } + const actual = fs.readdirSync(path.join(root, 'src'), {recursive:true, withFileTypes:true}) + .filter(entry => entry.isFile()).map(entry => path.relative(root, path.join(entry.parentPath, entry.name)) .split(path.sep).join('/')).sort(); - 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 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'}]}); + assert.deepEqual(actual, [...tracked].sort(), 'Source and resource inventory'); }); -check('Original and baseElectric aliases retain the native registry paths and state decoding', () => { - const old = git('show', `${nativeBaseline}:${oldJavaRoot}migration/ElectricLegacyMigrations.java`).toString(); - const source = read(migration); - const ids = text => [...text.matchAll(/for \(String path : new String\[\] \{([^}]+)\}/g)] - .map(match => [...match[1].matchAll(/"([^"]+)"/g)].map(id => id[1])); - assert.equal(ids(old).length, 2); - assert.deepEqual(ids(source), ids(old)); - const decoder = ' private static BlockState decodeState'; - assert.equal(source.slice(source.indexOf(decoder)), old.slice(old.indexOf(decoder))); - assert(source.includes('LegacyMigrationApi.registerNamespaceAlias(ElectricAdvantage.LEGACY_MODID, ElectricAdvantage.MODID)')); - assert(source.includes('LegacyMigrationApi.registerTileEntityAliases(id(path))')); - assert(source.includes('new String[] {ElectricAdvantage.LEGACY_MODID, ElectricAdvantage.MODID}')); - for (const alias of ['minecraft:electricadvantage.Hydroturbine', 'electricadvantage.electricadvantage.Hydroturbine']) { - assert(source.includes(`registerEntityAlias("${alias}", hydroturbine)`), alias); - } - const main = read(entry); - assert(main.includes('LEGACY_MODID = "electricadvantage"')); - for (const bridge of ['LegacyConfigMigration', 'LegacyOreSpawnConfigBridge']) { - assert(main.includes(`${bridge}.migrate(FMLPaths.CONFIGDIR.get(), MODID, LEGACY_MODID`)); - } - 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('ItemGroups.show(item)')); - assert(registration.includes('net.minecraftforge.registries.RegisterEvent')); + +check('Migration and registration code keep their accepted IDs and compatibility paths', () => { + for (const name of [ + 'migration/ElectricLegacyMigrations.java', 'init/Blocks.java', 'init/Items.java', + 'init/Entities.java', 'init/GUI.java', 'init/OreSpawnWorldGen.java', 'recipe/RecipeConditions.java' + ]) { + const file = javaRoot + name; + assert.equal(sha256(read(file)), sha256(old(file)), file); } - 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', 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}) - .filter(file => file.isFile() && file.name.endsWith('.java')) - .map(file => path.relative(root, path.join(file.parentPath, file.name))); -const tests = testFiles.reduce((n, file) => n + (read(file).match(/@Test\b/g) || []).length, 0); -check('All 27 inherited suites and 96 tests remain present', () => { - assert.equal(testFiles.length, 28); - assert.equal(testFiles.filter(file => /@Test\b/.test(read(file))).length, 27); - assert.equal(tests, 96); +check('All 96 inherited tests are still present, plus three new assertions', () => { + const methods = value => [...value.toString().matchAll(/@Test\s+public void (\w+)\(/g)].map(m => m[1]); + const files = tracked.filter(file => file.startsWith('src/test/java/') && file.endsWith('.java')); + let inherited = 0; + let actual = 0; + for (const file of files) { + const current = read(file); + const currentMethods = methods(current); + const priorMethods = methods(old(file)).map(name => name === 'resourcePackDeclaresTheNativeOneNineteenFourFormats' + ? 'resourcePackDeclaresTheNativeOneTwentyOneFormat' : name); + for (const name of priorMethods) assert(currentMethods.includes(name), file + ' lost ' + name); + assert.doesNotMatch(current.toString(), /@Ignore|Assume\.|assumeTrue\(|assumeFalse\(/, file); + inherited += priorMethods.length; + actual += currentMethods.length; + } + assert.equal(inherited, 96); + assert.equal(actual, 99); }); -const report = {baseline, nativeBaseline, portBase, scope:'Static source/resource contracts only; not compilation, runtime or manual acceptance', - inventory: {productionFiles:production.length, testFiles:testFiles.length, tests}, - changedFiles:changedFiles.map(file => ({file, sha256:fs.existsSync(path.join(root, file)) - ? createHash('sha256').update(fs.readFileSync(path.join(root, file))).digest('hex') : null})), - results}; -const reportFlag = process.argv.indexOf('--report'); -if (reportFlag !== -1) { - const output = path.resolve(root, process.argv[reportFlag + 1]); - assert(output.startsWith(root + path.sep), 'Report must remain inside this checkout'); - assert(!fs.existsSync(output), 'Refusing to overwrite source evidence'); + +const report = {baseline, scope: 'Static source and resource comparison only', checks}; +const flag = process.argv.indexOf('--report'); +if (flag >= 0) { + const output = path.resolve(root, process.argv[flag + 1]); + assert(output.startsWith(path.join(root, 'build', 'port-evidence') + path.sep)); fs.mkdirSync(path.dirname(output), {recursive:true}); - fs.writeFileSync(output, JSON.stringify(report, null, 2) + '\n'); + fs.writeFileSync(output, JSON.stringify(report, null, 2) + '\n', {flag:'wx'}); } -for (const r of results) console.log(`${r.passed ? 'PASS' : 'FAIL'} ${r.name}${r.error ? ': ' + r.error : ''}`); -process.exitCode = results.every(r => r.passed) ? 0 : 1; +for (const result of checks) console.log(result.pass ? 'PASS ' + result.name : 'FAIL ' + result.name + ': ' + result.error); +process.exitCode = checks.every(result => result.pass) ? 0 : 1;