User: joe Date: 07 Nov 26 09:16 Revision: 4ba0c277198196cf10ee47953b5289c2bdabe7ef Summary: Fix Forge/NeoForge versions where Minecraft version doesn't start with 1. Fixes #2652 TeamCity URL: http://ci.mcdev.io:80/viewModification.html?tab=vcsModificationFiles&modId=10677&personal=false Index: src/main/kotlin/creator/custom/model/ArchitecturyVersionsModel.kt =================================================================== --- src/main/kotlin/creator/custom/model/ArchitecturyVersionsModel.kt (revision ad1ce54bd00285f9e5165aa081836fe009e85752) +++ src/main/kotlin/creator/custom/model/ArchitecturyVersionsModel.kt (revision 4ba0c277198196cf10ee47953b5289c2bdabe7ef) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * Copyright (C) 2026 minecraft-dev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published @@ -21,6 +21,7 @@ package com.demonwav.mcdev.creator.custom.model import com.demonwav.mcdev.platform.fabric.util.FabricVersions +import com.demonwav.mcdev.util.MinecraftVersions import com.demonwav.mcdev.util.SemanticVersion @TemplateApi @@ -40,18 +41,8 @@ override val minecraftVersion: SemanticVersion = minecraft - val minecraftNext by lazy { - val mcNext = when (val part = minecraft.parts.getOrNull(1)) { - // Mimics the code used to get the next Minecraft version in Forge's MDK - // https://github.com/MinecraftForge/MinecraftForge/blob/0ff8a596fc1ef33d4070be89dd5cb4851f93f731/build.gradle#L884 - is SemanticVersion.Companion.VersionPart.ReleasePart -> (part.version + 1).toString() - null -> "?" - else -> part.versionString - } + val minecraftNext by lazy { MinecraftVersions.nextMajorVersion(minecraft) } - "1.$mcNext" - } - val hasForge: Boolean by lazy { !forge?.parts.isNullOrEmpty() } val forgeSpec: String? by lazy { forge?.parts?.getOrNull(0)?.versionString } Index: src/main/kotlin/creator/custom/model/ForgeVersions.kt =================================================================== --- src/main/kotlin/creator/custom/model/ForgeVersions.kt (revision ad1ce54bd00285f9e5165aa081836fe009e85752) +++ src/main/kotlin/creator/custom/model/ForgeVersions.kt (revision 4ba0c277198196cf10ee47953b5289c2bdabe7ef) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * Copyright (C) 2026 minecraft-dev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published @@ -20,6 +20,7 @@ package com.demonwav.mcdev.creator.custom.model +import com.demonwav.mcdev.util.MinecraftVersions import com.demonwav.mcdev.util.SemanticVersion @TemplateApi @@ -29,16 +30,6 @@ ) : HasMinecraftVersion { override val minecraftVersion = minecraft - val minecraftNext by lazy { - val mcNext = when (val part = minecraft.parts.getOrNull(1)) { - // Mimics the code used to get the next Minecraft version in Forge's MDK - // https://github.com/MinecraftForge/MinecraftForge/blob/0ff8a596fc1ef33d4070be89dd5cb4851f93f731/build.gradle#L884 - is SemanticVersion.Companion.VersionPart.ReleasePart -> (part.version + 1).toString() - null -> "?" - else -> part.versionString - } - - "1.$mcNext" - } + val minecraftNext by lazy { MinecraftVersions.nextMajorVersion(minecraft) } val forgeSpec by lazy { forge.parts[0].versionString } } Index: src/main/kotlin/creator/custom/model/NeoForgeVersions.kt =================================================================== --- src/main/kotlin/creator/custom/model/NeoForgeVersions.kt (revision ad1ce54bd00285f9e5165aa081836fe009e85752) +++ src/main/kotlin/creator/custom/model/NeoForgeVersions.kt (revision 4ba0c277198196cf10ee47953b5289c2bdabe7ef) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * Copyright (C) 2026 minecraft-dev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published @@ -20,6 +20,7 @@ package com.demonwav.mcdev.creator.custom.model +import com.demonwav.mcdev.util.MinecraftVersions import com.demonwav.mcdev.util.SemanticVersion @TemplateApi @@ -31,16 +32,6 @@ ) : HasMinecraftVersion { override val minecraftVersion = minecraft - val minecraftNext by lazy { - val mcNext = when (val part = minecraft.parts.getOrNull(1)) { - // Mimics the code used to get the next Minecraft version in Forge's MDK - // https://github.com/MinecraftForge/MinecraftForge/blob/0ff8a596fc1ef33d4070be89dd5cb4851f93f731/build.gradle#L884 - is SemanticVersion.Companion.VersionPart.ReleasePart -> (part.version + 1).toString() - null -> "?" - else -> part.versionString - } - - "1.$mcNext" - } + val minecraftNext by lazy { MinecraftVersions.nextMajorVersion(minecraft) } val neoforgeSpec by lazy { neoforge.parts[0].versionString } } Index: src/main/kotlin/platform/architectury/creator/asset-steps.kt =================================================================== --- src/main/kotlin/platform/architectury/creator/asset-steps.kt (revision ad1ce54bd00285f9e5165aa081836fe009e85752) +++ src/main/kotlin/platform/architectury/creator/asset-steps.kt (revision 4ba0c277198196cf10ee47953b5289c2bdabe7ef) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * Copyright (C) 2026 minecraft-dev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published @@ -46,6 +46,7 @@ import com.demonwav.mcdev.platform.forge.util.ForgePackDescriptor import com.demonwav.mcdev.platform.forge.version.ForgeVersion import com.demonwav.mcdev.util.MinecraftTemplates +import com.demonwav.mcdev.util.MinecraftVersions import com.demonwav.mcdev.util.SemanticVersion import com.demonwav.mcdev.util.toJavaClassName import com.demonwav.mcdev.util.toPackageName @@ -90,14 +91,6 @@ val description = data.getUserData(DescriptionStep.KEY) ?: "" val hasDisplayTestInManifest = forgeVersion >= ForgeConstants.DISPLAY_TEST_MANIFEST_VERSION - val nextMcVersion = when (val part = mcVersion.parts.getOrNull(1)) { - // Mimics the code used to get the next Minecraft version in Forge's MDK - // https://github.com/MinecraftForge/MinecraftForge/blob/0ff8a596fc1ef33d4070be89dd5cb4851f93f731/build.gradle#L884 - is SemanticVersion.Companion.VersionPart.ReleasePart -> (part.version + 1).toString() - null -> "?" - else -> part.versionString - } - assets.addAssets( GeneratorEmptyDirectory("common/src/main/java"), GeneratorEmptyDirectory("common/src/main/resources"), @@ -122,7 +115,7 @@ "DISPLAY_TEST" to hasDisplayTestInManifest, "FORGE_SPEC_VERSION" to forgeVersion.parts[0].versionString, "MC_VERSION" to mcVersion, - "MC_NEXT_VERSION" to "1.$nextMcVersion", + "MC_NEXT_VERSION" to MinecraftVersions.nextMajorVersion(mcVersion), "LICENSE" to license, "DESCRIPTION" to description, "MOD_DESCRIPTION" to description, Index: src/main/kotlin/platform/forge/creator/asset-steps.kt =================================================================== --- src/main/kotlin/platform/forge/creator/asset-steps.kt (revision ad1ce54bd00285f9e5165aa081836fe009e85752) +++ src/main/kotlin/platform/forge/creator/asset-steps.kt (revision 4ba0c277198196cf10ee47953b5289c2bdabe7ef) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * Copyright (C) 2026 minecraft-dev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published @@ -44,7 +44,6 @@ import com.demonwav.mcdev.platform.forge.util.ForgePackDescriptor import com.demonwav.mcdev.util.MinecraftTemplates import com.demonwav.mcdev.util.MinecraftVersions -import com.demonwav.mcdev.util.SemanticVersion import com.intellij.ide.wizard.NewProjectWizardStep import com.intellij.openapi.project.Project @@ -65,14 +64,6 @@ val authors = data.getUserData(AuthorsStep.KEY) ?: emptyList() val website = data.getUserData(WebsiteStep.KEY) ?: "" - val nextMcVersion = when (val part = mcVersion.parts.getOrNull(1)) { - // Mimics the code used to get the next Minecraft version in Forge's MDK - // https://github.com/MinecraftForge/MinecraftForge/blob/0ff8a596fc1ef33d4070be89dd5cb4851f93f731/build.gradle#L884 - is SemanticVersion.Companion.VersionPart.ReleasePart -> (part.version + 1).toString() - null -> "?" - else -> part.versionString - } - val packDescriptor = ForgePackDescriptor.forMcVersion(mcVersion) ?: ForgePackDescriptor.FORMAT_3 val additionalPackData = ForgePackAdditionalData.forMcVersion(mcVersion) @@ -87,7 +78,7 @@ "DISPLAY_TEST" to (forgeVersion >= ForgeConstants.DISPLAY_TEST_MANIFEST_VERSION), "FORGE_SPEC_VERSION" to forgeVersion.parts[0].versionString, "MC_VERSION" to mcVersion, - "MC_NEXT_VERSION" to "1.$nextMcVersion", + "MC_NEXT_VERSION" to MinecraftVersions.nextMajorVersion(mcVersion), "LICENSE" to license, "DESCRIPTION" to description, "PACK_FORMAT" to packDescriptor.format, Index: src/main/kotlin/platform/mcp/gradle/datahandler/McpModelNG7Handler.kt =================================================================== --- src/main/kotlin/platform/mcp/gradle/datahandler/McpModelNG7Handler.kt (revision ad1ce54bd00285f9e5165aa081836fe009e85752) +++ src/main/kotlin/platform/mcp/gradle/datahandler/McpModelNG7Handler.kt (revision 4ba0c277198196cf10ee47953b5289c2bdabe7ef) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * Copyright (C) 2026 minecraft-dev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published @@ -25,6 +25,7 @@ import com.demonwav.mcdev.platform.mcp.gradle.McpModelData import com.demonwav.mcdev.platform.mcp.gradle.tooling.McpModelNG7 import com.demonwav.mcdev.platform.mcp.srg.SrgType +import com.demonwav.mcdev.platform.neoforge.version.NeoForgeVersion import com.demonwav.mcdev.util.runWriteTaskLater import com.intellij.openapi.externalSystem.model.DataNode import com.intellij.openapi.externalSystem.model.project.ModuleData @@ -45,7 +46,7 @@ val data = resolverCtx.getExtraProject(gradleModule, McpModelNG7::class.java) ?: return val state = McpModuleSettings.State( - "1." + data.neoForgeVersion.substringBeforeLast('.').removeSuffix(".0"), + NeoForgeVersion.getMinecraftVersion(data.neoForgeVersion), null, data.mappingsFile.absolutePath, SrgType.TSRG, Index: src/main/kotlin/platform/mcp/gradle/datahandler/McpModelNMDHandler.kt =================================================================== --- src/main/kotlin/platform/mcp/gradle/datahandler/McpModelNMDHandler.kt (revision ad1ce54bd00285f9e5165aa081836fe009e85752) +++ src/main/kotlin/platform/mcp/gradle/datahandler/McpModelNMDHandler.kt (revision 4ba0c277198196cf10ee47953b5289c2bdabe7ef) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * Copyright (C) 2026 minecraft-dev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published @@ -25,6 +25,7 @@ import com.demonwav.mcdev.platform.mcp.gradle.McpModelData import com.demonwav.mcdev.platform.mcp.gradle.tooling.McpModelNMD import com.demonwav.mcdev.platform.mcp.srg.SrgType +import com.demonwav.mcdev.platform.neoforge.version.NeoForgeVersion import com.demonwav.mcdev.util.runWriteTaskLater import com.intellij.openapi.externalSystem.model.DataNode import com.intellij.openapi.externalSystem.model.project.ModuleData @@ -45,7 +46,7 @@ val data = resolverCtx.getExtraProject(gradleModule, McpModelNMD::class.java) ?: return val minecraftVersion = when { - data.neoForgeVersion != null -> "1." + data.neoForgeVersion!!.substringBeforeLast('.').removeSuffix(".0") + data.neoForgeVersion != null -> NeoForgeVersion.getMinecraftVersion(data.neoForgeVersion!!) data.neoFormVersion != null -> data.neoFormVersion!!.substringBeforeLast('-').removeSuffix(".0") else -> null } Index: src/main/kotlin/platform/neoforge/creator/asset-steps.kt =================================================================== --- src/main/kotlin/platform/neoforge/creator/asset-steps.kt (revision ad1ce54bd00285f9e5165aa081836fe009e85752) +++ src/main/kotlin/platform/neoforge/creator/asset-steps.kt (revision 4ba0c277198196cf10ee47953b5289c2bdabe7ef) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * Copyright (C) 2026 minecraft-dev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published @@ -42,7 +42,6 @@ import com.demonwav.mcdev.platform.forge.util.ForgePackDescriptor import com.demonwav.mcdev.util.MinecraftTemplates import com.demonwav.mcdev.util.MinecraftVersions -import com.demonwav.mcdev.util.SemanticVersion import com.intellij.ide.wizard.NewProjectWizardStep import com.intellij.openapi.project.Project @@ -64,13 +63,6 @@ val website = data.getUserData(WebsiteStep.KEY) ?: "" val useMixins = data.getUserData(UseMixinsStep.KEY) ?: false - val nextMcVersion = when (val part = mcVersion.parts.getOrNull(1)) { - // Extract the major version and increment (1.20.4 -> 1.21), as is done manually in the MDK - is SemanticVersion.Companion.VersionPart.ReleasePart -> (part.version + 1).toString() - null -> "?" - else -> part.versionString - } - val packDescriptor = ForgePackDescriptor.forMcVersion(mcVersion) ?: ForgePackDescriptor.FORMAT_3 assets.addTemplateProperties( @@ -82,7 +74,7 @@ "MOD_VERSION" to buildSystemProps.version, "NEOFORGE_SPEC_VERSION" to forgeVersion.parts[0].versionString, "MC_VERSION" to mcVersion, - "MC_NEXT_VERSION" to "1.$nextMcVersion", + "MC_NEXT_VERSION" to MinecraftVersions.nextMajorVersion(mcVersion), "LICENSE" to license, "DESCRIPTION" to description, "MIXIN_CONFIG" to if (useMixins) "$modId.mixins.json" else null, Index: src/main/kotlin/platform/neoforge/version/NeoForgeVersion.kt =================================================================== --- src/main/kotlin/platform/neoforge/version/NeoForgeVersion.kt (revision ad1ce54bd00285f9e5165aa081836fe009e85752) +++ src/main/kotlin/platform/neoforge/version/NeoForgeVersion.kt (revision 4ba0c277198196cf10ee47953b5289c2bdabe7ef) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * Copyright (C) 2026 minecraft-dev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published @@ -27,44 +27,47 @@ class NeoForgeVersion private constructor(val versions: List) { + private val versionsByMc: Map> by lazy { + versions.groupBy { getMinecraftVersion(it)?.let(SemanticVersion::tryParse) } + .filterKeys { it != null } + .mapKeys { it.key!! } + } + val sortedMcVersions: List by lazy { - val version = versions.asSequence() - .map { it.substringBeforeLast('.') } - .distinct() - .mapNotNull { - val shortVersion = SemanticVersion.tryParse(it) - if (shortVersion != null) { - val parts = shortVersion.parts.toMutableList() - // Insert the '1.' part to the base neoforge version - parts.add(0, SemanticVersion.Companion.VersionPart.ReleasePart(1, "1")) - SemanticVersion(parts) - } else null - }.distinct() - .sortedDescending() - .toList() - return@lazy version + versionsByMc.keys.sortedDescending() } fun getNeoForgeVersions(mcVersion: SemanticVersion): List { - val versionText = mcVersion.toString() - // Drop the 1. part of the mc version - var shortMcVersion = versionText.substringAfter('.') - if (!shortMcVersion.contains('.')) { - // Ensure we have the .0 part - shortMcVersion = "$shortMcVersion.0" - } - val toList = versions.asSequence() - .filter { it.substringBeforeLast('.') == shortMcVersion } + return versionsByMc[mcVersion].orEmpty().asSequence() .mapNotNull(SemanticVersion::tryParse) .sortedDescending() .take(50) .toList() - return toList } companion object { private val LOGGER = logger() + /** + * Returns the Minecraft version a NeoForge version targets, or `null` if it can't be determined. + * + * Before Minecraft 26.1, NeoForge versions dropped the leading `1.` of the Minecraft version + * (e.g. `21.1.77` -> `1.21.1`). Since 26.1, NeoForge versions start with the full Minecraft version, + * always including the patch part (e.g. `26.1.0.19-beta` -> `26.1`, `26.1.1.6-beta` -> `26.1.1`). + */ + fun getMinecraftVersion(neoForgeVersion: String): String? { + val base = neoForgeVersion.substringBeforeLast('.') + val major = base.substringBefore('.').toIntOrNull() ?: return null + return if (major >= 26) { + if (base.count { it == '.' } != 2) { + return null + } + base.removeSuffix(".0") + } else { + "1." + base.removeSuffix(".0") + } + } + suspend fun downloadData(): NeoForgeVersion? { try { val url = "https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml" Index: src/main/kotlin/util/MinecraftVersions.kt =================================================================== --- src/main/kotlin/util/MinecraftVersions.kt (revision ad1ce54bd00285f9e5165aa081836fe009e85752) +++ src/main/kotlin/util/MinecraftVersions.kt (revision 4ba0c277198196cf10ee47953b5289c2bdabe7ef) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * Copyright (C) 2026 minecraft-dev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published @@ -57,4 +57,20 @@ minecraftVersion <= MC1_21_11 -> JavaSdkVersion.JDK_21 else -> JavaSdkVersion.JDK_25 } + + /** + * Returns the version of the next major Minecraft release, mimicking the code used in Forge's MDK. + * For example 1.20.4 -> 1.21, and 26.1 -> 26.2 with the year-based versioning scheme. + * + * See https://github.com/MinecraftForge/MinecraftForge/blob/0ff8a596fc1ef33d4070be89dd5cb4851f93f731/build.gradle#L884 + */ + fun nextMajorVersion(minecraftVersion: SemanticVersion): String { + val first = minecraftVersion.parts.getOrNull(0)?.versionString ?: "1" + val mcNext = when (val part = minecraftVersion.parts.getOrNull(1)) { + is SemanticVersion.Companion.VersionPart.ReleasePart -> (part.version + 1).toString() + null -> "?" + else -> part.versionString -} + } + return "$first.$mcNext" + } +}