Add UI for warnings that occurred during loading (#5530)

This commit is contained in:
ichttt 2019-03-11 23:32:37 +01:00 committed by LexManos
parent 7113f4400c
commit 00845d5252
8 changed files with 192 additions and 31 deletions

View file

@ -139,4 +139,8 @@ public class LoadingModList
public void setBrokenFiles(final List<ModFile> brokenFiles) {
this.brokenFiles = brokenFiles;
}
public List<ModFile> getBrokenFiles() {
return this.brokenFiles;
}
}

View file

@ -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();
}
}

View file

@ -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<ModLoadingException> loadingExceptions;
private final List<ModLoadingWarning> 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<ModLoadingWarning> getWarnings()
{
return ImmutableList.copyOf(this.loadingWarnings);
}
public void addWarning(ModLoadingWarning warning)
{
this.loadingWarnings.add(warning);
}
}

View file

@ -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<Object> 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());
}
}

View file

@ -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<ModLoadingWarning> 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();
}

View file

@ -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<ModLoadingException> modLoadErrors;
private final List<ModLoadingWarning> modLoadWarnings;
private LoadingEntryList entryList;
private String errorHeader;
private String warningHeader;
public LoadingErrorScreen(LoadingFailedException loadingException, List<ModLoadingWarning> 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.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)));
public static class LoadingEntryList extends GuiListExtended<LoadingEntryList.LoadingMessageEntry> {
LoadingEntryList(final LoadingErrorScreen parent, final List<ModLoadingException> errors, final List<ModLoadingWarning> 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<ErrorEntry> {
private final ModLoadingException error;
public class LoadingMessageEntry extends GuiListExtended.IGuiListEntry<LoadingMessageEntry> {
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<String> strings = font.listFormattedStringToWidth(error.formatToString(), LoadingErrorList.this.width);
float f = (float)top + 2;
final List<String> 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;
}
}
}

View file

@ -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<ModLoadingWarning> warnings = ModLoader.get().getWarnings();
if (!warnings.isEmpty()) {
LOGGER.warn(LOADING, "Mods loaded with {} warnings", warnings.size());
warnings.forEach(warning -> LOGGER.warn(LOADING, warning.formatToString()));
}
}
}

View file

@ -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<Warnings} while loading mods\n{0,choice,1#1 warning has|1<{0} warnings have} occurred during loading",
"fml.language.missingversion": "Mod File {5} needs language provider {3}:{4,vr} to load\n\u00a77We have found {6,i18n,fml.messages.artifactversion}",
"fml.modloading.missingmetadata": "mods.toml missing metadata for modid {0}",
"fml.modloading.failedtoloadmodclass":"{0,modinfo,name} has class loading errors\n\u00a77{2,exc,msg}",
@ -28,6 +30,7 @@
"fml.modloading.missingdependency": "Mod \u00a7e{4}\u00a7r requires \u00a76{3}\u00a7r \u00a7o{5,vr}\u00a7r\n\u00a77Currently, \u00a76{3}\u00a7r\u00a77 is \u00a7o{6,i18n,fml.messages.artifactversion.ornotinstalled}",
"fml.modloading.cycle": "Detected a mod dependency cycle: {0}",
"fml.modloading.failedtoprocesswork":"{0,modinfo,name} ({0,modinfo,id}) encountered an error processing deferred work\n\u00a77{2,exc,msg}",
"fml.modloading.brokenfile": "File {2} is not a valid mod file",
"fml.messages.artifactversion.ornotinstalled":"{0,ornull,fml.messages.artifactversion.notinstalled}",
"fml.messages.artifactversion":"{0,ornull,fml.messages.artifactversion.none}",