From 4879b0611a907e19fa05a84817875fc399cc9b0f Mon Sep 17 00:00:00 2001 From: LexManos Date: Fri, 21 Dec 2018 16:07:39 -0800 Subject: [PATCH] Change how Forge itself is located to be loaded similar to a real mod. Fixes Minecraft/Forge annotation data missing. Fix Capability injection. --- .../minecraftforge/client/CloudRenderer.java | 6 +- .../client/ForgeClientHandler.java | 3 +- .../common/DimensionManager.java | 2 +- .../minecraftforge/common/ForgeConfig.java | 18 +- .../net/minecraftforge/common/ForgeHooks.java | 2 +- .../net/minecraftforge/common/ForgeMod.java | 259 +----------------- .../common/ForgeModContainer.java | 6 +- .../capabilities/CapabilityManager.java | 9 +- .../net/minecraftforge/fml/ModLoader.java | 19 +- .../minecraftforge/fml/config/ModConfig.java | 24 -- .../fml/loading/DefaultModInfos.java | 9 +- .../minecraftforge/fml/loading/FMLConfig.java | 2 + .../loading/moddiscovery/ForgeLocator.java | 244 +++++++++++++++++ .../fml/packs/ModFileResourcePack.java | 2 +- .../resource/SelectiveReloadStateHandler.java | 2 - ...forge.fml.loading.moddiscovery.IModLocator | 2 +- src/main/resources/forgemod.toml | 19 +- .../userdev/ClasspathLocator.java | 25 +- 18 files changed, 304 insertions(+), 349 deletions(-) delete mode 100644 src/main/java/net/minecraftforge/fml/config/ModConfig.java create mode 100644 src/main/java/net/minecraftforge/fml/loading/moddiscovery/ForgeLocator.java diff --git a/src/main/java/net/minecraftforge/client/CloudRenderer.java b/src/main/java/net/minecraftforge/client/CloudRenderer.java index 8f89435cc6..833da74975 100644 --- a/src/main/java/net/minecraftforge/client/CloudRenderer.java +++ b/src/main/java/net/minecraftforge/client/CloudRenderer.java @@ -28,7 +28,6 @@ import org.lwjgl.opengl.GL11; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.WorldClient; import net.minecraft.client.renderer.BufferBuilder; -import net.minecraft.client.renderer.EntityRenderer; import net.minecraft.client.renderer.GLAllocation; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.OpenGlHelper; @@ -43,8 +42,7 @@ import net.minecraft.resources.IResourceManager; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; -import net.minecraftforge.common.ForgeMod; -import net.minecraftforge.common.ForgeModContainer; +import net.minecraftforge.common.ForgeConfig; import net.minecraftforge.resource.IResourceType; import net.minecraftforge.resource.ISelectiveResourceReloadListener; import net.minecraftforge.resource.VanillaResourceType; @@ -272,7 +270,7 @@ public class CloudRenderer implements ISelectiveResourceReloadListener public void checkSettings() { - boolean newEnabled = ForgeMod.forgeCloudsEnabled + boolean newEnabled = ForgeConfig.CLIENT.forgeCloudsEnabled() && mc.gameSettings.shouldRenderClouds() != 0 && mc.world != null && mc.world.dimension.isSurfaceWorld(); diff --git a/src/main/java/net/minecraftforge/client/ForgeClientHandler.java b/src/main/java/net/minecraftforge/client/ForgeClientHandler.java index 0d7ad4069c..5050a8c296 100644 --- a/src/main/java/net/minecraftforge/client/ForgeClientHandler.java +++ b/src/main/java/net/minecraftforge/client/ForgeClientHandler.java @@ -23,7 +23,6 @@ import net.minecraftforge.client.event.ColorHandlerEvent; import net.minecraftforge.client.event.ModelRegistryEvent; import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.common.ForgeMod; -import net.minecraftforge.common.ForgeModContainer; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fluids.FluidRegistry; @@ -35,7 +34,7 @@ public class ForgeClientHandler // register model for the universal bucket, if it exists if (FluidRegistry.isUniversalBucketEnabled()) { - // TODO no more mesh definitions, this should be implemented with overrides + // TODO no more mesh definitions, this should be implemented with overrides // ModelLoader.setBucketModelDefinition(ForgeMod.getInstance().universalBucket); } } diff --git a/src/main/java/net/minecraftforge/common/DimensionManager.java b/src/main/java/net/minecraftforge/common/DimensionManager.java index 870f419aa3..7e7c55f6f3 100644 --- a/src/main/java/net/minecraftforge/common/DimensionManager.java +++ b/src/main/java/net/minecraftforge/common/DimensionManager.java @@ -395,7 +395,7 @@ public class DimensionManager { int id = queueIterator.nextInt(); DimensionData dimension = dimensions.get(id); - if (dimension.ticksWaited < ForgeMod.dimensionUnloadQueueDelay) + if (dimension.ticksWaited < ForgeConfig.GENERAL.dimensionUnloadQueueDelay()) { dimension.ticksWaited++; continue; diff --git a/src/main/java/net/minecraftforge/common/ForgeConfig.java b/src/main/java/net/minecraftforge/common/ForgeConfig.java index 8967a3b6e6..9f5e52e862 100644 --- a/src/main/java/net/minecraftforge/common/ForgeConfig.java +++ b/src/main/java/net/minecraftforge/common/ForgeConfig.java @@ -143,8 +143,7 @@ public class ForgeConfig } //TODO: Make this less duplciate? Maybe static CfgEntry zombieBaseSummonChance = create((spec, name) -> spec.comment().translation().define(name), "zombieBaseSummonChance") - public static class GENERAL - { + public static class GENERAL { public static double zombieBaseSummonChance() { return ForgeConfig.INSTANCE.configData.getOrElse("general.zombieBaseSummonChance", (double)0.01F); } @@ -160,19 +159,28 @@ public class ForgeConfig public static boolean removeErroringTileEntities() { return ForgeConfig.INSTANCE.configData.getOrElse("general.removeErroringTileEntities", false); } + public static boolean fullBoundingBoxLadders() { + return ForgeConfig.INSTANCE.configData.getOrElse("general.fullBoundingBoxLadders", false); + } + public static int dimensionUnloadQueueDelay() { + return ForgeConfig.INSTANCE.configData.getOrElse("general.dimensionUnloadQueueDelay", 0); + } + } + + public static class CLIENT { + public static boolean forgeCloudsEnabled() { + return ForgeConfig.INSTANCE.configData.getOrElse("general.forgeCloudsEnabled", true); + } } //General //public static boolean disableVersionCheck = false; - //public static boolean fullBoundingBoxLadders = false; //public static boolean logCascadingWorldGeneration = true; // see Chunk#logCascadingWorldGeneration() //public static boolean fixVanillaCascading = false; - //public static int dimensionUnloadQueueDelay = 0; //Client //public static int clumpingThreshold = 64; //public static boolean zoomInMissingModelTextInGui = false; - //public static boolean forgeCloudsEnabled = true; //public static boolean disableStairSlabCulling = false; //public static boolean alwaysSetupTerrainOffThread = false; //public static boolean forgeLightPipelineEnabled = true; diff --git a/src/main/java/net/minecraftforge/common/ForgeHooks.java b/src/main/java/net/minecraftforge/common/ForgeHooks.java index bba36a5882..6028ee9ec1 100644 --- a/src/main/java/net/minecraftforge/common/ForgeHooks.java +++ b/src/main/java/net/minecraftforge/common/ForgeHooks.java @@ -437,7 +437,7 @@ public class ForgeHooks { boolean isSpectator = (entity instanceof EntityPlayer && ((EntityPlayer)entity).isSpectator()); if (isSpectator) return false; - if (!ForgeMod.fullBoundingBoxLadders) + if (!ForgeConfig.GENERAL.fullBoundingBoxLadders()) { return state.getBlock().isLadder(state, world, pos, entity); } diff --git a/src/main/java/net/minecraftforge/common/ForgeMod.java b/src/main/java/net/minecraftforge/common/ForgeMod.java index f195f91005..1c73294b8a 100644 --- a/src/main/java/net/minecraftforge/common/ForgeMod.java +++ b/src/main/java/net/minecraftforge/common/ForgeMod.java @@ -19,16 +19,7 @@ package net.minecraftforge.common; -import net.minecraft.client.Minecraft; - -import static net.minecraftforge.common.config.Configuration.CATEGORY_CLIENT; -import static net.minecraftforge.common.config.Configuration.CATEGORY_GENERAL; - -import java.util.ArrayList; -import java.util.List; - -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.FMLWorldPersistenceHook; import net.minecraftforge.fml.VersionChecker; import net.minecraftforge.fml.WorldPersistenceHooks; import net.minecraftforge.fml.common.event.FMLServerStartingEvent; @@ -41,16 +32,12 @@ import org.apache.logging.log4j.Logger; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.storage.SaveHandler; import net.minecraft.world.storage.WorldInfo; -import net.minecraftforge.common.config.ConfigCategory; -import net.minecraftforge.common.config.Configuration; -import net.minecraftforge.common.config.Property; import net.minecraftforge.common.model.animation.CapabilityAnimation; import net.minecraftforge.energy.CapabilityEnergy; import net.minecraftforge.fluids.capability.CapabilityFluidHandler; import net.minecraftforge.items.CapabilityItemHandler; import net.minecraftforge.fluids.UniversalBucket; - -import net.minecraftforge.fml.client.event.ConfigChangedEvent.OnConfigChangedEvent; +import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.event.FMLLoadCompleteEvent; import net.minecraftforge.fml.common.event.FMLModIdMappingEvent; import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; @@ -60,33 +47,22 @@ import net.minecraftforge.fml.common.gameevent.PlayerEvent; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; +@Mod("forge") public class ForgeMod implements WorldPersistenceHooks.WorldPersistenceHook { public static final String VERSION_CHECK_CAT = "version_checking"; private static final Logger LOGGER = LogManager.getLogger(); private static final Marker FORGEMOD = MarkerManager.getMarker("FORGEMOD"); - public static int clumpingThreshold = 64; - public static boolean removeErroringEntities = false; - public static boolean removeErroringTileEntities = false; - public static boolean fullBoundingBoxLadders = false; - public static double zombieSummonBaseChance = 0.1; public static int[] blendRanges = { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34 }; - public static float zombieBabyChance = 0.05f; - public static boolean shouldSortRecipies = true; public static boolean disableVersionCheck = false; public static boolean forgeLightPipelineEnabled = true; - @Deprecated // TODO remove in 1.13 - public static boolean replaceVanillaBucketModel = true; public static boolean zoomInMissingModelTextInGui = false; - public static boolean forgeCloudsEnabled = true; public static boolean disableStairSlabCulling = false; // Also known as the "DontCullStairsBecauseIUseACrappyTexturePackThatBreaksBasicBlockShapesSoICantTrustBasicBlockCulling" flag public static boolean alwaysSetupTerrainOffThread = false; // In RenderGlobal.setupTerrain, always force the chunk render updates to be queued to the thread - public static int dimensionUnloadQueueDelay = 0; public static boolean logCascadingWorldGeneration = true; // see Chunk#logCascadingWorldGeneration() public static boolean fixVanillaCascading = false; // There are various places in vanilla that cause cascading worldgen. Enabling this WILL change where blocks are placed to prevent this. // DO NOT contact Forge about worldgen not 'matching' vanilla if this flag is set. - private static Configuration config; private static ForgeMod INSTANCE; public static ForgeMod getInstance() { @@ -98,6 +74,8 @@ public class ForgeMod implements WorldPersistenceHooks.WorldPersistenceHook public ForgeMod() { INSTANCE = this; + WorldPersistenceHooks.addHook(this); + WorldPersistenceHooks.addHook(new FMLWorldPersistenceHook()); FMLModLoadingContext.get().getModEventBus().addListener(this::preInit); FMLModLoadingContext.get().getModEventBus().addListener(this::postInit); FMLModLoadingContext.get().getModEventBus().addListener(this::onAvailable); @@ -106,233 +84,6 @@ public class ForgeMod implements WorldPersistenceHooks.WorldPersistenceHook MinecraftForge.EVENT_BUS.addListener(this::serverStopping); } -// public static Configuration getConfig() -// { -// return config; -// } - - private static void remapGeneralPropertyToClient(String key) - { - ConfigCategory GENERAL = config.getCategory(CATEGORY_GENERAL); - if (GENERAL.containsKey(key)) - { - LOGGER.debug(FORGEMOD, "Remapping property {} from category general to client",key); - Property property = GENERAL.get(key); - GENERAL.remove(key); - config.getCategory(CATEGORY_CLIENT).put(key, property); - } - } - - /** - * Synchronizes the local fields with the values in the Configuration object. - */ - private static void syncConfig(boolean load) - { - // By adding a property order list we are defining the order that the properties will appear both in the config file and on the GUIs. - // Property order lists are defined per-ConfigCategory. - List propOrder = new ArrayList(); - - if (!config.isChild) - { - if (load) - { - config.load(); - } - Property enableGlobalCfg = config.get(Configuration.CATEGORY_GENERAL, "enableGlobalConfig", false).setShowInGui(false); - if (enableGlobalCfg.getBoolean(false)) - { - Configuration.enableGlobalConfig(); - } - } - - Property prop; - - // clean up old properties that are not used anymore - if (config.getCategory(CATEGORY_GENERAL).containsKey("defaultSpawnFuzz")) config.getCategory(CATEGORY_GENERAL).remove("defaultSpawnFuzz"); - if (config.getCategory(CATEGORY_GENERAL).containsKey("spawnHasFuzz")) config.getCategory(CATEGORY_GENERAL).remove("spawnHasFuzz"); - if (config.getCategory(CATEGORY_GENERAL).containsKey("disableStitchedFileSaving")) config.getCategory(CATEGORY_GENERAL).remove("disableStitchedFileSaving"); - if (config.getCategory(CATEGORY_CLIENT).containsKey("java8Reminder")) config.getCategory(CATEGORY_CLIENT).remove("java8Reminder"); - if (config.getCategory(CATEGORY_CLIENT).containsKey("replaceVanillaBucketModel")) config.getCategory(CATEGORY_CLIENT).remove("replaceVanillaBucketModel"); - - // remap properties wrongly listed as general properties to client properties - remapGeneralPropertyToClient("biomeSkyBlendRange"); - remapGeneralPropertyToClient("forgeLightPipelineEnabled"); - - prop = config.get(CATEGORY_GENERAL, "disableVersionCheck", false); - prop.setComment("Set to true to disable Forge's version check mechanics. Forge queries a small json file on our server for version information. For more details see the ForgeVersion class in our github."); - // Language keys are a good idea to implement if you are using config GUIs. This allows you to use a .lang file that will hold the - // "pretty" version of the property name as well as allow others to provide their own localizations. - // This language key is also used to get the tooltip for a property. The tooltip language key is langKey + ".tooltip". - // If no tooltip language key is defined in your .lang file, the tooltip will default to the property comment field. - prop.setLanguageKey("forge.configgui.disableVersionCheck"); - disableVersionCheck = prop.getBoolean(disableVersionCheck); - propOrder.add(prop.getName()); - - prop = config.get(Configuration.CATEGORY_GENERAL, "clumpingThreshold", 64, - "Controls the number threshold at which Packet51 is preferred over Packet52, default and minimum 64, maximum 1024", 64, 1024); - prop.setLanguageKey("forge.configgui.clumpingThreshold").setRequiresWorldRestart(true); - clumpingThreshold = prop.getInt(64); - if (clumpingThreshold > 1024 || clumpingThreshold < 64) - { - clumpingThreshold = 64; - prop.set(64); - } - propOrder.add(prop.getName()); - - prop = config.get(CATEGORY_GENERAL, "sortRecipies", true); - prop.setComment("Set to true to enable the post initialization sorting of crafting recipes using Forge's sorter. May cause desyncing on conflicting recipes. MUST RESTART MINECRAFT IF CHANGED FROM THE CONFIG GUI."); - prop.setLanguageKey("forge.configgui.sortRecipies").setRequiresMcRestart(true); - shouldSortRecipies = prop.getBoolean(true); - propOrder.add(prop.getName()); - - prop = config.get(Configuration.CATEGORY_GENERAL, "removeErroringEntities", false); - prop.setComment("Set this to true to remove any Entity that throws an error in its update method instead of closing the server and reporting a crash log. BE WARNED THIS COULD SCREW UP EVERYTHING USE SPARINGLY WE ARE NOT RESPONSIBLE FOR DAMAGES."); - prop.setLanguageKey("forge.configgui.removeErroringEntities").setRequiresWorldRestart(true); - removeErroringEntities = prop.getBoolean(false); - propOrder.add(prop.getName()); - - if (removeErroringEntities) - { - LOGGER.warn(FORGEMOD, "Enabling removal of erroring Entities - USE AT YOUR OWN RISK"); - } - - prop = config.get(Configuration.CATEGORY_GENERAL, "removeErroringTileEntities", false); - prop.setComment("Set this to true to remove any TileEntity that throws an error in its update method instead of closing the server and reporting a crash log. BE WARNED THIS COULD SCREW UP EVERYTHING USE SPARINGLY WE ARE NOT RESPONSIBLE FOR DAMAGES."); - prop.setLanguageKey("forge.configgui.removeErroringTileEntities").setRequiresWorldRestart(true); - removeErroringTileEntities = prop.getBoolean(false); - propOrder.add(prop.getName()); - - if (removeErroringTileEntities) - { - LOGGER.warn(FORGEMOD, "Enabling removal of erroring Tile Entities - USE AT YOUR OWN RISK"); - } - - prop = config.get(Configuration.CATEGORY_GENERAL, "fullBoundingBoxLadders", false); - prop.setComment("Set this to true to check the entire entity's collision bounding box for ladders instead of just the block they are in. Causes noticeable differences in mechanics so default is vanilla behavior. Default: false"); - prop.setLanguageKey("forge.configgui.fullBoundingBoxLadders").setRequiresWorldRestart(true); - fullBoundingBoxLadders = prop.getBoolean(false); - propOrder.add(prop.getName()); - - prop = config.get(Configuration.CATEGORY_GENERAL, "zombieBaseSummonChance", 0.1, - "Base zombie summoning spawn chance. Allows changing the bonus zombie summoning mechanic.", 0.0D, 1.0D); - prop.setLanguageKey("forge.configgui.zombieBaseSummonChance").setRequiresWorldRestart(true); - zombieSummonBaseChance = prop.getDouble(0.1); - propOrder.add(prop.getName()); - - prop = config.get(Configuration.CATEGORY_GENERAL, "zombieBabyChance", 0.05, - "Chance that a zombie (or subclass) is a baby. Allows changing the zombie spawning mechanic.", 0.0D, 1.0D); - prop.setLanguageKey("forge.configgui.zombieBabyChance").setRequiresWorldRestart(true); - zombieBabyChance = (float) prop.getDouble(0.05); - propOrder.add(prop.getName()); - - prop = config.get(Configuration.CATEGORY_GENERAL, "logCascadingWorldGeneration", true, - "Log cascading chunk generation issues during terrain population."); - logCascadingWorldGeneration = prop.getBoolean(); - prop.setLanguageKey("forge.configgui.logCascadingWorldGeneration"); - propOrder.add(prop.getName()); - - prop = config.get(Configuration.CATEGORY_GENERAL, "fixVanillaCascading", false, - "Fix vanilla issues that cause worldgen cascading. This DOES change vanilla worldgen so DO NOT report bugs related to world differences if this flag is on."); - fixVanillaCascading = prop.getBoolean(); - prop.setLanguageKey("forge.configgui.fixVanillaCascading"); - propOrder.add(prop.getName()); - - prop = config.get(Configuration.CATEGORY_GENERAL, "dimensionUnloadQueueDelay", 0, - "The time in ticks the server will wait when a dimension was queued to unload. " + - "This can be useful when rapidly loading and unloading dimensions, like e.g. throwing items through a nether portal a few time per second."); - dimensionUnloadQueueDelay = prop.getInt(0); - prop.setLanguageKey("forge.configgui.dimensionUnloadQueueDelay"); - propOrder.add(prop.getName()); - - config.setCategoryPropertyOrder(CATEGORY_GENERAL, propOrder); - - propOrder = new ArrayList(); - prop = config.get(VERSION_CHECK_CAT, "Global", true, "Enable the entire mod update check system. This only applies to mods using the Forge system."); - propOrder.add("Global"); - - config.setCategoryPropertyOrder(VERSION_CHECK_CAT, propOrder); - - // Client-Side only properties - propOrder = new ArrayList(); - - prop = config.get(Configuration.CATEGORY_CLIENT, "zoomInMissingModelTextInGui", false, - "Toggle off to make missing model text in the gui fit inside the slot."); - zoomInMissingModelTextInGui = prop.getBoolean(false); - prop.setLanguageKey("forge.configgui.zoomInMissingModelTextInGui"); - propOrder.add(prop.getName()); - - prop = config.get(Configuration.CATEGORY_CLIENT, "forgeCloudsEnabled", true, - "Enable uploading cloud geometry to the GPU for faster rendering."); - prop.setLanguageKey("forge.configgui.forgeCloudsEnabled"); - forgeCloudsEnabled = prop.getBoolean(); - propOrder.add(prop.getName()); - - prop = config.get(Configuration.CATEGORY_CLIENT, "disableStairSlabCulling", false, - "Disable culling of hidden faces next to stairs and slabs. Causes extra rendering, but may fix some resource packs that exploit this vanilla mechanic."); - disableStairSlabCulling = prop.getBoolean(false); - prop.setLanguageKey("forge.configgui.disableStairSlabCulling"); - propOrder.add(prop.getName()); - - prop = config.get(Configuration.CATEGORY_CLIENT, "alwaysSetupTerrainOffThread", false, - "Enable forge to queue all chunk updates to the Chunk Update thread. May increase FPS significantly, but may also cause weird rendering lag. Not recommended for computers " + - "without a significant number of cores available."); - alwaysSetupTerrainOffThread = prop.getBoolean(false); - prop.setLanguageKey("forge.configgui.alwaysSetupTerrainOffThread"); - propOrder.add(prop.getName()); - - prop = config.get(Configuration.CATEGORY_CLIENT, "biomeSkyBlendRange", new int[] { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34 }); - prop.setComment("Control the range of sky blending for colored skies in biomes."); - prop.setLanguageKey("forge.configgui.biomeSkyBlendRange"); - blendRanges = prop.getIntList(); - propOrder.add(prop.getName()); - - prop = config.get(Configuration.CATEGORY_CLIENT, "forgeLightPipelineEnabled", true, - "Enable the forge block rendering pipeline - fixes the lighting of custom models."); - forgeLightPipelineEnabled = prop.getBoolean(true); - prop.setLanguageKey("forge.configgui.forgeLightPipelineEnabled"); - propOrder.add(prop.getName()); - - config.setCategoryPropertyOrder(CATEGORY_CLIENT, propOrder); - - if (config.hasChanged()) - { - config.save(); - } - } - - /** - * By subscribing to the OnConfigChangedEvent we are able to execute code when our config screens are closed. - * This implementation uses the optional configID string to handle multiple Configurations using one event handler. - */ - public void onConfigChanged(OnConfigChangedEvent event) - { - if (getModId().equals(event.getModID())) - { - if ("chunkLoader".equals(event.getConfigID())) - { - ForgeChunkManager.syncConfigDefaults(); - ForgeChunkManager.loadConfiguration(); - } - else - { - boolean tmpStairs = disableStairSlabCulling; - - syncConfig(false); - - if (event.isWorldRunning() && tmpStairs != disableStairSlabCulling) - { - DistExecutor.runWhenOn(Dist.CLIENT,()->()-> Minecraft.getInstance().renderGlobal.loadRenderers()); - } - - } - } - } - - public static Configuration getConfig() - { - return config; - } - /* public void missingMapping(RegistryEvent.MissingMappings event) { diff --git a/src/main/java/net/minecraftforge/common/ForgeModContainer.java b/src/main/java/net/minecraftforge/common/ForgeModContainer.java index 5e5e8eb7fb..2277c94856 100644 --- a/src/main/java/net/minecraftforge/common/ForgeModContainer.java +++ b/src/main/java/net/minecraftforge/common/ForgeModContainer.java @@ -23,9 +23,9 @@ import net.minecraftforge.fml.ModLoadingClassLoader; import net.minecraftforge.fml.javafmlmod.FMLModContainer; import net.minecraftforge.fml.loading.DefaultModInfos; +/* public class ForgeModContainer extends FMLModContainer { -/* static final Logger log = LogManager.getLogger(ForgeVersion.MOD_ID); private static ForgeModContainer INSTANCE; @@ -342,8 +342,6 @@ public class ForgeModContainer extends FMLModContainer { return updateJSONUrl; } - - */ public ForgeModContainer(ModLoadingClassLoader classLoader) { super(DefaultModInfos.forgeModInfo, "net.minecraftforge.common.ForgeMod", classLoader, null); @@ -351,3 +349,5 @@ public class ForgeModContainer extends FMLModContainer } } + +*/ diff --git a/src/main/java/net/minecraftforge/common/capabilities/CapabilityManager.java b/src/main/java/net/minecraftforge/common/capabilities/CapabilityManager.java index 1b4ea93ed5..1466d954ac 100644 --- a/src/main/java/net/minecraftforge/common/capabilities/CapabilityManager.java +++ b/src/main/java/net/minecraftforge/common/capabilities/CapabilityManager.java @@ -44,6 +44,7 @@ public enum CapabilityManager { INSTANCE; private static final Logger LOGGER = LogManager.getLogger(); + private static final Type CAP_INJECT = Type.getType(CapabilityInject.class); /** * Registers a capability to be consumed by others. @@ -76,9 +77,11 @@ public enum CapabilityManager private IdentityHashMap, Object>>> callbacks = new IdentityHashMap<>(); public void injectCapabilities(List data) { - final List capabilities = data.stream().map(ModFileScanData::getAnnotations).flatMap(Collection::stream). - filter(a -> Objects.equals(a.getClassType(), Type.getType(CapabilityInject.class))). - collect(Collectors.toList()); + final List capabilities = data.stream() + .map(ModFileScanData::getAnnotations) + .flatMap(Collection::stream) + .filter(a -> CAP_INJECT.equals(a.getAnnotationType())) + .collect(Collectors.toList()); capabilities.forEach(this::attachCapabilityToMethod); } diff --git a/src/main/java/net/minecraftforge/fml/ModLoader.java b/src/main/java/net/minecraftforge/fml/ModLoader.java index 6dcc4b86ea..be8acc8537 100644 --- a/src/main/java/net/minecraftforge/fml/ModLoader.java +++ b/src/main/java/net/minecraftforge/fml/ModLoader.java @@ -19,13 +19,11 @@ package net.minecraftforge.fml; -import com.google.common.collect.Streams; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.ModelRegistryEvent; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.capabilities.CapabilityManager; import net.minecraftforge.fml.language.IModInfo; -import net.minecraftforge.fml.loading.DefaultModInfos; import net.minecraftforge.fml.loading.FMLLoader; import net.minecraftforge.fml.loading.LoadingModList; import net.minecraftforge.fml.loading.moddiscovery.ModFile; @@ -34,7 +32,6 @@ import net.minecraftforge.registries.GameData; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.List; import java.util.Map; @@ -76,18 +73,6 @@ public class ModLoader public void loadMods() { final ModList modList = ModList.of(loadingModList.getModFiles().stream().map(ModFileInfo::getFile).collect(Collectors.toList()), loadingModList.getMods()); - ModContainer forgeModContainer; - try - { - forgeModContainer = (ModContainer)Class.forName("net.minecraftforge.common.ForgeModContainer", true, modClassLoader). - getConstructor(ModLoadingClassLoader.class).newInstance(modClassLoader); - } - catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException | InstantiationException | InvocationTargetException e) - { - LOGGER.error(CORE,"Unable to load the Forge Mod Container", e); - loadingExceptions.add(new ModLoadingException(DefaultModInfos.forgeModInfo, ModLoadingStage.VALIDATE, "fml.modloading.failedtoloadforge", e)); - forgeModContainer = null; - } if (!this.loadingExceptions.isEmpty()) { throw new LoadingFailedException(loadingExceptions); } @@ -99,10 +84,8 @@ public class ModLoader LOGGER.error(CORE, "Failed to initialize mod containers"); throw new LoadingFailedException(loadingExceptions); } - modList.setLoadedMods(Streams.concat(Stream.of(forgeModContainer), modContainerStream).collect(Collectors.toList())); + modList.setLoadedMods(modContainerStream.collect(Collectors.toList())); dispatchAndHandleError(LifecycleEventProvider.CONSTRUCT); - WorldPersistenceHooks.addHook(new FMLWorldPersistenceHook()); - WorldPersistenceHooks.addHook((WorldPersistenceHooks.WorldPersistenceHook)forgeModContainer.getMod()); GameData.fireCreateRegistryEvents(); CapabilityManager.INSTANCE.injectCapabilities(modList.getAllScanData()); dispatchAndHandleError(LifecycleEventProvider.PREINIT); diff --git a/src/main/java/net/minecraftforge/fml/config/ModConfig.java b/src/main/java/net/minecraftforge/fml/config/ModConfig.java deleted file mode 100644 index e4dc13d92b..0000000000 --- a/src/main/java/net/minecraftforge/fml/config/ModConfig.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Minecraft Forge - * Copyright (c) 2016-2018. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation version 2.1 - * of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -package net.minecraftforge.fml.config; - -public class ModConfig -{ -} diff --git a/src/main/java/net/minecraftforge/fml/loading/DefaultModInfos.java b/src/main/java/net/minecraftforge/fml/loading/DefaultModInfos.java index b24858d0a9..62b726490d 100644 --- a/src/main/java/net/minecraftforge/fml/loading/DefaultModInfos.java +++ b/src/main/java/net/minecraftforge/fml/loading/DefaultModInfos.java @@ -36,8 +36,7 @@ import java.util.Objects; public class DefaultModInfos { static { - FileConfig minecraftmod; - FileConfig forgemod; + FileConfig minecraftmod; FileConfig forgemod; try { final URI jarFileURI = DefaultModInfos.class.getClassLoader().getResource("minecraftmod.toml").toURI(); @@ -46,28 +45,24 @@ public class DefaultModInfos FileSystems.newFileSystem(jarFileURI, new HashMap<>()); } minecraftmod = FileConfig.of(Paths.get(jarFileURI)); - forgemod = FileConfig.of(Paths.get(DefaultModInfos.class.getClassLoader().getResource("forgemod.toml").toURI())); minecraftmod.load(); - forgemod.load(); } catch (IOException | URISyntaxException | NullPointerException e) { throw new RuntimeException("Missing toml configs for minecraft and forge!", e); } minecraftModInfo = new ModInfo(null, minecraftmod); - forgeModInfo = new ModInfo(null, forgemod); } public static final IModInfo minecraftModInfo; - public static final IModInfo forgeModInfo; // no construction private DefaultModInfos() {} public static List getModInfos() { - return Arrays.asList(minecraftModInfo, forgeModInfo); + return Arrays.asList(minecraftModInfo); } diff --git a/src/main/java/net/minecraftforge/fml/loading/FMLConfig.java b/src/main/java/net/minecraftforge/fml/loading/FMLConfig.java index d3633c99bd..01d1b76c5a 100644 --- a/src/main/java/net/minecraftforge/fml/loading/FMLConfig.java +++ b/src/main/java/net/minecraftforge/fml/loading/FMLConfig.java @@ -41,9 +41,11 @@ public class FMLConfig configSpec.defineInList("side", Dist.CLIENT.name(), Arrays.stream(Dist.values()).map(Enum::name).collect(Collectors.toList())); configSpec.defineInRange("maxframerate", 60, 10, 120); configSpec.defineInRange("minframerate", 60, 10, 120); + /* Tests that we know work and shouldn't be done in runtime. configSpec.defineInList(Arrays.asList("tasty","flavour"), Dist.CLIENT.name(), Arrays.stream(Dist.values()).map(Enum::name).collect(Collectors.toList())); configSpec.defineInList(Arrays.asList("tasty","teaser"), Dist.CLIENT.name(), Arrays.stream(Dist.values()).map(Enum::name).collect(Collectors.toList())); configSpec.define("longstring", StringUtils.repeat("AAAA", 10000), s->s!=null && ((String)s).length()>0); + */ } private CommentedFileConfig configData; diff --git a/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ForgeLocator.java b/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ForgeLocator.java new file mode 100644 index 0000000000..4bb8551321 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/loading/moddiscovery/ForgeLocator.java @@ -0,0 +1,244 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2018. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.fml.loading.moddiscovery; + +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.fml.loading.FMLEnvironment; +import net.minecraftforge.fml.loading.moddiscovery.IModLocator; +import net.minecraftforge.fml.loading.moddiscovery.ModFile; +import net.minecraftforge.versions.forge.ForgeVersion; +import net.minecraftforge.versions.mcp.MCPVersion; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.*; +import java.util.*; +import java.util.function.Consumer; +import java.util.jar.Manifest; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import static net.minecraftforge.fml.Logging.SCAN; + +/* + * Locate Forge in the classpath, this works similar to ClasspathLocator. + * Forge/Minecraft are in the following formats: + * Runtime: + * %lib%/net/minecraftforge/forge/{ver}/forge-{ver}.jar or ./forge-{ver}-universal.jar - Client is in libs, Server is in root + * %lib%/net/minecraftforge/forge/{ver}/forge-{ver}-{client|server}.jar - Forge, Vanilla classes, Patched + * %lib%/net/minecraft/{client|server}/{mcver}/{client|server}-{mcver}-srg.jar - Vanilla classes, SRG, Unpatched + * + * UserDev: + * Forge and vanilla is in a single jar file, patched, on the classpath. + * + * Eclipse: + * bin/{sourceset}/ + * + * Intellij: TODO: Verify, I don't use intellij + * out/production/resources/../classes/ - Resources are a separate folder so we have to navigate to classes + * + * Gradle: + * build/classes/{language}/{sourceset} + * build/resources/{sourceset} + * + */ +public class ForgeLocator implements IModLocator +{ + private static final Logger LOGGER = LogManager.getLogger(); + private static final String FORGE_META = "forgemod.toml"; + private static final String MAIN_SOURCESET = "main"; + private static final String LIB_RESOURCE = "org/objectweb/asm/Opcodes.class"; + private static final String LIB_PATH = "org/ow2/asm/asm".replace('/', File.separatorChar); + private List roots = new ArrayList<>(); + private boolean filterClasses = false; + + public ForgeLocator() { } + + @Override + public List scanMods() + { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + URL url = loader.getResource(FORGE_META); + if (url == null) + throw new IllegalStateException("Could not locate \"" + FORGE_META + "\", Forge is correupt/not on classpath!"); + + File base = getRoot(url, FORGE_META); + if (base == null) + throw new IllegalStateException("Could not determine root for Forge resource: " + url); + + url = loader.getResource(LIB_RESOURCE); + if (url == null) + throw new IllegalStateException("Could not locate \"" + LIB_RESOURCE + "\" to locate libraries directory."); + File libs = getRoot(url, LIB_RESOURCE); + //If In maven style the libraries folder we need to drop the file name, and version folder + if (libs != null && libs.getParentFile().getParentFile().getAbsolutePath().endsWith(LIB_PATH)) + { + int count = 2 + LIB_PATH.split(Pattern.quote("" + File.separatorChar)).length; + for (int x = 0; x < count; x++) + libs = libs.getParentFile(); + } + else + { + libs = null; + } + + LOGGER.info("Forge Locator: "); + LOGGER.info(" Forge: " + base); + LOGGER.info(" Libraries: " + libs); + + Path path = base.toPath(); + if (base.isDirectory()) + { // Development workspace. + int count = path.getNameCount(); + //Eclipse: /bin/{sourceset}/ + if (count >= 2 && MAIN_SOURCESET.equals(path.getName(count - 1).toString()) && "bin".equals(path.getName(count - 2).toString())) + { + roots.add(path); + roots.add(path.getParent().resolve("userdev")); //Do I need this? + } //Intellij: /out/production/resources/ & /out/production/classes/ + else if (count >= 1 && "resources".equals(path.getName(count - 1).toString())) + { + roots.add(path.getParent().resolve("classes")); + roots.add(path); + } //Gradle: build/resources/{sourceset} & build/classes/{language}/{sourceset} + else if (count >= 2 && MAIN_SOURCESET.equals(path.getName(count - 1).toString()) && "resources".equals(path.getName(count - 2).toString())) + { + Path classes = path.getParent().getParent().resolve("classes"); + roots.add(classes.resolve(MAIN_SOURCESET)); + roots.add(classes.resolve("userdev")); + roots.add(path); + roots.add(path.getParent().resolve("userdev")); + } + filterClasses = true; + } + else if (base.isFile()) + { + if (libs == null) //User dev workspace. + { + roots.add(path); + } + else //Standard install + { + String dist = FMLEnvironment.dist == Dist.CLIENT ? "client" : "server"; + String version = MCPVersion.getMCVersion() + "-" + ForgeVersion.getVersion(); + roots.add(path); // universal + roots.add(libs.toPath().resolve(Paths.get(ForgeVersion.getGroup().replace('.', File.separatorChar), "forge", version, "forge-" + version + "-" + dist + ".jar"))); // Patched classes + roots.add(libs.toPath().resolve(Paths.get("net", "minecraft", dist, MCPVersion.getMCPandMCVersion(), dist + "-" + MCPVersion.getMCPandMCVersion() + "-srg.jar"))); //Vanilla jar in srg names + } + + } + else + throw new IllegalStateException("Forge path is neither File or Directory... " + base); + + if (roots.isEmpty()) + return Collections.emptyList(); + + roots.forEach(r -> LOGGER.info(" Root: " + r)); + return Collections.singletonList(new ModFile(roots.get(0), this)); + } + + protected File getRoot(URL url, String resource) + { + String path = url.getPath(); + if ("jar".equals(url.getProtocol())) + { + int idx = path.indexOf("!/"); + path = path.substring(0, idx); + } + else + { + //Strip resource path off the end + path = path.substring(0, path.length() - resource.length()); + } + + if (path.startsWith("file:")) + path = path.substring(5); + + return new File(path).getAbsoluteFile(); + } + + @Override + public String name() + { + return "forge locator"; + } + + @Override + public Path findPath(final ModFile modFile, String... path) + { + if (path.length < 1) + throw new IllegalArgumentException("Missing path"); + + if (path.length == 2 && "META-INF".equals(path[0]) && "mods.toml".equals(path[1])) + path = FORGE_META.split("/"); + + if (filterClasses && path[path.length - 1].endsWith(".class")) + path[path.length - 1] = path[path.length - 1] + "THIS_FILE_WILL_NEVER_EXITS_DIRTY_HAX"; + + String[] tail = Arrays.copyOfRange(path, 1, path.length); + for (Path root : roots) + { + Path target = root.resolve(root.getFileSystem().getPath(path[0], tail)); + if (Files.exists(target)) + return target; + } + + return modFile.getFilePath().resolve(modFile.getFilePath().getFileSystem().getPath(path[0], tail)); + } + + @Override + public void scanFile(final ModFile modFile, final Consumer pathConsumer) + { + final Set visited = new HashSet<>(); + roots.stream().forEach(root -> + { + LOGGER.debug(SCAN,"Scanning Forge: " + root); + try (Stream files = Files.find(root, Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) + { + files.forEach(f -> { + String relative = root.relativize(f).toString(); //TODO: Test jared files... + if (visited.add(relative)) + pathConsumer.accept(Paths.get(relative)); + }); + } + catch (IOException e) + { + e.printStackTrace(); + } + }); + LOGGER.debug(SCAN,"Forge scan complete"); + } + + @Override + public String toString() + { + return "{Forge locator}"; + } + + @Override + public Optional findManifest(Path file) + { + return Optional.empty(); + } +} diff --git a/src/main/java/net/minecraftforge/fml/packs/ModFileResourcePack.java b/src/main/java/net/minecraftforge/fml/packs/ModFileResourcePack.java index f8ec48c417..264867b64f 100644 --- a/src/main/java/net/minecraftforge/fml/packs/ModFileResourcePack.java +++ b/src/main/java/net/minecraftforge/fml/packs/ModFileResourcePack.java @@ -82,7 +82,7 @@ public class ModFileResourcePack extends AbstractResourcePack map(path -> root.relativize(path.toAbsolutePath())). filter(path -> path.getNameCount() > 1 && path.getNameCount() - 1 <= maxDepth). // Make sure the depth is within bounds, ignoring domain filter(path -> !path.toString().endsWith(".mcmeta")). // Ignore .mcmeta files - filter(path -> path.subpath(1, path.getNameCount()).startsWith(inputPath)). // Make sure the target path is inside this one (again ignoring domain) + filter(path -> path.subpath(1, path.getNameCount()).startsWith(inputPath)). // Make sure the target path is inside this one (again ignoring domain) filter(path -> filter.test(path.getFileName().toString())). // Test the file name against the predicate // Finally we need to form the RL, so use the first name as the domain, and the rest as the path // It is VERY IMPORTANT that we do not rely on Path.toString as this is inconsistent between operating systems diff --git a/src/main/java/net/minecraftforge/resource/SelectiveReloadStateHandler.java b/src/main/java/net/minecraftforge/resource/SelectiveReloadStateHandler.java index b51329e560..f7d6ebf7e9 100644 --- a/src/main/java/net/minecraftforge/resource/SelectiveReloadStateHandler.java +++ b/src/main/java/net/minecraftforge/resource/SelectiveReloadStateHandler.java @@ -23,8 +23,6 @@ import javax.annotation.Nullable; import java.util.function.Predicate; import net.minecraft.resources.IResourceManagerReloadListener; -import net.minecraftforge.common.ForgeMod; -import net.minecraftforge.common.ForgeModContainer; /** * Handles reload parameters for selective loaders. diff --git a/src/main/resources/META-INF/services/net.minecraftforge.fml.loading.moddiscovery.IModLocator b/src/main/resources/META-INF/services/net.minecraftforge.fml.loading.moddiscovery.IModLocator index ef7ed2c765..8abc0aaab8 100644 --- a/src/main/resources/META-INF/services/net.minecraftforge.fml.loading.moddiscovery.IModLocator +++ b/src/main/resources/META-INF/services/net.minecraftforge.fml.loading.moddiscovery.IModLocator @@ -1,2 +1,2 @@ net.minecraftforge.fml.loading.moddiscovery.ModsFolderLocator -net.minecraftforge.fml.loading.moddiscovery.ExplodedDirectoryLocator \ No newline at end of file +net.minecraftforge.fml.loading.moddiscovery.ForgeLocator diff --git a/src/main/resources/forgemod.toml b/src/main/resources/forgemod.toml index a478beb19d..2ae7447dbb 100644 --- a/src/main/resources/forgemod.toml +++ b/src/main/resources/forgemod.toml @@ -1,8 +1,11 @@ -modId="forge" -version="${global.forgeVersion}" -displayName="Forge" -logoFile="forge_logo.png" -displayURL="https://files.minecraftforge.net/" -description=''' -Forge, the magic -''' \ No newline at end of file +modLoader="javafml" +loaderVersion="[2.0,]" +updateJSONURL="https://files.minecraftforge.net/maven/net/minecraftforge/forge/promotions_slim.json" +issueTrackerURL="http://www.minecraftforge.net/" + +[[mods]] + modId="forge" + version="${jarVersion}" + displayName="Forge" + description="Mod loading a compatibility API" + \ No newline at end of file diff --git a/src/userdev/java/net/minecraftforge/userdev/ClasspathLocator.java b/src/userdev/java/net/minecraftforge/userdev/ClasspathLocator.java index 755199c5c6..00358c27f4 100644 --- a/src/userdev/java/net/minecraftforge/userdev/ClasspathLocator.java +++ b/src/userdev/java/net/minecraftforge/userdev/ClasspathLocator.java @@ -19,8 +19,8 @@ package net.minecraftforge.userdev; +import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import com.google.common.collect.Streams; import net.minecraftforge.fml.loading.moddiscovery.IModLocator; import net.minecraftforge.fml.loading.moddiscovery.ModFile; import org.apache.logging.log4j.LogManager; @@ -28,16 +28,9 @@ import org.apache.logging.log4j.Logger; import java.io.File; import java.io.IOException; -import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.nio.channels.SeekableByteChannel; import java.nio.file.*; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.attribute.FileAttributeView; -import java.nio.file.attribute.UserPrincipalLookupService; -import java.nio.file.spi.FileSystemProvider; import java.util.*; import java.util.function.Consumer; import java.util.jar.Manifest; @@ -49,9 +42,11 @@ import static net.minecraftforge.fml.Logging.SCAN; public class ClasspathLocator implements IModLocator { private static final Logger LOGGER = LogManager.getLogger(); + private static final String COREMODS = "META-INF/coremods.json"; + private static final String MODS = "META-INF/mods.toml"; + private Map> paths = Maps.newHashMap(); - public ClasspathLocator() { - } + public ClasspathLocator() {} @Override public List scanMods() { @@ -59,10 +54,10 @@ public class ClasspathLocator implements IModLocator ClassLoader loader = Thread.currentThread().getContextClassLoader(); try { - modUrls.addAll(Collections.list(ClassLoader.getSystemResources("META-INF/coremods.json"))); - modUrls.addAll(Collections.list(loader.getResources("META-INF/coremods.json"))); - modUrls.addAll(Collections.list(ClassLoader.getSystemResources("META-INF/mods.toml"))); - modUrls.addAll(Collections.list(loader.getResources("META-INF/mods.toml"))); + modUrls.addAll(Collections.list(ClassLoader.getSystemResources(COREMODS))); + modUrls.addAll(Collections.list(loader.getResources(COREMODS))); + modUrls.addAll(Collections.list(ClassLoader.getSystemResources(MODS))); + modUrls.addAll(Collections.list(loader.getResources(MODS))); } catch (IOException e) { @@ -153,4 +148,4 @@ public class ClasspathLocator implements IModLocator { return Optional.empty(); } -} \ No newline at end of file +}