diff --git a/loader/src/main/java/net/neoforged/fml/common/Mod.java b/loader/src/main/java/net/neoforged/fml/common/Mod.java
index 7fabff9bb..cbe936d31 100644
--- a/loader/src/main/java/net/neoforged/fml/common/Mod.java
+++ b/loader/src/main/java/net/neoforged/fml/common/Mod.java
@@ -16,6 +16,7 @@
*
* Any class found with this annotation applied will be loaded as a mod entrypoint for the mod with the given {@linkplain #value() ID}.
* A mod loaded with the {@code javafml} language loader may have multiple entrypoints.
+ * Entrypoints with the least {@link #depends} are run first.
* Entrypoints for all {@link #dist}s are always run before entrypoints for a single {@link #dist}.
*/
@Retention(RetentionPolicy.RUNTIME)
@@ -34,4 +35,9 @@
* {@return the side to load this mod entrypoint on}
*/
Dist[] dist() default { Dist.CLIENT, Dist.DEDICATED_SERVER };
+
+ /**
+ * A list of mod IDs which are all required to be present in order to load this mod entrypoint.
+ */
+ String[] depends() default {};
}
diff --git a/loader/src/main/java/net/neoforged/fml/javafmlmod/FMLJavaModLanguageProvider.java b/loader/src/main/java/net/neoforged/fml/javafmlmod/FMLJavaModLanguageProvider.java
index 6db6c62c5..129f0d242 100644
--- a/loader/src/main/java/net/neoforged/fml/javafmlmod/FMLJavaModLanguageProvider.java
+++ b/loader/src/main/java/net/neoforged/fml/javafmlmod/FMLJavaModLanguageProvider.java
@@ -7,8 +7,10 @@
import java.lang.annotation.ElementType;
import java.util.Collection;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.ModLoadingIssue;
@@ -33,7 +35,9 @@ public ModContainer loadMod(IModInfo info, ModFileScanData modFileScanResults, M
var modClasses = modFileScanResults.getAnnotatedBy(Mod.class, ElementType.TYPE)
.filter(data -> data.annotationData().get("value").equals(info.getModId()))
.filter(ad -> AutomaticEventSubscriber.getSides(ad.annotationData().get("dist")).contains(FMLLoader.getCurrent().getDist()))
- .sorted(Comparator.comparingInt(ad -> -AutomaticEventSubscriber.getSides(ad.annotationData().get("dist")).size()))
+ .filter(ad -> getDepends(ad).stream().allMatch(otherMod -> FMLLoader.getCurrent().getLoadingModList().getModFileById(otherMod) != null))
+ .sorted(Comparator.comparingInt(ad -> getDepends(ad).size())
+ .thenComparingInt(ad -> -AutomaticEventSubscriber.getSides(ad.annotationData().get("dist")).size()))
.map(ad -> ad.clazz().getClassName())
.toList();
return new FMLModContainer(info, modClasses, modFileScanResults, layer);
@@ -57,4 +61,10 @@ public void validate(IModFile file, Collection loadedContainers, I
reporter.addIssue(issue);
});
}
+
+ @SuppressWarnings("unchecked")
+ private static List getDepends(ModFileScanData.AnnotationData data) {
+ var depends = data.annotationData().get("depends");
+ return depends != null ? (List) depends : Collections.emptyList();
+ }
}
diff --git a/loader/src/test/java/net/neoforged/fml/javafmlmod/FMLJavaModLanguageProviderTest.java b/loader/src/test/java/net/neoforged/fml/javafmlmod/FMLJavaModLanguageProviderTest.java
index 701b5dfa4..19d0a1fb7 100644
--- a/loader/src/test/java/net/neoforged/fml/javafmlmod/FMLJavaModLanguageProviderTest.java
+++ b/loader/src/test/java/net/neoforged/fml/javafmlmod/FMLJavaModLanguageProviderTest.java
@@ -127,6 +127,61 @@ public ClientEntryPoint() {
assertThat(MESSAGES).isEqualTo(List.of("common", "client"));
}
+ @Test
+ void testDependsEntrypointDoesntFire() throws Exception {
+ installation.setupProductionClient();
+
+ installation.buildModJar("test.jar")
+ .withTestmodModsToml()
+ .addClass("testmod.DependsEntryPoint", """
+ @net.neoforged.fml.common.Mod(value = "testmod", depends = "othermod")
+ public class DependsEntryPoint {
+ public DependsEntryPoint() {
+ net.neoforged.fml.javafmlmod.FMLJavaModLanguageProviderTest.MESSAGES.add("fired");
+ }
+ }
+ """)
+ .build();
+
+ launchAndLoad("neoforgeclient");
+
+ assertThat(MESSAGES).isEmpty();
+ }
+
+ @Test
+ void testDependsEntrypointOrdering() throws Exception {
+ installation.setupProductionClient();
+
+ installation.buildModJar("othermod.jar").withMod("othermod", "1.0").build();
+ installation.buildModJar("test.jar")
+ .withTestmodModsToml(builder -> {
+ builder.addDependency("testmod", "othermod", "[1,)", config -> {
+ config.set("type", "optional");
+ });
+ })
+ .addClass("testmod.EntryPoint", """
+ @net.neoforged.fml.common.Mod("testmod")
+ public class EntryPoint {
+ public EntryPoint() {
+ net.neoforged.fml.javafmlmod.FMLJavaModLanguageProviderTest.MESSAGES.add("common");
+ }
+ }
+ """)
+ .addClass("testmod.DependsEntryPoint", """
+ @net.neoforged.fml.common.Mod(value = "testmod", depends = "othermod")
+ public class DependsEntryPoint {
+ public DependsEntryPoint() {
+ net.neoforged.fml.javafmlmod.FMLJavaModLanguageProviderTest.MESSAGES.add("dependency");
+ }
+ }
+ """)
+ .build();
+
+ launchAndLoad("neoforgeclient");
+
+ assertThat(MESSAGES).isEqualTo(List.of("common", "dependency"));
+ }
+
@Test
void testErrorDuringEventDispatch() throws Exception {
installation.setupProductionClient();