Basic error display GUI.
This commit is contained in:
parent
dec65732ce
commit
bb9eca96a8
26 changed files with 451 additions and 83 deletions
|
@ -122,7 +122,7 @@ project(':forge') {
|
|||
api 'org.ow2.asm:asm:6.2'
|
||||
api 'org.ow2.asm:asm-commons:6.2'
|
||||
api 'org.ow2.asm:asm-tree:6.2'
|
||||
api 'cpw.mods:modlauncher:0.1.0-rc.+'
|
||||
api 'cpw.mods:modlauncher:0.1.0'
|
||||
api 'net.minecraftforge:accesstransformers:0.10+:shadowed'
|
||||
api 'net.minecraftforge:eventbus:0.1+:service'
|
||||
api 'net.minecraftforge:forgespi:0.1+'
|
||||
|
|
|
@ -35,9 +35,7 @@ import java.io.InputStreamReader;
|
|||
import java.io.OutputStreamWriter;
|
||||
import java.io.PushbackInputStream;
|
||||
import java.io.Reader;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
@ -51,7 +49,7 @@ import com.google.common.base.CharMatcher;
|
|||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import com.google.common.primitives.Floats;
|
||||
import net.minecraftforge.fml.common.FMLPaths;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
|
|
@ -45,8 +45,7 @@ public class BrandingControl
|
|||
brd.add("MCP " + ForgeVersion.mcpVersion);
|
||||
brd.add("Forge " + ForgeVersion.getVersion());
|
||||
int tModCount = ModList.get().size();
|
||||
|
||||
brd.add(MessageFormat.format(ForgeI18n.getPattern("fml.menu.loadingmods"), tModCount));
|
||||
brd.add(ForgeI18n.parseMessage("fml.menu.loadingmods", tModCount));
|
||||
brandings = brd.build();
|
||||
brandingsNoMC = brandings.subList(1, brandings.size());
|
||||
}
|
||||
|
@ -68,11 +67,11 @@ public class BrandingControl
|
|||
}
|
||||
private static final List<String> defaultClientBranding = Stream.of("fml", "forge").collect(Collectors.toList());
|
||||
public static String getClientBranding() {
|
||||
return defaultClientBranding.stream().collect(Collectors.joining(","));
|
||||
return String.join(",", defaultClientBranding);
|
||||
}
|
||||
|
||||
public static final List<String> defaultServerBranding = Arrays.asList("fml", "forge");
|
||||
public static String getServerBranding() {
|
||||
return defaultServerBranding.stream().collect(Collectors.joining(","));
|
||||
return String.join(",", defaultServerBranding);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,16 +19,83 @@
|
|||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.fml.loading.StringUtils;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
|
||||
import org.apache.commons.lang3.text.ExtendedMessageFormat;
|
||||
import org.apache.commons.lang3.text.FormatFactory;
|
||||
|
||||
import java.text.FieldPosition;
|
||||
import java.text.Format;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class ForgeI18n {
|
||||
private static Map<String,String> i18n;
|
||||
private static Map<String,FormatFactory> customFactories;
|
||||
|
||||
static String getPattern(final String patternName) {
|
||||
return i18n.get(patternName);
|
||||
static {
|
||||
customFactories = new HashMap<>();
|
||||
customFactories.put("modinfo", (name, formatString, locale) -> new CustomReadOnlyFormat((stringBuffer, objectToParse) -> parseModInfo(formatString, stringBuffer, objectToParse)));
|
||||
customFactories.put("lower", (name, formatString, locale) -> new CustomReadOnlyFormat((stringBuffer, objectToParse) -> stringBuffer.append(StringUtils.toLowerCase((String)objectToParse))));
|
||||
customFactories.put("upper", (name, formatString, locale) -> new CustomReadOnlyFormat((stringBuffer, objectToParse) -> stringBuffer.append(StringUtils.toUpperCase((String)objectToParse))));
|
||||
customFactories.put("exc", (name, formatString, locale) -> new CustomReadOnlyFormat((stringBuffer, objectToParse) -> parseException(formatString, stringBuffer, objectToParse)));
|
||||
}
|
||||
|
||||
private static void parseException(final String formatString, final StringBuffer stringBuffer, final Object objectToParse) {
|
||||
Throwable t = (Throwable) objectToParse;
|
||||
if (Objects.equals(formatString, "msg")) {
|
||||
stringBuffer.append(t.getMessage());
|
||||
} else if (Objects.equals(formatString, "cls")) {
|
||||
stringBuffer.append(t.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseModInfo(final String formatString, final StringBuffer stringBuffer, final Object modInfo) {
|
||||
final ModInfo info = (ModInfo) modInfo;
|
||||
if (Objects.equals(formatString, "id")) {
|
||||
stringBuffer.append(info.getModId());
|
||||
} else if (Objects.equals(formatString, "name")) {
|
||||
stringBuffer.append(info.getDisplayName());
|
||||
}
|
||||
}
|
||||
|
||||
public static String getPattern(final String patternName) {
|
||||
return i18n.getOrDefault(patternName, patternName);
|
||||
}
|
||||
|
||||
public static void loadLanguageData(final Map<String, String> properties) {
|
||||
i18n = properties;
|
||||
}
|
||||
|
||||
public static String parseMessage(final String i18nMessage, Object... args) {
|
||||
final String pattern = getPattern(i18nMessage);
|
||||
return parseFormat(pattern, args);
|
||||
}
|
||||
|
||||
public static String parseFormat(final String format, final Object... args) {
|
||||
final ExtendedMessageFormat extendedMessageFormat = new ExtendedMessageFormat(format, customFactories);
|
||||
return extendedMessageFormat.format(args);
|
||||
}
|
||||
|
||||
public static class CustomReadOnlyFormat extends Format {
|
||||
private final BiConsumer<StringBuffer, Object> formatter;
|
||||
|
||||
CustomReadOnlyFormat(final BiConsumer<StringBuffer, Object> formatter) {
|
||||
this.formatter = formatter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
|
||||
formatter.accept(toAppendTo, obj);
|
||||
return toAppendTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object parseObject(final String source, final ParsePosition pos) {
|
||||
throw new UnsupportedOperationException("Parsing is not supported");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,6 +22,8 @@ package net.minecraftforge.fml;
|
|||
import net.minecraftforge.fml.common.event.ModLifecycleEvent;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLModContainer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public enum LifecycleEventProvider
|
||||
|
@ -33,8 +35,8 @@ public enum LifecycleEventProvider
|
|||
POSTINIT(()->new LifecycleEvent(ModLoadingStage.POSTINIT)),
|
||||
COMPLETE(()->new LifecycleEvent(ModLoadingStage.COMPLETE));
|
||||
|
||||
public void dispatch() {
|
||||
ModList.get().dispatchLifeCycleEvent(this.event.get());
|
||||
public void dispatch(Consumer<List<ModLoadingException>> errorHandler) {
|
||||
ModList.get().dispatchLifeCycleEvent(this.event.get(), errorHandler);
|
||||
}
|
||||
private final Supplier<? extends LifecycleEvent> event;
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LoadingFailedException extends RuntimeException {
|
||||
private final List<ModLoadingException> loadingExceptions;
|
||||
|
||||
public LoadingFailedException(final List<ModLoadingException> loadingExceptions) {
|
||||
this.loadingExceptions = loadingExceptions;
|
||||
}
|
||||
|
||||
public List<ModLoadingException> getErrors() {
|
||||
return this.loadingExceptions;
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ package net.minecraftforge.fml;
|
|||
|
||||
import net.minecraftforge.fml.language.IModInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
|
@ -50,13 +50,11 @@ public abstract class ModContainer
|
|||
protected ModLoadingStage modLoadingStage;
|
||||
protected final Map<ModLoadingStage, Consumer<LifecycleEventProvider.LifecycleEvent>> triggerMap;
|
||||
protected final Map<ExtensionPoint, Supplier<?>> extensionPoints = new IdentityHashMap<>();
|
||||
protected final List<Throwable> modLoadingError;
|
||||
public ModContainer(IModInfo info)
|
||||
{
|
||||
this.modId = info.getModId();
|
||||
this.modInfo = info;
|
||||
this.triggerMap = new HashMap<>();
|
||||
this.modLoadingError = new ArrayList<>();
|
||||
this.modLoadingStage = ModLoadingStage.CONSTRUCT;
|
||||
}
|
||||
|
||||
|
@ -88,19 +86,18 @@ public abstract class ModContainer
|
|||
* Transition the mod to this event if possible.
|
||||
* @param event to transition to
|
||||
*/
|
||||
public final void transitionState(LifecycleEventProvider.LifecycleEvent event)
|
||||
public final void transitionState(LifecycleEventProvider.LifecycleEvent event, Consumer<List<ModLoadingException>> errorHandler)
|
||||
{
|
||||
if (modLoadingStage == event.fromStage())
|
||||
{
|
||||
try
|
||||
{
|
||||
triggerMap.getOrDefault(modLoadingStage, e->{}).accept(event);
|
||||
modLoadingStage = event.toStage();
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
catch (ModLoadingException e)
|
||||
{
|
||||
modLoadingError.add(e);
|
||||
modLoadingStage = ModLoadingStage.ERROR;
|
||||
errorHandler.accept(Collections.singletonList(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ public class ModList
|
|||
private ModList(final List<ModFile> modFiles, final List<ModInfo> sortedList)
|
||||
{
|
||||
this.modFiles = modFiles.stream().map(ModFile::getModFileInfo).map(ModFileInfo.class::cast).collect(Collectors.toList());
|
||||
this.sortedList = Streams.concat(DefaultModInfos.getModInfos().stream(), sortedList.stream()).
|
||||
this.sortedList = sortedList.stream().
|
||||
map(ModInfo.class::cast).
|
||||
collect(Collectors.toList());
|
||||
this.fileById = this.modFiles.stream().map(ModFileInfo::getMods).flatMap(Collection::stream).
|
||||
|
@ -91,23 +91,21 @@ public class ModList
|
|||
return this.fileById.get(modid);
|
||||
}
|
||||
|
||||
public void dispatchLifeCycleEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent) {
|
||||
public void dispatchLifeCycleEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent, final Consumer<List<ModLoadingException>> errorHandler) {
|
||||
FMLLoader.getLanguageLoadingProvider().forEach(lp->lp.preLifecycleEvent(lifecycleEvent));
|
||||
DeferredWorkQueue.deferredWorkQueue.clear();
|
||||
try
|
||||
{
|
||||
modLoadingThreadPool.submit(()->this.mods.parallelStream().forEach(m->m.transitionState(lifecycleEvent))).get();
|
||||
modLoadingThreadPool.submit(()->this.mods.parallelStream().forEach(m->m.transitionState(lifecycleEvent, errorHandler))).get();
|
||||
}
|
||||
catch (InterruptedException | ExecutionException e)
|
||||
{
|
||||
LOGGER.error(LOADING, "Encountered an exception during parallel processing", e);
|
||||
}
|
||||
LOGGER.debug(LOADING, "Dispatching synchronous work, {} jobs", DeferredWorkQueue.deferredWorkQueue.size());
|
||||
DeferredWorkQueue.deferredWorkQueue.forEach(FutureTask::run);
|
||||
LOGGER.debug(LOADING, "Synchronous work queue complete");
|
||||
FMLLoader.getLanguageLoadingProvider().forEach(lp->lp.postLifecycleEvent(lifecycleEvent));
|
||||
final List<ModContainer> erroredContainers = this.mods.stream().filter(m -> m.getCurrentState() == ModLoadingStage.ERROR).collect(Collectors.toList());
|
||||
if (!erroredContainers.isEmpty()) {
|
||||
throw new RuntimeException("Errored containers found!", erroredContainers.get(0).modLoadingError.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
public void setLoadedMods(final List<ModContainer> modContainers)
|
||||
|
|
|
@ -25,6 +25,8 @@ 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.EarlyLoadingException;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.LoadingModList;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
|
@ -34,11 +36,10 @@ import org.apache.logging.log4j.LogManager;
|
|||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -53,12 +54,15 @@ public class ModLoader
|
|||
private final LoadingModList loadingModList;
|
||||
private final ModLoadingClassLoader modClassLoader;
|
||||
|
||||
private final List<ModLoadingException> loadingExceptions;
|
||||
private ModLoader()
|
||||
{
|
||||
INSTANCE = this;
|
||||
this.launchClassLoader = FMLLoader.getLaunchClassLoader();
|
||||
this.loadingModList = FMLLoader.getLoadingModList();
|
||||
this.modClassLoader = new ModLoadingClassLoader(this.launchClassLoader);
|
||||
this.loadingExceptions = FMLLoader.getLoadingModList().
|
||||
getErrors().stream().map(ModLoadingException::fromEarlyException).collect(Collectors.toList());
|
||||
Thread.currentThread().setContextClassLoader(this.modClassLoader);
|
||||
}
|
||||
|
||||
|
@ -67,14 +71,17 @@ public class ModLoader
|
|||
return INSTANCE == null ? INSTANCE = new ModLoader() : INSTANCE;
|
||||
}
|
||||
|
||||
private static Callable<Boolean> fireClientEvents()
|
||||
private static Runnable fireClientEvents()
|
||||
{
|
||||
return ()->MinecraftForge.EVENT_BUS.post(new ModelRegistryEvent());
|
||||
}
|
||||
|
||||
public void loadMods() {
|
||||
if (!this.loadingExceptions.isEmpty()) {
|
||||
throw new LoadingFailedException(loadingExceptions);
|
||||
}
|
||||
final ModList modList = ModList.of(loadingModList.getModFiles().stream().map(ModFileInfo::getFile).collect(Collectors.toList()), loadingModList.getMods());
|
||||
final ModContainer forgeModContainer;
|
||||
ModContainer forgeModContainer;
|
||||
try
|
||||
{
|
||||
forgeModContainer = (ModContainer)Class.forName("net.minecraftforge.common.ForgeModContainer", true, modClassLoader).
|
||||
|
@ -83,21 +90,41 @@ public class ModLoader
|
|||
catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException | InstantiationException | InvocationTargetException e)
|
||||
{
|
||||
LOGGER.error(CORE,"Unable to load the Forge Mod Container", e);
|
||||
throw new RuntimeException(e);
|
||||
loadingExceptions.add(new ModLoadingException(DefaultModInfos.forgeModInfo, ModLoadingStage.CONSTRUCT, "fml.modloading.failedtoloadforge", e));
|
||||
forgeModContainer = null;
|
||||
}
|
||||
final Stream<ModContainer> modContainerStream = loadingModList.getModFiles().stream().
|
||||
map(ModFileInfo::getFile).
|
||||
map(mf -> buildMods(mf, modClassLoader)).
|
||||
flatMap(Collection::stream);
|
||||
if (!loadingExceptions.isEmpty()) {
|
||||
LOGGER.error(CORE, "Failed to initialize mod containers");
|
||||
throw new LoadingFailedException(loadingExceptions);
|
||||
}
|
||||
modList.setLoadedMods(Streams.concat(Stream.of(forgeModContainer), modContainerStream).collect(Collectors.toList()));
|
||||
LifecycleEventProvider.CONSTRUCT.dispatch();
|
||||
dispatchAndHandleError(LifecycleEventProvider.CONSTRUCT);
|
||||
WorldPersistenceHooks.addHook(new FMLWorldPersistenceHook());
|
||||
WorldPersistenceHooks.addHook((WorldPersistenceHooks.WorldPersistenceHook)forgeModContainer.getMod());
|
||||
GameData.fireCreateRegistryEvents();
|
||||
CapabilityManager.INSTANCE.injectCapabilities(modList.getAllScanData());
|
||||
LifecycleEventProvider.PREINIT.dispatch();
|
||||
Boolean result = DistExecutor.callWhenOn(Dist.CLIENT, ModLoader::fireClientEvents);
|
||||
LifecycleEventProvider.SIDEDINIT.dispatch();
|
||||
dispatchAndHandleError(LifecycleEventProvider.PREINIT);
|
||||
DistExecutor.runWhenOn(Dist.CLIENT, ModLoader::fireClientEvents);
|
||||
dispatchAndHandleError(LifecycleEventProvider.SIDEDINIT);
|
||||
}
|
||||
|
||||
private void dispatchAndHandleError(LifecycleEventProvider event) {
|
||||
if (!loadingExceptions.isEmpty()) {
|
||||
LOGGER.error("Skipping lifecycle event {}, {} errors found.", event, loadingExceptions.size());
|
||||
} else {
|
||||
event.dispatch(this::accumulateErrors);
|
||||
}
|
||||
if (!loadingExceptions.isEmpty()) {
|
||||
LOGGER.error("Failed to complete lifecycle event {}, {} errors found", event, loadingExceptions.size());
|
||||
throw new LoadingFailedException(loadingExceptions);
|
||||
}
|
||||
}
|
||||
private void accumulateErrors(List<ModLoadingException> errors) {
|
||||
loadingExceptions.addAll(errors);
|
||||
}
|
||||
|
||||
private List<ModContainer> buildMods(final ModFile modFile, final ModLoadingClassLoader modClassLoader)
|
||||
|
@ -105,16 +132,22 @@ public class ModLoader
|
|||
final Map<String, IModInfo> modInfoMap = modFile.getModFileInfo().getMods().stream().collect(Collectors.toMap(IModInfo::getModId, Function.identity()));
|
||||
|
||||
return modFile.getScanResult().getTargets().entrySet().stream().
|
||||
map(e->e.getValue().<ModContainer>loadMod(modInfoMap.get(e.getKey()), modClassLoader, modFile.getScanResult())).
|
||||
collect(Collectors.toList());
|
||||
map(e-> {
|
||||
try {
|
||||
return e.getValue().<ModContainer>loadMod(modInfoMap.get(e.getKey()), modClassLoader, modFile.getScanResult());
|
||||
} catch (ModLoadingException mle) {
|
||||
loadingExceptions.add(mle);
|
||||
return null;
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
public void finishMods()
|
||||
{
|
||||
LifecycleEventProvider.INIT.dispatch();
|
||||
LifecycleEventProvider.POSTINIT.dispatch();
|
||||
LifecycleEventProvider.COMPLETE.dispatch();
|
||||
dispatchAndHandleError(LifecycleEventProvider.INIT);
|
||||
dispatchAndHandleError(LifecycleEventProvider.POSTINIT);
|
||||
dispatchAndHandleError(LifecycleEventProvider.COMPLETE);
|
||||
GameData.freezeData();
|
||||
}
|
||||
|
||||
|
|
|
@ -19,16 +19,60 @@
|
|||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import com.google.common.collect.ObjectArrays;
|
||||
import com.google.common.collect.Streams;
|
||||
import net.minecraftforge.fml.language.IModInfo;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Accumulates errors during loading for compact handling
|
||||
* General purpose mod loading error message
|
||||
*/
|
||||
public class ModLoadingException extends RuntimeException
|
||||
{
|
||||
private List<ErrorEvent> errorEvents = new ArrayList<>();
|
||||
public static class ErrorEvent {
|
||||
/**
|
||||
* Mod Info for mod with issue
|
||||
*/
|
||||
private final IModInfo modInfo;
|
||||
/**
|
||||
* The stage where this error was encountered
|
||||
*/
|
||||
private final ModLoadingStage errorStage;
|
||||
|
||||
/**
|
||||
* I18N message to use for display
|
||||
*/
|
||||
private final String i18nMessage;
|
||||
|
||||
/**
|
||||
* Context for message display
|
||||
*/
|
||||
private final List<Object> context;
|
||||
|
||||
public ModLoadingException(final IModInfo modInfo, final ModLoadingStage errorStage, final String i18nMessage, final Throwable originalException, Object... context) {
|
||||
super(ForgeI18n.getPattern(i18nMessage), originalException);
|
||||
this.modInfo = modInfo;
|
||||
this.errorStage = errorStage;
|
||||
this.i18nMessage = i18nMessage;
|
||||
this.context = Arrays.asList(context);
|
||||
}
|
||||
|
||||
static ModLoadingException fromEarlyException(final EarlyLoadingException e) {
|
||||
return new ModLoadingException(null, ModLoadingStage.VALIDATE, e.getI18NMessage(), e, e.getContext().toArray());
|
||||
}
|
||||
|
||||
public String getI18NMessage() {
|
||||
return i18nMessage;
|
||||
}
|
||||
|
||||
public Object[] getContext() {
|
||||
return context.toArray();
|
||||
}
|
||||
|
||||
public String formatToString() {
|
||||
return ForgeI18n.parseMessage(i18nMessage, Streams.concat(Stream.of(modInfo, errorStage, getCause()), context.stream()).toArray());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import java.util.function.Supplier;
|
|||
public enum ModLoadingStage
|
||||
{
|
||||
ERROR(null),
|
||||
VALIDATE(null),
|
||||
CONSTRUCT(null),
|
||||
PREINIT(()->FMLPreInitializationEvent::new),
|
||||
SIDEDINIT(SidedProvider.SIDEDINIT::get),
|
||||
|
|
|
@ -29,11 +29,13 @@ import net.minecraft.resources.ResourcePackList;
|
|||
import net.minecraft.resources.data.IMetadataSectionSerializer;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fml.LoadingFailedException;
|
||||
import net.minecraftforge.fml.LogicalSidedProvider;
|
||||
import net.minecraftforge.fml.ModLoader;
|
||||
import net.minecraftforge.fml.SidedProvider;
|
||||
import net.minecraftforge.fml.VersionChecker;
|
||||
import net.minecraftforge.fml.ModLoader;
|
||||
import net.minecraftforge.fml.client.gui.GuiNotification;
|
||||
import net.minecraftforge.fml.client.gui.LoadingErrorScreen;
|
||||
import net.minecraftforge.fml.client.registry.RenderingRegistry;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -44,6 +46,7 @@ public class ClientModLoader
|
|||
{
|
||||
private static boolean loading;
|
||||
private static Minecraft mc;
|
||||
private static LoadingFailedException error;
|
||||
|
||||
public static void begin(final Minecraft minecraft, final ResourcePackList<ResourcePackInfoClient> defaultResourcePacks, final IReloadableResourceManager mcResourceManager, DownloadingPackFinder metadataSerializer)
|
||||
{
|
||||
|
@ -51,13 +54,21 @@ public class ClientModLoader
|
|||
ClientModLoader.mc = minecraft;
|
||||
SidedProvider.setClient(()->minecraft);
|
||||
LogicalSidedProvider.setClient(()->minecraft);
|
||||
ModLoader.get().loadMods();
|
||||
try {
|
||||
ModLoader.get().loadMods();
|
||||
} catch (LoadingFailedException e) {
|
||||
error = e;
|
||||
}
|
||||
ResourcePackLoader.loadResourcePacks(defaultResourcePacks);
|
||||
}
|
||||
|
||||
public static void end()
|
||||
{
|
||||
ModLoader.get().finishMods();
|
||||
try {
|
||||
ModLoader.get().finishMods();
|
||||
} catch (LoadingFailedException e) {
|
||||
if (error == null) error = e;
|
||||
}
|
||||
loading = false;
|
||||
mc.gameSettings.loadOptions();
|
||||
}
|
||||
|
@ -71,11 +82,9 @@ public class ClientModLoader
|
|||
{
|
||||
GlStateManager.disableTexture2D();
|
||||
GlStateManager.enableTexture2D();
|
||||
}
|
||||
|
||||
public static boolean isErrored()
|
||||
{
|
||||
return false;
|
||||
if (error != null) {
|
||||
mc.displayGuiScreen(new LoadingErrorScreen(error));
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isLoading()
|
||||
|
|
|
@ -19,12 +19,9 @@
|
|||
|
||||
package net.minecraftforge.fml.client;
|
||||
|
||||
import net.minecraft.client.resources.ResourcePackInfoClient;
|
||||
import net.minecraft.resources.AbstractResourcePack;
|
||||
import net.minecraft.resources.FilePack;
|
||||
import net.minecraft.resources.FolderPack;
|
||||
import net.minecraft.resources.IPackFinder;
|
||||
import net.minecraft.resources.IReloadableResourceManager;
|
||||
import net.minecraft.resources.IResourcePack;
|
||||
import net.minecraft.resources.ResourcePackInfo;
|
||||
import net.minecraft.resources.ResourcePackList;
|
||||
|
@ -33,7 +30,6 @@ import net.minecraftforge.fml.loading.FMLLoader;
|
|||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package net.minecraftforge.fml.client.gui;
|
||||
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
|
||||
import java.util.function.DoubleBinaryOperator;
|
||||
|
||||
public class GuiButtonClickConsumer extends GuiButton {
|
||||
private final DoubleBinaryOperator onClickAction;
|
||||
|
||||
public GuiButtonClickConsumer(final int buttonId, final int x, final int y, final int widthIn, final int heightIn, final String buttonText, DoubleBinaryOperator onClick) {
|
||||
super(buttonId, x, y, widthIn, heightIn, buttonText);
|
||||
this.onClickAction = onClick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(final double mouseX, final double mouseY) {
|
||||
onClickAction.applyAsDouble(mouseX, mouseY);
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ import net.minecraft.client.gui.GuiErrorScreen;
|
|||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fml.common.FMLPaths;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
package net.minecraftforge.fml.client.gui;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.gui.GuiErrorScreen;
|
||||
import net.minecraft.client.gui.GuiListExtended;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraftforge.fml.ForgeI18n;
|
||||
import net.minecraftforge.fml.LoadingFailedException;
|
||||
import net.minecraftforge.fml.ModLoadingException;
|
||||
import net.minecraftforge.fml.VersionChecker;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.awt.*;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
import static net.minecraft.util.StringUtils.stripControlCodes;
|
||||
|
||||
public class LoadingErrorScreen extends GuiErrorScreen {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private final Path modsDir;
|
||||
private final Path logFile;
|
||||
private final LoadingFailedException loadingFailedException;
|
||||
private LoadingErrorList errorList;
|
||||
public LoadingErrorScreen(LoadingFailedException loadingException)
|
||||
{
|
||||
super(null, null);
|
||||
this.loadingFailedException = loadingException;
|
||||
this.modsDir = FMLPaths.MODSDIR.get();
|
||||
this.logFile = FMLPaths.GAMEDIR.get().resolve(Paths.get("logs","latest.log"));
|
||||
}
|
||||
|
||||
private double openModsDir(double mouseX, double mouseY)
|
||||
{
|
||||
try
|
||||
{
|
||||
Desktop.getDesktop().open(modsDir.toFile());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Problem opening mods folder", e);
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private double openLogFile(double mouseX, double mouseY)
|
||||
{
|
||||
try
|
||||
{
|
||||
Desktop.getDesktop().open(logFile.toFile());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Problem opening log file {}", logFile, e);
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initGui()
|
||||
{
|
||||
super.initGui();
|
||||
this.buttons.clear();
|
||||
this.children.clear();
|
||||
this.addButton(new GuiButtonClickConsumer(10, 50, this.height - 38, this.width / 2 - 55, 20,
|
||||
ForgeI18n.parseMessage("fml.button.open.mods.folder"), this::openModsDir));
|
||||
this.addButton(new GuiButtonClickConsumer(11, this.width / 2 + 5, this.height - 38, this.width / 2 - 55, 20,
|
||||
ForgeI18n.parseMessage("fml.button.open.file", logFile.getFileName()), this::openLogFile));
|
||||
this.errorList = new LoadingErrorList(this, this.loadingFailedException.getErrors());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(int mouseX, int mouseY, float partialTicks)
|
||||
{
|
||||
this.drawDefaultBackground();
|
||||
this.errorList.drawScreen(mouseX, mouseY, partialTicks);
|
||||
drawMultiLineCenteredString(fontRenderer, ForgeI18n.parseMessage("fml.loadingerrorscreen.header", this.loadingFailedException.getErrors().size()), this.width / 2, 10);
|
||||
this.buttons.forEach(button -> button.render(mouseX, mouseY, partialTicks));
|
||||
}
|
||||
|
||||
private void drawMultiLineCenteredString(FontRenderer fr, String str, int x, int y) {
|
||||
for (String s : fr.listFormattedStringToWidth(str, this.width)) {
|
||||
fr.drawStringWithShadow(s, (float) (x - fr.getStringWidth(s) / 2.0), y, 0xFFFFFF);
|
||||
y+=fr.FONT_HEIGHT;
|
||||
}
|
||||
}
|
||||
public static class LoadingErrorList extends GuiListExtended<LoadingErrorList.ErrorEntry> {
|
||||
LoadingErrorList(final LoadingErrorScreen parent, final List<ModLoadingException> errors) {
|
||||
super(parent.mc,parent.width,parent.height,35,parent.height - 50, 2 * parent.mc.fontRenderer.FONT_HEIGHT + 8);
|
||||
errors.forEach(e->addEntry(new ErrorEntry(e)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getScrollBarX()
|
||||
{
|
||||
return this.right - 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getListWidth()
|
||||
{
|
||||
return this.width;
|
||||
}
|
||||
|
||||
public class ErrorEntry extends GuiListExtended.IGuiListEntry<ErrorEntry> {
|
||||
private final ModLoadingException error;
|
||||
|
||||
ErrorEntry(final ModLoadingException e) {
|
||||
this.error = e;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawEntry(final int entryWidth, final int entryHeight, final int mouseX, final int mouseY, final boolean p_194999_5_, final float partialTicks) {
|
||||
int top = this.getY();
|
||||
int left = this.getX();
|
||||
FontRenderer font = Minecraft.getInstance().fontRenderer;
|
||||
final List<String> strings = font.listFormattedStringToWidth(error.formatToString(), LoadingErrorList.this.width);
|
||||
float f = (float)top + 2;
|
||||
for (int i = 0; i < Math.min(strings.size(), 2); i++) {
|
||||
font.drawString(strings.get(i), left + 5, f, 0xFFFFFFFF);
|
||||
f += font.FONT_HEIGHT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -23,12 +23,13 @@ import net.minecraftforge.eventbus.EventBusErrorMessage;
|
|||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.eventbus.api.IEventListener;
|
||||
import net.minecraftforge.fml.AutomaticEventSubscriber;
|
||||
import net.minecraftforge.fml.LifecycleEventProvider;
|
||||
import net.minecraftforge.fml.ModContainer;
|
||||
import net.minecraftforge.fml.ModLoadingException;
|
||||
import net.minecraftforge.fml.ModLoadingStage;
|
||||
import net.minecraftforge.fml.ModThreadContext;
|
||||
import net.minecraftforge.fml.common.event.ModLifecycleEvent;
|
||||
import net.minecraftforge.fml.AutomaticEventSubscriber;
|
||||
import net.minecraftforge.fml.ModLoadingStage;
|
||||
import net.minecraftforge.fml.ModContainer;
|
||||
import net.minecraftforge.fml.language.IModInfo;
|
||||
import net.minecraftforge.fml.language.ModFileScanData;
|
||||
|
||||
|
@ -67,7 +68,7 @@ public class FMLModContainer extends ModContainer
|
|||
catch (Throwable e)
|
||||
{
|
||||
LOGGER.error(LOADING, "Failed to load class {}", className, e);
|
||||
throw new RuntimeException(e);
|
||||
throw new ModLoadingException(info, ModLoadingStage.CONSTRUCT, "fml.modloading.failedtoloadmodclass", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,7 +87,7 @@ public class FMLModContainer extends ModContainer
|
|||
private void onEventFailed(IEventBus iEventBus, Event event, IEventListener[] iEventListeners, int i, Throwable throwable)
|
||||
{
|
||||
LOGGER.error(new EventBusErrorMessage(event, i, iEventListeners, throwable));
|
||||
modLoadingError.add(throwable);
|
||||
|
||||
}
|
||||
|
||||
private void beforeEvent(LifecycleEventProvider.LifecycleEvent lifecycleEvent) {
|
||||
|
@ -105,8 +106,7 @@ public class FMLModContainer extends ModContainer
|
|||
catch (Throwable e)
|
||||
{
|
||||
LOGGER.error(LOADING,"Caught exception during event {} dispatch for modid {}", event, this.getModId(), e);
|
||||
modLoadingStage = ModLoadingStage.ERROR;
|
||||
modLoadingError.add(e);
|
||||
throw new ModLoadingException(modInfo, lifecycleEvent.fromStage(), "fml.modloading.errorduringevent", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,9 @@ public class FMLModContainer extends ModContainer
|
|||
|
||||
private void preinitMod(LifecycleEventProvider.LifecycleEvent lifecycleEvent)
|
||||
{
|
||||
LOGGER.debug(LOADING, "Injecting Automatic event subscribers for {}", getModId());
|
||||
AutomaticEventSubscriber.inject(this, this.scanResults, this.modClass.getClassLoader());
|
||||
LOGGER.debug(LOADING, "Completed Automatic event subscribers for {}", getModId());
|
||||
}
|
||||
|
||||
private void constructMod(LifecycleEventProvider.LifecycleEvent event)
|
||||
|
@ -134,8 +136,7 @@ public class FMLModContainer extends ModContainer
|
|||
catch (Throwable e)
|
||||
{
|
||||
LOGGER.error(LOADING,"Failed to create mod instance. ModID: {}, class {}", getModId(), modClass.getName(), e);
|
||||
modLoadingStage = ModLoadingStage.ERROR;
|
||||
modLoadingError.add(e);
|
||||
throw new ModLoadingException(modInfo, event.fromStage(), "fml.modloading.failedtoloadmod", e, modClass);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Thrown during early loading phase, and collected by the LoadingModList for handoff to the client
|
||||
* or server.
|
||||
*/
|
||||
public class EarlyLoadingException extends RuntimeException {
|
||||
private final String i18nMessage;
|
||||
private final List<Object> context;
|
||||
|
||||
public EarlyLoadingException(final String message, final String i18nMessage, final Throwable originalException, Object... context) {
|
||||
super(message, originalException);
|
||||
this.i18nMessage = i18nMessage;
|
||||
this.context = Arrays.asList(context);
|
||||
}
|
||||
|
||||
public String getI18NMessage() {
|
||||
return this.i18nMessage;
|
||||
}
|
||||
|
||||
public List<Object> getContext() {
|
||||
return this.context;
|
||||
}
|
||||
}
|
|
@ -23,7 +23,6 @@ import com.electronwill.nightconfig.core.ConfigSpec;
|
|||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
import com.electronwill.nightconfig.core.io.WritingMode;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.common.FMLPaths;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
|
|
|
@ -17,13 +17,14 @@
|
|||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.common;
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import cpw.mods.modlauncher.api.IEnvironment;
|
||||
import net.minecraftforge.fml.FileUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
|
@ -72,6 +73,12 @@ public enum FMLPaths
|
|||
for (FMLPaths path : FMLPaths.values())
|
||||
{
|
||||
path.absolutePath = rootPath.resolve(path.relativePath).toAbsolutePath();
|
||||
try {
|
||||
path.absolutePath = path.absolutePath.toRealPath();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Unable to resolve path {}", path.absolutePath, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
LOGGER.debug(CORE,"Path {} is {}", ()-> path, ()-> path.absolutePath);
|
||||
if (path.isDirectory)
|
||||
{
|
|
@ -25,7 +25,6 @@ import cpw.mods.modlauncher.api.ITransformer;
|
|||
import cpw.mods.modlauncher.api.IncompatibleEnvironmentException;
|
||||
import joptsimple.ArgumentAcceptingOptionSpec;
|
||||
import joptsimple.OptionSpecBuilder;
|
||||
import net.minecraftforge.fml.common.FMLPaths;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
@ -37,8 +36,6 @@ import java.util.function.BiFunction;
|
|||
|
||||
import static net.minecraftforge.fml.Logging.CORE;
|
||||
|
||||
import cpw.mods.modlauncher.api.ITransformationService.OptionResult;
|
||||
|
||||
public class FMLServiceProvider implements ITransformationService
|
||||
{
|
||||
|
||||
|
|
|
@ -30,10 +30,10 @@ import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
|
|||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -48,6 +48,7 @@ public class LoadingModList
|
|||
private final List<ModInfo> sortedList;
|
||||
private final Map<String, ModFileInfo> fileById;
|
||||
private BackgroundScanHandler scanner;
|
||||
private final List<EarlyLoadingException> preLoadErrors;
|
||||
|
||||
private LoadingModList(final List<ModFile> modFiles, final List<ModInfo> sortedList)
|
||||
{
|
||||
|
@ -58,11 +59,16 @@ public class LoadingModList
|
|||
this.fileById = this.modFiles.stream().map(ModFileInfo::getMods).flatMap(Collection::stream).
|
||||
map(ModInfo.class::cast).
|
||||
collect(Collectors.toMap(ModInfo::getModId, ModInfo::getOwningFile));
|
||||
this.preLoadErrors = new ArrayList<>();
|
||||
}
|
||||
|
||||
public static LoadingModList of(List<ModFile> modFiles, List<ModInfo> sortedList)
|
||||
public static LoadingModList of(List<ModFile> modFiles, List<ModInfo> sortedList, final EarlyLoadingException earlyLoadingException)
|
||||
{
|
||||
INSTANCE = new LoadingModList(modFiles, sortedList);
|
||||
if (earlyLoadingException != null)
|
||||
{
|
||||
INSTANCE.preLoadErrors.add(earlyLoadingException);
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
|
@ -109,4 +115,8 @@ public class LoadingModList
|
|||
{
|
||||
return this.sortedList;
|
||||
}
|
||||
|
||||
public List<EarlyLoadingException> getErrors() {
|
||||
return preLoadErrors;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,10 +56,15 @@ public class ModSorter
|
|||
public static LoadingModList sort(List<ModFile> mods)
|
||||
{
|
||||
final ModSorter ms = new ModSorter(mods);
|
||||
ms.buildUniqueList();
|
||||
ms.verifyDependencyVersions();
|
||||
ms.sort();
|
||||
return LoadingModList.of(ms.modFiles, ms.sortedList);
|
||||
EarlyLoadingException earlyLoadingException = null;
|
||||
try {
|
||||
ms.buildUniqueList();
|
||||
ms.verifyDependencyVersions();
|
||||
ms.sort();
|
||||
} catch (EarlyLoadingException ele) {
|
||||
earlyLoadingException = ele;
|
||||
}
|
||||
return LoadingModList.of(ms.modFiles, ms.sortedList, earlyLoadingException);
|
||||
}
|
||||
|
||||
private void sort()
|
||||
|
@ -77,7 +82,7 @@ public class ModSorter
|
|||
{
|
||||
TopologicalSort.TopoSortException.TopoSortExceptionData<Supplier<ModInfo>> data = e.getData();
|
||||
LOGGER.error(LOADING, ()-> data);
|
||||
throw e;
|
||||
throw new EarlyLoadingException("Sorting error", "fml.modloading.sortingerror", e, e.getData());
|
||||
}
|
||||
this.sortedList = sorted.stream().map(Supplier::get).map(ModFileInfo::getMods).
|
||||
flatMap(Collection::stream).map(ModInfo.class::cast).collect(Collectors.toList());
|
||||
|
@ -107,7 +112,7 @@ public class ModSorter
|
|||
final List<Map.Entry<String, List<ModInfo>>> dupedMods = modIds.entrySet().stream().filter(e -> e.getValue().size() > 1).collect(Collectors.toList());
|
||||
|
||||
if (!dupedMods.isEmpty()) {
|
||||
throw new DuplicateModsFoundException(null);
|
||||
throw new EarlyLoadingException("Duplicate mods found", "fml.modloading.dupesfound", null, dupedMods);
|
||||
}
|
||||
|
||||
modIdNameLookup = modIds.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
|
||||
|
@ -127,7 +132,7 @@ public class ModSorter
|
|||
LOGGER.debug(LOADING, "Found {} mandatory mod requirements missing", missingVersions.size());
|
||||
|
||||
if (!missingVersions.isEmpty()) {
|
||||
throw new RuntimeException("Missing mods");
|
||||
throw new EarlyLoadingException("Missing mods", "fml.modloading.missingmods", null, missingVersions);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.apache.commons.lang3.text.StrSubstitutor;
|
|||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
|
@ -32,7 +33,11 @@ import java.util.Optional;
|
|||
public class StringUtils
|
||||
{
|
||||
public static String toLowerCase(final String str) {
|
||||
return str.toLowerCase(java.util.Locale.ROOT);
|
||||
return str.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
public static String toUpperCase(final String str) {
|
||||
return str.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
public static boolean endsWith(final String search, final String... endings) {
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import net.minecraftforge.fml.loading.StringUtils;
|
||||
import net.minecraftforge.fml.common.FMLPaths;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
|
|
@ -7,6 +7,13 @@
|
|||
"fml.menu.mods.config": "Config",
|
||||
"fml.menu.modoptions": "Mod Options...",
|
||||
"fml.menu.loadingmods": "{0,choice,0#No mods|1#1 mod|1<{0} mods} loaded",
|
||||
"fml.button.open.file": "Open {0}",
|
||||
"fml.button.open.mods.folder": "Open Mods Folder",
|
||||
"fml.loadingerrorscreen.header": "Error loading mods\n{0,choice,1#1 error has|1<{0} errors have} occured during loading",
|
||||
"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.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",
|
||||
|
||||
"commands.forge.dimensions.list": "Currently registered dimensions by type:",
|
||||
"commands.forge.entity.list.invalid": "Invalid filter, does not match any entities. Use /forge entity list for a proper list",
|
||||
|
|
Loading…
Reference in a new issue