From e16f54eebe0d46948d6949dc01c5a41551c19f9f Mon Sep 17 00:00:00 2001 From: cpw Date: Thu, 9 Jun 2022 17:00:39 -0400 Subject: [PATCH] Implement a simple feature system for Forge (#8670) * Implement a simple feature system for Forge. Allows mods to demand certain features are available in the loading system. An example for java_version is provided, but not expected to be used widely. This is more targeted to properties of the display, such as GL version and glsl profile. Requires https://github.com/MinecraftForge/ForgeSPI/pull/13 to be merged first in ForgeSPI, and the SPI to be updated appropriately in build.gradle files. * rebase onto 1.19 and add in SPI update --- build.gradle | 2 +- .../net/minecraftforge/fml/ModLoader.java | 27 ++++++++++++++----- .../fml/loading/moddiscovery/ModInfo.java | 16 +++++++++-- .../resources/assets/forge/lang/en_us.json | 1 + 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index d7e60ead10..e2fda1e3fa 100644 --- a/build.gradle +++ b/build.gradle @@ -39,7 +39,7 @@ ext { MAPPING_VERSION = '1.19' MC_VERSION = '1.19' MCP_VERSION = '20220607.102129' - SPI_VERSION = '5.0.0' + SPI_VERSION = '5.0.3' ACCESSTRANSFORMERS_VERSION = '8.0.4' COREMODS_VERSION = '5.0.1' EVENTBUS_VERSION = '5.0.3' diff --git a/fmlcore/src/main/java/net/minecraftforge/fml/ModLoader.java b/fmlcore/src/main/java/net/minecraftforge/fml/ModLoader.java index 3d9715185e..21be77c0a1 100644 --- a/fmlcore/src/main/java/net/minecraftforge/fml/ModLoader.java +++ b/fmlcore/src/main/java/net/minecraftforge/fml/ModLoader.java @@ -8,23 +8,21 @@ package net.minecraftforge.fml; import com.google.common.collect.ImmutableList; import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.fml.event.IModBusEvent; +import net.minecraftforge.fml.loading.FMLEnvironment; import net.minecraftforge.fml.loading.FMLLoader; import net.minecraftforge.fml.loading.LoadingModList; import net.minecraftforge.fml.loading.moddiscovery.InvalidModIdentifier; import net.minecraftforge.fml.loading.moddiscovery.ModFile; import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo; +import net.minecraftforge.fml.loading.moddiscovery.ModInfo; import net.minecraftforge.fml.loading.progress.StartupMessageManager; import net.minecraftforge.forgespi.language.IModInfo; import net.minecraftforge.forgespi.language.IModLanguageProvider; +import net.minecraftforge.forgespi.locating.ForgeFeature; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.Executor; @@ -135,6 +133,7 @@ public class ModLoader * @param periodicTask Optional periodic task to perform on the main thread while other activities run */ public void gatherAndInitializeMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) { + ForgeFeature.registerFeature("java_version", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.SERVER, System.getProperty("java.version"))); loadingStateValid = true; statusConsumer.ifPresent(c->c.accept("Waiting for scan to complete")); FMLLoader.backgroundScanHandler.waitForScanToComplete(periodicTask); @@ -147,6 +146,22 @@ public class ModLoader loadingStateValid = false; throw new LoadingFailedException(loadingExceptions); } + statusConsumer.ifPresent(c->c.accept("Validating features")); + List failedBounds = loadingModList.getMods().stream() + .map(ModInfo::getForgeFeatures) + .flatMap(Collection::stream) + .filter(bound -> !ForgeFeature.testFeature(FMLEnvironment.dist, bound)) + .toList(); + + if (!failedBounds.isEmpty()) { + LOGGER.fatal(CORE, "Failed to validate feature bounds for mods"); + modList.setLoadedMods(Collections.emptyList()); + loadingStateValid = false; + throw new LoadingFailedException(failedBounds.stream() + .map(fb -> new ModLoadingException(fb.modInfo(), ModLoadingStage.CONSTRUCT, "fml.modloading.feature.missing", null, fb.featureName(), fb.featureBound(), ForgeFeature.featureValue(fb))) + .toList()); + } + statusConsumer.ifPresent(c->c.accept("Building Mod List")); final List modContainers = loadingModList.getModFiles().stream() .map(ModFileInfo::getFile) diff --git a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModInfo.java b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModInfo.java index 9f8e293801..c6db1c3f1e 100644 --- a/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModInfo.java +++ b/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ModInfo.java @@ -11,12 +11,11 @@ import net.minecraftforge.fml.loading.StringUtils; import net.minecraftforge.forgespi.language.IConfigurable; import net.minecraftforge.forgespi.language.IModInfo; import net.minecraftforge.forgespi.language.MavenVersionAdapter; +import net.minecraftforge.forgespi.locating.ForgeFeature; import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.apache.maven.artifact.versioning.VersionRange; import org.slf4j.Logger; -import org.slf4j.Marker; -import org.slf4j.MarkerFactory; import java.net.URL; import java.util.Collections; @@ -43,6 +42,8 @@ public class ModInfo implements IModInfo, IConfigurable private final boolean logoBlur; private final Optional updateJSONURL; private final List dependencies; + + private final List features; private final Map properties; private final IConfigurable config; private final Optional modUrl; @@ -84,6 +85,12 @@ public class ModInfo implements IModInfo, IConfigurable .collect(Collectors.toList())) .orElse(Collections.emptyList()); + this.features = ownFile.map(mfi -> mfi.>getConfigElement("features", this.modId) + .stream() + .flatMap(m->m.entrySet().stream()) + .map(e->new ForgeFeature.Bound(e.getKey(), e.getValue(), this)) + .collect(Collectors.toList())).orElse(Collections.emptyList()); + this.properties = ownFile.map(mfi -> mfi.>getConfigElement("modproperties", this.modId) .orElse(Collections.emptyMap())) .orElse(Collections.emptyMap()); @@ -156,6 +163,11 @@ public class ModInfo implements IModInfo, IConfigurable return this; } + @Override + public List getForgeFeatures() { + return this.features; + } + @Override public Optional getConfigElement(final String... key) { return this.config.getConfigElement(key); diff --git a/src/main/resources/assets/forge/lang/en_us.json b/src/main/resources/assets/forge/lang/en_us.json index 7b8f78cfb3..b9ce92ec8c 100644 --- a/src/main/resources/assets/forge/lang/en_us.json +++ b/src/main/resources/assets/forge/lang/en_us.json @@ -61,6 +61,7 @@ "fml.modloading.missingmetadata": "mods.toml missing metadata for modid {3}", "fml.modloading.failedtoloadmodclass":"{0,modinfo,name} has class loading errors\n\u00a77{2,exc,msg}", "fml.modloading.failedtoloadmod":"{0,modinfo,name} ({0,modinfo,id}) has failed to load correctly\n\u00a77{2,exc,msg}", + "fml.modloading.feature.missing": "{0,modinfo,name} ({0,modinfo,id}) is missing a feature it requires to run\n§7It requires {3}=>{4} but {5} is available", "fml.modloading.uncaughterror":"An uncaught parallel processing error has occurred.\n\u00a77{2,exc,msg}", "fml.modloading.errorduringevent":"{0,modinfo,name} ({0,modinfo,id}) encountered an error during the {1,lower} event phase\n\u00a77{2,exc,msg}", "fml.modloading.failedtoloadforge": "Failed to load Forge",