diff --git a/src/main/java/net/neoforged/neoforge/client/gui/ConfigurationScreen.java b/src/main/java/net/neoforged/neoforge/client/gui/ConfigurationScreen.java index 712b50c165c..8bb379ef73e 100644 --- a/src/main/java/net/neoforged/neoforge/client/gui/ConfigurationScreen.java +++ b/src/main/java/net/neoforged/neoforge/client/gui/ConfigurationScreen.java @@ -39,13 +39,20 @@ import net.minecraft.client.gui.components.Tooltip; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.ConfirmScreen; +import net.minecraft.client.gui.screens.GenericMessageScreen; import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; import net.minecraft.client.gui.screens.options.OptionsSubScreen; +import net.minecraft.client.multiplayer.ServerData; import net.minecraft.client.resources.language.I18n; +import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.neoforged.fml.ModContainer; import net.neoforged.fml.config.ConfigTracker; +import net.neoforged.fml.config.IConfigEvent; import net.neoforged.fml.config.ModConfig; import net.neoforged.fml.config.ModConfig.Type; import net.neoforged.fml.loading.FMLLoader; @@ -86,6 +93,14 @@ public void finish() { } } + public enum RestartType { + NONE, SERVER, GAME; + + RestartType with(RestartType other) { + return other == NONE ? this : (other == GAME || this == GAME) ? GAME : SERVER; + } + } + public static boolean compactList = false; private static final String LANG_PREFIX = "neoforge.configuration.uitext."; @@ -98,10 +113,18 @@ public void finish() { private static final Component MOVE_LIST_ELEMENT_DOWN = Component.translatable(LANG_PREFIX + "listelementdown"); private static final Component REMOVE_LIST_ELEMENT = Component.translatable(LANG_PREFIX + "listelementremove"); private static final Component UNSUPPORTED_ELEMENT = Component.translatable(LANG_PREFIX + "unsupportedelement"); + private static final Component GAME_RESTART_TITLE = Component.translatable(LANG_PREFIX + "restart.game.title"); + private static final Component GAME_RESTART_MESSAGE = Component.translatable(LANG_PREFIX + "restart.game.text"); + private static final Component GAME_RESTART_YES = Component.translatable("menu.quit"); + private static final Component SERVER_RESTART_TITLE = Component.translatable(LANG_PREFIX + "restart.server.title"); + private static final Component SERVER_RESTART_MESSAGE = Component.translatable(LANG_PREFIX + "restart.server.text"); + private static final Component RETURN_TO_MENU = Component.translatable("menu.returnToMenu"); + private static final Component SAVING_LEVEL = Component.translatable("menu.savingLevel"); + private static final Component RESTART_NO = Component.translatable(LANG_PREFIX + "restart.return"); static final TranslationChecker translationChecker = new TranslationChecker(); private final ModContainer mod; - private boolean needsRestart = false; + private RestartType needsRestart = RestartType.NONE; public ConfigurationScreen(final ModContainer mod, final Minecraft mc, final Screen parent) { super(parent, mc.options, Component.translatable(translationChecker.check(mod.getModId() + ".configuration.title"))); @@ -131,11 +154,55 @@ protected void addOptions() { @Override public void onClose() { - if (needsRestart) { - System.out.println("TODO"); // TODO - } translationChecker.finish(); - super.onClose(); + switch (needsRestart) { + case GAME: + minecraft.setScreen(new ConfirmScreen(b -> { + if (b) { + minecraft.stop(); + } else { + minecraft.setScreen(this); + } + }, GAME_RESTART_TITLE, GAME_RESTART_MESSAGE, GAME_RESTART_YES, RESTART_NO)); + return; + case SERVER: + if (minecraft.level != null) { + minecraft.setScreen(new ConfirmScreen(b -> { + if (b) { + // when changing server configs from the client is added, this is where we tell the server to restart and the new config. + // also needs a different text in MP ("server will restart/exit, yada yada") than in SP + onDisconnect(); + } else { + minecraft.setScreen(this); + } + }, SERVER_RESTART_TITLE, SERVER_RESTART_MESSAGE, minecraft.isLocalServer() ? RETURN_TO_MENU : CommonComponents.GUI_DISCONNECT, RESTART_NO)); + return; + } + // else fallthrough. If no server is running, we don't need to stop one. + case NONE: + super.onClose(); + } + } + + // direct copy from PauseScreen (which has the best implementation), sadly it's not really accessible + private void onDisconnect() { + boolean flag = this.minecraft.isLocalServer(); + ServerData serverdata = this.minecraft.getCurrentServer(); + this.minecraft.level.disconnect(); + if (flag) { + this.minecraft.disconnect(new GenericMessageScreen(SAVING_LEVEL)); + } else { + this.minecraft.disconnect(); + } + + TitleScreen titlescreen = new TitleScreen(); + if (flag) { + this.minecraft.setScreen(titlescreen); + } else if (serverdata != null && serverdata.isRealm()) { + this.minecraft.setScreen(new RealmsMainScreen(titlescreen)); + } else { + this.minecraft.setScreen(new JoinMultiplayerScreen(titlescreen)); + } } public static class ConfigurationSectionScreen extends OptionsSubScreen { @@ -184,12 +251,12 @@ public Object any() { protected final Context context; protected boolean changed = false; - protected boolean needsRestart = false; + protected RestartType needsRestart = RestartType.NONE; protected final Map undoMap = new HashMap<>(); public ConfigurationSectionScreen(final ModContainer mod, final Minecraft mc, final Screen parent, final ModConfig.Type type, final ModConfig modConfig) { this(Context.top(mod, mc, parent, type, modConfig), Component.translatable(translationChecker.check(mod.getModId() + ".configuration." + type.name().toLowerCase() + ".title"))); - needsRestart = type == Type.STARTUP; + needsRestart = type == Type.STARTUP ? RestartType.GAME : RestartType.NONE; } public ConfigurationSectionScreen(final Context parentContext, final Screen parent, final Map valueSpecs, final String key, @@ -240,8 +307,8 @@ protected Component getTooltipComponent(final String key) { protected void onChanged(final String key) { changed = true; final ValueSpec valueSpec = getValueSpec(key); - if (valueSpec != null) { - needsRestart |= valueSpec.needsWorldRestart(); + if (valueSpec != null && valueSpec.needsWorldRestart()) { + needsRestart = needsRestart.with(RestartType.SERVER); } } @@ -480,14 +547,16 @@ public void onClose() { if (lastScreen instanceof final ConfigurationSectionScreen parent) { parent.changed = true; } else { + // we are a top-level per-type config screen, i.e. one specific config file. Save the config and tell the mod to reload. + // Yes, this means this will fire multiple times if the user changes values in multiple configs, but that doesn't really matter. context.modConfig.save(); + IConfigEvent.reloading(context.modConfig).post(); } - } - if (needsRestart) { + // restart only matters when there were actual changes if (lastScreen instanceof final ConfigurationSectionScreen parent) { - parent.needsRestart = true; + parent.needsRestart = parent.needsRestart.with(needsRestart); } else if (lastScreen instanceof final ConfigurationScreen parent) { - parent.needsRestart = true; + parent.needsRestart = parent.needsRestart.with(needsRestart); } } super.onClose(); diff --git a/src/main/java/net/neoforged/neoforge/common/NeoForgeConfig.java b/src/main/java/net/neoforged/neoforge/common/NeoForgeConfig.java index 6e075ef4af9..a872061ba32 100644 --- a/src/main/java/net/neoforged/neoforge/common/NeoForgeConfig.java +++ b/src/main/java/net/neoforged/neoforge/common/NeoForgeConfig.java @@ -121,9 +121,9 @@ public static class Client { .define("useCombinedDepthStencilAttachment", false); logUntranslatedConfigurationWarnings = builder - .comment("A config option mainly for developers. Logs out confifiguration values that do not have translations when running a client in a development environment.") - .translation("neoforge.configgui.logUntranslatedConfigurationWarnings") - .define("logUntranslatedConfigurationWarnings", true); + .comment("A config option mainly for developers. Logs out confifiguration values that do not have translations when running a client in a development environment.") + .translation("neoforge.configgui.logUntranslatedConfigurationWarnings") + .define("logUntranslatedConfigurationWarnings", true); builder.pop(); } diff --git a/src/main/resources/assets/neoforge/lang/en_us.json b/src/main/resources/assets/neoforge/lang/en_us.json index f869b9790b3..60abf1f9c21 100644 --- a/src/main/resources/assets/neoforge/lang/en_us.json +++ b/src/main/resources/assets/neoforge/lang/en_us.json @@ -137,6 +137,12 @@ "neoforge.configuration.uitext.client": "Client Options", "neoforge.configuration.uitext.server": "Server Options", "neoforge.configuration.uitext.startup": "Startup Options", + "neoforge.configuration.uitext.restart.game.title": "Minecraft needs to be restarted", + "neoforge.configuration.uitext.restart.game.text": "One or more of the configuration option that were changed only take effect when the game is started.", + "neoforge.configuration.uitext.restart.server.title": "World needs to be reloaded", + "neoforge.configuration.uitext.restart.server.text": "One or more of the configuration option that were changed only take effect when a world is started or loaded.", + "neoforge.configuration.uitext.restart.return": "Not yet...", + "neoforge.configuration.title": "NeoForge Configuration",