diff --git a/src/fmllauncher/java/net/minecraftforge/fml/loading/LoadingModList.java b/src/fmllauncher/java/net/minecraftforge/fml/loading/LoadingModList.java index e0db81296..acfdd79f4 100644 --- a/src/fmllauncher/java/net/minecraftforge/fml/loading/LoadingModList.java +++ b/src/fmllauncher/java/net/minecraftforge/fml/loading/LoadingModList.java @@ -139,4 +139,8 @@ public class LoadingModList public void setBrokenFiles(final List brokenFiles) { this.brokenFiles = brokenFiles; } + + public List getBrokenFiles() { + return this.brokenFiles; + } } diff --git a/src/main/java/net/minecraftforge/common/ForgeConfig.java b/src/main/java/net/minecraftforge/common/ForgeConfig.java index 6f632ee08..490ebf975 100644 --- a/src/main/java/net/minecraftforge/common/ForgeConfig.java +++ b/src/main/java/net/minecraftforge/common/ForgeConfig.java @@ -124,6 +124,8 @@ public class ForgeConfig public final BooleanValue selectiveResourceReloadEnabled; + public final BooleanValue showLoadWarnings; + Client(ForgeConfigSpec.Builder builder) { builder.comment("Client only settings, mostly things related to rendering") .push("client"); @@ -160,6 +162,11 @@ public class ForgeConfig .translation("forge.configgui.selectiveResourceReloadEnabled") .define("selectiveResourceReloadEnabled", true); + showLoadWarnings = builder + .comment("When enabled, forge will show any warnings that occurred during loading") + .translation("forge.configgui.showloadwarnings") + .define("showLoadWarnings", true); + builder.pop(); } } diff --git a/src/main/java/net/minecraftforge/fml/ModLoader.java b/src/main/java/net/minecraftforge/fml/ModLoader.java index 3887c498d..ca5f5cfc4 100644 --- a/src/main/java/net/minecraftforge/fml/ModLoader.java +++ b/src/main/java/net/minecraftforge/fml/ModLoader.java @@ -19,6 +19,7 @@ package net.minecraftforge.fml; +import com.google.common.collect.ImmutableList; import cpw.mods.modlauncher.TransformingClassLoader; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.ModelRegistryEvent; @@ -40,6 +41,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; @@ -93,6 +95,7 @@ public class ModLoader private final LoadingModList loadingModList; private final List loadingExceptions; + private final List loadingWarnings; private ModLoader() { INSTANCE = this; @@ -100,6 +103,8 @@ public class ModLoader this.loadingModList = FMLLoader.getLoadingModList(); this.loadingExceptions = FMLLoader.getLoadingModList(). getErrors().stream().flatMap(ModLoadingException::fromEarlyException).collect(Collectors.toList()); + this.loadingWarnings = FMLLoader.getLoadingModList(). + getBrokenFiles().stream().map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, "fml.modloading.brokenfile", file.getFileName())).collect(Collectors.toList()); LOGGER.info(CORE, "Loading Network data for FML net version: {}", FMLNetworkConstants.NETVERSION); } @@ -179,7 +184,6 @@ public class ModLoader } } - public void finishMods() { dispatchAndHandleError(LifecycleEventProvider.ENQUEUE_IMC); @@ -188,4 +192,13 @@ public class ModLoader GameData.freezeData(); } + public List getWarnings() + { + return ImmutableList.copyOf(this.loadingWarnings); + } + + public void addWarning(ModLoadingWarning warning) + { + this.loadingWarnings.add(warning); + } } diff --git a/src/main/java/net/minecraftforge/fml/ModLoadingWarning.java b/src/main/java/net/minecraftforge/fml/ModLoadingWarning.java new file mode 100644 index 000000000..ad2ff87d8 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/ModLoadingWarning.java @@ -0,0 +1,60 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * 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; + +import com.google.common.collect.Streams; +import net.minecraftforge.forgespi.language.IModInfo; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +public class ModLoadingWarning +{ + /** + * Mod Info for mod with warning + */ + private final IModInfo modInfo; + /** + * The stage where this warning was encountered + */ + private final ModLoadingStage warningStage; + + /** + * I18N message to use for display + */ + private final String i18nMessage; + + /** + * Context for message display + */ + private final List context; + + public ModLoadingWarning(final IModInfo modInfo, final ModLoadingStage warningStage, final String i18nMessage, Object... context) { + this.modInfo = modInfo; + this.warningStage = warningStage; + this.i18nMessage = i18nMessage; + this.context = Arrays.asList(context); + } + + public String formatToString() { + return ForgeI18n.parseMessage(i18nMessage, Streams.concat(Stream.of(modInfo, warningStage), context.stream()).toArray()); + } +} diff --git a/src/main/java/net/minecraftforge/fml/client/ClientModLoader.java b/src/main/java/net/minecraftforge/fml/client/ClientModLoader.java index 815e6a528..3c4510fdb 100644 --- a/src/main/java/net/minecraftforge/fml/client/ClientModLoader.java +++ b/src/main/java/net/minecraftforge/fml/client/ClientModLoader.java @@ -27,17 +27,27 @@ import net.minecraft.resources.IReloadableResourceManager; import net.minecraft.resources.ResourcePackList; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.common.ForgeConfig; import net.minecraftforge.fml.LoadingFailedException; import net.minecraftforge.fml.LogicalSidedProvider; import net.minecraftforge.fml.ModLoader; +import net.minecraftforge.fml.ModLoadingWarning; import net.minecraftforge.fml.SidedProvider; import net.minecraftforge.fml.VersionChecker; import net.minecraftforge.fml.client.gui.LoadingErrorScreen; import net.minecraftforge.fml.packs.ResourcePackLoader; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Collections; +import java.util.List; + +import static net.minecraftforge.fml.loading.LogMarkers.LOADING; @OnlyIn(Dist.CLIENT) public class ClientModLoader { + private static final Logger LOGGER = LogManager.getLogger(); private static boolean loading; private static Minecraft mc; private static LoadingFailedException error; @@ -76,8 +86,17 @@ public class ClientModLoader { GlStateManager.disableTexture2D(); GlStateManager.enableTexture2D(); - if (error != null) { - mc.displayGuiScreen(new LoadingErrorScreen(error)); + List warnings = ModLoader.get().getWarnings(); + if (!ForgeConfig.CLIENT.showLoadWarnings.get()) { + //User disabled warning screen, as least log them + if (!warnings.isEmpty()) { + LOGGER.warn(LOADING, "Mods loaded with {} warning(s)", warnings.size()); + warnings.forEach(warning -> LOGGER.warn(LOADING, warning.formatToString())); + } + warnings = Collections.emptyList(); //Clear warnings, as the user does not want to see them + } + if (error != null || !warnings.isEmpty()) { + mc.displayGuiScreen(new LoadingErrorScreen(error, warnings)); } else { ClientHooks.logMissingTextureErrors(); } diff --git a/src/main/java/net/minecraftforge/fml/client/gui/LoadingErrorScreen.java b/src/main/java/net/minecraftforge/fml/client/gui/LoadingErrorScreen.java index eb38df7fa..8bad5cd23 100644 --- a/src/main/java/net/minecraftforge/fml/client/gui/LoadingErrorScreen.java +++ b/src/main/java/net/minecraftforge/fml/client/gui/LoadingErrorScreen.java @@ -19,37 +19,43 @@ package net.minecraftforge.fml.client.gui; +import com.google.common.base.Strings; 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.minecraft.util.Util; +import net.minecraft.util.text.TextFormatting; import net.minecraftforge.fml.ForgeI18n; import net.minecraftforge.fml.LoadingFailedException; import net.minecraftforge.fml.ModLoadingException; -import net.minecraftforge.fml.VersionChecker; +import net.minecraftforge.fml.ModLoadingWarning; +import net.minecraftforge.fml.client.ClientHooks; import net.minecraftforge.fml.loading.FMLPaths; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collections; import java.util.List; - -import static net.minecraft.util.StringUtils.stripControlCodes; +import java.util.Objects; 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) + private final List modLoadErrors; + private final List modLoadWarnings; + private LoadingEntryList entryList; + private String errorHeader; + private String warningHeader; + + public LoadingErrorScreen(LoadingFailedException loadingException, List warnings) { super(null, null); - this.loadingFailedException = loadingException; + this.modLoadWarnings = warnings; + this.modLoadErrors = loadingException == null ? Collections.emptyList() : loadingException.getErrors(); this.modsDir = FMLPaths.MODSDIR.get(); this.logFile = FMLPaths.GAMEDIR.get().resolve(Paths.get("logs","latest.log")); } @@ -66,26 +72,44 @@ public class LoadingErrorScreen extends GuiErrorScreen { return 0.0; } + private double continueGame(double mouseX, double mouseY) + { + ClientHooks.logMissingTextureErrors(); + mc.displayGuiScreen(null); + 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, + + this.errorHeader = TextFormatting.RED + ForgeI18n.parseMessage("fml.loadingerrorscreen.errorheader", this.modLoadErrors.size()) + TextFormatting.RESET; + this.warningHeader = TextFormatting.YELLOW + ForgeI18n.parseMessage("fml.loadingerrorscreen.warningheader", this.modLoadErrors.size()) + TextFormatting.RESET; + + int yOffset = this.modLoadErrors.isEmpty() ? 46 : 38; + this.addButton(new GuiButtonClickConsumer(10, 50, this.height - yOffset, 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, + this.addButton(new GuiButtonClickConsumer(11, this.width / 2 + 5, this.height - yOffset, this.width / 2 - 55, 20, ForgeI18n.parseMessage("fml.button.open.file", logFile.getFileName()), this::openLogFile)); - this.errorList = new LoadingErrorList(this, this.loadingFailedException.getErrors()); - this.children.add(this.errorList); + if (this.modLoadErrors.isEmpty()) { + this.addButton(new GuiButtonClickConsumer(12, this.width / 4, this.height - 24, this.width / 2, 20, + ForgeI18n.parseMessage("fml.button.continue.launch"), this::continueGame)); + } + + this.entryList = new LoadingEntryList(this, this.modLoadErrors, this.modLoadWarnings); + this.children.add(this.entryList); + this.setFocused(this.entryList); } @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.entryList.drawScreen(mouseX, mouseY, partialTicks); + drawMultiLineCenteredString(fontRenderer, this.modLoadErrors.isEmpty() ? warningHeader : errorHeader, this.width / 2, 10); this.buttons.forEach(button -> button.render(mouseX, mouseY, partialTicks)); } @@ -95,10 +119,19 @@ public class LoadingErrorScreen extends GuiErrorScreen { y+=fr.FONT_HEIGHT; } } - public static class LoadingErrorList extends GuiListExtended { - LoadingErrorList(final LoadingErrorScreen parent, final List 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))); + public static class LoadingEntryList extends GuiListExtended { + LoadingEntryList(final LoadingErrorScreen parent, final List errors, final List warnings) { + super(parent.mc, parent.width, parent.height, 35, parent.height - 50, 2 * parent.mc.fontRenderer.FONT_HEIGHT + 8); + boolean both = !errors.isEmpty() && !warnings.isEmpty(); + if (both) + addEntry(new LoadingMessageEntry(parent.errorHeader, true)); + errors.forEach(e->addEntry(new LoadingMessageEntry(e.formatToString()))); + if (both) { + int maxChars = (this.width - 10) / parent.mc.fontRenderer.getStringWidth("-"); + addEntry(new LoadingMessageEntry("\n" + Strings.repeat("-", maxChars) + "\n")); + addEntry(new LoadingMessageEntry(parent.warningHeader, true)); + } + warnings.forEach(w->addEntry(new LoadingMessageEntry(w.formatToString()))); } @Override @@ -113,11 +146,17 @@ public class LoadingErrorScreen extends GuiErrorScreen { return this.width; } - public class ErrorEntry extends GuiListExtended.IGuiListEntry { - private final ModLoadingException error; + public class LoadingMessageEntry extends GuiListExtended.IGuiListEntry { + private final String message; + private final boolean center; - ErrorEntry(final ModLoadingException e) { - this.error = e; + LoadingMessageEntry(final String message) { + this(message, false); + } + + LoadingMessageEntry(final String message, final boolean center) { + this.message = Objects.requireNonNull(message); + this.center = center; } @Override @@ -125,11 +164,14 @@ public class LoadingErrorScreen extends GuiErrorScreen { int top = this.getY(); int left = this.getX(); FontRenderer font = Minecraft.getInstance().fontRenderer; - final List strings = font.listFormattedStringToWidth(error.formatToString(), LoadingErrorList.this.width); - float f = (float)top + 2; + final List strings = font.listFormattedStringToWidth(message, LoadingEntryList.this.width); + int y = top + 2; for (int i = 0; i < Math.min(strings.size(), 2); i++) { - font.drawString(strings.get(i), left + 5, f, 0xFFFFFF); - f += font.FONT_HEIGHT; + if (center) + font.drawString(strings.get(i), left + (width / 2F) - font.getStringWidth(strings.get(i)) / 2F, y, 0xFFFFFF); + else + font.drawString(strings.get(i), left + 5, y, 0xFFFFFF); + y += font.FONT_HEIGHT; } } } diff --git a/src/main/java/net/minecraftforge/fml/server/ServerModLoader.java b/src/main/java/net/minecraftforge/fml/server/ServerModLoader.java index 5222103dc..2282639bc 100644 --- a/src/main/java/net/minecraftforge/fml/server/ServerModLoader.java +++ b/src/main/java/net/minecraftforge/fml/server/ServerModLoader.java @@ -22,10 +22,18 @@ package net.minecraftforge.fml.server; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraftforge.fml.LogicalSidedProvider; import net.minecraftforge.fml.ModLoader; +import net.minecraftforge.fml.ModLoadingWarning; import net.minecraftforge.fml.SidedProvider; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; + +import static net.minecraftforge.fml.loading.LogMarkers.LOADING; public class ServerModLoader { + private static final Logger LOGGER = LogManager.getLogger(); private static DedicatedServer server; public static void begin(DedicatedServer dedicatedServer) { ServerModLoader.server = dedicatedServer; @@ -37,5 +45,10 @@ public class ServerModLoader public static void end() { ModLoader.get().finishMods(); + List warnings = ModLoader.get().getWarnings(); + if (!warnings.isEmpty()) { + LOGGER.warn(LOADING, "Mods loaded with {} warnings", warnings.size()); + warnings.forEach(warning -> LOGGER.warn(LOADING, warning.formatToString())); + } } } diff --git a/src/main/resources/assets/forge/lang/en_us.json b/src/main/resources/assets/forge/lang/en_us.json index 78000e76c..2b67a3fc0 100644 --- a/src/main/resources/assets/forge/lang/en_us.json +++ b/src/main/resources/assets/forge/lang/en_us.json @@ -18,7 +18,9 @@ "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} occurred during loading", + "fml.button.continue.launch": "Proceed to main menu", + "fml.loadingerrorscreen.errorheader": "Error loading mods\n{0,choice,1#1 error has|1<{0} errors have} occurred during loading", + "fml.loadingerrorscreen.warningheader": "{0,choice,1#Warning|1