From a62cd60033682be7f370841704adff9b532daeba Mon Sep 17 00:00:00 2001 From: RedNesto Date: Wed, 17 Apr 2024 12:18:42 +0200 Subject: [PATCH 01/22] Fix #2278 1.20.5 requires Java 21 --- src/main/kotlin/platform/forge/util/ForgePackDescriptor.kt | 4 +++- src/main/kotlin/util/MinecraftVersions.kt | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/platform/forge/util/ForgePackDescriptor.kt b/src/main/kotlin/platform/forge/util/ForgePackDescriptor.kt index 4678979d5..0b984a288 100644 --- a/src/main/kotlin/platform/forge/util/ForgePackDescriptor.kt +++ b/src/main/kotlin/platform/forge/util/ForgePackDescriptor.kt @@ -55,6 +55,7 @@ data class ForgePackDescriptor(val format: Int, val comment: String) { val FORMAT_15 = ForgePackDescriptor(15, "") val FORMAT_18 = ForgePackDescriptor(18, "") val FORMAT_26 = ForgePackDescriptor(26, "") + val FORMAT_41 = ForgePackDescriptor(26, "") // See https://minecraft.gamepedia.com/Tutorials/Creating_a_resource_pack#.22pack_format.22 fun forMcVersion(version: SemanticVersion): ForgePackDescriptor? = when { @@ -69,7 +70,8 @@ data class ForgePackDescriptor(val format: Int, val comment: String) { version < MinecraftVersions.MC1_20 -> FORMAT_12 version < MinecraftVersions.MC1_20_2 -> FORMAT_15 version < MinecraftVersions.MC1_20_3 -> FORMAT_18 - version >= MinecraftVersions.MC1_20_3 -> FORMAT_26 + version < MinecraftVersions.MC1_20_5 -> FORMAT_26 + version >= MinecraftVersions.MC1_20_5 -> FORMAT_41 else -> null } } diff --git a/src/main/kotlin/util/MinecraftVersions.kt b/src/main/kotlin/util/MinecraftVersions.kt index 8635b79e8..6c446635e 100644 --- a/src/main/kotlin/util/MinecraftVersions.kt +++ b/src/main/kotlin/util/MinecraftVersions.kt @@ -38,10 +38,12 @@ object MinecraftVersions { val MC1_20_2 = SemanticVersion.release(1, 20, 2) val MC1_20_3 = SemanticVersion.release(1, 20, 3) val MC1_20_4 = SemanticVersion.release(1, 20, 4) + val MC1_20_5 = SemanticVersion.release(1, 20, 5) fun requiredJavaVersion(minecraftVersion: SemanticVersion) = when { minecraftVersion <= MC1_16_5 -> JavaSdkVersion.JDK_1_8 minecraftVersion <= MC1_17_1 -> JavaSdkVersion.JDK_16 - else -> JavaSdkVersion.JDK_17 + minecraftVersion <= MC1_20_4 -> JavaSdkVersion.JDK_17 + else -> JavaSdkVersion.JDK_21 } } From b2c6e44eeacb203cc3fbbebdf336e261031e4015 Mon Sep 17 00:00:00 2001 From: senseiwells <66843746+senseiwells@users.noreply.github.com> Date: Wed, 17 Apr 2024 12:13:44 +0100 Subject: [PATCH 02/22] Suppress `ReferenceToMixin` for `@Dynamic` (#2280) * Suppress `ReferenceToMixin` for `@Dynamic` * Fix formatting for `MixinClassCannotBeReferencedSuppressor` * Move logic to `MixinClassReferenceInspection` --- .../mixin/inspection/MixinClassReferenceInspection.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/kotlin/platform/mixin/inspection/MixinClassReferenceInspection.kt b/src/main/kotlin/platform/mixin/inspection/MixinClassReferenceInspection.kt index 43ae79ea9..4d91abbf3 100644 --- a/src/main/kotlin/platform/mixin/inspection/MixinClassReferenceInspection.kt +++ b/src/main/kotlin/platform/mixin/inspection/MixinClassReferenceInspection.kt @@ -20,14 +20,17 @@ package com.demonwav.mcdev.platform.mixin.inspection +import com.demonwav.mcdev.platform.mixin.util.MixinConstants import com.demonwav.mcdev.platform.mixin.util.isAccessorMixin import com.demonwav.mcdev.platform.mixin.util.isMixin import com.demonwav.mcdev.util.findContainingClass import com.intellij.codeInspection.ProblemsHolder import com.intellij.psi.JavaElementVisitor +import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiClassType import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiTypeElement +import com.intellij.psi.util.parentOfType class MixinClassReferenceInspection : MixinInspection() { @@ -58,6 +61,11 @@ class MixinClassReferenceInspection : MixinInspection() { return } + val annotation = type.parentOfType() + if (annotation != null && annotation.hasQualifiedName(MixinConstants.Annotations.DYNAMIC)) { + return + } + holder.registerProblem(type, "Mixin class cannot be referenced directly") } } From 1e828e178ab8423c8225d7d75ce2e6dd7b5ab574 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Wed, 17 Apr 2024 13:41:35 +0200 Subject: [PATCH 03/22] Update NeoForge MDK Up until https://github.com/neoforged/MDK/commit/6074a78fba8aea8453e10017244da6c385c78109 --- .../creator/buildsystem/gradle-steps.kt | 2 +- .../platform/fabric/creator/gradle-steps.kt | 2 +- .../platform/neoforge/creator/gradle-steps.kt | 2 +- .../j2ee/fabric/fabric_build.gradle.ft | 2 +- .../j2ee/neoforge/NeoForge Main Class.java.ft | 6 +++- .../j2ee/neoforge/NeoForge build.gradle.ft | 28 ++++++++++++++----- .../j2ee/neoforge/NeoForge mods.toml.ft | 17 +++++++++-- .../j2ee/neoforge/NeoForge settings.gradle.ft | 2 +- 8 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/creator/buildsystem/gradle-steps.kt b/src/main/kotlin/creator/buildsystem/gradle-steps.kt index ba7f2d531..8667ae158 100644 --- a/src/main/kotlin/creator/buildsystem/gradle-steps.kt +++ b/src/main/kotlin/creator/buildsystem/gradle-steps.kt @@ -61,7 +61,7 @@ import org.jetbrains.plugins.gradle.service.execution.GradleRunConfiguration import org.jetbrains.plugins.gradle.service.project.open.canLinkAndRefreshGradleProject import org.jetbrains.plugins.gradle.service.project.open.linkAndRefreshGradleProject -val DEFAULT_GRADLE_VERSION = SemanticVersion.release(8, 5) +val DEFAULT_GRADLE_VERSION = SemanticVersion.release(8, 7) val GRADLE_VERSION_KEY = Key.create("mcdev.gradleVersion") fun FixedAssetsNewProjectWizardStep.addGradleWrapperProperties(project: Project) { diff --git a/src/main/kotlin/platform/fabric/creator/gradle-steps.kt b/src/main/kotlin/platform/fabric/creator/gradle-steps.kt index c3fe4e8d4..2f4943269 100644 --- a/src/main/kotlin/platform/fabric/creator/gradle-steps.kt +++ b/src/main/kotlin/platform/fabric/creator/gradle-steps.kt @@ -57,7 +57,7 @@ class FabricGradleFilesStep(parent: NewProjectWizardStep) : AbstractLongRunningA val mcVersion = data.getUserData(FabricVersionChainStep.MC_VERSION_KEY) ?: return val yarnVersion = data.getUserData(FabricVersionChainStep.YARN_VERSION_KEY) ?: return val loaderVersion = data.getUserData(FabricVersionChainStep.LOADER_VERSION_KEY) ?: return - val loomVersion = "1.5-SNAPSHOT" + val loomVersion = "1.6-SNAPSHOT" val javaVersion = findStep().preferredJdk.ordinal val apiVersion = data.getUserData(FabricVersionChainStep.API_VERSION_KEY) val officialMappings = data.getUserData(FabricVersionChainStep.OFFICIAL_MAPPINGS_KEY) ?: false diff --git a/src/main/kotlin/platform/neoforge/creator/gradle-steps.kt b/src/main/kotlin/platform/neoforge/creator/gradle-steps.kt index f64e676be..26d3f3f73 100644 --- a/src/main/kotlin/platform/neoforge/creator/gradle-steps.kt +++ b/src/main/kotlin/platform/neoforge/creator/gradle-steps.kt @@ -49,7 +49,7 @@ import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VfsUtil import com.intellij.util.lang.JavaVersion -private val ngWrapperVersion = SemanticVersion.release(8, 4) +private val ngWrapperVersion = SemanticVersion.release(8, 6) const val MAGIC_RUN_CONFIGS_FILE = ".hello_from_mcdev" diff --git a/src/main/resources/fileTemplates/j2ee/fabric/fabric_build.gradle.ft b/src/main/resources/fileTemplates/j2ee/fabric/fabric_build.gradle.ft index be6e106e7..7048c36b3 100644 --- a/src/main/resources/fileTemplates/j2ee/fabric/fabric_build.gradle.ft +++ b/src/main/resources/fileTemplates/j2ee/fabric/fabric_build.gradle.ft @@ -79,7 +79,7 @@ jar { // configure the maven publication publishing { publications { - mavenJava(MavenPublication) { + create("mavenJava", MavenPublication) { from components.java } } diff --git a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge Main Class.java.ft b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge Main Class.java.ft index 15b3f4489..28c32e3b3 100644 --- a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge Main Class.java.ft +++ b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge Main Class.java.ft @@ -4,6 +4,7 @@ import com.mojang.logging.LogUtils; import net.minecraft.client.Minecraft; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; +import net.minecraft.network.chat.Component; import net.minecraft.world.food.FoodProperties; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.CreativeModeTab; @@ -56,6 +57,7 @@ public class ${CLASS_NAME} // Creates a creative tab with the id "${MOD_ID}:example_tab" for the example item, that is placed after the combat tab public static final DeferredHolder EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab", () -> CreativeModeTab.builder() + .title(Component.translatable("itemGroup.${MOD_ID}")) .withTabsBefore(CreativeModeTabs.COMBAT) .icon(() -> EXAMPLE_ITEM.get().getDefaultInstance()) .displayItems((parameters, output) -> { @@ -76,7 +78,9 @@ public class ${CLASS_NAME} // Register the Deferred Register to the mod event bus so tabs get registered CREATIVE_MODE_TABS.register(modEventBus); - // Register ourselves for server and other game events we are interested in + // Register ourselves for server and other game events we are interested in. + // Note that this is necessary if and only if we want *this* class (ExampleMod) to respond directly to events. + // Do not add this line if there are no @SubscribeEvent-annotated functions in this class, like onServerStarting() below. NeoForge.EVENT_BUS.register(this); // Register the item to a creative tab diff --git a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge build.gradle.ft b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge build.gradle.ft index 9f6eb7544..6989df8d1 100644 --- a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge build.gradle.ft +++ b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge build.gradle.ft @@ -3,7 +3,7 @@ plugins { id 'eclipse' id 'idea' id 'maven-publish' - id 'net.neoforged.gradle.userdev' version '7.0.74' + id 'net.neoforged.gradle.userdev' version '7.0.97' } version = mod_version @@ -109,16 +109,22 @@ dependencies { // See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html tasks.withType(ProcessResources).configureEach { var replaceProperties = [ - minecraft_version : minecraft_version, minecraft_version_range: minecraft_version_range, - neo_version : neo_version, neo_version_range: neo_version_range, - loader_version_range: loader_version_range, - mod_id : mod_id, mod_name: mod_name, mod_license: mod_license, mod_version: mod_version, - mod_authors : mod_authors, mod_description: mod_description, + minecraft_version : minecraft_version, + minecraft_version_range: minecraft_version_range, + neo_version : neo_version, + neo_version_range : neo_version_range, + loader_version_range : loader_version_range, + mod_id : mod_id, + mod_name : mod_name, + mod_license : mod_license, + mod_version : mod_version, + mod_authors : mod_authors, + mod_description : mod_description ] inputs.properties replaceProperties filesMatching(['META-INF/mods.toml']) { - expand replaceProperties + [project: project] + expand replaceProperties } } @@ -139,3 +145,11 @@ publishing { tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation } + +// IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior. +idea { + module { + downloadSources = true + downloadJavadoc = true + } +} diff --git a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge mods.toml.ft b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge mods.toml.ft index e5439855c..7115e3298 100644 --- a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge mods.toml.ft +++ b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge mods.toml.ft @@ -46,13 +46,26 @@ authors="${mod_authors}" #optional # IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. #displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) +# The description text for the mod (multi line!) (#mandatory) +description='''${mod_description}''' + +#set ( $h = "#" ) +# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded. #if (${MIXIN_CONFIG}) [[mixins]] config="${MIXIN_CONFIG}" +#else +${h}[[mixins]] +#config="${mod_id}.mixins.json" #end -# The description text for the mod (multi line!) (#mandatory) -description='''${mod_description}''' +# The [[accessTransformers]] block allows you to declare where your AT file is. +# If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg +${h}[[accessTransformers]] +#file="META-INF/accesstransformer.cfg" + +# The coremods config file path is not configurable and is always loaded from META-INF/coremods.json + # A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. [[dependencies."${mod_id}"]] #optional # the modid of the dependency diff --git a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge settings.gradle.ft b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge settings.gradle.ft index b359a59d3..ada876e2e 100644 --- a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge settings.gradle.ft +++ b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge settings.gradle.ft @@ -7,5 +7,5 @@ pluginManagement { } plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' } From 7c0ec1273812668c34e0e73e72c3be13b2da3235 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Thu, 25 Apr 2024 17:01:45 +0200 Subject: [PATCH 04/22] Small adjustment to fabric's build.gradle template Following fabric-example-mod change for 1.20.5 --- .../resources/fileTemplates/j2ee/fabric/fabric_build.gradle.ft | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/fileTemplates/j2ee/fabric/fabric_build.gradle.ft b/src/main/resources/fileTemplates/j2ee/fabric/fabric_build.gradle.ft index 7048c36b3..e49c87cce 100644 --- a/src/main/resources/fileTemplates/j2ee/fabric/fabric_build.gradle.ft +++ b/src/main/resources/fileTemplates/j2ee/fabric/fabric_build.gradle.ft @@ -80,6 +80,7 @@ jar { publishing { publications { create("mavenJava", MavenPublication) { + artifactId = project.archives_base_name from components.java } } From 0a29cf0bb5a759458818df03939fbd0ccf813e86 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Thu, 25 Apr 2024 17:05:35 +0200 Subject: [PATCH 05/22] Version 1.7.5 --- gradle.properties | 2 +- readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 1dd79f806..8e27d9470 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,7 +24,7 @@ kotlin.code.style=official ideaVersion = 2023.2.2 ideaVersionName = 2023.2.2 -coreVersion = 1.7.4 +coreVersion = 1.7.5 downloadIdeaSources = true pluginTomlVersion = 232.8660.88 diff --git a/readme.md b/readme.md index 423f996bb..f70860d0e 100644 --- a/readme.md +++ b/readme.md @@ -31,7 +31,7 @@ Minecraft Development for IntelliJ -Info and Documentation [![Current Release](https://img.shields.io/badge/release-1.7.4-orange.svg?style=flat-square)](https://plugins.jetbrains.com/plugin/8327) +Info and Documentation [![Current Release](https://img.shields.io/badge/release-1.7.5-orange.svg?style=flat-square)](https://plugins.jetbrains.com/plugin/8327) ---------------------- From 5992a6a569f899b6d3b9b77108ff66dce74a1e0f Mon Sep 17 00:00:00 2001 From: 7410 <85879080+O7410@users.noreply.github.com> Date: Sat, 27 Apr 2024 14:40:15 +0300 Subject: [PATCH 06/22] change error requirements from public to non-private (#2289) untested, I just used the web editor --- .../platform/mixin/inspection/StaticMemberInspection.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/platform/mixin/inspection/StaticMemberInspection.kt b/src/main/kotlin/platform/mixin/inspection/StaticMemberInspection.kt index a1395319d..f747cdb57 100644 --- a/src/main/kotlin/platform/mixin/inspection/StaticMemberInspection.kt +++ b/src/main/kotlin/platform/mixin/inspection/StaticMemberInspection.kt @@ -37,7 +37,7 @@ import com.intellij.psi.PsiModifier class StaticMemberInspection : MixinInspection() { override fun getStaticDescription() = - "A mixin class does not exist at runtime, and thus having them public does not make sense. " + + "A mixin class does not exist at runtime, and thus having them not private does not make sense. " + "Make the field/method private instead." override fun buildVisitor(holder: ProblemsHolder): PsiElementVisitor = Visitor(holder) @@ -56,7 +56,7 @@ class StaticMemberInspection : MixinInspection() { if (isProblematic(member)) { holder.registerProblem( member, - "Public static members are not allowed in Mixin classes", + "Non-private static members are not allowed in Mixin classes", QuickFixFactory.getInstance().createModifierListFix(member, PsiModifier.PRIVATE, true, false), ) } @@ -70,7 +70,7 @@ class StaticMemberInspection : MixinInspection() { val modifiers = member.modifierList!! - return modifiers.hasModifierProperty(PsiModifier.PUBLIC) && + return !modifiers.hasModifierProperty(PsiModifier.PRIVATE) && modifiers.hasModifierProperty(PsiModifier.STATIC) && modifiers.findAnnotation(SHADOW) == null && modifiers.findAnnotation(OVERWRITE) == null && From 3d61c67319a47466d7f5237569ed962910ce51db Mon Sep 17 00:00:00 2001 From: RedNesto Date: Sat, 27 Apr 2024 15:51:18 +0200 Subject: [PATCH 07/22] Add NeoGradle version dropdown --- .../platform/neoforge/creator/gradle-steps.kt | 2 + .../platform/neoforge/creator/ui-steps.kt | 44 +++++++++++------ .../neoforge/version/NeoGradleVersion.kt | 48 +++++++++++++++++++ .../j2ee/neoforge/NeoForge build.gradle.ft | 2 +- 4 files changed, 80 insertions(+), 16 deletions(-) create mode 100644 src/main/kotlin/platform/neoforge/version/NeoGradleVersion.kt diff --git a/src/main/kotlin/platform/neoforge/creator/gradle-steps.kt b/src/main/kotlin/platform/neoforge/creator/gradle-steps.kt index 26d3f3f73..c0f112778 100644 --- a/src/main/kotlin/platform/neoforge/creator/gradle-steps.kt +++ b/src/main/kotlin/platform/neoforge/creator/gradle-steps.kt @@ -71,6 +71,7 @@ class NeoForgeGradleFilesStep(parent: NewProjectWizardStep) : AbstractLongRunnin override fun setupAssets(project: Project) { val mcVersion = data.getUserData(NeoForgeVersionChainStep.MC_VERSION_KEY) ?: return val neoforgeVersion = data.getUserData(NeoForgeVersionChainStep.NEOFORGE_VERSION_KEY) ?: return + val neogradleVersion = data.getUserData(NeoForgeVersionChainStep.NEOGRADLE_VERSION_KEY) ?: return val modId = data.getUserData(AbstractModIdStep.KEY) ?: return val modName = data.getUserData(AbstractModNameStep.KEY) ?: return val buildSystemProps = findStep>() @@ -95,6 +96,7 @@ class NeoForgeGradleFilesStep(parent: NewProjectWizardStep) : AbstractLongRunnin "MC_NEXT_VERSION" to mcNextVersion, "NEOFORGE_VERSION" to neoforgeVersion, "NEOFORGE_SPEC_VERSION" to neoforgeVersion.parts[0].versionString, + "NEOGRADLE_VERSION" to neogradleVersion, "GROUP_ID" to buildSystemProps.groupId, "ARTIFACT_ID" to buildSystemProps.artifactId, "MOD_VERSION" to buildSystemProps.version, diff --git a/src/main/kotlin/platform/neoforge/creator/ui-steps.kt b/src/main/kotlin/platform/neoforge/creator/ui-steps.kt index 86755fed2..8a0d03909 100644 --- a/src/main/kotlin/platform/neoforge/creator/ui-steps.kt +++ b/src/main/kotlin/platform/neoforge/creator/ui-steps.kt @@ -36,6 +36,7 @@ import com.demonwav.mcdev.creator.step.UpdateUrlStep import com.demonwav.mcdev.creator.step.UseMixinsStep import com.demonwav.mcdev.creator.step.WebsiteStep import com.demonwav.mcdev.platform.neoforge.version.NeoForgeVersion +import com.demonwav.mcdev.platform.neoforge.version.NeoGradleVersion import com.demonwav.mcdev.util.MinecraftVersions import com.demonwav.mcdev.util.SemanticVersion import com.demonwav.mcdev.util.asyncIO @@ -47,25 +48,31 @@ import kotlinx.coroutines.coroutineScope private val minSupportedMcVersion = MinecraftVersions.MC1_20_2 -class NeoForgePlatformStep(parent: ModPlatformStep) : AbstractLatentStep(parent) { +class NeoForgePlatformStep(parent: ModPlatformStep) : + AbstractLatentStep>(parent) { override val description = "fetch NeoForge versions" override suspend fun computeData() = coroutineScope { - asyncIO { NeoForgeVersion.downloadData() }.await() + val neoforgeJob = asyncIO { NeoForgeVersion.downloadData() } + val neogradleJob = asyncIO { NeoGradleVersion.downloadData() } + val neoforge = neoforgeJob.await() ?: return@coroutineScope null + val neogradle = neogradleJob.await() ?: return@coroutineScope null + neoforge to neogradle } - override fun createStep(data: NeoForgeVersion) = NeoForgeVersionChainStep(this, data) - .nextStep(::ForgeStyleModIdStep) - .nextStep(::ModNameStep) - .nextStep(::MainClassStep) - .nextStep(::UseMixinsStep) - .nextStep(::LicenseStep) - .nextStep(::NeoForgeOptionalSettingsStep) - .nextStep(::NeoForgeBuildSystemStep) - .nextStep(::NeoForgeProjectFilesStep) - .nextStep(::NeoForgeMixinsJsonStep) - .nextStep(::NeoForgePostBuildSystemStep) - .nextStep(::NeoForgeReformatPackDescriptorStep) + override fun createStep(data: Pair): NewProjectWizardStep = + NeoForgeVersionChainStep(this, data.first, data.second) + .nextStep(::ForgeStyleModIdStep) + .nextStep(::ModNameStep) + .nextStep(::MainClassStep) + .nextStep(::UseMixinsStep) + .nextStep(::LicenseStep) + .nextStep(::NeoForgeOptionalSettingsStep) + .nextStep(::NeoForgeBuildSystemStep) + .nextStep(::NeoForgeProjectFilesStep) + .nextStep(::NeoForgeMixinsJsonStep) + .nextStep(::NeoForgePostBuildSystemStep) + .nextStep(::NeoForgeReformatPackDescriptorStep) class Factory : ModPlatformStep.Factory { override val name = "NeoForge" @@ -76,13 +83,17 @@ class NeoForgePlatformStep(parent: ModPlatformStep) : AbstractLatentStep("${NeoForgeVersionChainStep::class.java}.mcVersion") val NEOFORGE_VERSION_KEY = Key.create("${NeoForgeVersionChainStep::class.java}.neoforgeVersion") + val NEOGRADLE_VERSION_KEY = + Key.create("${NeoForgeVersionChainStep::class.java}.neogradleVersion") } override fun getAvailableVersions(versionsAbove: List>): List> { @@ -90,6 +101,8 @@ class NeoForgeVersionChainStep( MINECRAFT_VERSION -> neoforgeVersionData.sortedMcVersions.filter { it >= minSupportedMcVersion } NEOFORGE_VERSION -> neoforgeVersionData.getNeoForgeVersions(versionsAbove[MINECRAFT_VERSION] as SemanticVersion) + + NEOGRADLE_VERSION -> neogradleVersionData.versions else -> throw IncorrectOperationException() } } @@ -98,6 +111,7 @@ class NeoForgeVersionChainStep( super.setupProject(project) data.putUserData(MC_VERSION_KEY, getVersion(MINECRAFT_VERSION) as SemanticVersion) data.putUserData(NEOFORGE_VERSION_KEY, getVersion(NEOFORGE_VERSION) as SemanticVersion) + data.putUserData(NEOGRADLE_VERSION_KEY, getVersion(NEOGRADLE_VERSION) as SemanticVersion) } } diff --git a/src/main/kotlin/platform/neoforge/version/NeoGradleVersion.kt b/src/main/kotlin/platform/neoforge/version/NeoGradleVersion.kt new file mode 100644 index 000000000..158cfdefa --- /dev/null +++ b/src/main/kotlin/platform/neoforge/version/NeoGradleVersion.kt @@ -0,0 +1,48 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 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 + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.neoforge.version + +import com.demonwav.mcdev.creator.collectMavenVersions +import com.demonwav.mcdev.util.SemanticVersion +import com.intellij.openapi.diagnostic.logger +import java.io.IOException + +class NeoGradleVersion private constructor(val versions: List) { + + companion object { + private val LOGGER = logger() + + suspend fun downloadData(): NeoGradleVersion? { + try { + val url = "https://maven.neoforged.net/releases/net/neoforged/gradle/userdev/maven-metadata.xml" + val versions = collectMavenVersions(url) + .asSequence() + .mapNotNull(SemanticVersion.Companion::tryParse) + .sortedDescending() + .toList() + return NeoGradleVersion(versions) + } catch (e: IOException) { + LOGGER.error("Failed to retrieve NeoForge version data", e) + } + return null + } + } +} diff --git a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge build.gradle.ft b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge build.gradle.ft index 6989df8d1..6de0fe136 100644 --- a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge build.gradle.ft +++ b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge build.gradle.ft @@ -3,7 +3,7 @@ plugins { id 'eclipse' id 'idea' id 'maven-publish' - id 'net.neoforged.gradle.userdev' version '7.0.97' + id 'net.neoforged.gradle.userdev' version '${NEOGRADLE_VERSION}' } version = mod_version From 50002d73f4f3e5694cf1a307ee38c62d7fd6a5f9 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Sat, 27 Apr 2024 16:56:05 +0200 Subject: [PATCH 08/22] NeoForge 1.20.5 template --- .../platform/neoforge/creator/asset-steps.kt | 22 ++- .../platform/neoforge/creator/gradle-steps.kt | 10 +- src/main/kotlin/util/MinecraftTemplates.kt | 5 + .../neoforge/NeoForge (1.20.5) Config.java.ft | 63 ++++++++ .../NeoForge (1.20.5) Config.java.html | 25 +++ .../NeoForge (1.20.5) Main Class.java.ft | 134 ++++++++++++++++ .../NeoForge (1.20.5) Main Class.java.html | 25 +++ .../NeoForge (1.20.5) build.gradle.ft | 151 ++++++++++++++++++ .../NeoForge (1.20.5) build.gradle.html | 25 +++ .../neoforge/NeoForge neoforge.mods.toml.ft | 100 ++++++++++++ .../neoforge/NeoForge neoforge.mods.toml.html | 25 +++ 11 files changed, 578 insertions(+), 7 deletions(-) create mode 100644 src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) Config.java.ft create mode 100644 src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) Config.java.html create mode 100644 src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) Main Class.java.ft create mode 100644 src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) Main Class.java.html create mode 100644 src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) build.gradle.ft create mode 100644 src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) build.gradle.html create mode 100644 src/main/resources/fileTemplates/j2ee/neoforge/NeoForge neoforge.mods.toml.ft create mode 100644 src/main/resources/fileTemplates/j2ee/neoforge/NeoForge neoforge.mods.toml.html diff --git a/src/main/kotlin/platform/neoforge/creator/asset-steps.kt b/src/main/kotlin/platform/neoforge/creator/asset-steps.kt index 64cd30844..83978733c 100644 --- a/src/main/kotlin/platform/neoforge/creator/asset-steps.kt +++ b/src/main/kotlin/platform/neoforge/creator/asset-steps.kt @@ -41,6 +41,7 @@ import com.demonwav.mcdev.creator.step.UseMixinsStep import com.demonwav.mcdev.creator.step.WebsiteStep 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 @@ -101,21 +102,36 @@ class NeoForgeProjectFilesStep(parent: NewProjectWizardStep) : AbstractLongRunni assets.addTemplateProperties("WEBSITE" to website) } - val mainClassTemplate = MinecraftTemplates.NEOFORGE_MAIN_CLASS_TEMPLATE + val mainClassTemplate = when { + mcVersion >= MinecraftVersions.MC1_20_5 -> MinecraftTemplates.NEOFORGE_1_20_5_MAIN_CLASS_TEMPLATE + else -> MinecraftTemplates.NEOFORGE_MAIN_CLASS_TEMPLATE + } + + val (modManifestName, modManifestTemplate) = when { + mcVersion >= MinecraftVersions.MC1_20_5 -> + "neoforge.mods.toml" to MinecraftTemplates.NEOFORGE_NEOFORGE_MODS_TOML_TEMPLATE + + else -> "mods.toml" to MinecraftTemplates.NEOFORGE_MODS_TOML_TEMPLATE + } assets.addTemplates( project, "src/main/java/${mainClass.replace('.', '/')}.java" to mainClassTemplate, "src/main/resources/pack.mcmeta" to MinecraftTemplates.NEOFORGE_PACK_MCMETA_TEMPLATE, - "src/main/resources/META-INF/mods.toml" to MinecraftTemplates.NEOFORGE_MODS_TOML_TEMPLATE, + "src/main/resources/META-INF/$modManifestName" to modManifestTemplate, ) + val configClassTemplate = when { + mcVersion >= MinecraftVersions.MC1_20_5 -> MinecraftTemplates.NEOFORGE_1_20_5_CONFIG_TEMPLATE + else -> MinecraftTemplates.NEOFORGE_CONFIG_TEMPLATE + } + val configPath = if (mainPackageName != null) { "src/main/java/${mainPackageName.replace('.', '/')}/Config.java" } else { "src/main/java/Config.java" } - assets.addTemplates(project, configPath to MinecraftTemplates.NEOFORGE_CONFIG_TEMPLATE) + assets.addTemplates(project, configPath to configClassTemplate) assets.addLicense(project) } diff --git a/src/main/kotlin/platform/neoforge/creator/gradle-steps.kt b/src/main/kotlin/platform/neoforge/creator/gradle-steps.kt index c0f112778..a6a6deb18 100644 --- a/src/main/kotlin/platform/neoforge/creator/gradle-steps.kt +++ b/src/main/kotlin/platform/neoforge/creator/gradle-steps.kt @@ -41,6 +41,7 @@ import com.demonwav.mcdev.creator.step.DescriptionStep import com.demonwav.mcdev.creator.step.LicenseStep import com.demonwav.mcdev.creator.step.NewProjectWizardChainStep.Companion.nextStep 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.application.WriteAction @@ -49,7 +50,7 @@ import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VfsUtil import com.intellij.util.lang.JavaVersion -private val ngWrapperVersion = SemanticVersion.release(8, 6) +private val ngWrapperVersion = SemanticVersion.release(8, 7) const val MAGIC_RUN_CONFIGS_FILE = ".hello_from_mcdev" @@ -117,13 +118,14 @@ class NeoForgeGradleFilesStep(parent: NewProjectWizardStep) : AbstractLongRunnin assets.addTemplateProperties("JAVA_VERSION" to javaVersion.feature) } - if (neoforgeVersion >= SemanticVersion.release(39, 0, 88)) { - assets.addTemplateProperties("GAME_TEST_FRAMEWORK" to "true") + val buildGradleTemplate = when { + mcVersion >= MinecraftVersions.MC1_20_5 -> MinecraftTemplates.NEOFORGE_1_20_5_BUILD_GRADLE_TEMPLATE + else -> MinecraftTemplates.NEOFORGE_BUILD_GRADLE_TEMPLATE } assets.addTemplates( project, - "build.gradle" to MinecraftTemplates.NEOFORGE_BUILD_GRADLE_TEMPLATE, + "build.gradle" to buildGradleTemplate, "gradle.properties" to MinecraftTemplates.NEOFORGE_GRADLE_PROPERTIES_TEMPLATE, "settings.gradle" to MinecraftTemplates.NEOFORGE_SETTINGS_GRADLE_TEMPLATE, ) diff --git a/src/main/kotlin/util/MinecraftTemplates.kt b/src/main/kotlin/util/MinecraftTemplates.kt index 368e30cee..3769ea168 100644 --- a/src/main/kotlin/util/MinecraftTemplates.kt +++ b/src/main/kotlin/util/MinecraftTemplates.kt @@ -275,8 +275,13 @@ class MinecraftTemplates : FileTemplateGroupDescriptorFactory { const val NEOFORGE_GRADLE_PROPERTIES_TEMPLATE = "NeoForge gradle.properties" const val NEOFORGE_SETTINGS_GRADLE_TEMPLATE = "NeoForge settings.gradle" const val NEOFORGE_MODS_TOML_TEMPLATE = "NeoForge mods.toml" + const val NEOFORGE_NEOFORGE_MODS_TOML_TEMPLATE = "NeoForge neoforge.mods.toml" const val NEOFORGE_PACK_MCMETA_TEMPLATE = "NeoForge pack.mcmeta" + const val NEOFORGE_1_20_5_MAIN_CLASS_TEMPLATE = "NeoForge (1.20.5) Main Class.java" + const val NEOFORGE_1_20_5_CONFIG_TEMPLATE = "NeoForge (1.20.5) Config.java" + const val NEOFORGE_1_20_5_BUILD_GRADLE_TEMPLATE = "NeoForge (1.20.5) build.gradle" + const val NEOFORGE_BLOCK_TEMPLATE = "NeoForgeBlock.java" const val NEOFORGE_ITEM_TEMPLATE = "NeoForgeItem.java" const val NEOFORGE_PACKET_TEMPLATE = "NeoForgePacket.java" diff --git a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) Config.java.ft b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) Config.java.ft new file mode 100644 index 000000000..cf0c56d8d --- /dev/null +++ b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) Config.java.ft @@ -0,0 +1,63 @@ +package ${PACKAGE_NAME}; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.fml.event.config.ModConfigEvent; +import net.neoforged.neoforge.common.ModConfigSpec; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +// An example config class. This is not required, but it's a good idea to have one to keep your config organized. +// Demonstrates how to use Neo's config APIs +@EventBusSubscriber(modid = ${CLASS_NAME}.MODID, bus = EventBusSubscriber.Bus.MOD) +public class Config +{ + private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder(); + + private static final ModConfigSpec.BooleanValue LOG_DIRT_BLOCK = BUILDER + .comment("Whether to log the dirt block on common setup") + .define("logDirtBlock", true); + + private static final ModConfigSpec.IntValue MAGIC_NUMBER = BUILDER + .comment("A magic number") + .defineInRange("magicNumber", 42, 0, Integer.MAX_VALUE); + + public static final ModConfigSpec.ConfigValue MAGIC_NUMBER_INTRODUCTION = BUILDER + .comment("What you want the introduction message to be for the magic number") + .define("magicNumberIntroduction", "The magic number is... "); + + // a list of strings that are treated as resource locations for items + private static final ModConfigSpec.ConfigValue> ITEM_STRINGS = BUILDER + .comment("A list of items to log on common setup.") + .defineListAllowEmpty("items", List.of("minecraft:iron_ingot"), Config::validateItemName); + + static final ModConfigSpec SPEC = BUILDER.build(); + + public static boolean logDirtBlock; + public static int magicNumber; + public static String magicNumberIntroduction; + public static Set items; + + private static boolean validateItemName(final Object obj) + { + return obj instanceof String itemName && BuiltInRegistries.ITEM.containsKey(new ResourceLocation(itemName)); + } + + @SubscribeEvent + static void onLoad(final ModConfigEvent event) + { + logDirtBlock = LOG_DIRT_BLOCK.get(); + magicNumber = MAGIC_NUMBER.get(); + magicNumberIntroduction = MAGIC_NUMBER_INTRODUCTION.get(); + + // convert the list of strings into a set of items + items = ITEM_STRINGS.get().stream() + .map(itemName -> BuiltInRegistries.ITEM.get(new ResourceLocation(itemName))) + .collect(Collectors.toSet()); + } +} diff --git a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) Config.java.html b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) Config.java.html new file mode 100644 index 000000000..f5870882d --- /dev/null +++ b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) Config.java.html @@ -0,0 +1,25 @@ + + + + +

This is a built-in file template used to create a new config class for NeoForge 1.20.5 projects.

+ + diff --git a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) Main Class.java.ft b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) Main Class.java.ft new file mode 100644 index 000000000..a3a0c2795 --- /dev/null +++ b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) Main Class.java.ft @@ -0,0 +1,134 @@ +package ${PACKAGE_NAME}; + +import com.mojang.logging.LogUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.chat.Component; +import net.minecraft.world.food.FoodProperties; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.material.MapColor; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.config.ModConfig; +import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; +import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; +import net.neoforged.neoforge.event.server.ServerStartingEvent; +import net.neoforged.neoforge.registries.DeferredBlock; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.DeferredItem; +import net.neoforged.neoforge.registries.DeferredRegister; +import org.slf4j.Logger; + +// The value here should match an entry in the META-INF/neoforge.mods.toml file +@Mod(${CLASS_NAME}.MODID) +public class ${CLASS_NAME} +{ + // Define mod id in a common place for everything to reference + public static final String MODID = "${MOD_ID}"; + // Directly reference a slf4j logger + private static final Logger LOGGER = LogUtils.getLogger(); + // Create a Deferred Register to hold Blocks which will all be registered under the "${MOD_ID}" namespace + public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks(MODID); + // Create a Deferred Register to hold Items which will all be registered under the "${MOD_ID}" namespace + public static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(MODID); + // Create a Deferred Register to hold CreativeModeTabs which will all be registered under the "${MOD_ID}" namespace + public static final DeferredRegister CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID); + + // Creates a new Block with the id "${MOD_ID}:example_block", combining the namespace and path + public static final DeferredBlock EXAMPLE_BLOCK = BLOCKS.registerSimpleBlock("example_block", BlockBehaviour.Properties.of().mapColor(MapColor.STONE)); + // Creates a new BlockItem with the id "${MOD_ID}:example_block", combining the namespace and path + public static final DeferredItem EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem("example_block", EXAMPLE_BLOCK); + + // Creates a new food item with the id "${MOD_ID}:example_id", nutrition 1 and saturation 2 + public static final DeferredItem EXAMPLE_ITEM = ITEMS.registerSimpleItem("example_item", new Item.Properties().food(new FoodProperties.Builder() + .alwaysEdible().nutrition(1).saturationModifier(2f).build())); + + // Creates a creative tab with the id "${MOD_ID}:example_tab" for the example item, that is placed after the combat tab + public static final DeferredHolder EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab", () -> CreativeModeTab.builder() + .title(Component.translatable("itemGroup.${MOD_ID}")) + .withTabsBefore(CreativeModeTabs.COMBAT) + .icon(() -> EXAMPLE_ITEM.get().getDefaultInstance()) + .displayItems((parameters, output) -> { + output.accept(EXAMPLE_ITEM.get()); // Add the example item to the tab. For your own tabs, this method is preferred over the event + }).build()); + + // The constructor for the mod class is the first code that is run when your mod is loaded. + // FML will recognize some parameter types like IEventBus or ModContainer and pass them in automatically. + public ${CLASS_NAME}(IEventBus modEventBus, ModContainer modContainer) + { + // Register the commonSetup method for modloading + modEventBus.addListener(this::commonSetup); + + // Register the Deferred Register to the mod event bus so blocks get registered + BLOCKS.register(modEventBus); + // Register the Deferred Register to the mod event bus so items get registered + ITEMS.register(modEventBus); + // Register the Deferred Register to the mod event bus so tabs get registered + CREATIVE_MODE_TABS.register(modEventBus); + + // Register ourselves for server and other game events we are interested in. + // Note that this is necessary if and only if we want *this* class (ExampleMod) to respond directly to events. + // Do not add this line if there are no @SubscribeEvent-annotated functions in this class, like onServerStarting() below. + NeoForge.EVENT_BUS.register(this); + + // Register the item to a creative tab + modEventBus.addListener(this::addCreative); + + // Register our mod's ModConfigSpec so that FML can create and load the config file for us + modContainer.registerConfig(ModConfig.Type.COMMON, Config.SPEC); + } + + private void commonSetup(final FMLCommonSetupEvent event) + { + // Some common setup code + LOGGER.info("HELLO FROM COMMON SETUP"); + + if (Config.logDirtBlock) + LOGGER.info("DIRT BLOCK >> {}", BuiltInRegistries.BLOCK.getKey(Blocks.DIRT)); + + LOGGER.info(Config.magicNumberIntroduction + Config.magicNumber); + + Config.items.forEach((item) -> LOGGER.info("ITEM >> {}", item.toString())); + } + + // Add the example block item to the building blocks tab + private void addCreative(BuildCreativeModeTabContentsEvent event) + { + if (event.getTabKey() == CreativeModeTabs.BUILDING_BLOCKS) + event.accept(EXAMPLE_BLOCK_ITEM); + } + + // You can use SubscribeEvent and let the Event Bus discover methods to call + @SubscribeEvent + public void onServerStarting(ServerStartingEvent event) + { + // Do something when the server starts + LOGGER.info("HELLO from server starting"); + } + + // You can use EventBusSubscriber to automatically register all static methods in the class annotated with @SubscribeEvent + @EventBusSubscriber(modid = MODID, bus = EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) + public static class ClientModEvents + { + @SubscribeEvent + public static void onClientSetup(FMLClientSetupEvent event) + { + // Some client setup code + LOGGER.info("HELLO FROM CLIENT SETUP"); + LOGGER.info("MINECRAFT NAME >> {}", Minecraft.getInstance().getUser().getName()); + } + } +} diff --git a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) Main Class.java.html b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) Main Class.java.html new file mode 100644 index 000000000..701f5287f --- /dev/null +++ b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) Main Class.java.html @@ -0,0 +1,25 @@ + + + + +

This is a built-in file template used to create a new main class for NeoForge 1.20.5 projects.

+ + diff --git a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) build.gradle.ft b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) build.gradle.ft new file mode 100644 index 000000000..4edce50d1 --- /dev/null +++ b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) build.gradle.ft @@ -0,0 +1,151 @@ +plugins { + id 'java-library' + id 'eclipse' + id 'idea' + id 'maven-publish' + id 'net.neoforged.gradle.userdev' version '${NEOGRADLE_VERSION}' +} + +version = mod_version +group = mod_group_id + +repositories { + mavenLocal() +} + +base { + archivesName = mod_id +} + +java.toolchain.languageVersion = JavaLanguageVersion.of(${JAVA_VERSION}) + +//minecraft.accessTransformers.file rootProject.file('src/main/resources/META-INF/accesstransformer.cfg') +//minecraft.accessTransformers.entry public net.minecraft.client.Minecraft textureManager # textureManager + +// Default run configurations. +// These can be tweaked, removed, or duplicated as needed. +runs { + // applies to all the run configs below + configureEach { + // Recommended logging data for a userdev environment + // The markers can be added/remove as needed separated by commas. + // "SCAN": For mods scan. + // "REGISTRIES": For firing of registry events. + // "REGISTRYDUMP": For getting the contents of all registries. + systemProperty 'forge.logging.markers', 'REGISTRIES' + + // Recommended logging level for the console + // You can set various levels here. + // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels + systemProperty 'forge.logging.console.level', 'debug' + + modSource project.sourceSets.main + } + + client { + // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + } + + server { + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + programArgument '--nogui' + } + + // This run config launches GameTestServer and runs all registered gametests, then exits. + // By default, the server will crash when no gametests are provided. + // The gametest system is also enabled by default for other run configs under the /test command. + gameTestServer { + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + } + + data { + // example of overriding the workingDirectory set in configureEach above, uncomment if you want to use it + // workingDirectory project.file('run-data') + + // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. + programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } +} + +// Include resources generated by data generators. +sourceSets.main.resources { srcDir 'src/generated/resources' } + + +dependencies { + // Specify the version of Minecraft to use. + // Depending on the plugin applied there are several options. We will assume you applied the userdev plugin as shown above. + // The group for userdev is net.neoforge, the module name is neoforge, and the version is the same as the neoforge version. + // You can however also use the vanilla plugin (net.neoforged.gradle.vanilla) to use a version of Minecraft without the neoforge loader. + // And its provides the option to then use net.minecraft as the group, and one of; client, server or joined as the module name, plus the game version as version. + // For all intends and purposes: You can treat this dependency as if it is a normal library you would use. + implementation "net.neoforged:neoforge:${neo_version}" + + // Example mod dependency with JEI + // The JEI API is declared for compile time use, while the full JEI artifact is used at runtime + // compileOnly "mezz.jei:jei-${mc_version}-common-api:${jei_version}" + // compileOnly "mezz.jei:jei-${mc_version}-forge-api:${jei_version}" + // runtimeOnly "mezz.jei:jei-${mc_version}-forge:${jei_version}" + + // Example mod dependency using a mod jar from ./libs with a flat dir repository + // This maps to ./libs/coolmod-${mc_version}-${coolmod_version}.jar + // The group id is ignored when searching -- in this case, it is "blank" + // implementation "blank:coolmod-${mc_version}:${coolmod_version}" + + // Example mod dependency using a file as dependency + // implementation files("libs/coolmod-${mc_version}-${coolmod_version}.jar") + + // Example project dependency using a sister or child project: + // implementation project(":myproject") + + // For more info: + // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html + // http://www.gradle.org/docs/current/userguide/dependency_management.html +} + +// This block of code expands all declared replace properties in the specified resource targets. +// A missing property will result in an error. Properties are expanded using ${} Groovy notation. +// When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments. +// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html +tasks.withType(ProcessResources).configureEach { + var replaceProperties = [ + minecraft_version : minecraft_version, + minecraft_version_range: minecraft_version_range, + neo_version : neo_version, + neo_version_range : neo_version_range, + loader_version_range : loader_version_range, + mod_id : mod_id, + mod_name : mod_name, + mod_license : mod_license, + mod_version : mod_version, + mod_authors : mod_authors, + mod_description : mod_description + ] + inputs.properties replaceProperties + + filesMatching(['META-INF/neoforge.mods.toml']) { + expand replaceProperties + } +} + +// Example configuration to allow publishing using the maven-publish plugin +publishing { + publications { + register('mavenJava', MavenPublication) { + from components.java + } + } + repositories { + maven { + url "file://${project.projectDir}/repo" + } + } +} + +// IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior. +idea { + module { + downloadSources = true + downloadJavadoc = true + } +} diff --git a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) build.gradle.html b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) build.gradle.html new file mode 100644 index 000000000..8b175740d --- /dev/null +++ b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.20.5) build.gradle.html @@ -0,0 +1,25 @@ + + + + +

This is a built-in file template used to create a new build.gradle for NeoForge (1.20.5) projects.

+ + diff --git a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge neoforge.mods.toml.ft b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge neoforge.mods.toml.ft new file mode 100644 index 000000000..7e8266261 --- /dev/null +++ b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge neoforge.mods.toml.ft @@ -0,0 +1,100 @@ +# This is an example neoforge.mods.toml file. It contains the data relating to the loading mods. +# There are several mandatory fields (#mandatory), and many more that are optional (#optional). +# The overall format is standard TOML format, v0.5.0. +# Note that there are a couple of TOML lists in this file. +# Find more information on toml format here: https://github.com/toml-lang/toml +# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml +modLoader="javafml" #mandatory +# A version range to match for said mod loader - for regular FML @Mod it will be the the FML version. This is currently 47. +loaderVersion="${loader_version_range}" #mandatory +# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. +# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. +license="${mod_license}" +# A URL to refer people to when problems occur with this mod +#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional +# A list of mods - how many allowed here is determined by the individual mod loader +[[mods]] #mandatory +# The modid of the mod +modId="${mod_id}" #mandatory +# The version number of the mod +version="${mod_version}" #mandatory +# A display name for the mod +displayName="${mod_name}" #mandatory +# A URL to query for updates for this mod. See the JSON update specification https://docs.neoforge.net/docs/misc/updatechecker/ +#if (${UPDATE_URL}) +updateJSONURL="${UPDATE_URL}" #optional +#else +#updateJSONURL="https://change.me.example.invalid/updates.json" #optional +#end +# A URL for the "homepage" for this mod, displayed in the mod UI +#if (${WEBSITE}) +displayURL="${WEBSITE}" #optional +#else +#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional +#end +# A file name (in the root of the mod JAR) containing a logo for display +#logoFile="${MOD_ID}.png" #optional +# A text field displayed in the mod UI +#credits="" #optional +# A text field displayed in the mod UI +authors="${mod_authors}" #optional +# Display Test controls the display for your mod in the server connection screen +# MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. +# IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. +# IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. +# NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. +# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. +#displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) + +# The description text for the mod (multi line!) (#mandatory) +description='''${mod_description}''' + +#set ( $h = "#" ) +# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded. +#if (${MIXIN_CONFIG}) +[[mixins]] +config="${MIXIN_CONFIG}" +#else +${h}[[mixins]] +#config="${mod_id}.mixins.json" +#end + +# The [[accessTransformers]] block allows you to declare where your AT file is. +# If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg +${h}[[accessTransformers]] +#file="META-INF/accesstransformer.cfg" + +# The coremods config file path is not configurable and is always loaded from META-INF/coremods.json + +# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. +[[dependencies."${mod_id}"]] #optional +# the modid of the dependency +modId="neoforge" #mandatory +# The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive). +# 'required' requires the mod to exist, 'optional' does not +# 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning +type="required" #mandatory +# Optional field describing why the dependency is required or why it is incompatible +# reason="..." +# The version range of the dependency +versionRange="${neo_version_range}" #mandatory +# An ordering relationship for the dependency. +# BEFORE - This mod is loaded BEFORE the dependency +# AFTER - This mod is loaded AFTER the dependency +ordering="NONE" +# Side this dependency is applied on - BOTH, CLIENT, or SERVER +side="BOTH" +# Here's another dependency +[[dependencies."${mod_id}"]] +modId="minecraft" +type="required" +# This version range declares a minimum of the current minecraft version up to but not including the next major version +versionRange="${minecraft_version_range}" +ordering="NONE" +side="BOTH" + +# Features are specific properties of the game environment, that you may want to declare you require. This example declares +# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't +# stop your mod loading on the server for example. +#[features."${mod_id}"] +#openGLVersion="[3.2,)" diff --git a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge neoforge.mods.toml.html b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge neoforge.mods.toml.html new file mode 100644 index 000000000..eead33ce5 --- /dev/null +++ b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge neoforge.mods.toml.html @@ -0,0 +1,25 @@ + + + + +

This is a built-in file template used to create a new mods.toml for NeoForge 1.20.5 projects.

+ + From 2e4ae91e00277577913912f1677bb4c5c35f8b20 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Sat, 27 Apr 2024 16:58:21 +0200 Subject: [PATCH 09/22] Only show the 50 most recent NeoGradle versions --- src/main/kotlin/platform/neoforge/version/NeoGradleVersion.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/platform/neoforge/version/NeoGradleVersion.kt b/src/main/kotlin/platform/neoforge/version/NeoGradleVersion.kt index 158cfdefa..b7abd0aba 100644 --- a/src/main/kotlin/platform/neoforge/version/NeoGradleVersion.kt +++ b/src/main/kotlin/platform/neoforge/version/NeoGradleVersion.kt @@ -37,6 +37,7 @@ class NeoGradleVersion private constructor(val versions: List) .asSequence() .mapNotNull(SemanticVersion.Companion::tryParse) .sortedDescending() + .take(50) .toList() return NeoGradleVersion(versions) } catch (e: IOException) { From 8f2bb55f0d27c1ef0e0c6fde89ac4ee2c130ddac Mon Sep 17 00:00:00 2001 From: Bawnorton Date: Thu, 9 May 2024 06:43:35 -0400 Subject: [PATCH 10/22] Allow dynamic selectors to choose the class to perform member lookups in (#2293) * * fix dynamic selector missing namespace when the selector declares it at registration * allow dynamic selectors to change the target class that is used when looking up members * move custom owner over to mixin selector --- .../handlers/InjectorAnnotationHandler.kt | 22 +++++---- .../handlers/injectionPoint/AtResolver.kt | 18 +++++-- .../reference/AbstractMethodReference.kt | 9 +++- .../mixin/reference/MixinSelectors.kt | 47 ++++++++++++++++++- .../platform/mixin/util/MixinConstants.kt | 1 + 5 files changed, 80 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt b/src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt index 5c1079602..39058b76c 100644 --- a/src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt @@ -54,19 +54,25 @@ import org.objectweb.asm.tree.MethodNode abstract class InjectorAnnotationHandler : MixinAnnotationHandler { override fun resolveTarget(annotation: PsiAnnotation, targetClass: ClassNode): List { - val targetClassMethods = targetClass.methods ?: return emptyList() - val methodAttr = annotation.findAttributeValue("method") val method = methodAttr?.computeStringArray() ?: emptyList() val desc = annotation.findAttributeValue("desc")?.findAnnotations() ?: emptyList() val selectors = method.mapNotNull { parseMixinSelector(it, methodAttr!!) } + desc.mapNotNull { DescSelectorParser.descSelectorFromAnnotation(it) } - return targetClassMethods.mapNotNull { targetMethod -> - if (selectors.any { it.matchMethod(targetMethod, targetClass) }) { - MethodTargetMember(targetClass, targetMethod) - } else { - null + val targetClassMethods = selectors.associateWith { selector -> + val actualTarget = selector.getCustomOwner(targetClass) + (actualTarget to actualTarget.methods) + } + + return targetClassMethods.mapNotNull { (selector, pair) -> + val (clazz, methods) = pair + methods.firstNotNullOfOrNull { method -> + if (selector.matchMethod(method, clazz)) { + MethodTargetMember(clazz, method) + } else { + null + } } } } @@ -99,7 +105,7 @@ abstract class InjectorAnnotationHandler : MixinAnnotationHandler { override fun resolveForNavigation(annotation: PsiAnnotation, targetClass: ClassNode): List { return resolveTarget(annotation, targetClass).flatMap { targetMember -> val targetMethod = targetMember as? MethodTargetMember ?: return@flatMap emptyList() - resolveForNavigation(annotation, targetClass, targetMethod.classAndMethod.method) + resolveForNavigation(annotation, targetMethod.classAndMethod.clazz, targetMethod.classAndMethod.method) } } diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt index 15b2ab3e7..c1b1df6ac 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt @@ -20,6 +20,7 @@ package com.demonwav.mcdev.platform.mixin.handlers.injectionPoint +import com.demonwav.mcdev.platform.mixin.reference.MixinSelector import com.demonwav.mcdev.platform.mixin.reference.isMiscDynamicSelector import com.demonwav.mcdev.platform.mixin.reference.parseMixinSelector import com.demonwav.mcdev.platform.mixin.reference.target.TargetReference @@ -152,7 +153,7 @@ class AtResolver( val collectVisitor = injectionPoint.createCollectVisitor( at, target, - targetClass, + getTargetClass(target), CollectVisitor.Mode.MATCH_FIRST, ) if (collectVisitor == null) { @@ -181,7 +182,7 @@ class AtResolver( val targetAttr = at.findAttributeValue("target") val target = targetAttr?.let { parseMixinSelector(it) } - val collectVisitor = injectionPoint.createCollectVisitor(at, target, targetClass, mode) + val collectVisitor = injectionPoint.createCollectVisitor(at, target, getTargetClass(target), mode) ?: return InsnResolutionInfo.Failure() collectVisitor.visit(targetMethod) val result = collectVisitor.result @@ -201,7 +202,7 @@ class AtResolver( // Then attempt to find the corresponding source elements using the navigation visitor val targetElement = targetMethod.findSourceElement( - targetClass, + getTargetClass(target), at.project, GlobalSearchScope.allScope(at.project), canDecompile = true, @@ -223,16 +224,23 @@ class AtResolver( // Collect all possible targets fun doCollectVariants(injectionPoint: InjectionPoint): List { - val visitor = injectionPoint.createCollectVisitor(at, target, targetClass, CollectVisitor.Mode.COMPLETION) + val visitor = injectionPoint.createCollectVisitor( + at, target, getTargetClass(target), + CollectVisitor.Mode.COMPLETION + ) ?: return emptyList() visitor.visit(targetMethod) return visitor.result .mapNotNull { result -> - injectionPoint.createLookup(targetClass, result)?.let { completionHandler(it) } + injectionPoint.createLookup(getTargetClass(target), result)?.let { completionHandler(it) } } } return doCollectVariants(injectionPoint) } + + private fun getTargetClass(selector: MixinSelector?): ClassNode { + return selector?.getCustomOwner(targetClass) ?: targetClass + } } sealed class InsnResolutionInfo { diff --git a/src/main/kotlin/platform/mixin/reference/AbstractMethodReference.kt b/src/main/kotlin/platform/mixin/reference/AbstractMethodReference.kt index 67b355f30..89aa5dbd5 100644 --- a/src/main/kotlin/platform/mixin/reference/AbstractMethodReference.kt +++ b/src/main/kotlin/platform/mixin/reference/AbstractMethodReference.kt @@ -83,7 +83,9 @@ abstract class AbstractMethodReference : PolyReferenceResolver(), MixinReference val stringValue = context.constantStringValue ?: return false val targetMethodInfo = parseSelector(stringValue, context) ?: return false val targets = getTargets(context) ?: return false - return !targets.asSequence().flatMap { it.findMethods(targetMethodInfo) }.any() + return !targets.asSequence().flatMap { + targetMethodInfo.getCustomOwner(it).findMethods(targetMethodInfo) + }.any() } fun getReferenceIfAmbiguous(context: PsiElement): MemberReference? { @@ -125,7 +127,10 @@ abstract class AbstractMethodReference : PolyReferenceResolver(), MixinReference selector: MixinSelector, ): Sequence { return targets.asSequence() - .flatMap { target -> target.findMethods(selector).map { ClassAndMethodNode(target, it) } } + .flatMap { target -> + val actualTarget = selector.getCustomOwner(target) + actualTarget.findMethods(selector).map { ClassAndMethodNode(actualTarget, it) } + } } fun resolveIfUnique(context: PsiElement): ClassAndMethodNode? { diff --git a/src/main/kotlin/platform/mixin/reference/MixinSelectors.kt b/src/main/kotlin/platform/mixin/reference/MixinSelectors.kt index 59ab0d8a0..4e8d34f4f 100644 --- a/src/main/kotlin/platform/mixin/reference/MixinSelectors.kt +++ b/src/main/kotlin/platform/mixin/reference/MixinSelectors.kt @@ -52,6 +52,7 @@ import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.CommonClassNames import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiCallExpression import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiField @@ -61,6 +62,7 @@ import com.intellij.psi.PsiNameValuePair import com.intellij.psi.PsiTypes import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.searches.AnnotatedMembersSearch +import com.intellij.psi.search.searches.MethodReferencesSearch import com.intellij.psi.util.InheritanceUtil import com.intellij.psi.util.PsiModificationTracker import com.intellij.psi.util.PsiTreeUtil @@ -123,6 +125,10 @@ interface MixinSelector { return matchMethod(qualifier.name, method.name, method.desc) } + fun getCustomOwner(owner: ClassNode): ClassNode { + return owner + } + /** * Implement this to return false for early-out optimizations, so you don't need to resolve the member in the * navigation visitor @@ -417,13 +423,18 @@ private fun getAllDynamicSelectors(project: Project): Set { val annotation = member.findAnnotation(MixinConstants.Classes.SELECTOR_ID) ?: return@flatMap emptySequence() val value = annotation.findAttributeValue("value")?.constantStringValue ?: return@flatMap emptySequence() - val namespace = annotation.findAttributeValue("namespace")?.constantStringValue + var namespace = annotation.findAttributeValue("namespace")?.constantStringValue if (namespace.isNullOrEmpty()) { val builtinPrefix = "org.spongepowered.asm.mixin.injection.selectors." if (member.qualifiedName?.startsWith(builtinPrefix) == true) { sequenceOf(value, "mixin:$value") } else { - sequenceOf(value) + namespace = findNamespace(project, member) + if (namespace != null) { + sequenceOf("$namespace:$value") + } else { + sequenceOf(value) + } } } else { sequenceOf("$namespace:$value") @@ -432,6 +443,38 @@ private fun getAllDynamicSelectors(project: Project): Set { } } +/** + * Dynamic selectors don't have to declare their namespace in the annotation, + * so instead we look for the registration call and extract the namespace from there. + */ +private fun findNamespace( + project: Project, + member: PsiClass +): String? { + val targetSelector = JavaPsiFacade.getInstance(project) + .findClass(MixinConstants.Classes.TARGET_SELECTOR, GlobalSearchScope.allScope(project)) + val registerMethod = targetSelector?.findMethodsByName("register", false)?.firstOrNull() ?: return null + + val query = MethodReferencesSearch.search(registerMethod) + val usages = query.findAll() + for (usage in usages) { + val element = usage.element + val callExpression = PsiTreeUtil.getParentOfType(element, PsiCallExpression::class.java) ?: continue + val args = callExpression.argumentList ?: continue + if (args.expressions.size != 2) continue + + // is the registered selector the one we're checking? + val selectorName = args.expressions[0].text.removeSuffix(".class") + if (selectorName != member.name) continue + + val namespaceArg = args.expressions[1].text.removeSurrounding("\"") + if (namespaceArg.isEmpty()) continue + + return namespaceArg + } + return null +} + private val DYNAMIC_SELECTOR_PATTERN = "(?i)^@([a-z]+(:[a-z]+)?)(\\((.*)\\))?$".toRegex() abstract class DynamicSelectorParser(id: String, vararg aliases: String) : MixinSelectorParser { diff --git a/src/main/kotlin/platform/mixin/util/MixinConstants.kt b/src/main/kotlin/platform/mixin/util/MixinConstants.kt index b0e6093c6..7d74f6cdb 100644 --- a/src/main/kotlin/platform/mixin/util/MixinConstants.kt +++ b/src/main/kotlin/platform/mixin/util/MixinConstants.kt @@ -38,6 +38,7 @@ object MixinConstants { const val CONSTANT_CONDITION = "org.spongepowered.asm.mixin.injection.Constant.Condition" const val INJECTION_POINT = "org.spongepowered.asm.mixin.injection.InjectionPoint" const val SELECTOR = "org.spongepowered.asm.mixin.injection.InjectionPoint.Selector" + const val TARGET_SELECTOR = "org.spongepowered.asm.mixin.injection.selectors.TargetSelector" const val MIXIN_AGENT = "org.spongepowered.tools.agent.MixinAgent" const val MIXIN_CONFIG = "org.spongepowered.asm.mixin.transformer.MixinConfig" const val MIXIN_PLUGIN = "org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin" From 7127a3c340e336c08e028fc2c33a900ef23bbfc0 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Fri, 17 May 2024 01:17:32 +0200 Subject: [PATCH 11/22] Add 2024.2 to readme --- readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/readme.md b/readme.md index f70860d0e..3ccfde97a 100644 --- a/readme.md +++ b/readme.md @@ -23,6 +23,10 @@ Minecraft Development for IntelliJ 2024.1 2024.1 Nightly Status + + 2024.2 + 2024.3 Nightly Status + OS Tests From 1af46a4c2d6ef4e319bafc3fd45a0449bfefeaf4 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Fri, 17 May 2024 01:19:41 +0200 Subject: [PATCH 12/22] Fix typo It's late okay --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 3ccfde97a..35be97586 100644 --- a/readme.md +++ b/readme.md @@ -25,7 +25,7 @@ Minecraft Development for IntelliJ 2024.2 - 2024.3 Nightly Status + 2024.2 Nightly Status OS Tests From 10bd8d77f8b7f85ae3fab2c116af5fb9cc6e9da5 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Tue, 28 May 2024 15:54:46 +0100 Subject: [PATCH 13/22] New: Add support for `@WrapMethod` in MixinExtras. (#2300) --- .../handlers/mixinextras/WrapMethodHandler.kt | 83 +++++++++++++++++++ .../mixinextras/WrapOperationHandler.kt | 19 +---- src/main/kotlin/platform/mixin/util/Mixin.kt | 13 +++ src/main/resources/META-INF/plugin.xml | 1 + 4 files changed, 99 insertions(+), 17 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/handlers/mixinextras/WrapMethodHandler.kt diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapMethodHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapMethodHandler.kt new file mode 100644 index 000000000..cd2011c19 --- /dev/null +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapMethodHandler.kt @@ -0,0 +1,83 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 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 + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.handlers.mixinextras + +import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.InsnResolutionInfo +import com.demonwav.mcdev.platform.mixin.inspection.injector.MethodSignature +import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup +import com.demonwav.mcdev.platform.mixin.util.findSourceElement +import com.demonwav.mcdev.platform.mixin.util.getGenericReturnType +import com.demonwav.mcdev.platform.mixin.util.mixinExtrasOperationType +import com.demonwav.mcdev.util.Parameter +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiElement +import com.intellij.psi.search.GlobalSearchScope +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodNode + +class WrapMethodHandler : InjectorAnnotationHandler() { + override fun expectedMethodSignature( + annotation: PsiAnnotation, + targetClass: ClassNode, + targetMethod: MethodNode, + ): List { + val returnType = targetMethod.getGenericReturnType(targetClass, annotation.project) + + return listOf( + MethodSignature( + listOf( + ParameterGroup( + collectTargetMethodParameters(annotation.project, targetClass, targetMethod) + + Parameter( + "original", + mixinExtrasOperationType(annotation, returnType) ?: return emptyList() + ), + ) + ), + returnType + ) + ) + } + + override fun isUnresolved( + annotation: PsiAnnotation, + targetClass: ClassNode, + targetMethod: MethodNode + ): InsnResolutionInfo.Failure? { + // If we've got a target method that's good enough + return null + } + + override fun resolveForNavigation( + annotation: PsiAnnotation, + targetClass: ClassNode, + targetMethod: MethodNode + ): List { + val project = annotation.project + return targetMethod.findSourceElement( + targetClass, + project, + GlobalSearchScope.allScope(project), + canDecompile = true + )?.let(::listOf).orEmpty() + } +} diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt index 516256102..a5e2242e8 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt @@ -21,12 +21,9 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup -import com.demonwav.mcdev.platform.mixin.util.MixinConstants.MixinExtras.OPERATION +import com.demonwav.mcdev.platform.mixin.util.mixinExtrasOperationType import com.demonwav.mcdev.util.Parameter -import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiPrimitiveType import com.intellij.psi.PsiType import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode @@ -50,21 +47,9 @@ class WrapOperationHandler : MixinExtrasInjectorAnnotationHandler() { ): Pair? { val params = getPsiParameters(insn, targetClass, annotation) ?: return null val returnType = getPsiReturnType(insn, annotation) ?: return null - val operationType = getOperationType(annotation, returnType) ?: return null + val operationType = mixinExtrasOperationType(annotation, returnType) ?: return null return ParameterGroup( params + Parameter("original", operationType) ) to returnType } - - private fun getOperationType(context: PsiElement, type: PsiType): PsiType? { - val project = context.project - val boxedType = if (type is PsiPrimitiveType) { - type.getBoxedType(context) ?: return null - } else { - type - } - - return JavaPsiFacade.getElementFactory(project) - .createTypeFromText("$OPERATION<${boxedType.canonicalText}>", context) - } } diff --git a/src/main/kotlin/platform/mixin/util/Mixin.kt b/src/main/kotlin/platform/mixin/util/Mixin.kt index b7bd17629..844ee1433 100644 --- a/src/main/kotlin/platform/mixin/util/Mixin.kt +++ b/src/main/kotlin/platform/mixin/util/Mixin.kt @@ -27,6 +27,7 @@ import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.INVOKER import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.MIXIN import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Classes.CALLBACK_INFO import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Classes.CALLBACK_INFO_RETURNABLE +import com.demonwav.mcdev.platform.mixin.util.MixinConstants.MixinExtras.OPERATION import com.demonwav.mcdev.util.cached import com.demonwav.mcdev.util.computeStringArray import com.demonwav.mcdev.util.findModule @@ -154,6 +155,18 @@ fun callbackInfoReturnableType(project: Project, context: PsiElement, returnType .createTypeFromText("$CALLBACK_INFO_RETURNABLE<${boxedType.canonicalText}>", context) } +fun mixinExtrasOperationType(context: PsiElement, type: PsiType): PsiType? { + val project = context.project + val boxedType = if (type is PsiPrimitiveType) { + type.getBoxedType(context) ?: return null + } else { + type + } + + return JavaPsiFacade.getElementFactory(project) + .createTypeFromText("$OPERATION<${boxedType.canonicalText}>", context) +} + fun isAssignable(left: PsiType, right: PsiType, allowPrimitiveConversion: Boolean = true): Boolean { return when { left is PsiIntersectionType -> left.conjuncts.all { isAssignable(it, right) } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 45d635370..4f18c81e1 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -142,6 +142,7 @@ + From efcfd5dee27fd5a722e84faf635554938357900c Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sat, 1 Jun 2024 14:37:30 +0100 Subject: [PATCH 14/22] New: Support `WrongOperationParametersInspection` in `@WrapMethod`s. (#2303) --- .../mixinextras/WrongOperationParametersInspection.kt | 4 +++- src/main/kotlin/platform/mixin/util/MixinConstants.kt | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/platform/mixin/inspection/mixinextras/WrongOperationParametersInspection.kt b/src/main/kotlin/platform/mixin/inspection/mixinextras/WrongOperationParametersInspection.kt index 34491a9fa..12416ea77 100644 --- a/src/main/kotlin/platform/mixin/inspection/mixinextras/WrongOperationParametersInspection.kt +++ b/src/main/kotlin/platform/mixin/inspection/mixinextras/WrongOperationParametersInspection.kt @@ -61,7 +61,9 @@ class WrongOperationParametersInspection : MixinInspection() { PsiField::class.java ) ?: return - if (!containingMethod.hasAnnotation(MixinConstants.MixinExtras.WRAP_OPERATION)) { + if (!containingMethod.hasAnnotation(MixinConstants.MixinExtras.WRAP_OPERATION) && + !containingMethod.hasAnnotation(MixinConstants.MixinExtras.WRAP_METHOD) + ) { return } diff --git a/src/main/kotlin/platform/mixin/util/MixinConstants.kt b/src/main/kotlin/platform/mixin/util/MixinConstants.kt index 7d74f6cdb..93ae8408d 100644 --- a/src/main/kotlin/platform/mixin/util/MixinConstants.kt +++ b/src/main/kotlin/platform/mixin/util/MixinConstants.kt @@ -86,6 +86,7 @@ object MixinConstants { object MixinExtras { const val OPERATION = "com.llamalad7.mixinextras.injector.wrapoperation.Operation" const val WRAP_OPERATION = "com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation" + const val WRAP_METHOD = "com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod" const val LOCAL = "com.llamalad7.mixinextras.sugar.Local" const val LOCAL_REF_PACKAGE = "com.llamalad7.mixinextras.sugar.ref." From b7276ac69453e16ea81c4d7987b7d38a45cf71ba Mon Sep 17 00:00:00 2001 From: RedNesto Date: Fri, 14 Jun 2024 11:09:51 +0200 Subject: [PATCH 15/22] Minecraft 1.21 templates --- .../bungeecord/creator/bungee-platforms.kt | 2 + .../platform/forge/creator/asset-steps.kt | 2 + .../forge/util/ForgePackDescriptor.kt | 6 +- .../platform/neoforge/creator/asset-steps.kt | 1 + .../platform/neoforge/creator/gradle-steps.kt | 5 + src/main/kotlin/util/MinecraftTemplates.kt | 6 + src/main/kotlin/util/MinecraftVersions.kt | 2 + .../forge/Forge (1.20.6+) Main Class.java.ft | 125 ++++++++++++++++++ .../Forge (1.20.6+) Main Class.java.html | 25 ++++ .../j2ee/forge/Forge (1.21+) Config.java.ft | 64 +++++++++ .../j2ee/forge/Forge (1.21+) Config.java.html | 25 ++++ .../neoforge/NeoForge (1.21) Config.java.ft | 63 +++++++++ .../neoforge/NeoForge (1.21) Config.java.html | 25 ++++ .../neoforge/NeoForge gradle.properties.ft | 2 +- 14 files changed, 350 insertions(+), 3 deletions(-) create mode 100644 src/main/resources/fileTemplates/j2ee/forge/Forge (1.20.6+) Main Class.java.ft create mode 100644 src/main/resources/fileTemplates/j2ee/forge/Forge (1.20.6+) Main Class.java.html create mode 100644 src/main/resources/fileTemplates/j2ee/forge/Forge (1.21+) Config.java.ft create mode 100644 src/main/resources/fileTemplates/j2ee/forge/Forge (1.21+) Config.java.html create mode 100644 src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.21) Config.java.ft create mode 100644 src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.21) Config.java.html diff --git a/src/main/kotlin/platform/bungeecord/creator/bungee-platforms.kt b/src/main/kotlin/platform/bungeecord/creator/bungee-platforms.kt index 254c24c49..04b321428 100644 --- a/src/main/kotlin/platform/bungeecord/creator/bungee-platforms.kt +++ b/src/main/kotlin/platform/bungeecord/creator/bungee-platforms.kt @@ -28,6 +28,8 @@ import com.demonwav.mcdev.util.SemanticVersion class BungeeMainPlatformStep(parent: BungeePlatformStep) : AbstractBungeePlatformStep(parent, PlatformType.BUNGEECORD) { override fun getRepositories(mcVersion: SemanticVersion) = listOf( BuildRepository("sonatype", "https://oss.sonatype.org/content/groups/public/"), + // Seems to be required since 1.21 + BuildRepository("Minecraft Libraries", "https://libraries.minecraft.net/"), ) override fun getDependencies(mcVersion: SemanticVersion) = listOf( diff --git a/src/main/kotlin/platform/forge/creator/asset-steps.kt b/src/main/kotlin/platform/forge/creator/asset-steps.kt index a52811858..e68058f8f 100644 --- a/src/main/kotlin/platform/forge/creator/asset-steps.kt +++ b/src/main/kotlin/platform/forge/creator/asset-steps.kt @@ -108,6 +108,7 @@ class ForgeProjectFilesStep(parent: NewProjectWizardStep) : AbstractLongRunningA } val mainClassTemplate = when { + mcVersion >= MinecraftVersions.MC1_20_6 -> MinecraftTemplates.FG3_1_20_6_MAIN_CLASS_TEMPLATE mcVersion >= MinecraftVersions.MC1_20 -> MinecraftTemplates.FG3_1_20_MAIN_CLASS_TEMPLATE mcVersion >= MinecraftVersions.MC1_19_3 -> MinecraftTemplates.FG3_1_19_3_MAIN_CLASS_TEMPLATE mcVersion >= MinecraftVersions.MC1_19 -> MinecraftTemplates.FG3_1_19_MAIN_CLASS_TEMPLATE @@ -124,6 +125,7 @@ class ForgeProjectFilesStep(parent: NewProjectWizardStep) : AbstractLongRunningA ) val configTemplate = when { + mcVersion >= MinecraftVersions.MC1_21 -> MinecraftTemplates.FG3_1_21_CONFIG_TEMPLATE mcVersion >= MinecraftVersions.MC1_20 -> MinecraftTemplates.FG3_1_20_CONFIG_TEMPLATE else -> null } diff --git a/src/main/kotlin/platform/forge/util/ForgePackDescriptor.kt b/src/main/kotlin/platform/forge/util/ForgePackDescriptor.kt index 0b984a288..9fb68a20a 100644 --- a/src/main/kotlin/platform/forge/util/ForgePackDescriptor.kt +++ b/src/main/kotlin/platform/forge/util/ForgePackDescriptor.kt @@ -55,7 +55,8 @@ data class ForgePackDescriptor(val format: Int, val comment: String) { val FORMAT_15 = ForgePackDescriptor(15, "") val FORMAT_18 = ForgePackDescriptor(18, "") val FORMAT_26 = ForgePackDescriptor(26, "") - val FORMAT_41 = ForgePackDescriptor(26, "") + val FORMAT_41 = ForgePackDescriptor(41, "") + val FORMAT_48 = ForgePackDescriptor(48, "") // See https://minecraft.gamepedia.com/Tutorials/Creating_a_resource_pack#.22pack_format.22 fun forMcVersion(version: SemanticVersion): ForgePackDescriptor? = when { @@ -71,7 +72,8 @@ data class ForgePackDescriptor(val format: Int, val comment: String) { version < MinecraftVersions.MC1_20_2 -> FORMAT_15 version < MinecraftVersions.MC1_20_3 -> FORMAT_18 version < MinecraftVersions.MC1_20_5 -> FORMAT_26 - version >= MinecraftVersions.MC1_20_5 -> FORMAT_41 + version < MinecraftVersions.MC1_21 -> FORMAT_41 + version >= MinecraftVersions.MC1_21 -> FORMAT_48 else -> null } } diff --git a/src/main/kotlin/platform/neoforge/creator/asset-steps.kt b/src/main/kotlin/platform/neoforge/creator/asset-steps.kt index 83978733c..1d7af10e6 100644 --- a/src/main/kotlin/platform/neoforge/creator/asset-steps.kt +++ b/src/main/kotlin/platform/neoforge/creator/asset-steps.kt @@ -122,6 +122,7 @@ class NeoForgeProjectFilesStep(parent: NewProjectWizardStep) : AbstractLongRunni ) val configClassTemplate = when { + mcVersion >= MinecraftVersions.MC1_21 -> MinecraftTemplates.NEOFORGE_1_21_CONFIG_TEMPLATE mcVersion >= MinecraftVersions.MC1_20_5 -> MinecraftTemplates.NEOFORGE_1_20_5_CONFIG_TEMPLATE else -> MinecraftTemplates.NEOFORGE_CONFIG_TEMPLATE } diff --git a/src/main/kotlin/platform/neoforge/creator/gradle-steps.kt b/src/main/kotlin/platform/neoforge/creator/gradle-steps.kt index a6a6deb18..e1592551e 100644 --- a/src/main/kotlin/platform/neoforge/creator/gradle-steps.kt +++ b/src/main/kotlin/platform/neoforge/creator/gradle-steps.kt @@ -87,6 +87,10 @@ class NeoForgeGradleFilesStep(parent: NewProjectWizardStep) : AbstractLongRunnin } else { mcVersion } + val loaderVersion = when { + mcVersion < MinecraftVersions.MC1_21 -> "2" + else -> "4" + } data.putUserData(GRADLE_VERSION_KEY, ngWrapperVersion) @@ -97,6 +101,7 @@ class NeoForgeGradleFilesStep(parent: NewProjectWizardStep) : AbstractLongRunnin "MC_NEXT_VERSION" to mcNextVersion, "NEOFORGE_VERSION" to neoforgeVersion, "NEOFORGE_SPEC_VERSION" to neoforgeVersion.parts[0].versionString, + "LOADER_VERSION" to loaderVersion, "NEOGRADLE_VERSION" to neogradleVersion, "GROUP_ID" to buildSystemProps.groupId, "ARTIFACT_ID" to buildSystemProps.artifactId, diff --git a/src/main/kotlin/util/MinecraftTemplates.kt b/src/main/kotlin/util/MinecraftTemplates.kt index 3769ea168..5674d58c4 100644 --- a/src/main/kotlin/util/MinecraftTemplates.kt +++ b/src/main/kotlin/util/MinecraftTemplates.kt @@ -87,7 +87,9 @@ class MinecraftTemplates : FileTemplateGroupDescriptorFactory { forgeGroup.addTemplate(FileTemplateDescriptor(FG3_1_19_MAIN_CLASS_TEMPLATE)) forgeGroup.addTemplate(FileTemplateDescriptor(FG3_1_19_3_MAIN_CLASS_TEMPLATE)) forgeGroup.addTemplate(FileTemplateDescriptor(FG3_1_20_MAIN_CLASS_TEMPLATE)) + forgeGroup.addTemplate(FileTemplateDescriptor(FG3_1_20_6_MAIN_CLASS_TEMPLATE)) forgeGroup.addTemplate(FileTemplateDescriptor(FG3_1_20_CONFIG_TEMPLATE)) + forgeGroup.addTemplate(FileTemplateDescriptor(FG3_1_21_CONFIG_TEMPLATE)) forgeGroup.addTemplate(FileTemplateDescriptor(FG3_BUILD_GRADLE_TEMPLATE)) forgeGroup.addTemplate(FileTemplateDescriptor(FG3_GRADLE_PROPERTIES_TEMPLATE)) forgeGroup.addTemplate(FileTemplateDescriptor(FG3_SETTINGS_GRADLE_TEMPLATE)) @@ -214,7 +216,9 @@ class MinecraftTemplates : FileTemplateGroupDescriptorFactory { const val FG3_1_19_MAIN_CLASS_TEMPLATE = "Forge (1.19+) Main Class.java" const val FG3_1_19_3_MAIN_CLASS_TEMPLATE = "Forge (1.19.3+) Main Class.java" const val FG3_1_20_MAIN_CLASS_TEMPLATE = "Forge (1.20+) Main Class.java" + const val FG3_1_20_6_MAIN_CLASS_TEMPLATE = "Forge (1.20.6+) Main Class.java" const val FG3_1_20_CONFIG_TEMPLATE = "Forge (1.20+) Config.java" + const val FG3_1_21_CONFIG_TEMPLATE = "Forge (1.21+) Config.java" const val FG3_BUILD_GRADLE_TEMPLATE = "Forge (1.13+) build.gradle" const val FG3_GRADLE_PROPERTIES_TEMPLATE = "Forge (1.13+) gradle.properties" const val FG3_SETTINGS_GRADLE_TEMPLATE = "Forge (1.13+) settings.gradle" @@ -282,6 +286,8 @@ class MinecraftTemplates : FileTemplateGroupDescriptorFactory { const val NEOFORGE_1_20_5_CONFIG_TEMPLATE = "NeoForge (1.20.5) Config.java" const val NEOFORGE_1_20_5_BUILD_GRADLE_TEMPLATE = "NeoForge (1.20.5) build.gradle" + const val NEOFORGE_1_21_CONFIG_TEMPLATE = "NeoForge (1.21) Config.java" + const val NEOFORGE_BLOCK_TEMPLATE = "NeoForgeBlock.java" const val NEOFORGE_ITEM_TEMPLATE = "NeoForgeItem.java" const val NEOFORGE_PACKET_TEMPLATE = "NeoForgePacket.java" diff --git a/src/main/kotlin/util/MinecraftVersions.kt b/src/main/kotlin/util/MinecraftVersions.kt index 6c446635e..9d19a575c 100644 --- a/src/main/kotlin/util/MinecraftVersions.kt +++ b/src/main/kotlin/util/MinecraftVersions.kt @@ -39,6 +39,8 @@ object MinecraftVersions { val MC1_20_3 = SemanticVersion.release(1, 20, 3) val MC1_20_4 = SemanticVersion.release(1, 20, 4) val MC1_20_5 = SemanticVersion.release(1, 20, 5) + val MC1_20_6 = SemanticVersion.release(1, 20, 6) + val MC1_21 = SemanticVersion.release(1, 21) fun requiredJavaVersion(minecraftVersion: SemanticVersion) = when { minecraftVersion <= MC1_16_5 -> JavaSdkVersion.JDK_1_8 diff --git a/src/main/resources/fileTemplates/j2ee/forge/Forge (1.20.6+) Main Class.java.ft b/src/main/resources/fileTemplates/j2ee/forge/Forge (1.20.6+) Main Class.java.ft new file mode 100644 index 000000000..11a627ec8 --- /dev/null +++ b/src/main/resources/fileTemplates/j2ee/forge/Forge (1.20.6+) Main Class.java.ft @@ -0,0 +1,125 @@ +package ${PACKAGE_NAME}; + +import com.mojang.logging.LogUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.core.registries.Registries; +import net.minecraft.world.food.FoodProperties; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.material.MapColor; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.BuildCreativeModeTabContentsEvent; +import net.minecraftforge.event.server.ServerStartingEvent; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.ModLoadingContext; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.config.ModConfig; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; +import org.slf4j.Logger; + +// The value here should match an entry in the META-INF/mods.toml file +@Mod(${CLASS_NAME}.MODID) +public class ${CLASS_NAME} { + + // Define mod id in a common place for everything to reference + public static final String MODID = "${MOD_ID}"; + // Directly reference a slf4j logger + private static final Logger LOGGER = LogUtils.getLogger(); + // Create a Deferred Register to hold Blocks which will all be registered under the "${MOD_ID}" namespace + public static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID); + // Create a Deferred Register to hold Items which will all be registered under the "${MOD_ID}" namespace + public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MODID); + // Create a Deferred Register to hold CreativeModeTabs which will all be registered under the "examplemod" namespace + public static final DeferredRegister CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID); + + // Creates a new Block with the id "${MOD_ID}:example_block", combining the namespace and path + public static final RegistryObject EXAMPLE_BLOCK = BLOCKS.register("example_block", () -> new Block(BlockBehaviour.Properties.of().mapColor(MapColor.STONE))); + // Creates a new BlockItem with the id "${MOD_ID}:example_block", combining the namespace and path + public static final RegistryObject EXAMPLE_BLOCK_ITEM = ITEMS.register("example_block", () -> new BlockItem(EXAMPLE_BLOCK.get(), new Item.Properties())); + + // Creates a new food item with the id "examplemod:example_id", nutrition 1 and saturation 2 + public static final RegistryObject EXAMPLE_ITEM = ITEMS.register("example_item", () -> new Item(new Item.Properties().food(new FoodProperties.Builder() + .alwaysEdible().nutrition(1).saturationModifier(2f).build()))); + + // Creates a creative tab with the id "examplemod:example_tab" for the example item, that is placed after the combat tab + public static final RegistryObject EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab", () -> CreativeModeTab.builder() + .withTabsBefore(CreativeModeTabs.COMBAT) + .icon(() -> EXAMPLE_ITEM.get().getDefaultInstance()) + .displayItems((parameters, output) -> { + output.accept(EXAMPLE_ITEM.get()); // Add the example item to the tab. For your own tabs, this method is preferred over the event + }).build()); + + public ${CLASS_NAME}() { + IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); + + // Register the commonSetup method for modloading + modEventBus.addListener(this::commonSetup); + + // Register the Deferred Register to the mod event bus so blocks get registered + BLOCKS.register(modEventBus); + // Register the Deferred Register to the mod event bus so items get registered + ITEMS.register(modEventBus); + // Register the Deferred Register to the mod event bus so tabs get registered + CREATIVE_MODE_TABS.register(modEventBus); + + // Register ourselves for server and other game events we are interested in + MinecraftForge.EVENT_BUS.register(this); + + // Register the item to a creative tab + modEventBus.addListener(this::addCreative); + + // Register our mod's ForgeConfigSpec so that Forge can create and load the config file for us + ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, Config.SPEC); + } + + private void commonSetup(final FMLCommonSetupEvent event) { + // Some common setup code + LOGGER.info("HELLO FROM COMMON SETUP"); + LOGGER.info("DIRT BLOCK >> {}", ForgeRegistries.BLOCKS.getKey(Blocks.DIRT)); + + if (Config.logDirtBlock) + LOGGER.info("DIRT BLOCK >> {}", ForgeRegistries.BLOCKS.getKey(Blocks.DIRT)); + + LOGGER.info(Config.magicNumberIntroduction + Config.magicNumber); + + Config.items.forEach((item) -> LOGGER.info("ITEM >> {}", item.toString())); + } + + // Add the example block item to the building blocks tab + private void addCreative(BuildCreativeModeTabContentsEvent event) + { + if (event.getTabKey() == CreativeModeTabs.BUILDING_BLOCKS) + event.accept(EXAMPLE_BLOCK_ITEM); + } + // You can use SubscribeEvent and let the Event Bus discover methods to call + @SubscribeEvent + public void onServerStarting(ServerStartingEvent event) { + // Do something when the server starts + LOGGER.info("HELLO from server starting"); + } + + // You can use EventBusSubscriber to automatically register all static methods in the class annotated with @SubscribeEvent + @Mod.EventBusSubscriber(modid = MODID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) + public static class ClientModEvents { + + @SubscribeEvent + public static void onClientSetup(FMLClientSetupEvent event) + { + // Some client setup code + LOGGER.info("HELLO FROM CLIENT SETUP"); + LOGGER.info("MINECRAFT NAME >> {}", Minecraft.getInstance().getUser().getName()); + } + } +} diff --git a/src/main/resources/fileTemplates/j2ee/forge/Forge (1.20.6+) Main Class.java.html b/src/main/resources/fileTemplates/j2ee/forge/Forge (1.20.6+) Main Class.java.html new file mode 100644 index 000000000..a4d2b9279 --- /dev/null +++ b/src/main/resources/fileTemplates/j2ee/forge/Forge (1.20.6+) Main Class.java.html @@ -0,0 +1,25 @@ + + + + +

This is a built-in file template used to create a new main class for Forge projects 1.20.6 and above

+ + diff --git a/src/main/resources/fileTemplates/j2ee/forge/Forge (1.21+) Config.java.ft b/src/main/resources/fileTemplates/j2ee/forge/Forge (1.21+) Config.java.ft new file mode 100644 index 000000000..3a49f15d7 --- /dev/null +++ b/src/main/resources/fileTemplates/j2ee/forge/Forge (1.21+) Config.java.ft @@ -0,0 +1,64 @@ +package ${PACKAGE_NAME}; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraftforge.common.ForgeConfigSpec; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.config.ModConfigEvent; +import net.minecraftforge.registries.ForgeRegistries; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +// An example config class. This is not required, but it's a good idea to have one to keep your config organized. +// Demonstrates how to use Forge's config APIs +@Mod.EventBusSubscriber(modid = ${CLASS_NAME}.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) +public class Config +{ + private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); + + private static final ForgeConfigSpec.BooleanValue LOG_DIRT_BLOCK = BUILDER + .comment("Whether to log the dirt block on common setup") + .define("logDirtBlock", true); + + private static final ForgeConfigSpec.IntValue MAGIC_NUMBER = BUILDER + .comment("A magic number") + .defineInRange("magicNumber", 42, 0, Integer.MAX_VALUE); + + public static final ForgeConfigSpec.ConfigValue MAGIC_NUMBER_INTRODUCTION = BUILDER + .comment("What you want the introduction message to be for the magic number") + .define("magicNumberIntroduction", "The magic number is... "); + + // a list of strings that are treated as resource locations for items + private static final ForgeConfigSpec.ConfigValue> ITEM_STRINGS = BUILDER + .comment("A list of items to log on common setup.") + .defineListAllowEmpty("items", List.of("minecraft:iron_ingot"), Config::validateItemName); + + static final ForgeConfigSpec SPEC = BUILDER.build(); + + public static boolean logDirtBlock; + public static int magicNumber; + public static String magicNumberIntroduction; + public static Set items; + + private static boolean validateItemName(final Object obj) + { + return obj instanceof final String itemName && ForgeRegistries.ITEMS.containsKey(ResourceLocation.parse(itemName)); + } + + @SubscribeEvent + static void onLoad(final ModConfigEvent event) + { + logDirtBlock = LOG_DIRT_BLOCK.get(); + magicNumber = MAGIC_NUMBER.get(); + magicNumberIntroduction = MAGIC_NUMBER_INTRODUCTION.get(); + + // convert the list of strings into a set of items + items = ITEM_STRINGS.get().stream() + .map(itemName -> ForgeRegistries.ITEMS.getValue(ResourceLocation.parse(itemName))) + .collect(Collectors.toSet()); + } +} diff --git a/src/main/resources/fileTemplates/j2ee/forge/Forge (1.21+) Config.java.html b/src/main/resources/fileTemplates/j2ee/forge/Forge (1.21+) Config.java.html new file mode 100644 index 000000000..7bc7c827e --- /dev/null +++ b/src/main/resources/fileTemplates/j2ee/forge/Forge (1.21+) Config.java.html @@ -0,0 +1,25 @@ + + + + +

This is a built-in file template used to create a new config class for Forge projects 1.21 and above

+ + diff --git a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.21) Config.java.ft b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.21) Config.java.ft new file mode 100644 index 000000000..b04b88c8f --- /dev/null +++ b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.21) Config.java.ft @@ -0,0 +1,63 @@ +package ${PACKAGE_NAME}; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.fml.event.config.ModConfigEvent; +import net.neoforged.neoforge.common.ModConfigSpec; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +// An example config class. This is not required, but it's a good idea to have one to keep your config organized. +// Demonstrates how to use Neo's config APIs +@EventBusSubscriber(modid = ${CLASS_NAME}.MODID, bus = EventBusSubscriber.Bus.MOD) +public class Config +{ + private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder(); + + private static final ModConfigSpec.BooleanValue LOG_DIRT_BLOCK = BUILDER + .comment("Whether to log the dirt block on common setup") + .define("logDirtBlock", true); + + private static final ModConfigSpec.IntValue MAGIC_NUMBER = BUILDER + .comment("A magic number") + .defineInRange("magicNumber", 42, 0, Integer.MAX_VALUE); + + public static final ModConfigSpec.ConfigValue MAGIC_NUMBER_INTRODUCTION = BUILDER + .comment("What you want the introduction message to be for the magic number") + .define("magicNumberIntroduction", "The magic number is... "); + + // a list of strings that are treated as resource locations for items + private static final ModConfigSpec.ConfigValue> ITEM_STRINGS = BUILDER + .comment("A list of items to log on common setup.") + .defineListAllowEmpty("items", List.of("minecraft:iron_ingot"), Config::validateItemName); + + static final ModConfigSpec SPEC = BUILDER.build(); + + public static boolean logDirtBlock; + public static int magicNumber; + public static String magicNumberIntroduction; + public static Set items; + + private static boolean validateItemName(final Object obj) + { + return obj instanceof String itemName && BuiltInRegistries.ITEM.containsKey(ResourceLocation.parse(itemName)); + } + + @SubscribeEvent + static void onLoad(final ModConfigEvent event) + { + logDirtBlock = LOG_DIRT_BLOCK.get(); + magicNumber = MAGIC_NUMBER.get(); + magicNumberIntroduction = MAGIC_NUMBER_INTRODUCTION.get(); + + // convert the list of strings into a set of items + items = ITEM_STRINGS.get().stream() + .map(itemName -> BuiltInRegistries.ITEM.get(ResourceLocation.parse(itemName))) + .collect(Collectors.toSet()); + } +} diff --git a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.21) Config.java.html b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.21) Config.java.html new file mode 100644 index 000000000..8ff2f24fa --- /dev/null +++ b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge (1.21) Config.java.html @@ -0,0 +1,25 @@ + + + + +

This is a built-in file template used to create a new config class for NeoForge 1.21 projects.

+ + diff --git a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge gradle.properties.ft b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge gradle.properties.ft index 91afe9ac6..3fff05605 100644 --- a/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge gradle.properties.ft +++ b/src/main/resources/fileTemplates/j2ee/neoforge/NeoForge gradle.properties.ft @@ -16,7 +16,7 @@ neo_version=${NEOFORGE_VERSION} # The Neo version range can use any version of Neo as bounds neo_version_range=[${NEOFORGE_SPEC_VERSION},) # The loader version range can only use the major version of FML as bounds -loader_version_range=[2,) +loader_version_range=[${LOADER_VERSION},) #if (${PARCHMENT_VERSION}) neogradle.subsystems.parchment.minecraftVersion=${PARCHMENT_MC_VERSION} From 4bb60870ed26865a53d9c7375ef4b2eba04df967 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Fri, 14 Jun 2024 12:06:31 +0200 Subject: [PATCH 16/22] Version 1.7.6 --- gradle.properties | 2 +- readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 8e27d9470..9bebb7a1c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,7 +24,7 @@ kotlin.code.style=official ideaVersion = 2023.2.2 ideaVersionName = 2023.2.2 -coreVersion = 1.7.5 +coreVersion = 1.7.6 downloadIdeaSources = true pluginTomlVersion = 232.8660.88 diff --git a/readme.md b/readme.md index 35be97586..6d607a566 100644 --- a/readme.md +++ b/readme.md @@ -35,7 +35,7 @@ Minecraft Development for IntelliJ -Info and Documentation [![Current Release](https://img.shields.io/badge/release-1.7.5-orange.svg?style=flat-square)](https://plugins.jetbrains.com/plugin/8327) +Info and Documentation [![Current Release](https://img.shields.io/badge/release-1.7.6-orange.svg?style=flat-square)](https://plugins.jetbrains.com/plugin/8327) ---------------------- From 41240cad956f4148291c28c4c25775de27cc874e Mon Sep 17 00:00:00 2001 From: Nel <57587152+nelind3@users.noreply.github.com> Date: Fri, 21 Jun 2024 01:02:59 +0200 Subject: [PATCH 17/22] fix: #2316 (#2317) --- .../inspection/SpongeInjectionInspection.kt | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/platform/sponge/inspection/SpongeInjectionInspection.kt b/src/main/kotlin/platform/sponge/inspection/SpongeInjectionInspection.kt index 7387d5d5a..76c14eb02 100644 --- a/src/main/kotlin/platform/sponge/inspection/SpongeInjectionInspection.kt +++ b/src/main/kotlin/platform/sponge/inspection/SpongeInjectionInspection.kt @@ -244,11 +244,13 @@ class SpongeInjectionInspection : AbstractBaseJavaLocalInspectionTool() { ) } } - "ninja.leaping.configurate.loader.ConfigurationLoader" -> { + "ninja.leaping.configurate.loader.ConfigurationLoader", + "org.spongepowered.configurate.reference.ConfigurationReference", + "org.spongepowered.configurate.loader.ConfigurationLoader" -> { if (defaultConfig == null) { holder.registerProblem( variable.nameIdentifier ?: variable, - "Injected ConfigurationLoader must be annotated with @DefaultConfig.", + "Injected ${classType.name} must be annotated with @DefaultConfig.", ProblemHighlightType.GENERIC_ERROR, AddAnnotationFix(SpongeConstants.DEFAULT_CONFIG_ANNOTATION, annotationsOwner), ) @@ -257,7 +259,7 @@ class SpongeInjectionInspection : AbstractBaseJavaLocalInspectionTool() { if (configDir != null) { holder.registerProblem( configDir, - "Injected ConfigurationLoader cannot be annotated with @ConfigDir.", + "Injected ${classType.name} cannot be annotated with @ConfigDir.", ProblemHighlightType.GENERIC_ERROR, QuickFixFactory.getInstance().createDeleteFix(configDir, "Remove @ConfigDir"), ) @@ -267,7 +269,7 @@ class SpongeInjectionInspection : AbstractBaseJavaLocalInspectionTool() { val ref = classType.reference holder.registerProblem( ref, - "Injected ConfigurationLoader must have a generic parameter.", + "Injected ${classType.name} must have a generic parameter.", ProblemHighlightType.GENERIC_ERROR, MissingConfLoaderTypeParamFix(ref), ) @@ -275,14 +277,17 @@ class SpongeInjectionInspection : AbstractBaseJavaLocalInspectionTool() { classType.parameters.firstOrNull()?.let { param -> val paramType = param as? PsiClassReferenceType ?: return@let val paramTypeFQName = paramType.fullQualifiedName ?: return@let - if (paramTypeFQName != "ninja.leaping.configurate.commented.CommentedConfigurationNode") { + if ( + paramTypeFQName != "ninja.leaping.configurate.commented.CommentedConfigurationNode" && + paramTypeFQName != "org.spongepowered.configurate.CommentedConfigurationNode" + ) { val ref = param.reference holder.registerProblem( ref, "Injected ConfigurationLoader generic parameter must be " + "CommentedConfigurationNode.", ProblemHighlightType.GENERIC_ERROR, - WrongConfLoaderTypeParamFix(ref), + WrongConfLoaderTypeParamFix(classType.className, ref), ) } } @@ -371,7 +376,8 @@ class SpongeInjectionInspection : AbstractBaseJavaLocalInspectionTool() { } } - class WrongConfLoaderTypeParamFix(ref: PsiJavaCodeReferenceElement) : LocalQuickFixOnPsiElement(ref) { + class WrongConfLoaderTypeParamFix(private val clazzName: String, param: PsiJavaCodeReferenceElement) : + LocalQuickFixOnPsiElement(param) { override fun getFamilyName(): String = name @@ -379,7 +385,11 @@ class SpongeInjectionInspection : AbstractBaseJavaLocalInspectionTool() { override fun invoke(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement) { val newRef = JavaPsiFacade.getElementFactory(project).createReferenceFromText( - "ninja.leaping.configurate.commented.CommentedConfigurationNode", + when (clazzName) { + "ninja.leaping.configurate.loader.ConfigurationLoader" -> + "ninja.leaping.configurate.commented.CommentedConfigurationNode" + else -> { "org.spongepowered.configurate.CommentedConfigurationNode" } + }, startElement, ) startElement.replace(newRef) @@ -393,11 +403,23 @@ class SpongeInjectionInspection : AbstractBaseJavaLocalInspectionTool() { override fun getText(): String = "Insert generic parameter" override fun invoke(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement) { - val newRef = JavaPsiFacade.getElementFactory(project).createReferenceFromText( - "ninja.leaping.configurate.loader.ConfigurationLoader" + - "", - startElement, - ) + val newRef: PsiElement = if ( + JavaPsiFacade.getInstance(project) + .findPackage("ninja.leaping.configurate") != null + ) { + JavaPsiFacade.getElementFactory(project).createReferenceFromText( + "ninja.leaping.configurate.loader.ConfigurationLoader" + + "", + startElement + ) + } else { + JavaPsiFacade.getElementFactory(project).createReferenceFromText( + "org.spongepowered.configurate.loader.ConfigurationLoader" + + "", + startElement + ) + } + startElement.replace(newRef) } } From 6de337673004d203fd7ee14f891ea16f5c75c697 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Fri, 21 Jun 2024 10:04:26 +0200 Subject: [PATCH 18/22] Initial support for NeoForge's ModDevGradle Mappings don't work currently, the TSRG file seems to contain indices to an array in another JSON file? --- .../NeoModDevGradleModelBuilderImpl.groovy | 75 ++++++++++++++++++ .../neomoddev/NeoModDevGradleModelImpl.groovy | 43 +++++++++++ .../mcp/gradle/tooling/McpModelNMD.java | 30 ++++++++ ...plugins.gradle.tooling.ModelBuilderService | 1 + .../mcp/gradle/McpProjectResolverExtension.kt | 6 +- .../gradle/datahandler/McpModelNMDHandler.kt | 76 +++++++++++++++++++ 6 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 src/gradle-tooling-extension/groovy/com/demonwav/mcdev/platform/mcp/gradle/tooling/neomoddev/NeoModDevGradleModelBuilderImpl.groovy create mode 100644 src/gradle-tooling-extension/groovy/com/demonwav/mcdev/platform/mcp/gradle/tooling/neomoddev/NeoModDevGradleModelImpl.groovy create mode 100644 src/gradle-tooling-extension/java/com/demonwav/mcdev/platform/mcp/gradle/tooling/McpModelNMD.java create mode 100644 src/main/kotlin/platform/mcp/gradle/datahandler/McpModelNMDHandler.kt diff --git a/src/gradle-tooling-extension/groovy/com/demonwav/mcdev/platform/mcp/gradle/tooling/neomoddev/NeoModDevGradleModelBuilderImpl.groovy b/src/gradle-tooling-extension/groovy/com/demonwav/mcdev/platform/mcp/gradle/tooling/neomoddev/NeoModDevGradleModelBuilderImpl.groovy new file mode 100644 index 000000000..02ef8e305 --- /dev/null +++ b/src/gradle-tooling-extension/groovy/com/demonwav/mcdev/platform/mcp/gradle/tooling/neomoddev/NeoModDevGradleModelBuilderImpl.groovy @@ -0,0 +1,75 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 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 + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mcp.gradle.tooling.neomoddev + +import com.demonwav.mcdev.platform.mcp.gradle.tooling.McpModelNMD +import org.gradle.api.Project +import org.jetbrains.annotations.NotNull +import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder +import org.jetbrains.plugins.gradle.tooling.ModelBuilderService + +import java.nio.file.Files + +final class NeoModDevGradleModelBuilderImpl implements ModelBuilderService { + + @Override + boolean canBuild(String modelName) { + return McpModelNMD.name == modelName + } + + @Override + Object buildAll(String modelName, Project project) { + def extension = project.extensions.findByName('neoForge') + if (extension == null) { + return null + } + + if (!project.plugins.findPlugin("net.neoforged.moddev")) { + return null + } + + def neoforgeVersion = extension.version.get() + if (neoforgeVersion == null) { + return null + } + + def accessTransformers = extension.accessTransformers.get().collect { project.file(it) } + + // Hacky way to guess where the mappings file is, but I could not find a proper way to find it + def neoformDir = project.buildDir.toPath().resolve("neoForm") + def mappingsFile = Files.list(neoformDir) + .map { it.resolve("config/joined.tsrg") } + .filter { Files.exists(it) } + .findFirst() + .orElse(null) + ?.toFile() + + //noinspection GroovyAssignabilityCheck + return new NeoModDevGradleModelImpl(neoforgeVersion, mappingsFile, accessTransformers) + } + + @Override + ErrorMessageBuilder getErrorMessageBuilder(@NotNull Project project, @NotNull Exception e) { + return ErrorMessageBuilder.create( + project, e, "MinecraftDev import errors" + ).withDescription("Unable to build MinecraftDev MCP project configuration") + } +} diff --git a/src/gradle-tooling-extension/groovy/com/demonwav/mcdev/platform/mcp/gradle/tooling/neomoddev/NeoModDevGradleModelImpl.groovy b/src/gradle-tooling-extension/groovy/com/demonwav/mcdev/platform/mcp/gradle/tooling/neomoddev/NeoModDevGradleModelImpl.groovy new file mode 100644 index 000000000..5640af6fd --- /dev/null +++ b/src/gradle-tooling-extension/groovy/com/demonwav/mcdev/platform/mcp/gradle/tooling/neomoddev/NeoModDevGradleModelImpl.groovy @@ -0,0 +1,43 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 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 + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mcp.gradle.tooling.neomoddev + + +import com.demonwav.mcdev.platform.mcp.gradle.tooling.McpModelNMD +import groovy.transform.CompileStatic + +@CompileStatic +final class NeoModDevGradleModelImpl implements McpModelNMD, Serializable { + + final String neoForgeVersion + final File mappingsFile + final List accessTransformers + + NeoModDevGradleModelImpl( + final String neoForgeVersion, + final File mappingsFile, + final List accessTransformers + ) { + this.neoForgeVersion = neoForgeVersion + this.mappingsFile = mappingsFile + this.accessTransformers = accessTransformers + } +} diff --git a/src/gradle-tooling-extension/java/com/demonwav/mcdev/platform/mcp/gradle/tooling/McpModelNMD.java b/src/gradle-tooling-extension/java/com/demonwav/mcdev/platform/mcp/gradle/tooling/McpModelNMD.java new file mode 100644 index 000000000..beff8af28 --- /dev/null +++ b/src/gradle-tooling-extension/java/com/demonwav/mcdev/platform/mcp/gradle/tooling/McpModelNMD.java @@ -0,0 +1,30 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 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 + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mcp.gradle.tooling; + +import java.io.File; +import java.util.List; + +public interface McpModelNMD { + String getNeoForgeVersion(); + File getMappingsFile(); + List getAccessTransformers(); +} diff --git a/src/gradle-tooling-extension/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService b/src/gradle-tooling-extension/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService index 382ea8362..9746eaf54 100644 --- a/src/gradle-tooling-extension/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService +++ b/src/gradle-tooling-extension/resources/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService @@ -1,6 +1,7 @@ com.demonwav.mcdev.platform.mcp.gradle.tooling.archloom.ArchitecturyModelBuilderImpl com.demonwav.mcdev.platform.mcp.gradle.tooling.fabricloom.FabricLoomModelBuilderImpl com.demonwav.mcdev.platform.mcp.gradle.tooling.neogradle.NeoGradle7ModelBuilderImpl +com.demonwav.mcdev.platform.mcp.gradle.tooling.neomoddev.NeoModDevGradleModelBuilderImpl com.demonwav.mcdev.platform.mcp.gradle.tooling.vanillagradle.VanillaGradleModelBuilderImpl com.demonwav.mcdev.platform.mcp.gradle.tooling.McpModelFG2BuilderImpl com.demonwav.mcdev.platform.mcp.gradle.tooling.McpModelFG3BuilderImpl diff --git a/src/main/kotlin/platform/mcp/gradle/McpProjectResolverExtension.kt b/src/main/kotlin/platform/mcp/gradle/McpProjectResolverExtension.kt index 53b90142a..5d45a3d79 100644 --- a/src/main/kotlin/platform/mcp/gradle/McpProjectResolverExtension.kt +++ b/src/main/kotlin/platform/mcp/gradle/McpProjectResolverExtension.kt @@ -23,9 +23,11 @@ package com.demonwav.mcdev.platform.mcp.gradle import com.demonwav.mcdev.platform.mcp.gradle.datahandler.McpModelFG2Handler import com.demonwav.mcdev.platform.mcp.gradle.datahandler.McpModelFG3Handler import com.demonwav.mcdev.platform.mcp.gradle.datahandler.McpModelNG7Handler +import com.demonwav.mcdev.platform.mcp.gradle.datahandler.McpModelNMDHandler import com.demonwav.mcdev.platform.mcp.gradle.tooling.McpModelFG2 import com.demonwav.mcdev.platform.mcp.gradle.tooling.McpModelFG3 import com.demonwav.mcdev.platform.mcp.gradle.tooling.McpModelNG7 +import com.demonwav.mcdev.platform.mcp.gradle.tooling.McpModelNMD import com.demonwav.mcdev.util.runGradleTask import com.intellij.openapi.externalSystem.model.DataNode import com.intellij.openapi.externalSystem.model.project.ModuleData @@ -38,7 +40,7 @@ class McpProjectResolverExtension : AbstractProjectResolverExtension() { // Register our custom Gradle tooling API model in IntelliJ's project resolver override fun getExtraProjectModelClasses(): Set> = - setOf(McpModelFG2::class.java, McpModelFG3::class.java, McpModelNG7::class.java) + setOf(McpModelFG2::class.java, McpModelFG3::class.java, McpModelNG7::class.java, McpModelNMD::class.java) override fun getToolingExtensionsClasses() = extraProjectModelClasses @@ -91,6 +93,6 @@ class McpProjectResolverExtension : AbstractProjectResolverExtension() { } companion object { - private val handlers = listOf(McpModelFG2Handler, McpModelFG3Handler, McpModelNG7Handler) + private val handlers = listOf(McpModelFG2Handler, McpModelFG3Handler, McpModelNG7Handler, McpModelNMDHandler) } } diff --git a/src/main/kotlin/platform/mcp/gradle/datahandler/McpModelNMDHandler.kt b/src/main/kotlin/platform/mcp/gradle/datahandler/McpModelNMDHandler.kt new file mode 100644 index 000000000..ca496ad49 --- /dev/null +++ b/src/main/kotlin/platform/mcp/gradle/datahandler/McpModelNMDHandler.kt @@ -0,0 +1,76 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 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 + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mcp.gradle.datahandler + +import com.demonwav.mcdev.platform.mcp.McpModuleSettings +import com.demonwav.mcdev.platform.mcp.at.AtFileType +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.util.runWriteTaskLater +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.project.ModuleData +import com.intellij.openapi.fileTypes.ExactFileNameMatcher +import com.intellij.openapi.fileTypes.FileTypeManager +import com.intellij.openapi.vfs.LocalFileSystem +import org.gradle.tooling.model.idea.IdeaModule +import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData +import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext + +object McpModelNMDHandler : McpModelDataHandler { + + override fun build( + gradleModule: IdeaModule, + node: DataNode, + resolverCtx: ProjectResolverContext, + ) { + val data = resolverCtx.getExtraProject(gradleModule, McpModelNMD::class.java) ?: return + + val state = McpModuleSettings.State( + "1." + data.neoForgeVersion.substringBefore('.'), + null, + data.mappingsFile?.absolutePath, + SrgType.TSRG, + data.neoForgeVersion, + ) + + val ats = data.accessTransformers + if (ats != null && ats.isNotEmpty()) { + runWriteTaskLater { + for (at in ats) { + val fileTypeManager = FileTypeManager.getInstance() + val atFile = LocalFileSystem.getInstance().findFileByIoFile(at) ?: continue + fileTypeManager.associate(AtFileType, ExactFileNameMatcher(atFile.name)) + } + } + } + + val modelData = McpModelData(node.data, state, null, data.accessTransformers) + node.createChild(McpModelData.KEY, modelData) + + for (child in node.children) { + val childData = child.data + if (childData is GradleSourceSetData) { + child.createChild(McpModelData.KEY, modelData.copy(module = childData)) + } + } + } +} From 9da38412022ada1eb86303c955939d4f0dee67cb Mon Sep 17 00:00:00 2001 From: RedNesto Date: Tue, 2 Jul 2024 14:19:07 +0200 Subject: [PATCH 19/22] Improve fabric.mod.json entrypoints insight - Recognize entrypoints declared in object form - Add more conditions to the inspection - Add some tests to cover the inspection Fixes #2296 --- build.gradle.kts | 2 + gradle/libs.versions.toml | 1 + .../inspection/FabricEntrypointsInspection.kt | 48 +++- .../fabric/reference/EntryPointReference.kt | 54 ++-- .../reference/FabricReferenceContributor.kt | 26 +- .../fabric/reference/ResourceFileReference.kt | 40 ++- src/test/kotlin/framework/ProjectBuilder.kt | 6 + .../fabric/FabricEntrypointsInspectionTest.kt | 264 ++++++++++++++++++ 8 files changed, 396 insertions(+), 45 deletions(-) create mode 100644 src/test/kotlin/platform/fabric/FabricEntrypointsInspectionTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index f92d06dc6..fd9b13534 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -83,6 +83,7 @@ repositories { maven("https://maven.fabricmc.net/") { content { includeModule("net.fabricmc", "mapping-io") + includeModule("net.fabricmc", "fabric-loader") } } mavenCentral() @@ -119,6 +120,7 @@ dependencies { classifier = "shaded" } } + testLibs(libs.test.fabricloader) testLibs(libs.test.nbt) { artifact { extension = "nbt" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index abc4aad82..f8dabc6d7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,6 +33,7 @@ fuel-coroutines = { module = "com.github.kittinunf.fuel:fuel-coroutines", versio test-mockJdk = "org.jetbrains.idea:mock-jdk:1.7-4d76c50" test-mixin = "org.spongepowered:mixin:0.8.5" test-spongeapi = "org.spongepowered:spongeapi:7.4.0" +test-fabricloader = "net.fabricmc:fabric-loader:0.15.11" test-nbt = "com.demonwav.mcdev:all-types-nbt:1.0" junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } diff --git a/src/main/kotlin/platform/fabric/inspection/FabricEntrypointsInspection.kt b/src/main/kotlin/platform/fabric/inspection/FabricEntrypointsInspection.kt index 3ecde42dd..7092fca87 100644 --- a/src/main/kotlin/platform/fabric/inspection/FabricEntrypointsInspection.kt +++ b/src/main/kotlin/platform/fabric/inspection/FabricEntrypointsInspection.kt @@ -22,12 +22,15 @@ package com.demonwav.mcdev.platform.fabric.inspection import com.demonwav.mcdev.platform.fabric.reference.EntryPointReference import com.demonwav.mcdev.platform.fabric.util.FabricConstants +import com.demonwav.mcdev.util.equivalentTo import com.intellij.codeInspection.InspectionManager import com.intellij.codeInspection.LocalInspectionTool import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder +import com.intellij.json.psi.JsonArray import com.intellij.json.psi.JsonElementVisitor +import com.intellij.json.psi.JsonLiteral import com.intellij.json.psi.JsonProperty import com.intellij.json.psi.JsonStringLiteral import com.intellij.psi.JavaPsiFacade @@ -79,8 +82,7 @@ class FabricEntrypointsInspection : LocalInspectionTool() { val element = resolved.singleOrNull()?.element when { element is PsiClass && !literal.text.contains("::") -> { - val propertyKey = literal.parentOfType()?.name - val expectedType = propertyKey?.let { FabricConstants.ENTRYPOINT_BY_TYPE[it] } + val (propertyKey, expectedType) = findEntrypointKeyAndType(literal) if (propertyKey != null && expectedType != null && !isEntrypointOfCorrectType(element, propertyKey) ) { @@ -111,21 +113,43 @@ class FabricEntrypointsInspection : LocalInspectionTool() { reference.rangeInElement, ) } + + if (!element.hasModifierProperty(PsiModifier.PUBLIC)) { + holder.registerProblem( + literal, + "Entrypoint method must be public", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + reference.rangeInElement, + ) + } + + if (!element.hasModifierProperty(PsiModifier.STATIC)) { + val clazz = element.containingClass + if (clazz != null && clazz.constructors.isNotEmpty() && + clazz.constructors.find { !it.hasParameters() } == null + ) { + holder.registerProblem( + literal, + "Entrypoint instance method class must have an empty constructor", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + reference.rangeInElement, + ) + } + } } element is PsiField -> { - if (!element.hasModifierProperty(PsiModifier.STATIC)) { + if (!element.hasModifierProperty(PsiModifier.PUBLIC)) { holder.registerProblem( literal, - "Entrypoint field must be static", + "Entrypoint field must be public", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, reference.rangeInElement, ) } - val propertyKey = literal.parentOfType()?.name + val (propertyKey, expectedType) = findEntrypointKeyAndType(literal) val fieldTypeClass = (element.type as? PsiClassType)?.resolve() - val expectedType = propertyKey?.let { FabricConstants.ENTRYPOINT_BY_TYPE[it] } if (propertyKey != null && fieldTypeClass != null && expectedType != null && !isEntrypointOfCorrectType(fieldTypeClass, propertyKey) ) { @@ -141,11 +165,21 @@ class FabricEntrypointsInspection : LocalInspectionTool() { } } + private fun findEntrypointKeyAndType(literal: JsonLiteral): Pair { + val propertyKey = when (val parent = literal.parent) { + is JsonArray -> (parent.parent as? JsonProperty)?.name + is JsonProperty -> parent.parentOfType()?.name + else -> null + } + val expectedType = propertyKey?.let { FabricConstants.ENTRYPOINT_BY_TYPE[it] } + return propertyKey to expectedType + } + private fun isEntrypointOfCorrectType(element: PsiClass, type: String): Boolean { val entrypointClass = FabricConstants.ENTRYPOINT_BY_TYPE[type] ?: return false val clazz = JavaPsiFacade.getInstance(element.project).findClass(entrypointClass, element.resolveScope) - return clazz != null && element.isInheritor(clazz, true) + return clazz != null && (element.equivalentTo(clazz) || element.isInheritor(clazz, true)) } } } diff --git a/src/main/kotlin/platform/fabric/reference/EntryPointReference.kt b/src/main/kotlin/platform/fabric/reference/EntryPointReference.kt index 796058d64..1b8041c8a 100644 --- a/src/main/kotlin/platform/fabric/reference/EntryPointReference.kt +++ b/src/main/kotlin/platform/fabric/reference/EntryPointReference.kt @@ -25,6 +25,8 @@ import com.demonwav.mcdev.util.fullQualifiedName import com.demonwav.mcdev.util.manipulator import com.demonwav.mcdev.util.reference.InspectionReference import com.intellij.codeInsight.completion.JavaLookupElementBuilder +import com.intellij.json.psi.JsonArray +import com.intellij.json.psi.JsonProperty import com.intellij.json.psi.JsonStringLiteral import com.intellij.openapi.util.TextRange import com.intellij.psi.JavaPsiFacade @@ -40,7 +42,9 @@ import com.intellij.psi.PsiReference import com.intellij.psi.PsiReferenceBase import com.intellij.psi.PsiReferenceProvider import com.intellij.psi.ResolveResult +import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.searches.ClassInheritorsSearch +import com.intellij.psi.util.parentOfType import com.intellij.util.ArrayUtil import com.intellij.util.IncorrectOperationException import com.intellij.util.ProcessingContext @@ -136,27 +140,17 @@ object EntryPointReference : PsiReferenceProvider() { fun isEntryPointReference(reference: PsiReference) = reference is Reference - fun isValidEntrypointClass(element: PsiClass): Boolean { - val psiFacade = JavaPsiFacade.getInstance(element.project) - var inheritsEntrypointInterface = false - for (entrypoint in FabricConstants.ENTRYPOINTS) { - val entrypointClass = psiFacade.findClass(entrypoint, element.resolveScope) - ?: continue - if (element.isInheritor(entrypointClass, true)) { - inheritsEntrypointInterface = true - break - } - } - return inheritsEntrypointInterface + fun isValidEntrypointClass(element: PsiClass, entrypointClass: PsiClass): Boolean { + return element.isInheritor(entrypointClass, true) } - fun isValidEntrypointField(field: PsiField): Boolean { + fun isValidEntrypointField(field: PsiField, entrypointClass: PsiClass): Boolean { if (!field.hasModifierProperty(PsiModifier.PUBLIC) || !field.hasModifierProperty(PsiModifier.STATIC)) { return false } val fieldTypeClass = (field.type as? PsiClassType)?.resolve() - return fieldTypeClass != null && isValidEntrypointClass(fieldTypeClass) + return fieldTypeClass != null && isValidEntrypointClass(fieldTypeClass, entrypointClass) } fun isValidEntrypointMethod(method: PsiMethod): Boolean { @@ -228,30 +222,36 @@ object EntryPointReference : PsiReferenceProvider() { val text = element.text.substring(range.startOffset, range.endOffset) val parts = text.split("::", limit = 2) + val psiFacade = JavaPsiFacade.getInstance(element.project) + val entrypointType = getEntrypointType()?.let(FabricConstants.ENTRYPOINT_BY_TYPE::get) + ?: return ArrayUtil.EMPTY_OBJECT_ARRAY + val entrypointClass = psiFacade.findClass(entrypointType, element.resolveScope) + ?: return ArrayUtil.EMPTY_OBJECT_ARRAY + val variants = mutableListOf() if (!isMemberReference) { - val psiFacade = JavaPsiFacade.getInstance(element.project) - for (entrypoint in FabricConstants.ENTRYPOINTS) { - val entrypointClass = psiFacade.findClass(entrypoint, element.resolveScope) - ?: continue - ClassInheritorsSearch.search(entrypointClass, true) - .mapNotNullTo(variants) { - val shortName = it.name ?: return@mapNotNullTo null - val fqName = it.fullQualifiedName ?: return@mapNotNullTo null - JavaLookupElementBuilder.forClass(it, fqName, true).withPresentableText(shortName) - } - } + val scope = element.resolveScope.intersectWith(GlobalSearchScope.projectScope(element.project)) + ClassInheritorsSearch.search(entrypointClass, scope, true) + .mapNotNullTo(variants) { + val shortName = it.name ?: return@mapNotNullTo null + val fqName = it.fullQualifiedName ?: return@mapNotNullTo null + JavaLookupElementBuilder.forClass(it, fqName, true).withPresentableText(shortName) + } } else if (parts.size >= 2) { - val psiFacade = JavaPsiFacade.getInstance(element.project) val className = parts[0].replace('$', '.') val clazz = psiFacade.findClass(className, element.resolveScope) if (clazz != null) { - clazz.fields.filterTo(variants, ::isValidEntrypointField) + clazz.fields.filterTo(variants) { isValidEntrypointField(it, entrypointClass) } clazz.methods.filterTo(variants, ::isValidEntrypointMethod) } } return variants.toTypedArray() } + + private fun getEntrypointType(): String? { + val entrypointsProperty = element.parentOfType()?.parent as? JsonProperty + return entrypointsProperty?.name + } } } diff --git a/src/main/kotlin/platform/fabric/reference/FabricReferenceContributor.kt b/src/main/kotlin/platform/fabric/reference/FabricReferenceContributor.kt index 896da285e..758e3e890 100644 --- a/src/main/kotlin/platform/fabric/reference/FabricReferenceContributor.kt +++ b/src/main/kotlin/platform/fabric/reference/FabricReferenceContributor.kt @@ -23,9 +23,11 @@ package com.demonwav.mcdev.platform.fabric.reference import com.demonwav.mcdev.platform.fabric.util.FabricConstants import com.demonwav.mcdev.util.isPropertyValue import com.intellij.json.psi.JsonArray +import com.intellij.json.psi.JsonElement import com.intellij.json.psi.JsonObject import com.intellij.json.psi.JsonStringLiteral import com.intellij.patterns.PlatformPatterns +import com.intellij.patterns.StandardPatterns import com.intellij.psi.PsiReferenceContributor import com.intellij.psi.PsiReferenceRegistrar @@ -34,19 +36,25 @@ class FabricReferenceContributor : PsiReferenceContributor() { val stringInModJson = PlatformPatterns.psiElement(JsonStringLiteral::class.java) .inVirtualFile(PlatformPatterns.virtualFile().withName(FabricConstants.FABRIC_MOD_JSON)) - val entryPointPattern = stringInModJson.withParent( - PlatformPatterns.psiElement(JsonArray::class.java) - .withSuperParent( - 2, - PlatformPatterns.psiElement(JsonObject::class.java).isPropertyValue("entrypoints"), - ), - ) + val entrypointsArray = PlatformPatterns.psiElement(JsonArray::class.java) + .withSuperParent(2, PlatformPatterns.psiElement(JsonObject::class.java).isPropertyValue("entrypoints")) + val entryPointSimplePattern = stringInModJson.withParent(entrypointsArray) + val entryPointObjectPattern = stringInModJson.isPropertyValue("value") + .withSuperParent(2, PlatformPatterns.psiElement(JsonObject::class.java).withParent(entrypointsArray)) + val entryPointPattern = StandardPatterns.or(entryPointSimplePattern, entryPointObjectPattern) registrar.registerReferenceProvider(entryPointPattern, EntryPointReference) - val mixinConfigPattern = stringInModJson.withParent( + val mixinConfigSimplePattern = stringInModJson.withParent( PlatformPatterns.psiElement(JsonArray::class.java).isPropertyValue("mixins"), ) - registrar.registerReferenceProvider(mixinConfigPattern, ResourceFileReference("mixin config '%s'")) + val mixinsConfigArray = PlatformPatterns.psiElement(JsonArray::class.java).isPropertyValue("mixins") + val mixinConfigObjectPattern = stringInModJson.isPropertyValue("config") + .withSuperParent(2, PlatformPatterns.psiElement(JsonElement::class.java).withParent(mixinsConfigArray)) + val mixinConfigPattern = StandardPatterns.or(mixinConfigSimplePattern, mixinConfigObjectPattern) + registrar.registerReferenceProvider( + mixinConfigPattern, + ResourceFileReference("mixin config '%s'", Regex("(.+)\\.mixins\\.json")) + ) registrar.registerReferenceProvider( stringInModJson.isPropertyValue("accessWidener"), diff --git a/src/main/kotlin/platform/fabric/reference/ResourceFileReference.kt b/src/main/kotlin/platform/fabric/reference/ResourceFileReference.kt index 834ae6e0c..1088b03e9 100644 --- a/src/main/kotlin/platform/fabric/reference/ResourceFileReference.kt +++ b/src/main/kotlin/platform/fabric/reference/ResourceFileReference.kt @@ -27,8 +27,12 @@ import com.demonwav.mcdev.util.manipulator import com.demonwav.mcdev.util.mapFirstNotNull import com.demonwav.mcdev.util.reference.InspectionReference import com.intellij.json.psi.JsonStringLiteral +import com.intellij.openapi.application.runReadAction import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.project.rootManager import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.vfs.findPsiFile import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager @@ -37,13 +41,17 @@ import com.intellij.psi.PsiReferenceBase import com.intellij.psi.PsiReferenceProvider import com.intellij.util.IncorrectOperationException import com.intellij.util.ProcessingContext +import org.jetbrains.jps.model.java.JavaResourceRootType -class ResourceFileReference(private val description: String) : PsiReferenceProvider() { +class ResourceFileReference( + private val description: String, + private val filenamePattern: Regex? = null +) : PsiReferenceProvider() { override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array { return arrayOf(Reference(description, element as JsonStringLiteral)) } - private class Reference(desc: String, element: JsonStringLiteral) : + private inner class Reference(desc: String, element: JsonStringLiteral) : PsiReferenceBase(element), InspectionReference { override val description = desc @@ -61,6 +69,9 @@ class ResourceFileReference(private val description: String) : PsiReferenceProvi ?: ModuleRootManager.getInstance(module) .getDependencies(false) .mapFirstNotNull(::findFileIn) + ?: ModuleManager.getInstance(element.project) + .getModuleDependentModules(module) + .mapFirstNotNull(::findFileIn) } override fun bindToElement(newTarget: PsiElement): PsiElement? { @@ -70,5 +81,30 @@ class ResourceFileReference(private val description: String) : PsiReferenceProvi val manipulator = element.manipulator ?: return null return manipulator.handleContentChange(element, manipulator.getRangeInElement(element), newTarget.name) } + + override fun getVariants(): Array { + if (filenamePattern == null) { + return emptyArray() + } + + val module = element.findModule() ?: return emptyArray() + val variants = mutableListOf() + val relevantModules = ModuleManager.getInstance(element.project).getModuleDependentModules(module) + module + runReadAction { + val relevantRoots = relevantModules.flatMap { + it.rootManager.getSourceRoots(JavaResourceRootType.RESOURCE) + } + for (roots in relevantRoots) { + for (child in roots.children) { + val relativePath = child.path.removePrefix(roots.path) + val testRelativePath = "/$relativePath" + if (testRelativePath.matches(filenamePattern)) { + variants.add(child.findPsiFile(element.project) ?: relativePath) + } + } + } + } + return variants.toTypedArray() + } } } diff --git a/src/test/kotlin/framework/ProjectBuilder.kt b/src/test/kotlin/framework/ProjectBuilder.kt index 45e4dd2ed..07fdc5edf 100644 --- a/src/test/kotlin/framework/ProjectBuilder.kt +++ b/src/test/kotlin/framework/ProjectBuilder.kt @@ -63,6 +63,12 @@ class ProjectBuilder(private val fixture: JavaCodeInsightTestFixture, private va configure: Boolean = true, allowAst: Boolean = false, ) = file(path, code, ".nbtt", configure, allowAst) + fun json( + path: String, + @Language("JSON") code: String, + configure: Boolean = true, + allowAst: Boolean = false, + ) = file(path, code, ".json", configure, allowAst) inline fun dir(path: String, block: ProjectBuilder.() -> Unit) { val oldIntermediatePath = intermediatePath diff --git a/src/test/kotlin/platform/fabric/FabricEntrypointsInspectionTest.kt b/src/test/kotlin/platform/fabric/FabricEntrypointsInspectionTest.kt new file mode 100644 index 000000000..619fc4ad8 --- /dev/null +++ b/src/test/kotlin/platform/fabric/FabricEntrypointsInspectionTest.kt @@ -0,0 +1,264 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 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 + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.fabric + +import com.demonwav.mcdev.framework.BaseMinecraftTest +import com.demonwav.mcdev.framework.EdtInterceptor +import com.demonwav.mcdev.framework.ProjectBuilder +import com.demonwav.mcdev.framework.createLibrary +import com.demonwav.mcdev.platform.PlatformType +import com.demonwav.mcdev.platform.fabric.inspection.FabricEntrypointsInspection +import com.demonwav.mcdev.util.runWriteTask +import com.intellij.openapi.roots.ModuleRootModificationUtil +import com.intellij.openapi.roots.libraries.Library +import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar +import org.intellij.lang.annotations.Language +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(EdtInterceptor::class) +@DisplayName("Fabric Entrypoints Inspection Tests") +class FabricEntrypointsInspectionTest : BaseMinecraftTest(PlatformType.FABRIC) { + + private var library: Library? = null + + @BeforeEach + fun initFabric() { + runWriteTask { + library = createLibrary(project, "fabric-loader") + } + + ModuleRootModificationUtil.updateModel(module) { model -> + model.addLibraryEntry(library ?: throw IllegalStateException("Library not created")) + } + } + + @AfterEach + fun cleanupFabric() { + library?.let { l -> + ModuleRootModificationUtil.updateModel(module) { model -> + model.removeOrderEntry( + model.findLibraryOrderEntry(l) ?: throw IllegalStateException("Library not found"), + ) + } + + runWriteTask { + val table = LibraryTablesRegistrar.getInstance().getLibraryTable(project) + table.modifiableModel.let { model -> + model.removeLibrary(l) + model.commit() + } + } + } + } + + private fun doTest(@Language("JSON") json: String, builder: (ProjectBuilder.() -> Unit) = {}) { + buildProject { + java( + "GoodSimpleModInitializer.java", + """ + import net.fabricmc.api.ModInitializer; + + public class GoodSimpleModInitializer implements ModInitializer { + @Override + public void onInitialize() { + } + + public void handle() {} + } + """.trimIndent() + ) + java( + "GoodSimpleClientModInitializer.java", + """ + import net.fabricmc.api.ClientModInitializer; + + public class GoodSimpleClientModInitializer implements ClientModInitializer { + @Override + public void onInitializeClient() { + } + } + """.trimIndent() + ) + java( + "BadSimpleModInitializer.java", + """ + public class BadSimpleModInitializer { + public void handle(String param) {} + } + """.trimIndent() + ) + java( + "BadSimpleClientModInitializer.java", + """ + public class BadSimpleClientModInitializer {} + """.trimIndent() + ) + + builder() + + json("fabric.mod.json", json) + } + + fixture.enableInspections(FabricEntrypointsInspection::class) + fixture.checkHighlighting(false, false, false) + } + + @Test + fun validInitializers() { + doTest( + """ + { + "entrypoints": { + "main": [ + { + "value": "GoodSimpleModInitializer" + }, + "GoodSimpleModInitializer::handle" + ], + "client": [ + "GoodSimpleClientModInitializer" + ] + } + } + """.trimIndent() + ) + } + + @Test + fun invalidInitializers() { + doTest( + """ + { + "entrypoints": { + "main": [ + "GoodSimpleClientModInitializer", + { + "value": "BadSimpleModInitializer" + } + ], + "client": [ + "BadSimpleClientModInitializer", + "GoodSimpleModInitializer" + ] + } + } + """.trimIndent() + ) + } + + @Test + fun missingEmptyConstructor() { + doTest( + """ + { + "entrypoints": { + "main": [ + "BadCtorSimpleModInitializer" + ] + } + } + """.trimIndent() + ) { + java( + "BadCtorSimpleModInitializer.java", + """ + import net.fabricmc.api.ModInitializer; + + public class BadCtorSimpleModInitializer implements ModInitializer { + public BadCtorSimpleModInitializer(String param) {} + } + """.trimIndent() + ) + } + } + + @Test + fun entrypointMethodWithParameter() { + doTest( + """ + { + "entrypoints": { + "main": [ + "BadSimpleModInitializer::handle" + ] + } + } + """.trimIndent() + ) + } + + @Test + fun entrypointInstanceMethodInClassWithNoEmptyCtor() { + doTest( + """ + { + "entrypoints": { + "main": [ + "BadTestInitializer::goodInitialize", + "BadTestInitializer::badInitialize" + ] + } + } + """.trimIndent() + ) { + java( + "BadTestInitializer.java", + """ + public class BadTestInitializer { + public BadTestInitializer(String param) {} + public static void goodInitialize() {} + public void badInitialize() {} + } + """.trimIndent() + ) + } + } + + @Test + fun entrypointFieldInitializers() { + doTest( + """ + { + "entrypoints": { + "main": [ + "ModInitializerContainer::initializer", + "ModInitializerContainer::badTypeInitializer" + ] + } + } + """.trimIndent() + ) { + java( + "ModInitializerContainer.java", + """ + public class ModInitializerContainer { + public static GoodSimpleModInitializer initializer = new GoodSimpleModInitializer(); + public static String badTypeInitializer = "No..."; + } + """.trimIndent() + ) + } + } +} From 3ecde2dd5961afd995ca0c2f2dec3339037d5e22 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Tue, 2 Jul 2024 16:34:19 +0100 Subject: [PATCH 20/22] MixinExtras Expressions: Migrate to Expressions library. --- build.gradle.kts | 10 +++-- .../expression/MEExpressionCompletionUtil.kt | 10 ++--- .../mixin/expression/MEExpressionMatchUtil.kt | 7 ++- .../mixin/expression/MEExpressionService.kt | 44 +++++++++++++++++++ .../mixin/expression/MEFlowContext.kt | 26 +++++++++++ .../MixinExtrasInjectorAnnotationHandler.kt | 12 ++--- .../ModifyExpressionValueHandler.kt | 12 ++--- .../mixinextras/WrapOperationHandler.kt | 22 ++++++---- .../kotlin/platform/mixin/util/AsmUtil.kt | 4 +- .../kotlin/platform/mixin/BaseMixinTest.kt | 2 +- 10 files changed, 117 insertions(+), 32 deletions(-) create mode 100644 src/main/kotlin/platform/mixin/expression/MEExpressionService.kt create mode 100644 src/main/kotlin/platform/mixin/expression/MEFlowContext.kt diff --git a/build.gradle.kts b/build.gradle.kts index d4ac4965b..99e7431fb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -89,7 +89,7 @@ repositories { mavenCentral() // TODO: temporary waiting for MixinExtras expression library - maven("https://repo.spongepowered.org/") + maven("https://repo.spongepowered.org/maven/") maven("https://jitpack.io/") { content { includeGroupByRegex("com\\.github\\..+") @@ -101,9 +101,11 @@ dependencies { // Add tools.jar for the JDI API implementation(files(Jvm.current().toolsJar)) - // TODO: temporary waiting for MixinExtras expression library - testLibs(implementation("com.github.LlamaLad7.MixinExtras:mixinextras-common:86c9835")!!) - implementation("org.spongepowered:mixin:0.8.4") + // TODO: temporary waiting for a release + fun mixinExtras(variant: String) = "com.github.LlamaLad7.MixinExtras:mixinextras-$variant:4d2e01e" + + implementation(mixinExtras("expressions")) + testLibs(mixinExtras("common")) implementation("org.ow2.asm:asm-util:9.3") // Kotlin diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt index 115f24b70..dcda90ba2 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionCompletionUtil.kt @@ -118,7 +118,7 @@ import com.llamalad7.mixinextras.expression.impl.flow.expansion.InsnExpander import com.llamalad7.mixinextras.expression.impl.flow.postprocessing.InstantiationInfo import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool -import com.llamalad7.mixinextras.utils.Decorations +import com.llamalad7.mixinextras.expression.impl.utils.FlowDecorations import org.apache.commons.lang3.mutable.MutableInt import org.objectweb.asm.Handle import org.objectweb.asm.Opcodes @@ -770,7 +770,7 @@ object MEExpressionCompletionUtil { lookup.withTail( BracketsTailType( 1, - flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, + flows[insn]?.hasDecoration(FlowDecorations.ARRAY_CREATION_INFO) == true, ) ) .createEliminable("new [${insn.insn.desc}") @@ -778,7 +778,7 @@ object MEExpressionCompletionUtil { } Opcodes.NEW -> { val initCall = flows[insn] - ?.getDecoration(Decorations.INSTANTIATION_INFO) + ?.getDecoration(FlowDecorations.INSTANTIATION_INFO) ?.initCall ?.virtualInsnOrNull ?.insn as MethodInsnNode? @@ -811,7 +811,7 @@ object MEExpressionCompletionUtil { .withTail( BracketsTailType( 1, - flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true, + flows[insn]?.hasDecoration(FlowDecorations.ARRAY_CREATION_INFO) == true, ) ) .createEliminable("new $type[]") @@ -827,7 +827,7 @@ object MEExpressionCompletionUtil { .withTail( BracketsTailType( type.dimensions, - flows[insn]?.hasDecoration(Decorations.ARRAY_CREATION_INFO) == true + flows[insn]?.hasDecoration(FlowDecorations.ARRAY_CREATION_INFO) == true ) ) .createEliminable("new ${insn.insn.desc}") diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index f1663f4ea..1461facc4 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -40,6 +40,7 @@ import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project import com.intellij.psi.PsiModifierList import com.llamalad7.mixinextras.expression.impl.ExpressionParserFacade +import com.llamalad7.mixinextras.expression.impl.ExpressionService import com.llamalad7.mixinextras.expression.impl.ast.expressions.Expression import com.llamalad7.mixinextras.expression.impl.flow.ComplexDataException import com.llamalad7.mixinextras.expression.impl.flow.FlowInterpreter @@ -73,13 +74,17 @@ value class VirtualInsn(val insn: AbstractInsnNode) object MEExpressionMatchUtil { private val LOGGER = logger() + init { + ExpressionService.offerInstance(MEExpressionService) + } + fun getFlowMap(project: Project, classIn: ClassNode, methodIn: MethodNode): FlowMap? { if (methodIn.instructions == null) { return null } return methodIn.cached(classIn, project) { classNode, methodNode -> - val interpreter = object : FlowInterpreter(classNode, methodNode) { + val interpreter = object : FlowInterpreter(classNode, methodNode, MEFlowContext(project)) { override fun newValue(type: Type?): FlowValue? { ProgressManager.checkCanceled() return super.newValue(type) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionService.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionService.kt new file mode 100644 index 000000000..5a3408652 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionService.kt @@ -0,0 +1,44 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 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 + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.demonwav.mcdev.platform.mixin.util.toPsiType +import com.demonwav.mcdev.util.descriptor +import com.intellij.psi.GenericsUtil +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiManager +import com.llamalad7.mixinextras.expression.impl.ExpressionService +import com.llamalad7.mixinextras.expression.impl.flow.FlowContext +import org.objectweb.asm.Type + +object MEExpressionService : ExpressionService() { + override fun getCommonSuperClass(ctx: FlowContext, type1: Type, type2: Type): Type { + ctx as MEFlowContext + val elementFactory = JavaPsiFacade.getElementFactory(ctx.project) + return Type.getType( + GenericsUtil.getLeastUpperBound( + type1.toPsiType(elementFactory), + type2.toPsiType(elementFactory), + PsiManager.getInstance(ctx.project) + )?.descriptor ?: error("Failed to merge types $type1 and $type2!") + ) + } +} diff --git a/src/main/kotlin/platform/mixin/expression/MEFlowContext.kt b/src/main/kotlin/platform/mixin/expression/MEFlowContext.kt new file mode 100644 index 000000000..e7d22f578 --- /dev/null +++ b/src/main/kotlin/platform/mixin/expression/MEFlowContext.kt @@ -0,0 +1,26 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 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 + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.platform.mixin.expression + +import com.intellij.openapi.project.Project +import com.llamalad7.mixinextras.expression.impl.flow.FlowContext + +class MEFlowContext(val project: Project) : FlowContext diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt index 5604134f6..2e76bcb9b 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt @@ -38,7 +38,7 @@ import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiElement import com.intellij.psi.PsiType import com.intellij.psi.PsiTypes -import com.llamalad7.mixinextras.utils.Decorations +import com.llamalad7.mixinextras.expression.impl.utils.ExpressionDecorations import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode @@ -78,14 +78,16 @@ abstract class MixinExtrasInjectorAnnotationHandler : InjectorAnnotationHandler( }, SIMPLE_OPERATION { override fun matches(target: TargetInsn) = - target.hasDecoration(Decorations.SIMPLE_OPERATION_ARGS) && - target.hasDecoration(Decorations.SIMPLE_OPERATION_RETURN_TYPE) + target.hasDecoration(ExpressionDecorations.SIMPLE_OPERATION_ARGS) && + target.hasDecoration(ExpressionDecorations.SIMPLE_OPERATION_RETURN_TYPE) }, SIMPLE_EXPRESSION { - override fun matches(target: TargetInsn) = target.hasDecoration(Decorations.SIMPLE_EXPRESSION_TYPE) + override fun matches(target: TargetInsn) = + target.hasDecoration(ExpressionDecorations.SIMPLE_EXPRESSION_TYPE) }, STRING_CONCAT_EXPRESSION { - override fun matches(target: TargetInsn) = target.hasDecoration(Decorations.IS_STRING_CONCAT_EXPRESSION) + override fun matches(target: TargetInsn) = + target.hasDecoration(ExpressionDecorations.IS_STRING_CONCAT_EXPRESSION) }; abstract fun matches(target: TargetInsn): Boolean diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt index 4b1bbc983..f16bf4924 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt @@ -28,8 +28,8 @@ import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiType import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext -import com.llamalad7.mixinextras.utils.Decorations -import com.llamalad7.mixinextras.utils.TypeUtils +import com.llamalad7.mixinextras.expression.impl.utils.ExpressionASMUtils +import com.llamalad7.mixinextras.expression.impl.utils.ExpressionDecorations import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode @@ -57,8 +57,8 @@ class ModifyExpressionValueHandler : MixinExtrasInjectorAnnotationHandler() { } override fun intLikeTypePositions(target: TargetInsn): List { - val expressionType = target.getDecoration(Decorations.SIMPLE_EXPRESSION_TYPE) - if (expressionType == TypeUtils.INTLIKE_TYPE) { + val expressionType = target.getDecoration(ExpressionDecorations.SIMPLE_EXPRESSION_TYPE) + if (expressionType == ExpressionASMUtils.INTLIKE_TYPE) { return listOf(MethodSignature.TypePosition.Return, MethodSignature.TypePosition.Param(0)) } return emptyList() @@ -68,12 +68,12 @@ class ModifyExpressionValueHandler : MixinExtrasInjectorAnnotationHandler() { target: TargetInsn, annotation: PsiAnnotation ): PsiType? { - if (target.hasDecoration(Decorations.IS_STRING_CONCAT_EXPRESSION)) { + if (target.hasDecoration(ExpressionDecorations.IS_STRING_CONCAT_EXPRESSION)) { return PsiType.getJavaLangString(annotation.manager, annotation.resolveScope) } val psiReturnType = getPsiReturnType(target.insn, annotation) val rawReturnType = getInsnReturnType(target.insn) - val exprType = target.getDecoration(Decorations.SIMPLE_EXPRESSION_TYPE) + val exprType = target.getDecoration(ExpressionDecorations.SIMPLE_EXPRESSION_TYPE) if (exprType != null && rawReturnType != exprType) { // The expression knows more than the standard logic does. return exprType.toPsiType(JavaPsiFacade.getElementFactory(annotation.project)) diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt index 2acb6be78..ef1726cc8 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt @@ -29,8 +29,8 @@ import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiType import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext -import com.llamalad7.mixinextras.utils.Decorations -import com.llamalad7.mixinextras.utils.TypeUtils +import com.llamalad7.mixinextras.expression.impl.utils.ExpressionASMUtils +import com.llamalad7.mixinextras.expression.impl.utils.ExpressionDecorations import org.objectweb.asm.Type import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode @@ -60,11 +60,14 @@ class WrapOperationHandler : MixinExtrasInjectorAnnotationHandler() { } override fun intLikeTypePositions(target: TargetInsn) = buildList { - if (target.getDecoration(Decorations.SIMPLE_OPERATION_RETURN_TYPE) == TypeUtils.INTLIKE_TYPE) { + if ( + target.getDecoration(ExpressionDecorations.SIMPLE_OPERATION_RETURN_TYPE) + == ExpressionASMUtils.INTLIKE_TYPE + ) { add(MethodSignature.TypePosition.Return) } - target.getDecoration>(Decorations.SIMPLE_OPERATION_ARGS)?.forEachIndexed { i, it -> - if (it == TypeUtils.INTLIKE_TYPE) { + target.getDecoration>(ExpressionDecorations.SIMPLE_OPERATION_ARGS)?.forEachIndexed { i, it -> + if (it == ExpressionASMUtils.INTLIKE_TYPE) { add(MethodSignature.TypePosition.Param(i)) } } @@ -76,8 +79,11 @@ class WrapOperationHandler : MixinExtrasInjectorAnnotationHandler() { annotation: PsiAnnotation ): List? { getPsiParameters(target.insn, targetClass, annotation)?.let { return it } - val args = target.getDecoration>(Decorations.SIMPLE_OPERATION_ARGS) ?: return null - return args.toList().toParameters(annotation, target.getDecoration(Decorations.SIMPLE_OPERATION_PARAM_NAMES)) + val args = target.getDecoration>(ExpressionDecorations.SIMPLE_OPERATION_ARGS) ?: return null + return args.toList().toParameters( + annotation, + target.getDecoration(ExpressionDecorations.SIMPLE_OPERATION_PARAM_NAMES) + ) } private fun getReturnType( @@ -85,7 +91,7 @@ class WrapOperationHandler : MixinExtrasInjectorAnnotationHandler() { annotation: PsiAnnotation ): PsiType? { getPsiReturnType(target.insn, annotation)?.let { return it } - val type = target.getDecoration(Decorations.SIMPLE_OPERATION_RETURN_TYPE) ?: return null + val type = target.getDecoration(ExpressionDecorations.SIMPLE_OPERATION_RETURN_TYPE) ?: return null return type.toPsiType(JavaPsiFacade.getElementFactory(annotation.project)) } diff --git a/src/main/kotlin/platform/mixin/util/AsmUtil.kt b/src/main/kotlin/platform/mixin/util/AsmUtil.kt index 161ad6a56..981be7320 100644 --- a/src/main/kotlin/platform/mixin/util/AsmUtil.kt +++ b/src/main/kotlin/platform/mixin/util/AsmUtil.kt @@ -76,7 +76,7 @@ import com.intellij.psi.util.CachedValue import com.intellij.psi.util.PsiUtil import com.intellij.refactoring.util.LambdaRefactoringUtil import com.intellij.util.CommonJavaRefactoringUtil -import com.llamalad7.mixinextras.utils.TypeUtils +import com.llamalad7.mixinextras.expression.impl.utils.ExpressionASMUtils import java.io.PrintWriter import java.io.StringWriter import java.lang.reflect.InvocationTargetException @@ -143,7 +143,7 @@ private fun hasModifier(access: Int, @PsiModifier.ModifierConstant modifier: Str } fun Type.toPsiType(elementFactory: PsiElementFactory, context: PsiElement? = null): PsiType { - if (this == TypeUtils.INTLIKE_TYPE) { + if (this == ExpressionASMUtils.INTLIKE_TYPE) { return PsiTypes.intType() } val javaClassName = className.replace("(\\$)(\\D)".toRegex()) { "." + it.groupValues[2] } diff --git a/src/test/kotlin/platform/mixin/BaseMixinTest.kt b/src/test/kotlin/platform/mixin/BaseMixinTest.kt index a2e406849..caac9bc5d 100644 --- a/src/test/kotlin/platform/mixin/BaseMixinTest.kt +++ b/src/test/kotlin/platform/mixin/BaseMixinTest.kt @@ -41,7 +41,7 @@ abstract class BaseMixinTest : BaseMinecraftTest(PlatformType.MIXIN) { fun initMixin() { runWriteTask { mixinLibrary = createLibrary(project, "mixin") - mixinExtrasLibrary = createLibrary(project, "mixinextras-common") // TODO: this will probably change + mixinExtrasLibrary = createLibrary(project, "mixinextras-common") testDataLibrary = createLibrary(project, "mixin-test-data") } From d8a9f837ed1e4f8ee74fad3fec0eccd9ba49b1df Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Tue, 2 Jul 2024 19:11:42 +0100 Subject: [PATCH 21/22] Fix: Resolve being unable to get the descriptor for some complex types. --- src/main/kotlin/util/bytecode-utils.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/util/bytecode-utils.kt b/src/main/kotlin/util/bytecode-utils.kt index 5eab8bbcf..777210c54 100644 --- a/src/main/kotlin/util/bytecode-utils.kt +++ b/src/main/kotlin/util/bytecode-utils.kt @@ -69,7 +69,9 @@ fun getPrimitiveType(internalName: Char): PsiPrimitiveType? { } val PsiType.descriptor - get() = appendDescriptor(StringBuilder()).toString() + get() = erasure().appendDescriptor(StringBuilder()).toString() + +private fun PsiType.erasure() = TypeConversionUtil.erasure(this)!! fun getPrimitiveWrapperClass(internalName: Char, project: Project): PsiClass? { val type = getPrimitiveType(internalName) ?: return null From c88fd70b656a73cfd11459579370cb0107de9d35 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Tue, 2 Jul 2024 19:46:04 +0100 Subject: [PATCH 22/22] MixinExtras Expressions: Recreate `ClassInfo#getCommonSuperClassOrInterface`. It's quite scuffed and often returns `Object` even when there is a suitable common interface, but what's important is that this matches the runtime logic. --- .../mixin/expression/MEExpressionService.kt | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionService.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionService.kt index 5a3408652..473717217 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionService.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionService.kt @@ -22,9 +22,13 @@ package com.demonwav.mcdev.platform.mixin.expression import com.demonwav.mcdev.platform.mixin.util.toPsiType import com.demonwav.mcdev.util.descriptor -import com.intellij.psi.GenericsUtil +import com.intellij.openapi.project.Project import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiClassType +import com.intellij.psi.PsiElementFactory import com.intellij.psi.PsiManager +import com.intellij.psi.PsiType import com.llamalad7.mixinextras.expression.impl.ExpressionService import com.llamalad7.mixinextras.expression.impl.flow.FlowContext import org.objectweb.asm.Type @@ -34,11 +38,39 @@ object MEExpressionService : ExpressionService() { ctx as MEFlowContext val elementFactory = JavaPsiFacade.getElementFactory(ctx.project) return Type.getType( - GenericsUtil.getLeastUpperBound( - type1.toPsiType(elementFactory), - type2.toPsiType(elementFactory), - PsiManager.getInstance(ctx.project) - )?.descriptor ?: error("Failed to merge types $type1 and $type2!") + getCommonSuperClass( + ctx.project, + type1.toPsiType(elementFactory) as PsiClassType, + type2.toPsiType(elementFactory) as PsiClassType + )?.descriptor ?: error("Could not intersect types $type1 and $type2!") ) } + + // Copied from ClassInfo + private fun getCommonSuperClass( + project: Project, + type1: PsiType, + type2: PsiType + ): PsiClassType? { + val left = (type1 as? PsiClassType)?.resolve() ?: return null + val right = (type2 as? PsiClassType)?.resolve() ?: return null + + fun objectType() = PsiType.getJavaLangObject(PsiManager.getInstance(project), left.resolveScope) + fun PsiClass.type() = PsiElementFactory.getInstance(project).createType(this) + + if (left.isInheritor(right, true)) { + return right.type() + } + if (right.isInheritor(left, true)) { + return left.type() + } + if (left.isInterface || right.isInterface) { + return objectType() + } + + return generateSequence(left) { it.superClass } + .firstOrNull { right.isInheritor(it, true) } + ?.type() + ?: objectType() + } }