diff --git a/.github/verify-published-pins.sh b/.github/verify-published-pins.sh index 4e2361e..8cd0265 100644 --- a/.github/verify-published-pins.sh +++ b/.github/verify-published-pins.sh @@ -9,10 +9,10 @@ require_merged() { echo "::error::$repo needs an exact source pin. A pending local candidate is not a public dependency." exit 1 fi - status="$(gh api "repos/MinecraftModDevelopmentMods/$repo/compare/$ref...master-1.21.1" --jq '.status')" + status="$(gh api "repos/MinecraftModDevelopmentMods/$repo/compare/$ref...master-1.21.11" --jq '.status')" case "$status" in identical|ahead) ;; - *) echo "::error::$repo $ref is not on upstream master-1.21.1 ($status)."; exit 1 ;; + *) echo "::error::$repo $ref is not on upstream master-1.21.11 ($status)."; exit 1 ;; esac } @@ -39,7 +39,7 @@ require_merged basePower "$power_ref" require_merged BaseMetals "$(value basemetals_source_commit)" require_merged OreSpawn "$orespawn_ref" -inputs="${RUNNER_TEMP:?}/forge52-release-dependencies" +inputs="${RUNNER_TEMP:?}/forge61-release-dependencies" mkdir -p "$inputs" power="$inputs/basePower-$(value poweradvantage_version).jar" metals="$inputs/BaseMetals-$(value basemetals_version).jar" @@ -48,8 +48,8 @@ public_jar basePower 1780140 "$(value poweradvantage_public_file_id)" "$(value p public_jar BaseMetals 240967 "$(value basemetals_public_file_id)" "$(value basemetals_deobf_sha256)" "$metals" public_jar OreSpawn 245586 "$(value orespawn_public_file_id)" "$(value orespawn_deobf_sha256)" "$orespawn" -installer="$inputs/forge-1.21.1-52.1.0-installer.jar" -download 'https://maven.minecraftforge.net/net/minecraftforge/forge/1.21.1-52.1.0/forge-1.21.1-52.1.0-installer.jar' \ +installer="$inputs/forge-1.21.11-61.1.0-installer.jar" +download 'https://maven.minecraftforge.net/net/minecraftforge/forge/1.21.11-61.1.0/forge-1.21.11-61.1.0-installer.jar' \ "$(value forge_installer_sha256)" "$installer" { printf 'power_ref=%s\n' "$power_ref" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99c0d8f..77bccf0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,16 @@ -name: baseSteam 1.21.1 CI +name: baseSteam 1.21.11 CI on: push: - branches: [master-1.21.1, 'feature/**'] + branches: [master-1.21.11, 'feature/**'] pull_request: - branches: [master-1.21.1] + branches: [master-1.21.11] permissions: contents: read concurrency: - group: basesteam-1.21.1-${{ github.workflow }}-${{ github.ref }} + group: basesteam-1.21.11-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: @@ -62,7 +62,7 @@ jobs: -Porg.gradle.java.installations.auto-download=false" >> "$GITHUB_ENV" - name: Set up Gradle uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 - - name: Verify the Forge 52 build inputs + - name: Verify the Forge 61 build inputs shell: bash env: ORESPAWN_REF: ${{ steps.public-pins.outputs.orespawn_ref }} @@ -74,7 +74,7 @@ jobs: test "$(git -C "$orespawn" rev-parse HEAD)" = "$ORESPAWN_REF" test "$(git -C "$power" rev-parse HEAD)" = "$POWER_REF" mavenizer="$orespawn/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar" - printf '%s %s\n' '019032A4A26ECB473D403EF017054D5892CE59A9904E8831B452287CD756397E' "$mavenizer" | sha256sum -c - + printf '%s %s\n' 'B36CA046DB72E30F27BD38E02B4AE34274ADB51AE1D65BAF9E1D07763755682E' "$mavenizer" | sha256sum -c - echo "MAVENIZER=$mavenizer" >> "$GITHUB_ENV" - name: Build the released basePower test bootstrap shell: bash @@ -82,7 +82,7 @@ jobs: set -euo pipefail power="$GITHUB_WORKSPACE/.ci-dependencies/basePower" (cd "$power" && bash ./gradlew $MMD_GRADLE_TOOLCHAINS unitBootstrapJar \ - "-Pforge52MavenizerJar=$MAVENIZER" "-PbaseMetalsDeobfJar=$METALS_PLAYER" "-PoreSpawnDeobfJar=$ORESPAWN_PLAYER" --no-daemon) + "-Pforge61MavenizerJar=$MAVENIZER" "-PbaseMetalsDeobfJar=$METALS_PLAYER" "-PoreSpawnDeobfJar=$ORESPAWN_PLAYER" --no-daemon) echo "POWER_BOOTSTRAP=$power/build/unit-bootstrap/forge-native-unit-bootstrap.jar" >> "$GITHUB_ENV" echo "POWER_BOOTSTRAP_SOURCE=$power/test-rig/unit-bootstrap/ForgeCapabilityBootstrap.java" >> "$GITHUB_ENV" echo "POWER_REGISTRY_SOURCE=$power/test-rig/unit-support/zone/moddev/mc/advantageworks/unit/ForgeUnitRegistries.java" >> "$GITHUB_ENV" @@ -91,27 +91,27 @@ jobs: run: | ./gradlew $MMD_GRADLE_TOOLCHAINS clean check build javadoc verifyReleaseDependencies verifyReleaseArtifacts \ writeReleaseChecksums verifyEclipseProductionClasspath --no-build-cache --no-daemon --stacktrace \ - "-Pforge52MavenizerJar=$MAVENIZER" "-PpowerAdvantageDeobfJar=$POWER_PLAYER" \ + "-Pforge61MavenizerJar=$MAVENIZER" "-PpowerAdvantageDeobfJar=$POWER_PLAYER" \ "-PbaseMetalsDeobfJar=$METALS_PLAYER" "-PoreSpawnDeobfJar=$ORESPAWN_PLAYER" \ "-PforgeCapabilityBootstrapJar=$POWER_BOOTSTRAP" "-PforgeCapabilityBootstrapSource=$POWER_BOOTSTRAP_SOURCE" \ "-PforgeUnitRegistriesSource=$POWER_REGISTRY_SOURCE" - name: Upload audited release candidate uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: baseSteam-1.21.1-${{ github.sha }} + name: baseSteam-1.21.11-${{ github.sha }} if-no-files-found: error retention-days: 30 path: | - build/libs/baseSteam-2.2.15.121011.jar - build/libs/baseSteam-2.2.15.121011-sources.jar - build/libs/baseSteam-2.2.15.121011-javadoc.jar + build/libs/baseSteam-2.2.16.121111.jar + build/libs/baseSteam-2.2.16.121111-sources.jar + build/libs/baseSteam-2.2.16.121111-javadoc.jar build/release/SHA256SUMS CHANGELOG.txt - name: Upload diagnostics on failure if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: baseSteam-1.21.1-diagnostics-${{ github.sha }} + name: baseSteam-1.21.11-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 f3faaa9..2ebea56 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -2,9 +2,9 @@ name: CodeQL on: push: - branches: [master-1.21.1, 'feature/**'] + branches: [master-1.21.11, 'feature/**'] pull_request: - branches: [master-1.21.1] + branches: [master-1.21.11] permissions: contents: read @@ -12,7 +12,7 @@ permissions: security-events: write concurrency: - group: basesteam-1.21.1-${{ github.workflow }}-${{ github.ref }} + group: basesteam-1.21.11-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: @@ -64,7 +64,7 @@ jobs: -Porg.gradle.java.installations.auto-download=false" >> "$GITHUB_ENV" - name: Set up Gradle uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 - - name: Verify the Forge 52 build inputs + - name: Verify the Forge 61 build inputs shell: bash env: ORESPAWN_REF: ${{ steps.public-pins.outputs.orespawn_ref }} @@ -76,7 +76,7 @@ jobs: test "$(git -C "$orespawn" rev-parse HEAD)" = "$ORESPAWN_REF" test "$(git -C "$power" rev-parse HEAD)" = "$POWER_REF" mavenizer="$orespawn/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar" - printf '%s %s\n' '019032A4A26ECB473D403EF017054D5892CE59A9904E8831B452287CD756397E' "$mavenizer" | sha256sum -c - + printf '%s %s\n' 'B36CA046DB72E30F27BD38E02B4AE34274ADB51AE1D65BAF9E1D07763755682E' "$mavenizer" | sha256sum -c - echo "MAVENIZER=$mavenizer" >> "$GITHUB_ENV" - name: Initialize CodeQL uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 @@ -87,7 +87,7 @@ jobs: shell: bash run: | ./gradlew $MMD_GRADLE_TOOLCHAINS classes verifyReleaseDependencies --rerun-tasks --no-build-cache --no-daemon --stacktrace \ - "-Pforge52MavenizerJar=$MAVENIZER" "-PpowerAdvantageDeobfJar=$POWER_PLAYER" \ + "-Pforge61MavenizerJar=$MAVENIZER" "-PpowerAdvantageDeobfJar=$POWER_PLAYER" \ "-PbaseMetalsDeobfJar=$METALS_PLAYER" "-PoreSpawnDeobfJar=$ORESPAWN_PLAYER" - name: Analyze uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml index 94ce821..9a26df1 100644 --- a/.github/workflows/release-on-tag.yml +++ b/.github/workflows/release-on-tag.yml @@ -37,9 +37,9 @@ jobs: minecraft_version="$(value minecraft_version)" loader_name="$(value loader_name)" loader_code="$(value loader_code)" - if [[ "$minecraft_version" != '1.21.1' || "$(value forge_version)" != '52.1.0' || - "$(value mapping_version)" != '1.21.1' || "$(value mcp_version)" != '20240808.132146' ]]; then - echo 'Expected the exact Minecraft 1.21.1 Forge 52.1.0 release target' >&2 + if [[ "$minecraft_version" != '1.21.11' || "$(value forge_version)" != '61.1.0' || + "$(value mapping_version)" != '1.21.11' || "$(value mcp_version)" != '20251223.124241' ]]; then + echo 'Expected the exact Minecraft 1.21.11 Forge 61.1.0 release target' >&2 exit 1 fi diff --git a/.github/workflows/validate-gradle-build.yml b/.github/workflows/validate-gradle-build.yml index 2f2c732..b37dfc9 100644 --- a/.github/workflows/validate-gradle-build.yml +++ b/.github/workflows/validate-gradle-build.yml @@ -3,11 +3,11 @@ name: Validate Gradle Wrapper on: push: branches: - - master-1.21.1 + - master-1.21.11 - 'feature/**' pull_request: branches: - - master-1.21.1 + - master-1.21.11 permissions: contents: read diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 58bc847..489c869 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,15 @@ +baseSteam 2.2.16.121111 (Minecraft 1.21.11 / Forge) + +- Target Forge 61.1.0, Java 21 and released basePower 2.4.15.121111. +- Adapt native registration, persistence, recipes, item definitions and drill rendering. +- Retain registry identities, legacy state keys, recipe payloads and configuration defaults. +- Use native Forge hidden-client pack metadata with data-format 94.1 at the mod root. +- Require Base Metals on both client and server in Forge metadata as well as distribution metadata; retain the defensive recipe guards unchanged. +- Keep block/material sprites, including the drill bit, out of the native item atlas; retain legacy item-icon lookup compatibility. +- Make the atlas regression test use the configured, checksum-verified Power jar, including builds outside the usual sibling folders. +- Keep native resource tests noninteractive in CI without suppressing warnings or errors. +- Complete the combined machine, upgrade, graphical and two-world endurance checks against the current jars. + baseSteam 2.2.15.121011 (Minecraft 1.21.1 / Forge) - Target Forge 52.1.0 and Java 21 with basePower 2.4.14.121011. diff --git a/README.md b/README.md index 31c1f7c..2c4f5ab 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # baseSteam -**Minecraft 1.21.1 (Forge), version 2.2.15.121011.** +**Minecraft 1.21.11 (Forge), baseSteam 2.2.16.121111.** baseSteam adds boilers, steam-powered machines, pumps, storage, elevators and a blackpowder musket to basePower. Items and blocks share the basePower @@ -8,27 +8,41 @@ creative tab. The mod ID and resource namespace remain `basesteam`. ## Build -Target: Forge 52.1.0, official Minecraft 1.21.1 mappings and MCP -20240808.132146. ForgeGradle 7.0.34 and the checksum-pinned Gradle 9.6.1 +Target: Forge 61.1.0, official Minecraft 1.21.11 mappings and MCP +20251223.124241. ForgeGradle 7.0.34 and the checksum-pinned Gradle 9.6.1 wrapper use Temurin 21.0.7+6. Mavenizer uses Java 25; the launcher helper also needs Java 8. Minecraft itself runs on Java 21. -The default dependency paths use the coordinated 1.21.1 checkouts. This -version needs the released basePower 2.4.14.121011, Base Metals 3.1.2.121011 and -OreSpawn 4.1.0.121011 as separate native jars, not bundled copies. -Exact source, public file, jar and test-bootstrap pins are in `gradle.properties`. -They are checked before compilation and tests. CurseForge, Maven and GitHub -releases use the same checked player jar. +Install [basePower 2.4.15.121111](https://github.com/MinecraftModDevelopmentMods/basePower/releases/tag/2.4.15.121111), +[Base Metals 3.1.2.121111](https://www.curseforge.com/minecraft/mc-mods/base-metals/files/9566738) +and [OreSpawn 4.1.0.121111](https://www.curseforge.com/minecraft/mc-mods/mmd-orespawn/files/9268464) +as separate Forge 1.21.11 jars on both client and server, not bundled copies. +Base Metals is REQUIRED on both client and server, not an optional integration. +The default build paths use the coordinated 1.21.11 checkouts. Exact released +source, jar and test-bootstrap pins are in `gradle.properties` and are checked +before compilation and tests. -The Mavenizer fixture comes from OreSpawn's OS1211 checkout. Another layout -can use `-Pforge52MavenizerJar=`. The `*DeobfJar` and `*PlayerJar` +This version has passed automated qualification and manual acceptance. The +[qualification record](https://github.com/MinecraftModDevelopmentMods/basePower/blob/master-1.21.11/docs/1.21.11/QUALIFICATION.md) +records the machine, upgrade, graphical and two-world endurance checks and +their coverage limits. Earlier no-Base-Metals receipts are +historical evidence, not a supported installation profile. +The mod root declares server data format 94.1. Forge supplies format 75.x +metadata for its hidden client resource aggregate. Native resource-reader +tests cover both paths without codec warnings; see +[Forge metadata notes](docs/FORGE61-RESOURCE-METADATA.md) for the separate +Forge 61.1.0 server compatibility-label defect. + +The Mavenizer fixture comes from OreSpawn's OS12111 checkout. Another layout +can use `-Pforge61MavenizerJar=`. The `*DeobfJar` and `*PlayerJar` properties select separate development and packaged inputs; both must satisfy the committed checksums. -With the pinned local dependencies available: +With the pinned released dependencies available locally: ```powershell $env:JAVA_HOME = "$env:USERPROFILE/.jdks/temurin-21.0.7+6" +$env:GRADLE_USER_HOME = 'D:/GradleCaches/basesteam-12111-port' $jdk8 = "$env:USERPROFILE/.jdks/temurin-8.0.502+7/jdk8u502-b07" $jdks = "$env:JAVA_HOME,$env:USERPROFILE/.jdks/jdk-25.0.3+9,$jdk8" $toolchains = @("-Porg.gradle.java.installations.paths=$jdks", @@ -37,14 +51,31 @@ $toolchains = @("-Porg.gradle.java.installations.paths=$jdks", ./gradlew.bat @toolchains check build deobfJar verifyReleaseArtifacts writeReleaseChecksums ./gradlew.bat @toolchains verifyEclipseProductionClasspath ./gradlew.bat @toolchains -PsteamGameTestRunId=candidate verifyPlayerDependencies runGameTestServer -./tools/run-packaged-server-smoke.ps1 -ForgeInstall $env:FORGE_1211_SERVER_INSTALL -RunId candidate-with-metals -./tools/run-packaged-server-smoke.ps1 -ForgeInstall $env:FORGE_1211_SERVER_INSTALL -RunId candidate-without-metals -WithoutBaseMetals +./tools/run-packaged-server-smoke.ps1 -ForgeInstall $env:FORGE_12111_SERVER_INSTALL -RunId candidate-with-metals -UseNioTransport -Reopen ``` +For a checkout outside the sibling-folder layout, unit tests also need the +verified Power test bootstrap and its two source files. Build Power first, +then supply these paths as well as the dependency-jar overrides: + +```text +-PforgeCapabilityBootstrapJar=/build/unit-bootstrap/forge-native-unit-bootstrap.jar +-PforgeCapabilityBootstrapSource=/test-rig/unit-bootstrap/ForgeCapabilityBootstrap.java +-PforgeUnitRegistriesSource=/test-rig/unit-support/zone/moddev/mc/advantageworks/unit/ForgeUnitRegistries.java +``` + +The atlas test uses the same verified Power jar selected for compilation; +it does not look for a second copy in a fixed local folder. Missing or +changed inputs fail the checks. The current source check passes 252 unit +tests. Native resource tests disable interactive terminal setup in their +child process, while keeping warnings and errors fatal. +Keep Java option environment variables unset +when running the packaged family rig; it records its own runtime arguments. + Independent source checks: `node tools/verify-native-port.js` and `node tools/generate-recipe-unlocks.js --check`. -Hosted CI and CodeQL require merged sources and checksum-matching public -downloads before compiling. Pending local pins cannot pass that gate. +Hosted CI and CodeQL verify merged dependency sources and checksum-matching +public downloads before compiling. ## Old Worlds @@ -56,8 +87,11 @@ Steam retains machine IDs and saved fields, drill directions, musket wear and loading, and all four musket enchantments. The singular datapack folders do not change logical recipe or advancement identities. -Use basePower's migration rig for whole-world checks. Before accepting the -port, check machines, restarts, redstone, all drill directions, elevators, +Use basePower's migration rig for whole-world checks. For your own world, +check machines, restarts, redstone, all drill directions, elevators, GUI captions, whole fluid gauges, track transparency and musket effects -in-game. Automated, packaged and manual acceptance are separate gates. -See the [1.21.1 notes](docs/PORT1211-STATUS.md). +in-game. Release acceptance does not turn the remaining manual checks into +automated passes or qualify every modpack. +See the [1.21.11 notes](docs/PORT12111-STATUS.md). The +[1.21.1 notes](docs/PORT1211-STATUS.md) describe the accepted source baseline, +not qualification of this port. diff --git a/build.gradle b/build.gradle index a8f4d9b..cbba5bf 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ plugins { id 'net.minecraftforge.gradle' version '7.0.34' } -apply from: 'gradle/forge52-mavenizer.gradle' +apply from: 'gradle/forge61-mavenizer.gradle' group = project.mod_group version = project.mod_version @@ -133,6 +133,7 @@ minecraft { workingDir.set(steamGameTestRunDirectory) systemProperty 'forge.enableGameTest', 'true' systemProperty 'forge.enabledGameTestNamespaces', mod_id + args '--tests', "${mod_id}:*", '--report', 'steam-gametest-results.xml' mods { basesteam { source sourceSets.gameTestValidation } } } register('data') { @@ -151,7 +152,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 52 launches.' + description = 'Merges classes and processed resources for Forge 61 launches.' dependsOn tasks.named('classes') from sourceSets.main.output into forgeRunModClassesDirectory @@ -163,6 +164,7 @@ def prepareForgeGameTestModClasses = tasks.register('prepareForgeGameTestModClas into gameTestModClassesDirectory } tasks.withType(JavaExec).configureEach { + jvmArgs '-XX:ActiveProcessorCount=4' javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(21) vendor = JvmVendorSpec.ADOPTIUM @@ -203,6 +205,20 @@ tasks.withType(JavaExec).configureEach { if (!output.contains('All 4 required tests passed') || (output =~ /\[[^\]\r\n]*\/(?:ERROR|FATAL)\]/).find()) { throw new GradleException("The four Steam GameTests did not pass; see ${receipt}") } + File report = new File(steamGameTestRunDirectory.get().asFile, 'steam-gametest-results.xml') + if (!report.isFile()) throw new GradleException('Missing native GameTest XML report') + def results = new groovy.xml.XmlSlurper().parse(report) + def cases = results.depthFirst().findAll { it.name() == 'testcase' } + Set expectedTests = [ + 'drill_bit_packet_preserves_type_position_and_direction', + 'legacy_musket_wear_and_loaded_byte_survive_native_componentization', + 'older_musket_alias_and_top_level_wear_survive_power_migration', + 'native_resources_load_with_expected_ids' + ].collect { "${mod_id}:${it}".toString() } as Set + if (cases.size() != 4 || (cases.collect { it.@name.text() } as Set) != expectedTests + || cases.any { it.failure.size() || it.error.size() || it.skipped.size() }) { + throw new GradleException("Expected exactly four successful Steam GameTests without skips: ${report}") + } } } } @@ -230,12 +246,12 @@ repositories { // Only code and launch tasks require the exact jars; source/resource checks can run independently. def explicitJar = { String property -> providers.gradleProperty(property).map { file(it) }.getOrNull() } def powerRoot = '../../PowerAdvantage' -def baseMetalsRoot = '../../../../BaseMetals/.verification-basemetals1211/Advantage shared hook' +def baseMetalsRoot = '../../../../BaseMetals/.verification-basemetals12111/Advantage shared hook' def powerNativeJar = file("${powerRoot}/build/libs/basePower-${project.poweradvantage_version}.jar") def powerAdvantageDeobfJar = explicitJar('powerAdvantageDeobfJar') ?: powerNativeJar def baseMetalsDeobfJar = explicitJar('baseMetalsDeobfJar') ?: file( "${baseMetalsRoot}/build/libs/BaseMetals-${project.basemetals_version}.jar") -def oreSpawnRoot = '../../../../MinecraftMineralogy/OS1211/OreSpawn' +def oreSpawnRoot = '../../../../MinecraftMineralogy/OS12111/OreSpawn' def oreSpawnJar = { String variant -> explicitJar("oreSpawn${variant}Jar") ?: file( "${oreSpawnRoot}/build/libs/OreSpawn-${project.orespawn_version}.jar") @@ -292,6 +308,7 @@ def unitBootstrapSources = [ ] tasks.named('test', Test) { useJUnit() + jvmArgs '-XX:ActiveProcessorCount=4' inputs.files(forgeCapabilityBootstrapJar) inputs.files(unitBootstrapSources.values()) inputs.property('powerBootstrapSha256', providers.gradleProperty('poweradvantage_test_bootstrap_sha256')) @@ -322,6 +339,8 @@ tasks.named('test', Test) { } } logger.lifecycle("basePower test bootstrap: ${bootstrapJar.canonicalPath} SHA-256 ${actualHash}") + systemProperty 'basesteam.nativePackClasspath', sourceSets.test.runtimeClasspath.asPath + systemProperty 'basesteam.nativePowerJar', powerAdvantageDeobfJar.canonicalPath jvmArgs "-javaagent:${bootstrapJar.canonicalPath}" jvmArgs '--add-opens', 'java.base/java.lang.invoke=ALL-UNNAMED' } @@ -360,7 +379,7 @@ tasks.named('jar', Jar) { } } -// Forge 52 runs official names. The player jar needs no SRG remapping. +// Forge 61 runs official names. The player jar needs no SRG remapping. def releaseJar = tasks.register('releaseJar', Jar) { dependsOn tasks.named('classes'), 'verifyReleaseDependencies' archiveClassifier = null @@ -437,8 +456,18 @@ def expectedReleaseFiles = providers.provider { def preparedReleaseDir = providers.gradleProperty('preparedReleaseDir') def verifyPackFormats = { pack -> - if (pack.pack_format != 34 || pack.supported_formats != [min_inclusive: 34, max_inclusive: 48]) { - throw new GradleException('Minecraft 1.21.1 requires resource format 34 and data format 48') + if (pack.min_format != [94, 1] || pack.max_format != [94, 1] + || pack.containsKey('pack_format') || pack.containsKey('supported_formats')) { + throw new GradleException('The mod root requires data format 94.1; Forge supplies hidden client pack metadata') + } +} + +def verifyRequiredBaseMetalsMetadata = { String metadata -> + String normalized = metadata.replace('\r\n', '\n') + String declaration = 'modId="basemetals"\nmandatory=true\nversionRange="[3.1.2.121111,4.0.0)"\nordering="AFTER"\nside="BOTH"' + if (!normalized.contains(declaration) + || normalized.readLines().count { it.trim() == 'modId="basemetals"' } != 1) { + throw new GradleException('Base Metals must be declared exactly once as REQUIRED on BOTH sides, ordered AFTER, in [3.1.2.121111,4.0.0)') } } @@ -454,15 +483,15 @@ tasks.register('verifyReleaseConfiguration') { '9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14') { throw new GradleException('Use the checksum-pinned Gradle 9.6.1 wrapper') } - if (project.mod_version != '2.2.15.121011') { + if (project.mod_version != '2.2.16.121111') { throw new GradleException("Unexpected baseSteam release version: ${project.mod_version}") } - if (project.minecraft_version != '1.21.1' || project.minecraft_version_range != '[1.21.1]' - || project.forge_version != '52.1.0' || project.forge_version_range != '[52.1.0,53)' - || project.loader_version_range != '[52,53)' + if (project.minecraft_version != '1.21.11' || project.minecraft_version_range != '[1.21.11]' + || project.forge_version != '61.1.0' || project.forge_version_range != '[61.1.0,62)' + || project.loader_version_range != '[61,62)' || project.mapping_channel != 'official' - || project.mapping_version != '1.21.1' - || project.mcp_version != '20240808.132146') { + || project.mapping_version != '1.21.11' + || project.mcp_version != '20251223.124241') { throw new GradleException('Unexpected Minecraft, Forge, or mappings target') } if (project.loader_name != 'forge' || project.loader_code != '1' @@ -482,11 +511,11 @@ tasks.register('verifyReleaseConfiguration') { } if (project.cf_requirements != 'basepower,base-metals' || project.cf_optionals != '' - || project.cf_versions != '1.21.1') { + || project.cf_versions != '1.21.11') { throw new GradleException('Unexpected CurseForge dependency or game-version metadata') } - if (project.basemetals_version != '3.1.2.121011' - || project.orespawn_version != '4.1.0.121011') { + if (project.basemetals_version != '3.1.2.121111' + || project.orespawn_version != '4.1.0.121111') { throw new GradleException('Unexpected development API versions') } if (project.poweradvantage_source_repository != 'MinecraftModDevelopmentMods/basePower') { @@ -495,17 +524,17 @@ tasks.register('verifyReleaseConfiguration') { String mainSource = file( 'src/main/java/zone/moddev/mc/basesteam/SteamAdvantage.java').getText('UTF-8') if (!mainSource.contains('MODID = "basesteam"') - || !mainSource.contains('VERSION = "2.2.15.121011"')) { + || !mainSource.contains('VERSION = "2.2.16.121111"')) { throw new GradleException('baseSteam source identity changed') } String metadata = file('src/main/resources/META-INF/mods.toml').getText('UTF-8').replace('\r\n', '\n') - ['modId="basesteam"', 'displayName="baseSteam"', - 'modId="basepower"\nmandatory=true\nversionRange="[${poweradvantage_version},3.0.0)"', - 'modId="basemetals"\nmandatory=false\nversionRange="[3.1.2.121011,4.0.0)"'].each { marker -> + ['showAsResourcePack=false', 'modId="basesteam"', 'displayName="baseSteam"', + 'modId="basepower"\nmandatory=true\nversionRange="[${poweradvantage_version},3.0.0)"'].each { marker -> if (!metadata.contains(marker)) { throw new GradleException("Source mods.toml is missing ${marker}") } } + verifyRequiredBaseMetalsMetadata(metadata) } } @@ -587,7 +616,7 @@ tasks.register('verifyPlayerDependencies') { dependsOn tasks.named('verifyCompanionPlayerDependencies') doLast { committedPin('poweradvantage_source_ref', /[a-f0-9]{40}/) - committedPin('poweradvantage_version', /\d+\.\d+\.\d+\.121011/) + committedPin('poweradvantage_version', /\d+\.\d+\.\d+\.121111/) auditJar(explicitJar('powerAdvantagePlayerJar') ?: powerNativeJar, 'powerAdvantagePlayerJar', 'poweradvantage_player_sha256', 'basepower', project.poweradvantage_version, powerApi) } @@ -598,7 +627,7 @@ tasks.register('verifyNativeDevelopmentDependencies') { description = 'Resolves Forge and audits the exact development JARs.' dependsOn tasks.named('verifyCompanionDevelopmentDependencies') doLast { - committedPin('poweradvantage_version', /\d+\.\d+\.\d+\.121011/) + committedPin('poweradvantage_version', /\d+\.\d+\.\d+\.121111/) committedPin('poweradvantage_source_ref', /[a-f0-9]{40}/) auditJar(powerAdvantageDeobfJar, 'powerAdvantageDeobfJar', 'poweradvantage_deobf_sha256', 'basepower', project.poweradvantage_version, powerApi) @@ -637,10 +666,14 @@ tasks.register('verifyReleaseDependencies') { } def blocksAtlasPath = 'assets/minecraft/atlases/blocks.json' -def steamBlocksAtlas = [sources: [[type: 'single', resource: 'basesteam:entity/drill_bit']]] -def verifySteamBlocksAtlas = { Object atlas -> - if (atlas != steamBlocksAtlas) { - throw new GradleException("${blocksAtlasPath} must stitch only basesteam:entity/drill_bit as a single source") +def itemsAtlasPath = 'assets/minecraft/atlases/items.json' +def steamAtlasPaths = [blocksAtlasPath, itemsAtlasPath] +def steamDrillAtlas = [sources: [[type: 'single', resource: 'basesteam:entity/drill_bit']]] +def steamItemsAtlas = [sources: [[type: 'directory', source: 'items', prefix: 'items/']]] +def verifySteamAtlas = { String path, Object atlas -> + def expected = path == blocksAtlasPath ? steamDrillAtlas : steamItemsAtlas + if (!steamAtlasPaths.contains(path) || atlas != expected) { + throw new GradleException("Steam ${path} must contain only ${expected}; block and item sources must remain disjoint") } } def verifyPackagedAssetNamespaces = { ZipFile zip -> @@ -648,11 +681,13 @@ def verifyPackagedAssetNamespaces = { ZipFile zip -> def foreignAssets = zip.entries().findAll { !it.directory && it.name.startsWith('assets/') && !it.name.startsWith(steamAssets) }.collect { it.name } - if (foreignAssets != [blocksAtlasPath]) { - throw new GradleException("${zip.name} must contain only ${blocksAtlasPath} outside ${steamAssets}; found ${foreignAssets}") + if (foreignAssets.sort() != steamAtlasPaths.sort(false)) { + throw new GradleException("${zip.name} must contain only ${steamAtlasPaths} outside ${steamAssets}; found ${foreignAssets}") } - zip.getInputStream(zip.getEntry(blocksAtlasPath)).withCloseable { input -> - verifySteamBlocksAtlas(new JsonSlurper().parse(input, 'UTF-8')) + steamAtlasPaths.each { path -> + zip.getInputStream(zip.getEntry(path)).withCloseable { input -> + verifySteamAtlas(path, new JsonSlurper().parse(input, 'UTF-8')) + } } } @@ -719,6 +754,15 @@ tasks.register('verifyReleaseArtifacts') { throw new GradleException("Release jar is missing ${required}") } } + ['steam_pipe', 'steam_track', 'steam_boiler_coal', 'steam_boiler_electric', + 'steam_boiler_geothermal', 'steam_tank', 'steam_furnace', 'steam_crusher', + 'steam_drill', 'drillbit', 'platform', 'steam_elevator', 'steam_switch', + 'pump_pipe_steam', 'steam_still', 'steam_pump', 'steam_boiler_oil', + 'steam_governor', 'steam_drill_bit', 'blackpowder_cartridge', 'musket'].each { id -> + if (!names.contains("assets/basesteam/items/${id}.json".toString())) { + throw new GradleException("Release jar is missing native item definition ${id}") + } + } ['steam_pipe', 'steam_track', 'steam_boiler_coal', 'steam_boiler_electric', 'steam_boiler_geothermal', 'steam_tank', 'steam_furnace', 'steam_crusher', 'steam_drill', 'steam_elevator', 'steam_switch', 'steam_still', 'steam_pump', @@ -748,7 +792,9 @@ tasks.register('verifyReleaseArtifacts') { } String metadata = zip.getInputStream(zip.getEntry('META-INF/mods.toml')) .getText(StandardCharsets.UTF_8.name()) + verifyRequiredBaseMetalsMetadata(metadata) if (!metadata.contains('modId="basesteam"') + || !metadata.contains('showAsResourcePack=false') || !metadata.contains("version=\"${project.mod_version}\"") || !metadata.contains("versionRange=\"${project.minecraft_version_range}\"") || !metadata.contains("versionRange=\"${project.forge_version_range}\"") @@ -1239,7 +1285,7 @@ tasks.register('verifyEclipseProductionClasspath') { || (name == 'runClient.launch' && !arguments.startsWith('--launcher-side client ')) || !vmArguments.contains('--add-opens java.base/java.lang.invoke=ALL-UNNAMED') || contents.contains('net.minecraftforge.userdev.LaunchTesting')) { - throw new GradleException("${name} must retain the native Forge 52 bootstrap and Java 21 module access") + throw new GradleException("${name} must retain the native Forge 61 bootstrap and Java 21 module access") } if (launch.booleanAttribute.find { it.@key.text() == 'org.eclipse.jdt.launching.ATTR_EXCLUDE_TEST_CODE' @@ -1297,7 +1343,7 @@ tasks.register('verifyEclipseProductionClasspath') { String target = name == 'runClient.launch' ? 'forge_userdev_client' : 'forge_userdev_server' if (!arguments.contains("--launchTarget ${target}") || !arguments.contains("forge-${project.minecraft_version}-${project.forge_version}-metadata.zip")) { - throw new GradleException("${name} does not target the exact Forge 52 development runtime") + throw new GradleException("${name} does not target the exact Forge 61 development runtime") } } } @@ -1311,9 +1357,9 @@ tasks.register('verifyModelAssets') { File assetRoot = new File(resourceRoot, "assets/${project.mod_id}") File modelRoot = new File(assetRoot, 'models') File blockstateRoot = new File(assetRoot, 'blockstates') - File steamAtlas = new File(resourceRoot, blocksAtlasPath) + List steamAtlases = steamAtlasPaths.collect { new File(resourceRoot, it) } inputs.dir(assetRoot) - inputs.files(fileTree(resourceRoot) { include blocksAtlasPath }) + inputs.files(steamAtlases) inputs.file(powerAdvantageDeobfJar) doLast { @@ -1323,22 +1369,40 @@ tasks.register('verifyModelAssets') { Set atlasPrefixes = ['block/', 'item/'] as Set Set singleSprites = [] as Set - if (steamAtlas.isFile()) { + steamAtlasPaths.each { String atlasPath -> + File steamAtlas = new File(resourceRoot, atlasPath) + if (!steamAtlas.isFile()) throw new GradleException("Missing native Steam atlas ${steamAtlas}") def atlas = new JsonSlurper().parse(steamAtlas) - verifySteamBlocksAtlas(atlas) - singleSprites.addAll(atlas.sources.collect { it.resource }) + verifySteamAtlas(atlasPath, atlas) + singleSprites.addAll(atlas.sources.findAll { it.type == 'single' }.collect { it.resource }) + atlasPrefixes.addAll(atlas.sources.findAll { it.type == 'directory' }.collect { it.prefix }) } + Set itemAtlasPrefixes = ['item/', 'items/'] as Set Set powerAdvantageAssets = new ZipFile(powerAdvantageDeobfJar).withCloseable { zip -> - def atlasEntry = zip.getEntry(blocksAtlasPath) - if (atlasEntry != null) { + Map> sharedPrefixes = [:] + steamAtlasPaths.each { atlasPath -> + def atlasEntry = zip.getEntry(atlasPath) + if (atlasEntry == null) throw new GradleException("basePower is missing native atlas ${atlasPath}") zip.getInputStream(atlasEntry).withCloseable { input -> def atlas = new JsonSlurper().parse(input, 'UTF-8') + if (atlasPath == itemsAtlasPath) verifySteamAtlas(atlasPath, atlas) // Power's directory sources preserve the existing texture IDs across namespaces. - atlas.sources.findAll { + Set prefixes = atlas.sources.findAll { it.type == 'directory' && it.source instanceof String && it.prefix == it.source + '/' - }.each { atlasPrefixes.add(it.prefix) } + }.collect { it.prefix } as Set + sharedPrefixes[atlasPath] = prefixes } } + Set collisions = sharedPrefixes[blocksAtlasPath].intersect(sharedPrefixes[itemsAtlasPath]) + collisions.retainAll(['blocks/', 'materials/']) + if (!collisions.isEmpty()) { + throw new GradleException("basePower shadows block/material directories in the item atlas: ${collisions}") + } + if (!sharedPrefixes[blocksAtlasPath].containsAll(['blocks/', 'materials/', 'items/'])) { + throw new GradleException('basePower block atlas must retain legacy blocks/, materials/ and generated-item items/ sources') + } + sharedPrefixes.values().each { atlasPrefixes.addAll(it) } + itemAtlasPrefixes.addAll(sharedPrefixes[itemsAtlasPath]) zip.entries().collect { it.name } as Set } def verifyReference = { String reference, String assetType, String sourcePath -> @@ -1361,6 +1425,10 @@ tasks.register('verifyModelAssets') { && !atlasPrefixes.any { path.startsWith(it) }) { throw new GradleException("${sourcePath} references unstitched blocks atlas texture ${reference}") } + if (assetType == 'textures' && new File(sourcePath).toPath().startsWith(new File(modelRoot, 'block').toPath()) + && itemAtlasPrefixes.any { path.startsWith(it) }) { + throw new GradleException("${sourcePath} references an item-atlas texture from a block model: ${reference}") + } } fileTree(modelRoot) { include '**/*.json' }.files.sort().each { File modelFile -> @@ -1397,6 +1465,12 @@ tasks.register('verifyModelAssets') { } verifyBlockstateModels(blockstate, blockstateFile.path) } + File itemDefinitions = new File(assetRoot, 'items') + def definitions = fileTree(itemDefinitions) { include '*.json' }.files + if (definitions.size() != 21) throw new GradleException('Expected all 21 native item definitions') + definitions.sort().each { File definition -> + verifyBlockstateModels(new JsonSlurper().parse(definition), definition.path) + } } } @@ -1458,6 +1532,8 @@ tasks.register('verifyRecipeTagGuards') { } } else if (node instanceof Collection) { node.each { collectTags(it, tags) } + } else if (node instanceof String && node.startsWith('#forge:')) { + tags.add(node.substring(1)) } } fileTree(recipeRoot) { include '**/*.json' }.files.sort().each { File recipeFile -> diff --git a/docs/FORGE61-RESOURCE-METADATA.md b/docs/FORGE61-RESOURCE-METADATA.md new file mode 100644 index 0000000..b31ef55 --- /dev/null +++ b/docs/FORGE61-RESOURCE-METADATA.md @@ -0,0 +1,81 @@ +# Forge 61 Resource Metadata + +This applies to the exact Forge 1.21.11-61.1.0 target, not newer loaders. + +## Mod Metadata + +Keep pack.mcmeta at the mod root, declaring server data format 94.1: + +```json +{"pack":{"description":"baseSteam","min_format":[94,1],"max_format":[94,1]}} +``` + +Keep showAsResourcePack=false at the top level of META-INF/mods.toml. +It defaults to false, but the explicit value protects this contract. + +ResourcePackLoader uses PathPackResources for each mod. On the client it +combines hidden mods into DelegatingPackResources named mod_resources, +with native client metadata for 75.x. On the server it exposes the mod's +PathPackResources and reads the root's 94.1 metadata. A mod with +showAsResourcePack=true cannot use this hidden-client metadata strategy. + +Do not decode the server-format root as if it were the effective client +pack. The negative test still proves that a raw 94.1 resource pack does not +accept client format 75.0. Both effective native packs must be verified. + +A single raw [75,0] through [94,1] declaration fails server decoding because +the server's pre-minor threshold is 81 and requires legacy fields there. +The client's threshold is 64, so it rejects those legacy fields at 75. +Neither fallback metadata nor ignored diagnostic messages fixes that. + +Omitting pack.mcmeta is not a solution. The native mod PathResourcesSupplier +returns null metadata for both PackTypes. Pack.readPackMetadata then reports +missing metadata, and ResourcePackLoader enters its broken-resources warning +path. ForgeDevLocator's development-only fallback writes obsolete format 18; +it is not correct per-PackType metadata for a packaged 1.21.11 mod. + +## Native APIs + +MetadataSectionSerializer is replaced by MetadataSectionType. A native +PackResources implementation overrides: + +```java +public T getMetadataSection(MetadataSectionType section) throws IOException +``` + +PackMetadataSection takes a Component and InclusiveRange, not +an integer pack version. Use PackMetadataSection.forPackType(type) for the +correct codec, SharedConstants.getCurrentVersion().packVersion(type) for the +current format, and Pack.readMetaAndCreate(..., type, ...) when creating a +built-in pack. A server-only MaterialPolicyPack should use SERVER_DATA. +Native DelegatingPackResources is an example of directly supplied metadata. + +## Separate Loader Defect + +Forge 61.1.0 ResourcePackLoader.findPacks always gets its comparison version +from DetectedVersion.BUILT_IN.packVersion(CLIENT_RESOURCES), even for server +data. A correctly declared 94.1 server mod pack is therefore labelled +TOO_NEW by that discovery method. Re-reading the same native supplier with +the actual SERVER_DATA version returns COMPATIBLE, and every server resource +is accessible. The test receipt records the discrepancy explicitly. + +This is distinct from codec failure. It is not fixed or hidden here, and +server compatibility-label acceptance remains a parent/platform decision. +Do not change data to format 75, suppress warnings, or fake compatibility. + +## Proof + +NativePackResourceProof runs a separate JVM, builds a resource-only fixture +jar from Gradle's processed Steam resources, parses it with real SecureJar +and ModFile, and calls ResourcePackLoader through PackRepository. It then +opens the actual client/server packs and MultiPackResourceManager instances. +All 160 client and 91 server files are compared byte for byte with the input. +The effective metadata round-trips through the appropriate native codec, +rejects the opposite PackType's version and rejects out-of-range versions. +The Log4j appender observes WARN/ERROR messages without filtering existing +appenders, and the parent also checks the complete child log. Any diagnostic +fails the proof. The test child has its own working directory and leaves +its jar, logs and receipt under ignored build/native-pack-resources/. + +This proves resource/metadata behavior, not a full gameplay client/server +launch, basePower compatibility or the Steam release artifact. diff --git a/docs/PORT12111-STATUS.md b/docs/PORT12111-STATUS.md new file mode 100644 index 0000000..7677d11 --- /dev/null +++ b/docs/PORT12111-STATUS.md @@ -0,0 +1,263 @@ +# Steam on Minecraft 1.21.11 + +## Public Dependency Pins + +The 12 November 2026 build uses released basePower 2.4.15.121111 source +9b0043483d79d3e575895f12aeb0dde7ef6c4890, CurseForge file 9566998, and Base +Metals 3.1.2.121111 source 6f4c44e4d0a68bca3838fb025c09cddfaaa08fe8, +CurseForge file 9566738. Their player hashes remain C98D and 598D; the native +unit-bootstrap and source pins are unchanged. + +The actual releaseJar task was forced with the verified public Power jar. +The resulting Steam player remains +F23DDC4ABB36A1C4C7A4205151FBB6DDFD25397E86909E1C0B146C4089B747E0. +All 324 payload entries, including 69 classes and the manifest, are identical +to the accepted Steam jar. No production or resource change was needed. +Earlier jar-task handoffs checked the existing player bytes but did not prove +a fresh player build; the releaseJar receipt records that correction. + +The default-path full release suite passes all 45 tasks and 252 unit tests +without failures, errors or skips. Fresh Eclipse ECJ output compiles the main, +test and GameTest scopes and executes all 252 unit tests without ignored tests +or assumptions. All four named native source GameTests pass. The independent +source-archive build and packaged Steam restart witness have separate receipts. + +Steam publication is held for its hosted checks and the parent's fresh +final-hash family restart, 1800-second endurance and independent replay gate. +New qualification receipts remain separate from the earlier family below. + +## Accepted Family Proof (11 November) + +The current `F23DDC4ABB36A1C4C7A4205151FBB6DDFD25397E86909E1C0B146C4089B747E0` +player jar was accepted for release on 11 November 2026 with Power C98D, +Electric 5466, Base Metals 598D and OreSpawn DE39. Publication was authorized +in dependency order. This section records the pre-public-pin family, including +the earlier Electric 5466 jar; it does not qualify the later Electric 7128 jar. +basePower's `docs/1.21.11/QUALIFICATION.md` records the full hashes and limits. + +That frozen family passes 132 finite jobs, upgrades, chunk/network and +drill lifecycle tests, recipes, world generation, oils, graphical checks and +two 30-minute endurance worlds with full restarts. Independent replay passes +all 66 checkpoints and exact final stopped machine data. These are new +current-jar results; the C2 evidence below remains historical. + +The current forced, uncached source check passes all 252 unit tests with no +failures, errors or skips. This includes the portable Power atlas-test input +controls and two noninteractive native resource-test regressions. The child +uses terminal.jline=false with pipes and TERM=dumb, verifies 161 client and +91 server files, and rejects WARN, ERROR and FATAL diagnostics unchanged. +Its failing pre-fix receipt is preserved. + +The earlier clean-path and normal builds, and the fresh real Eclipse compiler +run, passed all 250 tests before those two terminal regressions were added. +Those receipts are not relabelled as 252-test runs. All three jars reproduce +exactly and four fresh native source GameTests pass. No Steam production +change was needed during this rerun. Connected-player interaction +and live-world visual limits remain recorded; acceptance does not relabel +unrun checks as passes. + +## Historical Publication Preparation + +The following is the preparation status on 11 November 2026. + +The local Power source pin remains 41959572b11dce236df98f23e97c6a3868cdda03; +the Base Metals shared-hook pin remains 616d527e50e4bf0137eeb9c44fa586e34a0452a0. +Their public file IDs remain empty. CI and CodeQL deliberately require merged +upstream source and checksum-matching public jars before compilation. Those +gates must stay closed until the dependency releases are verified. + +The upstream master-1.21.11 branch still points at merged 1.21.1 tip +b145f8d1a7c2a148f8f259faec7b203cf146b6b5, which is an ancestor of this port. +There is no hosted check for the local Steam HEAD or 2.2.16.121111 tag yet. +The current CI upload list names the correct player, sources and Javadoc +jars. Its out-of-layout build supplies the Power bootstrap and both audited +sources, and the atlas test now uses that same verified dependency jar. + +The default-branch publisher needs the 1.21.11 Java 21 route and a sealed +release-registry entry. Only the route is prepared locally; the entry must +wait for the merged source and its successful CI artifact. The tag validator +checks the build job, not CodeQL or wrapper validation. No effective upstream +branch rules were returned by the audit, so all three successful checks must +also be verified before release. No tag, dispatch or publication is part of +this preparation. + +## Dependency Metadata Correction History + +Base Metals is REQUIRED on both client and server. The local mods.toml +declares it exactly once with mandatory=true, ordering AFTER and the unchanged +version range [3.1.2.121111,4.0.0). Distribution metadata already required it. +The existing recipe conditions remain defensive data guards; they do not make +a no-Base-Metals installation supported. + +This correction changes player-jar metadata bytes, not version, production +classes, recipes, configuration or dependency pins. The previous 25EE player +jar is held under build/required-basemetals-41/held-before, with its checksum +recorded before editing. Focused native ModInfo regression/static checks and +any local candidate-byte report are recorded under build/required-basemetals-41. +These local edits are not a frozen release or whole-world qualification. + +Power's approved metadata source 41959572b11dce236df98f23e97c6a3868cdda03 +and SHA-256 C98D2CF163BE2480D9AFB0023BBD2D92D401AE0D50BEF73191235BF3F83774A7 +were pinned for the default build at that stage. The RED attempt used the +preserved, hash-checked D input without changing its committed pins at that +stage. No OreSpawn change, publication, version bump or packaged-world/endurance +campaign is part of this correction. The smoke helper rejects its former +no-Base-Metals mode before starting a runtime. + +## Historical C2 Proof + +This is historical C2 candidate qualification against Power134F, not an +overall family release claim. The parent reports a later, broader chained +1.13 dimension route exposing a Power dynamic-biome-registry issue; its fix +and replacement pin are outside this preserved candidate's proof. + +Candidate C2 pins the local Power registry, BlockItem-name and atlas fixes +and the Base Metals callback fix below. A/B evidence remains preserved under +build/qualified-A-63eef004 and build/native-B-35, not relabelled C2. +The forced 43-task source/release suite, actual Eclipse ECJ scopes, all four +GameTests and both packaged save/reopen profiles pass against these pins. +The ordinary Steam player jar matches the frozen C2 client candidate: +25EE7784EAA180C77B7735B90928801F56A7342B5733B49222E2B0E72E5577F9. +Only assets/minecraft/atlases/items.json differs from the earlier 5B4D jar; +all production class payloads are unchanged. +Exact dependency hashes and source commits are build-audit pins, not embedded +manifest fields; the runtime metadata retains the same dependency versions. + +Local source port 2.2.16.121111 targets Forge 61.1.0, official mappings, +MCP 20251223.124241, Java 21, ForgeGradle 7.0.34 and Gradle 9.6.1. +The starting source is manually accepted 1.21.1 commit +9c5bf9b4610e06d3b5a6d5a0bde38d7516327d96. Its 228 unit tests and four +GameTests are a baseline, not qualification of this port. + +The approved local basePower 2.4.15.121111 integration candidate is pinned to +a1cd197b4b6c3fc9c5d56da254d0f0ae9929fe8c and native jar SHA-256 +134FF55595E86D450DBFB5531F389BF0BE81FE6347F1EB03765B571A1D488B59 +with exact native unit-bootstrap and registry-support source hashes. The +default local dependency paths pass without path overrides. Public release +file IDs for local-only candidates remain empty and hosted publication gates +remain closed. +The screen-aware overload uses the native Matrix3x2fStack and retains its +independent pivot and sweep geometry checks. The deprecated global overload +retains its signature and delegates through the current native machine screen +only during its actual decoration callback. No shared Power API or runtime +jar change was needed. Submission and failure paths restore the caller matrix. +The local Base Metals 3.1.2.121111 shared-hook callback fix is pinned to source +616d527e50e4bf0137eeb9c44fa586e34a0452a0 and jar SHA-256 +598D3FB0FF956EBCC67FE477A01CE393C75D0013AF0CE00A79E2072B63E9F2BA. +Earlier receipts used Power source 398439074a60bf9b8f8872f92485cffc4aa96be9 +and jar 0240E032075AF6B158887FBF98CE038504942329527036B82FD7CF9EEE602503, +with Base Metals source e6b869ffe7d6fd6a011a99f1844d2a7995394daf and jar +6D24D760E8E025960095DE04C51E6B79CFF93071618FC0B6BF64E05C346CC097. +The parent's real-handler regression found a cancellable RETURN ordering +defect in those shared inputs; they and their receipts remain preserved under +build/native-pinned-28, not relabelled as fixed or used for current proof. +OreSpawn 4.1.0.121111 is pinned to the published native jar. +Forge installer, server shim, server and universal hashes were checked against +the authentic local runtime seed and the proven native Base Metals reference. + +Persistent IDs, the legacy namespace, recipe identities, configuration +keys and saved machine fields are unchanged. The native port uses keyed +block/item properties, Identifier, Forge event bus groups, value-based +block-entity persistence, server recipe access and render-state submission. +Ingredients use native string/tag selectors. Native item definitions retain +the musket loaded byte and using-item model choices. + +The mod root declares data format [94,1]. showAsResourcePack=false keeps the +client assets in Forge's native hidden aggregate, which supplies format 75.x +metadata. Both former codec regressions now exercise actual Forge mod-file, +pack-discovery and resource-reader paths. No fallback metadata, ignored codec +warnings or synthetic replacement APIs are used. Omitting pack.mcmeta does +not synthesize suitable metadata in the native PathPackResources supplier. + +Forge 61.1.0 independently labels server mod packs using the client version +75.0, so its discovered server pack reports TOO_NEW even though native +SERVER_DATA metadata correctly accepts 94.1. The receipt records both values; +this platform compatibility-label issue remains open, not silently waived. +See FORGE61-RESOURCE-METADATA.md for parent integration guidance. + +The default local check/build/Javadoc/dependency/archive/checksum/Eclipse +metadata suite passes (43 tasks), with 244 unit tests and zero failures, +errors or skips. All three real Eclipse .classpath scopes compile using ECJ +3.46.0; those ECJ-compiled unit tests also pass with zero skips/assumptions. +Both source compilers include production, unit and GameTest Java sources. +All four native GameTests pass, selected by basesteam:* with exact named XML +results and no failures/skips. Forge 61's test-function registry and native +test_instance resources are confined to the isolated GameTest source scope. +The first launch's built-in-only one-test result correctly failed the gate. +Historical pre-clarification dedicated startup/save/reopen evidence exists +for both the with-Base-Metals and without-Base-Metals inputs. The latter is +retained only as old evidence: it is not supported by the current REQUIRED +dependency policy and does not qualify the new metadata bytes. Every old process verifies +the exact copied candidate hashes and checks latest/stdout/stderr without +filtering loader/data errors. These profiles explicitly select native NIO +transport on Windows. The earlier default native-availability selection failed +the strict stderr gate on the platform's Log4j/Netty unsupported kqueue/epoll +probe; its failed receipt is retained, not waived as a clean default run. +The parent's pure-platform receipt independently shows the same diagnostic. +The empty-cache platform proof is preserved under build/repro path with +spaces 34. C2's independent path-with-spaces reproduction is recorded under +build/repro path with spaces 39, with a fresh source stage and the same +native-engine cache, not another cold download. It uses C2's exact pins and +no dependency path overrides. Combined migration/endurance and manual acceptance remain +separate parent/user gates; these isolated checks do not establish them. +Nothing has been pushed, tagged or published. + +Merged 1.21.1 source b145f8d1a7c2a148f8f259faec7b203cf146b6b5 changes only +release pins and documentation from 9c5bf9b4610e06d3b5a6d5a0bde38d7516327d96. +Historical release notes were carried forward without importing 1.21.1 pins +or transferring its runtime acceptance to this port. + +The native item atlas contains only the items directory with prefix items/. +The drill single source remains exclusively in Steam's block atlas. Native +SpriteSources/resource-reader tests cover every model texture and reject +block/material sources in ITEMS and any item-atlas resolution of world-block +textures. Power's block atlas intentionally retains legacy items/ lookup; +true item-icon overlap is tested positively, not mistaken for the block/material +shadowing defect. The omitted-Power and malformed-source controls remain strict. +Both native BlockItem description and hover-name keys are checked through +the actual registration factory for all 17 blocks, with a missing-prefix +negative control. These proofs run in child JVMs so intrusive holders cannot +leak into later registry-freeze tests. The initial isolation failure is retained. + +The parent's actual frozen combined client run 20261111-182839-413cf9f7 +passes with this exact Steam jar, Power134F, BM598D and the other frozen +profile inputs: 94 items, 905 states, 39 GUIs, 351 caption checks, 355 rejected +negative controls, 26 fluid-gauge pixel cases, recipe-book grouping and zero +resource errors. The frozen profile hash is +9109C8904BE3D42185F5A636EC5F0EF6ED36D5DD5986F14E4B607EC03704D700. +Input copies/origins and the receipt hashes were checked independently; +build/native-C2-38/frozen-client-proof-receipt.json records the binding. +Worldless renderer geometry retains its reported manual gaps; automated +client resource proof is not manual gameplay or world-renderer acceptance. + +Independent checks pass for the source/resource contracts, 24 recipe unlocks, +33 normalized recipe payloads and 113 unchanged protected resource files. +The two companion native jar audits pass. Eclipse metadata generation passes. +The earlier 27-test receipt with two pack-codec failures is retained. Current +native resource-reader tests verify 161 client and 91 server files byte for +byte from a resource-only fixture jar, with no WARN/ERROR diagnostics. +The refreshed independent batch compiles with javac and actual Eclipse ECJ and +passes 32 tests. Native Ingredient.CODEC controls cover strings, tags and +alternative arrays, rejecting legacy object ingredients, empty lists and +malformed selectors. ItemPredicate.CODEC controls prove the native discovery +predicates remain restrictive and expose the ignored old item field. +The generator's 24 checked-in unlocks remain unchanged and --check passes. +Earlier evidence is under ignored build/native-independent-26 and +build/steam-native-compile-22. Current C2 evidence is under +build/native-C2-38, build/ecj-native61-C2-38-final, +build/steam-gametest-native61-C2-38-final and +evidence/packaged-server-native61-C2-38-{with,without}-metals-nio. +The C2 archives are compared against their exact graphics/client-bound bytes. +Earlier source/archive/ECJ and old-pin cold receipts remain under +build/native-pinned-28 and build/repro path with spaces 32. Failed attempts remain +alongside later runs, including the old ValueOutput namespace false positive +and initial atlas-fixture setup failures. C2 also retains the initially +over-broad global atlas-disjointness failure; the approved native contract +forbids block/material shadowing while preserving legacy true-item lookup. +No gameplay or world-upgrade +qualification is implied by source/asset checks. + +The parent's migration-probe module remains an unchanged 1.10/1.12 source +fixture writer and was not rebuilt by this child. Native C2 profiles instead +use the pinned native AdvantageWorksTestHarness; they do not load that legacy +probe. No native variant or unrun legacy-probe build is claimed as passing. diff --git a/gradle.properties b/gradle.properties index a992b2e..56111c0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,23 +1,24 @@ -org.gradle.jvmargs=-Xmx3G +org.gradle.jvmargs=-Xmx3G -XX:ActiveProcessorCount=4 +org.gradle.workers.max=2 org.gradle.daemon=false org.gradle.configuration-cache=false org.gradle.caching=true org.gradle.parallel=false net.minecraftforge.gradle.merge-source-sets=false -minecraft_version=1.21.1 -minecraft_version_range=[1.21.1] -forge_version=52.1.0 -forge_installer_sha256=F1B620F2879AD6A5BBE15DABA4D8F81EAB9F5E08004967604960B98996BDEBC4 -# Packaged smoke requires hashes from the coordinated, verified Forge 52 installation. -forge_server_shim_sha256=F7B706C79896728723DA0E4E8C5A6ECDB6A617AD4DF9D7EA1DDC6367187BC291 -forge_server_sha256=2DEA3FEFAD7DDF03E0A609CB2084A651732A35AE46DCF1D949D9359405A8621F -forge_universal_sha256=88EF6625122CFDB9D7B3176BD61B8C0692A191BAD0EDD77FFDDC6A765EE4979C -forge_version_range=[52.1.0,53) -loader_version_range=[52,53) +minecraft_version=1.21.11 +minecraft_version_range=[1.21.11] +forge_version=61.1.0 +# Verified against the parent's authentic Forge 61 runtime seed. +forge_installer_sha256=8BD1724EAEAF26F51DACFB7FDC46D79E9B640705B70D5F0019ED38907145EB48 +forge_server_shim_sha256=09008DB9881E35E62669865F3B7420741EDB1A52BC59C67A5FA03D24C6C8E6E3 +forge_server_sha256=8D997159003977A22DB8E4EA6DAAB6CBB0E76F752A196A1821C74734A1CF71D9 +forge_universal_sha256=101041931319D508D94EF9ED6643CA4374EA3950237443301F7AA5A3B2631CC4 +forge_version_range=[61.1.0,62) +loader_version_range=[61,62) mapping_channel=official -mapping_version=1.21.1 -mcp_version=20240808.132146 +mapping_version=1.21.11 +mcp_version=20251223.124241 loader_name=forge loader_code=1 @@ -30,35 +31,35 @@ curseforge_project_id=1781373 mod_id=basesteam mod_name=baseSteam mod_license=GPL-3.0-only -mod_version=2.2.15.121011 +mod_version=2.2.16.121111 mod_group=zone.moddev.mc.basesteam mod_authors=DrCyano mod_description=Steam-powered machines and infrastructure for basePower. cf_requirements=basepower,base-metals cf_optionals= -cf_versions=1.21.1 +cf_versions=1.21.11 -poweradvantage_version=2.4.14.121011 +poweradvantage_version=2.4.15.121111 poweradvantage_source_repository=MinecraftModDevelopmentMods/basePower -# Exact released dependencies. Source and jar checks reject mismatched overrides. -poweradvantage_source_ref=8d9937b3c0c9ef28a231b90fc2ff6adfea1af733 -poweradvantage_public_file_id=9559143 -poweradvantage_deobf_sha256=F91A613EB5FAA218AAD2DC3C881DEC7B897A5EE874215282A439E34B915C1AE6 -# Forge 52 uses the same native jar for development and packaged runtime. -poweradvantage_player_sha256=F91A613EB5FAA218AAD2DC3C881DEC7B897A5EE874215282A439E34B915C1AE6 -poweradvantage_test_bootstrap_sha256=6AF174EBBBF2501457CE4786A130AFDB6ED92CC47DD105465F9EF8685ECE90E3 -poweradvantage_test_bootstrap_source_sha256=CCEB755FDA6D38C492124DC122C993FA7EDA2A945CB603573D4CD9562B6AE280 -poweradvantage_test_registry_source_sha256=2E2C52DBAA14B4FA7EF1FF8C3A2BEFF9B1DF56CD6203C9CD692CAC1531D0981F +# Exact merged source and independently verified public Forge 1.21.11 release. +poweradvantage_source_ref=9b0043483d79d3e575895f12aeb0dde7ef6c4890 +poweradvantage_public_file_id=9566998 +poweradvantage_deobf_sha256=C98D2CF163BE2480D9AFB0023BBD2D92D401AE0D50BEF73191235BF3F83774A7 +# Forge 61 uses the same native jar for development and packaged runtime. +poweradvantage_player_sha256=C98D2CF163BE2480D9AFB0023BBD2D92D401AE0D50BEF73191235BF3F83774A7 +poweradvantage_test_bootstrap_sha256=FDE28801E41787E05212F3DEF7FAF34C6859402A22B71866D15B468C300DA883 +poweradvantage_test_bootstrap_source_sha256=CDE40D09A40DB95F293BC56723E70B321C205850275696BEBFC2A575A8B7FE48 +poweradvantage_test_registry_source_sha256=01DDE50C9874C2DC501BE2F699CD8D16D6974FACFAAC1B803434CA8C64227ABF -basemetals_version=3.1.2.121011 -basemetals_source_commit=327c197bcdd0bb5e883d844bb6a6c8791bc580b7 -basemetals_public_file_id=9559006 -basemetals_deobf_sha256=7C0B961A8D4991C9B69379518AAD700A71C1E4B2076CF5DEF292C58029408A07 -basemetals_player_sha256=7C0B961A8D4991C9B69379518AAD700A71C1E4B2076CF5DEF292C58029408A07 +basemetals_version=3.1.2.121111 +basemetals_source_commit=6f4c44e4d0a68bca3838fb025c09cddfaaa08fe8 +basemetals_public_file_id=9566738 +basemetals_deobf_sha256=598D3FB0FF956EBCC67FE477A01CE393C75D0013AF0CE00A79E2072B63E9F2BA +basemetals_player_sha256=598D3FB0FF956EBCC67FE477A01CE393C75D0013AF0CE00A79E2072B63E9F2BA -orespawn_version=4.1.0.121011 -orespawn_source_commit=62c08169c71f4d4fd8f107289e63fd2a4c7c655c -orespawn_public_file_id=9189594 -orespawn_deobf_sha256=EB369B7D7CE6D6C453658347A420C2F0783B7F4D1AC7383C87176AC05192B5A0 -orespawn_player_sha256=EB369B7D7CE6D6C453658347A420C2F0783B7F4D1AC7383C87176AC05192B5A0 +orespawn_version=4.1.0.121111 +orespawn_source_commit=ecad75b24309a344f712d898d717c6a8823fc9f2 +orespawn_public_file_id=9268464 +orespawn_deobf_sha256=DE39929057288CEA110196C9E9D708E129B80BC360A2E53976337FFA6A84A67B +orespawn_player_sha256=DE39929057288CEA110196C9E9D708E129B80BC360A2E53976337FFA6A84A67B diff --git a/gradle/forge52-mavenizer.gradle b/gradle/forge52-mavenizer.gradle deleted file mode 100644 index 9031f95..0000000 --- a/gradle/forge52-mavenizer.gradle +++ /dev/null @@ -1,25 +0,0 @@ -import java.security.MessageDigest -import org.gradle.jvm.toolchain.JvmVendorSpec - -// Use OreSpawn's checksum-verified fixture for the native Forge 52 toolchain. -def tool = 'ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar' -def defaultJar = "../../../../MinecraftMineralogy/OS1211/OreSpawn/${tool}" -def fixture = file(providers.gradleProperty('forge52MavenizerJar').getOrElse(defaultJar)) -def expected = '019032A4A26ECB473D403EF017054D5892CE59A9904E8831B452287CD756397E' -if (!fixture.isFile()) { - throw new GradleException("Forge 52 requires OreSpawn's Mavenizer fixture: ${fixture}. " - + "Use -Pforge52MavenizerJar= for another checkout layout.") -} -def actual = MessageDigest.getInstance('SHA-256').digest(fixture.bytes).encodeHex().toString().toUpperCase(Locale.ROOT) -if (actual != expected) { - throw new GradleException("Forge 52 Mavenizer fixture SHA-256 mismatch: expected ${expected}, got ${actual}") -} - -fgtools.configure('mavenizer') { - classpath.from(fixture) - mainClass.set('net.minecraftforge.mcmaven.cli.Main') - javaLauncher.set(javaToolchains.launcherFor { - languageVersion = JavaLanguageVersion.of(25) - vendor = JvmVendorSpec.ADOPTIUM - }) -} diff --git a/gradle/forge61-mavenizer.gradle b/gradle/forge61-mavenizer.gradle new file mode 100644 index 0000000..f6bad96 --- /dev/null +++ b/gradle/forge61-mavenizer.gradle @@ -0,0 +1,31 @@ +import java.security.MessageDigest +import org.gradle.jvm.toolchain.JvmVendorSpec + +// Use OreSpawn's checksum-verified fixture for the native Forge 61 toolchain. +def tool = 'ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar' +def defaultJar = "../../../../MinecraftMineralogy/OS12111/OreSpawn/${tool}" +def fixture = file(providers.gradleProperty('forge61MavenizerJar').getOrElse(defaultJar)) +def expected = 'B36CA046DB72E30F27BD38E02B4AE34274ADB51AE1D65BAF9E1D07763755682E' +if (!fixture.isFile()) { + throw new GradleException("Forge 61 requires OreSpawn's Mavenizer fixture: ${fixture}. " + + "Use -Pforge61MavenizerJar= for another checkout layout.") +} +def actual = MessageDigest.getInstance('SHA-256').digest(fixture.bytes).encodeHex().toString().toUpperCase(Locale.ROOT) +if (actual != expected) { + throw new GradleException("Forge 61 Mavenizer fixture SHA-256 mismatch: expected ${expected}, got ${actual}") +} +def rules = new File(fixture.parentFile, 'minecraft-source-compatibility.json') +def rulesHash = '88F2EE4C6B7C903EB1C7D693C0BEEFC0EFC601C1023FB197F7CBA187B1142DB7' +if (!rules.isFile() || MessageDigest.getInstance('SHA-256').digest(rules.bytes) + .encodeHex().toString().toUpperCase(Locale.ROOT) != rulesHash) { + throw new GradleException("Forge 61 Mavenizer source rules mismatch: ${rules}") +} + +fgtools.configure('mavenizer') { + classpath.from(fixture) + mainClass.set('net.minecraftforge.mcmaven.cli.Main') + javaLauncher.set(javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(25) + vendor = JvmVendorSpec.ADOPTIUM + }) +} diff --git a/src/gametest/java/zone/moddev/mc/basesteam/gametest/SteamGameTestRegistration.java b/src/gametest/java/zone/moddev/mc/basesteam/gametest/SteamGameTestRegistration.java new file mode 100644 index 0000000..3658a48 --- /dev/null +++ b/src/gametest/java/zone/moddev/mc/basesteam/gametest/SteamGameTestRegistration.java @@ -0,0 +1,28 @@ +package zone.moddev.mc.basesteam.gametest; + +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.Identifier; +import net.minecraftforge.eventbus.api.listener.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.registries.RegisterEvent; +import zone.moddev.mc.basesteam.SteamAdvantage; + +/** Native test functions are registered only in the isolated GameTest source scope. */ +@Mod.EventBusSubscriber(modid = SteamAdvantage.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) +public final class SteamGameTestRegistration { + private SteamGameTestRegistration() {} + + @SubscribeEvent public static void register(RegisterEvent event) { + event.register(Registries.TEST_FUNCTION, registry -> { + registry.register(id("drill_bit_packet_preserves_type_position_and_direction"), + SteamNativeGameTests::drillBitPacketPreservesTypePositionAndDirection); + registry.register(id("legacy_musket_wear_and_loaded_byte_survive_native_componentization"), + SteamNativeGameTests::legacyMusketWearAndLoadedByteSurviveNativeComponentization); + registry.register(id("older_musket_alias_and_top_level_wear_survive_power_migration"), + SteamNativeGameTests::olderMusketAliasAndTopLevelWearSurvivePowerMigration); + registry.register(id("native_resources_load_with_expected_ids"), SteamNativeGameTests::nativeResourcesLoadWithExpectedIds); + }); + } + + private static Identifier id(String path) { return Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, path); } +} diff --git a/src/gametest/java/zone/moddev/mc/basesteam/gametest/SteamNativeGameTests.java b/src/gametest/java/zone/moddev/mc/basesteam/gametest/SteamNativeGameTests.java index 479c474..9b80abf 100644 --- a/src/gametest/java/zone/moddev/mc/basesteam/gametest/SteamNativeGameTests.java +++ b/src/gametest/java/zone/moddev/mc/basesteam/gametest/SteamNativeGameTests.java @@ -3,12 +3,12 @@ import com.mojang.serialization.Dynamic; import io.netty.buffer.Unpooled; import net.minecraft.SharedConstants; -import net.minecraft.advancements.critereon.InventoryChangeTrigger; +import net.minecraft.advancements.criterion.InventoryChangeTrigger; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.Registries; -import net.minecraft.gametest.framework.GameTest; +import net.minecraftforge.gametest.GameTest; import net.minecraft.gametest.framework.GameTestHelper; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; @@ -16,7 +16,7 @@ import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.tags.TagKey; import net.minecraft.util.datafix.DataFixers; import net.minecraft.util.datafix.fixes.References; @@ -27,7 +27,7 @@ import net.minecraft.world.level.storage.loot.parameters.LootContextParams; import net.minecraft.world.phys.Vec3; import net.minecraftforge.gametest.GameTestDontPrefix; -import net.minecraftforge.gametest.GameTestHolder; +import net.minecraftforge.gametest.GameTestNamespace; import zone.moddev.mc.basepower.migration.LegacyWorldDataHook; import zone.moddev.mc.basepower.util.ItemData; import zone.moddev.mc.basesteam.SteamAdvantage; @@ -38,16 +38,16 @@ import zone.moddev.mc.basesteam.init.Enchantments; import zone.moddev.mc.basesteam.items.MusketItem; -@GameTestHolder(value = SteamAdvantage.MODID, namespace = SteamAdvantage.MODID) +@GameTestNamespace(SteamAdvantage.MODID) @GameTestDontPrefix public final class SteamNativeGameTests { private SteamNativeGameTests() {} - @GameTest(batch = SteamAdvantage.MODID, template = "basesteam_empty") + @GameTest(structure = "basesteam:basesteam_empty") public static void drillBitPacketPreservesTypePositionAndDirection(GameTestHelper helper) { BlockPos local = new BlockPos(1, 1, 1); helper.setBlock(local, Blocks.drillbit); - DrillBitTileEntity source = (DrillBitTileEntity) helper.getBlockEntity(local); + DrillBitTileEntity source = helper.getBlockEntity(local, DrillBitTileEntity.class); var registries = helper.getLevel().registryAccess(); for (Direction direction : Direction.values()) { source.setDirection(direction); @@ -58,8 +58,9 @@ public static void drillBitPacketPreservesTypePositionAndDirection(GameTestHelpe helper.assertTrue(received.getType() == Entities.DRILLBIT, "Wrong drill-bit packet type"); helper.assertTrue(received.getPos().equals(helper.absolutePos(local)), "Wrong drill-bit position"); DrillBitTileEntity destination = new DrillBitTileEntity(received.getPos(), source.getBlockState()); - destination.onDataPacket(null, received, registries); - helper.assertTrue(destination.getUpdateTag(registries).getByte("dir") == direction.get3DDataValue(), + destination.onDataPacket(null, net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, registries, received.getTag()), registries); + helper.assertTrue(destination.getUpdateTag(registries).getByteOr("dir", (byte)0) == direction.get3DDataValue(), "Lost drill-bit direction " + direction); } finally { buffer.release(); @@ -68,7 +69,7 @@ public static void drillBitPacketPreservesTypePositionAndDirection(GameTestHelpe helper.succeed(); } - @GameTest(batch = SteamAdvantage.MODID, template = "basesteam_empty") + @GameTest(structure = "basesteam:basesteam_empty") public static void legacyMusketWearAndLoadedByteSurviveNativeComponentization(GameTestHelper helper) { CompoundTag legacy = new CompoundTag(); legacy.putString("id", "basesteam:musket"); @@ -81,23 +82,24 @@ public static void legacyMusketWearAndLoadedByteSurviveNativeComponentization(Ga legacy.put("tag", tag); var registries = helper.getLevel().registryAccess(); var fixed = DataFixers.getDataFixer().update(References.ITEM_STACK, - new Dynamic<>(NbtOps.INSTANCE, legacy), 3465, SharedConstants.getCurrentVersion().getDataVersion().getVersion()); - ItemStack stack = ItemStack.parse(registries, fixed.getValue()).orElseThrow(); + new Dynamic<>(NbtOps.INSTANCE, legacy), 3465, SharedConstants.getCurrentVersion().dataVersion().version()); + ItemStack stack = ItemStack.CODEC.parse(registries.createSerializationContext(NbtOps.INSTANCE), fixed.getValue()).getOrThrow(); helper.assertTrue(stack.is(Items.blackpowder_musket), "Converted the wrong item"); helper.assertTrue(stack.getDamageValue() == 249 && stack.getMaxDamage() == 250, "Lost legacy wear"); helper.assertTrue(MusketItem.isLoaded(stack), "Lost the legacy loaded byte"); assertMusketEnchantments(helper, stack); MusketItem.unload(stack); - ItemStack reloaded = ItemStack.parse(registries, stack.save(registries)).orElseThrow(); + ItemStack reloaded = ItemStack.CODEC.parse(registries.createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE), + ItemStack.CODEC.encodeStart(registries.createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE), stack).getOrThrow()).getOrThrow(); helper.assertTrue(MusketItem.isNotLoaded(reloaded) && reloaded.getDamageValue() == 249, "Changing loaded state must not reset wear"); - helper.assertTrue("legacy fixture".equals(reloaded.get(DataComponents.CUSTOM_DATA).copyTag().getString("owner")), + helper.assertTrue("legacy fixture".equals(reloaded.get(DataComponents.CUSTOM_DATA).copyTag().getStringOr("owner", "")), "Changing loaded state must not discard unrelated legacy data"); assertMusketEnchantments(helper, reloaded); helper.succeed(); } - @GameTest(batch = SteamAdvantage.MODID, template = "basesteam_empty") + @GameTest(structure = "basesteam:basesteam_empty") public static void olderMusketAliasAndTopLevelWearSurvivePowerMigration(GameTestHelper helper) { CompoundTag legacy = new CompoundTag(); legacy.putString("id", "steamadvantage:musket"); @@ -116,22 +118,22 @@ public static void olderMusketAliasAndTopLevelWearSurvivePowerMigration(GameTest helper.succeed(); } - @GameTest(batch = SteamAdvantage.MODID, template = "basesteam_empty") + @GameTest(structure = "basesteam:basesteam_empty") public static void nativeResourcesLoadWithExpectedIds(GameTestHelper helper) { var level = helper.getLevel(); var registries = level.registryAccess(); - var nativeEnchantments = registries.registryOrThrow(Registries.ENCHANTMENT); + var nativeEnchantments = registries.lookupOrThrow(Registries.ENCHANTMENT); for (var key : java.util.List.of(Enchantments.high_explosive, Enchantments.powderless, Enchantments.rapid_reload, Enchantments.recoil)) { - var holder = nativeEnchantments.getHolderOrThrow(key); + var holder = nativeEnchantments.getOrThrow(key); helper.assertTrue(holder.is(net.minecraft.tags.EnchantmentTags.IN_ENCHANTING_TABLE), - "Missing table discovery for " + key.location()); + "Missing table discovery for " + key.identifier()); helper.assertTrue(holder.is(net.minecraft.tags.EnchantmentTags.TRADEABLE), - "Missing enchanted-book trade discovery for " + key.location()); + "Missing enchanted-book trade discovery for " + key.identifier()); helper.assertTrue(holder.is(net.minecraft.tags.EnchantmentTags.ON_RANDOM_LOOT), - "Missing random loot discovery for " + key.location()); + "Missing random loot discovery for " + key.identifier()); helper.assertTrue(holder.value().isPrimaryItem(new ItemStack(Items.blackpowder_musket)), - "Native musket support tag was not bound for " + key.location()); + "Native musket support tag was not bound for " + key.identifier()); } var fragile = TagKey.create(Registries.BLOCK, id("musket_fragile")); helper.assertTrue(net.minecraft.world.level.block.Blocks.SHORT_GRASS.defaultBlockState().is(fragile), @@ -165,12 +167,13 @@ public static void nativeResourcesLoadWithExpectedIds(GameTestHelper helper) { if (!chestTable.getRandomItems(params, seed).isEmpty()) foundSteamItem = true; } helper.assertTrue(foundSteamItem, "Steam chest rolls returned no items"); - helper.assertTrue(loot.getLootTable(Blocks.steam_pipe.getLootTable()) != LootTable.EMPTY, + helper.assertTrue(loot.getLootTable(Blocks.steam_pipe.getLootTable().orElseThrow()) != LootTable.EMPTY, "Missing Steam pipe block loot"); - var pipe = level.getRecipeManager().byKey(id("steam_pipe")); - var brass = net.minecraft.tags.ItemTags.create(ResourceLocation.fromNamespaceAndPath("forge", "ingots/brass")); + var pipe = level.recipeAccess().byKey(ResourceKey.create(Registries.RECIPE, id("steam_pipe"))); + var brass = net.minecraft.tags.ItemTags.create(Identifier.fromNamespaceAndPath("forge", "ingots/brass")); if (registries.lookupOrThrow(Registries.ITEM).get(brass).filter(tag -> tag.size() > 0).isPresent()) { - var result = pipe.orElseThrow().value().getResultItem(registries); + var recipe = (net.minecraft.world.item.crafting.ShapedRecipe)pipe.orElseThrow().value(); + var result = recipe.assemble(net.minecraft.world.item.crafting.CraftingInput.EMPTY, registries); helper.assertTrue(result.is(Blocks.steam_pipe.asItem()) && result.getCount() == 6, "Wrong Steam pipe recipe output"); } else { @@ -179,8 +182,8 @@ public static void nativeResourcesLoadWithExpectedIds(GameTestHelper helper) { helper.succeed(); } - private static ResourceLocation id(String path) { - return ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, path); + private static Identifier id(String path) { + return Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, path); } private static ListTag legacyEnchantments(String namespace) { @@ -200,7 +203,7 @@ private static void assertMusketEnchantments(GameTestHelper helper, ItemStack st Enchantments.rapid_reload, Enchantments.recoil)) { int expected = key.equals(Enchantments.high_explosive) || key.equals(Enchantments.recoil) ? 5 : 1; helper.assertTrue(Enchantments.level(key, stack, registries) == expected, - "Lost legacy enchantment holder or level: " + key.location()); + "Lost legacy enchantment holder or level: " + key.identifier()); } } } diff --git a/src/gametest/resources/data/basesteam/test_instance/drill_bit_packet_preserves_type_position_and_direction.json b/src/gametest/resources/data/basesteam/test_instance/drill_bit_packet_preserves_type_position_and_direction.json new file mode 100644 index 0000000..a26ddb9 --- /dev/null +++ b/src/gametest/resources/data/basesteam/test_instance/drill_bit_packet_preserves_type_position_and_direction.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "function": "basesteam:drill_bit_packet_preserves_type_position_and_direction", + "environment": "minecraft:default", + "structure": "basesteam:basesteam_empty", + "max_ticks": 100 +} diff --git a/src/gametest/resources/data/basesteam/test_instance/legacy_musket_wear_and_loaded_byte_survive_native_componentization.json b/src/gametest/resources/data/basesteam/test_instance/legacy_musket_wear_and_loaded_byte_survive_native_componentization.json new file mode 100644 index 0000000..abfb9ff --- /dev/null +++ b/src/gametest/resources/data/basesteam/test_instance/legacy_musket_wear_and_loaded_byte_survive_native_componentization.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "function": "basesteam:legacy_musket_wear_and_loaded_byte_survive_native_componentization", + "environment": "minecraft:default", + "structure": "basesteam:basesteam_empty", + "max_ticks": 100 +} diff --git a/src/gametest/resources/data/basesteam/test_instance/native_resources_load_with_expected_ids.json b/src/gametest/resources/data/basesteam/test_instance/native_resources_load_with_expected_ids.json new file mode 100644 index 0000000..7947b54 --- /dev/null +++ b/src/gametest/resources/data/basesteam/test_instance/native_resources_load_with_expected_ids.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "function": "basesteam:native_resources_load_with_expected_ids", + "environment": "minecraft:default", + "structure": "basesteam:basesteam_empty", + "max_ticks": 100 +} diff --git a/src/gametest/resources/data/basesteam/test_instance/older_musket_alias_and_top_level_wear_survive_power_migration.json b/src/gametest/resources/data/basesteam/test_instance/older_musket_alias_and_top_level_wear_survive_power_migration.json new file mode 100644 index 0000000..ba25e0f --- /dev/null +++ b/src/gametest/resources/data/basesteam/test_instance/older_musket_alias_and_top_level_wear_survive_power_migration.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "function": "basesteam:older_musket_alias_and_top_level_wear_survive_power_migration", + "environment": "minecraft:default", + "structure": "basesteam:basesteam_empty", + "max_ticks": 100 +} diff --git a/src/main/java/zone/moddev/mc/basesteam/SteamAdvantage.java b/src/main/java/zone/moddev/mc/basesteam/SteamAdvantage.java index 0afc165..7ad2d4e 100644 --- a/src/main/java/zone/moddev/mc/basesteam/SteamAdvantage.java +++ b/src/main/java/zone/moddev/mc/basesteam/SteamAdvantage.java @@ -9,7 +9,6 @@ import org.apache.logging.log4j.Logger; import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; @@ -27,12 +26,12 @@ import zone.moddev.mc.basesteam.migration.SteamConfigMigration; import zone.moddev.mc.basesteam.recipe.RecipeConditions; -/** Forge 1.21.1 entry point for baseSteam. */ +/** Forge 1.21.11 entry point for baseSteam. */ @Mod(SteamAdvantage.MODID) public final class SteamAdvantage { public static final String MODID = "basesteam"; public static final String NAME = "baseSteam"; - public static final String VERSION = "2.2.15.121011"; + public static final String VERSION = "2.2.16.121111"; public static final Logger LOGGER = LogManager.getLogger(MODID); public static float MUSKET_DAMAGE = 20.0F; @@ -45,13 +44,15 @@ public SteamAdvantage(FMLJavaModLoadingContext context) { migrateLegacyConfiguration(); context.registerConfig(ModConfig.Type.COMMON, SteamAdvantageConfig.SPEC); - var modBus = context.getModEventBus(); - modBus.addListener(SteamAdvantageConfig::onConfigLoading); - modBus.addListener(this::commonSetup); + var modBus = context.getModBusGroup(); + net.minecraftforge.fml.event.config.ModConfigEvent.Loading.getBus(modBus) + .addListener(SteamAdvantageConfig::onConfigLoading); + FMLCommonSetupEvent.getBus(modBus).addListener(this::commonSetup); RecipeConditions.register(modBus); - DistExecutor.runWhenOn(Dist.CLIENT, - () -> () -> zone.moddev.mc.basesteam.graphics.ClientRegistration.register(modBus)); + if (net.minecraftforge.fml.loading.FMLEnvironment.dist == Dist.CLIENT) { + zone.moddev.mc.basesteam.graphics.ClientRegistration.register(modBus); + } } private static void migrateLegacyConfiguration() { diff --git a/src/main/java/zone/moddev/mc/basesteam/blocks/DrillBitBlock.java b/src/main/java/zone/moddev/mc/basesteam/blocks/DrillBitBlock.java index 1e57274..1857ac8 100644 --- a/src/main/java/zone/moddev/mc/basesteam/blocks/DrillBitBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/blocks/DrillBitBlock.java @@ -20,7 +20,7 @@ public class DrillBitBlock extends Block implements EntityBlock { private static final VoxelShape SHAPE = Shapes.box(0.25D, 0.25D, 0.25D, 0.75D, 0.75D, 0.75D); public DrillBitBlock() { - super(SteamBlockProperties.metalHelper(2000.0F)); + super(SteamBlockProperties.metalHelper("drillbit", 2000.0F)); } @Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return new DrillBitTileEntity(pos, state); } @@ -35,29 +35,21 @@ public net.minecraft.world.level.block.entity.BlockEntit @Override public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { return SHAPE; } @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity victim) { - if (!world.isClientSide) victim.hurt(Power.machineDamage(world), 2.0F); - } - - @Override - public void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean moving) { - if (state.getBlock() != newState.getBlock()) { - BlockEntity tile = world.getBlockEntity(pos); - if (!world.isClientSide && tile instanceof DrillBitTileEntity) { - ((DrillBitTileEntity) tile).destroyLine(false); - } + public void entityInside(BlockState state, Level world, BlockPos pos, Entity victim, + net.minecraft.world.entity.InsideBlockEffectApplier effects, boolean moving) { + if (world instanceof net.minecraft.server.level.ServerLevel server) { + victim.hurtServer(server, Power.machineDamage(world), 2.0F); } - super.onRemove(state, world, pos, newState, moving); } @Override - public void wasExploded(Level world, BlockPos pos, Explosion explosion) { + public void wasExploded(net.minecraft.server.level.ServerLevel world, BlockPos pos, Explosion explosion) { destroyNeighbors(world, pos); super.wasExploded(world, pos, explosion); } private void destroyNeighbors(Level world, BlockPos pos) { - if (world.isClientSide) return; + if (world.isClientSide()) return; for (Direction side : Direction.values()) { BlockEntity tile = world.getBlockEntity(pos.relative(side)); if (tile instanceof DrillBitTileEntity) ((DrillBitTileEntity) tile).destroyLine(); diff --git a/src/main/java/zone/moddev/mc/basesteam/blocks/DrillBitTileEntity.java b/src/main/java/zone/moddev/mc/basesteam/blocks/DrillBitTileEntity.java index 06171b8..a8e82d8 100644 --- a/src/main/java/zone/moddev/mc/basesteam/blocks/DrillBitTileEntity.java +++ b/src/main/java/zone/moddev/mc/basesteam/blocks/DrillBitTileEntity.java @@ -27,7 +27,7 @@ public DrillBitTileEntity(BlockPos pos, BlockState state) { } public void tick(){ - if(hasLevel() && getLevel().isClientSide){ + if(hasLevel() && getLevel().isClientSide()){ rotation = ROTATION_PER_TICK * (getLevel().getGameTime() % 20); } } @@ -40,6 +40,12 @@ public void setDirection(Direction dir){ public Direction.Axis getDirection(){ return direction.getAxis(); } + + @Override + public void preRemoveSideEffects(BlockPos pos, BlockState state) { + destroyLine(false); + super.preRemoveSideEffects(pos, state); + } public static void createDrillBitBlock(Level w, BlockPos coord, Direction dir){ w.setBlockAndUpdate(coord, zone.moddev.mc.basesteam.init.Blocks.drillbit.defaultBlockState()); @@ -55,7 +61,7 @@ public void destroyLine(){ } public void destroyLine(boolean removeOrigin){ - if(!hasLevel() || getLevel().isClientSide) return; + if(!hasLevel() || getLevel().isClientSide()) return; List line = new ArrayList<>(); line.add(getBlockPos()); collectLine(line, direction); @@ -78,16 +84,16 @@ private void collectLine(List line, Direction f){ } @Override - protected void saveAdditional(CompoundTag root, HolderLookup.Provider registries){ - super.saveAdditional(root, registries); - writeDirectionToNBT(root); + protected void saveAdditional(net.minecraft.world.level.storage.ValueOutput root){ + super.saveAdditional(root); + root.putByte("dir", (byte)direction.get3DDataValue()); } @Override - protected void loadAdditional(CompoundTag root, HolderLookup.Provider registries){ - super.loadAdditional(root, registries); - readDirectionFromNBT(root); + protected void loadAdditional(net.minecraft.world.level.storage.ValueInput root){ + super.loadAdditional(root); + this.direction = decodeDirection(root.getByteOr("dir", root.getByteOr("d", (byte)direction.get3DDataValue()))); } @Override @@ -109,20 +115,12 @@ private void writeDirectionToNBT(CompoundTag root) { root.putByte("dir", (byte)direction.get3DDataValue()); } - private void readDirectionFromNBT(CompoundTag root) { - if(root.contains("dir")){ - this.direction = decodeDirection(root.getByte("dir")); - } else if(root.contains("d")){ - this.direction = decodeDirection(root.getByte("d")); - } - } - private static Direction decodeDirection(int index) { return index >= 0 && index < Direction.values().length ? Direction.from3DDataValue(index) : Direction.DOWN; } private void syncDirection() { - if(hasLevel() && !getLevel().isClientSide) { + if(hasLevel() && !getLevel().isClientSide()) { setChanged(); BlockState state = getLevel().getBlockState(getBlockPos()); getLevel().sendBlockUpdated(getBlockPos(), state, state, 3); diff --git a/src/main/java/zone/moddev/mc/basesteam/blocks/PlatformBlock.java b/src/main/java/zone/moddev/mc/basesteam/blocks/PlatformBlock.java index 4100996..09a1847 100644 --- a/src/main/java/zone/moddev/mc/basesteam/blocks/PlatformBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/blocks/PlatformBlock.java @@ -24,7 +24,7 @@ public class PlatformBlock extends Block { }; public PlatformBlock() { - super(SteamBlockProperties.metalHelper(2000.0F) + super(SteamBlockProperties.metalHelper("platform", 2000.0F) .isRedstoneConductor((state, world, pos) -> false) .isSuffocating((state, world, pos) -> false) .isViewBlocking((state, world, pos) -> false)); @@ -36,7 +36,8 @@ public PlatformBlock() { @Override public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { return SHAPES[state.getValue(HEIGHT)]; } @Override - public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean moving) { + public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, + net.minecraft.world.level.redstone.Orientation orientation, boolean moving) { if (!canBlockStay(world, pos)) world.destroyBlock(pos, true); } diff --git a/src/main/java/zone/moddev/mc/basesteam/blocks/PumpPipeBlock.java b/src/main/java/zone/moddev/mc/basesteam/blocks/PumpPipeBlock.java index dbdb175..cdd2597 100644 --- a/src/main/java/zone/moddev/mc/basesteam/blocks/PumpPipeBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/blocks/PumpPipeBlock.java @@ -15,26 +15,27 @@ public class PumpPipeBlock extends Block { private static final VoxelShape SHAPE = Shapes.box(0.0D, 0.25D, 0.0D, 0.75D, 1.0D, 1.0D); public PumpPipeBlock() { - super(SteamBlockProperties.metalHelper(10.0F)); + super(SteamBlockProperties.metalHelper("pump_pipe_steam", 10.0F)); } @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 void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean moving) { - if (state.getBlock() != newState.getBlock()) destroyShaft(world, pos); - super.onRemove(state, world, pos, newState, moving); + public void affectNeighborsAfterRemoval(BlockState state, net.minecraft.server.level.ServerLevel world, + BlockPos pos, boolean moving) { + destroyShaft(world, pos); + super.affectNeighborsAfterRemoval(state, world, pos, moving); } @Override - public void wasExploded(Level world, BlockPos pos, Explosion explosion) { + public void wasExploded(net.minecraft.server.level.ServerLevel world, BlockPos pos, Explosion explosion) { destroyShaft(world, pos); super.wasExploded(world, pos, explosion); } private void destroyShaft(Level world, BlockPos pos) { - if (world.isClientSide) return; + if (world.isClientSide()) return; BlockPos cursor = pos.below(); while (cursor.getY() > 0 && world.getBlockState(cursor).getBlock() == this) { world.destroyBlock(cursor, false); diff --git a/src/main/java/zone/moddev/mc/basesteam/blocks/SteamBlockProperties.java b/src/main/java/zone/moddev/mc/basesteam/blocks/SteamBlockProperties.java index 8f17043..51d381f 100644 --- a/src/main/java/zone/moddev/mc/basesteam/blocks/SteamBlockProperties.java +++ b/src/main/java/zone/moddev/mc/basesteam/blocks/SteamBlockProperties.java @@ -5,16 +5,21 @@ import net.minecraft.world.level.material.PushReaction; /** Native block properties matching the old piston-like construction material. */ -final class SteamBlockProperties { +public final class SteamBlockProperties { private SteamBlockProperties() {} - static Block.Properties pistonLike() { - return Block.Properties.of().mapColor(MapColor.STONE).pushReaction(PushReaction.BLOCK) + public static net.minecraft.resources.ResourceKey key(String path) { + return net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.BLOCK, + net.minecraft.resources.Identifier.fromNamespaceAndPath("basesteam", path)); + } + + public static Block.Properties pistonLike(String path) { + return Block.Properties.of().setId(key(path)).mapColor(MapColor.STONE).pushReaction(PushReaction.BLOCK) .forceSolidOn().strength(0.75F); } - static Block.Properties metalHelper(float resistance) { - return Block.Properties.of().mapColor(MapColor.METAL).forceSolidOn().strength(5.0F, resistance) + static Block.Properties metalHelper(String path, float resistance) { + return Block.Properties.of().setId(key(path)).mapColor(MapColor.METAL).forceSolidOn().strength(5.0F, resistance) .noOcclusion().noLootTable(); } } diff --git a/src/main/java/zone/moddev/mc/basesteam/blocks/SteamPipeBlock.java b/src/main/java/zone/moddev/mc/basesteam/blocks/SteamPipeBlock.java index a8558b0..49949d7 100644 --- a/src/main/java/zone/moddev/mc/basesteam/blocks/SteamPipeBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/blocks/SteamPipeBlock.java @@ -5,7 +5,7 @@ public class SteamPipeBlock extends cyano.poweradvantage.api.simple.BlockSimplePowerConduit{ public SteamPipeBlock() { - super(SteamBlockProperties.pistonLike(), 2f/16f, Power.steam_power); + super(SteamBlockProperties.pistonLike("steam_pipe"), 2f/16f, Power.steam_power); } } diff --git a/src/main/java/zone/moddev/mc/basesteam/blocks/SteamTrackBlock.java b/src/main/java/zone/moddev/mc/basesteam/blocks/SteamTrackBlock.java index 932a78a..eb9ab3f 100644 --- a/src/main/java/zone/moddev/mc/basesteam/blocks/SteamTrackBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/blocks/SteamTrackBlock.java @@ -11,7 +11,7 @@ public class SteamTrackBlock extends BlockSimplePowerConduit { public SteamTrackBlock() { - super(SteamBlockProperties.pistonLike().noOcclusion() + super(SteamBlockProperties.pistonLike("steam_track").noOcclusion() .isRedstoneConductor((state, world, pos) -> false) .isSuffocating((state, world, pos) -> false) .isViewBlocking((state, world, pos) -> false), diff --git a/src/main/java/zone/moddev/mc/basesteam/compat/BaseMetalsCompat.java b/src/main/java/zone/moddev/mc/basesteam/compat/BaseMetalsCompat.java index 6d79986..67de180 100644 --- a/src/main/java/zone/moddev/mc/basesteam/compat/BaseMetalsCompat.java +++ b/src/main/java/zone/moddev/mc/basesteam/compat/BaseMetalsCompat.java @@ -37,8 +37,10 @@ public static ItemStack getCrusherRecipeOutput(Level world, ItemStack input) { private static final class LoadedCrushingRecipes { private static ItemStack find(Level world, ItemStack input) { SingleRecipeInput inventory = new SingleRecipeInput(input.copy()); - for (var candidate : world.getRecipeManager().getAllRecipesFor(CrushingRecipe.TYPE.get())) { - CrushingRecipe crushing = candidate.value(); + if (!(world instanceof net.minecraft.server.level.ServerLevel server)) return null; + for (var candidate : server.recipeAccess().getRecipes().stream() + .filter(holder -> CrushingRecipe.TYPE.get().equals(holder.value().getType())).toList()) { + CrushingRecipe crushing = (CrushingRecipe)candidate.value(); if (crushing.matches(inventory, world)) { return crushing.getResultItem(world.registryAccess()).copy(); } diff --git a/src/main/java/zone/moddev/mc/basesteam/graphics/ClientRegistration.java b/src/main/java/zone/moddev/mc/basesteam/graphics/ClientRegistration.java index 558b145..d67e505 100644 --- a/src/main/java/zone/moddev/mc/basesteam/graphics/ClientRegistration.java +++ b/src/main/java/zone/moddev/mc/basesteam/graphics/ClientRegistration.java @@ -3,12 +3,7 @@ import net.minecraftforge.client.event.EntityRenderersEvent; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; -import net.minecraftforge.eventbus.api.IEventBus; -import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; -import net.minecraft.client.renderer.item.ItemProperties; -import net.minecraft.resources.ResourceLocation; -import zone.moddev.mc.basesteam.init.Items; -import zone.moddev.mc.basesteam.items.MusketItem; +import net.minecraftforge.eventbus.api.bus.BusGroup; import zone.moddev.mc.basesteam.init.Entities; /** Client-only renderer registration. */ @@ -16,22 +11,12 @@ public final class ClientRegistration { private ClientRegistration() {} - public static void register(IEventBus modBus) { - modBus.addListener(ClientRegistration::onClientSetup); - modBus.addListener(ClientRegistration::registerRenderers); + public static void register(BusGroup modBus) { + EntityRenderersEvent.RegisterRenderers.BUS.addListener(ClientRegistration::registerRenderers); } private static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) { event.registerBlockEntityRenderer(Entities.DRILLBIT, DrillBitRenderer::new); } - private static void onClientSetup(FMLClientSetupEvent event) { - event.enqueueWork(() -> { - ItemProperties.register(Items.blackpowder_musket, ResourceLocation.withDefaultNamespace("loaded"), - (item, world, entity, seed) -> MusketItem.isLoaded(item) ? 1.0F : 0.0F); - ItemProperties.register(Items.blackpowder_musket, ResourceLocation.withDefaultNamespace("loading"), - (item, world, entity, seed) -> entity != null && entity.isUsingItem() - && entity.getUseItem() == item ? 1.0F : 0.0F); - }); - } } diff --git a/src/main/java/zone/moddev/mc/basesteam/graphics/DrillBitRenderer.java b/src/main/java/zone/moddev/mc/basesteam/graphics/DrillBitRenderer.java index 94dfb6e..237a72b 100644 --- a/src/main/java/zone/moddev/mc/basesteam/graphics/DrillBitRenderer.java +++ b/src/main/java/zone/moddev/mc/basesteam/graphics/DrillBitRenderer.java @@ -2,44 +2,80 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.rendertype.RenderTypes; +import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; +import net.minecraft.client.renderer.feature.ModelFeatureRenderer; +import net.minecraft.client.renderer.state.CameraRenderState; +import net.minecraft.world.phys.Vec3; import com.mojang.math.Axis; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider.Context; import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import zone.moddev.mc.basesteam.SteamAdvantage; import zone.moddev.mc.basesteam.blocks.DrillBitTileEntity; @OnlyIn(Dist.CLIENT) -public class DrillBitRenderer implements BlockEntityRenderer { - private static final ResourceLocation TEXTURE = - ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/entity/drill_bit.png"); +public class DrillBitRenderer implements BlockEntityRenderer { + private static final Identifier TEXTURE = + Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/entity/drill_bit.png"); public DrillBitRenderer(Context dispatcher) { } + public static final class State extends BlockEntityRenderState { + Direction.Axis axis = Direction.Axis.Y; + float rotation; + } + + @Override public State createRenderState() { return new State(); } + + @Override + public void extractRenderState(DrillBitTileEntity tile, State state, float partialTick, Vec3 camera, + ModelFeatureRenderer.CrumblingOverlay breaking) { + BlockEntityRenderer.super.extractRenderState(tile, state, partialTick, camera, breaking); + state.axis = tile.getDirection(); + state.rotation = tile.rotation + DrillBitTileEntity.ROTATION_PER_TICK * partialTick; + } + @Override - public void render(DrillBitTileEntity tile, float partialTick, PoseStack matrices, - MultiBufferSource buffers, int light, int overlay) { - renderGeometry(tile.getDirection(), tile.rotation + DrillBitTileEntity.ROTATION_PER_TICK * partialTick, - matrices, buffers.getBuffer(RenderType.entityTranslucent(TEXTURE)), light, overlay); + public void submit(State state, PoseStack matrices, SubmitNodeCollector collector, CameraRenderState camera) { + matrices.pushPose(); + try { + positionGeometry(state.axis, state.rotation, matrices); + int light = state.lightCoords; + collector.submitCustomGeometry(matrices, RenderTypes.entityTranslucent(TEXTURE), + (pose, vertices) -> renderQuads(pose, vertices, light, + net.minecraft.client.renderer.texture.OverlayTexture.NO_OVERLAY)); + } finally { + matrices.popPose(); + } } static void renderGeometry(Direction.Axis axis, float rotation, PoseStack matrices, VertexConsumer vertices, int light, int overlay) { matrices.pushPose(); try { - matrices.translate(0.5D, 0.5D, 0.5D); - if (axis != Direction.Axis.Y) { - matrices.mulPose(Axis.XP.rotationDegrees(90.0F)); - if (axis != Direction.Axis.Z) matrices.mulPose(Axis.ZP.rotationDegrees(90.0F)); - } - matrices.mulPose(Axis.YP.rotationDegrees(rotation)); - PoseStack.Pose transform = matrices.last(); + positionGeometry(axis, rotation, matrices); + renderQuads(matrices.last(), vertices, light, overlay); + } finally { + matrices.popPose(); + } + } + + private static void positionGeometry(Direction.Axis axis, float rotation, PoseStack matrices) { + matrices.translate(0.5D, 0.5D, 0.5D); + if (axis != Direction.Axis.Y) { + matrices.mulPose(Axis.XP.rotationDegrees(90.0F)); + if (axis != Direction.Axis.Z) matrices.mulPose(Axis.ZP.rotationDegrees(90.0F)); + } + matrices.mulPose(Axis.YP.rotationDegrees(rotation)); + } + + private static void renderQuads(PoseStack.Pose transform, VertexConsumer vertices, int light, int overlay) { final float radius = 0.25F; // Retain the original six quads and UV layout; native buffers supply lighting and overlay. vertex(transform, vertices, radius, 0.5F, -radius, 0, 0, 0, 0, -1, light, overlay); @@ -71,9 +107,6 @@ static void renderGeometry(Direction.Axis axis, float rotation, PoseStack matric vertex(transform, vertices, -radius, -0.5F, -radius, 0.5F, 0.5F, 0, -1, 0, light, overlay); vertex(transform, vertices, radius, -0.5F, -radius, 1, 0.5F, 0, -1, 0, light, overlay); vertex(transform, vertices, radius, -0.5F, radius, 1, 0, 0, -1, 0, light, overlay); - } finally { - matrices.popPose(); - } } private static void vertex(PoseStack.Pose transform, VertexConsumer vertices, diff --git a/src/main/java/zone/moddev/mc/basesteam/gui/BlastFurnaceGUI.java b/src/main/java/zone/moddev/mc/basesteam/gui/BlastFurnaceGUI.java index 499d7eb..a4b5dcd 100644 --- a/src/main/java/zone/moddev/mc/basesteam/gui/BlastFurnaceGUI.java +++ b/src/main/java/zone/moddev/mc/basesteam/gui/BlastFurnaceGUI.java @@ -3,7 +3,7 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import cyano.poweradvantage.api.simple.SimpleMachineGUI; import zone.moddev.mc.basepower.math.Integer2D; @@ -14,14 +14,14 @@ public class BlastFurnaceGUI extends SimpleMachineGUI{ public BlastFurnaceGUI() { super( - ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/steam_furnace.png"), + Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/steam_furnace.png"), Integer2D.fromCoordinates(29,80, 93,19, 111,19, 129,19, 93,80, 111,80, 129,80) ); for(int i = 4; i < 7; i++)super.specialSlots.put(i, (SlotContext c)->new Slot(c.machineInventory,c.slotIndex,c.screenPositionX,c.screenPositionY){ @Override public void onTake(Player player, ItemStack stack) { - stack.onCraftedBy(player.level(), player, stack.getCount()); + stack.onCraftedBy(player, stack.getCount()); super.onTake(player, stack); } @Override diff --git a/src/main/java/zone/moddev/mc/basesteam/gui/CoalBoilerGUI.java b/src/main/java/zone/moddev/mc/basesteam/gui/CoalBoilerGUI.java index c781a83..68339d9 100644 --- a/src/main/java/zone/moddev/mc/basesteam/gui/CoalBoilerGUI.java +++ b/src/main/java/zone/moddev/mc/basesteam/gui/CoalBoilerGUI.java @@ -1,6 +1,6 @@ package zone.moddev.mc.basesteam.gui; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import cyano.poweradvantage.api.simple.SimpleMachineGUI; import zone.moddev.mc.basepower.math.Integer2D; @@ -11,7 +11,7 @@ public class CoalBoilerGUI extends SimpleMachineGUI{ public CoalBoilerGUI() { super( - ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/coal_boiler.png"), + Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/coal_boiler.png"), new Integer2D[] {new Integer2D(80,80)} ); } diff --git a/src/main/java/zone/moddev/mc/basesteam/gui/ElectricBoilerGUI.java b/src/main/java/zone/moddev/mc/basesteam/gui/ElectricBoilerGUI.java index 4a205c0..1810810 100644 --- a/src/main/java/zone/moddev/mc/basesteam/gui/ElectricBoilerGUI.java +++ b/src/main/java/zone/moddev/mc/basesteam/gui/ElectricBoilerGUI.java @@ -1,6 +1,6 @@ package zone.moddev.mc.basesteam.gui; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import cyano.poweradvantage.api.simple.SimpleMachineGUI; import zone.moddev.mc.basepower.math.Integer2D; @@ -11,7 +11,7 @@ public class ElectricBoilerGUI extends SimpleMachineGUI{ public ElectricBoilerGUI() { super( - ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/electric_boiler.png"), + Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/electric_boiler.png"), new Integer2D[0] ); } diff --git a/src/main/java/zone/moddev/mc/basesteam/gui/GUIHelper.java b/src/main/java/zone/moddev/mc/basesteam/gui/GUIHelper.java index d1f5976..80c2f9d 100644 --- a/src/main/java/zone/moddev/mc/basesteam/gui/GUIHelper.java +++ b/src/main/java/zone/moddev/mc/basesteam/gui/GUIHelper.java @@ -2,13 +2,8 @@ import cyano.poweradvantage.api.simple.SimpleMachineGUI.GUIContainer; import zone.moddev.mc.basepower.gui.FluidTankGUI; -import com.mojang.blaze3d.vertex.Tesselator; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.math.Axis; -import com.mojang.blaze3d.vertex.BufferBuilder; -import com.mojang.blaze3d.vertex.DefaultVertexFormat; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; +import org.joml.Matrix3x2fStack; +import net.minecraft.resources.Identifier; import cyano.poweradvantage.api.fluid.FluidStack; @net.minecraftforge.api.distmarker.OnlyIn(net.minecraftforge.api.distmarker.Dist.CLIENT) @@ -26,20 +21,20 @@ public static float maxDelta(float newValue,float oldValue,float maxChange){ } public static void drawNeedle(GUIContainer container, float pivotX, float pivotY, float zLevel, float value) { - PoseStack pose = container.getMatrices(); - pose.pushPose(); + Matrix3x2fStack pose = container.getMatrices(); + pose.pushMatrix(); try { - positionNeedle(pose, pivotX, pivotY, zLevel, value); + positionNeedle(pose, pivotX, pivotY, value); container.blit(0, 0, 176, 0, 7, 35); } finally { - pose.popPose(); + pose.popMatrix(); } } - static void positionNeedle(PoseStack pose, float pivotX, float pivotY, float zLevel, float value) { - pose.translate(pivotX, pivotY, zLevel); - pose.mulPose(Axis.ZP.rotationDegrees(needleRotation(value))); - pose.translate(-3.5F, -17.5F, 0.0F); + static void positionNeedle(Matrix3x2fStack pose, float pivotX, float pivotY, float value) { + pose.translate(pivotX, pivotY); + pose.rotate((float) Math.toRadians(needleRotation(value))); + pose.translate(-3.5F, -17.5F); } private static float needleRotation(float value) { @@ -50,65 +45,12 @@ private static float needleRotation(float value) { @Deprecated public static void drawNeedle(float pivotX, float pivotY, float zLevel, float value) { - value = displayLevel(value); - - - final float pixelSize = 1.0f / 256; // conversion factor - - final float PI = 3.141592654f; - final float needleX = 176 ; - final float needleW = 7 ; - final float needleY = 0 ; - final float needleH = 35; - final float needleL = needleH * 0.5f; - final float needleR = needleW * 0.5f; - - float angle = (1f - value) * PI; - float offsetX = pivotX; - float offsetY = pivotY; - - float sin = Mth.sin(angle); - float cos = Mth.cos(angle); - - float tipX = cos * needleL + offsetX; - float tipY = -sin * needleL + offsetY; - float baseX = -cos * needleL + offsetX; - float baseY = sin * needleL + offsetY; - float dx = -sin * needleR; - float dy = -cos * needleR; - - float[] x = new float[4]; - float[] y = new float[4]; - float[] u = new float[4]; - float[] v = new float[4]; - // top-left, bottom-left, bottom-right, top-right - x[0] = tipX - dx; - x[1] = baseX - dx; - x[2] = baseX + dx; - x[3] = tipX + dx; - y[0] = tipY - dy; - y[1] = baseY - dy; - y[2] = baseY + dy; - y[3] = tipY + dy; - u[0] = pixelSize * needleX; - u[1] = u[0]; - u[2] = u[0] + pixelSize * needleW; - u[3] = u[2]; - v[0] = pixelSize * needleY; - v[1] = v[0] + pixelSize * needleH; - v[2] = v[1]; - v[3] = v[0]; - - com.mojang.blaze3d.systems.RenderSystem.setShader(net.minecraft.client.renderer.GameRenderer::getPositionTexShader); - BufferBuilder renderer = Tesselator.getInstance().begin( - com.mojang.blaze3d.vertex.VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX); + drawNeedle(drawingContainer(net.minecraft.client.Minecraft.getInstance().screen), pivotX, pivotY, zLevel, value); + } - // (x, y, z, u, v) - for(int i = 3; i >= 0; i--){ - renderer.addVertex(x[i], y[i], zLevel).setUv(u[i],v[i]); - } - com.mojang.blaze3d.vertex.BufferUploader.drawWithShader(renderer.buildOrThrow()); - + static GUIContainer drawingContainer(net.minecraft.client.gui.screens.Screen screen) { + if (screen instanceof GUIContainer container && container.getGraphics() != null) return container; + throw new IllegalStateException("A needle requires an active machine decoration callback"); } public static void drawFlameProgress(int x, int y, float flameHeight, GUIContainer gc){ @@ -127,7 +69,7 @@ public static void drawFlameProgress(int x, int y, float flameHeight, GUIContain public static void drawFluidBar(FluidStack fs, float barHeight, int xPos, int yPos, GUIContainer guiContainer, int x, int y, float z, - ResourceLocation displayImage, int texOverlayX, int texOverlayY, int texOverlayW, int texOverlayH){ + Identifier displayImage, int texOverlayX, int texOverlayY, int texOverlayW, int texOverlayH){ barHeight = displayLevel(barHeight); final int texMarginW = (texOverlayW - 16) / 2; final int texMarginH = (texOverlayH - 60) / 2; diff --git a/src/main/java/zone/moddev/mc/basesteam/gui/GeothermalBoilerGUI.java b/src/main/java/zone/moddev/mc/basesteam/gui/GeothermalBoilerGUI.java index 8df9301..067458e 100644 --- a/src/main/java/zone/moddev/mc/basesteam/gui/GeothermalBoilerGUI.java +++ b/src/main/java/zone/moddev/mc/basesteam/gui/GeothermalBoilerGUI.java @@ -1,6 +1,6 @@ package zone.moddev.mc.basesteam.gui; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import cyano.poweradvantage.api.simple.SimpleMachineGUI; import zone.moddev.mc.basepower.math.Integer2D; @@ -11,7 +11,7 @@ public class GeothermalBoilerGUI extends SimpleMachineGUI{ public GeothermalBoilerGUI() { super( - ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/geothermal_boiler.png"), + Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/geothermal_boiler.png"), new Integer2D[0] ); } diff --git a/src/main/java/zone/moddev/mc/basesteam/gui/OilBoilerGUI.java b/src/main/java/zone/moddev/mc/basesteam/gui/OilBoilerGUI.java index 5ba8fc6..042b938 100644 --- a/src/main/java/zone/moddev/mc/basesteam/gui/OilBoilerGUI.java +++ b/src/main/java/zone/moddev/mc/basesteam/gui/OilBoilerGUI.java @@ -5,14 +5,14 @@ import zone.moddev.mc.basepower.math.Integer2D; import zone.moddev.mc.basesteam.SteamAdvantage; import zone.moddev.mc.basesteam.machines.OilBoilerTileEntity; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import cyano.poweradvantage.api.fluid.FluidStack; public class OilBoilerGUI extends SimpleMachineGUI{ public OilBoilerGUI() { super( - ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/oil_boiler.png"), + Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/oil_boiler.png"), new Integer2D[0] ); } diff --git a/src/main/java/zone/moddev/mc/basesteam/gui/RockCrusherGUI.java b/src/main/java/zone/moddev/mc/basesteam/gui/RockCrusherGUI.java index 68c0e25..8f44c70 100644 --- a/src/main/java/zone/moddev/mc/basesteam/gui/RockCrusherGUI.java +++ b/src/main/java/zone/moddev/mc/basesteam/gui/RockCrusherGUI.java @@ -1,6 +1,6 @@ package zone.moddev.mc.basesteam.gui; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import cyano.poweradvantage.api.simple.SimpleMachineGUI; import zone.moddev.mc.basepower.math.Integer2D; @@ -11,7 +11,7 @@ public class RockCrusherGUI extends SimpleMachineGUI{ public RockCrusherGUI() { super( - ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/rock_crusher.png"), + Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/rock_crusher.png"), Integer2D.fromCoordinates(44,19, 44,88, 62,88, 80,88, 98,88, 116,88) ); } diff --git a/src/main/java/zone/moddev/mc/basesteam/gui/SteamDrillGUI.java b/src/main/java/zone/moddev/mc/basesteam/gui/SteamDrillGUI.java index 8c9b035..629b467 100644 --- a/src/main/java/zone/moddev/mc/basesteam/gui/SteamDrillGUI.java +++ b/src/main/java/zone/moddev/mc/basesteam/gui/SteamDrillGUI.java @@ -1,6 +1,6 @@ package zone.moddev.mc.basesteam.gui; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import cyano.poweradvantage.api.simple.SimpleMachineGUI; import zone.moddev.mc.basepower.math.Integer2D; @@ -11,7 +11,7 @@ public class SteamDrillGUI extends SimpleMachineGUI{ public SteamDrillGUI() { super( - ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/steam_drill.png"), + Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/steam_drill.png"), Integer2D.fromCoordinates(44,88, 62,88, 80,88, 98,88, 116,88) ); } diff --git a/src/main/java/zone/moddev/mc/basesteam/gui/SteamPumpGUI.java b/src/main/java/zone/moddev/mc/basesteam/gui/SteamPumpGUI.java index 51efb16..53d6de4 100644 --- a/src/main/java/zone/moddev/mc/basesteam/gui/SteamPumpGUI.java +++ b/src/main/java/zone/moddev/mc/basesteam/gui/SteamPumpGUI.java @@ -5,14 +5,14 @@ import zone.moddev.mc.basepower.math.Integer2D; import zone.moddev.mc.basesteam.SteamAdvantage; import zone.moddev.mc.basesteam.machines.SteamPumpTileEntity; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import cyano.poweradvantage.api.fluid.FluidStack; public class SteamPumpGUI extends SimpleMachineGUI{ public SteamPumpGUI() { super( - ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/steam_pump.png"), + Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/steam_pump.png"), new Integer2D[0] ); } diff --git a/src/main/java/zone/moddev/mc/basesteam/gui/SteamStillGUI.java b/src/main/java/zone/moddev/mc/basesteam/gui/SteamStillGUI.java index 2a9dbe5..18f667e 100644 --- a/src/main/java/zone/moddev/mc/basesteam/gui/SteamStillGUI.java +++ b/src/main/java/zone/moddev/mc/basesteam/gui/SteamStillGUI.java @@ -5,14 +5,14 @@ import zone.moddev.mc.basepower.math.Integer2D; import zone.moddev.mc.basesteam.SteamAdvantage; import zone.moddev.mc.basesteam.machines.SteamStillTileEntity; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import cyano.poweradvantage.api.fluid.FluidStack; public class SteamStillGUI extends SimpleMachineGUI{ public SteamStillGUI() { super( - ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/steam_still.png"), + Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/steam_still.png"), new Integer2D[0] ); } diff --git a/src/main/java/zone/moddev/mc/basesteam/gui/SteamTankGUI.java b/src/main/java/zone/moddev/mc/basesteam/gui/SteamTankGUI.java index 21f2a4b..7ec65cc 100644 --- a/src/main/java/zone/moddev/mc/basesteam/gui/SteamTankGUI.java +++ b/src/main/java/zone/moddev/mc/basesteam/gui/SteamTankGUI.java @@ -1,6 +1,6 @@ package zone.moddev.mc.basesteam.gui; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import cyano.poweradvantage.api.simple.SimpleMachineGUI; import zone.moddev.mc.basepower.math.Integer2D; @@ -11,7 +11,7 @@ public class SteamTankGUI extends SimpleMachineGUI{ public SteamTankGUI() { super( - ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/steam_tank.png"), + Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, "textures/gui/container/steam_tank.png"), new Integer2D[0] ); } diff --git a/src/main/java/zone/moddev/mc/basesteam/init/Blocks.java b/src/main/java/zone/moddev/mc/basesteam/init/Blocks.java index 8aa0ade..4c52316 100644 --- a/src/main/java/zone/moddev/mc/basesteam/init/Blocks.java +++ b/src/main/java/zone/moddev/mc/basesteam/init/Blocks.java @@ -8,8 +8,8 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.item.Item; import net.minecraft.world.item.BlockItem; -import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraft.resources.Identifier; +import net.minecraftforge.eventbus.api.listener.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.RegisterEvent; @@ -78,7 +78,8 @@ public static synchronized void init() { steam_elevator_platforms[i] = steam_elevator_platform.defaultBlockState().setValue(PlatformBlock.HEIGHT, i); } steam_elevator = (GUIBlock) add(new SteamElevatorBlock(), "steam_elevator"); - steam_switch = add(new BlockPowerSwitch(Power.steam_power), "steam_switch"); + steam_switch = add(new BlockPowerSwitch(Power.steam_power, + zone.moddev.mc.basesteam.blocks.SteamBlockProperties.pistonLike("steam_switch")), "steam_switch"); pump_pipe_steam = add(new PumpPipeBlock(), "pump_pipe_steam"); steam_still = (GUIBlock) add(new SteamStillBlock(), "steam_still"); steam_pump = (GUIBlock) add(new SteamPumpBlock(), "steam_pump"); @@ -96,14 +97,16 @@ public static void registerBlocks(RegisterEvent event) { if (!ForgeRegistries.Keys.BLOCKS.equals(event.getRegistryKey())) return; init(); event.register(ForgeRegistries.Keys.BLOCKS, registry -> - ALL_BLOCKS.forEach((id, block) -> registry.register(ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, id), block))); + ALL_BLOCKS.forEach((id, block) -> registry.register(Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, id), block))); } @SubscribeEvent public static void registerBlockItems(RegisterEvent event) { event.register(ForgeRegistries.Keys.ITEMS, registry -> ALL_BLOCKS.forEach((id, block) -> { - BlockItem item = new BlockItem(block, new Item.Properties()); - registry.register(ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, id), item); + BlockItem item = new BlockItem(block, new Item.Properties().setId( + net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.ITEM, + Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, id))).useBlockDescriptionPrefix()); + registry.register(Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, id), item); if (!"pump_pipe_steam".equals(id) && !"drillbit".equals(id)) ItemGroups.show(item); })); } diff --git a/src/main/java/zone/moddev/mc/basesteam/init/Enchantments.java b/src/main/java/zone/moddev/mc/basesteam/init/Enchantments.java index 8bec0b5..848ae15 100644 --- a/src/main/java/zone/moddev/mc/basesteam/init/Enchantments.java +++ b/src/main/java/zone/moddev/mc/basesteam/init/Enchantments.java @@ -7,7 +7,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.enchantment.Enchantment; import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; /** Stable save identities; holders belong to the current world's data-driven registry. */ public final class Enchantments { @@ -20,11 +20,11 @@ private Enchantments() {} public static int level(ResourceKey enchantment, ItemStack stack, RegistryAccess registries) { return EnchantmentHelper.getItemEnchantmentLevel( - registries.registryOrThrow(Registries.ENCHANTMENT).getHolderOrThrow(enchantment), stack); + registries.lookupOrThrow(Registries.ENCHANTMENT).getOrThrow(enchantment), stack); } private static ResourceKey key(String path) { return ResourceKey.create(Registries.ENCHANTMENT, - ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, path)); + Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, path)); } } diff --git a/src/main/java/zone/moddev/mc/basesteam/init/Entities.java b/src/main/java/zone/moddev/mc/basesteam/init/Entities.java index 78cb371..e287de4 100644 --- a/src/main/java/zone/moddev/mc/basesteam/init/Entities.java +++ b/src/main/java/zone/moddev/mc/basesteam/init/Entities.java @@ -4,8 +4,8 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.Block; -import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraft.resources.Identifier; +import net.minecraftforge.eventbus.api.listener.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.RegisterEvent; @@ -61,8 +61,8 @@ public static void register(RegisterEvent event) { private static BlockEntityType register( RegisterEvent event, String id, BlockEntityType.BlockEntitySupplier factory, Block block) { - BlockEntityType type = BlockEntityType.Builder.of(factory, block).build(null); - event.register(ForgeRegistries.Keys.BLOCK_ENTITY_TYPES, ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, id), () -> type); + BlockEntityType type = new BlockEntityType<>(factory, java.util.Set.of(block)); + event.register(ForgeRegistries.Keys.BLOCK_ENTITY_TYPES, Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, id), () -> type); return type; } } diff --git a/src/main/java/zone/moddev/mc/basesteam/init/Items.java b/src/main/java/zone/moddev/mc/basesteam/init/Items.java index 846f63b..09d1400 100644 --- a/src/main/java/zone/moddev/mc/basesteam/init/Items.java +++ b/src/main/java/zone/moddev/mc/basesteam/init/Items.java @@ -4,9 +4,9 @@ import java.util.Map; import java.util.Set; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.world.item.Item; -import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.eventbus.api.listener.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.RegisterEvent; @@ -28,10 +28,10 @@ private Items() {} /** Constructs items during the Forge item registry event. */ public static synchronized void init() { if (initialized) return; - steam_governor = add("steam_governor", new Item(new Item.Properties())); - steam_drill_bit = add("steam_drill_bit", new Item(new Item.Properties())); - blackpowder_cartridge = add("blackpowder_cartridge", new Item(new Item.Properties())); - blackpowder_musket = add("musket", new MusketItem(new Item.Properties().stacksTo(1))); + steam_governor = add("steam_governor", new Item(properties("steam_governor"))); + steam_drill_bit = add("steam_drill_bit", new Item(properties("steam_drill_bit"))); + blackpowder_cartridge = add("blackpowder_cartridge", new Item(properties("blackpowder_cartridge"))); + blackpowder_musket = add("musket", new MusketItem(properties("musket").stacksTo(1))); initialized = true; } @@ -40,12 +40,18 @@ private static Item add(String id, Item item) { return item; } + private static Item.Properties properties(String path) { + return new Item.Properties().setId(net.minecraft.resources.ResourceKey.create( + net.minecraft.core.registries.Registries.ITEM, + Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, path))); + } + @SubscribeEvent public static void register(RegisterEvent event) { if (!ForgeRegistries.Keys.ITEMS.equals(event.getRegistryKey())) return; init(); event.register(ForgeRegistries.Keys.ITEMS, registry -> allItems.forEach((id, item) -> { - registry.register(ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, id), item); + registry.register(Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, id), item); ItemGroups.show(item); })); } diff --git a/src/main/java/zone/moddev/mc/basesteam/init/Power.java b/src/main/java/zone/moddev/mc/basesteam/init/Power.java index 5a6d3ce..0a69527 100644 --- a/src/main/java/zone/moddev/mc/basesteam/init/Power.java +++ b/src/main/java/zone/moddev/mc/basesteam/init/Power.java @@ -4,7 +4,7 @@ import net.minecraft.world.damagesource.DamageType; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.world.level.Level; import cyano.poweradvantage.api.ConduitType; import zone.moddev.mc.basesteam.SteamAdvantage; @@ -28,10 +28,10 @@ public static DamageSource musketDamage(Level level) { } private static ResourceKey damageType(String id) { - return ResourceKey.create(Registries.DAMAGE_TYPE, ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, id)); + return ResourceKey.create(Registries.DAMAGE_TYPE, Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, id)); } private static DamageSource damageSource(Level level, ResourceKey type) { - return new DamageSource(level.registryAccess().registryOrThrow(Registries.DAMAGE_TYPE).getHolderOrThrow(type)); + return new DamageSource(level.registryAccess().lookupOrThrow(Registries.DAMAGE_TYPE).getOrThrow(type)); } } diff --git a/src/main/java/zone/moddev/mc/basesteam/init/Villages.java b/src/main/java/zone/moddev/mc/basesteam/init/Villages.java index 638734d..0f9995b 100644 --- a/src/main/java/zone/moddev/mc/basesteam/init/Villages.java +++ b/src/main/java/zone/moddev/mc/basesteam/init/Villages.java @@ -5,14 +5,14 @@ import net.minecraft.tags.EnchantmentTags; import net.minecraft.util.RandomSource; import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.entity.npc.VillagerProfession; -import net.minecraft.world.entity.npc.VillagerTrades; +import net.minecraft.world.entity.npc.villager.VillagerProfession; +import net.minecraft.world.entity.npc.villager.VillagerTrades; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.trading.MerchantOffer; import net.minecraft.world.item.trading.ItemCost; import net.minecraftforge.event.village.VillagerTradesEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.eventbus.api.listener.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import zone.moddev.mc.basesteam.SteamAdvantage; @@ -24,7 +24,7 @@ public static void init() {} @SubscribeEvent public static void addTrades(VillagerTradesEvent event) { - VillagerProfession profession = event.getType(); + net.minecraft.resources.ResourceKey profession = event.getType(); List novice = event.getTrades().get(1); if (profession == VillagerProfession.ARMORER || profession == VillagerProfession.TOOLSMITH || profession == VillagerProfession.WEAPONSMITH || profession == VillagerProfession.LIBRARIAN) { @@ -36,12 +36,12 @@ public static void addTrades(VillagerTradesEvent event) { novice.add(sell(Items.blackpowder_musket, 10, 15)); } if (profession == VillagerProfession.CLERIC) { - event.getTrades().get(3).add((trader, random) -> { + event.getTrades().get(3).add((level, trader, random) -> { int enchantmentLevel = 5 + random.nextInt(15); ItemStack musket = EnchantmentHelper.enchantItem(random, new ItemStack(Items.blackpowder_musket), enchantmentLevel, - trader.registryAccess().registryOrThrow(Registries.ENCHANTMENT) - .getTag(EnchantmentTags.NON_TREASURE).orElseThrow().stream()); + trader.registryAccess().lookupOrThrow(Registries.ENCHANTMENT) + .getOrThrow(EnchantmentTags.NON_TREASURE).stream()); int price = Math.min(randomPrice(random, 15, 25) + enchantmentLevel, 64); return offer(price, musket); }); @@ -49,7 +49,7 @@ public static void addTrades(VillagerTradesEvent event) { } private static VillagerTrades.ItemListing sell(Item item, int min, int max) { - return (trader, random) -> { + return (level, trader, random) -> { int price = randomPrice(random, min, max); return offer(price < 0 ? 1 : price, new ItemStack(item, price < 0 ? -price : 1)); }; diff --git a/src/main/java/zone/moddev/mc/basesteam/items/MusketItem.java b/src/main/java/zone/moddev/mc/basesteam/items/MusketItem.java index 38ffccc..42a683b 100644 --- a/src/main/java/zone/moddev/mc/basesteam/items/MusketItem.java +++ b/src/main/java/zone/moddev/mc/basesteam/items/MusketItem.java @@ -24,7 +24,7 @@ import net.minecraft.sounds.SoundEvents; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.EquipmentSlotGroup; -import net.minecraft.world.item.UseAnim; +import net.minecraft.world.item.ItemUseAnimation; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Item; import net.minecraft.world.item.component.CustomData; @@ -32,10 +32,10 @@ import net.minecraft.core.particles.ParticleTypes; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket; -import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionHand; import net.minecraft.core.particles.ParticleOptions; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.sounds.SoundSource; import net.minecraft.sounds.SoundEvent; import net.minecraft.world.phys.AABB; @@ -56,18 +56,18 @@ @SuppressWarnings("deprecation") public class MusketItem extends net.minecraft.world.item.Item{ private static final TagKey FRAGILE_TARGETS = TagKey.create(Registries.BLOCK, - ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, "musket_fragile")); + Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, "musket_fragile")); public static final String NBT_DATA_KEY_LOADED = "loaded"; public static final double MAX_RANGE = 64; private static final int maxUseTime = 7200; - private static final TagKey BLACKPOWDER_AMMO_TAG = ItemTags.create(ResourceLocation.fromNamespaceAndPath("forge", "ammo/blackpowder")); - private static final TagKey STEEL_INGOT_TAG = ItemTags.create(ResourceLocation.fromNamespaceAndPath("forge", "ingots/steel")); + private static final TagKey BLACKPOWDER_AMMO_TAG = ItemTags.create(Identifier.fromNamespaceAndPath("forge", "ammo/blackpowder")); + private static final TagKey STEEL_INGOT_TAG = ItemTags.create(Identifier.fromNamespaceAndPath("forge", "ingots/steel")); public MusketItem(Item.Properties properties){ - super(properties.durability(250).attributes(ItemAttributeModifiers.builder() + super(properties.durability(250).enchantable(1).repairable(STEEL_INGOT_TAG).attributes(ItemAttributeModifiers.builder() .add(Attributes.ATTACK_DAMAGE, new AttributeModifier(BASE_ATTACK_DAMAGE_ID, getMeleeDamage(), AttributeModifier.Operation.ADD_VALUE), EquipmentSlotGroup.MAINHAND) @@ -86,39 +86,34 @@ public static float getMeleeDamage(){ } @Override - public int getEnchantmentValue(){ - return 1; - } - - @Override - public UseAnim getUseAnimation(ItemStack stack){ + public ItemUseAnimation getUseAnimation(ItemStack stack){ if(isLoaded(stack)){ - return UseAnim.BOW; + return ItemUseAnimation.BOW; } else { - return UseAnim.BLOCK; + return ItemUseAnimation.BLOCK; } } @Override - public InteractionResultHolder use(Level world, Player playerEntity, InteractionHand hand){ + public InteractionResult use(Level world, Player playerEntity, InteractionHand hand){ ItemStack srcItemStack = playerEntity.getItemInHand(hand); - if(hand != InteractionHand.MAIN_HAND) return InteractionResultHolder.pass(srcItemStack); - if(isNotLoaded(srcItemStack) && !hasAmmo(playerEntity, srcItemStack)) return InteractionResultHolder.fail(srcItemStack); + if(hand != InteractionHand.MAIN_HAND) return InteractionResult.PASS; + if(isNotLoaded(srcItemStack) && !hasAmmo(playerEntity, srcItemStack)) return InteractionResult.FAIL; if(isNotLoaded(srcItemStack) && Enchantments.level(Enchantments.rapid_reload, srcItemStack, world.registryAccess()) > 0){ // instant reload decrementAmmo(playerEntity,srcItemStack); startLoad(srcItemStack); playSound(SoundEvents.LEVER_CLICK,world,playerEntity); finishLoad(srcItemStack); - return InteractionResultHolder.success(srcItemStack); + return InteractionResult.SUCCESS; } playerEntity.startUsingItem(hand); - return InteractionResultHolder.success(srcItemStack); + return InteractionResult.SUCCESS; } @Override public void onUseTick(Level level, LivingEntity player, ItemStack stack, int count){ - if(level.isClientSide && count % 7 == 3 && isNotLoaded(stack)){ + if(level.isClientSide() && count % 7 == 3 && isNotLoaded(stack)){ // indicator to player that the gun is loading player.playSound(SoundType.STONE.getStepSound(), 0.5f, 1.0f); } @@ -139,12 +134,13 @@ public ItemStack finishUsingItem (ItemStack srcItemStack, Level world, LivingEnt } @Override - public void releaseUsing(ItemStack stack, Level worldIn, LivingEntity playerIn, int timeLeft) { + public boolean releaseUsing(ItemStack stack, Level worldIn, LivingEntity playerIn, int timeLeft) { if(isAlmostLoaded(stack)){ finishLoad(stack); } else if(isLoaded(stack)){ fire(stack,playerIn,worldIn); } + return true; } protected void fire(ItemStack srcItemStack, LivingEntity playerEntity, @@ -159,7 +155,7 @@ protected void fire(ItemStack srcItemStack, LivingEntity playerEntity, Vec3 lookVector = playerEntity.getLookAngle(); spawnParticle(ParticleTypes.EXPLOSION_EMITTER, world, playerEntity.getX()+lookVector.x, playerEntity.getY()+playerEntity.getEyeHeight()+lookVector.y, playerEntity.getZ() + lookVector.z); - if(world.isClientSide){ + if(world.isClientSide()){ playerEntity.setXRot(playerEntity.getXRot() - 15); return; } @@ -178,7 +174,7 @@ protected void fire(ItemStack srcItemStack, LivingEntity playerEntity, final Level.ExplosionInteraction explosionInteraction = Level.ExplosionInteraction.TNT; if(rayTrace instanceof EntityHitResult){ Entity e = ((EntityHitResult) rayTrace).getEntity(); - e.hurt(Power.musketDamage(world), getShotDamage()); + e.hurtServer((ServerLevel)world, Power.musketDamage(world), getShotDamage()); if(explosive > 0){ int lvl = explosive; world.explode(playerEntity, e.getX(), e.getY()+0.5, e.getZ(), @@ -200,8 +196,9 @@ protected void fire(ItemStack srcItemStack, LivingEntity playerEntity, if (bs.is(FRAGILE_TARGETS)) { world.destroyBlock(coord, true); } else if (bs.getBlock() instanceof TntBlock) { - TntBlock.explode(world, coord); - world.removeBlock(coord, false); + if (bs.getBlock().onCaughtFire(bs, world, coord, null, null)) { + world.removeBlock(coord, false); + } } } if(explosive > 0){ @@ -248,15 +245,14 @@ private void spawnParticle(ParticleOptions p, Level w, double x, double y, doubl } @Override - public boolean hurtEnemy(ItemStack stack, LivingEntity target, LivingEntity attacker) + public void hurtEnemy(ItemStack stack, LivingEntity target, LivingEntity attacker) { stack.hurtAndBreak(1, attacker, EquipmentSlot.MAINHAND); - return true; } /** plays a sound at the player location */ protected void playSound(SoundEvent sound, Level world, Entity e){ - if (!world.isClientSide) + if (!world.isClientSide()) { playSoundAtPosition(e.getX(), e.getY(), e.getZ(), sound,SoundSource.PLAYERS,1F,1F,world); } @@ -280,7 +276,7 @@ private static void setLoadedState(ItemStack musket, byte state) { private static byte loadedState(ItemStack musket) { CustomData data = musket.get(DataComponents.CUSTOM_DATA); - return data == null ? 0 : data.copyTag().getByte(NBT_DATA_KEY_LOADED); + return data == null ? 0 : data.copyTag().getByteOr(NBT_DATA_KEY_LOADED, (byte)0); } public static boolean isLoaded(ItemStack musket){ @@ -304,8 +300,8 @@ public static boolean hasAmmo(LivingEntity e, ItemStack musket) { if (playerEntity.isCreative() || Enchantments.level(net.minecraft.world.item.enchantment.Enchantments.INFINITY, musket, e.registryAccess()) > 0 || Enchantments.level(Enchantments.powderless, musket, e.registryAccess()) > 0) return true; - for (int i = 0; i < playerEntity.getInventory().items.size(); i++) { - ItemStack inventoryStack = playerEntity.getInventory().items.get(i); + for (int i = 0; i < playerEntity.getInventory().getNonEquipmentItems().size(); i++) { + ItemStack inventoryStack = playerEntity.getInventory().getItem(i); if (inventoryStack.isEmpty()) continue; if (inventoryStack.is(BLACKPOWDER_AMMO_TAG)) return true; } @@ -320,12 +316,12 @@ public static void decrementAmmo(LivingEntity e, ItemStack musket) { if (playerEntity.isCreative() || Enchantments.level(net.minecraft.world.item.enchantment.Enchantments.INFINITY, musket, e.registryAccess()) > 0 || Enchantments.level(Enchantments.powderless, musket, e.registryAccess()) > 0) return; - for (int i = 0; i < playerEntity.getInventory().items.size(); i++) { - ItemStack inventoryStack = playerEntity.getInventory().items.get(i); + for (int i = 0; i < playerEntity.getInventory().getNonEquipmentItems().size(); i++) { + ItemStack inventoryStack = playerEntity.getInventory().getItem(i); if (inventoryStack.isEmpty()) continue; if (inventoryStack.is(BLACKPOWDER_AMMO_TAG)) { inventoryStack.shrink(1); - if (inventoryStack.isEmpty()) playerEntity.getInventory().items.set(i, ItemStack.EMPTY); + if (inventoryStack.isEmpty()) playerEntity.getInventory().setItem(i, ItemStack.EMPTY); return; } } @@ -338,7 +334,7 @@ public static HitResult rayTraceBlocksAndEntities(Level w, double maxRange, Livi Vec3 rayDirection = source.getLookAngle(); Vec3 loadedEnd = loadedRayEnd(rayOrigin, rayOrigin.add(rayDirection.scale(maxRange)), w::hasChunkAt); if(loadedEnd.equals(rayOrigin)) return BlockHitResult.miss(rayOrigin, - net.minecraft.core.Direction.getNearest(rayDirection.x, rayDirection.y, rayDirection.z), BlockPos.containing(rayOrigin)); + net.minecraft.core.Direction.getApproximateNearest(rayDirection.x, rayDirection.y, rayDirection.z), BlockPos.containing(rayOrigin)); BlockHitResult terrainHit = w.clip(new ClipContext(rayOrigin, loadedEnd, ClipContext.Block.COLLIDER, ClipContext.Fluid.SOURCE_ONLY, source)); Vec3 rayEnd = terrainHit.getLocation(); @@ -416,24 +412,17 @@ private static double min(double a, double b){ return Math.min(a, b); } - /** - * Return whether this item is repairable in an anvil. - */ - @Override public boolean isValidRepairItem(ItemStack srcItemStack, ItemStack rawMaterial) - { - // repair with steel ingots - return rawMaterial.is(STEEL_INGOT_TAG); - } - @Override - public void appendHoverText(ItemStack stack, TooltipContext context, List list, net.minecraft.world.item.TooltipFlag flag){ - super.appendHoverText(stack,context,list,flag); - list.add(Component.translatable("tooltip.musket.damage", (int)MusketItem.getShotDamage())); + public void appendHoverText(ItemStack stack, TooltipContext context, + net.minecraft.world.item.component.TooltipDisplay display, java.util.function.Consumer list, + net.minecraft.world.item.TooltipFlag flag){ + super.appendHoverText(stack,context,display,list,flag); + list.accept(Component.translatable("tooltip.musket.damage", (int)MusketItem.getShotDamage())); if(isLoaded(stack)){ - list.add(Component.translatable("tooltip.musket.loaded")); + list.accept(Component.translatable("tooltip.musket.loaded")); } else { - list.add(Component.translatable("tooltip.musket.unloaded")); - list.add(Component.translatable("tooltip.musket.ammo", Component.translatable("item.basesteam.blackpowder_cartridge"))); + list.accept(Component.translatable("tooltip.musket.unloaded")); + list.accept(Component.translatable("tooltip.musket.ammo", Component.translatable("item.basesteam.blackpowder_cartridge"))); } } diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/BlastFurnaceBlock.java b/src/main/java/zone/moddev/mc/basesteam/machines/BlastFurnaceBlock.java index 8b3c39f..cee1f85 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/BlastFurnaceBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/BlastFurnaceBlock.java @@ -16,7 +16,7 @@ public class BlastFurnaceBlock extends cyano.poweradvantage.api.simple.BlockSimp public BlastFurnaceBlock() { - super(machineProperties(0.75F), Power.steam_power); + super(machineProperties(0.75F).setId(zone.moddev.mc.basesteam.blocks.SteamBlockProperties.key("steam_furnace")), Power.steam_power); } @Override diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/BlastFurnaceTileEntity.java b/src/main/java/zone/moddev/mc/basesteam/machines/BlastFurnaceTileEntity.java index 8622d8c..7172f18 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/BlastFurnaceTileEntity.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/BlastFurnaceTileEntity.java @@ -2,7 +2,8 @@ import cyano.poweradvantage.api.ConduitType; import zone.moddev.mc.basesteam.init.Power; -import net.minecraft.core.HolderLookup; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.entity.player.Player; import net.minecraft.sounds.SoundEvents; import net.minecraft.world.item.ItemStack; @@ -203,12 +204,12 @@ private void energyDecay() { private int getFuelBurnTime(ItemStack item) { if(item == null) return 0; - return zone.moddev.mc.basesteam.util.FurnaceFuelHelper.getBurnTime(item); + return zone.moddev.mc.basesteam.util.FurnaceFuelHelper.getBurnTime(item, getLevel()); } private void decrementFuel() { if(inventory[0].getCount() == 1){ - inventory[0] = inventory[0].getCraftingRemainingItem(); + inventory[0] = inventory[0].getCraftingRemainder(); } else { this.removeItem(0, 1); } @@ -268,17 +269,17 @@ public void onDataFieldUpdate() { @Override - protected void saveAdditional(CompoundTag tagRoot, HolderLookup.Provider registries){ - super.saveAdditional(tagRoot, registries); + protected void saveAdditional(ValueOutput tagRoot){ + super.saveAdditional(tagRoot); prepareDataFieldsForSync(); tagRoot.putIntArray("Data", getDataFieldArray()); } @Override - protected void loadAdditional(CompoundTag tagRoot, HolderLookup.Provider registries){ - super.loadAdditional(tagRoot, registries); - if(tagRoot.contains("Data")){ - int[] data = tagRoot.getIntArray("Data"); + protected void loadAdditional(ValueInput tagRoot){ + super.loadAdditional(tagRoot); + if(tagRoot.read("Data", com.mojang.serialization.Codec.INT_STREAM).isPresent()){ + int[] data = tagRoot.getIntArray("Data").orElseGet(() -> new int[0]); System.arraycopy(data, 0, this.getDataFieldArray(), 0, Math.min(data.length,this.getDataFieldArray().length)); onDataFieldUpdate(); } @@ -332,9 +333,10 @@ public boolean canPlaceItem(final int slot, final ItemStack item) { } private ItemStack getSmeltingResult(ItemStack input) { - if (input == null || input.isEmpty() || getLevel() == null) return ItemStack.EMPTY; + if (input == null || input.isEmpty() + || !(getLevel() instanceof net.minecraft.server.level.ServerLevel server)) return ItemStack.EMPTY; SingleRecipeInput inventory = new SingleRecipeInput(input); - return getLevel().getRecipeManager().getRecipeFor(RecipeType.SMELTING, inventory, getLevel()) + return server.recipeAccess().getRecipeFor(RecipeType.SMELTING, inventory, server) .map(recipe -> recipe.value().assemble(inventory, getLevel().registryAccess())).orElse(ItemStack.EMPTY); } diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/CoalBoilerBlock.java b/src/main/java/zone/moddev/mc/basesteam/machines/CoalBoilerBlock.java index 64d02a1..2b6a8e1 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/CoalBoilerBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/CoalBoilerBlock.java @@ -21,7 +21,7 @@ public class CoalBoilerBlock extends cyano.poweradvantage.api.simple.BlockSimple public CoalBoilerBlock() { - super(machineProperties(0.75F), Power.steam_power, Fluids.fluidConduit_general); + super(machineProperties(0.75F).setId(zone.moddev.mc.basesteam.blocks.SteamBlockProperties.key("steam_boiler_coal")), Power.steam_power, Fluids.fluidConduit_general); } @Override diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/CoalBoilerTileEntity.java b/src/main/java/zone/moddev/mc/basesteam/machines/CoalBoilerTileEntity.java index ab01420..52bf177 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/CoalBoilerTileEntity.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/CoalBoilerTileEntity.java @@ -7,7 +7,8 @@ import cyano.poweradvantage.api.fluid.FluidRequest; import zone.moddev.mc.basepower.init.Fluids; import zone.moddev.mc.basesteam.init.Power; -import net.minecraft.core.HolderLookup; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.entity.player.Player; import net.minecraft.sounds.SoundEvents; import net.minecraft.world.item.ItemStack; @@ -49,7 +50,7 @@ public CoalBoilerTileEntity(net.minecraft.core.BlockPos pos, net.minecraft.world @Override protected void onContentsChanged() { if (CoalBoilerTileEntity.this.getLevel() != null - && !CoalBoilerTileEntity.this.getLevel().isClientSide) { + && !CoalBoilerTileEntity.this.getLevel().isClientSide()) { CoalBoilerTileEntity.this.setChanged(); } } @@ -111,13 +112,13 @@ private void energyDecay() { private int getFuelBurnTime() { if(inventory[0] == null) return 0; - return zone.moddev.mc.basesteam.util.FurnaceFuelHelper.getBurnTime(inventory[0]); + return zone.moddev.mc.basesteam.util.FurnaceFuelHelper.getBurnTime(inventory[0], getLevel()); } private void decrementFuel() { if(inventory[0].getCount() == 1){ - inventory[0] = inventory[0].getCraftingRemainingItem(); + inventory[0] = inventory[0].getCraftingRemainder(); } else { this.removeItem(0, 1); } @@ -215,11 +216,11 @@ public void onDataFieldUpdate() { * @param tagRoot An NBT tag */ @Override - protected void saveAdditional(final CompoundTag tagRoot, HolderLookup.Provider registries) { - super.saveAdditional(tagRoot, registries); + protected void saveAdditional(ValueOutput tagRoot) { + super.saveAdditional(tagRoot); CompoundTag tankTag = new CompoundTag(); this.getTank().writeToNBT(tankTag); - tagRoot.put("Tank", tankTag); + tagRoot.store("Tank", CompoundTag.CODEC, tankTag); if(this.burnTime > 0)tagRoot.putInt("BurnTime", this.burnTime); if(this.totalBurnTime > 0)tagRoot.putInt("BurnTimeTotal", this.totalBurnTime); } @@ -228,23 +229,23 @@ protected void saveAdditional(final CompoundTag tagRoot, HolderLookup.Provider r * @param tagRoot An NBT tag */ @Override - protected void loadAdditional(final CompoundTag tagRoot, HolderLookup.Provider registries) { - super.loadAdditional(tagRoot, registries); - if (tagRoot.contains("Tank")) { - CompoundTag tankTag = tagRoot.getCompound("Tank"); + protected void loadAdditional(ValueInput tagRoot) { + super.loadAdditional(tagRoot); + if (tagRoot.read("Tank", CompoundTag.CODEC).isPresent()) { + CompoundTag tankTag = tagRoot.read("Tank", CompoundTag.CODEC).orElseGet(CompoundTag::new); getTank().readFromNBT(tankTag); if(tankTag.contains("Empty")){ // empty the tank if NBT says its empty (not default behavior of Tank.read(...) ) getTank().setFluid(null); } } - if(tagRoot.contains("BurnTime")){ - this.burnTime = tagRoot.getInt("BurnTime"); + if(tagRoot.read("BurnTime", com.mojang.serialization.Codec.INT).isPresent()){ + this.burnTime = tagRoot.getIntOr("BurnTime", 0); } else { this.burnTime = 0; } - if(tagRoot.contains("BurnTimeTotal")){ - this.totalBurnTime = tagRoot.getInt("BurnTimeTotal"); + if(tagRoot.read("BurnTimeTotal", com.mojang.serialization.Codec.INT).isPresent()){ + this.totalBurnTime = tagRoot.getIntOr("BurnTimeTotal", 0); } else { this.totalBurnTime = 0; } @@ -438,7 +439,7 @@ public FluidTankInfo[] getTankInfo(Direction face) { @Override public boolean canPlaceItem(final int slot, final ItemStack item) { - return super.canPlaceItem(slot, item) && zone.moddev.mc.basesteam.util.FurnaceFuelHelper.getBurnTime(item) > 0; + return super.canPlaceItem(slot, item) && zone.moddev.mc.basesteam.util.FurnaceFuelHelper.getBurnTime(item, getLevel()) > 0; } @Override diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/ElectricBoilerBlock.java b/src/main/java/zone/moddev/mc/basesteam/machines/ElectricBoilerBlock.java index 82b718f..bce1e8b 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/ElectricBoilerBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/ElectricBoilerBlock.java @@ -16,7 +16,7 @@ public class ElectricBoilerBlock extends cyano.poweradvantage.api.simple.BlockS public ElectricBoilerBlock() { - super(machineProperties(0.75F), Power.steam_power, ElectricBoilerTileEntity.ELECTRIC_POWER, Fluids.fluidConduit_general); + super(machineProperties(0.75F).setId(zone.moddev.mc.basesteam.blocks.SteamBlockProperties.key("steam_boiler_electric")), Power.steam_power, ElectricBoilerTileEntity.ELECTRIC_POWER, Fluids.fluidConduit_general); } @Override diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/ElectricBoilerTileEntity.java b/src/main/java/zone/moddev/mc/basesteam/machines/ElectricBoilerTileEntity.java index 882dfa1..14b4acb 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/ElectricBoilerTileEntity.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/ElectricBoilerTileEntity.java @@ -7,7 +7,8 @@ import cyano.poweradvantage.api.fluid.FluidRequest; import zone.moddev.mc.basepower.init.Fluids; import zone.moddev.mc.basesteam.init.Power; -import net.minecraft.core.HolderLookup; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.entity.player.Player; import net.minecraft.sounds.SoundEvents; import net.minecraft.world.item.ItemStack; @@ -50,7 +51,7 @@ public ElectricBoilerTileEntity(net.minecraft.core.BlockPos pos, net.minecraft.w @Override protected void onContentsChanged() { if (ElectricBoilerTileEntity.this.getLevel() != null - && !ElectricBoilerTileEntity.this.getLevel().isClientSide) { + && !ElectricBoilerTileEntity.this.getLevel().isClientSide()) { ElectricBoilerTileEntity.this.setChanged(); } } @@ -188,11 +189,11 @@ public void onDataFieldUpdate() { * @param tagRoot An NBT tag */ @Override - protected void saveAdditional(final CompoundTag tagRoot, HolderLookup.Provider registries) { - super.saveAdditional(tagRoot, registries); + protected void saveAdditional(ValueOutput tagRoot) { + super.saveAdditional(tagRoot); CompoundTag tankTag = new CompoundTag(); this.getTank().writeToNBT(tankTag); - tagRoot.put("Tank", tankTag); + tagRoot.store("Tank", CompoundTag.CODEC, tankTag); if(getEnergy(ELECTRIC_POWER) > 0)tagRoot.putFloat("Electricity", getEnergy(ELECTRIC_POWER)); } /** @@ -200,18 +201,18 @@ protected void saveAdditional(final CompoundTag tagRoot, HolderLookup.Provider r * @param tagRoot An NBT tag */ @Override - protected void loadAdditional(final CompoundTag tagRoot, HolderLookup.Provider registries) { - super.loadAdditional(tagRoot, registries); - if (tagRoot.contains("Tank")) { - CompoundTag tankTag = tagRoot.getCompound("Tank"); + protected void loadAdditional(ValueInput tagRoot) { + super.loadAdditional(tagRoot); + if (tagRoot.read("Tank", CompoundTag.CODEC).isPresent()) { + CompoundTag tankTag = tagRoot.read("Tank", CompoundTag.CODEC).orElseGet(CompoundTag::new); getTank().readFromNBT(tankTag); if(tankTag.contains("Empty")){ // empty the tank if NBT says its empty (not default behavior of Tank.read(...) ) getTank().setFluid(null); } } - if(tagRoot.contains("Electricity")){ - this.setEnergy(tagRoot.getFloat("Electricity"),ELECTRIC_POWER); + if(tagRoot.read("Electricity", com.mojang.serialization.Codec.FLOAT).isPresent()){ + this.setEnergy(tagRoot.getFloatOr("Electricity", 0.0F),ELECTRIC_POWER); } else { this.setEnergy(0,ELECTRIC_POWER); } diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/GeothermalBoilerBlock.java b/src/main/java/zone/moddev/mc/basesteam/machines/GeothermalBoilerBlock.java index 0a62b57..e6ac6e1 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/GeothermalBoilerBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/GeothermalBoilerBlock.java @@ -13,7 +13,7 @@ public class GeothermalBoilerBlock extends cyano.poweradvantage.api.simple.BlockSimplePowerMachine{ public GeothermalBoilerBlock() { - super(machineProperties(0.75F), Power.steam_power, Fluids.fluidConduit_general); + super(machineProperties(0.75F).setId(zone.moddev.mc.basesteam.blocks.SteamBlockProperties.key("steam_boiler_geothermal")), Power.steam_power, Fluids.fluidConduit_general); } @Override diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/GeothermalBoilerTileEntity.java b/src/main/java/zone/moddev/mc/basesteam/machines/GeothermalBoilerTileEntity.java index 001480b..ea9aa58 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/GeothermalBoilerTileEntity.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/GeothermalBoilerTileEntity.java @@ -7,7 +7,8 @@ import cyano.poweradvantage.api.fluid.FluidRequest; import zone.moddev.mc.basepower.init.Fluids; import zone.moddev.mc.basesteam.init.Power; -import net.minecraft.core.HolderLookup; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.entity.player.Player; @@ -53,7 +54,7 @@ public GeothermalBoilerTileEntity(net.minecraft.core.BlockPos pos, net.minecraft @Override protected void onContentsChanged() { if (GeothermalBoilerTileEntity.this.getLevel() != null - && !GeothermalBoilerTileEntity.this.getLevel().isClientSide) { + && !GeothermalBoilerTileEntity.this.getLevel().isClientSide()) { GeothermalBoilerTileEntity.this.setChanged(); } } @@ -221,11 +222,11 @@ public void onDataFieldUpdate() { * @param tagRoot An NBT tag */ @Override - protected void saveAdditional(final CompoundTag tagRoot, HolderLookup.Provider registries) { - super.saveAdditional(tagRoot, registries); + protected void saveAdditional(ValueOutput tagRoot) { + super.saveAdditional(tagRoot); CompoundTag tankTag = new CompoundTag(); this.getTank().writeToNBT(tankTag); - tagRoot.put("Tank", tankTag); + tagRoot.store("Tank", CompoundTag.CODEC, tankTag); if(this.temperature > 0)tagRoot.putFloat("Temperature", this.temperature); } /** @@ -233,20 +234,20 @@ protected void saveAdditional(final CompoundTag tagRoot, HolderLookup.Provider r * @param tagRoot An NBT tag */ @Override - protected void loadAdditional(final CompoundTag tagRoot, HolderLookup.Provider registries) { - super.loadAdditional(tagRoot, registries); - if (tagRoot.contains("Tank")) { - CompoundTag tankTag = tagRoot.getCompound("Tank"); + protected void loadAdditional(ValueInput tagRoot) { + super.loadAdditional(tagRoot); + if (tagRoot.read("Tank", CompoundTag.CODEC).isPresent()) { + CompoundTag tankTag = tagRoot.read("Tank", CompoundTag.CODEC).orElseGet(CompoundTag::new); getTank().readFromNBT(tankTag); if(tankTag.contains("Empty")){ // empty the tank if NBT says its empty (not default behavior of Tank.read(...) ) getTank().setFluid(null); } } - if(tagRoot.contains("Temperature")){ - this.temperature = tagRoot.getFloat("Temperature"); - } else if(tagRoot.contains("Tempoerature")){ - this.temperature = tagRoot.getFloat("Tempoerature"); + if(tagRoot.read("Temperature", com.mojang.serialization.Codec.FLOAT).isPresent()){ + this.temperature = tagRoot.getFloatOr("Temperature", 0.0F); + } else if(tagRoot.read("Tempoerature", com.mojang.serialization.Codec.FLOAT).isPresent()){ + this.temperature = tagRoot.getFloatOr("Tempoerature", 0.0F); } else { this.temperature = 0; } diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/OilBoilerBlock.java b/src/main/java/zone/moddev/mc/basesteam/machines/OilBoilerBlock.java index 64541b1..8420a26 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/OilBoilerBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/OilBoilerBlock.java @@ -21,7 +21,7 @@ public class OilBoilerBlock extends cyano.poweradvantage.api.simple.BlockSimpleP public OilBoilerBlock() { - super(machineProperties(0.75F), Power.steam_power, Fluids.fluidConduit_general); + super(machineProperties(0.75F).setId(zone.moddev.mc.basesteam.blocks.SteamBlockProperties.key("steam_boiler_oil")), Power.steam_power, Fluids.fluidConduit_general); } @Override diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/OilBoilerTileEntity.java b/src/main/java/zone/moddev/mc/basesteam/machines/OilBoilerTileEntity.java index 8342b17..3c3d624 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/OilBoilerTileEntity.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/OilBoilerTileEntity.java @@ -12,14 +12,15 @@ import zone.moddev.mc.basepower.registry.FuelRegistry; import zone.moddev.mc.basesteam.SteamAdvantage; import zone.moddev.mc.basesteam.init.Power; -import net.minecraft.core.HolderLookup; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.entity.player.Player; import net.minecraft.sounds.SoundEvents; import net.minecraft.world.item.ItemStack; import net.minecraft.nbt.CompoundTag; import net.minecraft.core.Direction; import net.minecraft.sounds.SoundSource; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.world.level.material.Fluid; import cyano.poweradvantage.api.fluid.FluidStack; import cyano.poweradvantage.api.fluid.FluidTank; @@ -148,7 +149,7 @@ private static Float getBurnTimePerBucketFor(Fluid fluid){ } private static Float getConfiguredBurnTimePerBucketFor(Fluid fluid) { - ResourceLocation id = net.minecraftforge.registries.ForgeRegistries.FLUIDS.getKey(fluid); + Identifier id = net.minecraftforge.registries.ForgeRegistries.FLUIDS.getKey(fluid); if (id != null) { if (SteamAdvantage.fluidBurnValues.containsKey(id.toString())) return SteamAdvantage.fluidBurnValues.get(id.toString()); if (SteamAdvantage.fluidBurnValues.containsKey(id.getPath())) return SteamAdvantage.fluidBurnValues.get(id.getPath()); @@ -264,14 +265,14 @@ public void onDataFieldUpdate() { * @param tagRoot An NBT tag */ @Override - protected void saveAdditional(final CompoundTag tagRoot, HolderLookup.Provider registries) { - super.saveAdditional(tagRoot, registries); + protected void saveAdditional(ValueOutput tagRoot) { + super.saveAdditional(tagRoot); CompoundTag tankTag = new CompoundTag(); this.getWaterTank().writeToNBT(tankTag); - tagRoot.put("WaterTank", tankTag); + tagRoot.store("WaterTank", CompoundTag.CODEC, tankTag); CompoundTag tankTag2 = new CompoundTag(); this.getFuelTank().writeToNBT(tankTag2); - tagRoot.put("FuelTank", tankTag2); + tagRoot.store("FuelTank", CompoundTag.CODEC, tankTag2); if(this.burnTime > 0)tagRoot.putInt("BurnTime", this.burnTime); if(this.totalBurnTime > 0)tagRoot.putInt("BurnTimeTotal", this.totalBurnTime); } @@ -280,31 +281,31 @@ protected void saveAdditional(final CompoundTag tagRoot, HolderLookup.Provider r * @param tagRoot An NBT tag */ @Override - protected void loadAdditional(final CompoundTag tagRoot, HolderLookup.Provider registries) { - super.loadAdditional(tagRoot, registries); - if (tagRoot.contains("WaterTank")) { - CompoundTag tankTag = tagRoot.getCompound("WaterTank"); + protected void loadAdditional(ValueInput tagRoot) { + super.loadAdditional(tagRoot); + if (tagRoot.read("WaterTank", CompoundTag.CODEC).isPresent()) { + CompoundTag tankTag = tagRoot.read("WaterTank", CompoundTag.CODEC).orElseGet(CompoundTag::new); getWaterTank().readFromNBT(tankTag); if(tankTag.contains("Empty")){ // empty the tank if NBT says its empty (not default behavior of Tank.read(...) ) getWaterTank().setFluid(null); } } - if (tagRoot.contains("FuelTank")) { - CompoundTag tankTag = tagRoot.getCompound("FuelTank"); + if (tagRoot.read("FuelTank", CompoundTag.CODEC).isPresent()) { + CompoundTag tankTag = tagRoot.read("FuelTank", CompoundTag.CODEC).orElseGet(CompoundTag::new); getFuelTank().readFromNBT(tankTag); if(tankTag.contains("Empty")){ // empty the tank if NBT says its empty (not default behavior of Tank.read(...) ) getFuelTank().setFluid(null); } } - if(tagRoot.contains("BurnTime")){ - this.burnTime = tagRoot.getInt("BurnTime"); + if(tagRoot.read("BurnTime", com.mojang.serialization.Codec.INT).isPresent()){ + this.burnTime = tagRoot.getIntOr("BurnTime", 0); } else { this.burnTime = 0; } - if(tagRoot.contains("BurnTimeTotal")){ - this.totalBurnTime = tagRoot.getInt("BurnTimeTotal"); + if(tagRoot.read("BurnTimeTotal", com.mojang.serialization.Codec.INT).isPresent()){ + this.totalBurnTime = tagRoot.getIntOr("BurnTimeTotal", 0); } else { this.totalBurnTime = 0; } diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/RockCrusherBlock.java b/src/main/java/zone/moddev/mc/basesteam/machines/RockCrusherBlock.java index 9ee77a5..7e1b7f0 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/RockCrusherBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/RockCrusherBlock.java @@ -16,7 +16,7 @@ public class RockCrusherBlock extends cyano.poweradvantage.api.simple.BlockSimpl public RockCrusherBlock() { - super(machineProperties(0.75F), Power.steam_power); + super(machineProperties(0.75F).setId(zone.moddev.mc.basesteam.blocks.SteamBlockProperties.key("steam_crusher")), Power.steam_power); } @Override diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/RockCrusherTileEntity.java b/src/main/java/zone/moddev/mc/basesteam/machines/RockCrusherTileEntity.java index 5145c2d..59cc916 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/RockCrusherTileEntity.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/RockCrusherTileEntity.java @@ -1,7 +1,8 @@ package zone.moddev.mc.basesteam.machines; import cyano.poweradvantage.api.ConduitType; -import net.minecraft.core.HolderLookup; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.entity.player.Player; import net.minecraft.sounds.SoundEvents; import net.minecraft.world.item.ItemStack; @@ -200,16 +201,16 @@ private int canAddToOutputInventory(ItemStack output) { } @Override - protected void saveAdditional(CompoundTag tagRoot, HolderLookup.Provider registries){ - super.saveAdditional(tagRoot, registries); + protected void saveAdditional(ValueOutput tagRoot){ + super.saveAdditional(tagRoot); tagRoot.putShort("progress",(short)progress); } @Override - protected void loadAdditional(CompoundTag tagRoot, HolderLookup.Provider registries){ - super.loadAdditional(tagRoot, registries); - if(tagRoot.contains("progress")){ - progress = tagRoot.getShort("progress"); + protected void loadAdditional(ValueInput tagRoot){ + super.loadAdditional(tagRoot); + if(tagRoot.read("progress", com.mojang.serialization.Codec.SHORT).isPresent()){ + progress = tagRoot.getShortOr("progress", (short)0); } } diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/SteamDrillBlock.java b/src/main/java/zone/moddev/mc/basesteam/machines/SteamDrillBlock.java index 8e32125..9b49a38 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/SteamDrillBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/SteamDrillBlock.java @@ -5,7 +5,7 @@ import zone.moddev.mc.basepower.conduitnetwork.ConduitRegistry; import zone.moddev.mc.basesteam.blocks.DrillBitTileEntity; import zone.moddev.mc.basesteam.init.Power; -import net.minecraft.world.level.block.state.properties.DirectionProperty; +import net.minecraft.world.level.block.state.properties.EnumProperty; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.BlockState; @@ -28,10 +28,10 @@ public class SteamDrillBlock extends GUIBlock implements ITypedConduit { /** * Blockstate property */ - public static final DirectionProperty FACING = BlockStateProperties.FACING; + public static final EnumProperty FACING = BlockStateProperties.FACING; public SteamDrillBlock(){ - super(machineProperties(0.75F)); + super(machineProperties(0.75F).setId(zone.moddev.mc.basesteam.blocks.SteamBlockProperties.key("steam_drill"))); this.registerDefaultState(getStateDefinition().any().setValue(FACING, Direction.DOWN)); } @@ -47,18 +47,16 @@ public void onPlace(final BlockState state, final Level world, final BlockPos co * This method is called when the block is removed from the world by an entity. */ @Override - public void onRemove(BlockState state, Level w, BlockPos coord, BlockState newState, boolean isMoving){ - if (state.getBlock() != newState.getBlock()) { - destroyNeighbors(w, coord, state); - ConduitRegistry.getInstance().conduitBlockRemovedEvent(w, coord, Power.steam_power); - } - super.onRemove(state, w, coord, newState, isMoving); + public void affectNeighborsAfterRemoval(BlockState state, net.minecraft.server.level.ServerLevel w, + BlockPos coord, boolean isMoving){ + ConduitRegistry.getInstance().conduitBlockRemovedEvent(w, coord, Power.steam_power); + super.affectNeighborsAfterRemoval(state, w, coord, isMoving); } /** * This method is called when the block is destroyed by an explosion. */ @Override - public void wasExploded(Level w, BlockPos coord, Explosion boom){ + public void wasExploded(net.minecraft.server.level.ServerLevel w, BlockPos coord, Explosion boom){ destroyNeighbors(w,coord,w.getBlockState(coord)); ConduitRegistry.getInstance().conduitBlockRemovedEvent(w, coord, Power.steam_power); super.wasExploded(w, coord, boom); @@ -66,8 +64,8 @@ public void wasExploded(Level w, BlockPos coord, Explosion boom){ - private void destroyNeighbors(Level w, BlockPos coord, BlockState state) { - if(w.isClientSide) return; + static void destroyNeighbors(Level w, BlockPos coord, BlockState state) { + if(w.isClientSide()) return; // destroy connected drill bits for(int i = 0; i < 6; i++){ BlockPos pos = coord.relative(Direction.from3DDataValue(i)); diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/SteamDrillTileEntity.java b/src/main/java/zone/moddev/mc/basesteam/machines/SteamDrillTileEntity.java index 9c52cb9..ddb3287 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/SteamDrillTileEntity.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/SteamDrillTileEntity.java @@ -6,7 +6,8 @@ import zone.moddev.mc.basesteam.compat.BaseMetalsCompat; import zone.moddev.mc.basesteam.init.Blocks; import zone.moddev.mc.basesteam.init.Power; -import net.minecraft.core.HolderLookup; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.entity.player.Player; @@ -193,7 +194,8 @@ private boolean followTrack(){ dataTransfer.putInt("x", nextPos.getX()); dataTransfer.putInt("y", nextPos.getY()); dataTransfer.putInt("z", nextPos.getZ()); - te.loadWithComponents(dataTransfer, w.registryAccess()); + te.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, w.registryAccess(), dataTransfer)); te.clearRemoved(); te.subtractEnergy(ENERGY_COST_MOVE, Power.steam_power); te.setChanged(); @@ -346,7 +348,7 @@ private void targetBlock (BlockPos n){ } private void restoreTargetBlock(){ - if(powerAdvantageWorld() == null || powerAdvantageWorld().isClientSide + if(powerAdvantageWorld() == null || powerAdvantageWorld().isClientSide() || !powerAdvantageWorld().hasChunkAt(targetBlockCoord)) return; int savedProgress = progress; if(savedTargetState != null && !savedTargetState.equals( @@ -468,8 +470,8 @@ private void inventoryTransfer(BlockPos adj, Direction otherFace) { } @Override - protected void saveAdditional(CompoundTag tagRoot, HolderLookup.Provider registries){ - super.saveAdditional(tagRoot, registries); + protected void saveAdditional(ValueOutput tagRoot){ + super.saveAdditional(tagRoot); tagRoot.putShort("progress",(short)progress); // getFacing refreshes this during normal ticks. World lookups here can // synchronously reload a chunk while its unload-save is in progress. @@ -478,30 +480,36 @@ protected void saveAdditional(CompoundTag tagRoot, HolderLookup.Provider registr tagRoot.putInt("targetX", targetBlockCoord.getX()); tagRoot.putInt("targetY", targetBlockCoord.getY()); tagRoot.putInt("targetZ", targetBlockCoord.getZ()); - if(savedTargetState != null) tagRoot.put("TargetState", savedTargetState.copy()); + if(savedTargetState != null) tagRoot.store("TargetState", CompoundTag.CODEC, savedTargetState.copy()); } } + + @Override + public void preRemoveSideEffects(BlockPos pos, BlockState state) { + if (getLevel() != null) SteamDrillBlock.destroyNeighbors(getLevel(), pos, state); + super.preRemoveSideEffects(pos, state); + } @Override - protected void loadAdditional(CompoundTag tagRoot, HolderLookup.Provider registries){ - super.loadAdditional(tagRoot, registries); + protected void loadAdditional(ValueInput tagRoot){ + super.loadAdditional(tagRoot); untargetBlock(); savedFacing = null; oldDir = null; - if(tagRoot.contains("Facing")){ - int facingIndex = tagRoot.getByte("Facing"); + if(tagRoot.read("Facing", com.mojang.serialization.Codec.BYTE).isPresent()){ + int facingIndex = tagRoot.getByteOr("Facing", (byte)0); if(facingIndex >= 0 && facingIndex < Direction.values().length){ savedFacing = Direction.from3DDataValue(facingIndex); } } - if(tagRoot.contains("progress")){ - progress = tagRoot.getShort("progress"); + if(tagRoot.read("progress", com.mojang.serialization.Codec.SHORT).isPresent()){ + progress = tagRoot.getShortOr("progress", (short)0); } - if(tagRoot.contains("targetX") && tagRoot.contains("targetY") && tagRoot.contains("targetZ")){ - int x = tagRoot.getInt("targetX"); - int y = tagRoot.getInt("targetY"); - int z = tagRoot.getInt("targetZ"); - savedTargetState = tagRoot.contains("TargetState", 10) ? tagRoot.getCompound("TargetState").copy() : null; + if(tagRoot.read("targetX", com.mojang.serialization.Codec.INT).isPresent() && tagRoot.read("targetY", com.mojang.serialization.Codec.INT).isPresent() && tagRoot.read("targetZ", com.mojang.serialization.Codec.INT).isPresent()){ + int x = tagRoot.getIntOr("targetX", 0); + int y = tagRoot.getIntOr("targetY", 0); + int z = tagRoot.getIntOr("targetZ", 0); + savedTargetState = tagRoot.read("TargetState", CompoundTag.CODEC).isPresent() ? tagRoot.read("TargetState", CompoundTag.CODEC).orElseGet(CompoundTag::new).copy() : null; // Loot and hardness are restored on the server after the world is attached. this.deferredTargetBlock(new BlockPos(x,y,z)); } diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/SteamElevatorBlock.java b/src/main/java/zone/moddev/mc/basesteam/machines/SteamElevatorBlock.java index d061eda..8a7bf75 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/SteamElevatorBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/SteamElevatorBlock.java @@ -16,7 +16,7 @@ public class SteamElevatorBlock extends cyano.poweradvantage.api.simple.BlockSim public SteamElevatorBlock() { - super(machineProperties(0.75F), Power.steam_power); + super(machineProperties(0.75F).setId(zone.moddev.mc.basesteam.blocks.SteamBlockProperties.key("steam_elevator")), Power.steam_power); } @Override diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/SteamElevatorTileEntity.java b/src/main/java/zone/moddev/mc/basesteam/machines/SteamElevatorTileEntity.java index 051e3f4..5b89225 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/SteamElevatorTileEntity.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/SteamElevatorTileEntity.java @@ -3,7 +3,8 @@ import cyano.poweradvantage.api.ConduitType; import zone.moddev.mc.basesteam.init.Blocks; import zone.moddev.mc.basesteam.init.Power; -import net.minecraft.core.HolderLookup; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraft.sounds.SoundEvents; @@ -173,16 +174,16 @@ public void onDataFieldUpdate() { @Override - protected void saveAdditional(CompoundTag tagRoot, HolderLookup.Provider registries){ - super.saveAdditional(tagRoot, registries); + protected void saveAdditional(ValueOutput tagRoot){ + super.saveAdditional(tagRoot); tagRoot.putBoolean("up",above); } @Override - protected void loadAdditional(CompoundTag tagRoot, HolderLookup.Provider registries){ - super.loadAdditional(tagRoot, registries); - if(tagRoot.contains("up")){ - above = tagRoot.getBoolean("up"); + protected void loadAdditional(ValueInput tagRoot){ + super.loadAdditional(tagRoot); + if(tagRoot.read("up", com.mojang.serialization.Codec.BOOL).isPresent()){ + above = tagRoot.getBooleanOr("up", false); } } diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/SteamPumpBlock.java b/src/main/java/zone/moddev/mc/basesteam/machines/SteamPumpBlock.java index ea97183..88831a2 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/SteamPumpBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/SteamPumpBlock.java @@ -22,7 +22,7 @@ public class SteamPumpBlock extends cyano.poweradvantage.api.simple.BlockSimpleP public SteamPumpBlock() { - super(machineProperties(0.75F), Power.steam_power, Fluids.fluidConduit_general); + super(machineProperties(0.75F).setId(zone.moddev.mc.basesteam.blocks.SteamBlockProperties.key("steam_pump")), Power.steam_power, Fluids.fluidConduit_general); } @Override @@ -56,20 +56,14 @@ public boolean isPowerSource(ConduitType type){ return !ConduitType.areSameType(Power.steam_power, type); } @Override - public void onRemove(BlockState state, Level w, BlockPos coord, BlockState newState, boolean isMoving) { - if (state.getBlock() != newState.getBlock()) destroyPipe(w, coord); - super.onRemove(state, w, coord, newState, isMoving); - } - - @Override - public void wasExploded(Level w, BlockPos coord, Explosion boom){ + public void wasExploded(net.minecraft.server.level.ServerLevel w, BlockPos coord, Explosion boom){ destroyPipe(w, coord); super.wasExploded(w, coord, boom); } - private void destroyPipe(Level w, BlockPos coord) { - if(w.isClientSide) return; + static void destroyPipe(Level w, BlockPos coord) { + if(w.isClientSide()) return; // destroy connected drill bits BlockPos c = coord.below(); while(c.getY() > 0 && w.getBlockState(c).getBlock() == Blocks.pump_pipe_steam){ diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/SteamPumpTileEntity.java b/src/main/java/zone/moddev/mc/basesteam/machines/SteamPumpTileEntity.java index 83427d9..0f9b516 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/SteamPumpTileEntity.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/SteamPumpTileEntity.java @@ -10,7 +10,8 @@ import zone.moddev.mc.basepower.init.Fluids; import zone.moddev.mc.basepower.util.FluidHandlerHelper; import zone.moddev.mc.basesteam.init.Power; -import net.minecraft.core.HolderLookup; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.BucketPickup; import net.minecraft.world.entity.player.Player; @@ -285,23 +286,29 @@ public void onDataFieldUpdate() { * @param tagRoot An NBT tag */ @Override - protected void saveAdditional(final CompoundTag tagRoot, HolderLookup.Provider registries) { - super.saveAdditional(tagRoot, registries); + protected void saveAdditional(ValueOutput tagRoot) { + super.saveAdditional(tagRoot); CompoundTag tankTag = new CompoundTag(); this.getTank().writeToNBT(tankTag); - tagRoot.put("Tank", tankTag); + tagRoot.store("Tank", CompoundTag.CODEC, tankTag); tagRoot.putByte("NextPump", (byte)this.timeUntilNextPump); } + + @Override + public void preRemoveSideEffects(BlockPos pos, BlockState state) { + if (getLevel() != null) SteamPumpBlock.destroyPipe(getLevel(), pos); + super.preRemoveSideEffects(pos, state); + } /** * Handles data saving and loading * @param tagRoot An NBT tag */ @Override - protected void loadAdditional(final CompoundTag tagRoot, HolderLookup.Provider registries) { - super.loadAdditional(tagRoot, registries); - String tankKey = tagRoot.contains("Tank") ? "Tank" : "TankOut"; - if (tagRoot.contains(tankKey)) { - CompoundTag tankTag = tagRoot.getCompound(tankKey); + protected void loadAdditional(ValueInput tagRoot) { + super.loadAdditional(tagRoot); + String tankKey = tagRoot.read("Tank", CompoundTag.CODEC).isPresent() ? "Tank" : "TankOut"; + if (tagRoot.read(tankKey, CompoundTag.CODEC).isPresent()) { + CompoundTag tankTag = tagRoot.read(tankKey, CompoundTag.CODEC).orElseGet(CompoundTag::new); getTank().readFromNBT(tankTag); if(tankTag.contains("Empty")){ // empty the tank if NBT says its empty (not default behavior of Tank.read(...) ) @@ -309,8 +316,8 @@ protected void loadAdditional(final CompoundTag tagRoot, HolderLookup.Provider r } } - if (tagRoot.contains("NextPump")) { - this.timeUntilNextPump = tagRoot.getByte("NextPump"); + if (tagRoot.read("NextPump", com.mojang.serialization.Codec.BYTE).isPresent()) { + this.timeUntilNextPump = tagRoot.getByteOr("NextPump", (byte)0); } } diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/SteamStillBlock.java b/src/main/java/zone/moddev/mc/basesteam/machines/SteamStillBlock.java index e8fc782..283395b 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/SteamStillBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/SteamStillBlock.java @@ -21,7 +21,7 @@ public class SteamStillBlock extends cyano.poweradvantage.api.simple.BlockSimple public SteamStillBlock() { - super(machineProperties(0.75F), Power.steam_power, Fluids.fluidConduit_general); + super(machineProperties(0.75F).setId(zone.moddev.mc.basesteam.blocks.SteamBlockProperties.key("steam_still")), Power.steam_power, Fluids.fluidConduit_general); } @Override diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/SteamStillTileEntity.java b/src/main/java/zone/moddev/mc/basesteam/machines/SteamStillTileEntity.java index 97907a5..f3e721b 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/SteamStillTileEntity.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/SteamStillTileEntity.java @@ -12,7 +12,8 @@ import zone.moddev.mc.basepower.registry.still.recipe.DistillationRecipe; import zone.moddev.mc.basepower.registry.still.recipe.DistillationRecipeRegistry; import zone.moddev.mc.basesteam.init.Power; -import net.minecraft.core.HolderLookup; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.entity.player.Player; import net.minecraft.sounds.SoundEvents; import net.minecraft.world.item.ItemStack; @@ -177,24 +178,24 @@ public void onDataFieldUpdate() { * @param tagRoot An NBT tag */ @Override - protected void saveAdditional(final CompoundTag tagRoot, HolderLookup.Provider registries) { - super.saveAdditional(tagRoot, registries); + protected void saveAdditional(ValueOutput tagRoot) { + super.saveAdditional(tagRoot); CompoundTag tankTag = new CompoundTag(); this.getOutputTank().writeToNBT(tankTag); - tagRoot.put("TankOut", tankTag); + tagRoot.store("TankOut", CompoundTag.CODEC, tankTag); CompoundTag tankTag2 = new CompoundTag(); this.getInputTank().writeToNBT(tankTag2); - tagRoot.put("TankIn", tankTag2); + tagRoot.store("TankIn", CompoundTag.CODEC, tankTag2); } /** * Handles data saving and loading * @param tagRoot An NBT tag */ @Override - protected void loadAdditional(final CompoundTag tagRoot, HolderLookup.Provider registries) { - super.loadAdditional(tagRoot, registries); - if (tagRoot.contains("TankOut")) { - CompoundTag tankTag = tagRoot.getCompound("TankOut"); + protected void loadAdditional(ValueInput tagRoot) { + super.loadAdditional(tagRoot); + if (tagRoot.read("TankOut", CompoundTag.CODEC).isPresent()) { + CompoundTag tankTag = tagRoot.read("TankOut", CompoundTag.CODEC).orElseGet(CompoundTag::new); getOutputTank().readFromNBT(tankTag); if(tankTag.contains("Empty")){ // empty the tank if NBT says its empty (not default behavior of Tank.read(...) ) @@ -202,8 +203,8 @@ protected void loadAdditional(final CompoundTag tagRoot, HolderLookup.Provider r } } - if (tagRoot.contains("TankIn")) { - CompoundTag tankTag = tagRoot.getCompound("TankIn"); + if (tagRoot.read("TankIn", CompoundTag.CODEC).isPresent()) { + CompoundTag tankTag = tagRoot.read("TankIn", CompoundTag.CODEC).orElseGet(CompoundTag::new); getInputTank().readFromNBT(tankTag); if(tankTag.contains("Empty")){ // empty the tank if NBT says its empty (not default behavior of Tank.read(...) ) diff --git a/src/main/java/zone/moddev/mc/basesteam/machines/SteamTankBlock.java b/src/main/java/zone/moddev/mc/basesteam/machines/SteamTankBlock.java index cd65238..e0ccd14 100644 --- a/src/main/java/zone/moddev/mc/basesteam/machines/SteamTankBlock.java +++ b/src/main/java/zone/moddev/mc/basesteam/machines/SteamTankBlock.java @@ -11,7 +11,7 @@ public class SteamTankBlock extends cyano.poweradvantage.api.simple.BlockSimplePowerMachine { public SteamTankBlock(){ - super(machineProperties(0.75F), Power.steam_power); + super(machineProperties(0.75F).setId(zone.moddev.mc.basesteam.blocks.SteamBlockProperties.key("steam_tank")), Power.steam_power); } @Override diff --git a/src/main/java/zone/moddev/mc/basesteam/migration/SteamLegacyMigrations.java b/src/main/java/zone/moddev/mc/basesteam/migration/SteamLegacyMigrations.java index 88b7d80..4bbbdcb 100644 --- a/src/main/java/zone/moddev/mc/basesteam/migration/SteamLegacyMigrations.java +++ b/src/main/java/zone/moddev/mc/basesteam/migration/SteamLegacyMigrations.java @@ -6,7 +6,7 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.nbt.CompoundTag; import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import zone.moddev.mc.basepower.blocks.BlockPowerSwitch; import zone.moddev.mc.basesteam.SteamAdvantage; import zone.moddev.mc.basesteam.blocks.PlatformBlock; @@ -80,15 +80,15 @@ static int legacyPlatformHeight(int metadata) { } static boolean repairPersistenceKeys(CompoundTag compound) { - String id = compound.getString("id"); + String id = compound.getStringOr("id", ""); boolean changed = false; - if (isTile(id, "steam_pump") && !compound.contains("Tank", 10) && compound.contains("TankOut", 10)) { - compound.put("Tank", compound.getCompound("TankOut").copy()); + if (isTile(id, "steam_pump") && compound.getCompound("Tank").isEmpty() && compound.getCompound("TankOut").isPresent()) { + compound.put("Tank", compound.getCompoundOrEmpty("TankOut").copy()); changed = true; } if (isTile(id, "steam_boiler_geothermal") - && !compound.contains("Temperature", 99) && compound.contains("Tempoerature", 99)) { - compound.putFloat("Temperature", compound.getFloat("Tempoerature")); + && compound.getFloat("Temperature").isEmpty() && compound.getFloat("Tempoerature").isPresent()) { + compound.putFloat("Temperature", compound.getFloatOr("Tempoerature", 0.0F)); changed = true; } return changed; @@ -104,7 +104,7 @@ private static boolean matchesTileId(String value, String namespace, String path || dotted.equalsIgnoreCase(value) || ("minecraft:" + dotted).equalsIgnoreCase(value); } - private static ResourceLocation id(String path) { - return ResourceLocation.fromNamespaceAndPath(SteamAdvantage.MODID, path); + private static Identifier id(String path) { + return Identifier.fromNamespaceAndPath(SteamAdvantage.MODID, path); } } diff --git a/src/main/java/zone/moddev/mc/basesteam/recipe/RecipeConditions.java b/src/main/java/zone/moddev/mc/basesteam/recipe/RecipeConditions.java index a4ef06c..61a708f 100644 --- a/src/main/java/zone/moddev/mc/basesteam/recipe/RecipeConditions.java +++ b/src/main/java/zone/moddev/mc/basesteam/recipe/RecipeConditions.java @@ -5,7 +5,7 @@ import com.mojang.serialization.DynamicOps; import com.mojang.serialization.MapCodec; import net.minecraftforge.common.crafting.conditions.ICondition; -import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.eventbus.api.bus.BusGroup; import net.minecraftforge.registries.DeferredRegister; import net.minecraftforge.registries.ForgeRegistries; import zone.moddev.mc.basepower.PowerAdvantage; @@ -25,7 +25,7 @@ public final class RecipeConditions { private RecipeConditions() {} - public static void register(IEventBus bus) { + public static void register(BusGroup bus) { SERIALIZERS.register(bus); } diff --git a/src/main/java/zone/moddev/mc/basesteam/util/FurnaceFuelHelper.java b/src/main/java/zone/moddev/mc/basesteam/util/FurnaceFuelHelper.java index bdbb53a..a56e245 100644 --- a/src/main/java/zone/moddev/mc/basesteam/util/FurnaceFuelHelper.java +++ b/src/main/java/zone/moddev/mc/basesteam/util/FurnaceFuelHelper.java @@ -1,13 +1,23 @@ package zone.moddev.mc.basesteam.util; import net.minecraft.world.item.ItemStack; -import net.minecraftforge.common.ForgeHooks; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.FuelValues; /** Forge-aware access to the vanilla furnace fuel calculation. */ public final class FurnaceFuelHelper { private FurnaceFuelHelper() {} public static int getBurnTime(ItemStack stack) { - return ForgeHooks.getBurnTime(stack, null); + var server = net.minecraftforge.server.ServerLifecycleHooks.getCurrentServer(); + FuelValues fuels = server == null ? FuelValues.vanillaBurnTimes( + net.minecraft.core.RegistryAccess.fromRegistryOfRegistries( + net.minecraft.core.registries.BuiltInRegistries.REGISTRY), + net.minecraft.world.flag.FeatureFlags.DEFAULT_FLAGS) : server.fuelValues(); + return fuels.burnDuration(stack, null); + } + + public static int getBurnTime(ItemStack stack, Level level) { + return level == null ? getBurnTime(stack) : level.fuelValues().burnDuration(stack, null); } } diff --git a/src/main/java/zone/moddev/mc/basesteam/util/SoundHelper.java b/src/main/java/zone/moddev/mc/basesteam/util/SoundHelper.java index 4161405..d984bb4 100644 --- a/src/main/java/zone/moddev/mc/basesteam/util/SoundHelper.java +++ b/src/main/java/zone/moddev/mc/basesteam/util/SoundHelper.java @@ -23,7 +23,7 @@ public static void playSoundAtTileEntity(SoundEvent sound, SoundSource soundType } public static void playSoundAtPosition(double x, double y, double z, SoundEvent sound, SoundSource soundType, float volume, float pitch, Level serverWorld){ - if(serverWorld.isClientSide) return; + if(serverWorld.isClientSide()) return; final double range = 16; final AABB area = new AABB( x - range, y - range, z - range, @@ -39,7 +39,7 @@ public static void playSoundAtPosition(double x, double y, double z, SoundEvent } public static void playBigSoundAtPosition(double x, double y, double z, SoundEvent sound, SoundSource soundType, float volume, float pitch, Level serverWorld){ - if(serverWorld.isClientSide) return; + if(serverWorld.isClientSide()) return; final double range = 50; final AABB area = new AABB( x - range, y - range, z - range, diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index b6dd11c..f426bd1 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -1,6 +1,7 @@ modLoader="javafml" loaderVersion="${loader_version_range}" license="GPL-3.0-only" +showAsResourcePack=false issueTrackerURL="https://github.com/MinecraftModDevelopmentMods/baseSteam/issues" [[mods]] @@ -20,8 +21,8 @@ side="BOTH" [[dependencies.basesteam]] modId="basemetals" -mandatory=false -versionRange="[3.1.2.121011,4.0.0)" +mandatory=true +versionRange="[3.1.2.121111,4.0.0)" ordering="AFTER" side="BOTH" diff --git a/src/main/resources/assets/basesteam/items/blackpowder_cartridge.json b/src/main/resources/assets/basesteam/items/blackpowder_cartridge.json new file mode 100644 index 0000000..79e96a3 --- /dev/null +++ b/src/main/resources/assets/basesteam/items/blackpowder_cartridge.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/blackpowder_cartridge" + } +} diff --git a/src/main/resources/assets/basesteam/items/drillbit.json b/src/main/resources/assets/basesteam/items/drillbit.json new file mode 100644 index 0000000..484c31a --- /dev/null +++ b/src/main/resources/assets/basesteam/items/drillbit.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/drillbit" + } +} diff --git a/src/main/resources/assets/basesteam/items/musket.json b/src/main/resources/assets/basesteam/items/musket.json new file mode 100644 index 0000000..98823aa --- /dev/null +++ b/src/main/resources/assets/basesteam/items/musket.json @@ -0,0 +1,24 @@ +{ + "model": { + "type": "minecraft:condition", + "property": "minecraft:component", + "predicate": "minecraft:custom_data", + "value": "{loaded:1b}", + "on_true": { + "type": "minecraft:model", + "model": "basesteam:item/musket_loaded" + }, + "on_false": { + "type": "minecraft:condition", + "property": "minecraft:using_item", + "on_true": { + "type": "minecraft:model", + "model": "basesteam:item/musket_loading" + }, + "on_false": { + "type": "minecraft:model", + "model": "basesteam:item/musket_unloaded" + } + } + } +} diff --git a/src/main/resources/assets/basesteam/items/platform.json b/src/main/resources/assets/basesteam/items/platform.json new file mode 100644 index 0000000..03b5100 --- /dev/null +++ b/src/main/resources/assets/basesteam/items/platform.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/platform" + } +} diff --git a/src/main/resources/assets/basesteam/items/pump_pipe_steam.json b/src/main/resources/assets/basesteam/items/pump_pipe_steam.json new file mode 100644 index 0000000..0681a95 --- /dev/null +++ b/src/main/resources/assets/basesteam/items/pump_pipe_steam.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/pump_pipe_steam" + } +} diff --git a/src/main/resources/assets/basesteam/items/steam_boiler_coal.json b/src/main/resources/assets/basesteam/items/steam_boiler_coal.json new file mode 100644 index 0000000..873d815 --- /dev/null +++ b/src/main/resources/assets/basesteam/items/steam_boiler_coal.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/steam_boiler_coal" + } +} diff --git a/src/main/resources/assets/basesteam/items/steam_boiler_electric.json b/src/main/resources/assets/basesteam/items/steam_boiler_electric.json new file mode 100644 index 0000000..ae65c66 --- /dev/null +++ b/src/main/resources/assets/basesteam/items/steam_boiler_electric.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/steam_boiler_electric" + } +} diff --git a/src/main/resources/assets/basesteam/items/steam_boiler_geothermal.json b/src/main/resources/assets/basesteam/items/steam_boiler_geothermal.json new file mode 100644 index 0000000..78f544c --- /dev/null +++ b/src/main/resources/assets/basesteam/items/steam_boiler_geothermal.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/steam_boiler_geothermal" + } +} diff --git a/src/main/resources/assets/basesteam/items/steam_boiler_oil.json b/src/main/resources/assets/basesteam/items/steam_boiler_oil.json new file mode 100644 index 0000000..3498603 --- /dev/null +++ b/src/main/resources/assets/basesteam/items/steam_boiler_oil.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/steam_boiler_oil" + } +} diff --git a/src/main/resources/assets/basesteam/items/steam_crusher.json b/src/main/resources/assets/basesteam/items/steam_crusher.json new file mode 100644 index 0000000..8d53a44 --- /dev/null +++ b/src/main/resources/assets/basesteam/items/steam_crusher.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/steam_crusher" + } +} diff --git a/src/main/resources/assets/basesteam/items/steam_drill.json b/src/main/resources/assets/basesteam/items/steam_drill.json new file mode 100644 index 0000000..165b071 --- /dev/null +++ b/src/main/resources/assets/basesteam/items/steam_drill.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/steam_drill" + } +} diff --git a/src/main/resources/assets/basesteam/items/steam_drill_bit.json b/src/main/resources/assets/basesteam/items/steam_drill_bit.json new file mode 100644 index 0000000..074686c --- /dev/null +++ b/src/main/resources/assets/basesteam/items/steam_drill_bit.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/steam_drill_bit" + } +} diff --git a/src/main/resources/assets/basesteam/items/steam_elevator.json b/src/main/resources/assets/basesteam/items/steam_elevator.json new file mode 100644 index 0000000..9bb3d0d --- /dev/null +++ b/src/main/resources/assets/basesteam/items/steam_elevator.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/steam_elevator" + } +} diff --git a/src/main/resources/assets/basesteam/items/steam_furnace.json b/src/main/resources/assets/basesteam/items/steam_furnace.json new file mode 100644 index 0000000..9904525 --- /dev/null +++ b/src/main/resources/assets/basesteam/items/steam_furnace.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/steam_furnace" + } +} diff --git a/src/main/resources/assets/basesteam/items/steam_governor.json b/src/main/resources/assets/basesteam/items/steam_governor.json new file mode 100644 index 0000000..eaa8848 --- /dev/null +++ b/src/main/resources/assets/basesteam/items/steam_governor.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/steam_governor" + } +} diff --git a/src/main/resources/assets/basesteam/items/steam_pipe.json b/src/main/resources/assets/basesteam/items/steam_pipe.json new file mode 100644 index 0000000..58a39ec --- /dev/null +++ b/src/main/resources/assets/basesteam/items/steam_pipe.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/steam_pipe" + } +} diff --git a/src/main/resources/assets/basesteam/items/steam_pump.json b/src/main/resources/assets/basesteam/items/steam_pump.json new file mode 100644 index 0000000..22953e0 --- /dev/null +++ b/src/main/resources/assets/basesteam/items/steam_pump.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/steam_pump" + } +} diff --git a/src/main/resources/assets/basesteam/items/steam_still.json b/src/main/resources/assets/basesteam/items/steam_still.json new file mode 100644 index 0000000..32b111e --- /dev/null +++ b/src/main/resources/assets/basesteam/items/steam_still.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/steam_still" + } +} diff --git a/src/main/resources/assets/basesteam/items/steam_switch.json b/src/main/resources/assets/basesteam/items/steam_switch.json new file mode 100644 index 0000000..2d5f8f1 --- /dev/null +++ b/src/main/resources/assets/basesteam/items/steam_switch.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/steam_switch" + } +} diff --git a/src/main/resources/assets/basesteam/items/steam_tank.json b/src/main/resources/assets/basesteam/items/steam_tank.json new file mode 100644 index 0000000..8a26e43 --- /dev/null +++ b/src/main/resources/assets/basesteam/items/steam_tank.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/steam_tank" + } +} diff --git a/src/main/resources/assets/basesteam/items/steam_track.json b/src/main/resources/assets/basesteam/items/steam_track.json new file mode 100644 index 0000000..50252ca --- /dev/null +++ b/src/main/resources/assets/basesteam/items/steam_track.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "basesteam:item/steam_track" + } +} diff --git a/src/main/resources/assets/basesteam/models/item/musket.json b/src/main/resources/assets/basesteam/models/item/musket.json index ca93dee..e57568a 100644 --- a/src/main/resources/assets/basesteam/models/item/musket.json +++ b/src/main/resources/assets/basesteam/models/item/musket.json @@ -1,25 +1,3 @@ { - "parent": "builtin/generated", - "overrides": [ - { - "predicate": { - "loaded": 0, - "loading": 0 - }, - "model": "basesteam:item/musket_unloaded" - }, - { - "predicate": { - "loaded": 0, - "loading": 1 - }, - "model": "basesteam:item/musket_loading" - }, - { - "predicate": { - "loaded": 1 - }, - "model": "basesteam:item/musket_loaded" - } - ] -} \ No newline at end of file + "parent": "basesteam:item/musket_unloaded" +} diff --git a/src/main/resources/assets/minecraft/atlases/items.json b/src/main/resources/assets/minecraft/atlases/items.json new file mode 100644 index 0000000..c2643ee --- /dev/null +++ b/src/main/resources/assets/minecraft/atlases/items.json @@ -0,0 +1,5 @@ +{ + "sources": [ + { "type": "directory", "source": "items", "prefix": "items/" } + ] +} diff --git a/src/main/resources/data/basesteam/recipe/blackpowder_cartridge.json b/src/main/resources/data/basesteam/recipe/blackpowder_cartridge.json index 3be805a..d1bdb82 100644 --- a/src/main/resources/data/basesteam/recipe/blackpowder_cartridge.json +++ b/src/main/resources/data/basesteam/recipe/blackpowder_cartridge.json @@ -6,15 +6,9 @@ "p" ], "key": { - "L": { - "tag": "forge:nuggets/lead" - }, - "g": { - "item": "minecraft:gunpowder" - }, - "p": { - "item": "minecraft:paper" - } + "L": "#forge:nuggets/lead", + "g": "minecraft:gunpowder", + "p": "minecraft:paper" }, "result": { "id": "basesteam:blackpowder_cartridge" diff --git a/src/main/resources/data/basesteam/recipe/crushing_steam_boiler_coal.json b/src/main/resources/data/basesteam/recipe/crushing_steam_boiler_coal.json index 3dc84d1..828740d 100644 --- a/src/main/resources/data/basesteam/recipe/crushing_steam_boiler_coal.json +++ b/src/main/resources/data/basesteam/recipe/crushing_steam_boiler_coal.json @@ -1,8 +1,6 @@ { "type": "basemetals:crushing", - "ingredient": { - "item": "basesteam:steam_boiler_coal" - }, + "ingredient": "basesteam:steam_boiler_coal", "result": { "id": "basesteam:steam_governor", "count": 2 diff --git a/src/main/resources/data/basesteam/recipe/crushing_steam_boiler_electric.json b/src/main/resources/data/basesteam/recipe/crushing_steam_boiler_electric.json index 5975cb1..035f553 100644 --- a/src/main/resources/data/basesteam/recipe/crushing_steam_boiler_electric.json +++ b/src/main/resources/data/basesteam/recipe/crushing_steam_boiler_electric.json @@ -1,8 +1,6 @@ { "type": "basemetals:crushing", - "ingredient": { - "item": "basesteam:steam_boiler_electric" - }, + "ingredient": "basesteam:steam_boiler_electric", "result": { "id": "basesteam:steam_governor", "count": 2 diff --git a/src/main/resources/data/basesteam/recipe/crushing_steam_boiler_geothermal.json b/src/main/resources/data/basesteam/recipe/crushing_steam_boiler_geothermal.json index 088b861..7a42c7e 100644 --- a/src/main/resources/data/basesteam/recipe/crushing_steam_boiler_geothermal.json +++ b/src/main/resources/data/basesteam/recipe/crushing_steam_boiler_geothermal.json @@ -1,8 +1,6 @@ { "type": "basemetals:crushing", - "ingredient": { - "item": "basesteam:steam_boiler_geothermal" - }, + "ingredient": "basesteam:steam_boiler_geothermal", "result": { "id": "basesteam:steam_governor", "count": 2 diff --git a/src/main/resources/data/basesteam/recipe/crushing_steam_crusher.json b/src/main/resources/data/basesteam/recipe/crushing_steam_crusher.json index 6605395..b35d962 100644 --- a/src/main/resources/data/basesteam/recipe/crushing_steam_crusher.json +++ b/src/main/resources/data/basesteam/recipe/crushing_steam_crusher.json @@ -1,8 +1,6 @@ { "type": "basemetals:crushing", - "ingredient": { - "item": "basesteam:steam_crusher" - }, + "ingredient": "basesteam:steam_crusher", "result": { "id": "basesteam:steam_governor", "count": 2 diff --git a/src/main/resources/data/basesteam/recipe/crushing_steam_drill.json b/src/main/resources/data/basesteam/recipe/crushing_steam_drill.json index fbe25f9..eff9679 100644 --- a/src/main/resources/data/basesteam/recipe/crushing_steam_drill.json +++ b/src/main/resources/data/basesteam/recipe/crushing_steam_drill.json @@ -1,8 +1,6 @@ { "type": "basemetals:crushing", - "ingredient": { - "item": "basesteam:steam_drill" - }, + "ingredient": "basesteam:steam_drill", "result": { "id": "basesteam:steam_governor", "count": 2 diff --git a/src/main/resources/data/basesteam/recipe/crushing_steam_elevator.json b/src/main/resources/data/basesteam/recipe/crushing_steam_elevator.json index 85317da..f5b5e76 100644 --- a/src/main/resources/data/basesteam/recipe/crushing_steam_elevator.json +++ b/src/main/resources/data/basesteam/recipe/crushing_steam_elevator.json @@ -1,8 +1,6 @@ { "type": "basemetals:crushing", - "ingredient": { - "item": "basesteam:steam_elevator" - }, + "ingredient": "basesteam:steam_elevator", "result": { "id": "basesteam:steam_governor", "count": 3 diff --git a/src/main/resources/data/basesteam/recipe/crushing_steam_furnace.json b/src/main/resources/data/basesteam/recipe/crushing_steam_furnace.json index 8eaecb9..6aa4f54 100644 --- a/src/main/resources/data/basesteam/recipe/crushing_steam_furnace.json +++ b/src/main/resources/data/basesteam/recipe/crushing_steam_furnace.json @@ -1,8 +1,6 @@ { "type": "basemetals:crushing", - "ingredient": { - "item": "basesteam:steam_furnace" - }, + "ingredient": "basesteam:steam_furnace", "result": { "id": "basesteam:steam_governor", "count": 2 diff --git a/src/main/resources/data/basesteam/recipe/crushing_steam_governor.json b/src/main/resources/data/basesteam/recipe/crushing_steam_governor.json index ed3f560..4e41cbe 100644 --- a/src/main/resources/data/basesteam/recipe/crushing_steam_governor.json +++ b/src/main/resources/data/basesteam/recipe/crushing_steam_governor.json @@ -1,8 +1,6 @@ { "type": "basemetals:crushing", - "ingredient": { - "item": "basesteam:steam_governor" - }, + "ingredient": "basesteam:steam_governor", "result": { "id": "basepower:sprocket", "count": 2 diff --git a/src/main/resources/data/basesteam/recipe/crushing_steam_tank.json b/src/main/resources/data/basesteam/recipe/crushing_steam_tank.json index db592dc..790b36e 100644 --- a/src/main/resources/data/basesteam/recipe/crushing_steam_tank.json +++ b/src/main/resources/data/basesteam/recipe/crushing_steam_tank.json @@ -1,8 +1,6 @@ { "type": "basemetals:crushing", - "ingredient": { - "item": "basesteam:steam_tank" - }, + "ingredient": "basesteam:steam_tank", "result": { "id": "basesteam:steam_governor" }, diff --git a/src/main/resources/data/basesteam/recipe/musket_left_normal.json b/src/main/resources/data/basesteam/recipe/musket_left_normal.json index 7221dfc..0078406 100644 --- a/src/main/resources/data/basesteam/recipe/musket_left_normal.json +++ b/src/main/resources/data/basesteam/recipe/musket_left_normal.json @@ -5,15 +5,9 @@ "w " ], "key": { - "f": { - "item": "minecraft:flint_and_steel" - }, - "s": { - "tag": "forge:ingots/steel" - }, - "w": { - "tag": "minecraft:planks" - } + "f": "minecraft:flint_and_steel", + "s": "#forge:ingots/steel", + "w": "#minecraft:planks" }, "result": { "id": "basesteam:musket" diff --git a/src/main/resources/data/basesteam/recipe/musket_left_tech.json b/src/main/resources/data/basesteam/recipe/musket_left_tech.json index 6667b33..65b1c20 100644 --- a/src/main/resources/data/basesteam/recipe/musket_left_tech.json +++ b/src/main/resources/data/basesteam/recipe/musket_left_tech.json @@ -5,15 +5,9 @@ "w " ], "key": { - "f": { - "item": "minecraft:flint_and_steel" - }, - "s": { - "tag": "forge:ingots/steel" - }, - "w": { - "tag": "minecraft:planks" - } + "f": "minecraft:flint_and_steel", + "s": "#forge:ingots/steel", + "w": "#minecraft:planks" }, "result": { "id": "basesteam:musket" diff --git a/src/main/resources/data/basesteam/recipe/musket_right_normal.json b/src/main/resources/data/basesteam/recipe/musket_right_normal.json index 794f2d2..695825b 100644 --- a/src/main/resources/data/basesteam/recipe/musket_right_normal.json +++ b/src/main/resources/data/basesteam/recipe/musket_right_normal.json @@ -5,15 +5,9 @@ " w" ], "key": { - "f": { - "item": "minecraft:flint_and_steel" - }, - "s": { - "tag": "forge:ingots/steel" - }, - "w": { - "tag": "minecraft:planks" - } + "f": "minecraft:flint_and_steel", + "s": "#forge:ingots/steel", + "w": "#minecraft:planks" }, "result": { "id": "basesteam:musket" diff --git a/src/main/resources/data/basesteam/recipe/musket_right_tech.json b/src/main/resources/data/basesteam/recipe/musket_right_tech.json index a04b48c..65491d3 100644 --- a/src/main/resources/data/basesteam/recipe/musket_right_tech.json +++ b/src/main/resources/data/basesteam/recipe/musket_right_tech.json @@ -5,15 +5,9 @@ " w" ], "key": { - "f": { - "item": "minecraft:flint_and_steel" - }, - "s": { - "tag": "forge:ingots/steel" - }, - "w": { - "tag": "minecraft:planks" - } + "f": "minecraft:flint_and_steel", + "s": "#forge:ingots/steel", + "w": "#minecraft:planks" }, "result": { "id": "basesteam:musket" diff --git a/src/main/resources/data/basesteam/recipe/steam_boiler_coal.json b/src/main/resources/data/basesteam/recipe/steam_boiler_coal.json index 662396d..4f63c3a 100644 --- a/src/main/resources/data/basesteam/recipe/steam_boiler_coal.json +++ b/src/main/resources/data/basesteam/recipe/steam_boiler_coal.json @@ -5,18 +5,10 @@ "pmp" ], "key": { - "X": { - "tag": "forge:conduits/steam" - }, - "g": { - "tag": "forge:governors" - }, - "p": { - "tag": "forge:plates/iron" - }, - "m": { - "tag": "forge:frames/steel" - } + "X": "#forge:conduits/steam", + "g": "#forge:governors", + "p": "#forge:plates/iron", + "m": "#forge:frames/steel" }, "result": { "id": "basesteam:steam_boiler_coal" diff --git a/src/main/resources/data/basesteam/recipe/steam_boiler_electric.json b/src/main/resources/data/basesteam/recipe/steam_boiler_electric.json index 0c3ae8a..dfc5a1b 100644 --- a/src/main/resources/data/basesteam/recipe/steam_boiler_electric.json +++ b/src/main/resources/data/basesteam/recipe/steam_boiler_electric.json @@ -6,21 +6,11 @@ "pmp" ], "key": { - "X": { - "tag": "forge:wires" - }, - "Y": { - "item": "minecraft:bucket" - }, - "g": { - "tag": "forge:governors" - }, - "p": { - "tag": "forge:plates/iron" - }, - "m": { - "tag": "forge:frames/steel" - } + "X": "#forge:wires", + "Y": "minecraft:bucket", + "g": "#forge:governors", + "p": "#forge:plates/iron", + "m": "#forge:frames/steel" }, "result": { "id": "basesteam:steam_boiler_electric" diff --git a/src/main/resources/data/basesteam/recipe/steam_boiler_geothermal.json b/src/main/resources/data/basesteam/recipe/steam_boiler_geothermal.json index 1a5f586..d93bc7f 100644 --- a/src/main/resources/data/basesteam/recipe/steam_boiler_geothermal.json +++ b/src/main/resources/data/basesteam/recipe/steam_boiler_geothermal.json @@ -6,21 +6,11 @@ "pmp" ], "key": { - "X": { - "tag": "forge:conduits/steam" - }, - "Y": { - "item": "minecraft:obsidian" - }, - "g": { - "tag": "forge:governors" - }, - "p": { - "tag": "forge:plates/iron" - }, - "m": { - "tag": "forge:frames/steel" - } + "X": "#forge:conduits/steam", + "Y": "minecraft:obsidian", + "g": "#forge:governors", + "p": "#forge:plates/iron", + "m": "#forge:frames/steel" }, "result": { "id": "basesteam:steam_boiler_geothermal" diff --git a/src/main/resources/data/basesteam/recipe/steam_boiler_oil.json b/src/main/resources/data/basesteam/recipe/steam_boiler_oil.json index 544b0c2..e3e388d 100644 --- a/src/main/resources/data/basesteam/recipe/steam_boiler_oil.json +++ b/src/main/resources/data/basesteam/recipe/steam_boiler_oil.json @@ -6,21 +6,11 @@ "pmp" ], "key": { - "X": { - "item": "minecraft:furnace" - }, - "Y": { - "item": "minecraft:bucket" - }, - "g": { - "tag": "forge:governors" - }, - "p": { - "tag": "forge:plates/iron" - }, - "m": { - "tag": "forge:frames/steel" - } + "X": "minecraft:furnace", + "Y": "minecraft:bucket", + "g": "#forge:governors", + "p": "#forge:plates/iron", + "m": "#forge:frames/steel" }, "result": { "id": "basesteam:steam_boiler_oil" diff --git a/src/main/resources/data/basesteam/recipe/steam_crusher.json b/src/main/resources/data/basesteam/recipe/steam_crusher.json index 96ebf20..3712bcd 100644 --- a/src/main/resources/data/basesteam/recipe/steam_crusher.json +++ b/src/main/resources/data/basesteam/recipe/steam_crusher.json @@ -6,21 +6,11 @@ "pmp" ], "key": { - "X": { - "item": "minecraft:piston" - }, - "Y": { - "tag": "forge:storage_blocks/steel" - }, - "g": { - "tag": "forge:governors" - }, - "p": { - "tag": "forge:plates/iron" - }, - "m": { - "tag": "forge:frames/steel" - } + "X": "minecraft:piston", + "Y": "#forge:storage_blocks/steel", + "g": "#forge:governors", + "p": "#forge:plates/iron", + "m": "#forge:frames/steel" }, "result": { "id": "basesteam:steam_crusher" diff --git a/src/main/resources/data/basesteam/recipe/steam_drill.json b/src/main/resources/data/basesteam/recipe/steam_drill.json index b0dba7d..918488e 100644 --- a/src/main/resources/data/basesteam/recipe/steam_drill.json +++ b/src/main/resources/data/basesteam/recipe/steam_drill.json @@ -5,18 +5,10 @@ "pmp" ], "key": { - "X": { - "item": "basesteam:steam_drill_bit" - }, - "g": { - "tag": "forge:governors" - }, - "p": { - "tag": "forge:plates/iron" - }, - "m": { - "tag": "forge:frames/steel" - } + "X": "basesteam:steam_drill_bit", + "g": "#forge:governors", + "p": "#forge:plates/iron", + "m": "#forge:frames/steel" }, "result": { "id": "basesteam:steam_drill" diff --git a/src/main/resources/data/basesteam/recipe/steam_drill_bit.json b/src/main/resources/data/basesteam/recipe/steam_drill_bit.json index f75189c..88fbf60 100644 --- a/src/main/resources/data/basesteam/recipe/steam_drill_bit.json +++ b/src/main/resources/data/basesteam/recipe/steam_drill_bit.json @@ -6,15 +6,9 @@ "did" ], "key": { - "g": { - "tag": "forge:sprockets" - }, - "i": { - "tag": "forge:ingots/steel" - }, - "d": { - "tag": "forge:gems/diamond" - } + "g": "#forge:sprockets", + "i": "#forge:ingots/steel", + "d": "#forge:gems/diamond" }, "result": { "id": "basesteam:steam_drill_bit" diff --git a/src/main/resources/data/basesteam/recipe/steam_drill_diamond_pickaxe_normal.json b/src/main/resources/data/basesteam/recipe/steam_drill_diamond_pickaxe_normal.json index 296056f..209d7b4 100644 --- a/src/main/resources/data/basesteam/recipe/steam_drill_diamond_pickaxe_normal.json +++ b/src/main/resources/data/basesteam/recipe/steam_drill_diamond_pickaxe_normal.json @@ -5,18 +5,10 @@ "pmp" ], "key": { - "X": { - "item": "minecraft:diamond_pickaxe" - }, - "g": { - "tag": "forge:governors" - }, - "p": { - "tag": "forge:plates/iron" - }, - "m": { - "tag": "forge:frames/steel" - } + "X": "minecraft:diamond_pickaxe", + "g": "#forge:governors", + "p": "#forge:plates/iron", + "m": "#forge:frames/steel" }, "result": { "id": "basesteam:steam_drill" diff --git a/src/main/resources/data/basesteam/recipe/steam_elevator.json b/src/main/resources/data/basesteam/recipe/steam_elevator.json index 82709ed..2fa1822 100644 --- a/src/main/resources/data/basesteam/recipe/steam_elevator.json +++ b/src/main/resources/data/basesteam/recipe/steam_elevator.json @@ -6,21 +6,11 @@ "pmp" ], "key": { - "X": { - "item": "minecraft:piston" - }, - "Y": { - "tag": "forge:sprockets" - }, - "g": { - "tag": "forge:governors" - }, - "p": { - "tag": "forge:plates/iron" - }, - "m": { - "tag": "forge:frames/steel" - } + "X": "minecraft:piston", + "Y": "#forge:sprockets", + "g": "#forge:governors", + "p": "#forge:plates/iron", + "m": "#forge:frames/steel" }, "result": { "id": "basesteam:steam_elevator" diff --git a/src/main/resources/data/basesteam/recipe/steam_furnace.json b/src/main/resources/data/basesteam/recipe/steam_furnace.json index e7006eb..1c14873 100644 --- a/src/main/resources/data/basesteam/recipe/steam_furnace.json +++ b/src/main/resources/data/basesteam/recipe/steam_furnace.json @@ -5,18 +5,10 @@ "pmp" ], "key": { - "X": { - "item": "minecraft:furnace" - }, - "g": { - "tag": "forge:governors" - }, - "p": { - "tag": "forge:plates/iron" - }, - "m": { - "tag": "forge:frames/steel" - } + "X": "minecraft:furnace", + "g": "#forge:governors", + "p": "#forge:plates/iron", + "m": "#forge:frames/steel" }, "result": { "id": "basesteam:steam_furnace" diff --git a/src/main/resources/data/basesteam/recipe/steam_governor_apocalyptic.json b/src/main/resources/data/basesteam/recipe/steam_governor_apocalyptic.json index cb9bfc8..10a28c5 100644 --- a/src/main/resources/data/basesteam/recipe/steam_governor_apocalyptic.json +++ b/src/main/resources/data/basesteam/recipe/steam_governor_apocalyptic.json @@ -6,18 +6,10 @@ "btb" ], "key": { - "t": { - "tag": "forge:sprockets" - }, - "s": { - "tag": "forge:rods" - }, - "r": { - "tag": "forge:rods" - }, - "b": { - "tag": "forge:ingots/brass" - } + "t": "#forge:sprockets", + "s": "#forge:rods", + "r": "#forge:rods", + "b": "#forge:ingots/brass" }, "result": { "id": "basesteam:steam_governor" diff --git a/src/main/resources/data/basesteam/recipe/steam_governor_normal.json b/src/main/resources/data/basesteam/recipe/steam_governor_normal.json index ebe3123..389ad1f 100644 --- a/src/main/resources/data/basesteam/recipe/steam_governor_normal.json +++ b/src/main/resources/data/basesteam/recipe/steam_governor_normal.json @@ -6,18 +6,10 @@ "btb" ], "key": { - "t": { - "tag": "forge:nuggets/iron" - }, - "s": { - "item": "minecraft:stick" - }, - "r": { - "item": "minecraft:stick" - }, - "b": { - "tag": "forge:ingots/brass" - } + "t": "#forge:nuggets/iron", + "s": "minecraft:stick", + "r": "minecraft:stick", + "b": "#forge:ingots/brass" }, "result": { "id": "basesteam:steam_governor" diff --git a/src/main/resources/data/basesteam/recipe/steam_governor_tech.json b/src/main/resources/data/basesteam/recipe/steam_governor_tech.json index 62e0121..2646914 100644 --- a/src/main/resources/data/basesteam/recipe/steam_governor_tech.json +++ b/src/main/resources/data/basesteam/recipe/steam_governor_tech.json @@ -6,18 +6,10 @@ "btb" ], "key": { - "t": { - "tag": "forge:sprockets" - }, - "s": { - "tag": "forge:rods" - }, - "r": { - "tag": "forge:rods" - }, - "b": { - "tag": "forge:ingots/brass" - } + "t": "#forge:sprockets", + "s": "#forge:rods", + "r": "#forge:rods", + "b": "#forge:ingots/brass" }, "result": { "id": "basesteam:steam_governor" diff --git a/src/main/resources/data/basesteam/recipe/steam_pipe.json b/src/main/resources/data/basesteam/recipe/steam_pipe.json index e36a495..93fd884 100644 --- a/src/main/resources/data/basesteam/recipe/steam_pipe.json +++ b/src/main/resources/data/basesteam/recipe/steam_pipe.json @@ -6,9 +6,7 @@ "xxx" ], "key": { - "x": { - "tag": "forge:ingots/brass" - } + "x": "#forge:ingots/brass" }, "result": { "id": "basesteam:steam_pipe", diff --git a/src/main/resources/data/basesteam/recipe/steam_pump.json b/src/main/resources/data/basesteam/recipe/steam_pump.json index 8caa905..f59e03a 100644 --- a/src/main/resources/data/basesteam/recipe/steam_pump.json +++ b/src/main/resources/data/basesteam/recipe/steam_pump.json @@ -6,21 +6,11 @@ "pmp" ], "key": { - "X": { - "item": "minecraft:piston" - }, - "Y": { - "item": "minecraft:bucket" - }, - "g": { - "tag": "forge:governors" - }, - "p": { - "tag": "forge:plates/iron" - }, - "m": { - "tag": "forge:frames/steel" - } + "X": "minecraft:piston", + "Y": "minecraft:bucket", + "g": "#forge:governors", + "p": "#forge:plates/iron", + "m": "#forge:frames/steel" }, "result": { "id": "basesteam:steam_pump" diff --git a/src/main/resources/data/basesteam/recipe/steam_still.json b/src/main/resources/data/basesteam/recipe/steam_still.json index 5749711..a7acef2 100644 --- a/src/main/resources/data/basesteam/recipe/steam_still.json +++ b/src/main/resources/data/basesteam/recipe/steam_still.json @@ -6,21 +6,11 @@ "pmp" ], "key": { - "X": { - "item": "minecraft:bucket" - }, - "Y": { - "item": "minecraft:bucket" - }, - "g": { - "tag": "forge:governors" - }, - "p": { - "tag": "forge:plates/iron" - }, - "m": { - "tag": "forge:frames/steel" - } + "X": "minecraft:bucket", + "Y": "minecraft:bucket", + "g": "#forge:governors", + "p": "#forge:plates/iron", + "m": "#forge:frames/steel" }, "result": { "id": "basesteam:steam_still" diff --git a/src/main/resources/data/basesteam/recipe/steam_switch.json b/src/main/resources/data/basesteam/recipe/steam_switch.json index a3984b5..5f07f88 100644 --- a/src/main/resources/data/basesteam/recipe/steam_switch.json +++ b/src/main/resources/data/basesteam/recipe/steam_switch.json @@ -5,15 +5,9 @@ "pfp" ], "key": { - "L": { - "item": "minecraft:lever" - }, - "p": { - "tag": "forge:conduits/steam" - }, - "f": { - "tag": "forge:frames/steel" - } + "L": "minecraft:lever", + "p": "#forge:conduits/steam", + "f": "#forge:frames/steel" }, "result": { "id": "basesteam:steam_switch" diff --git a/src/main/resources/data/basesteam/recipe/steam_tank.json b/src/main/resources/data/basesteam/recipe/steam_tank.json index 6c55bb7..462f6e6 100644 --- a/src/main/resources/data/basesteam/recipe/steam_tank.json +++ b/src/main/resources/data/basesteam/recipe/steam_tank.json @@ -6,15 +6,9 @@ "xxx" ], "key": { - "x": { - "tag": "forge:plates/copper" - }, - "p": { - "tag": "forge:conduits/steam" - }, - "g": { - "tag": "forge:governors" - } + "x": "#forge:plates/copper", + "p": "#forge:conduits/steam", + "g": "#forge:governors" }, "result": { "id": "basesteam:steam_tank" diff --git a/src/main/resources/data/basesteam/recipe/steam_track.json b/src/main/resources/data/basesteam/recipe/steam_track.json index 97ee6ee..d69668e 100644 --- a/src/main/resources/data/basesteam/recipe/steam_track.json +++ b/src/main/resources/data/basesteam/recipe/steam_track.json @@ -1,12 +1,8 @@ { "type": "minecraft:crafting_shapeless", "ingredients": [ - { - "item": "basesteam:steam_pipe" - }, - { - "item": "basepower:steel_frame" - } + "basesteam:steam_pipe", + "basepower:steel_frame" ], "result": { "id": "basesteam:steam_track" diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index 25eee2f..ba78a5d 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,7 +1,7 @@ { "pack":{ - "pack_format":34, - "supported_formats":{"min_inclusive":34,"max_inclusive":48}, + "min_format":[94,1], + "max_format":[94,1], "description":"baseSteam" } } diff --git a/src/test/java/zone/moddev/mc/basesteam/blocks/DrillBitPersistenceTest.java b/src/test/java/zone/moddev/mc/basesteam/blocks/DrillBitPersistenceTest.java index 33a7102..10ce728 100644 --- a/src/test/java/zone/moddev/mc/basesteam/blocks/DrillBitPersistenceTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/blocks/DrillBitPersistenceTest.java @@ -21,14 +21,16 @@ public class DrillBitPersistenceTest { var registries = NativeTestBootstrap.registries(); CompoundTag saved = tile.saveWithoutMetadata(registries); - assertEquals(direction.get3DDataValue(), saved.getByte("dir")); + assertEquals(direction.get3DDataValue(), saved.getByteOr("dir", (byte)0)); DrillBitTileEntity restored = new DrillBitTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("drillbit")); - restored.loadWithComponents(saved, registries); - assertEquals(direction.get3DDataValue(), restored.getUpdateTag(registries).getByte("dir")); + restored.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, registries, saved)); + assertEquals(direction.get3DDataValue(), restored.getUpdateTag(registries).getByteOr("dir", (byte)0)); DrillBitTileEntity client = new DrillBitTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("drillbit")); - client.handleUpdateTag(tile.getUpdateTag(registries), registries); - assertEquals(direction.get3DDataValue(), client.getUpdateTag(registries).getByte("dir")); + client.handleUpdateTag(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, registries, tile.getUpdateTag(registries)), registries); + assertEquals(direction.get3DDataValue(), client.getUpdateTag(registries).getByteOr("dir", (byte)0)); } } @@ -36,10 +38,12 @@ public class DrillBitPersistenceTest { CompoundTag tag = new CompoundTag(); tag.putByte("d", (byte) Direction.EAST.get3DDataValue()); DrillBitTileEntity tile = new DrillBitTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("drillbit")); - tile.loadWithComponents(tag, NativeTestBootstrap.registries()); + tile.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), tag)); assertEquals(Direction.Axis.X, tile.getDirection()); tag.putByte("dir", (byte) Direction.NORTH.get3DDataValue()); - tile.loadWithComponents(tag, NativeTestBootstrap.registries()); + tile.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), tag)); assertEquals(Direction.Axis.Z, tile.getDirection()); } @@ -47,7 +51,8 @@ public class DrillBitPersistenceTest { CompoundTag tag = new CompoundTag(); tag.putByte("dir", (byte) 127); DrillBitTileEntity tile = new DrillBitTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("drillbit")); - tile.loadWithComponents(tag, NativeTestBootstrap.registries()); - assertEquals(Direction.DOWN.get3DDataValue(), tile.getUpdateTag(NativeTestBootstrap.registries()).getByte("dir")); + tile.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), tag)); + assertEquals(Direction.DOWN.get3DDataValue(), tile.getUpdateTag(NativeTestBootstrap.registries()).getByteOr("dir", (byte)0)); } } diff --git a/src/test/java/zone/moddev/mc/basesteam/graphics/DrillBitRendererTest.java b/src/test/java/zone/moddev/mc/basesteam/graphics/DrillBitRendererTest.java index d2959e8..c220fcf 100644 --- a/src/test/java/zone/moddev/mc/basesteam/graphics/DrillBitRendererTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/graphics/DrillBitRendererTest.java @@ -95,9 +95,15 @@ private static class RecordingBuffer implements VertexConsumer { vertex.color[0] = red; vertex.color[1] = green; vertex.color[2] = blue; vertex.color[3] = alpha; return this; } + @Override public VertexConsumer setColor(int color) { + return setColor((color >>> 16) & 255, (color >>> 8) & 255, color & 255, color >>> 24); + } @Override public VertexConsumer setUv(float u, float v) { vertex.u = u; vertex.v = v; return this; } @Override public VertexConsumer setUv1(int u, int v) { vertex.overlay = u | v << 16; return this; } @Override public VertexConsumer setUv2(int u, int v) { vertex.light = u | v << 16; return this; } + @Override public VertexConsumer setLineWidth(float width) { + throw new AssertionError("Drill geometry must submit quads, not line vertices"); + } @Override public VertexConsumer setNormal(float x, float y, float z) { vertex.normal[0] = x; vertex.normal[1] = y; vertex.normal[2] = z; return this; diff --git a/src/test/java/zone/moddev/mc/basesteam/gui/GUIHelperTest.java b/src/test/java/zone/moddev/mc/basesteam/gui/GUIHelperTest.java index e18b274..d270462 100644 --- a/src/test/java/zone/moddev/mc/basesteam/gui/GUIHelperTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/gui/GUIHelperTest.java @@ -1,7 +1,10 @@ package zone.moddev.mc.basesteam.gui; -import com.mojang.blaze3d.vertex.PoseStack; -import org.joml.Vector3f; +import org.joml.Matrix3x2fStack; +import org.joml.Matrix3x2f; +import org.joml.Vector2f; +import cyano.poweradvantage.api.simple.SimpleMachineGUI; +import cyano.poweradvantage.api.simple.SimpleMachineGUI.GUIContainer; import org.junit.Test; import static org.junit.Assert.*; @@ -16,14 +19,12 @@ public class GUIHelperTest { @Test public void needleSweepsLeftToRightThroughTheSamePivot() { for (float value : new float[] {0F, 0.25F, 0.5F, 0.75F, 1F, Float.NaN}) { - PoseStack pose = new PoseStack(); - GUIHelper.positionNeedle(pose, 44F, 40F, 1F, value); - var transform = pose.last().pose(); - var pivot = transform.transformPosition(new Vector3f(3.5F, 17.5F, 0F)); + Matrix3x2fStack pose = new Matrix3x2fStack(2); + GUIHelper.positionNeedle(pose, 44F, 40F, value); + var pivot = pose.transformPosition(new Vector2f(3.5F, 17.5F)); assertEquals(44F, pivot.x, 0.0001F); assertEquals(40F, pivot.y, 0.0001F); - assertEquals(1F, pivot.z, 0.0001F); - var tip = transform.transformPosition(new Vector3f(3.5F, 0F, 0F)); + var tip = pose.transformPosition(new Vector2f(3.5F, 0F)); // Independent geometry from the previous native vertex implementation. double legacyAngle = (1D - (Float.isNaN(value) ? 0D : value)) * Math.PI; assertEquals(44D + Math.cos(legacyAngle) * 17.5D, tip.x, 0.0001D); @@ -36,4 +37,67 @@ public class GUIHelperTest { assertEquals(0F, GUIHelper.displayLevel(value), 0F); } } + + @Test public void screenAwareNeedleSubmitsTheOriginalRegionAndRestoresTheMatrixEvenOnFailure() throws Exception { + var field = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe"); + field.setAccessible(true); + var unsafe = field.get(null); + DrawingContainer container = (DrawingContainer) unsafe.getClass().getMethod("allocateInstance", Class.class) + .invoke(unsafe, DrawingContainer.class); + container.matrices = new Matrix3x2fStack(2); + container.matrices.translate(10F, 20F); + Matrix3x2f before = new Matrix3x2f(container.matrices); + GUIHelper.drawNeedle(container, 44F, 40F, 1F, 0.75F); + assertEquals(before, container.matrices); + assertArrayEquals(new int[] {0, 0, 176, 0, 7, 35}, container.region); + var pivot = container.submitted.transformPosition(new Vector2f(3.5F, 17.5F)); + assertEquals(54F, pivot.x, 0.0001F); + assertEquals(60F, pivot.y, 0.0001F); + container.refuse = true; + assertThrows(IllegalStateException.class, () -> GUIHelper.drawNeedle(container, 44F, 40F, 1F, 0.75F)); + assertEquals(before, container.matrices); + assertThrows("The legacy overload cannot draw outside the real decoration callback", IllegalStateException.class, + () -> GUIHelper.drawingContainer(container)); + assertThrows(IllegalStateException.class, () -> GUIHelper.drawingContainer(null)); + } + + @Test public void legacySignatureDelegatesFromTheNativeCurrentScreenWithoutImmediateGpuCalls() throws Exception { + var node = new org.objectweb.asm.tree.ClassNode(); + try (var input = GUIHelper.class.getResourceAsStream("GUIHelper.class")) { + assertNotNull(input); + new org.objectweb.asm.ClassReader(input).accept(node, 0); + } + var method = node.methods.stream().filter(value -> value.name.equals("drawNeedle") + && value.desc.equals("(FFFF)V")).findFirst().orElseThrow(); + boolean currentScreen = false, delegate = false; + for (var instruction : method.instructions) { + if (instruction instanceof org.objectweb.asm.tree.FieldInsnNode field) { + if (field.owner.equals("net/minecraft/client/Minecraft") && field.name.equals("screen")) currentScreen = true; + } + if (instruction instanceof org.objectweb.asm.tree.MethodInsnNode call) { + assertFalse(call.owner, call.owner.startsWith("com/mojang/blaze3d/")); + if (call.owner.equals(node.name) && call.name.equals("drawNeedle") + && call.desc.equals("(Lcyano/poweradvantage/api/simple/SimpleMachineGUI$GUIContainer;FFFF)V")) delegate = true; + } + } + assertTrue(currentScreen && delegate); + } + + private static final class DrawingContainer extends GUIContainer { + private Matrix3x2fStack matrices; + private Matrix3x2f submitted; + private int[] region; + private boolean refuse; + + private DrawingContainer(SimpleMachineGUI owner) { + owner.super((SimpleMachineGUI.Container) null, null, net.minecraft.network.chat.Component.empty()); + } + + @Override public Matrix3x2fStack getMatrices() { return matrices; } + @Override public void blit(int x, int y, int u, int v, int width, int height) { + submitted = new Matrix3x2f(matrices); + region = new int[] {x, y, u, v, width, height}; + if (refuse) throw new IllegalStateException("needle submission failed"); + } + } } diff --git a/src/test/java/zone/moddev/mc/basesteam/items/MusketPersistenceTest.java b/src/test/java/zone/moddev/mc/basesteam/items/MusketPersistenceTest.java index 154079c..ae34133 100644 --- a/src/test/java/zone/moddev/mc/basesteam/items/MusketPersistenceTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/items/MusketPersistenceTest.java @@ -25,19 +25,20 @@ public class MusketPersistenceTest { stack.setDamageValue(123); assertTrue(MusketItem.isNotLoaded(stack)); MusketItem.startLoad(stack); - assertEquals(2, data(stack).getByte("loaded")); + assertEquals(2, data(stack).getByteOr("loaded", (byte)0)); assertTrue(MusketItem.isAlmostLoaded(stack)); MusketItem.finishLoad(stack); - assertEquals(1, data(stack).getByte("loaded")); + assertEquals(1, data(stack).getByteOr("loaded", (byte)0)); assertTrue(MusketItem.isLoaded(stack.copy())); var registries = NativeTestBootstrap.registries(); - ItemStack restored = ItemStack.parse(registries, stack.save(registries)).orElseThrow(); + ItemStack restored = ItemStack.CODEC.parse(registries.createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE), + ItemStack.CODEC.encodeStart(registries.createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE), stack).getOrThrow()).getOrThrow(); assertTrue(MusketItem.isLoaded(restored)); assertEquals(123, restored.getDamageValue()); MusketItem.unload(stack); - assertEquals(0, data(stack).getByte("loaded")); + assertEquals(0, data(stack).getByteOr("loaded", (byte)0)); assertTrue(MusketItem.isNotLoaded(stack)); - assertEquals("legacy", data(stack).getString("owner")); + assertEquals("legacy", data(stack).getStringOr("owner", "")); assertEquals(123, stack.getDamageValue()); assertTrue(MusketItem.isLoaded(restored)); } diff --git a/src/test/java/zone/moddev/mc/basesteam/items/MusketTargetingContractTest.java b/src/test/java/zone/moddev/mc/basesteam/items/MusketTargetingContractTest.java index 6b1864f..a808697 100644 --- a/src/test/java/zone/moddev/mc/basesteam/items/MusketTargetingContractTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/items/MusketTargetingContractTest.java @@ -55,8 +55,8 @@ public class MusketTargetingContractTest { .findFirst().orElseThrow(() -> new AssertionError("Missing Minecraft's onUseTick override")); boolean clientGate = false, loadGate = false, sound = false; for (AbstractInsnNode instruction : useTick.instructions.toArray()) { - if (instruction instanceof FieldInsnNode field && field.name.equals("isClientSide")) clientGate = true; if (!(instruction instanceof MethodInsnNode call)) continue; + if (call.name.equals("isClientSide")) clientGate = true; if (call.name.equals("isNotLoaded")) loadGate = true; if (call.name.equals("playSound")) { assertTrue("Reload sound must be client-only and stop when loaded", clientGate && loadGate); diff --git a/src/test/java/zone/moddev/mc/basesteam/machines/CoalBoilerFillPersistenceTest.java b/src/test/java/zone/moddev/mc/basesteam/machines/CoalBoilerFillPersistenceTest.java index 8109450..9ec7afe 100644 --- a/src/test/java/zone/moddev/mc/basesteam/machines/CoalBoilerFillPersistenceTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/machines/CoalBoilerFillPersistenceTest.java @@ -177,12 +177,12 @@ private static SaveWitnessBoiler seeded(int amount) { private static void assertSaved(SaveWitnessBoiler boiler, int amount) { assertTrue("An accepted external fill must request saving without relying on a later tile tick", boiler.dirtyCalls > 0); - assertEquals(amount, boiler.savedTank.getInt("Amount")); + assertEquals(amount, boiler.savedTank.getIntOr("Amount", 0)); } private static void assertUnchangedSave(SaveWitnessBoiler boiler, int amount) { assertEquals(0, boiler.dirtyCalls); - assertEquals(amount, boiler.savedTank.getInt("Amount")); + assertEquals(amount, boiler.savedTank.getIntOr("Amount", 0)); } private static final class SaveWitnessBoiler extends CoalBoilerTileEntity { @@ -217,6 +217,6 @@ private static SaveWorld world(boolean remote) { } } private static final class SaveWorld extends net.minecraft.server.level.ServerLevel { - private SaveWorld() { super(null, null, null, null, null, null, null, false, 0L, java.util.Collections.emptyList(), false, null); } + private SaveWorld() { super(null, null, null, null, null, null, false, 0L, java.util.Collections.emptyList(), false, null); } } } diff --git a/src/test/java/zone/moddev/mc/basesteam/machines/RockCrusherReloadRedstoneTest.java b/src/test/java/zone/moddev/mc/basesteam/machines/RockCrusherReloadRedstoneTest.java index 35e0e9a..5c0e915 100644 --- a/src/test/java/zone/moddev/mc/basesteam/machines/RockCrusherReloadRedstoneTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/machines/RockCrusherReloadRedstoneTest.java @@ -84,7 +84,7 @@ private static final class ControlSample extends RuntimeException {} private static final class ControlWorld extends ServerLevel { boolean powered, stopAtFirstSample; int samples; - private ControlWorld() { super(null, null, null, null, null, null, null, false, 0L, java.util.Collections.emptyList(), false, null); } + private ControlWorld() { super(null, null, null, null, null, null, false, 0L, java.util.Collections.emptyList(), false, null); } @Override public boolean hasNeighborSignal(BlockPos pos) { samples++; if (stopAtFirstSample) throw new ControlSample(); diff --git a/src/test/java/zone/moddev/mc/basesteam/machines/SteamDrillPersistenceTest.java b/src/test/java/zone/moddev/mc/basesteam/machines/SteamDrillPersistenceTest.java index 474dca7..cc3c31b 100644 --- a/src/test/java/zone/moddev/mc/basesteam/machines/SteamDrillPersistenceTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/machines/SteamDrillPersistenceTest.java @@ -36,15 +36,18 @@ public class SteamDrillPersistenceTest { @Test public void retainsLegacyProgressUntilWorldIsAttached() { SteamDrillTileEntity drill = new SteamDrillTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("steam_drill")); - drill.loadWithComponents(target(94), NativeTestBootstrap.registries()); + drill.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), target(94))); drill.prepareDataFieldsForSync(); assertEquals(94, drill.getDataFieldArray()[1]); } @Test public void clearsPreviousProgressOnReadingAnUntargetedTile() { SteamDrillTileEntity drill = new SteamDrillTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("steam_drill")); - drill.loadWithComponents(target(94), NativeTestBootstrap.registries()); - drill.loadWithComponents(new CompoundTag(), NativeTestBootstrap.registries()); + drill.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), target(94))); + drill.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), new CompoundTag())); drill.prepareDataFieldsForSync(); assertEquals(0, drill.getDataFieldArray()[1]); } @@ -57,7 +60,8 @@ public class SteamDrillPersistenceTest { state.putString("Name", "minecraft:stone"); tag.put("TargetState", state); SteamDrillTileEntity drill = new SteamDrillTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("steam_drill")); - drill.loadWithComponents(tag, NativeTestBootstrap.registries()); + drill.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), tag)); assertEquals(facing, field(drill, "savedFacing")); assertEquals(new BlockPos(2048, 80, 2049), field(drill, "targetBlockCoord")); assertEquals(state, field(drill, "savedTargetState")); @@ -71,11 +75,14 @@ public class SteamDrillPersistenceTest { SteamDrillTileEntity drill = new SteamDrillTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("steam_drill")); CompoundTag tag = target(94); tag.putByte("Facing", (byte) Direction.UP.get3DDataValue()); - drill.loadWithComponents(tag, NativeTestBootstrap.registries()); - drill.loadWithComponents(target(12), NativeTestBootstrap.registries()); + drill.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), tag)); + drill.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), target(12))); assertNull(field(drill, "savedFacing")); tag.putByte("Facing", (byte) 127); - drill.loadWithComponents(tag, NativeTestBootstrap.registries()); + drill.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), tag)); assertNull(field(drill, "savedFacing")); } @@ -90,7 +97,8 @@ public class SteamDrillPersistenceTest { items.add(stack); tag.put("Items", items); SteamDrillTileEntity drill = new SteamDrillTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("steam_drill")); - drill.loadWithComponents(tag, NativeTestBootstrap.registries()); + drill.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), tag)); assertEquals(37.5F, drill.getEnergy(Power.steam_power), 0.0F); ItemStack output = drill.getItem(4); assertEquals(net.minecraft.world.item.Items.COBBLESTONE, output.getItem()); @@ -110,9 +118,9 @@ public class SteamDrillPersistenceTest { assertNull(drill.addItem(different)); assertNull(drill.addItem(matching)); assertEquals(11, drill.getItem(0).getCount()); - assertEquals("first", drill.getItem(0).get(DataComponents.CUSTOM_DATA).copyTag().getString("source")); + assertEquals("first", drill.getItem(0).get(DataComponents.CUSTOM_DATA).copyTag().getStringOr("source", "")); assertEquals(4, drill.getItem(1).getCount()); - assertEquals("second", drill.getItem(1).get(DataComponents.CUSTOM_DATA).copyTag().getString("source")); + assertEquals("second", drill.getItem(1).get(DataComponents.CUSTOM_DATA).copyTag().getStringOr("source", "")); } @Test public void serializationNeverLooksUpTheWorldOrInfersNeighbours() throws Exception { @@ -130,14 +138,37 @@ public class SteamDrillPersistenceTest { if (!(instruction instanceof MethodInsnNode)) continue; MethodInsnNode call = (MethodInsnNode) instruction; assertFalse("Serialization must not reacquire a saving chunk: " + call.name, - call.name.equals("getFacing") || call.name.equals("inferFacing") - || call.name.equals("powerAdvantageWorld") || call.owner.startsWith("net/minecraft/world/")); + serializationTouchesWorld(call)); } } assertEquals(1, methods); assertTrue("Cached facing must still be serialized", cachedFacingReads > 0); } + @Test public void nativeOutputWriteAllowlistStillRejectsWorldAccessAndUnrelatedIoMethods() { + String output = "net/minecraft/world/level/storage/ValueOutput"; + assertFalse(serializationTouchesWorld(new MethodInsnNode(org.objectweb.asm.Opcodes.INVOKEINTERFACE, + output, "putShort", "(Ljava/lang/String;S)V", true))); + assertTrue(serializationTouchesWorld(new MethodInsnNode(org.objectweb.asm.Opcodes.INVOKEINTERFACE, + output, "child", "(Ljava/lang/String;)Lnet/minecraft/world/level/storage/ValueOutput;", true))); + assertTrue(serializationTouchesWorld(new MethodInsnNode(org.objectweb.asm.Opcodes.INVOKEVIRTUAL, + "net/minecraft/world/level/Level", "getBlockState", + "(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;", false))); + for (String method : new String[] {"getFacing", "inferFacing", "powerAdvantageWorld"}) + assertTrue(serializationTouchesWorld(new MethodInsnNode(org.objectweb.asm.Opcodes.INVOKEVIRTUAL, + "zone/moddev/mc/basesteam/machines/SteamDrillTileEntity", method, "()Ljava/lang/Object;", false))); + } + + private static boolean serializationTouchesWorld(MethodInsnNode call) { + if (call.name.equals("getFacing") || call.name.equals("inferFacing") || call.name.equals("powerAdvantageWorld")) return true; + if (!call.owner.startsWith("net/minecraft/world/")) return false; + if (!call.owner.equals("net/minecraft/world/level/storage/ValueOutput") + || call.getOpcode() != org.objectweb.asm.Opcodes.INVOKEINTERFACE) return true; + return !java.util.Set.of("putShort(Ljava/lang/String;S)V", "putByte(Ljava/lang/String;B)V", + "putInt(Ljava/lang/String;I)V", "store(Ljava/lang/String;Lcom/mojang/serialization/Codec;Ljava/lang/Object;)V") + .contains(call.name + call.desc); + } + @Test public void facinglessLegacyTargetsInferAllDirectionsWithoutAnAttachedWorld() throws Exception { Method infer = SteamDrillTileEntity.class.getDeclaredMethod("inferFacing"); infer.setAccessible(true); @@ -149,7 +180,8 @@ public class SteamDrillPersistenceTest { tag.putInt("targetY", target.getY()); tag.putInt("targetZ", target.getZ()); SteamDrillTileEntity drill = new SteamDrillTileEntity(origin, NativeMachineFixture.state("steam_drill")); - drill.loadWithComponents(tag, NativeTestBootstrap.registries()); + drill.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), tag)); assertNull(field(drill, "savedFacing")); assertEquals(direction, infer.invoke(drill)); } diff --git a/src/test/java/zone/moddev/mc/basesteam/machines/SteamMachinePersistenceTest.java b/src/test/java/zone/moddev/mc/basesteam/machines/SteamMachinePersistenceTest.java index ec9f8b4..c0e4959 100644 --- a/src/test/java/zone/moddev/mc/basesteam/machines/SteamMachinePersistenceTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/machines/SteamMachinePersistenceTest.java @@ -8,7 +8,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.ForgeRegistry; import org.junit.BeforeClass; @@ -54,20 +54,24 @@ public class SteamMachinePersistenceTest { tag.putInt("BurnTime", 127); tag.putInt("BurnTimeTotal", 1600); CoalBoilerTileEntity coal = new CoalBoilerTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("steam_boiler_coal")); - coal.loadWithComponents(tag, NativeTestBootstrap.registries()); + coal.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), tag)); CompoundTag saved = coal.saveWithFullMetadata(NativeTestBootstrap.registries()); - assertEquals("basesteam:steam_boiler_coal", saved.getString("id")); - assertEquals(127, saved.getInt("BurnTime")); - assertEquals(1600, saved.getInt("BurnTimeTotal")); + assertEquals("basesteam:steam_boiler_coal", saved.getStringOr("id", "")); + assertEquals(127, saved.getIntOr("BurnTime", 0)); + assertEquals(1600, saved.getIntOr("BurnTimeTotal", 0)); CoalBoilerTileEntity restored = new CoalBoilerTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("steam_boiler_coal")); - restored.loadWithComponents(saved, NativeTestBootstrap.registries()); + restored.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), saved)); assertEquals(37.5F, restored.getEnergy(Power.steam_power), 0.0F); assertEquals(1500, restored.getTank().getFluidAmount()); tag.putFloat("Electricity", 60.0F); ElectricBoilerTileEntity electric = new ElectricBoilerTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("steam_boiler_electric")); - electric.loadWithComponents(tag, NativeTestBootstrap.registries()); + electric.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), tag)); ElectricBoilerTileEntity electricRestored = new ElectricBoilerTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("steam_boiler_electric")); - electricRestored.loadWithComponents(electric.saveWithoutMetadata(NativeTestBootstrap.registries()), NativeTestBootstrap.registries()); + electricRestored.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), electric.saveWithoutMetadata(NativeTestBootstrap.registries()))); assertEquals(37.5F, electricRestored.getEnergy(Power.steam_power), 0.0F); assertEquals(60.0F, electricRestored.getEnergy(ElectricBoilerTileEntity.ELECTRIC_POWER), 0.0F); assertEquals(1500, electricRestored.getTank().getFluidAmount()); @@ -78,21 +82,25 @@ public class SteamMachinePersistenceTest { tag.put("TankOut", water(750)); tag.putByte("NextPump", (byte) 11); SteamPumpTileEntity pump = new SteamPumpTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("steam_pump")); - pump.loadWithComponents(tag, NativeTestBootstrap.registries()); + pump.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), tag)); CompoundTag saved = pump.saveWithoutMetadata(NativeTestBootstrap.registries()); - assertEquals(750, saved.getCompound("Tank").getInt("Amount")); - assertEquals(11, saved.getByte("NextPump")); + assertEquals(750, saved.getCompoundOrEmpty("Tank").getIntOr("Amount", 0)); + assertEquals(11, saved.getByteOr("NextPump", (byte)0)); SteamPumpTileEntity pumpRestored = new SteamPumpTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("steam_pump")); - pumpRestored.loadWithComponents(saved, NativeTestBootstrap.registries()); + pumpRestored.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), saved)); assertEquals(750, pumpRestored.getTank().getFluidAmount()); assertEquals(37.5F, pumpRestored.getEnergy(Power.steam_power), 0.0F); tag.putFloat("Tempoerature", 412.5F); GeothermalBoilerTileEntity geothermal = new GeothermalBoilerTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("steam_boiler_geothermal")); - geothermal.loadWithComponents(tag, NativeTestBootstrap.registries()); - assertEquals(412.5F, geothermal.saveWithoutMetadata(NativeTestBootstrap.registries()).getFloat("Temperature"), 0.0F); + geothermal.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), tag)); + assertEquals(412.5F, geothermal.saveWithoutMetadata(NativeTestBootstrap.registries()).getFloatOr("Temperature", 0.0F), 0.0F); tag.putFloat("Temperature", 500.0F); - geothermal.loadWithComponents(tag, NativeTestBootstrap.registries()); - assertEquals(500.0F, geothermal.saveWithoutMetadata(NativeTestBootstrap.registries()).getFloat("Temperature"), 0.0F); + geothermal.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), tag)); + assertEquals(500.0F, geothermal.saveWithoutMetadata(NativeTestBootstrap.registries()).getFloatOr("Temperature", 0.0F), 0.0F); } @Test public void stillKeepsSeparateTanksAndExplicitlyEmptyReloads() { @@ -100,15 +108,18 @@ public class SteamMachinePersistenceTest { tag.put("TankIn", water(625)); tag.put("TankOut", water(375)); SteamStillTileEntity still = new SteamStillTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("steam_still")); - still.loadWithComponents(tag, NativeTestBootstrap.registries()); + still.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), tag)); SteamStillTileEntity restored = new SteamStillTileEntity(net.minecraft.core.BlockPos.ZERO, NativeMachineFixture.state("steam_still")); - restored.loadWithComponents(still.saveWithoutMetadata(NativeTestBootstrap.registries()), NativeTestBootstrap.registries()); + restored.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), still.saveWithoutMetadata(NativeTestBootstrap.registries()))); assertEquals(625, restored.getInputTank().getFluidAmount()); assertEquals(375, restored.getOutputTank().getFluidAmount()); assertEquals(37.5F, restored.getEnergy(Power.steam_power), 0.0F); tag.put("TankIn", water(0)); tag.put("TankOut", water(0)); - restored.loadWithComponents(tag, NativeTestBootstrap.registries()); + restored.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), tag)); assertEquals(0, restored.getInputTank().getFluidAmount()); assertEquals(0, restored.getOutputTank().getFluidAmount()); } diff --git a/src/test/java/zone/moddev/mc/basesteam/machines/SteamOperationalPumpSourceTest.java b/src/test/java/zone/moddev/mc/basesteam/machines/SteamOperationalPumpSourceTest.java index 08cd263..8652ea0 100644 --- a/src/test/java/zone/moddev/mc/basesteam/machines/SteamOperationalPumpSourceTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/machines/SteamOperationalPumpSourceTest.java @@ -54,7 +54,7 @@ public class SteamOperationalPumpSourceTest { } @Test public void unsupportedModSourceIsNeverReplacedOrCredited() { - Block host = new Block(Block.Properties.of()) { + Block host = new Block(properties("unsupported_source")) { @Override public FluidState getFluidState(BlockState state) { return Fluids.WATER.defaultFluidState(); } }; BlockState source = host.defaultBlockState(); @@ -129,10 +129,10 @@ private static MethodNode method(ClassNode node, String name) { private static final class RefusingSource extends Block implements BucketPickup { private int pickups; - private RefusingSource() { super(Block.Properties.of()); } + private RefusingSource() { super(SteamOperationalPumpSourceTest.properties("refusing_source")); } @Override public FluidState getFluidState(BlockState state) { return Fluids.WATER.defaultFluidState(); } @Override public net.minecraft.world.item.ItemStack pickupBlock( - net.minecraft.world.entity.player.Player player, LevelAccessor world, BlockPos pos, BlockState state) { + net.minecraft.world.entity.LivingEntity player, LevelAccessor world, BlockPos pos, BlockState state) { assertNull("Automated pickup has no acting player", player); pickups++; return net.minecraft.world.item.ItemStack.EMPTY; @@ -140,6 +140,12 @@ private static final class RefusingSource extends Block implements BucketPickup @Override public java.util.Optional getPickupSound() { return java.util.Optional.empty(); } } + private static Block.Properties properties(String name) { + return Block.Properties.of().setId(net.minecraft.resources.ResourceKey.create( + net.minecraft.core.registries.Registries.BLOCK, + net.minecraft.resources.Identifier.fromNamespaceAndPath("basesteam_test", name))); + } + private static final class PickupWorld { private BlockState state; private int writes; diff --git a/src/test/java/zone/moddev/mc/basesteam/machines/WaterBoilerTankNotificationTest.java b/src/test/java/zone/moddev/mc/basesteam/machines/WaterBoilerTankNotificationTest.java index c36ae17..ab4bdd3 100644 --- a/src/test/java/zone/moddev/mc/basesteam/machines/WaterBoilerTankNotificationTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/machines/WaterBoilerTankNotificationTest.java @@ -84,7 +84,7 @@ public class WaterBoilerTankNotificationTest { assertEquals(5, fill(water(5), true)); assertEquals(15, probe.tank().getFluidAmount()); assertEquals(0, probe.save().dirtyCalls); - assertEquals(10, probe.save().savedTank.getInt("Amount")); + assertEquals(10, probe.save().savedTank.getIntOr("Amount", 0)); } @Test public void aClientDirectDrainDoesNotRequestServerSaving() { @@ -92,7 +92,7 @@ public class WaterBoilerTankNotificationTest { assertEquals(5, probe.tank().drain(5, true).amount); assertEquals(5, probe.tank().getFluidAmount()); assertEquals(0, probe.save().dirtyCalls); - assertEquals(10, probe.save().savedTank.getInt("Amount")); + assertEquals(10, probe.save().savedTank.getIntOr("Amount", 0)); } private static FluidStack water(int amount) { return new FluidStack(net.minecraft.world.level.material.Fluids.WATER, amount); } @@ -100,12 +100,12 @@ public class WaterBoilerTankNotificationTest { private void saved(int amount) { assertTrue("Real server-side tank mutation must notify its owner immediately", probe.save().dirtyCalls > 0); assertEquals(amount, probe.tank().getFluidAmount()); - assertEquals(amount, probe.save().savedTank.getInt("Amount")); + assertEquals(amount, probe.save().savedTank.getIntOr("Amount", 0)); } private void unchanged(int amount) { assertEquals(0, probe.save().dirtyCalls); assertEquals(amount, probe.tank().getFluidAmount()); - assertEquals(amount, probe.save().savedTank.getInt("Amount")); + assertEquals(amount, probe.save().savedTank.getIntOr("Amount", 0)); } private int fill(FluidStack offer, boolean execute) throws Exception { return ((Number) probe.machine().getClass().getMethod("fill", net.minecraft.core.Direction.class, FluidStack.class, boolean.class) @@ -162,6 +162,6 @@ private static Level world(boolean client) { } catch (ReflectiveOperationException failure) { throw new AssertionError(failure); } } private static final class SaveWorld extends net.minecraft.server.level.ServerLevel { - private SaveWorld() { super(null, null, null, null, null, null, null, false, 0L, java.util.Collections.emptyList(), false, null); } + private SaveWorld() { super(null, null, null, null, null, null, false, 0L, java.util.Collections.emptyList(), false, null); } } } diff --git a/src/test/java/zone/moddev/mc/basesteam/migration/SteamLegacyMigrationsTest.java b/src/test/java/zone/moddev/mc/basesteam/migration/SteamLegacyMigrationsTest.java index 0a694cb..54ff55b 100644 --- a/src/test/java/zone/moddev/mc/basesteam/migration/SteamLegacyMigrationsTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/migration/SteamLegacyMigrationsTest.java @@ -7,7 +7,7 @@ import cyano.poweradvantage.api.migration.LegacyMigrationApi; import net.minecraft.nbt.CompoundTag; import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import org.junit.Test; public class SteamLegacyMigrationsTest { @@ -30,20 +30,20 @@ public class SteamLegacyMigrationsTest { oldTank.putInt("Amount", 1250); pump.put("TankOut", oldTank); assertTrue(SteamLegacyMigrations.repairPersistenceKeys(pump)); - assertEquals(1250, pump.getCompound("Tank").getInt("Amount")); + assertEquals(1250, pump.getCompoundOrEmpty("Tank").getIntOr("Amount", 0)); CompoundTag boiler = new CompoundTag(); boiler.putString("id", "steamadvantage:steam_boiler_geothermal"); boiler.putFloat("Tempoerature", 412.5F); assertTrue(SteamLegacyMigrations.repairPersistenceKeys(boiler)); - assertEquals(412.5F, boiler.getFloat("Temperature"), 0.0F); + assertEquals(412.5F, boiler.getFloatOr("Temperature", 0.0F), 0.0F); CompoundTag canonical = new CompoundTag(); canonical.putString("id", "basesteam:steam_boiler_geothermal"); canonical.putFloat("Temperature", 500.0F); canonical.putFloat("Tempoerature", 100.0F); assertFalse(SteamLegacyMigrations.repairPersistenceKeys(canonical)); - assertEquals(500.0F, canonical.getFloat("Temperature"), 0.0F); + assertEquals(500.0F, canonical.getFloatOr("Temperature", 0.0F), 0.0F); } @Test public void aliasesLegacyTileIds() { @@ -65,15 +65,15 @@ public class SteamLegacyMigrationsTest { "steam_boiler_geothermal", "steam_tank", "steam_furnace", "steam_crusher", "steam_drill", "drillbit", "platform", "steam_elevator", "steam_switch", "pump_pipe_steam", "steam_still", "steam_pump", "steam_boiler_oil"}) { - ResourceLocation current = ResourceLocation.fromNamespaceAndPath("basesteam", path); - assertEquals(current, LegacyMigrationApi.blockTarget(ResourceLocation.fromNamespaceAndPath("steamadvantage", path))); + Identifier current = Identifier.fromNamespaceAndPath("basesteam", path); + assertEquals(current, LegacyMigrationApi.blockTarget(Identifier.fromNamespaceAndPath("steamadvantage", path))); assertTrue(path, LegacyMigrationApi.blockStateCodecs().containsKey(current)); } - ResourceLocation oldMusket = ResourceLocation.parse("steamadvantage:musket"); - assertEquals(ResourceLocation.parse("basesteam:musket"), LegacyMigrationApi.itemTarget(oldMusket)); + Identifier oldMusket = Identifier.parse("steamadvantage:musket"); + assertEquals(Identifier.parse("basesteam:musket"), LegacyMigrationApi.itemTarget(oldMusket)); assertTrue(LegacyMigrationApi.isDurableItem(oldMusket)); - assertEquals(ResourceLocation.parse("basesteam:rapid_reload"), - LegacyMigrationApi.registryTarget(ResourceLocation.parse("steamadvantage:rapid_reload"))); + assertEquals(Identifier.parse("basesteam:rapid_reload"), + LegacyMigrationApi.registryTarget(Identifier.parse("steamadvantage:rapid_reload"))); } @Test public void repairsOldKeysForBothNamespacesWithoutDependingOnRemapOrder() { @@ -84,8 +84,8 @@ public class SteamLegacyMigrationsTest { tank.putInt("Amount", 1250); pump.put("TankOut", tank); assertTrue(source, SteamLegacyMigrations.repairPersistenceKeys(pump)); - assertEquals(source, pump.getString("id")); - assertEquals(tank, pump.getCompound("Tank")); + assertEquals(source, pump.getStringOr("id", "")); + assertEquals(tank, pump.getCompoundOrEmpty("Tank")); assertFalse(SteamLegacyMigrations.repairPersistenceKeys(pump)); } } diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/BuildWorkflowContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/BuildWorkflowContractTest.java index d6d4354..927798a 100644 --- a/src/test/java/zone/moddev/mc/basesteam/resources/BuildWorkflowContractTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/resources/BuildWorkflowContractTest.java @@ -12,12 +12,12 @@ public class BuildWorkflowContractTest { try (var reader = Files.newBufferedReader(Path.of("gradle.properties"))) { pins.load(reader); } - assertEquals("1.21.1", pins.getProperty("minecraft_version")); - assertEquals("52.1.0", pins.getProperty("forge_version")); + assertEquals("1.21.11", pins.getProperty("minecraft_version")); + assertEquals("61.1.0", pins.getProperty("forge_version")); assertEquals("21", pins.getProperty("java_version")); assertEquals("official", pins.getProperty("mapping_channel")); - assertEquals("1.21.1", pins.getProperty("mapping_version")); - assertEquals("2.4.14.121011", pins.getProperty("poweradvantage_version")); + assertEquals("1.21.11", pins.getProperty("mapping_version")); + assertEquals("2.4.15.121111", pins.getProperty("poweradvantage_version")); for (String key : new String[] {"poweradvantage_source_ref", "poweradvantage_deobf_sha256", "poweradvantage_player_sha256", "poweradvantage_test_bootstrap_sha256", "poweradvantage_test_bootstrap_source_sha256", "poweradvantage_test_registry_source_sha256", @@ -29,7 +29,7 @@ public class BuildWorkflowContractTest { } assertEquals(pins.getProperty("poweradvantage_deobf_sha256"), pins.getProperty("poweradvantage_player_sha256")); String build = Files.readString(Path.of("build.gradle")); - assertTrue(build.contains("apply from: 'gradle/forge52-mavenizer.gradle'")); + assertTrue(build.contains("apply from: 'gradle/forge61-mavenizer.gradle'")); assertFalse(build.contains("renamer.classes(")); assertFalse(build.contains("toSrgFile")); assertTrue(build.contains("basePower-${project.poweradvantage_version}.jar")); @@ -40,9 +40,9 @@ public class BuildWorkflowContractTest { String source = Files.readString(Path.of(".github/workflows", name)); int pinCheck = source.indexOf("bash .github/verify-published-pins.sh"); assertTrue(name, pinCheck > 0 && pinCheck < source.indexOf("Check out released basePower source")); - assertTrue(name, source.contains("branches: [master-1.21.1")); + assertTrue(name, source.contains("branches: [master-1.21.11")); assertTrue(name, source.contains("java-version: '21.0.7+6.0.LTS'")); - assertTrue(name, source.contains("-Pforge52MavenizerJar=$MAVENIZER")); + assertTrue(name, source.contains("-Pforge61MavenizerJar=$MAVENIZER")); assertTrue(name, source.contains("-PpowerAdvantageDeobfJar=$POWER_PLAYER")); assertFalse(name, source.contains("forge47")); } @@ -52,4 +52,17 @@ public class BuildWorkflowContractTest { assertTrue(script.contains("$(value orespawn_public_file_id)")); assertTrue(script.contains("sha256sum -c -")); } + + @Test public void sourceAndPackageAuditsEnforceRequiredBaseMetalsWithoutAnOptionalLaunchScope() throws Exception { + String build = Files.readString(Path.of("build.gradle")); + assertEquals(2, build.lines().filter(line -> line.contains("verifyRequiredBaseMetalsMetadata(metadata)")).count()); + assertTrue(build.contains("Base Metals must be declared exactly once as REQUIRED on BOTH sides")); + String readme = Files.readString(Path.of("README.md")); + assertTrue(readme.contains("Base Metals is REQUIRED")); + assertFalse(readme.contains("-WithoutBaseMetals")); + String smoke = Files.readString(Path.of("tools/run-packaged-server-smoke.ps1")); + int reject = smoke.indexOf("if ($WithoutBaseMetals)"); + assertTrue(reject >= 0 && reject < smoke.indexOf("$project =")); + assertTrue(smoke.contains("a no-Base-Metals runtime is not supported")); + } } diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/MusketFragileTagContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/MusketFragileTagContractTest.java index 84131c1..96e8543 100644 --- a/src/test/java/zone/moddev/mc/basesteam/resources/MusketFragileTagContractTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/resources/MusketFragileTagContractTest.java @@ -8,7 +8,7 @@ import java.util.HexFormat; import java.util.TreeSet; import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import org.junit.BeforeClass; import org.junit.Test; import zone.moddev.mc.basesteam.testing.NativeTestBootstrap; @@ -31,7 +31,7 @@ public class MusketFragileTagContractTest { assertTrue(blocks.contains("minecraft:short_grass")); for (String id : blocks) { assertTrue("Unregistered Forge 52 block would invalidate the tag: " + id, - BuiltInRegistries.BLOCK.containsKey(ResourceLocation.parse(id))); + BuiltInRegistries.BLOCK.containsKey(Identifier.parse(id))); } assertFalse(blocks.contains("minecraft:tnt")); assertFalse(blocks.contains("minecraft:stone")); diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/NativeBlockEntityContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/NativeBlockEntityContractTest.java index 055f99c..06c39bf 100644 --- a/src/test/java/zone/moddev/mc/basesteam/resources/NativeBlockEntityContractTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/resources/NativeBlockEntityContractTest.java @@ -44,7 +44,8 @@ public class NativeBlockEntityContractTest { saved.putInt("x", 7); saved.putInt("y", 9); saved.putInt("z", 11); saved.putIntArray("Energy", new int[] { Float.floatToIntBits(37.5F) }); saved.putByte("Facing", (byte) direction.get3DDataValue()); - drill.loadWithComponents(saved, NativeTestBootstrap.registries()); + drill.loadWithComponents(net.minecraft.world.level.storage.TagValueInput.create( + net.minecraft.util.ProblemReporter.DISCARDING, NativeTestBootstrap.registries(), saved)); assertEquals(destination, drill.getBlockPos()); assertSame(state, drill.getBlockState()); assertEquals(direction, drill.getBlockState().getValue(SteamDrillBlock.FACING)); @@ -56,7 +57,7 @@ public class NativeBlockEntityContractTest { DrillBitBlock block = new DrillBitBlock(); BlockEntityType previous = Entities.DRILLBIT; try { - Entities.DRILLBIT = BlockEntityType.Builder.of(DrillBitTileEntity::new, block).build(null); + Entities.DRILLBIT = new BlockEntityType<>(DrillBitTileEntity::new, java.util.Set.of(block)); assertNotNull(block.getTicker(null, block.defaultBlockState(), Entities.DRILLBIT)); assertNull(block.getTicker(null, block.defaultBlockState(), BlockEntityType.FURNACE)); DrillBitTileEntity tile = new DrillBitTileEntity(new BlockPos(17, 80, 31), block.defaultBlockState()); @@ -69,8 +70,8 @@ public class NativeBlockEntityContractTest { @Test public void nativeConstructorStillRejectsAMismatchedTypeAndBlock() { BlockEntityType previous = Entities.DRILLBIT; try { - Entities.DRILLBIT = BlockEntityType.Builder.of(DrillBitTileEntity::new, - net.minecraft.world.level.block.Blocks.STONE).build(null); + Entities.DRILLBIT = new BlockEntityType<>(DrillBitTileEntity::new, + java.util.Set.of(net.minecraft.world.level.block.Blocks.STONE)); assertThrows(IllegalStateException.class, () -> new DrillBitTileEntity( BlockPos.ZERO, NativeMachineFixture.state("drillbit"))); } finally { Entities.DRILLBIT = previous; } diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/NativeBlockItemTranslationTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/NativeBlockItemTranslationTest.java new file mode 100644 index 0000000..8a45f4c --- /dev/null +++ b/src/test/java/zone/moddev/mc/basesteam/resources/NativeBlockItemTranslationTest.java @@ -0,0 +1,129 @@ +package zone.moddev.mc.basesteam.resources; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.serialization.Lifecycle; +import java.lang.management.ManagementFactory; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.chat.contents.TranslatableContents; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.ForgeRegistry; +import net.minecraftforge.registries.RegisterEvent; +import org.junit.Test; +import zone.moddev.mc.basepower.init.ItemGroups; +import zone.moddev.mc.basesteam.init.Blocks; +import zone.moddev.mc.basesteam.testing.NativeTestBootstrap; +import static org.junit.Assert.*; + +public class NativeBlockItemTranslationTest { + @Test public void actualProductionBlockItemFactoryUsesTranslatedBlockKeys() throws Exception { + isolated("factory", "BLOCKITEM_FACTORY_17_PASS"); + } + + @Test public void omittedNativeBlockPrefixReproducesTheUntranslatedItemKey() throws Exception { + isolated("negative", "BLOCKITEM_MISSING_PREFIX_NEGATIVE_PASS"); + } + + private static void isolated(String mode, String marker) throws Exception { + Path root = Path.of("build/native-blockitem-translations").toAbsolutePath(); + Files.createDirectories(root); + Path evidence = Files.createTempDirectory(root, mode + "-"); + Path javaExecutable = Path.of(System.getProperty("java.home"), "bin", "java"); + if (!Files.isExecutable(javaExecutable)) javaExecutable = javaExecutable.resolveSibling("java.exe"); + String classpath = Arrays.stream(System.getProperty("basesteam.nativePackClasspath", + System.getProperty("java.class.path")).split(java.util.regex.Pattern.quote(java.io.File.pathSeparator))) + .map(entry -> new java.io.File(entry).getAbsolutePath()) + .collect(java.util.stream.Collectors.joining(java.io.File.pathSeparator)); + var command = new ArrayList<>(java.util.List.of(javaExecutable.toString(), "-XX:ActiveProcessorCount=4", "-Xmx512m", + "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED")); + command.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments().stream() + .filter(argument -> argument.startsWith("-javaagent:")).toList()); + command.addAll(java.util.List.of("-cp", classpath, NativeBlockItemTranslationTest.class.getName(), mode)); + Path log = evidence.resolve("native-factory.log"); + Process process = new ProcessBuilder(command).redirectErrorStream(true).redirectOutput(log.toFile()).start(); + if (!process.waitFor(90, TimeUnit.SECONDS)) { + process.destroyForcibly().waitFor(); + throw new AssertionError("Native BlockItem proof timed out: " + log); + } + String output = Files.readString(log); + assertEquals(log + "\n" + output, 0, process.exitValue()); + assertTrue(log + "\n" + output, output.contains(marker)); + } + + public static void main(String[] args) throws Exception { + if (args.length != 1) throw new IllegalArgumentException("Expected factory or negative proof"); + NativeTestBootstrap.start(); + Blocks.init(); + switch (args[0]) { + case "factory" -> { + proveFactory(); + System.out.println("BLOCKITEM_FACTORY_17_PASS"); + } + case "negative" -> { + proveNegative(); + System.out.println("BLOCKITEM_MISSING_PREFIX_NEGATIVE_PASS"); + } + default -> throw new IllegalArgumentException("Unknown BlockItem proof: " + args[0]); + } + } + + @SuppressWarnings("unchecked") + private static void proveFactory() throws Exception { + MappedRegistry items = new MappedRegistry<>(Registries.ITEM, Lifecycle.stable()); + var constructor = RegisterEvent.class.getDeclaredConstructor(ResourceKey.class, ForgeRegistry.class, Registry.class); + constructor.setAccessible(true); + var visible = ItemGroups.class.getDeclaredField("VISIBLE_ITEMS"); + visible.setAccessible(true); + Set visibleItems = (Set)visible.get(null); + try { + Blocks.registerBlockItems(constructor.newInstance(ForgeRegistries.Keys.ITEMS, null, items)); + } finally { + for (Item item : items) visibleItems.remove(item); + } + assertEquals(Set.of("steam_pipe", "steam_track", "steam_boiler_coal", "steam_boiler_electric", + "steam_boiler_geothermal", "steam_tank", "steam_furnace", "steam_crusher", "steam_drill", + "drillbit", "platform", "steam_elevator", "steam_switch", "pump_pipe_steam", "steam_still", + "steam_pump", "steam_boiler_oil"), + items.keySet().stream().map(Identifier::getPath).collect(java.util.stream.Collectors.toSet())); + JsonObject english = english(); + for (Identifier id : items.keySet()) { + String key = "block.basesteam." + id.getPath(); + assertEquals("basesteam", id.getNamespace()); + assertTrue(items.getValue(id) instanceof BlockItem); + BlockItem item = (BlockItem)items.getValue(id); + assertEquals(key, item.getBlock().getDescriptionId()); + assertEquals(key, item.getDescriptionId()); + assertTrue(item.getDefaultInstance().getHoverName().getContents() instanceof TranslatableContents); + assertEquals(key, ((TranslatableContents)item.getDefaultInstance().getHoverName().getContents()).getKey()); + assertTrue("Untranslated native block item " + key, english.has(key)); + assertFalse(english.get(key).getAsString().isBlank()); + } + } + + private static void proveNegative() throws Exception { + BlockItem wrong = new BlockItem(Blocks.steam_pipe, new Item.Properties().setId(ResourceKey.create( + Registries.ITEM, Identifier.fromNamespaceAndPath("basesteam", "steam_pipe")))); + assertEquals("item.basesteam.steam_pipe", wrong.getDescriptionId()); + assertEquals("item.basesteam.steam_pipe", + ((TranslatableContents)wrong.getDefaultInstance().getHoverName().getContents()).getKey()); + assertFalse(english().has(wrong.getDescriptionId())); + assertTrue(english().has("block.basesteam.steam_pipe")); + } + + private static JsonObject english() throws Exception { + return JsonParser.parseString(Files.readString( + Path.of("src/main/resources/assets/basesteam/lang/en_us.json"))).getAsJsonObject(); + } +} diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/NativeBlockPropertiesTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/NativeBlockPropertiesTest.java index f2e18a1..fde65cb 100644 --- a/src/test/java/zone/moddev/mc/basesteam/resources/NativeBlockPropertiesTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/resources/NativeBlockPropertiesTest.java @@ -13,7 +13,6 @@ import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.level.EmptyBlockGetter; import net.minecraft.world.level.material.PushReaction; -import net.minecraft.world.level.storage.loot.BuiltInLootTables; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -50,7 +49,7 @@ public static Collection cases() { @Test public void remainsNonOpaque() { for (BlockState state : states()) { - assertFalse(state.toString(), state.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)); + assertFalse(state.toString(), state.isSolidRender()); } } @@ -58,7 +57,7 @@ public static Collection cases() { // Native opacity is 1, not 0, for a non-solid block with a full-cube outline. int expected = height < 0 || height == 4 ? 1 : 0; for (BlockState state : states()) { - assertEquals(state.toString(), expected, state.getLightBlock(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)); + assertEquals(state.toString(), expected, state.getLightBlock()); } } @@ -92,7 +91,7 @@ public static Collection cases() { state.canBeReplaced(net.minecraft.world.level.material.Fluids.WATER)); assertEquals(height < 0 ? PushReaction.BLOCK : PushReaction.NORMAL, state.getPistonPushReaction()); } - if (height >= 0) assertEquals(BuiltInLootTables.EMPTY, block.getLootTable()); + if (height >= 0) assertTrue("Hidden helpers have no native loot-table key", block.getLootTable().isEmpty()); } @Test public void fullCollisionHelpersNeitherSuffocateNorBlockTheView() { diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/NativeEnchantmentContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/NativeEnchantmentContractTest.java index d5ff346..c0d4806 100644 --- a/src/test/java/zone/moddev/mc/basesteam/resources/NativeEnchantmentContractTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/resources/NativeEnchantmentContractTest.java @@ -17,7 +17,7 @@ import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.tags.ItemTags; import net.minecraft.tags.TagKey; import net.minecraft.world.entity.EquipmentSlot; @@ -43,17 +43,18 @@ public class NativeEnchantmentContractTest { NativeTestBootstrap.start(); Items.init(); Items.allItems.forEach((id, item) -> { - ResourceLocation key = ResourceLocation.fromNamespaceAndPath("basesteam", id); + Identifier key = Identifier.fromNamespaceAndPath("basesteam", id); if (!ForgeRegistries.ITEMS.containsKey(key)) ForgeRegistries.ITEMS.register(key, item); assertSame(item, ForgeRegistries.ITEMS.getValue(key)); }); previousTags = new HashMap<>(); - BuiltInRegistries.ITEM.getTags().forEach(pair -> - previousTags.put(pair.getFirst(), pair.getSecond().stream().toList())); + BuiltInRegistries.ITEM.freeze(); + BuiltInRegistries.ITEM.getTags().forEach(tag -> + previousTags.put(tag.key(), tag.stream().toList())); var tags = new HashMap<>(previousTags); - tags.put(ItemTags.create(ResourceLocation.parse("basesteam:muskets")), + tags.put(ItemTags.create(Identifier.parse("basesteam:muskets")), List.of(Items.blackpowder_musket.builtInRegistryHolder())); - BuiltInRegistries.ITEM.bindTags(tags); + NativeTestBootstrap.bindItemTags(tags); var enchantments = new MappedRegistry<>(Registries.ENCHANTMENT, Lifecycle.stable()); registries = new RegistryAccess.ImmutableRegistryAccess(Stream.concat( NativeTestBootstrap.registries().registries(), @@ -61,7 +62,7 @@ public class NativeEnchantmentContractTest { var ops = registries.createSerializationContext(JsonOps.INSTANCE); for (var key : KEYS) { try (var reader = Files.newBufferedReader(Path.of( - "src/main/resources/data/basesteam/enchantment", key.location().getPath() + ".json"))) { + "src/main/resources/data/basesteam/enchantment", key.identifier().getPath() + ".json"))) { Enchantment value = Enchantment.DIRECT_CODEC.parse(ops, JsonParser.parseReader(reader)).getOrThrow(); enchantments.register(key, value, RegistrationInfo.BUILT_IN); } @@ -70,7 +71,7 @@ public class NativeEnchantmentContractTest { } @AfterClass public static void restoreTags() { - if (previousTags != null) BuiltInRegistries.ITEM.bindTags(previousTags); + if (previousTags != null) NativeTestBootstrap.bindItemTags(previousTags); } @Test public void nativeDefinitionsRetainEveryLegacyCostLevelWeightAndSlot() { @@ -105,7 +106,8 @@ public class NativeEnchantmentContractTest { zone.moddev.mc.basesteam.items.MusketItem.startLoad(stack); zone.moddev.mc.basesteam.items.MusketItem.finishLoad(stack); zone.moddev.mc.basesteam.items.MusketItem.unload(stack); - ItemStack restored = ItemStack.parse(registries, stack.save(registries)).orElseThrow(); + ItemStack restored = ItemStack.CODEC.parse(registries.createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE), + ItemStack.CODEC.encodeStart(registries.createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE), stack).getOrThrow()).getOrThrow(); assertEquals(original, restored.get(DataComponents.ENCHANTMENTS)); assertEquals(123, restored.getDamageValue()); for (var key : KEYS) @@ -116,11 +118,11 @@ public class NativeEnchantmentContractTest { var stack = new ItemStack(Items.blackpowder_musket); assertThrows(IllegalStateException.class, () -> Enchantments.level(Enchantments.recoil, stack, RegistryAccess.EMPTY)); - var missing = ResourceKey.create(Registries.ENCHANTMENT, ResourceLocation.parse("basesteam:missing")); + var missing = ResourceKey.create(Registries.ENCHANTMENT, Identifier.parse("basesteam:missing")); assertThrows(IllegalStateException.class, () -> Enchantments.level(missing, stack, registries)); } private static Holder.Reference holder(ResourceKey key) { - return registries.registryOrThrow(Registries.ENCHANTMENT).getHolderOrThrow(key); + return registries.lookupOrThrow(Registries.ENCHANTMENT).getOrThrow(key); } } diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/NativeFuelContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/NativeFuelContractTest.java index 3bf11b4..57001a3 100644 --- a/src/test/java/zone/moddev/mc/basesteam/resources/NativeFuelContractTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/resources/NativeFuelContractTest.java @@ -12,11 +12,10 @@ import net.minecraft.tags.TagKey; import net.minecraft.tags.TagLoader; import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.core.Holder; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; -import net.minecraftforge.common.ForgeHooks; import net.minecraftforge.registries.ForgeRegistries; import java.util.HashMap; import java.util.List; @@ -36,22 +35,22 @@ public class NativeFuelContractTest { try (MultiPackResourceManager resources = new MultiPackResourceManager( PackType.SERVER_DATA, List.of(ServerPacksSource.createVanillaPackSource()))) { TagLoader> reader = new TagLoader<>( - id -> BuiltInRegistries.ITEM.getHolder(ResourceKey.create(Registries.ITEM, id)), "tags/item"); + (id, required) -> BuiltInRegistries.ITEM.get(ResourceKey.create(Registries.ITEM, id)) + .map(holder -> (Holder)holder), "tags/item"); Map, List>> loaded = new HashMap<>(); - reader.loadAndBuild(resources).forEach((id, tag) -> loaded.put(ItemTags.create(id), List.copyOf(tag))); + reader.build(reader.load(resources)).forEach((id, tag) -> loaded.put(ItemTags.create(id), List.copyOf(tag))); assertTrue(loaded.containsKey(ItemTags.NON_FLAMMABLE_WOOD)); previousTags = new HashMap<>(); - BuiltInRegistries.ITEM.getTags().forEach(pair -> - previousTags.put(pair.getFirst(), pair.getSecond().stream().toList())); - BuiltInRegistries.ITEM.bindTags(loaded); + BuiltInRegistries.ITEM.freeze(); + BuiltInRegistries.ITEM.getTags().forEach(tag -> + previousTags.put(tag.key(), tag.stream().toList())); + NativeTestBootstrap.bindItemTags(loaded); } - ForgeHooks.updateBurns(); } @AfterClass public static void restoreNativeTags() { if (previousTags != null) { - BuiltInRegistries.ITEM.bindTags(previousTags); - ForgeHooks.updateBurns(); + NativeTestBootstrap.bindItemTags(previousTags); } } @@ -64,13 +63,16 @@ public class NativeFuelContractTest { } @Test public void retainsTheForgeItemBurnTimeOverride() { - Item item = new Item(new Item.Properties()) { + ((net.minecraft.core.MappedRegistry)BuiltInRegistries.ITEM).unfreeze(); + Item item = new Item(new Item.Properties().setId(ResourceKey.create(Registries.ITEM, + Identifier.fromNamespaceAndPath("basesteam_test", "fuel_override")))) { @Override public int getBurnTime(ItemStack stack, RecipeType recipeType) { assertNull(recipeType); return 237; } }; - ForgeRegistries.ITEMS.register(ResourceLocation.fromNamespaceAndPath("basesteam_test", "fuel_override"), item); + ForgeRegistries.ITEMS.register(Identifier.fromNamespaceAndPath("basesteam_test", "fuel_override"), item); + BuiltInRegistries.ITEM.freeze(); assertEquals(237, FurnaceFuelHelper.getBurnTime(new ItemStack(item))); } } diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/NativeIngredientCodecContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/NativeIngredientCodecContractTest.java new file mode 100644 index 0000000..83bfbec --- /dev/null +++ b/src/test/java/zone/moddev/mc/basesteam/resources/NativeIngredientCodecContractTest.java @@ -0,0 +1,92 @@ +package zone.moddev.mc.basesteam.resources; + +import com.google.gson.JsonParser; +import com.mojang.serialization.JsonOps; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.repository.ServerPacksSource; +import net.minecraft.server.packs.resources.MultiPackResourceManager; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.tags.TagLoader; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.Ingredient; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import zone.moddev.mc.basesteam.testing.NativeTestBootstrap; +import static org.junit.Assert.*; + +public class NativeIngredientCodecContractTest { + private static Map, List>> previousTags; + + @BeforeClass public static void bootstrap() throws Exception { + NativeTestBootstrap.registerCraftingSerializers(); + try (var resources = new MultiPackResourceManager(PackType.SERVER_DATA, + List.of(ServerPacksSource.createVanillaPackSource()))) { + TagLoader> reader = new TagLoader<>((id, required) -> BuiltInRegistries.ITEM + .get(ResourceKey.create(Registries.ITEM, id)).map(holder -> (Holder) holder), "tags/item"); + Map, List>> loaded = new HashMap<>(); + reader.build(reader.load(resources)).forEach((id, values) -> loaded.put(ItemTags.create(id), List.copyOf(values))); + assertTrue("Load real vanilla tag data", loaded.containsKey(ItemTags.PLANKS)); + previousTags = new HashMap<>(); + BuiltInRegistries.ITEM.freeze(); + BuiltInRegistries.ITEM.getTags().forEach(tag -> previousTags.put(tag.key(), tag.stream().toList())); + NativeTestBootstrap.bindItemTags(loaded); + } + } + + @AfterClass public static void restoreTags() { + if (previousTags != null) NativeTestBootstrap.bindItemTags(previousTags); + } + + @Test public void nativeItemStringKeepsItsExactMembershipAndWireShape() { + Ingredient ingredient = decode("\"minecraft:string\""); + assertTrue(ingredient.test(new ItemStack(Items.STRING))); + assertFalse(ingredient.test(new ItemStack(Items.STONE))); + assertFalse(ingredient.test(ItemStack.EMPTY)); + assertEquals(JsonParser.parseString("\"minecraft:string\""), Ingredient.CODEC.encodeStart( + NativeTestBootstrap.registries().createSerializationContext(JsonOps.INSTANCE), ingredient).getOrThrow()); + } + + @Test public void nativeTagStringUsesTheActualVanillaTagAndNotAnyItem() { + Ingredient ingredient = decode("\"#minecraft:planks\""); + for (Item plank : List.of(Items.OAK_PLANKS, Items.SPRUCE_PLANKS, Items.CRIMSON_PLANKS)) + assertTrue(ingredient.test(new ItemStack(plank))); + assertFalse(ingredient.test(new ItemStack(Items.STICK))); + assertFalse(ingredient.test(new ItemStack(Items.STONE))); + assertFalse(ingredient.test(ItemStack.EMPTY)); + } + + @Test public void nativeStringArrayMeansAlternativesWithoutAddingOtherItems() { + Ingredient ingredient = decode("[\"minecraft:string\",\"minecraft:feather\"]"); + assertTrue(ingredient.test(new ItemStack(Items.STRING))); + assertTrue(ingredient.test(new ItemStack(Items.FEATHER))); + assertFalse(ingredient.test(new ItemStack(Items.STONE))); + assertFalse(ingredient.test(ItemStack.EMPTY)); + } + + @Test public void legacyObjectsAndMalformedOrEmptySelectorsAreRejectedByTheNativeCodec() { + for (String json : List.of("{\"item\":\"minecraft:string\"}", "{\"tag\":\"minecraft:planks\"}", + "[{\"item\":\"minecraft:string\"}]", "{}", "[]", "null", "1", "\"#\"", + "\"INVALID:SPACE ID\"", "\"missing_mod:unregistered_item\"")) { + var result = Ingredient.CODEC.parse(NativeTestBootstrap.registries().createSerializationContext(JsonOps.INSTANCE), + JsonParser.parseString(json)); + assertTrue(json + " must produce a native codec error", result.error().isPresent()); + assertTrue(json + " cannot leave an accepted partial ingredient", result.result().isEmpty()); + } + } + + private static Ingredient decode(String json) { + return Ingredient.CODEC.parse(NativeTestBootstrap.registries().createSerializationContext(JsonOps.INSTANCE), + JsonParser.parseString(json)).getOrThrow(); + } +} diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/NativeItemAtlasContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/NativeItemAtlasContractTest.java new file mode 100644 index 0000000..94bc16e --- /dev/null +++ b/src/test/java/zone/moddev/mc/basesteam/resources/NativeItemAtlasContractTest.java @@ -0,0 +1,212 @@ +package zone.moddev.mc.basesteam.resources; + +import com.google.gson.JsonParser; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.JsonOps; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.util.HashMap; +import java.util.HashSet; +import java.util.HexFormat; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +import java.util.Set; +import net.minecraft.client.renderer.texture.atlas.SpriteSource; +import net.minecraft.client.renderer.texture.atlas.SpriteSourceList; +import net.minecraft.client.renderer.texture.atlas.SpriteSources; +import net.minecraft.client.renderer.texture.atlas.sources.DirectoryLister; +import net.minecraft.data.AtlasIds; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.server.packs.FilePackResources; +import net.minecraft.server.packs.PackLocationInfo; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.PathPackResources; +import net.minecraft.server.packs.repository.PackSource; +import net.minecraft.server.packs.resources.MultiPackResourceManager; +import net.minecraft.server.packs.resources.ResourceManager; +import org.junit.BeforeClass; +import org.junit.Test; +import zone.moddev.mc.basesteam.testing.NativeTestBootstrap; +import static org.junit.Assert.*; + +public class NativeItemAtlasContractTest { + @BeforeClass public static void bootstrap() { + NativeTestBootstrap.start(); + SpriteSources.bootstrap(); + } + + @Test public void pinnedNativeAtlasesResolveEveryModelWithoutBlockMaterialCollisions() throws Exception { + Properties pins = new Properties(); + try (var input = Files.newInputStream(Path.of("gradle.properties"))) { pins.load(input); } + Path power = pinnedPower(pins, System.getProperty("basesteam.nativePowerJar")); + var steam = new PathPackResources.PathResourcesSupplier(Path.of("src/main/resources")); + var parent = new FilePackResources.FileResourcesSupplier(power); + assertEquals(Identifier.withDefaultNamespace("items"), AtlasIds.ITEMS); + for (String directory : List.of("blocks", "materials")) { + assertThrows("Block/material directories must never be accepted by the item atlas", AssertionError.class, + () -> requireItemsOnly(List.of(new DirectoryLister(directory, directory + "/")))); + } + assertThrows("Even an items source must retain its exact sprite prefix", AssertionError.class, + () -> requireItemsOnly(List.of(new DirectoryLister("items", "")))); + try (var resources = new MultiPackResourceManager(PackType.CLIENT_RESOURCES, + List.of(parent.openPrimary(info("basepower")), steam.openPrimary(info("basesteam"))))) { + Set blockDirectories = new HashSet<>(); + Set itemDirectories = new HashSet<>(); + Set blockSprites = sprites(resources, AtlasIds.BLOCKS, blockDirectories); + Set itemSprites = sprites(resources, AtlasIds.ITEMS, itemDirectories); + Set blockTextures = textures(Path.of("src/main/resources/assets/basesteam/models/block")); + assertFalse("The block-model texture oracle must not be empty", blockTextures.isEmpty()); + Set duplicatedDirectories = new HashSet<>(blockDirectories); + duplicatedDirectories.retainAll(itemDirectories); + duplicatedDirectories.retainAll(Set.of("blocks", "materials")); + assertTrue("ITEMS must not shadow block/material directories: " + duplicatedDirectories, + duplicatedDirectories.isEmpty()); + Set collisions = new HashSet<>(blockSprites); + collisions.retainAll(itemSprites); + collisions.retainAll(blockTextures); + assertTrue("block_or_item must not resolve world-block textures through ITEMS: " + collisions, + collisions.isEmpty()); + Set expected = textures(Path.of("src/main/resources/assets/basesteam/models")); + assertFalse("Inspect the actual Steam model texture declarations", expected.isEmpty()); + assertTrue("The loaded musket retains its original barrel-tip material texture", + expected.contains(Identifier.parse("basesteam:materials/steel_dark_hole"))); + assertTrue("The shared Power textures must also be covered", expected.stream() + .anyMatch(id -> id.getNamespace().equals("basepower"))); + Set allSprites = new HashSet<>(blockSprites); + allSprites.addAll(itemSprites); + Set missing = new HashSet<>(expected); + missing.removeAll(allSprites); + assertTrue("Textures absent from both actual native atlas sources: " + missing, missing.isEmpty()); + assertTrue("BlockSimpleModelWrapper requires the block atlas", blockSprites.containsAll(blockTextures)); + assertTrue("Block model materials must not resolve through ITEMS", + java.util.Collections.disjoint(blockTextures, itemSprites)); + Set itemIcons = expected.stream().filter(id -> id.getPath().startsWith("items/")) + .collect(java.util.stream.Collectors.toSet()); + assertFalse("The legacy generated-item icon oracle must not be empty", itemIcons.isEmpty()); + assertTrue("Native item lookup uses ITEMS", itemSprites.containsAll(itemIcons)); + assertTrue("Legacy generated-item lookup retains BLOCKS compatibility", blockSprites.containsAll(itemIcons)); + assertTrue(blockSprites.contains(Identifier.parse("basesteam:entity/drill_bit"))); + assertFalse(itemSprites.contains(Identifier.parse("basesteam:entity/drill_bit"))); + assertFalse(allSprites.contains(Identifier.parse("basesteam:missing_atlas_negative_control"))); + for (Identifier id : expected) { + var file = id.withPrefix("textures/").withSuffix(".png"); + try (var input = resources.getResourceOrThrow(file).open()) { + assertArrayEquals(id.toString(), new byte[] {(byte)137, 80, 78, 71, 13, 10, 26, 10}, input.readNBytes(8)); + } + } + } + try (var resources = new MultiPackResourceManager(PackType.CLIENT_RESOURCES, + List.of(steam.openPrimary(info("basesteam"))))) { + assertEquals("Steam contributes one item-directory definition", 1, + resources.getResourceStack(Identifier.withDefaultNamespace("atlases/items.json")).size()); + assertTrue("Shared materials cannot exist without the required Power pack", resources.getResource( + Identifier.parse("basepower:textures/materials/wood.png")).isEmpty()); + assertEquals("Without Power, the native item loader offers three icons plus missing-texture fallback", 4, + SpriteSourceList.load(resources, AtlasIds.ITEMS).list(resources).size()); + Set available = new HashSet<>(sprites(resources, AtlasIds.BLOCKS, new HashSet<>())); + available.addAll(sprites(resources, AtlasIds.ITEMS, new HashSet<>())); + Set missing = textures(Path.of("src/main/resources/assets/basesteam/models")); + missing.removeAll(available); + assertTrue("Omitting Power must still expose the missing shared material", + missing.contains(Identifier.parse("basesteam:materials/steel_dark_hole"))); + } + } + + @Test public void missingVerifiedPowerPathIsRejected() { + assertThrows(AssertionError.class, () -> pinnedPower(new Properties(), null)); + } + + @Test public void wrongPowerBytesAreRejectedEvenInAPathWithSpaces() throws Exception { + Path directory = Files.createTempDirectory("baseSteam dependency with spaces "); + Path wrong = directory.resolve("basePower.jar"); + try { + Files.write(wrong, new byte[] {1, 2, 3}); + Properties pins = new Properties(); + pins.setProperty("poweradvantage_deobf_sha256", "0".repeat(64)); + assertThrows(AssertionError.class, () -> pinnedPower(pins, wrong.toString())); + } finally { + Files.deleteIfExists(wrong); + Files.deleteIfExists(directory); + } + } + + private static Path pinnedPower(Properties pins, String configured) throws Exception { + assertNotNull("Gradle must supply its verified Power dependency to the atlas test", configured); + Path power = Path.of(configured).toAbsolutePath().normalize(); + assertTrue("Missing verified Power dependency: " + power, Files.isRegularFile(power)); + assertEquals(pins.getProperty("poweradvantage_deobf_sha256"), HexFormat.of().withUpperCase() + .formatHex(MessageDigest.getInstance("SHA-256").digest(Files.readAllBytes(power)))); + return power; + } + + private static Set sprites(ResourceManager resources, Identifier atlas, Set directories) + throws Exception { + var definitions = resources.getResourceStack(atlas.withPrefix("atlases/").withSuffix(".json")); + assertFalse("Missing native atlas " + atlas, definitions.isEmpty()); + Map found = new HashMap<>(); + SpriteSource.Output output = new SpriteSource.Output() { + @Override public void add(Identifier id, SpriteSource.DiscardableLoader loader) { + var replaced = found.put(id, loader); + if (replaced != null) replaced.discard(); + } + @Override public void removeAll(java.util.function.Predicate filter) { + found.entrySet().removeIf(entry -> { + if (!filter.test(entry.getKey())) return false; + entry.getValue().discard(); + return true; + }); + } + }; + try { + for (var definition : definitions) { + try (var reader = definition.openAsReader()) { + var sources = SpriteSources.FILE_CODEC.parse( + new Dynamic<>(JsonOps.INSTANCE, JsonParser.parseReader(reader))).getOrThrow(); + if (atlas.equals(AtlasIds.ITEMS)) requireItemsOnly(sources); + for (SpriteSource source : sources) { + if (source instanceof DirectoryLister directory) { + assertEquals(directory.sourcePath() + "/", directory.idPrefix()); + directories.add(directory.sourcePath()); + } + source.run(resources, output); + } + } + } + return new HashSet<>(found.keySet()); + } finally { + found.values().forEach(SpriteSource.DiscardableLoader::discard); + } + } + + private static void requireItemsOnly(List sources) { + assertEquals("Only the native items directory belongs in this atlas", 1, sources.size()); + assertTrue("Single block/material sprites must not shadow the block atlas", sources.getFirst() instanceof DirectoryLister); + DirectoryLister source = (DirectoryLister)sources.getFirst(); + assertEquals("items", source.sourcePath()); + assertEquals("items/", source.idPrefix()); + } + + private static Set textures(Path root) throws IOException { + Set result = new HashSet<>(); + try (var files = Files.walk(root)) { + for (Path file : files.filter(path -> path.toString().endsWith(".json")).toList()) { + var model = JsonParser.parseString(Files.readString(file)).getAsJsonObject(); + if (!model.has("textures")) continue; + for (var texture : model.getAsJsonObject("textures").entrySet()) { + String id = texture.getValue().getAsString(); + if (!id.startsWith("#")) result.add(Identifier.parse(id)); + } + } + } + return result; + } + + private static PackLocationInfo info(String mod) { + return new PackLocationInfo("mod:" + mod, Component.literal(mod), PackSource.DEFAULT, Optional.empty()); + } +} diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/NativeItemDefinitionContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/NativeItemDefinitionContractTest.java new file mode 100644 index 0000000..9e91ad4 --- /dev/null +++ b/src/test/java/zone/moddev/mc/basesteam/resources/NativeItemDefinitionContractTest.java @@ -0,0 +1,65 @@ +package zone.moddev.mc.basesteam.resources; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.serialization.JsonOps; +import java.nio.file.Files; +import java.nio.file.Path; +import net.minecraft.client.renderer.item.ClientItem; +import net.minecraft.client.renderer.item.ItemModels; +import net.minecraft.client.renderer.item.properties.conditional.ComponentMatches; +import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperties; +import net.minecraft.core.component.DataComponents; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.component.CustomData; +import org.junit.BeforeClass; +import org.junit.Test; +import zone.moddev.mc.basesteam.testing.NativeTestBootstrap; +import static org.junit.Assert.*; + +public class NativeItemDefinitionContractTest { + private static final Path ROOT = Path.of("src/main/resources/assets/basesteam/items"); + + @BeforeClass public static void bootstrap() { + NativeTestBootstrap.start(); + ItemModels.bootstrap(); + ConditionalItemModelProperties.bootstrap(); + } + + @Test public void allTwentyOneDefinitionsDecodeWithTheNativeClientCodec() throws Exception { + try (var files = Files.list(ROOT)) { + var definitions = files.filter(path -> path.toString().endsWith(".json")).toList(); + assertEquals(21, definitions.size()); + for (Path path : definitions) { + ClientItem item = ClientItem.CODEC.parse(JsonOps.INSTANCE, + JsonParser.parseString(Files.readString(path))).getOrThrow(); + assertNotNull(path.toString(), item.model()); + } + } + } + + @Test public void loadedPredicateUsesTheOriginalByteAndNeverChangesCustomData() throws Exception { + JsonObject model = JsonParser.parseString(Files.readString(ROOT.resolve("musket.json"))) + .getAsJsonObject().getAsJsonObject("model"); + assertEquals("basesteam:item/musket_loaded", model.getAsJsonObject("on_true").get("model").getAsString()); + var loading = model.getAsJsonObject("on_false"); + assertEquals("minecraft:using_item", loading.get("property").getAsString()); + assertEquals("basesteam:item/musket_loading", loading.getAsJsonObject("on_true").get("model").getAsString()); + assertEquals("basesteam:item/musket_unloaded", loading.getAsJsonObject("on_false").get("model").getAsString()); + var predicate = ComponentMatches.MAP_CODEC.codec().parse(JsonOps.INSTANCE, model).getOrThrow(); + ItemStack stack = new ItemStack(Items.STICK); + assertFalse(predicate.get(stack, null, null, 0, ItemDisplayContext.GUI)); + for (byte state : new byte[] {0, 1, 2, -1, 127}) { + CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> { + tag.putByte("loaded", state); + tag.putString("owner", "preserved"); + }); + CustomData before = stack.get(DataComponents.CUSTOM_DATA); + assertEquals(state == 1, predicate.get(stack, null, null, 0, ItemDisplayContext.GUI)); + assertSame(before, stack.get(DataComponents.CUSTOM_DATA)); + assertEquals("preserved", before.copyTag().getStringOr("owner", "")); + } + } +} diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/NativeLootContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/NativeLootContractTest.java index a0cbb18..f3abf36 100644 --- a/src/test/java/zone/moddev/mc/basesteam/resources/NativeLootContractTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/resources/NativeLootContractTest.java @@ -39,12 +39,12 @@ public class NativeLootContractTest { } } - @Test public void packUsesNative1211ResourceAndDataFormats() throws Exception { + @Test public void packUsesNative12111ResourceAndDataFormats() throws Exception { JsonObject pack = read(ROOT.resolve("pack.mcmeta")).getAsJsonObject("pack"); - assertEquals(34, pack.get("pack_format").getAsInt()); - JsonObject supported = pack.getAsJsonObject("supported_formats"); - assertEquals(34, supported.get("min_inclusive").getAsInt()); - assertEquals(48, supported.get("max_inclusive").getAsInt()); + assertEquals("[94,1]", pack.getAsJsonArray("min_format").toString()); + assertEquals("[94,1]", pack.getAsJsonArray("max_format").toString()); + assertFalse(pack.has("pack_format")); + assertFalse(pack.has("supported_formats")); assertFalse(pack.has("forge:server_data_pack_format")); assertFalse(pack.has("forge:resource_pack_format")); assertFalse(pack.has("forge:data_pack_format")); diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/NativePackFormatContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/NativePackFormatContractTest.java new file mode 100644 index 0000000..5d6db52 --- /dev/null +++ b/src/test/java/zone/moddev/mc/basesteam/resources/NativePackFormatContractTest.java @@ -0,0 +1,53 @@ +package zone.moddev.mc.basesteam.resources; + +import com.google.gson.JsonParser; +import com.mojang.serialization.JsonOps; +import java.nio.file.Files; +import java.nio.file.Path; +import net.minecraft.SharedConstants; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.metadata.pack.PackFormat; +import net.minecraft.server.packs.metadata.pack.PackMetadataSection; +import org.junit.Test; +import zone.moddev.mc.basesteam.testing.NativePackResourceProof; +import static org.junit.Assert.*; + +public class NativePackFormatContractTest { + @org.junit.BeforeClass public static void bootstrap() { + zone.moddev.mc.basesteam.testing.NativeTestBootstrap.start(); + } + + @Test public void nativeClientAndServerMetadataAcceptTheSameCombinedPack() throws Exception { + var proof = NativePackResourceProof.read(); + assertEquals(0, proof.getAsJsonArray("warningsAndErrors").size()); + var clientProof = proof.getAsJsonObject("CLIENT_RESOURCES"); + var serverProof = proof.getAsJsonObject("SERVER_DATA"); + assertEquals("mod_resources", clientProof.get("id").getAsString()); + assertEquals("net.minecraftforge.resource.DelegatingPackResources", clientProof.get("resourcesClass").getAsString()); + assertEquals("mod:basesteam", serverProof.get("id").getAsString()); + assertEquals("net.minecraft.server.packs.PathPackResources", serverProof.get("resourcesClass").getAsString()); + assertEquals("COMPATIBLE", clientProof.get("currentCompatibility").getAsString()); + assertEquals("COMPATIBLE", serverProof.get("currentCompatibility").getAsString()); + assertTrue(clientProof.get("resourcesVerified").getAsInt() > 0); + assertTrue(serverProof.get("resourcesVerified").getAsInt() > 0); + var pack = JsonParser.parseString(Files.readString(Path.of("src/main/resources/pack.mcmeta"))) + .getAsJsonObject().getAsJsonObject("pack"); + assertFalse(pack.has("pack_format")); + assertFalse(pack.has("supported_formats")); + var client = PackMetadataSection.forPackType(PackType.CLIENT_RESOURCES).codec() + .parse(JsonOps.INSTANCE, clientProof.get("nativeMetadata")).getOrThrow(); + var server = PackMetadataSection.forPackType(PackType.SERVER_DATA).codec() + .parse(JsonOps.INSTANCE, serverProof.get("nativeMetadata")).getOrThrow(); + assertEquals(server, PackMetadataSection.forPackType(PackType.SERVER_DATA).codec() + .parse(JsonOps.INSTANCE, pack).getOrThrow()); + var version = SharedConstants.getCurrentVersion(); + assertEquals(PackFormat.of(75, 0), version.packVersion(PackType.CLIENT_RESOURCES)); + assertEquals(PackFormat.of(94, 1), version.packVersion(PackType.SERVER_DATA)); + assertTrue(client.supportedFormats().isValueInRange(version.packVersion(PackType.CLIENT_RESOURCES))); + assertTrue(server.supportedFormats().isValueInRange(version.packVersion(PackType.SERVER_DATA))); + assertFalse(client.supportedFormats().isValueInRange(PackFormat.of(74))); + assertFalse(server.supportedFormats().isValueInRange(PackFormat.of(95))); + assertFalse(client.supportedFormats().isValueInRange(PackFormat.of(94, 1))); + assertFalse(server.supportedFormats().isValueInRange(PackFormat.of(75, 0))); + } +} diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/NativePackRangeContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/NativePackRangeContractTest.java new file mode 100644 index 0000000..3440153 --- /dev/null +++ b/src/test/java/zone/moddev/mc/basesteam/resources/NativePackRangeContractTest.java @@ -0,0 +1,56 @@ +package zone.moddev.mc.basesteam.resources; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.serialization.JsonOps; +import java.nio.file.Files; +import java.nio.file.Path; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.metadata.pack.PackFormat; +import org.junit.Test; +import zone.moddev.mc.basesteam.testing.NativePackResourceProof; +import static org.junit.Assert.*; + +public class NativePackRangeContractTest { + @Test public void combinedPackRangeAcceptsBothNativePackTypes() throws Exception { + var proof = NativePackResourceProof.read(); + assertEquals(0, proof.getAsJsonArray("warningsAndErrors").size()); + for (PackType type : PackType.values()) { + var side = proof.getAsJsonObject(type.name()); + JsonObject pack = side.getAsJsonObject("nativeMetadata"); + var range = PackFormat.packCodec(type).codec().parse(JsonOps.INSTANCE, pack).getOrThrow(); + assertEquals(type.toString(), type == PackType.CLIENT_RESOURCES, + range.isValueInRange(PackFormat.of(75, 0))); + assertEquals(type.toString(), type == PackType.SERVER_DATA, + range.isValueInRange(PackFormat.of(94, 1))); + assertFalse(range.isValueInRange(PackFormat.of(74))); + assertFalse(range.isValueInRange(PackFormat.of(95))); + } + } + + @Test public void omissionDoesNotSynthesizeMetadataInTheNativeModPathSupplier() throws Exception { + var root = Path.of("build/native-pack-resources").toAbsolutePath(); + Files.createDirectories(root); + var omitted = Files.createTempDirectory(root, "missing-metadata-"); + var info = new net.minecraft.server.packs.PackLocationInfo("mod:basesteam", + net.minecraft.network.chat.Component.literal("baseSteam"), + net.minecraft.server.packs.repository.PackSource.DEFAULT, java.util.Optional.empty()); + var supplier = new net.minecraft.server.packs.PathPackResources.PathResourcesSupplier(omitted); + try (var resources = supplier.openPrimary(info)) { + assertNull(resources.getRootResource("pack.mcmeta")); + for (PackType type : PackType.values()) { + assertNull(type.toString(), resources.getMetadataSection( + net.minecraft.server.packs.metadata.pack.PackMetadataSection.forPackType(type))); + } + } + } + + @Test public void dataOnlyRangeDoesNotQualifyClientResources() { + JsonObject dataOnly = JsonParser.parseString("{\"min_format\":[94,1],\"max_format\":[94,1]}") + .getAsJsonObject(); + var range = PackFormat.packCodec(PackType.CLIENT_RESOURCES).codec() + .parse(JsonOps.INSTANCE, dataOnly).getOrThrow(); + assertFalse(range.isValueInRange(PackFormat.of(75, 0))); + assertTrue(range.isValueInRange(PackFormat.of(94, 1))); + } +} diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/NativeRequiredDependencyContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/NativeRequiredDependencyContractTest.java new file mode 100644 index 0000000..d9cae1d --- /dev/null +++ b/src/test/java/zone/moddev/mc/basesteam/resources/NativeRequiredDependencyContractTest.java @@ -0,0 +1,105 @@ +package zone.moddev.mc.basesteam.resources; + +import cpw.mods.jarhandling.SecureJar; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import net.minecraftforge.fml.loading.moddiscovery.ModFile; +import net.minecraftforge.fml.loading.moddiscovery.ModFileParser; +import net.minecraftforge.fml.loading.moddiscovery.ModInfo; +import net.minecraftforge.fml.loading.targets.ForgeDevLocator; +import net.minecraftforge.forgespi.language.IModInfo; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.junit.Test; +import static org.junit.Assert.*; + +public class NativeRequiredDependencyContractTest { + @Test public void processedMetadataRequiresBaseMetalsThroughActualForgeModInfo() throws Exception { + requireBaseMetals(parse(Files.readString(Path.of("build/resources/main/META-INF/mods.toml")))); + } + + @Test public void nativeParserDistinguishesRequiredAndOptionalDependencyFlags() throws Exception { + assertFalse(baseMetals(parse(fixture(dependency(false)))).isMandatory()); + assertTrue(baseMetals(parse(fixture(dependency(true)))).isMandatory()); + } + + @Test public void requiredContractRejectsOptionalMissingAndDuplicateDependencies() throws Exception { + for (String declarations : new String[] {dependency(false), "", dependency(true) + dependency(true)}) { + ModInfo parsed = parse(fixture(declarations)); + assertThrows("Missing, optional or duplicated Base Metals cannot satisfy the contract", + AssertionError.class, () -> requireBaseMetals(parsed)); + } + } + + public static void main(String[] args) throws Exception { + if (args.length != 1) throw new IllegalArgumentException("Expected the actual candidate jar path"); + requireBaseMetals(parseJar(Path.of(args[0]))); + System.out.println("NATIVE_REQUIRED_BASEMETALS_ARTIFACT_PASS"); + } + + private static void requireBaseMetals(ModInfo info) { + IModInfo.ModVersion dependency = baseMetals(info); + assertTrue("Base Metals is REQUIRED, not an optional installation scope", dependency.isMandatory()); + assertEquals(IModInfo.Ordering.AFTER, dependency.getOrdering()); + assertEquals(IModInfo.DependencySide.BOTH, dependency.getSide()); + assertEquals("[3.1.2.121111,4.0.0)", dependency.getVersionRange().toString()); + assertTrue(dependency.getVersionRange().containsVersion(new DefaultArtifactVersion("3.1.2.121111"))); + assertFalse(dependency.getVersionRange().containsVersion(new DefaultArtifactVersion("3.1.1.121111"))); + assertFalse(dependency.getVersionRange().containsVersion(new DefaultArtifactVersion("4.0.0"))); + } + + private static IModInfo.ModVersion baseMetals(ModInfo info) { + var dependencies = info.getDependencies().stream().filter(item -> item.getModId().equals("basemetals")).toList(); + assertEquals("Declare exactly one Base Metals dependency", 1, dependencies.size()); + return dependencies.getFirst(); + } + + private static ModInfo parse(String metadata) throws Exception { + Path root = Path.of("build/native-required-dependency-metadata"); + Files.createDirectories(root); + Path jar = Files.createTempDirectory(root, "forge61-").resolve("metadata.jar"); + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + manifest.getMainAttributes().putValue("Automatic-Module-Name", "basesteam"); + try (JarOutputStream output = new JarOutputStream(Files.newOutputStream(jar), manifest)) { + output.putNextEntry(new JarEntry("META-INF/mods.toml")); + output.write(metadata.getBytes(StandardCharsets.UTF_8)); + output.closeEntry(); + } + return parseJar(jar); + } + + private static ModInfo parseJar(Path jar) { + ModFile mod = new ModFile(SecureJar.from(jar), new ForgeDevLocator(), ModFileParser::modsTomlParser); + assertEquals(1, mod.getModInfos().size()); + assertEquals("basesteam", mod.getModInfos().getFirst().getModId()); + return (ModInfo)mod.getModInfos().getFirst(); + } + + private static String fixture(String dependency) { + return """ + modLoader="javafml" + loaderVersion="[61,62)" + license="GPL-3.0-only" + [[mods]] + modId="basesteam" + version="2.2.16.121111" + displayName="baseSteam" + """ + dependency; + } + + private static String dependency(boolean required) { + return """ + [[dependencies.basesteam]] + modId="basemetals" + mandatory=%s + versionRange="[3.1.2.121111,4.0.0)" + ordering="AFTER" + side="BOTH" + """.formatted(required); + } +} diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/OptionalCrushingRecipeContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/OptionalCrushingRecipeContractTest.java index 48dc5fb..124a65a 100644 --- a/src/test/java/zone/moddev/mc/basesteam/resources/OptionalCrushingRecipeContractTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/resources/OptionalCrushingRecipeContractTest.java @@ -91,7 +91,7 @@ private static boolean baseGuard(JsonElement value) { assertFalse(condition, baseGuard(json(condition))); } - @Test public void nativeForge52CodecRetainsTheExactProviderIdentity() { + @Test public void nativeForge61CodecRetainsTheExactProviderIdentity() { JsonObject guard = json("{\"type\":\"forge:mod_loaded\",\"modid\":\"basemetals\"}"); ModLoadedCondition condition = ModLoadedCondition.CODEC.codec().parse(JsonOps.INSTANCE, guard).getOrThrow(); assertSame(ModLoadedCondition.CODEC, condition.codec()); @@ -106,9 +106,8 @@ private static boolean baseGuard(JsonElement value) { for (String machine : MACHINES) { JsonObject recipe = recipes.get("crushing_" + machine + ".json"); assertEquals("Only the original payload plus conditions is expected", 4, recipe.size()); - JsonObject ingredient = recipe.getAsJsonObject("ingredient"); - assertEquals(1, ingredient.size()); - assertEquals("basesteam:" + machine, ingredient.get("item").getAsString()); + assertTrue(recipe.get("ingredient").isJsonPrimitive()); + assertEquals("basesteam:" + machine, recipe.get("ingredient").getAsString()); JsonObject output = recipe.getAsJsonObject("result"); assertEquals(machine.equals("steam_tank") ? 1 : 2, output.size()); assertEquals(machine.equals("steam_governor") ? "basepower:sprocket" diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/RecipeRegistrationThreadTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/RecipeRegistrationThreadTest.java index d7b0cd9..7a80ce3 100644 --- a/src/test/java/zone/moddev/mc/basesteam/resources/RecipeRegistrationThreadTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/resources/RecipeRegistrationThreadTest.java @@ -17,7 +17,7 @@ public class RecipeRegistrationThreadTest { assertEquals(1L, mod.methods.stream().filter(method -> method.name.equals("")) .flatMap(RecipeRegistrationThreadTest::calls) .filter(call -> call.owner.equals(CONDITIONS) && call.name.equals("register") - && call.desc.equals("(Lnet/minecraftforge/eventbus/api/IEventBus;)V")) + && call.desc.equals("(Lnet/minecraftforge/eventbus/api/bus/BusGroup;)V")) .count()); assertFalse(mod.methods.stream().filter(method -> method.name.equals("commonSetup")) .flatMap(RecipeRegistrationThreadTest::calls).anyMatch(call -> call.owner.equals(CONDITIONS))); diff --git a/src/test/java/zone/moddev/mc/basesteam/resources/RecipeUnlockContractTest.java b/src/test/java/zone/moddev/mc/basesteam/resources/RecipeUnlockContractTest.java index d098e14..7781808 100644 --- a/src/test/java/zone/moddev/mc/basesteam/resources/RecipeUnlockContractTest.java +++ b/src/test/java/zone/moddev/mc/basesteam/resources/RecipeUnlockContractTest.java @@ -19,7 +19,7 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import net.minecraft.advancements.critereon.ItemPredicate; +import net.minecraft.advancements.criterion.ItemPredicate; import net.minecraft.world.item.Items; import org.junit.BeforeClass; import org.junit.Test; @@ -65,14 +65,14 @@ public class RecipeUnlockContractTest { assertEquals("Only an item or tag predicate is expected", 1, predicate.size()); actual.add(predicateKey(predicate)); } - List choices = new ArrayList<>(); + List choices = new ArrayList<>(); if (recipe.has("key")) { for (Map.Entry entry : recipe.getAsJsonObject("key").entrySet()) collect(entry.getValue(), choices); } else collect(recipe.has("ingredients") ? recipe.get("ingredients") : recipe.get("ingredient"), choices); Set expected = choices.stream().map(RecipeUnlockContractTest::ingredientKey) .filter(key -> !IGNORED.contains(key)).collect(Collectors.toCollection(LinkedHashSet::new)); - if (expected.isEmpty()) for (JsonObject choice : choices) expected.add(ingredientKey(choice)); + if (expected.isEmpty()) for (JsonElement choice : choices) expected.add(ingredientKey(choice)); assertEquals("Trigger must correspond to a relevant recipe ingredient: " + file, expected, actual); JsonArray requirements = unlock.getAsJsonArray("requirements"); assertEquals("All criteria must form one OR group", 1, requirements.size()); @@ -110,16 +110,16 @@ public class RecipeUnlockContractTest { } @Test public void nativeSingletonPredicatesKeepRecipeIngredientSemantics() { - JsonObject ingredient = new JsonParser().parse("{\"item\":\"minecraft:string\"}").getAsJsonObject(); + JsonElement ingredient = new com.google.gson.JsonPrimitive("minecraft:string"); JsonObject predicate = new JsonParser().parse("{\"items\":[\"minecraft:string\"]}").getAsJsonObject(); assertEquals("item:minecraft:string", predicateKey(predicate)); assertEquals(ingredientKey(ingredient), predicateKey(predicate)); - JsonObject tag = new JsonParser().parse("{\"tag\":\"minecraft:planks\"}").getAsJsonObject(); + JsonElement tag = new com.google.gson.JsonPrimitive("#minecraft:planks"); JsonObject nativeTag = new JsonParser().parse("{\"items\":\"#minecraft:planks\"}").getAsJsonObject(); assertEquals("tag:minecraft:planks", predicateKey(nativeTag)); assertEquals(ingredientKey(tag), predicateKey(nativeTag)); assertThrows("Legacy item predicates must not compare equal to native predicates", - AssertionError.class, () -> predicateKey(ingredient)); + AssertionError.class, () -> predicateKey(new JsonParser().parse("{\"item\":\"minecraft:string\"}").getAsJsonObject())); } @Test public void malformedOrBroadItemPredicatesAreRejected() { @@ -188,18 +188,16 @@ private static String predicateKey(JsonObject predicate) { return "item:" + item.getAsString(); } - private static String ingredientKey(JsonObject ingredient) { - assertEquals("Only one recipe item or tag ingredient is expected", 1, ingredient.size()); - String type = ingredient.has("tag") ? "tag" : "item"; - assertTrue("Recipe ingredients retain singular item or tag syntax", ingredient.has(type)); - JsonElement value = ingredient.get(type); - assertTrue("Recipe ingredient must contain a string ID", value.isJsonPrimitive() && value.getAsJsonPrimitive().isString()); - return type + ":" + value.getAsString(); + private static String ingredientKey(JsonElement ingredient) { + assertTrue("Native recipe ingredient must contain a string ID", + ingredient.isJsonPrimitive() && ingredient.getAsJsonPrimitive().isString()); + String value = ingredient.getAsString(); + return value.startsWith("#") ? "tag:" + value.substring(1) : "item:" + value; } - private static void collect(JsonElement element, List result) { + private static void collect(JsonElement element, List result) { if (element.isJsonArray()) for (JsonElement child : element.getAsJsonArray()) collect(child, result); - else result.add(element.getAsJsonObject()); + else result.add(element); } private static JsonObject read(Path file) throws IOException { diff --git a/src/test/java/zone/moddev/mc/basesteam/testing/EclipseUnitLauncher.java b/src/test/java/zone/moddev/mc/basesteam/testing/EclipseUnitLauncher.java new file mode 100644 index 0000000..8f3490a --- /dev/null +++ b/src/test/java/zone/moddev/mc/basesteam/testing/EclipseUnitLauncher.java @@ -0,0 +1,43 @@ +package zone.moddev.mc.basesteam.testing; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.runner.JUnitCore; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunListener; + +/** Emits exact JUnit counts for the separately compiled Eclipse test scope. */ +public final class EclipseUnitLauncher { + private EclipseUnitLauncher() {} + + public static void main(String[] arguments) throws Exception { + if (arguments.length < 2) throw new IllegalArgumentException("Receipt path and test classes required"); + Class[] tests = new Class[arguments.length - 1]; + for (int index = 1; index < arguments.length; index++) tests[index - 1] = Class.forName(arguments[index]); + AtomicInteger assumptions = new AtomicInteger(); + JUnitCore core = new JUnitCore(); + core.addListener(new RunListener() { + @Override public void testAssumptionFailure(Failure failure) { assumptions.incrementAndGet(); } + @Override public void testFailure(Failure failure) { System.err.println(failure.getTrace()); } + }); + var result = core.run(tests); + JsonObject receipt = new JsonObject(); + receipt.add("classes", new com.google.gson.Gson().toJsonTree(Arrays.copyOfRange(arguments, 1, arguments.length))); + receipt.addProperty("tests", result.getRunCount()); + receipt.addProperty("failures", result.getFailureCount()); + receipt.addProperty("ignored", result.getIgnoreCount()); + receipt.addProperty("assumptions", assumptions.get()); + receipt.addProperty("runtimeMilliseconds", result.getRunTime()); + boolean passed = result.wasSuccessful() && result.getRunCount() > 0 + && result.getIgnoreCount() == 0 && assumptions.get() == 0; + receipt.addProperty("passed", passed); + Files.writeString(Path.of(arguments[0]), new GsonBuilder().setPrettyPrinting().create().toJson(receipt) + "\n"); + System.out.println("ECJ_JUNIT tests=" + result.getRunCount() + " failures=" + result.getFailureCount() + + " ignored=" + result.getIgnoreCount() + " assumptions=" + assumptions.get()); + if (!passed) System.exit(1); + } +} diff --git a/src/test/java/zone/moddev/mc/basesteam/testing/NativeMachineFixture.java b/src/test/java/zone/moddev/mc/basesteam/testing/NativeMachineFixture.java index 64843b3..4690e2d 100644 --- a/src/test/java/zone/moddev/mc/basesteam/testing/NativeMachineFixture.java +++ b/src/test/java/zone/moddev/mc/basesteam/testing/NativeMachineFixture.java @@ -5,7 +5,7 @@ import java.util.function.Consumer; import java.util.function.Supplier; import net.minecraft.core.BlockPos; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; @@ -98,12 +98,12 @@ private Fixture(String id, Block block, BlockEntityType.BlockEntitySupplier f this.block = block; this.getter = getter; this.setter = setter; - type = BlockEntityType.Builder.of(factory, block).build(null); + type = new BlockEntityType<>(factory, java.util.Set.of(block)); ForgeRegistry> registry = (ForgeRegistry>) ForgeRegistries.BLOCK_ENTITY_TYPES; boolean locked = registry.isLocked(); registry.unfreeze(); try { - registry.register(ResourceLocation.fromNamespaceAndPath("basesteam", id), type); + registry.register(Identifier.fromNamespaceAndPath("basesteam", id), type); } finally { if (locked) registry.freeze(); } diff --git a/src/test/java/zone/moddev/mc/basesteam/testing/NativePackResourceProof.java b/src/test/java/zone/moddev/mc/basesteam/testing/NativePackResourceProof.java new file mode 100644 index 0000000..98d5909 --- /dev/null +++ b/src/test/java/zone/moddev/mc/basesteam/testing/NativePackResourceProof.java @@ -0,0 +1,198 @@ +package zone.moddev.mc.basesteam.testing; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import cpw.mods.jarhandling.SecureJar; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import net.minecraft.SharedConstants; +import net.minecraft.server.Bootstrap; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.PathPackResources; +import net.minecraft.server.packs.metadata.pack.PackMetadataSection; +import net.minecraft.server.packs.repository.Pack; +import net.minecraft.server.packs.repository.PackRepository; +import net.minecraft.server.packs.resources.MultiPackResourceManager; +import net.minecraft.resources.Identifier; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.loading.moddiscovery.ModFile; +import net.minecraftforge.fml.loading.moddiscovery.ModFileParser; +import net.minecraftforge.fml.loading.moddiscovery.ModInfo; +import net.minecraftforge.fml.loading.targets.ForgeDevLocator; +import net.minecraftforge.resource.ResourcePackLoader; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.layout.PatternLayout; + +/** Runs real Forge discovery in a child VM so ModList and native registry state cannot leak into other tests. */ +public final class NativePackResourceProof { + private static JsonObject receipt; + + private NativePackResourceProof() {} + + public static synchronized JsonObject read() throws Exception { + if (receipt != null) return receipt.deepCopy(); + Path evidenceRoot = Path.of("build/native-pack-resources").toAbsolutePath(); + Files.createDirectories(evidenceRoot); + Path evidence = Files.createTempDirectory(evidenceRoot, "forge61-"); + String classpath = Arrays.stream(System.getProperty("basesteam.nativePackClasspath", + System.getProperty("java.class.path")).split(java.util.regex.Pattern.quote(java.io.File.pathSeparator))) + .map(entry -> new java.io.File(entry).getAbsolutePath()) + .collect(java.util.stream.Collectors.joining(java.io.File.pathSeparator)); + Path javaExecutable = Path.of(System.getProperty("java.home"), "bin", "java"); + if (!Files.isExecutable(javaExecutable)) javaExecutable = javaExecutable.resolveSibling("java.exe"); + Path log = evidence.resolve("forge-resources.log"); + ProcessBuilder builder = new ProcessBuilder(javaExecutable.toString(), "-XX:ActiveProcessorCount=4", "-Xmx512m", + "-Dterminal.jline=false", + "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED", "-cp", classpath, + NativePackResourceProof.class.getName(), Path.of("build/resources/main").toAbsolutePath().toString(), + evidence.toString()).directory(evidence.toFile()) + .redirectInput(ProcessBuilder.Redirect.PIPE).redirectErrorStream(true).redirectOutput(log.toFile()); + builder.environment().put("TERM", "dumb"); + Process process = builder.start(); + if (!process.waitFor(90, TimeUnit.SECONDS)) { + process.destroyForcibly().waitFor(); + throw new AssertionError("Native Forge resource proof timed out: " + log); + } + String output = Files.readString(log); + if (process.exitValue() != 0) throw new AssertionError("Native Forge resource proof failed: " + log + "\n" + output); + requireCleanOutput(output, log); + receipt = JsonParser.parseString(Files.readString(evidence.resolve("receipt.json"))).getAsJsonObject(); + return receipt.deepCopy(); + } + + static void requireCleanOutput(String output, Path log) { + if (java.util.regex.Pattern.compile("(?m)(?:\\[[^\\r\\n]*\\b(?:WARN|ERROR|FATAL)\\b[^\\r\\n]*\\]|\\bmain (?:WARN|ERROR|FATAL)\\b)") + .matcher(output).find()) { + throw new AssertionError("Native Forge resource diagnostic in " + log + "\n" + output); + } + } + + public static void main(String[] args) throws Exception { + SharedConstants.tryDetectVersion(); + Bootstrap.bootStrap(); + Path resources = Path.of(args[0]); + Path evidence = Path.of(args[1]); + Path jar = evidence.resolve("basesteam-resource-contract.jar"); + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + manifest.getMainAttributes().putValue("Automatic-Module-Name", "basesteam"); + try (JarOutputStream output = new JarOutputStream(Files.newOutputStream(jar), manifest); + var files = Files.walk(resources)) { + for (Path file : files.filter(Files::isRegularFile).sorted().toList()) { + output.putNextEntry(new JarEntry(resources.relativize(file).toString().replace('\\', '/'))); + Files.copy(file, output); + output.closeEntry(); + } + } + ModFile mod = new ModFile(SecureJar.from(jar), new ForgeDevLocator(), ModFileParser::modsTomlParser); + if (mod.getModFileInfo().showAsResourcePack()) throw new AssertionError("Steam must use Forge's hidden client pack"); + ModList.of(List.of(mod), mod.getModInfos().stream().map(ModInfo.class::cast).toList()); + List diagnostics = new ArrayList<>(); + AbstractAppender appender = new AbstractAppender("SteamPackContract", null, + PatternLayout.createDefaultLayout(), false, Property.EMPTY_ARRAY) { + @Override public void append(LogEvent event) { + if (event.getLevel().isMoreSpecificThan(Level.WARN)) diagnostics.add(event.getMessage().getFormattedMessage()); + } + }; + appender.start(); + var logger = (org.apache.logging.log4j.core.Logger)LogManager.getRootLogger(); + logger.addAppender(appender); + JsonObject result = new JsonObject(); + JsonObject child = new JsonObject(); + child.addProperty("terminalJline", System.getProperty("terminal.jline")); + child.addProperty("term", System.getenv("TERM")); + child.addProperty("consoleAttached", System.console() != null); + child.addProperty("terminalCreated", net.minecrell.terminalconsole.TerminalConsoleAppender.getTerminal() != null); + result.add("childProcess", child); + try { + for (PackType type : PackType.values()) { + PackRepository repository = new PackRepository(); + ResourcePackLoader.loadResourcePacks(repository, type == PackType.CLIENT_RESOURCES); + repository.reload(); + String id = type == PackType.CLIENT_RESOURCES ? "mod_resources" : "mod:basesteam"; + if (repository.getAvailableIds().size() != 1 || !repository.isAvailable(id)) { + throw new AssertionError("Unexpected native packs: " + repository.getAvailableIds()); + } + Pack pack = repository.getPack(id); + if (pack == null) throw new AssertionError("Forge did not discover " + id); + JsonObject side = new JsonObject(); + side.addProperty("id", id); + side.addProperty("loaderCompatibility", pack.getCompatibility().name()); + try (var opened = pack.open()) { + PackMetadataSection metadata = opened.getMetadataSection(PackMetadataSection.forPackType(type)); + if (metadata == null) throw new AssertionError("Missing native " + type + " metadata"); + var formats = metadata.supportedFormats(); + var codec = PackMetadataSection.forPackType(type).codec(); + var encoded = codec.encodeStart(com.mojang.serialization.JsonOps.INSTANCE, metadata).getOrThrow(); + var decoded = codec.parse(com.mojang.serialization.JsonOps.INSTANCE, encoded).getOrThrow(); + // Native JSON widens numeric translation arguments; compare the wire value and exact range. + if (!formats.equals(decoded.supportedFormats()) + || !encoded.equals(codec.encodeStart(com.mojang.serialization.JsonOps.INSTANCE, decoded).getOrThrow())) { + throw new AssertionError("Native metadata round-trip changed " + type); + } + side.addProperty("resourcesClass", opened.getClass().getName()); + side.addProperty("acceptsClient", formats.isValueInRange(SharedConstants.getCurrentVersion().packVersion(PackType.CLIENT_RESOURCES))); + side.addProperty("acceptsServer", formats.isValueInRange(SharedConstants.getCurrentVersion().packVersion(PackType.SERVER_DATA))); + side.addProperty("accepts74", formats.isValueInRange(net.minecraft.server.packs.metadata.pack.PackFormat.of(74))); + side.addProperty("accepts95", formats.isValueInRange(net.minecraft.server.packs.metadata.pack.PackFormat.of(95))); + side.add("nativeMetadata", encoded); + if (type == PackType.CLIENT_RESOURCES) { + if (opened.getChildren() == null || opened.getChildren().size() != 1) { + throw new AssertionError("Expected the actual Forge client delegate"); + } + side.addProperty("currentCompatibility", pack.getCompatibility().name()); + } else { + var supplier = new PathPackResources.PathResourcesSupplier(mod.findResource("")); + var current = Pack.readPackMetadata(pack.location(), supplier, + SharedConstants.getCurrentVersion().packVersion(type), type); + if (current == null) throw new AssertionError("Native current-version server metadata was rejected"); + side.addProperty("currentCompatibility", current.compatibility().name()); + } + } + repository.setSelected(List.of(id)); + int checked = 0; + try (MultiPackResourceManager manager = new MultiPackResourceManager(type, repository.openAllSelected()); + var files = Files.walk(resources.resolve(type.getDirectory()))) { + for (Path file : files.filter(Files::isRegularFile).sorted().toList()) { + Path relative = resources.resolve(type.getDirectory()).relativize(file); + String namespace = relative.getName(0).toString(); + String path = relative.subpath(1, relative.getNameCount()).toString().replace('\\', '/'); + var resource = manager.getResource(Identifier.fromNamespaceAndPath(namespace, path)).orElseThrow(); + try (var input = resource.open()) { + if (!Arrays.equals(Files.readAllBytes(file), input.readAllBytes())) { + throw new AssertionError("Forge changed " + type + " resource " + relative); + } + } + checked++; + } + } + if (checked == 0) throw new AssertionError("No native " + type + " resources were read"); + side.addProperty("resourcesVerified", checked); + result.add(type.name(), side); + } + } finally { + logger.removeAppender(appender); + appender.stop(); + } + JsonArray messages = new JsonArray(); + diagnostics.forEach(messages::add); + result.add("warningsAndErrors", messages); + Files.writeString(evidence.resolve("receipt.json"), result.toString(), StandardCharsets.UTF_8); + if (!diagnostics.isEmpty()) throw new AssertionError("Native Forge resource diagnostics: " + diagnostics); + System.out.println("BASESTEAM_NATIVE_PACK_RESOURCES_PASS " + result); + } +} diff --git a/src/test/java/zone/moddev/mc/basesteam/testing/NativePackResourceProofTest.java b/src/test/java/zone/moddev/mc/basesteam/testing/NativePackResourceProofTest.java new file mode 100644 index 0000000..429eb24 --- /dev/null +++ b/src/test/java/zone/moddev/mc/basesteam/testing/NativePackResourceProofTest.java @@ -0,0 +1,38 @@ +package zone.moddev.mc.basesteam.testing; + +import java.nio.file.Path; +import net.minecraft.server.packs.PackType; +import org.junit.Test; +import static org.junit.Assert.*; + +public class NativePackResourceProofTest { + @Test public void childResourceReaderDisablesJLineInPipedDumbTerminal() throws Exception { + var proof = NativePackResourceProof.read(); + var child = proof.getAsJsonObject("childProcess"); + assertEquals("terminal.jline", net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY); + assertEquals("false", child.get("terminalJline").isJsonNull() + ? null : child.get("terminalJline").getAsString()); + assertEquals("dumb", child.get("term").getAsString()); + assertFalse("The child must not attach a console", child.get("consoleAttached").getAsBoolean()); + assertFalse("JLine must not construct a terminal", child.get("terminalCreated").getAsBoolean()); + assertEquals(0, proof.getAsJsonArray("warningsAndErrors").size()); + for (PackType type : PackType.values()) { + assertTrue(type.toString(), proof.getAsJsonObject(type.name()).get("resourcesVerified").getAsInt() > 0); + } + } + + @Test public void terminalWarningsAndResourceErrorsRemainFatal() { + for (String severity : new String[] {"WARN", "ERROR", "FATAL"}) { + String message = severity.equals("WARN") + ? "Advanced terminal features are not available in this environment" : "Resource diagnostic"; + for (String output : new String[] { + "2026-11-12 00:00:00 main " + severity + " " + message, + "[00:00:00] [main/" + severity + "] [NativeProof]: " + message}) { + assertThrows(output, AssertionError.class, + () -> NativePackResourceProof.requireCleanOutput(output, Path.of("negative-control.log"))); + } + } + NativePackResourceProof.requireCleanOutput("[00:00:00] [main/INFO] Native resources loaded", + Path.of("positive-control.log")); + } +} diff --git a/src/test/java/zone/moddev/mc/basesteam/testing/NativeTestBootstrap.java b/src/test/java/zone/moddev/mc/basesteam/testing/NativeTestBootstrap.java index 0af43f8..e614176 100644 --- a/src/test/java/zone/moddev/mc/basesteam/testing/NativeTestBootstrap.java +++ b/src/test/java/zone/moddev/mc/basesteam/testing/NativeTestBootstrap.java @@ -8,7 +8,6 @@ import net.minecraft.SharedConstants; import net.minecraft.server.Bootstrap; import net.minecraftforge.common.ForgeMod; -import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.registries.DeferredRegister; import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.ForgeRegistry; @@ -35,17 +34,7 @@ public static synchronized void registerVanillaFluidTypes() throws ReflectiveOpe // Plain JUnit has no mod loader to dispatch these two Forge registry phases. if (ForgeRegistries.FLUID_TYPES.get() == null) { - var registries = ForgeRegistries.class.getDeclaredField("registries"); - registries.setAccessible(true); - DeferredRegister fluidTypes = ((List) registries.get(null)).stream() - .map(DeferredRegister.class::cast) - .filter(registry -> ForgeRegistries.Keys.FLUID_TYPES.equals(registry.getRegistryKey())) - .findFirst().orElseThrow(); - var event = new NewRegistryEvent(); - invokeRegistryPhase(fluidTypes, "createRegistry", event); - var fill = NewRegistryEvent.class.getDeclaredMethod("fill"); - fill.setAccessible(true); - fill.invoke(event); + createForgeRegistry(ForgeRegistries.Keys.FLUID_TYPES); } var vanillaTypes = ForgeMod.class.getDeclaredField("VANILLA_FLUID_TYPES"); @@ -56,7 +45,34 @@ public static synchronized void registerVanillaFluidTypes() throws ReflectiveOpe event.newInstance(ForgeRegistries.Keys.FLUID_TYPES, ForgeRegistries.FLUID_TYPES.get(), null)); } - private static void invokeRegistryPhase(DeferredRegister register, String phase, Event event) + public static synchronized void registerCraftingSerializers() throws ReflectiveOperationException { + start(); + if (ForgeRegistries.INGREDIENT_SERIALIZERS.get() == null) + createForgeRegistry(ForgeRegistries.Keys.INGREDIENT_SERIALIZERS); + var field = ForgeMod.class.getDeclaredField("INGREDIENT_SERIALIZERS"); + field.setAccessible(true); + var register = (DeferredRegister) field.get(null); + if (register.getEntries().stream().allMatch(net.minecraftforge.registries.RegistryObject::isPresent)) return; + var event = RegisterEvent.class.getDeclaredConstructor(ResourceKey.class, ForgeRegistry.class, Registry.class); + event.setAccessible(true); + invokeRegistryPhase(register, "handleEvent", event.newInstance( + ForgeRegistries.Keys.INGREDIENT_SERIALIZERS, ForgeRegistries.INGREDIENT_SERIALIZERS.get(), null)); + } + + private static void createForgeRegistry(ResourceKey key) throws ReflectiveOperationException { + var registries = ForgeRegistries.class.getDeclaredField("registries"); + registries.setAccessible(true); + DeferredRegister register = ((List) registries.get(null)).stream() + .map(DeferredRegister.class::cast).filter(value -> key.equals(value.getRegistryKey())) + .findFirst().orElseThrow(); + var event = new NewRegistryEvent(); + invokeRegistryPhase(register, "createRegistry", event); + var fill = NewRegistryEvent.class.getDeclaredMethod("fill"); + fill.setAccessible(true); + fill.invoke(event); + } + + private static void invokeRegistryPhase(DeferredRegister register, String phase, Object event) throws ReflectiveOperationException { // The launch-only EventBus transform is not present in plain JUnit. Class dispatcher = Class.forName(DeferredRegister.class.getName() + "$EventDispatcher"); @@ -67,6 +83,16 @@ private static void invokeRegistryPhase(DeferredRegister register, String pha method.invoke(constructor.newInstance(register), event); } + public static void bindItemTags(java.util.Map, + List>> tags) { + var registry = (net.minecraft.core.MappedRegistry)BuiltInRegistries.ITEM; + registry.unfreeze(); + registry.bindAllTagsToEmpty(); + BuiltInRegistries.ITEM.freeze(); + BuiltInRegistries.ITEM.prepareTagReload(new net.minecraft.tags.TagLoader.LoadResult<>( + net.minecraft.core.registries.Registries.ITEM, tags)).apply(); + } + private static final class NativeRegistries { static { SharedConstants.tryDetectVersion(); diff --git a/tools/generate-recipe-unlocks.js b/tools/generate-recipe-unlocks.js index 6053fc6..110df6a 100644 --- a/tools/generate-recipe-unlocks.js +++ b/tools/generate-recipe-unlocks.js @@ -14,8 +14,8 @@ const ignored = new Set(['item:minecraft:stick', 'item:minecraft:string', function choices(value) { if (Array.isArray(value)) return value.flatMap(choices); - if (value && value.tag) return [{tag: value.tag}]; - if (value && value.item) return [{item: value.item}]; + if (typeof value === 'string' && value.startsWith('#')) return [{tag: value.slice(1)}]; + if (typeof value === 'string') return [{item: value}]; throw new Error('Unsupported recipe ingredient: ' + JSON.stringify(value)); } diff --git a/tools/run-packaged-server-smoke.ps1 b/tools/run-packaged-server-smoke.ps1 index 67b4e57..a9d6720 100644 --- a/tools/run-packaged-server-smoke.ps1 +++ b/tools/run-packaged-server-smoke.ps1 @@ -6,16 +6,21 @@ param( [string]$PowerJar, [string]$BaseMetalsJar, [string]$OreSpawnJar, - [switch]$WithoutBaseMetals + [switch]$WithoutBaseMetals, + [switch]$UseNioTransport, + [switch]$Reopen ) $ErrorActionPreference = 'Stop' +if ($WithoutBaseMetals) { + throw 'Base Metals is required by baseSteam; a no-Base-Metals runtime is not supported.' +} $project = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot '..')).Path $forge = (Resolve-Path -LiteralPath $ForgeInstall).Path $pins = Get-Content -Raw -LiteralPath (Join-Path $project 'gradle.properties') | ConvertFrom-StringData -if ($pins.minecraft_version -ne '1.21.1' -or $pins.forge_version -ne '52.1.0' -or +if ($pins.minecraft_version -ne '1.21.11' -or $pins.forge_version -ne '61.1.0' -or $pins.poweradvantage_source_ref -cnotmatch '^[0-9a-f]{40}$') { - throw 'An exact Forge 52.1.0 candidate and committed Power source pin are required.' + throw 'An exact Forge 61.1.0 candidate and committed Power source pin are required.' } $java = (Resolve-Path -LiteralPath $JavaExe).Path $javaVersion = (& $java -version 2>&1 | Out-String).Trim() @@ -34,19 +39,17 @@ if (!$PowerJar) { $PowerJar = Join-Path $project "../../PowerAdvantage/build/libs/basePower-$($pins.poweradvantage_version).jar" } if (!$BaseMetalsJar) { - $BaseMetalsJar = Join-Path $project "../../../../BaseMetals/.verification-basemetals1211/Advantage shared hook/build/libs/BaseMetals-$($pins.basemetals_version).jar" + $BaseMetalsJar = Join-Path $project "../../../../BaseMetals/.verification-basemetals12111/Advantage shared hook/build/libs/BaseMetals-$($pins.basemetals_version).jar" } if (!$OreSpawnJar) { - $OreSpawnJar = Join-Path $project "../../../../MinecraftMineralogy/OS1211/OreSpawn/build/libs/OreSpawn-$($pins.orespawn_version).jar" + $OreSpawnJar = Join-Path $project "../../../../MinecraftMineralogy/OS12111/OreSpawn/build/libs/OreSpawn-$($pins.orespawn_version).jar" } $mods = [ordered]@{ $steamName = @{ Path = (Join-Path $project "build/libs/$steamName"); Sha256 = $steamSha } "basePower-$($pins.poweradvantage_version).jar" = @{ Path = $PowerJar; Sha256 = $pins.poweradvantage_player_sha256 } "OreSpawn-$($pins.orespawn_version).jar" = @{ Path = $OreSpawnJar; Sha256 = $pins.orespawn_player_sha256 } } -if (!$WithoutBaseMetals) { - $mods["BaseMetals-$($pins.basemetals_version).jar"] = @{ Path = $BaseMetalsJar; Sha256 = $pins.basemetals_player_sha256 } -} +$mods["BaseMetals-$($pins.basemetals_version).jar"] = @{ Path = $BaseMetalsJar; Sha256 = $pins.basemetals_player_sha256 } $forgeVersion = "$($pins.minecraft_version)-$($pins.forge_version)" $shim = "forge-$forgeVersion-shim.jar" $forgeLib = "libraries/net/minecraftforge/forge/$forgeVersion" @@ -57,7 +60,7 @@ $forgeJars = [ordered]@{ } foreach ($file in $forgeJars.Keys) { if ($forgeJars[$file] -cnotmatch '^[A-F0-9]{64}$') { - throw "Supply the coordinated Forge 52.1.0 runtime hash in gradle.properties: $file" + throw "Supply the coordinated Forge 61.1.0 runtime hash in gradle.properties: $file" } if ((Get-FileHash -LiteralPath (Join-Path $forge $file)).Hash -cne $forgeJars[$file]) { throw "Installed Forge runtime hash mismatch: $file" @@ -84,6 +87,8 @@ $receipt = [ordered]@{ java = $javaVersion mods = $mods world = (Join-Path $run 'steam-smoke') + transport = $(if ($UseNioTransport) { 'NIO' } else { 'native-availability-selection' }) + processes = @() finishedUtc = $null } $process = $null @@ -100,47 +105,79 @@ try { Set-Content -LiteralPath (Join-Path $run 'eula.txt') -Value 'eula=true' -Encoding ascii Set-Content -LiteralPath (Join-Path $run 'server.properties') -Encoding ascii -Value @( 'server-ip=127.0.0.1', 'server-port=0', 'level-name=steam-smoke', + "use-native-transport=$((!$UseNioTransport).ToString().ToLowerInvariant())", 'level-type=minecraft:flat', 'generate-structures=false', 'view-distance=2', 'simulation-distance=2', 'generator-settings={"layers":[{"height":1,"block":"minecraft:bedrock"},{"height":2,"block":"minecraft:dirt"},{"height":1,"block":"minecraft:grass_block"}],"biome":"minecraft:plains"}' ) - $start = [System.Diagnostics.ProcessStartInfo]::new($java) - $start.WorkingDirectory = $run - $start.UseShellExecute = $false - $start.CreateNoWindow = $true - $start.RedirectStandardInput = $true - $start.RedirectStandardOutput = $true - $start.RedirectStandardError = $true - foreach ($argument in @('-Xms512m', '-Xmx3g', '-Djava.net.preferIPv6Addresses=system', '-jar', $shim, '--nogui')) { - $start.ArgumentList.Add($argument) - } - $process = [System.Diagnostics.Process]::Start($start) - $stdout = $process.StandardOutput.ReadToEndAsync() - $stderr = $process.StandardError.ReadToEndAsync() - $latest = Join-Path $run 'logs/latest.log' - $deadline = [DateTime]::UtcNow.AddMinutes(3) - do { - if ($process.HasExited) { throw "Forge exited before ready: $($process.ExitCode)" } - $log = if (Test-Path -LiteralPath $latest) { Get-Content -Raw -LiteralPath $latest } - if ($log -match 'Done \([^)]+\)!') { break } - Start-Sleep -Seconds 1 - } while ([DateTime]::UtcNow -lt $deadline) - if ($log -notmatch 'Done \([^)]+\)!') { throw 'Timed out waiting for a fresh Forge world.' } - $process.StandardInput.WriteLine('stop') - if (!$process.WaitForExit(60000) -or $process.ExitCode -ne 0) { throw 'Forge did not stop cleanly.' } - $log = Get-Content -Raw -LiteralPath $latest - if ($log -notmatch "baseSteam $([regex]::Escape($pins.mod_version)) common setup complete" -or - $log -match '\[[^\]\r\n]*/(?:ERROR|FATAL)\]|NoClassDefFoundError') { - throw 'Missing Steam setup or a relevant loader/data error appeared in the server log.' + foreach ($phase in $(if ($Reopen) { @('startup', 'reopen') } else { @('startup') })) { + foreach ($name in $mods.Keys) { + if ((Get-FileHash -LiteralPath (Join-Path $run "mods/$name")).Hash -cne $mods[$name].Sha256) { + throw "Runtime jar changed before $phase : $name" + } + } + $phaseReceipt = [ordered]@{ phase = $phase; status = 'FAILED'; pid = $null; exitCode = $null; levelDatSha256 = $null } + $receipt.processes += $phaseReceipt + $process = $null + try { + $latest = Join-Path $run 'logs/latest.log' + if ($phase -eq 'reopen' -and (Test-Path -LiteralPath $latest)) { + Move-Item -LiteralPath $latest -Destination (Join-Path $run 'startup-original-latest.log') + } + $start = [System.Diagnostics.ProcessStartInfo]::new($java) + $start.WorkingDirectory = $run + $start.UseShellExecute = $false + $start.CreateNoWindow = $true + $start.RedirectStandardInput = $true + $start.RedirectStandardOutput = $true + $start.RedirectStandardError = $true + foreach ($argument in @('-Xms512m', '-Xmx3g', '-XX:ActiveProcessorCount=4', '-Djava.net.preferIPv6Addresses=system', '-jar', $shim, '--nogui')) { + $start.ArgumentList.Add($argument) + } + $process = [System.Diagnostics.Process]::Start($start) + $phaseReceipt.pid = $process.Id + $stdout = $process.StandardOutput.ReadToEndAsync() + $stderr = $process.StandardError.ReadToEndAsync() + $latest = Join-Path $run 'logs/latest.log' + $deadline = [DateTime]::UtcNow.AddMinutes(3) + $log = '' + do { + if ($process.HasExited) { throw "Forge exited before $phase ready: $($process.ExitCode)" } + if (Test-Path -LiteralPath $latest) { $log = Get-Content -Raw -LiteralPath $latest } + if ($log -match 'Done \([^)]+\)!') { break } + Start-Sleep -Seconds 1 + } while ([DateTime]::UtcNow -lt $deadline) + if ($log -notmatch 'Done \([^)]+\)!') { throw "Timed out waiting for Forge $phase." } + $process.StandardInput.WriteLine('stop') + if (!$process.WaitForExit(60000) -or $process.ExitCode -ne 0) { throw "Forge $phase did not stop cleanly." } + $phaseReceipt.exitCode = $process.ExitCode + $log = Get-Content -Raw -LiteralPath $latest + Copy-Item -LiteralPath $latest -Destination (Join-Path $run "$phase-latest.log") + $allLogs = $log + $stdout.GetAwaiter().GetResult() + $stderr.GetAwaiter().GetResult() + if ($log -notmatch "baseSteam $([regex]::Escape($pins.mod_version)) common setup complete" -or + $allLogs -match '\[[^\]\r\n]*/(?:ERROR|FATAL)\]|NoClassDefFoundError|Exception in thread') { + throw "Missing Steam setup or a loader/data error appeared during $phase." + } + $levelDat = Join-Path $run 'steam-smoke/level.dat' + if (!(Test-Path -LiteralPath $levelDat) -or (Get-Item -LiteralPath $levelDat).Length -eq 0) { + throw "Forge $phase did not save its world." + } + $phaseReceipt.levelDatSha256 = (Get-FileHash -LiteralPath $levelDat -Algorithm SHA256).Hash + $phaseReceipt.status = 'START_SAVE_STOP_PASS' + } finally { + if ($process) { + if (!$process.HasExited) { $process.Kill($true); $process.WaitForExit() } + $suffix = if ($phase -eq 'startup') { '' } else { "-$phase" } + Set-Content -LiteralPath (Join-Path $run "stdout$suffix.log") -Value $stdout.GetAwaiter().GetResult() + Set-Content -LiteralPath (Join-Path $run "stderr$suffix.log") -Value $stderr.GetAwaiter().GetResult() + $phaseReceipt.exitCode = $process.ExitCode + $process.Dispose() + $process = $null + } + } } - $receipt.status = 'PACKAGED_DEDICATED_SERVER_PASS' + $receipt.status = if ($Reopen) { 'PACKAGED_DEDICATED_SERVER_REOPEN_PASS' } else { 'PACKAGED_DEDICATED_SERVER_PASS' } Write-Output "$($receipt.status): $run" } finally { - if ($process) { - if (!$process.HasExited) { $process.Kill($true); $process.WaitForExit() } - Set-Content -LiteralPath (Join-Path $run 'stdout.log') -Value $stdout.GetAwaiter().GetResult() - Set-Content -LiteralPath (Join-Path $run 'stderr.log') -Value $stderr.GetAwaiter().GetResult() - $process.Dispose() - } $receipt.finishedUtc = [DateTime]::UtcNow.ToString('o') $receipt | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $run 'receipt.json') } diff --git a/tools/verify-eclipse-ecj.ps1 b/tools/verify-eclipse-ecj.ps1 new file mode 100644 index 0000000..9744ef1 --- /dev/null +++ b/tools/verify-eclipse-ecj.ps1 @@ -0,0 +1,106 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory)][ValidatePattern('^[a-zA-Z0-9_-]+$')][string]$RunId, + [string]$JavaExe = "$env:USERPROFILE/.jdks/temurin-21.0.7+6/bin/java.exe", + [string]$CompilerJar = "$env:USERPROFILE/.p2/pool/plugins/org.eclipse.jdt.core.compiler.batch_3.46.0.v20260528-0407.jar" +) + +$ErrorActionPreference = 'Stop' +$project = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot '..')).Path +$java = (Resolve-Path -LiteralPath $JavaExe).Path +$compiler = (Resolve-Path -LiteralPath $CompilerJar).Path +$compilerHash = (Get-FileHash -LiteralPath $compiler -Algorithm SHA256).Hash +if ($compilerHash -cne 'D0D43F8E2D7003E5EFED612E2CBB5F01870043397D8F1BBE536FD9128F4FCBF7') { + throw 'The installed Eclipse ECJ 3.46.0 compiler does not match the qualified tool.' +} +$version = (& $java -version 2>&1 | Out-String).Trim() +if ($LASTEXITCODE -ne 0 -or $version -notmatch 'Temurin-21\.0\.7\+6') { throw "Unexpected JVM: $version" } +$pins = Get-Content -Raw -LiteralPath (Join-Path $project 'gradle.properties') | ConvertFrom-StringData +[xml]$classpath = Get-Content -Raw -LiteralPath (Join-Path $project '.classpath') +$entries = @($classpath.classpath.classpathentry) +$run = Join-Path $project "build/ecj-$RunId" +if (Test-Path -LiteralPath $run) { throw "Evidence already exists: $run" } +New-Item -ItemType Directory -Path $run | Out-Null +$receipt = [ordered]@{ + status = 'FAILED' + commit = (& git -C $project rev-parse HEAD) + java = $version + compiler = @{ path = $compiler; sha256 = $compilerHash } + classpathSha256 = (Get-FileHash -LiteralPath (Join-Path $project '.classpath') -Algorithm SHA256).Hash + powerSource = $pins.poweradvantage_source_ref + scopes = [ordered]@{} + finishedUtc = $null +} +function Attribute($entry, [string]$name) { + @($entry.attributes.attribute | Where-Object { $_.name -eq $name } | ForEach-Object value) -join ',' +} +function ProjectPath([string]$path) { + (Resolve-Path -LiteralPath $(if ([IO.Path]::IsPathRooted($path)) { $path } else { Join-Path $project $path })).Path +} +Push-Location -LiteralPath $project +try { + foreach ($scope in @('main', 'test', 'gameTestValidation')) { + $roots = @($entries | Where-Object { $_.kind -eq 'src' -and (Attribute $_ 'gradle_scope') -eq $scope }) + $sources = @($roots | ForEach-Object { + if ($_.GetAttribute('including') -or $_.GetAttribute('excluding')) { + throw "Unsupported Eclipse source filter on $($_.path)" + } + Get-ChildItem -LiteralPath (ProjectPath $_.path) -Filter *.java -Recurse | ForEach-Object FullName + } | Sort-Object -Unique) + if (!$sources.Count) { throw "Empty Eclipse $scope source scope" } + $libraries = @($entries | Where-Object { $_.kind -eq 'lib' -and + ((Attribute $_ 'gradle_used_by_scope').Split(',') -contains $scope) } | ForEach-Object { ProjectPath $_.path }) + foreach ($dependency in @( + @{ pattern = 'basePower-*.jar'; hash = $pins.poweradvantage_deobf_sha256 }, + @{ pattern = 'BaseMetals-*.jar'; hash = $pins.basemetals_deobf_sha256 }, + @{ pattern = 'OreSpawn-*.jar'; hash = $pins.orespawn_deobf_sha256 })) { + $matches = @($libraries | Where-Object { [IO.Path]::GetFileName($_) -like $dependency.pattern }) + if ($matches.Count -ne 1 -or (Get-FileHash -LiteralPath $matches[0] -Algorithm SHA256).Hash -cne $dependency.hash) { + throw "Eclipse $scope is missing the exact dependency $($dependency.pattern)" + } + } + if ($scope -ne 'test' -and @($libraries | Where-Object { [IO.Path]::GetFileName($_) -like 'junit-*' }).Count) { + throw "JUnit leaked into the Eclipse $scope compiler scope" + } + $output = Join-Path $run $scope + New-Item -ItemType Directory -Path $output | Out-Null + if ($scope -ne 'main') { $libraries = @((Join-Path $run 'main')) + $libraries } + $arguments = @('-21', '-proc:none', '-encoding', 'UTF-8', '-classpath', ('"' + ($libraries -join ';') + '"'), + '-d', ('"' + $output + '"')) + @($sources | ForEach-Object { '"' + $_ + '"' }) + $argumentFile = Join-Path $run "$scope.args" + $arguments | Set-Content -LiteralPath $argumentFile -Encoding utf8 + $receipt.scopes[$scope] = [ordered]@{ + sourceCount = $sources.Count + sources = @($sources | ForEach-Object { @{ path = $_; sha256 = (Get-FileHash -LiteralPath $_ -Algorithm SHA256).Hash } }) + libraries = $libraries + exitCode = $null + } + & $java -XX:ActiveProcessorCount=4 -Xmx1G -jar $compiler "@$argumentFile" *> (Join-Path $run "$scope.log") + $receipt.scopes[$scope].exitCode = $LASTEXITCODE + if ($LASTEXITCODE -ne 0) { throw "ECJ $scope failed; see $run/$scope.log" } + } + $bootstrap = ProjectPath '../../PowerAdvantage/build/unit-bootstrap/forge-native-unit-bootstrap.jar' + if ((Get-FileHash -LiteralPath $bootstrap -Algorithm SHA256).Hash -cne $pins.poweradvantage_test_bootstrap_sha256) { + throw 'Pinned native unit bootstrap changed' + } + $runtime = @((Join-Path $run 'test'), (Join-Path $run 'main'), (Join-Path $project 'build/resources/main')) + $runtime += @($receipt.scopes.test.libraries | Where-Object { $_ -ne (Join-Path $run 'main') }) + $tests = @($receipt.scopes.test.sources | Where-Object { $_.path -like '*Test.java' } | ForEach-Object { + $source = Get-Content -Raw -LiteralPath $_.path + if ($source -notmatch '(?m)^\s*package\s+([\w.]+)\s*;') { throw "Missing package in $($_.path)" } + $Matches[1] + '.' + [IO.Path]::GetFileNameWithoutExtension($_.path) + }) + $unitReceipt = Join-Path $run 'junit.json' + & $java -XX:ActiveProcessorCount=4 -Xmx1536m "-javaagent:$bootstrap" --add-opens java.base/java.lang.invoke=ALL-UNNAMED ` + -cp ($runtime -join ';') zone.moddev.mc.basesteam.testing.EclipseUnitLauncher $unitReceipt @tests ` + *> (Join-Path $run 'junit.log') + $receipt.unitExitCode = $LASTEXITCODE + if ($LASTEXITCODE -ne 0) { throw "ECJ-compiled JUnit failed; see $run/junit.log" } + $receipt.unit = Get-Content -Raw -LiteralPath $unitReceipt | ConvertFrom-Json + $receipt.status = 'ECJ_ALL_SCOPES_AND_UNIT_PASS' + Write-Output "$($receipt.status): $run" +} finally { + $receipt.finishedUtc = [DateTime]::UtcNow.ToString('o') + $receipt | ConvertTo-Json -Depth 9 | Set-Content -LiteralPath (Join-Path $run 'receipt.json') -Encoding utf8 + Pop-Location +} diff --git a/tools/verify-native-port.js b/tools/verify-native-port.js index ce7637a..8d53439 100644 --- a/tools/verify-native-port.js +++ b/tools/verify-native-port.js @@ -25,12 +25,13 @@ for (const name of files(resources).filter(name => /\.(json|mcmeta)$/.test(name) json.set(name, JSON.parse(text)); } assert.deepEqual(json.get(`${resources}/pack.mcmeta`).pack, { - pack_format: 34, supported_formats: {min_inclusive: 34, max_inclusive: 48}, description: 'baseSteam' + min_format: [94, 1], max_format: [94, 1], description: 'baseSteam' }); const metadata = read(`${resources}/META-INF/mods.toml`); +assert.match(metadata, /^showAsResourcePack=false$/m); assert.match(metadata, /modId="basesteam"/); assert.match(metadata, /modId="basepower"\r?\nmandatory=true\r?\nversionRange="\[\$\{poweradvantage_version\},3\.0\.0\)"/); -assert.match(metadata, /modId="basemetals"\r?\nmandatory=false/); +assert.match(metadata, /modId="basemetals"\r?\nmandatory=true/); assert.doesNotMatch(metadata, /dependencies\.steamadvantage|modId="(?:steamadvantage|poweradvantage)"/); function verifyLocalAsset(id, directory, extension, name) { @@ -105,6 +106,13 @@ for (const id of pickaxe.values.map(value => value.split(':')[1]).filter(id => ! } const tests = files('src/test/java').filter(name => name.endsWith('.java')); const testCount = tests.reduce((sum, name) => sum + (read(name).match(/@Test\b/g) || []).length, 0); -console.log(`BASESTEAM_NATIVE1211_SOURCE_PASS json=${json.size} java=${source.length} factories=${ids.length} harvest=${pickaxe.values.length} testMethods=${testCount}`); +for (const id of [...pickaxe.values.map(value => value.split(':')[1]), + 'steam_governor', 'steam_drill_bit', 'blackpowder_cartridge', 'musket']) { + assert(json.has(`${assets}/items/${id}.json`), `Missing native item definition: ${id}`); +} +for (const name of source) { + assert(!/ResourceLocation|net\.minecraftforge\.eventbus\.api\.(?:IEventBus|SubscribeEvent)/.test(read(name)), name); +} +console.log(`BASESTEAM_NATIVE12111_SOURCE_PASS json=${json.size} java=${source.length} factories=${ids.length} harvest=${pickaxe.values.length} testMethods=${testCount}`); console.log('Source checks only. Compilation, executed tests, packaged runtime and manual acceptance are separate.'); require('./verify-enchantment-data');