diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 433d69cbc2..17aae2e44a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,24 +1,24 @@ -name: IronAgeFurniture 1.20.1 CI +name: IronAgeFurniture NeoForge 1.20.1 CI on: push: branches: - - master-1.20.1 + - master-1.20.1-neo - 'feature/**' pull_request: branches: - - master-1.20.1 + - master-1.20.1-neo permissions: contents: read concurrency: - group: iron-age-furniture-1.19-${{ github.workflow }}-${{ github.ref }} + group: iron-age-furniture-1.20.1-neoforge-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: cold-forge-bootstrap: - name: Cold Forge bootstrap + name: Cold early-NeoForge bootstrap runs-on: ubuntu-latest timeout-minutes: 60 @@ -44,7 +44,7 @@ jobs: distribution: temurin java-version: '17.0.1+12' - - name: Bootstrap Forge from an empty cache + - name: Bootstrap early NeoForge from an empty cache shell: bash env: GRADLE_USER_HOME: ${{ runner.temp }}/iron-age-furniture-cold-gradle @@ -62,7 +62,7 @@ jobs: | tee "$RUNNER_TEMP/cold-forge-bootstrap.log" grep -F "OreSpawn Mavenizer compatibility runtime: Java 25.0.3" \ "$RUNNER_TEMP/cold-forge-bootstrap.log" - grep -F "OreSpawn Mavenizer compatibility: applied 8 rule(s) for net.minecraftforge:forge:1.20.1-47.4.10" \ + grep -F "OreSpawn Mavenizer compatibility: applied 8 rule(s) for net.neoforged:forge:1.20.1-47.1.106" \ "$RUNNER_TEMP/cold-forge-bootstrap.log" find "$GRADLE_USER_HOME" .gradle/mavenizer \ -name '*.orespawn-compatibility' -type f -print0 \ @@ -135,13 +135,13 @@ jobs: - name: Upload audited release candidate uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: iron-age-furniture-1.20.1-${{ github.sha }} + name: iron-age-furniture-1.20.1-neoforge-${{ github.sha }} if-no-files-found: error retention-days: 30 path: | - build/libs/iron-age-furniture-0.3.0.120011.jar - build/libs/iron-age-furniture-0.3.0.120011-sources.jar - build/libs/iron-age-furniture-0.3.0.120011-javadoc.jar + build/libs/iron-age-furniture-0.3.0.120012.jar + build/libs/iron-age-furniture-0.3.0.120012-sources.jar + build/libs/iron-age-furniture-0.3.0.120012-javadoc.jar build/release/SHA256SUMS CHANGELOG.md @@ -149,7 +149,7 @@ jobs: if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: iron-age-furniture-1.20.1-diagnostics-${{ github.sha }} + name: iron-age-furniture-1.20.1-neoforge-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 c70c1f7050..aa657cb5c0 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -3,11 +3,11 @@ name: CodeQL on: push: branches: - - master-1.20.1 + - master-1.20.1-neo - 'feature/**' pull_request: branches: - - master-1.20.1 + - master-1.20.1-neo schedule: - cron: '43 7 * * 4' @@ -69,7 +69,7 @@ jobs: echo "CodeQL compilation failed after $attempt attempts." >&2 exit 1 fi - echo "::warning::CodeQL compilation attempt $attempt failed; retrying with the preserved Forge cache." + echo "::warning::CodeQL compilation attempt $attempt failed; retrying with the preserved early-NeoForge ForgeGradle cache." done - name: Analyze diff --git a/.github/workflows/validate-gradle-build.yml b/.github/workflows/validate-gradle-build.yml index 63abc33fb6..79f330f69a 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.20.1 + - master-1.20.1-neo - 'feature/**' pull_request: branches: - - master-1.20.1 + - master-1.20.1-neo permissions: contents: read diff --git a/CHANGELOG.md b/CHANGELOG.md index 3362cc1ef0..162c944f89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,18 @@ -# IronAgeFurniture 0.3.0.120011 +# IronAgeFurniture 0.3.0.120012 -First IronAgeFurniture release for Minecraft 1.20.1 and Forge 47.4.10. +First IronAgeFurniture release for Minecraft 1.20.1 and early NeoForge. It is +built against NeoForge 47.1.106 and supports the 47.1.99+ 47.x line. ## Player-visible changes - Add furniture for the vanilla mangrove, cherry, and bamboo wood families. -- Support the Biomes O' Plenty wood set shared by its compatible 1.20.1 - releases, and conditionally add Empyreal, Maple, and Pine furniture when BOP - 19.0.0.96 or newer is installed, without imposing a minimum BOP version. +- Support the complete Biomes O' Plenty 18.0.0.598 wood set. BOP 19.0.0.96 and + its GlitchCore dependency require Forge 47.3+ and cannot start on early + NeoForge 47.1, so its extra Empyreal, Maple, and Pine tier is not advertised + for this branch. - Replace the retired Oh The Biomes You'll Go integration with Oh The Biomes - We've Gone furniture, while retaining Immersive Engineering treated-wood + We've Gone furniture using BWG 1.5.11, the newest 1.20.1 build explicitly + published for NeoForge, while retaining Immersive Engineering treated-wood furniture. - Remap IronAgeFurniture's former Biomes O' Plenty cherry furniture to vanilla cherry. On installations with BWG, remap supported BYG furniture names to @@ -37,7 +40,7 @@ First IronAgeFurniture release for Minecraft 1.20.1 and Forge 47.4.10. resource, configuration, entity, NBT, and saved-world identities. - Keep the supported Java API namespace at `zone.moddev.mc.ironagefurniture`. The Maven coordinate is - `zone.moddev.mc:iron-age-furniture:0.3.0.120011`. + `zone.moddev.mc:iron-age-furniture:0.3.0.120012`. - Add a deterministic, checked-in furniture catalog covering registrations, blockstates, models, recipes, advancements, and translations. - Resolve each generated optional wood model to that wood's actual texture @@ -45,7 +48,10 @@ First IronAgeFurniture release for Minecraft 1.20.1 and Forge 47.4.10. treated-wood paths. - Verify the supported optional integrations with pinned positive-path runtime probes while keeping their test fixtures out of release artifacts. -- Build with ForgeGradle 7.0.34, Gradle 9.6.1, and pinned Java toolchains. +- Build the early-NeoForge `net.neoforged:forge` artifact with ForgeGradle + 7.0.34, Gradle 9.6.1, and pinned Java toolchains. +- Preserve the loader's Forge-era Java API, `mods.toml`, and `forge:*` + conditional-data contracts while publishing this target as NeoForge. - Produce deterministic main, sources, and Javadoc jars with SHA-256 checksums. - Provide reproducible Eclipse/Buildship setup and guarded CI, validation, and release workflows. diff --git a/README.md b/README.md index 1ae094875a..984ed5795a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Discord](https://img.shields.io/badge/Discord-MMD-green.svg?style=flat&logo=Discord)](https://discord.moddev.zone) [![CurseForge](https://img.shields.io/badge/CurseForge-Iron%20Age%20Furniture-orange.svg)](https://www.curseforge.com/minecraft/mc-mods/iron-age-furniture) -[![Build, test, and audit](https://github.com/MinecraftModDevelopmentMods/IronAgeFurniture/actions/workflows/ci.yml/badge.svg?branch=master-1.20.1)](https://github.com/MinecraftModDevelopmentMods/IronAgeFurniture/actions/workflows/ci.yml?query=branch%3Amaster-1.20.1) +[![Build, test, and audit](https://github.com/MinecraftModDevelopmentMods/IronAgeFurniture/actions/workflows/ci.yml/badge.svg?branch=master-1.20.1-neo)](https://github.com/MinecraftModDevelopmentMods/IronAgeFurniture/actions/workflows/ci.yml?query=branch%3Amaster-1.20.1-neo) # IronAgeFurniture @@ -9,9 +9,14 @@ olde-style furniture to Minecraft. Seating is functional, furniture supports the appropriate vanilla and optional-mod materials, and lighting includes falling and throwable lava lamps. -This branch provides IronAgeFurniture `0.3.0.120011` for Minecraft 1.20.1 and -Forge 47.4.10. It requires Java 17 and can be installed on both clients and -dedicated servers. +This branch provides IronAgeFurniture `0.3.0.120012` for Minecraft 1.20.1 and +early NeoForge 47.1.99 or newer in the 47.x line. It is built against NeoForge +47.1.106, requires Java 17, and can be installed on both clients and dedicated +servers. + +NeoForge 1.20.1 retained the Forge-era Java API, `mods.toml`, `forge:*` data +conditions, and ForgeGradle user-development contract. Those names are +therefore intentional on this branch and do not indicate a Forge loader build. ## Optional integrations @@ -25,11 +30,15 @@ installed: They are optional. Their recipes and recipe advancements are conditionally loaded, so a normal installation does not need any of them. -Biomes O' Plenty 18 and 19 both receive furniture for their shared wood set. -When BOP 19.0.0.96 or newer is detected, IronAgeFurniture additionally enables -Empyreal, Maple, and Pine furniture. This does not impose a minimum BOP version. -Back up a world before downgrading from BOP 19 to BOP 18: those three source -woods, and furniture made from them, are not available in BOP 18. +The positive-path compatibility set pins BWG 1.5.11, the newest published +1.20.1 build explicitly marked for NeoForge. Later Forge-only BWG releases +require Forge 47.4+ and are not compatible with this early NeoForge line. + +The tested Biomes O' Plenty target is 18.0.0.598, which receives furniture for +its complete 1.20.1 wood set. BOP 19.0.0.96 was also inspected, but it and its +GlitchCore dependency require Forge 47.3+ and cannot start on the early +NeoForge 47.1 line. Its Empyreal, Maple, and Pine tier is therefore not +advertised as supported by this branch. ## Compatibility @@ -42,7 +51,7 @@ migration. The supported Java source namespace is `zone.moddev.mc.ironagefurniture`. The Maven coordinate is -`zone.moddev.mc:iron-age-furniture:0.3.0.120011`. +`zone.moddev.mc:iron-age-furniture:0.3.0.120012`. See [CHANGELOG.md](CHANGELOG.md) for the release notes and [docs/VERSIONS.md](docs/VERSIONS.md) for the versioning scheme. Bugs can be @@ -60,7 +69,7 @@ gradlew.bat clean check build javadoc verifyReleaseArtifacts verifyReleaseChecks ``` Before importing the nested project into its Eclipse workspace, generate the -reproducible Buildship metadata and Forge launch profiles with: +reproducible Buildship metadata and early-NeoForge launch profiles with: ```text gradlew.bat prepareEclipse verifyEclipseProductionClasspath diff --git a/build.gradle b/build.gradle index 47c0b37076..08a9e5668a 100644 --- a/build.gradle +++ b/build.gradle @@ -40,15 +40,15 @@ def mavenizerLicense = mavenizerToolsDirectory.file('LICENSE-MAVENIZER.txt') def mavenizerReadme = mavenizerToolsDirectory.file('README.md') def mavenizerFixtureChecksums = [ (mavenizerCompatibilityJar.asFile): - '416DCA4797BC55826F0DDCF03FDC405F1990FC15C2133FA27A1F143BF7976177', + 'F4DB44F64C98F6401ED3A856ECEBCC5E1C2126E7C02BD1237FAB38793B4AF275', (mavenizerRuleManifest.asFile): - '8945511DFF53B8F1A91EE44B14AF9EC7DB34C373617EA37A608C6958BFEEB22E', + '351746200B44A4B9656C9AD5888DA3371E69AA601880FECA048C0131223DCCAC', (mavenizerSourcePatch.asFile): - 'D856190DEF5574716C5E4F4379912C451B1F5952B0B111576651A98EECF58544', + '0AC522C64FEBA87C14792364D86FE1487E064681C54629F747E7DC80E8A83650', (mavenizerLicense.asFile): '20C17D8B8C48A600800DFD14F95D5CB9FF47066A9641DDEAB48DC54AEC96E331', (mavenizerReadme.asFile): - '177C1DDEDEF9704B9FDC8EA24B8DCDF4DDCC826DD0E253E7872A00DB2EA34518' + '2F95F09D20EABE8A7010EA68FF04FB770FC5E2F4F6A3856D1495AF0B1AAB676B' ] [mavenizerCompatibilityJar.asFile, mavenizerRuleManifest.asFile].each { fixture -> @@ -172,12 +172,14 @@ def normalizeArchiveLineEndings = { details -> tasks.register('verifyBuildEnvironment') { group = 'verification' - description = 'Verifies the supported Gradle runtime and exact Java 17 compiler toolchain.' + description = 'Verifies the supported Gradle runtime and Temurin Java 17 compiler toolchain.' doLast { def metadata = java17Compiler.get().metadata String actual = metadata.javaRuntimeVersion.toString() - if (actual != project.java_toolchain_version.toString()) { - throw new GradleException("Java compiler toolchain must be ${project.java_toolchain_version}; " + + if (metadata.languageVersion.asInt() != project.java_version.toString().toInteger() + || !metadata.vendor.toString().toLowerCase(Locale.ROOT).contains('temurin')) { + throw new GradleException("Java compiler toolchain must be Temurin ${project.java_version} " + + "(release baseline ${project.java_toolchain_version}); " + "found ${metadata.vendor} ${actual} at ${metadata.installationPath}") } } @@ -239,7 +241,7 @@ sourceSets.main.resources.srcDir 'src/generated/resources' def developmentModOutput = layout.buildDirectory.dir('forge-run-mod-classes/main') def prepareDevelopmentMod = tasks.register('prepareDevelopmentMod', Sync) { group = 'ide' - description = 'Merges production classes and resources for Forge 47 development discovery.' + description = 'Merges production classes and resources for early NeoForge 47 development discovery.' dependsOn tasks.named('classes') from sourceSets.main.output.classesDirs from sourceSets.main.output.resourcesDir @@ -261,6 +263,10 @@ tasks.withType(JavaExec).configureEach { JavaExec runTask -> repositories { minecraft.mavenizer(it) + maven { + name = 'NeoForge' + url = 'https://maven.neoforged.net/releases' + } maven fg.forgeMaven maven fg.minecraftLibsMaven mavenCentral() @@ -268,7 +274,7 @@ repositories { dependencies { implementation minecraft.dependency( - "net.minecraftforge:forge:${project.minecraft_version}-${project.forge_version}") + "net.neoforged:forge:${project.minecraft_version}-${project.neo_version}") } renamer.mappings.from minecraft.dependency.toSrgFile @@ -279,6 +285,7 @@ tasks.withType(Test).configureEach { def optionalIntegrationManifestFile = layout.projectDirectory.file( 'gradle/verification/optional-integration-mods.json') +def optionalIntegrationRuntimeTotals = [recipes: '1380', advancements: '1364'] def optionalIntegrationRunDirectory = layout.projectDirectory.dir('run-optional-integrations') def optionalIntegrationServerDirectory = providers.gradleProperty('optionalIntegrationServerDir') .map { project.file(it) } @@ -297,7 +304,7 @@ tasks.register('verifyOptionalIntegrationMods') { doLast { Map manifest = new JsonSlurper().parse(optionalIntegrationManifestFile.asFile) as Map if (manifest.minecraft != project.minecraft_version.toString() - || !(manifest.mods instanceof List) || manifest.mods.size() != 8) { + || !(manifest.mods instanceof List) || manifest.mods.size() != 7) { throw new GradleException('Optional-integration mod manifest is incomplete or targets the wrong game') } File modsDirectory = optionalIntegrationModsDirectory.get() @@ -399,7 +406,7 @@ tasks.register('generateOptionalIntegrationFixtureData') { tasks.register('compileOptionalIntegrationFixture', JavaCompile) { group = 'verification' - description = 'Compiles the test-only Forge probe for positive optional-integration validation.' + description = 'Compiles the test-only early-NeoForge probe for positive optional-integration validation.' dependsOn tasks.named('classes') source fileTree('src/optionalIntegrationTest/java') classpath = files(sourceSets.main.output, sourceSets.main.compileClasspath) @@ -452,7 +459,7 @@ tasks.register('stageOptionalIntegrationFixture', Copy) { tasks.register('runOptionalIntegrationServer', Exec) { group = 'verification' - description = 'Runs the packaged Forge server and proves all optional recipes are loaded.' + description = 'Runs the packaged NeoForge server and proves all optional recipes are loaded.' dependsOn tasks.named('stageOptionalIntegrationFixture') doFirst { File serverDirectory = optionalIntegrationServerDirectory.get() @@ -463,11 +470,11 @@ tasks.register('runOptionalIntegrationServer', Exec) { String argumentFileName = System.getProperty('os.name').toLowerCase(Locale.ROOT) .contains('windows') ? 'win_args.txt' : 'unix_args.txt' File argumentFile = new File(serverDirectory, - "libraries/net/minecraftforge/forge/${minecraft_version}-${forge_version}/" + + "libraries/net/neoforged/forge/${minecraft_version}-${neo_version}/" + argumentFileName) File userArguments = new File(serverDirectory, 'user_jvm_args.txt') if (!argumentFile.isFile() || !userArguments.isFile()) { - throw new GradleException("Install the Forge ${minecraft_version}-${forge_version} " + + throw new GradleException("Install NeoForge ${minecraft_version}-${neo_version} " + "server in ${serverDirectory} before running this test") } workingDir serverDirectory @@ -483,8 +490,8 @@ tasks.register('runOptionalIntegrationServer', Exec) { Properties result = new Properties() marker.withInputStream { result.load(it) } if (result.getProperty('status') != 'PASS' - || result.getProperty('conditional_recipes_loaded') != '1497' - || result.getProperty('conditional_advancements_loaded') != '1481') { + || result.getProperty('conditional_recipes_loaded') != optionalIntegrationRuntimeTotals.recipes + || result.getProperty('conditional_advancements_loaded') != optionalIntegrationRuntimeTotals.advancements) { throw new GradleException("Unexpected optional-integration result ${result}") } } @@ -495,8 +502,8 @@ tasks.named('processResources', ProcessResources) { def replaceProperties = [ version : project.version, minecraft_version : project.minecraft_version, - forge_version : project.forge_version, - forge_version_range : project.forge_version_range, + neo_version : project.neo_version, + neo_version_range : project.neo_version_range, loader_version_range : project.loader_version_range, mod_id : project.mod_id, mod_name : project.mod_name, @@ -532,7 +539,7 @@ def commonManifest = { 'Implementation-Vendor' : project.mod_authors, 'Maven-Artifact' : "${project.group}:${base.archivesName.get()}:${project.version}", 'Built-On-Java' : project.java_version, - 'Built-On' : "${project.minecraft_version}-${project.forge_version}" + 'Built-On' : "${project.minecraft_version}-${project.neo_version}" ]) } @@ -670,10 +677,10 @@ tasks.register('verifyMavenizerCompatibilityFixture') { } def parsed = new JsonSlurper().parse(mavenizerRuleManifest.asFile) if (parsed.schema != 1 || parsed.targets.keySet() != - ['net.minecraftforge:forge:1.20.1-47.4.10'] as Set) { + ['net.neoforged:forge:1.20.1-47.1.106'] as Set) { throw new GradleException('Mavenizer target-rule manifest has unexpected targets or schema') } - def rules = parsed.targets['net.minecraftforge:forge:1.20.1-47.4.10'] + def rules = parsed.targets['net.neoforged:forge:1.20.1-47.1.106'] if (rules.expectedApplications != 8 || rules.rules.size() != 8 || rules.rules.collect { it.id }.toSet().size() != 8) { throw new GradleException('Mavenizer target-rule manifest must define eight unique Forge 47 rules') @@ -703,6 +710,7 @@ tasks.register('verifyMavenizerCompatibilityFixture') { } if (!mavenizerReadme.asFile.getText('UTF-8').contains('Temurin `25.0.3+9`') || !mavenizerSourcePatch.asFile.getText('UTF-8').contains('SourceCompatibilityPatcher.java') + || !mavenizerSourcePatch.asFile.getText('UTF-8').contains('NEOFORGE_LEGACY_ARTIFACT') || !mavenizerLicense.asFile.getText('UTF-8').contains('GNU LESSER GENERAL PUBLIC LICENSE')) { throw new GradleException('Mavenizer provenance, patch, or license documentation is incomplete') } @@ -715,15 +723,15 @@ tasks.register('verifyReleaseConfiguration') { dependsOn tasks.named('verifyMavenizerCompatibilityFixture') doLast { Map expected = [ - mod_version : '0.3.0.120011', + mod_version : '0.3.0.120012', mod_group : 'zone.moddev.mc', mod_archive_name : 'iron-age-furniture', mod_id : 'ironagefurniture', minecraft_version : '1.20.1', - loader_name : 'forge', - loader_code : '1', - forge_version : '47.4.10', - forge_version_range : '[47.4.10,48)', + loader_name : 'neoforge', + loader_code : '2', + neo_version : '47.1.106', + neo_version_range : '[47.1.99,48)', loader_version_range : '[47,)', mcp_mappings_channel : 'official', mcp_mappings_version : '1.20.1', @@ -1017,7 +1025,7 @@ tasks.register('verifyNamespace') { tasks.register('verifyVersionMetadata') { group = 'verification' - description = 'Checks authoritative version and generated Forge metadata.' + description = 'Checks authoritative version and generated early-NeoForge metadata.' dependsOn tasks.named('processResources') doLast { String source = file( @@ -1036,7 +1044,7 @@ tasks.register('verifyVersionMetadata') { 'modId="ironagefurniture"', "version=\"${project.version}\"", 'license="LGPL-2.1"', - 'versionRange="[47.4.10,48)"', + 'versionRange="[47.1.99,48)"', 'versionRange="[1.20.1]"' ].each { required -> if (!metadata.contains(required)) { @@ -1049,6 +1057,51 @@ tasks.register('verifyVersionMetadata') { } } +tasks.register('verifyEarlyNeoForgeContract') { + group = 'verification' + description = 'Guards the Forge-era API and data contracts used by early NeoForge 1.20.1.' + dependsOn tasks.named('processResources') + doLast { + List productionSources = fileTree('src/main/java') { include '**/*.java' }.files as List + List laterApiSources = productionSources.findAll { + it.getText('UTF-8').contains('net.neoforged.') + }.collect { project.relativePath(it) } + if (!laterApiSources.isEmpty()) { + throw new GradleException("Later NeoForge Java APIs leaked into the early-loader branch: ${laterApiSources}") + } + + if (file('src/main/resources/META-INF/neoforge.mods.toml').exists()) { + throw new GradleException('Early NeoForge 1.20.1 must use META-INF/mods.toml') + } + String sourceMetadata = file('src/main/resources/META-INF/mods.toml').getText('UTF-8') + if (!sourceMetadata.contains('modId="forge"') + || !sourceMetadata.contains('versionRange="${neo_version_range}"')) { + throw new GradleException('Early NeoForge metadata must retain the Forge loader identity') + } + + List runtimeJson = fileTree('src/main/resources') { include '**/*.json' }.files as List + List laterConditionFiles = runtimeJson.findAll { + it.getText('UTF-8').contains('"neoforge:') + }.collect { project.relativePath(it) } + if (!laterConditionFiles.isEmpty()) { + throw new GradleException("Later NeoForge data conditions leaked into the early-loader branch: " + + laterConditionFiles.take(10)) + } + int forgeConditionFiles = runtimeJson.count { + String text = it.getText('UTF-8') + text.contains('"forge:mod_loaded"') || text.contains('"forge:item_exists"') + } + if (forgeConditionFiles == 0) { + throw new GradleException('Expected Forge-era optional integration conditions') + } + + String buildScript = file('build.gradle').getText('UTF-8') + if (!buildScript.contains('net.neoforged:forge:${project.minecraft_version}-${project.neo_version}')) { + throw new GradleException('Build does not declare the official early-NeoForge Maven coordinate') + } + } +} + tasks.register('verifyDedicatedServerBoundaries') { group = 'verification' description = 'Keeps common proxy code free of client-only Minecraft and Forge types.' @@ -1100,6 +1153,7 @@ tasks.register('verifyReleaseArtifacts') { dependsOn tasks.named('verifyResourceJson') dependsOn tasks.named('verifyNamespace') dependsOn tasks.named('verifyVersionMetadata') + dependsOn tasks.named('verifyEarlyNeoForgeContract') doLast { File libs = layout.buildDirectory.dir('libs').get().asFile List jars = (libs.listFiles() ?: [] as File[]) @@ -1275,7 +1329,7 @@ tasks.withType(PublishToMavenRepository).configureEach { tasks.register('syncForge47EclipseLaunches') { group = 'ide' - description = 'Synchronizes processed resources and hardens Forge 47 Eclipse launches.' + description = 'Synchronizes processed resources and hardens early NeoForge 47 Eclipse launches.' dependsOn tasks.named('processResources') dependsOn tasks.named('verifyBuildEnvironment') doLast { @@ -1379,7 +1433,7 @@ tasks.register('syncForge47EclipseLaunches') { tasks.register('verifyEclipseProductionClasspath') { group = 'verification' - description = 'Verifies Buildship and production-only Forge 47 launch metadata.' + description = 'Verifies Buildship and production-only early NeoForge 47 launch metadata.' dependsOn tasks.named('syncForge47EclipseLaunches') doLast { File classpath = file('.classpath') @@ -1461,7 +1515,7 @@ tasks.matching { it.name == 'eclipse' }.configureEach { tasks.register('prepareEclipse') { group = 'ide' - description = 'Creates the Buildship project model and ForgeGradle 7 launch profiles.' + description = 'Creates the Buildship project model and early-NeoForge ForgeGradle 7 launch profiles.' dependsOn tasks.named('eclipse') dependsOn tasks.named('isolateEclipseProductionRuns') } @@ -1474,6 +1528,7 @@ tasks.named('check') { dependsOn tasks.named('verifyResourceJson') dependsOn tasks.named('verifyNamespace') dependsOn tasks.named('verifyVersionMetadata') + dependsOn tasks.named('verifyEarlyNeoForgeContract') dependsOn tasks.named('verifyWorkflowPins') dependsOn tasks.named('verifyDedicatedServerBoundaries') } diff --git a/ci-fixtures/tools/README.md b/ci-fixtures/tools/README.md index 5cb98ca6fc..6ef8b58dbe 100644 --- a/ci-fixtures/tools/README.md +++ b/ci-fixtures/tools/README.md @@ -1,9 +1,9 @@ -# ForgeGradle 7 Mavenizer compatibility fixture +# Early NeoForge 1.20.1 ForgeGradle 7 Mavenizer compatibility fixture This directory contains a build-only derivative of MinecraftForge's MinecraftMavenizer `0.5.21`. It is used only while ForgeGradle prepares the -exact Forge `1.20.1-47.4.10` development dependency and is excluded from every -OreSpawn publication artifact. +exact NeoForge `net.neoforged:forge:1.20.1-47.1.106` development dependency and +is excluded from every IronAgeFurniture publication artifact. ## Provenance and licence @@ -15,13 +15,17 @@ OreSpawn publication artifact. - Embedded/external rule manifest: `minecraft-source-compatibility.json` The patch adds a target-aware compatibility stage before Mavenizer recompiles -decompiled sources. Rules run only for an exact Maven artifact listed in the +decompiled sources. It also permits the early-NeoForge +`net.neoforged:forge` group to use Mavenizer's existing Forge userdev pipeline +while preserving that group in downloaded and generated Maven artifacts. The +alias is limited to the `forge` artifact; ordinary Forge behavior is unchanged. +Compatibility rules run only for an exact Maven artifact listed in the manifest. Each rule requires one exact source file, one exact record declaration and one accessor in that record. Missing, partial, duplicate or ambiguous states fail preparation. Targets without an explicit rule set are left unchanged. -For Forge `1.20.1-47.4.10`, the manifest removes the eight explicit accessors +For NeoForge `1.20.1-47.1.106`, the manifest removes the eight explicit accessors that duplicate compiler-generated record accessors in `Holder.Direct`, `OptionInstance` and `MemoryCondition`. The implicit record methods have the same public contract. A marker beside Mavenizer's output records the target, @@ -29,8 +33,8 @@ manifest SHA-256 and applied rule IDs. Reprocessing already-patched sources is validated and idempotent. The derivative also propagates Gradle offline mode when the build sets -`ORESPAWN_MAVENIZER_OFFLINE=true`. Mavenizer itself runs on Java 25; OreSpawn -and Minecraft 1.20.1 continue to compile for Java 17. +`ORESPAWN_MAVENIZER_OFFLINE=true`. Mavenizer itself runs on Java 25; +IronAgeFurniture and Minecraft 1.20.1 continue to compile for Java 17. ## Rebuild @@ -41,7 +45,7 @@ and Minecraft 1.20.1 continue to compile for Java 17. 4. Run `./gradlew clean build --no-daemon` (or `gradlew.bat` on Windows). 5. Copy `build/libs/minecraft-mavenizer-0.5.21.jar` to `minecraft-mavenizer-0.5.21-orespawn-compat.jar`. -6. Run OreSpawn's `verifyMavenizerCompatibilityFixture` task. The build script +6. Run IronAgeFurniture's `verifyMavenizerCompatibilityFixture` task. The build script contains the authoritative checksums and also verifies the embedded manifest, Java class version and licence/provenance files. diff --git a/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar b/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar index 2c068aaec2..a82197366d 100644 Binary files a/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar and b/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.jar differ diff --git a/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.patch b/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.patch index 28951fba98..b3cf82345a 100644 --- a/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.patch +++ b/ci-fixtures/tools/minecraft-mavenizer-0.5.21-orespawn-compat.patch @@ -1,4 +1,4 @@ -From 10279f8ab1adadb87b56f36c85dbda3cf64e62b3 Mon Sep 17 00:00:00 2001 +From 89a912c5487156a26fc5812d4c15f697646f2f5b Mon Sep 17 00:00:00 2001 From: OreSpawn Build Fixture Date: Fri, 28 Aug 2026 15:21:10 +0100 Subject: [PATCH] Add target-aware source compatibility rules @@ -7,10 +7,13 @@ Subject: [PATCH] Add target-aware source compatibility rules build.gradle | 2 +- .../net/minecraftforge/mcmaven/cli/Main.java | 18 +- .../minecraftforge/mcmaven/cli/MavenTask.java | 3 + + .../mcmaven/impl/MinecraftMaven.java | 4 +- + .../mcmaven/impl/repo/forge/ForgeRepo.java | 26 +- + .../mcmaven/impl/util/Constants.java | 2 + .../mcmaven/impl/util/ProcessUtils.java | 2 + .../impl/util/SourceCompatibilityPatcher.java | 241 ++++++++++++++++++ .../minecraft-source-compatibility.json | 58 +++++ - 6 files changed, 322 insertions(+), 2 deletions(-) + 9 files changed, 343 insertions(+), 13 deletions(-) create mode 100644 src/main/java/net/minecraftforge/mcmaven/impl/util/SourceCompatibilityPatcher.java create mode 100644 src/main/resources/META-INF/orespawn/minecraft-source-compatibility.json @@ -96,6 +99,133 @@ index 825820e..39bb9b3 100644 var mappings = getMappings(options, mappingsO, parchmentO); var foreignRepositories = new HashMap(); +diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java b/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java +index c5435b1..ec9e281 100644 +--- a/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java ++++ b/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java +@@ -114,8 +114,8 @@ public record MinecraftMaven( + } + + var mcprepo = new MCPConfigRepo(this.cache, dependenciesOnly); +- if (Constants.FORGE_GROUP.equals(artifact.getGroup()) && Constants.FORGE_NAME.equals(artifact.getName())) { +- var repo = new ForgeRepo(this.cache, mcprepo); ++ if ((Constants.FORGE_ARTIFACT.equals(module) || Constants.NEOFORGE_LEGACY_ARTIFACT.equals(module))) { ++ var repo = new ForgeRepo(this.cache, mcprepo, artifact.getGroup(), artifact.getName()); + createForge(artifact, mcprepo, repo, outputJson); + } else if (Constants.MC_GROUP.equals(artifact.getGroup())) { + createMinecraft(artifact, mcprepo, outputJson); +diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java +index 559ec78..d712a52 100644 +--- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java ++++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java +@@ -73,6 +73,8 @@ public final class ForgeRepo extends Repo { + + final MCPConfigRepo mcpconfig; + final File globalBuild; ++ final String forgeGroup; ++ final String forgeName; + + /** + * Creates a new Forge repository. +@@ -80,10 +82,12 @@ public final class ForgeRepo extends Repo { + * @param cache The cache directory + * @param mcpconfig The MCPConfig repo + */ +- public ForgeRepo(Cache cache, MCPConfigRepo mcpconfig) { ++ public ForgeRepo(Cache cache, MCPConfigRepo mcpconfig, String forgeGroup, String forgeName) { + super(cache); + this.mcpconfig = mcpconfig; + this.globalBuild = new File(cache.root(), "forge/.global"); ++ this.forgeGroup = forgeGroup; ++ this.forgeName = forgeName; + } + + public static boolean isSupported(String version) { +@@ -110,7 +114,7 @@ public final class ForgeRepo extends Repo { + public List process(Artifact artifact, Mappings mappings, Map> outputJson) { + var module = artifact.getGroup() + ':' + artifact.getName(); + var version = artifact.getVersion(); +- if (!Constants.FORGE_ARTIFACT.equals(module)) ++ if (!(Constants.FORGE_ARTIFACT.equals(module) || Constants.NEOFORGE_LEGACY_ARTIFACT.equals(module))) + throw new IllegalArgumentException("Unknown or unsupported module: " + module); + + var fg = FGVersion.fromForge(version); +@@ -150,11 +154,15 @@ public final class ForgeRepo extends Repo { + } + } + +- private static Artifact getUserdev(String forge) { ++ private Artifact getUserdev(String forge) { ++ return getUserdev(this.forgeGroup, this.forgeName, forge); ++ } ++ ++ private static Artifact getUserdev(String forgeGroup, String forgeName, String forge) { + var forgever = new ComparableVersion(forge); + // userdev3, old attempt to make 1.12.2 FG3 compatible + var userdev3 = forgever.compareTo(USERDEV3_START) >= 0 && forgever.compareTo(USERDEV3_END) < 0; +- return Artifact.from(Constants.FORGE_GROUP, Constants.FORGE_NAME, forge, userdev3 ? "userdev3" : "userdev", "jar"); ++ return Artifact.from(forgeGroup, forgeName, forge, userdev3 ? "userdev3" : "userdev", "jar"); + } + + /// This handles legacy FG2.0->2.3 artifacts +@@ -171,7 +179,7 @@ public final class ForgeRepo extends Repo { + /// - `net.minecraft:mappings_{CHANNEL}:{MCP_VERSION}[-{VERSION}]@zip` + /// - A zip file containing fields, methods, and params.csv files mapping SRG->MCP names. + private List processV2(String version, Mappings baseMappings, Map> outputJson, FGVersion fgVersion) { +- var name = Artifact.from(Constants.FORGE_GROUP, Constants.FORGE_NAME, version); ++ var name = Artifact.from(this.forgeGroup, this.forgeName, version); + var userdev = getUserdev(version); + + var build = new File(this.cache.root(), "forge/" + userdev.getFolder()); +@@ -248,7 +256,7 @@ public final class ForgeRepo extends Repo { + /// - Standard maven pom file that contains all dependency information. + // Made this an MD comment to make it easier to read in IDE - Jonathan + private List processV3(String version, Mappings baseMappings, Map> outputJson) { +- var name = Artifact.from(Constants.FORGE_GROUP, Constants.FORGE_NAME, version); ++ var name = Artifact.from(this.forgeGroup, this.forgeName, version); + var userdev = getUserdev(version); + + var build = new File(this.cache.root(), "forge/" + userdev.getFolder()); +@@ -401,7 +409,7 @@ public final class ForgeRepo extends Repo { + }); + } + +- private static Task pom(File build, ForgeVersionCommon forge, String version, @Nullable Artifact clientExtra, @Nullable Artifact mappings) { ++ private Task pom(File build, ForgeVersionCommon forge, String version, @Nullable Artifact clientExtra, @Nullable Artifact mappings) { + return Task.named("pom[forge]", () -> { + var output = new File(build, "forge.pom"); + +@@ -418,7 +426,7 @@ public final class ForgeRepo extends Repo { + if (Mavenizer.checkCache(output, cache)) + return output; + +- var builder = new POMBuilder(Constants.FORGE_GROUP, Constants.FORGE_NAME, version).preferGradleModule().dependencies(dependencies -> { ++ var builder = new POMBuilder(this.forgeGroup, this.forgeName, version).preferGradleModule().dependencies(dependencies -> { + if (clientExtra != null) + dependencies.add(clientExtra); + +@@ -529,7 +537,7 @@ public final class ForgeRepo extends Repo { + } + @SuppressWarnings("unused") + private static List gatherVariants(MavenCache cache, String version, @Nullable FGVersion fg) { +- var userdev = getUserdev(version); ++ var userdev = getUserdev(Constants.FORGE_GROUP, Constants.FORGE_NAME, version); + if (fg == null) + userdev = userdev.withClassifier("src").withExtension("zip"); + var mappingsFiles = new ArrayList(); +diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/Constants.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/Constants.java +index 3b39894..8dfd653 100644 +--- a/src/main/java/net/minecraftforge/mcmaven/impl/util/Constants.java ++++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/Constants.java +@@ -13,6 +13,8 @@ public final class Constants { + public static final String FORGE_GROUP = "net.minecraftforge"; + public static final String FORGE_NAME = "forge"; + public static final String FORGE_ARTIFACT = FORGE_GROUP + ':' + FORGE_NAME; ++ public static final String NEOFORGE_LEGACY_GROUP = "net.neoforged"; ++ public static final String NEOFORGE_LEGACY_ARTIFACT = NEOFORGE_LEGACY_GROUP + ':' + FORGE_NAME; + public static final String FORGE_MAVEN = "https://maven.minecraftforge.net/"; + public static final String MCP_DOWNLOADS = FORGE_FILES + "mcp/"; + diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/ProcessUtils.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/ProcessUtils.java index 9fa2888..d8c14fd 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/util/ProcessUtils.java @@ -358,60 +488,60 @@ index 0000000..2cb07e0 +} diff --git a/src/main/resources/META-INF/orespawn/minecraft-source-compatibility.json b/src/main/resources/META-INF/orespawn/minecraft-source-compatibility.json new file mode 100644 -index 0000000..462d571 +index 0000000..ddf2eba --- /dev/null +++ b/src/main/resources/META-INF/orespawn/minecraft-source-compatibility.json @@ -0,0 +1,58 @@ +{ + "schema": 1, + "targets": { -+ "net.minecraftforge:forge:1.19.4-45.4.0": { ++ "net.neoforged:forge:1.20.1-47.1.106": { + "expectedApplications": 8, + "rules": [ + { -+ "id": "forge-1.19.4-holder-direct-value", ++ "id": "forge-1.20.1-holder-direct-value", + "file": "net/minecraft/core/Holder.java", + "recordDeclaration": "public static record Direct(T value) implements Holder", + "accessor": "\n public T value() {\n return this.value;\n }\n" + }, + { -+ "id": "forge-1.19.4-option-alt-enum-value-setter", ++ "id": "forge-1.20.1-option-alt-enum-value-setter", + "file": "net/minecraft/client/OptionInstance.java", + "recordDeclaration": "public static record AltEnum(List values, List altValues, BooleanSupplier altCondition, OptionInstance.CycleableValueSet.ValueSetter valueSetter, Codec codec)", + "accessor": "\n public OptionInstance.CycleableValueSet.ValueSetter valueSetter() {\n return this.valueSetter;\n }\n" + }, + { -+ "id": "forge-1.19.4-option-alt-enum-codec", ++ "id": "forge-1.20.1-option-alt-enum-codec", + "file": "net/minecraft/client/OptionInstance.java", + "recordDeclaration": "public static record AltEnum(List values, List altValues, BooleanSupplier altCondition, OptionInstance.CycleableValueSet.ValueSetter valueSetter, Codec codec)", + "accessor": "\n public Codec codec() {\n return this.codec;\n }\n" + }, + { -+ "id": "forge-1.19.4-option-enum-codec", ++ "id": "forge-1.20.1-option-enum-codec", + "file": "net/minecraft/client/OptionInstance.java", + "recordDeclaration": "public static record Enum(List values, Codec codec)", + "accessor": "\n public Codec codec() {\n return this.codec;\n }\n" + }, + { -+ "id": "forge-1.19.4-option-lazy-enum-codec", ++ "id": "forge-1.20.1-option-lazy-enum-codec", + "file": "net/minecraft/client/OptionInstance.java", + "recordDeclaration": "public static record LazyEnum(Supplier> values, Function> validateValue, Codec codec)", + "accessor": "\n public Codec codec() {\n return this.codec;\n }\n" + }, + { -+ "id": "forge-1.19.4-memory-absent-memory", ++ "id": "forge-1.20.1-memory-absent-memory", + "file": "net/minecraft/world/entity/ai/behavior/declarative/MemoryCondition.java", + "recordDeclaration": "public static record Absent(MemoryModuleType memory)", + "accessor": "\n public MemoryModuleType memory() {\n return this.memory;\n }\n" + }, + { -+ "id": "forge-1.19.4-memory-present-memory", ++ "id": "forge-1.20.1-memory-present-memory", + "file": "net/minecraft/world/entity/ai/behavior/declarative/MemoryCondition.java", + "recordDeclaration": "public static record Present(MemoryModuleType memory)", + "accessor": "\n public MemoryModuleType memory() {\n return this.memory;\n }\n" + }, + { -+ "id": "forge-1.19.4-memory-registered-memory", ++ "id": "forge-1.20.1-memory-registered-memory", + "file": "net/minecraft/world/entity/ai/behavior/declarative/MemoryCondition.java", + "recordDeclaration": "public static record Registered(MemoryModuleType memory)", + "accessor": "\n public MemoryModuleType memory() {\n return this.memory;\n }\n" diff --git a/ci-fixtures/tools/minecraft-source-compatibility.json b/ci-fixtures/tools/minecraft-source-compatibility.json index 858f197622..ddf2ebada2 100644 --- a/ci-fixtures/tools/minecraft-source-compatibility.json +++ b/ci-fixtures/tools/minecraft-source-compatibility.json @@ -1,7 +1,7 @@ { "schema": 1, "targets": { - "net.minecraftforge:forge:1.20.1-47.4.10": { + "net.neoforged:forge:1.20.1-47.1.106": { "expectedApplications": 8, "rules": [ { diff --git a/docs/OPTIONAL-INTEGRATION-TEST.md b/docs/OPTIONAL-INTEGRATION-TEST.md index bde2355893..25c518ac6a 100644 --- a/docs/OPTIONAL-INTEGRATION-TEST.md +++ b/docs/OPTIONAL-INTEGRATION-TEST.md @@ -1,7 +1,7 @@ # Optional integration runtime test -This positive-path test starts Forge 1.20.1 with Iron Age Furniture and all three -supported optional integrations. A test-only Forge mod then checks the live server +This positive-path test starts early NeoForge 1.20.1 with Iron Age Furniture and +all three supported optional integrations. A test-only early-NeoForge mod then checks the live server registries for every conditional recipe and recipe advancement and stops the server. The third-party mod jars are deliberately not committed or redistributed. Their @@ -10,7 +10,7 @@ exact filenames, versions, SHA-256 checksums, roles, and source pages are pinned ## Setup -1. Install the Forge `1.20.1-47.4.10` server into the ignored +1. Install the NeoForge `1.20.1-47.1.106` server into the ignored `run-optional-integrations` directory and accept its EULA. 2. Download every jar listed in the manifest into `run-optional-integrations/mods`. This directory may also contain the generated @@ -18,7 +18,7 @@ exact filenames, versions, SHA-256 checksums, roles, and source pages are pinned 3. Run `gradlew.bat runOptionalIntegrationServer` with the project's pinned Java installations available as described in `gradle.properties`. -An existing disposable Forge server can be used without moving it by adding +An existing disposable NeoForge server can be used without moving it by adding `-PoptionalIntegrationServerDir=` to the command. The manifest jars must be in that server's `mods` directory. The task stages only the current packaged Iron Age Furniture jar and generated probe; the server installation and @@ -33,11 +33,27 @@ It then builds and stages `ironagefurniture-optional-integration-probe.jar`, removes any stale result, and accepts success only when the probe writes `optional-integration-pass.properties` in the selected server directory with all -applicable conditional data present. The BOP 18 profile expects 1,380 recipes -and 1,364 recipe advancements; the BOP 19 profile additionally enables the 117 -Empyreal, Maple, and Pine recipes and advancements, for totals of 1,497 and -1,481. Version-tiered entries are selected by their public registry capability, -using Forge's `forge:item_exists` condition. +applicable conditional data present. The BOP 18.0.0.598 profile expects 1,380 +recipes and 1,364 recipe advancements. Version-tiered entries are selected by +their public registry capability using the early loader's `forge:item_exists` +condition. + +BOP 19.0.0.96 and its required GlitchCore 0.0.1.1 were inspected and exercised +as a negative compatibility probe. Both require Forge 47.3+, so they are not a +qualifying runtime combination for NeoForge 47.1.106 and are deliberately not +present in the positive manifest. Core packaged server smokes are also run on +the minimum supported NeoForge 47.1.99 loader. + +The profile uses BWG 1.5.11 because it is the newest 1.20.1 BWG publication +whose loader metadata explicitly includes NeoForge. BWG 1.8.0 is Forge-only +and requires Forge 47.4+, so it is deliberately not used on this target. + +BWG 1.5.11 emits an upstream, non-fatal `EatBlockGoalMixin` descriptor warning +on NeoForge 47.1.106 (plus two CorgiLib/Oh The Trees overwrite-conflict +warnings). The isolated BWG server still reaches `Done`, registers the BWG +furniture, and passes all 936 BWG recipe and 936 recipe-advancement checks. +Treat any additional error, missing texture/model, registry failure, or IAF +recipe/advancement warning as a test failure. The probe also records the loaded versions and per-integration counts. It is built from `src/optionalIntegrationTest` and is explicitly excluded from all release diff --git a/docs/VERSIONS.md b/docs/VERSIONS.md index 607e057e1a..6b7642bfe5 100644 --- a/docs/VERSIONS.md +++ b/docs/VERSIONS.md @@ -4,7 +4,7 @@ IronAgeFurniture releases use `Major.Minor.Bug.Target`. The first three componen The target is calculated as `MmmppL`: Minecraft major without padding, two-digit minor, two-digit patch, and a one-digit loader code. Forge uses loader code `1`; NeoForge uses `2`. -For Minecraft 1.20.1 Forge, the target is `120011`, so the current functionally equivalent `0.3.0` release is `0.3.0.120011`. The release tag is exactly that complete version. +For Minecraft 1.20.1 NeoForge, the target is `120012`, so the current functionally equivalent `0.3.0` release is `0.3.0.120012`. The release tag is exactly that complete version. This early NeoForge generation still uses ForgeGradle and Forge-era runtime metadata; the loader code follows the distributed loader, not the build-plugin package names. Equivalent forward ports retain `0.3.0` and change only the target. A player-visible feature or fix changes Major, Minor, or Bug independently of the target. diff --git a/gradle.properties b/gradle.properties index ca2bef238c..8cfb7102ae 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -# Reproducible Gradle and ForgeGradle configuration. +# Reproducible Gradle and early-NeoForge/ForgeGradle configuration. org.gradle.jvmargs=-Xmx4G org.gradle.daemon=false org.gradle.configuration-cache=false @@ -7,7 +7,7 @@ org.gradle.parallel=false net.minecraftforge.gradle.merge-source-sets=false # Project and release identity. -mod_version=0.3.0.120011 +mod_version=0.3.0.120012 mod_group=zone.moddev.mc mod_archive_name=iron-age-furniture mod_id=ironagefurniture @@ -18,8 +18,8 @@ mod_license=LGPL-2.1 # Generic release-dispatcher metadata. minecraft_version=1.20.1 -loader_name=forge -loader_code=1 +loader_name=neoforge +loader_code=2 java_version=17 java_toolchain_version=17.0.1+12 java_setup_version=17.0.1+12 @@ -27,9 +27,9 @@ gradle_java_version=17 curseforge_project_id=341834 curseforge_optionals=biomes-o-plenty,immersive-engineering,oh-the-biomes-weve-gone -# Forge 1.20.1 build coordinates. -forge_version=47.4.10 -forge_version_range=[47.4.10,48) +# NeoForge 1.20.1 retained the Forge-era userdev and metadata contracts. +neo_version=47.1.106 +neo_version_range=[47.1.99,48) loader_version_range=[47,) mcp_mappings_channel=official mcp_mappings_version=1.20.1 diff --git a/gradle/furniture-catalog.gradle b/gradle/furniture-catalog.gradle index 01eea9cd42..f1e43cbce4 100644 --- a/gradle/furniture-catalog.gradle +++ b/gradle/furniture-catalog.gradle @@ -317,7 +317,7 @@ tasks.register('updateFurnitureCatalog') { tasks.register('verifyFurnitureCatalog') { group = 'verification' - description = 'Regenerates furniture into a temporary tree and compares it byte-for-byte with committed output.' + description = 'Regenerates furniture into a temporary tree and compares canonical text with committed output.' inputs.file furnitureCatalogFile inputs.dir furnitureTemplateDirectory doLast { @@ -329,7 +329,9 @@ tasks.register('verifyFurnitureCatalog') { File expectedFile = new File(expectedRoot, relative) File actualFile = new File(project.projectDir, relative) if (!actualFile.isFile()) throw new GradleException("Missing catalog output: ${relative}") - if (Files.mismatch(expectedFile.toPath(), actualFile.toPath()) != -1L) { + String expectedText = expectedFile.getText('UTF-8').replace('\r\n', '\n') + String actualText = actualFile.getText('UTF-8').replace('\r\n', '\n') + if (expectedText != actualText) { throw new GradleException("Catalog output differs; run updateFurnitureCatalog: ${relative}") } } @@ -380,11 +382,31 @@ tasks.register('verifyOptionalIntegrationTextures') { } } + def catalog = readFurnitureCatalog() + Set unavailableTierSuffixes = new TreeSet<>() + catalog.integrations.each { integration -> + integrationFurnitureTiers(integration).findAll { it.minimumVersion }.each { tier -> + tier.woods.each { wood -> + Map textures = integrationTextureValues(integration, wood) + Set expectedEntries = [ + textures.externalLogTexture, + textures.externalLogTopTexture, + textures.externalPlanksTexture + ].collect { "assets/${integration.namespace}/textures/${it}.png" } as Set + if (!jarEntries[integration.namespace].containsAll(expectedEntries)) { + unavailableTierSuffixes.add( + "_${integration.integration}_${wood.wood}.json".toString()) + } + } + } + } + List blockModels = fileTree('src/main/resources/assets/ironagefurniture/models/block') { include '**/*.json' }.files.sort { it.path } Set referencedTextures = new TreeSet<>() blockModels.each { modelFile -> + if (unavailableTierSuffixes.any { modelFile.name.endsWith(it) }) return Map model = new JsonSlurper().parse(modelFile) as Map if (!(model.textures instanceof Map)) return ((Map) model.textures).values().findAll { it instanceof String }.each { value -> @@ -403,7 +425,6 @@ tasks.register('verifyOptionalIntegrationTextures') { } } - def catalog = readFurnitureCatalog() catalog.integrations.each { integration -> integrationFurnitureTiers(integration).each { tier -> tier.woods.each { wood -> @@ -414,6 +435,7 @@ tasks.register('verifyOptionalIntegrationTextures') { "${integration.namespace}:${textures.externalPlanksTexture}" ] as Set String suffix = "_${integration.integration}_${wood.wood}.json" + if (unavailableTierSuffixes.contains(suffix)) return List woodModels = blockModels.findAll { it.name.endsWith(suffix) } if (woodModels.isEmpty()) { throw new GradleException("No generated block models found for " + @@ -439,7 +461,8 @@ tasks.register('verifyOptionalIntegrationTextures') { throw new GradleException('No optional-mod texture references were audited') } logger.lifecycle("Verified ${referencedTextures.size()} optional-mod textures against " + - "${integrationJars.size()} pinned integration jars") + "${integrationJars.size()} pinned integration jars; skipped " + + "${unavailableTierSuffixes.size()} unavailable version-tier woods") } } diff --git a/gradle/verification/optional-integration-mods.json b/gradle/verification/optional-integration-mods.json index 4f3766b6fe..6e554ec2b8 100644 --- a/gradle/verification/optional-integration-mods.json +++ b/gradle/verification/optional-integration-mods.json @@ -4,26 +4,26 @@ { "modId": "biomesoplenty", "role": "integration", - "version": "19.0.0.96", - "file": "BiomesOPlenty-forge-1.20.1-19.0.0.96.jar", - "sha256": "84bbfe8fa25a453dae3a8e6c46e8e129f48f7d9df58e33bb7f3d28b90f591b3f", - "source": "https://modrinth.com/mod/biomes-o-plenty/version/jxUqRzSD" + "version": "18.0.0.598", + "file": "BiomesOPlenty-1.20.1-18.0.0.598.jar", + "sha256": "71eb3ddb35c1c2db6c15f80f001a6655508435a5f3828c7e2f666eada616c19d", + "source": "https://modrinth.com/mod/biomes-o-plenty/version/peO5lWzX" }, { "modId": "biomeswevegone", "role": "integration", - "version": "1.8.0-Forge", - "file": "Oh-The-Biomes-Weve-Gone-Forge-1.8.0.jar", - "sha256": "a6d21a08170dbfb6c3983b571bb2c1cd92aa93109f5b3a9b47101688dd81ea01", - "source": "https://modrinth.com/mod/oh-the-biomes-weve-gone/version/8L5cwpjz" + "version": "1.5.11-Forge", + "file": "Oh-The-Biomes-Weve-Gone-Forge-1.5.11.jar", + "sha256": "f672244dbe664119c8704199ced51ba5114ed7b49207669be2d405069c6b7e33", + "source": "https://modrinth.com/mod/oh-the-biomes-weve-gone/version/qj6FvHC7" }, { "modId": "immersiveengineering", "role": "integration", - "version": "10.2.0-183", - "file": "ImmersiveEngineering-1.20.1-10.2.0-183.jar", - "sha256": "061dd0e8e066dc39277736d5607a47a18fee4aff428de3363882f96e5c405aaa", - "source": "https://modrinth.com/mod/immersiveengineering/version/WUzj4tgJ" + "version": "1.20.1-10.1.0-171", + "file": "ImmersiveEngineering-1.20.1-10.1.0-171.jar", + "sha256": "bbb04345b8263b1de026521500e28ceba207470e0dd78c9f022331d7fbca42d2", + "source": "https://modrinth.com/mod/immersiveengineering/version/MAqXk6P8" }, { "modId": "terrablender", @@ -56,14 +56,6 @@ "file": "Oh-The-Trees-Youll-Grow-forge-1.20.1-1.7.0.jar", "sha256": "b3495f6b0ffa38bc5bf7189e29a27eef0c23b58540dc2f32033889c2943e5694", "source": "https://modrinth.com/mod/oh-the-trees-youll-grow/version/AAp1NdQX" - }, - { - "modId": "glitchcore", - "role": "dependency", - "version": "0.0.1.1", - "file": "GlitchCore-forge-1.20.1-0.0.1.1.jar", - "sha256": "dedd857b1ee356f002471ec97a2174c7d193d6d1aede60782d0bbe2c241b2a7f", - "source": "https://modrinth.com/mod/glitchcore/version/pYPZ5MNI" } ] } diff --git a/src/main/java/zone/moddev/mc/ironagefurniture/Ironagefurniture.java b/src/main/java/zone/moddev/mc/ironagefurniture/Ironagefurniture.java index 159d11252f..a519333dbb 100644 --- a/src/main/java/zone/moddev/mc/ironagefurniture/Ironagefurniture.java +++ b/src/main/java/zone/moddev/mc/ironagefurniture/Ironagefurniture.java @@ -4,10 +4,10 @@ import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; -import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import zone.moddev.mc.ironagefurniture.api.entity.Entities; import zone.moddev.mc.ironagefurniture.api.CreativeModeBreakTracker; import zone.moddev.mc.ironagefurniture.client.renderer.ClientHandler; @@ -43,10 +43,9 @@ public class Ironagefurniture public static final CommonProxy PROXY = DistExecutor.runForDist(() -> zone.moddev.mc.ironagefurniture.proxy.ClientProxy::new, () -> CommonProxy::new); private static final Logger LOGGER = LogUtils.getLogger(); - public Ironagefurniture(FMLJavaModLoadingContext context) { + public Ironagefurniture(IEventBus modEventBus) { LOGGER.info("Iron Age Furniture Mod is loading..."); - IEventBus modEventBus = context.getModEventBus(); ModVanillaChairs.REGISTER.register(modEventBus); ModVanillaShieldChairs.REGISTER.register(modEventBus); @@ -92,7 +91,8 @@ public Ironagefurniture(FMLJavaModLoadingContext context) { modEventBus.addListener(ClientHandler::onRegisterRenderers); }); - context.registerConfig(ModConfig.Type.COMMON, IronAgeFurnitureConfiguration.SPEC); + ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, + IronAgeFurnitureConfiguration.SPEC); } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 9a775c2bb1..88b2194694 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -8,7 +8,7 @@ modId="${mod_id}" version="${version}" displayName="${mod_name}" displayURL="https://www.curseforge.com/minecraft/mc-mods/iron-age-furniture" -credits="Cyanobacterium, SkyBlade1978, FoxNekitsune, the MMD Team, Forge, FML, and the mod authors" +credits="Cyanobacterium, SkyBlade1978, FoxNekitsune, the MMD Team, NeoForge, Forge, FML, and the mod authors" authors="${mod_authors}" description=''' ${mod_description} @@ -17,7 +17,7 @@ ${mod_description} [[dependencies.ironagefurniture]] modId="forge" mandatory=true - versionRange="${forge_version_range}" + versionRange="${neo_version_range}" ordering="NONE" side="BOTH" diff --git a/src/optionalIntegrationTest/java/zone/moddev/mc/ironagefurniture/fixture/OptionalIntegrationRecipeProbe.java b/src/optionalIntegrationTest/java/zone/moddev/mc/ironagefurniture/fixture/OptionalIntegrationRecipeProbe.java index 20a6e5f8da..265925f740 100644 --- a/src/optionalIntegrationTest/java/zone/moddev/mc/ironagefurniture/fixture/OptionalIntegrationRecipeProbe.java +++ b/src/optionalIntegrationTest/java/zone/moddev/mc/ironagefurniture/fixture/OptionalIntegrationRecipeProbe.java @@ -31,7 +31,7 @@ public final class OptionalIntegrationRecipeProbe private static final Logger LOGGER = LogUtils.getLogger(); private static final List REQUIRED_MODS = List.of( "biomesoplenty", "biomeswevegone", "immersiveengineering", - "terrablender", "corgilib", "geckolib", "ohthetreesyoullgrow", "glitchcore"); + "terrablender", "corgilib", "geckolib", "ohthetreesyoullgrow"); public OptionalIntegrationRecipeProbe() { @@ -54,7 +54,7 @@ private void serverStarted(ServerStartedEvent event) "biomesoplenty:maple_planks", "biomesoplenty:pine_planks") .stream() - .map(ResourceLocation::parse) + .map(ResourceLocation::new) .filter(ForgeRegistries.ITEMS::containsKey) .count(); int expectedBopCount = 390 + (extendedBopWoods * 39); @@ -147,8 +147,8 @@ private static List readExpectedEntries(String resourceName) require(parts.length == 2, "Invalid probe entry: " + line); String[] selector = parts[0].split("\\|", 2); ResourceLocation requiredItem = selector.length == 2 - ? ResourceLocation.parse(selector[1]) : null; - result.add(new ExpectedEntry(selector[0], ResourceLocation.parse(parts[1]), + ? new ResourceLocation(selector[1]) : null; + result.add(new ExpectedEntry(selector[0], new ResourceLocation(parts[1]), requiredItem)); } } diff --git a/src/optionalIntegrationTest/resources/META-INF/mods.toml b/src/optionalIntegrationTest/resources/META-INF/mods.toml index 0a5c615373..97be0eaacf 100644 --- a/src/optionalIntegrationTest/resources/META-INF/mods.toml +++ b/src/optionalIntegrationTest/resources/META-INF/mods.toml @@ -10,7 +10,7 @@ displayName="Iron Age Furniture Optional Integration Probe" [[dependencies.ironagefurnitureintegrationprobe]] modId="forge" mandatory=true -versionRange="[47.4.10,48)" +versionRange="[47.1.99,48)" ordering="NONE" side="SERVER" @@ -24,7 +24,7 @@ side="SERVER" [[dependencies.ironagefurnitureintegrationprobe]] modId="ironagefurniture" mandatory=true -versionRange="[0.3.0.120011]" +versionRange="[0.3.0.120012]" ordering="AFTER" side="SERVER" @@ -38,13 +38,13 @@ side="SERVER" [[dependencies.ironagefurnitureintegrationprobe]] modId="biomeswevegone" mandatory=true -versionRange="[1.8.0]" +versionRange="[1.5.11]" ordering="AFTER" side="SERVER" [[dependencies.ironagefurnitureintegrationprobe]] modId="immersiveengineering" mandatory=true -versionRange="[1.20.1-10.2.0-183]" +versionRange="[1.20.1-10.1.0-171]" ordering="AFTER" side="SERVER"