Skip to content

Commit

Permalink
Conditional mods & event bus subscribers
Browse files Browse the repository at this point in the history
  • Loading branch information
wired-tomato committed Nov 12, 2024
1 parent ee3c2b9 commit 06b3ef0
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 4 deletions.
16 changes: 16 additions & 0 deletions loader/src/main/java/net/neoforged/fml/common/ChainDependency.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.fml.common;

public @interface ChainDependency {
Dependency value();

Operator operator() default Operator.AND;

enum Operator {
AND, OR
}
}
23 changes: 23 additions & 0 deletions loader/src/main/java/net/neoforged/fml/common/Dependency.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.fml.common;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Dependency {
/**
* @return the array of mod ids to be checked against the {@link #condition() condition}
*/
String[] value();

Condition condition() default Condition.ALL_PRESENT;

enum Condition {
ALL_PRESENT, AT_LEAST_ONE_PRESENT, NONE_PRESENT, AT_LEAST_ONE_IS_NOT_PRESENT
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@
*/
Bus bus() default Bus.GAME;

/**
* @return the array of dependencies required for contained events to be auto-subscribed (all dependencies must match)
*/
ChainDependency[] dependencies() default {};

enum Bus {
/**
* The main NeoForge Event Bus, used after the game has started up.
Expand Down
5 changes: 5 additions & 0 deletions loader/src/main/java/net/neoforged/fml/common/Mod.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,9 @@
* {@return the side to load this mod entrypoint on}
*/
Dist[] dist() default { Dist.CLIENT, Dist.DEDICATED_SERVER };

/**
* {@return the array of dependencies required for this class to be loaded (all dependencies must match for this class to be loaded)}
*/
ChainDependency[] dependencies() default {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import static net.neoforged.fml.Logging.LOADING;

import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
Expand All @@ -16,6 +17,7 @@
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.Bindings;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.ChainDependency;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.loading.FMLEnvironment;
Expand Down Expand Up @@ -47,7 +49,9 @@ public static void inject(final ModContainer mod, final ModFileScanData scanData
final String modId = (String) ad.annotationData().getOrDefault("modid", modids.getOrDefault(ad.clazz().getClassName(), mod.getModId()));
final ModAnnotation.EnumHolder busTargetHolder = (ModAnnotation.EnumHolder) ad.annotationData().getOrDefault("bus", new ModAnnotation.EnumHolder(null, EventBusSubscriber.Bus.GAME.name()));
final EventBusSubscriber.Bus busTarget = EventBusSubscriber.Bus.valueOf(busTargetHolder.value());
if (Objects.equals(mod.getModId(), modId) && sides.contains(FMLEnvironment.dist)) {
final List<ChainDependency> chain = Arrays.stream(((ChainDependency[]) ad.annotationData().getOrDefault("dependencies", new ChainDependency[] {}))).toList();
var allDepsMatch = DependencyUtil.evaluateChain(chain, modids.values());
if (Objects.equals(mod.getModId(), modId) && sides.contains(FMLEnvironment.dist) && allDepsMatch) {
try {
IEventBus bus = switch (busTarget) {
case GAME -> Bindings.getGameBus();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.fml.javafmlmod;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import net.neoforged.fml.common.ChainDependency;
import net.neoforged.fml.common.Dependency;

public final class DependencyUtil {
public static Boolean evaluateChain(List<ChainDependency> chain, Collection<String> loadedMods) {
var matches = true;
ChainDependency.Operator previousOperator = null;
for (ChainDependency chainDep : chain) {
if (previousOperator == null) {
matches = evaluateDependency(chainDep.value(), loadedMods);
previousOperator = chainDep.operator();
continue;
}

switch (previousOperator) {
case AND -> matches = matches && evaluateDependency(chainDep.value(), loadedMods);
case OR -> matches = matches || evaluateDependency(chainDep.value(), loadedMods);
}

previousOperator = chainDep.operator();
}

return matches;
}

public static Boolean evaluateDependency(Dependency dep, Collection<String> loadedMods) {
return switch (dep.condition()) {
case ALL_PRESENT -> Arrays.stream(dep.value()).allMatch(loadedMods::contains);
case AT_LEAST_ONE_PRESENT -> Arrays.stream(dep.value()).anyMatch(loadedMods::contains);
case NONE_PRESENT -> Arrays.stream(dep.value()).noneMatch(loadedMods::contains);
case AT_LEAST_ONE_IS_NOT_PRESENT -> Arrays.stream(dep.value()).anyMatch((modId) -> !loadedMods.contains(modId));
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

package net.neoforged.fml.javafmlmod;

import java.lang.annotation.ElementType;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
Expand All @@ -22,6 +24,7 @@
import net.neoforged.fml.ModLoadingContext;
import net.neoforged.fml.ModLoadingException;
import net.neoforged.fml.ModLoadingIssue;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.event.IModBusEvent;
import net.neoforged.fml.loading.FMLLoader;
import net.neoforged.neoforgespi.language.IModInfo;
Expand Down Expand Up @@ -112,9 +115,16 @@ protected void constructMod() {
}

// All arguments are found
constructor.newInstance(constructorArgs);

LOGGER.trace(LOADING, "Loaded mod instance {} of type {}", getModId(), modClass.getName());
var chain = Arrays.stream(modClass.getAnnotation(Mod.class).dependencies()).toList();
var loadedMods = scanResults.getAnnotatedBy(Mod.class, ElementType.TYPE)
.map(data -> (String) data.annotationData().get("value"))
.toList();

var dependenciesMatch = DependencyUtil.evaluateChain(chain, loadedMods);
if (dependenciesMatch) {
constructor.newInstance(constructorArgs);
LOGGER.trace(LOADING, "Loaded mod instance {} of type {}", getModId(), modClass.getName());
} else LOGGER.trace(LOADING, "Didn't load mod instance {} of type {} because of non-matching dependencies", modClass, getModId());
} catch (Throwable e) {
if (e instanceof InvocationTargetException) e = e.getCause(); // exceptions thrown when a reflected method call throws are wrapped in an InvocationTargetException. However, this isn't useful for the end user who has to dig through the logs to find the actual cause.
LOGGER.error(LOADING, "Failed to create mod instance. ModID: {}, class {}", getModId(), modClass.getName(), e);
Expand Down

0 comments on commit 06b3ef0

Please sign in to comment.