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();