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