diff --git a/src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java b/src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java
index 4da9e5e..9bf0637 100644
--- a/src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java
+++ b/src/main/java/net/neoforged/moddevgradle/dsl/RunModel.java
@@ -156,8 +156,21 @@ public void client() {
getType().set("client");
}
+ /**
+ * Equivalent to setting {@code type = "clientData"}.
+ *
+ *
Should only be used for Minecraft versions starting from 1.21.4.
+ * (The first snapshot that supports this is 24w45a).
+ */
+ public void clientData() {
+ getType().set("clientData");
+ }
+
/**
* Equivalent to setting {@code type = "data"}.
+ *
+ *
Should only be used for Minecraft versions up to 1.21.3 included.
+ * (The last snapshot that supports this is 24w44a).
*/
public void data() {
getType().set("data");
@@ -170,6 +183,16 @@ public void server() {
getType().set("server");
}
+ /**
+ * Equivalent to setting {@code type = "serverData"}.
+ *
+ *
Should only be used for Minecraft versions starting from 1.21.4.
+ * (The first snapshot that supports this is 24w45a).
+ */
+ public void serverData() {
+ getType().set("serverData");
+ }
+
/**
* Equivalent to setting {@code ideName = ""}
*/
diff --git a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java
index 726491e..1231903 100644
--- a/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java
+++ b/src/main/java/net/neoforged/moddevgradle/internal/ModDevPlugin.java
@@ -309,7 +309,8 @@ public void apply(Project project) {
userDevConfigOnly,
modulePath -> modulePath.getDependencies().addLater(modulePathDependency),
legacyClassPath -> legacyClassPath.extendsFrom(additionalClasspath),
- downloadAssets.flatMap(DownloadAssets::getAssetPropertiesFile)
+ downloadAssets.flatMap(DownloadAssets::getAssetPropertiesFile),
+ extension.getNeoFormVersion()
);
setupJarJar(project);
@@ -478,7 +479,8 @@ static void setupRuns(Project project,
Object runTemplatesSourceFile,
Consumer configureModulePath,
Consumer configureLegacyClasspath,
- Provider assetPropertiesFile
+ Provider assetPropertiesFile,
+ Provider neoFormVersion
) {
var ideIntegration = IdeIntegration.of(project, branding);
@@ -499,7 +501,8 @@ static void setupRuns(Project project,
configureModulePath,
configureLegacyClasspath,
assetPropertiesFile,
- devLaunchConfig
+ devLaunchConfig,
+ neoFormVersion
);
prepareRunTasks.put(run, prepareRunTask);
});
@@ -521,7 +524,8 @@ private static TaskProvider setupRunInGradle(
Consumer configureModulePath,
Consumer configureLegacyClasspath, // TODO: can be removed in favor of directly passing a configuration for the moddev libraries
Provider assetPropertiesFile,
- Configuration devLaunchConfig
+ Configuration devLaunchConfig,
+ Provider neoFormVersion
) {
var ideIntegration = IdeIntegration.of(project, branding);
var configurations = project.getConfigurations();
@@ -553,7 +557,7 @@ private static TaskProvider setupRunInGradle(
spec.shouldResolveConsistentlyWith(runtimeClasspathConfig.get());
spec.attributes(attributes -> {
attributes.attributeProvider(MinecraftDistribution.ATTRIBUTE, type.map(t -> {
- var name = t.equals("client") || t.equals("data") ? MinecraftDistribution.CLIENT : MinecraftDistribution.SERVER;
+ var name = t.equals("client") || t.equals("data") || t.equals("clientData") ? MinecraftDistribution.CLIENT : MinecraftDistribution.SERVER;
return project.getObjects().named(MinecraftDistribution.class, name);
}));
setNamedAttribute(project, attributes, Usage.USAGE_ATTRIBUTE, Usage.JAVA_RUNTIME);
@@ -590,6 +594,7 @@ private static TaskProvider setupRunInGradle(
task.getProgramArguments().set(run.getProgramArguments());
task.getJvmArguments().set(run.getJvmArguments());
task.getGameLogLevel().set(run.getLogLevel());
+ task.getNeoFormVersion().set(neoFormVersion);
});
ideIntegration.runTaskOnProjectSync(prepareRunTask);
diff --git a/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java b/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java
index 2ba0df8..997f95f 100644
--- a/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java
+++ b/src/main/java/net/neoforged/moddevgradle/internal/NeoDevFacade.java
@@ -42,7 +42,30 @@ public static void setupRuns(Project project,
runTemplatesSourceFile,
configureModulePath,
configureAdditionalClasspath,
- assetPropertiesFile
+ assetPropertiesFile,
+ project.getObjects().property(String.class) // empty provider
+ );
+ }
+
+ public static void setupRuns(Project project,
+ Provider argFileDir,
+ DomainObjectCollection runs,
+ Object runTemplatesSourceFile,
+ Consumer configureModulePath,
+ Consumer configureAdditionalClasspath,
+ Provider assetPropertiesFile,
+ Provider neoFormVersion
+ ) {
+ ModDevPlugin.setupRuns(
+ project,
+ Branding.NEODEV,
+ argFileDir,
+ runs,
+ runTemplatesSourceFile,
+ configureModulePath,
+ configureAdditionalClasspath,
+ assetPropertiesFile,
+ neoFormVersion
);
}
diff --git a/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java b/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java
index b865685..759144a 100644
--- a/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java
+++ b/src/main/java/net/neoforged/moddevgradle/internal/PrepareRunOrTest.java
@@ -3,6 +3,7 @@
import net.neoforged.moddevgradle.internal.utils.FileUtils;
import net.neoforged.moddevgradle.internal.utils.OperatingSystem;
import net.neoforged.moddevgradle.internal.utils.StringUtils;
+import net.neoforged.moddevgradle.internal.utils.VersionUtils;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty;
@@ -28,6 +29,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -91,6 +93,14 @@ abstract class PrepareRunOrTest extends DefaultTask {
@Input
public abstract Property getGameLogLevel();
+ /**
+ * Only used when {@link #getRunTypeTemplatesSource()} is empty,
+ * to know whether the associated Minecraft version requires one or two data runs.
+ */
+ @Optional
+ @Input
+ public abstract Property getNeoFormVersion();
+
private final ProgramArgsFormat programArgsFormat;
protected PrepareRunOrTest(ProgramArgsFormat programArgsFormat) {
@@ -147,17 +157,32 @@ private UserDevConfig getSimulatedUserDevConfigForVanilla() {
var clientArgs = List.of("--gameDir", ".", "--assetIndex", "{asset_index}", "--assetsDir", "{assets_root}", "--accessToken", "NotValid", "--version", "ModDevGradle");
var commonArgs = List.of();
- return new UserDevConfig("", "", "", List.of(), List.of(), Map.of(
- "client", new UserDevRunType(
- true, "net.minecraft.client.main.Main", clientArgs, List.of(), true, false, false, false, Map.of(), Map.of()
- ),
- "server", new UserDevRunType(
- true, "net.minecraft.server.Main", commonArgs, List.of(), false, true, false, false, Map.of(), Map.of()
- ),
- "data", new UserDevRunType(
- true, "net.minecraft.data.Main", commonArgs, List.of(), false, false, true, false, Map.of(), Map.of()
- )
+ var runTypes = new LinkedHashMap();
+ runTypes.put("client", new UserDevRunType(
+ true, "net.minecraft.client.main.Main", clientArgs, List.of(), Map.of(), Map.of()
));
+ runTypes.put("server", new UserDevRunType(
+ true, "net.minecraft.server.Main", commonArgs, List.of(), Map.of(), Map.of()
+ ));
+
+ var splitData = getNeoFormVersion()
+ .map(VersionUtils::hasSplitDataRuns)
+ .orElse(false) // Default to single run for backwards compatibility
+ .get();
+ if (splitData) {
+ runTypes.put("clientData", new UserDevRunType(
+ true, "net.minecraft.client.data.Main", commonArgs, List.of(), Map.of(), Map.of()
+ ));
+ runTypes.put("serverData", new UserDevRunType(
+ true, "net.minecraft.data.Main", commonArgs, List.of(), Map.of(), Map.of()
+ ));
+ } else {
+ runTypes.put("data", new UserDevRunType(
+ true, "net.minecraft.data.Main", commonArgs, List.of(), Map.of(), Map.of()
+ ));
+ }
+
+ return new UserDevConfig(runTypes);
}
private void writeJvmArguments(UserDevRunType runConfig) throws IOException {
diff --git a/src/main/java/net/neoforged/moddevgradle/internal/UserDevConfig.java b/src/main/java/net/neoforged/moddevgradle/internal/UserDevConfig.java
index 364cff3..4327c8d 100644
--- a/src/main/java/net/neoforged/moddevgradle/internal/UserDevConfig.java
+++ b/src/main/java/net/neoforged/moddevgradle/internal/UserDevConfig.java
@@ -6,11 +6,12 @@
import java.io.File;
import java.io.Serializable;
import java.nio.file.Files;
-import java.util.List;
import java.util.Map;
-public record UserDevConfig(String mcp, String sources, String universal, List libraries, List modules,
- Map runs) implements Serializable {
+/**
+ * Sourced from the userdev config json. The run templates are the only thing that we use.
+ */
+public record UserDevConfig(Map runs) implements Serializable {
public static UserDevConfig from(File userDevFile) {
try (var reader = Files.newBufferedReader(userDevFile.toPath())) {
return new Gson().fromJson(reader, UserDevConfig.class);
diff --git a/src/main/java/net/neoforged/moddevgradle/internal/UserDevRunType.java b/src/main/java/net/neoforged/moddevgradle/internal/UserDevRunType.java
index 27ffb86..ff8a3c9 100644
--- a/src/main/java/net/neoforged/moddevgradle/internal/UserDevRunType.java
+++ b/src/main/java/net/neoforged/moddevgradle/internal/UserDevRunType.java
@@ -4,6 +4,5 @@
import java.util.Map;
public record UserDevRunType(boolean singleInstance, String main, List args, List jvmArgs,
- boolean client, boolean server, boolean dataGenerator, boolean gameTest,
Map env, Map props) {
}
diff --git a/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionUtils.java b/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionUtils.java
new file mode 100644
index 0000000..cd10680
--- /dev/null
+++ b/src/main/java/net/neoforged/moddevgradle/internal/utils/VersionUtils.java
@@ -0,0 +1,38 @@
+package net.neoforged.moddevgradle.internal.utils;
+
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+public final class VersionUtils {
+ private VersionUtils() {}
+
+ private static final Pattern RELEASE_PATTERN = Pattern.compile("1\\.(\\d+)(?:.(\\d+))?(?:-.*)?$");
+
+ /**
+ * Checks whether the provided NeoForm version should have split client and server data runs.
+ */
+ public static boolean hasSplitDataRuns(String neoFormVersion) {
+ // Snapshots starting from 24w45a
+ if (neoFormVersion.length() >= 5 && neoFormVersion.charAt(2) == 'w') {
+ try {
+ var year = Integer.parseInt(neoFormVersion.substring(0, 2));
+ var week = Integer.parseInt(neoFormVersion.substring(3, 5));
+
+ return year > 24 || (year == 24 && week >= 45);
+ } catch (NumberFormatException ignored) {}
+ }
+ // Releases starting from 1.21.4
+ var matcher = RELEASE_PATTERN.matcher(neoFormVersion);
+ if (matcher.find()) {
+ try {
+ int minor = Integer.parseInt(matcher.group(1));
+ // If there is no patch version, the second group has a null value
+ int patch = Integer.parseInt(Objects.requireNonNullElse(matcher.group(2), "0"));
+
+ return minor > 21 || (minor == 21 && patch >= 4);
+ } catch (NumberFormatException ignored) {}
+ }
+ // Assume other version patterns are newer and therefore split
+ return true;
+ }
+}
diff --git a/src/test/java/net/neoforged/moddevgradle/internal/VersionUtilsTest.java b/src/test/java/net/neoforged/moddevgradle/internal/VersionUtilsTest.java
new file mode 100644
index 0000000..c497fe4
--- /dev/null
+++ b/src/test/java/net/neoforged/moddevgradle/internal/VersionUtilsTest.java
@@ -0,0 +1,49 @@
+package net.neoforged.moddevgradle.internal;
+
+import net.neoforged.moddevgradle.internal.utils.VersionUtils;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class VersionUtilsTest {
+ @ParameterizedTest()
+ @CsvSource({
+ "1.21.4,true",
+ "1.21.4-pre1-20241120.190508,true",
+ "1.21.3,false",
+ "24w45a,true",
+ "24w44a,false",
+ "1.21.3-pre1,false",
+ "25w01a,true",
+ "23w07a,false",
+ "1.20,false",
+ "1.20-pre1,false",
+ "1.21,false",
+ "1.21-pre1-20240529.150918,false",
+ "1.21-pre1,false",
+ "1.22,true",
+ "1.22-pre1,true"
+ })
+ public void testSplitDataRunsCorrectness(String neoFormVersion, boolean splitDataRuns) {
+ assertThat(VersionUtils.hasSplitDataRuns(neoFormVersion))
+ .isEqualTo(splitDataRuns);
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "1",
+ "1.",
+ "1.21.",
+ "test",
+ "24w",
+ "24w5",
+ "24w50",
+ "2aw50",
+ "24242",
+ })
+ public void testSplitDataRunsDoesNotCrash(String neoFormVersion) {
+ assertThat(VersionUtils.hasSplitDataRuns(neoFormVersion))
+ .isTrue();
+ }
+}