diff --git a/src/main/java/org/embeddedt/modernfix/ModernFixClient.java b/src/main/java/org/embeddedt/modernfix/ModernFixClient.java index b9546f40..95e33c1f 100644 --- a/src/main/java/org/embeddedt/modernfix/ModernFixClient.java +++ b/src/main/java/org/embeddedt/modernfix/ModernFixClient.java @@ -1,18 +1,23 @@ package org.embeddedt.modernfix; +import com.mojang.blaze3d.platform.InputConstants; import com.mojang.datafixers.util.Pair; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import net.minecraft.client.KeyMapping; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.DebugScreenOverlay; import net.minecraft.client.gui.screens.ConnectScreen; import net.minecraft.client.gui.screens.TitleScreen; import net.minecraft.util.MemoryReserve; +import net.minecraftforge.client.ClientRegistry; +import net.minecraftforge.client.ConfigGuiHandler; import net.minecraftforge.client.event.ScreenEvent; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.world.entity.Entity; import net.minecraftforge.client.event.*; import net.minecraftforge.client.gui.ForgeIngameGui; +import net.minecraftforge.client.settings.KeyConflictContext; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.TagsUpdatedEvent; import net.minecraftforge.event.TickEvent; @@ -20,13 +25,18 @@ import net.minecraftforge.event.server.ServerStartingEvent; import net.minecraftforge.event.world.WorldEvent; import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.IExtensionPoint; import net.minecraftforge.fml.ModContainer; import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.ModLoadingContext; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.util.ObfuscationReflectionHelper; import net.minecraftforge.network.NetworkEvent; import org.embeddedt.modernfix.core.ModernFixMixinPlugin; import org.embeddedt.modernfix.core.config.ModernFixConfig; import org.embeddedt.modernfix.packet.EntityIDSyncPacket; +import org.embeddedt.modernfix.screen.ModernFixConfigScreen; import org.embeddedt.modernfix.world.IntegratedWatchdog; import java.lang.management.ManagementFactory; @@ -52,6 +62,27 @@ public class ModernFixClient { if(mfContainer.isPresent()) brandingString = "ModernFix " + mfContainer.get().getModInfo().getVersion().toString(); } + + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::clientSetup); + ModLoadingContext.get().registerExtensionPoint( + ConfigGuiHandler.ConfigGuiFactory.class, + () -> new ConfigGuiHandler.ConfigGuiFactory((mc, screen) -> new ModernFixConfigScreen(screen)) + ); + } + + private KeyMapping configKey; + + private void clientSetup(FMLClientSetupEvent event) { + configKey = new KeyMapping("key.modernfix.config", KeyConflictContext.UNIVERSAL, InputConstants.UNKNOWN, "key.modernfix"); + ClientRegistry.registerKeyBinding(configKey); + + } + + @SubscribeEvent + public void onConfigKey(TickEvent.ClientTickEvent event) { + if(event.phase == TickEvent.Phase.START && configKey.consumeClick()) { + Minecraft.getInstance().setScreen(new ModernFixConfigScreen(Minecraft.getInstance().screen)); + } } public void resetWorldLoadStateMachine() { diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index 22557872..c90e1617 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -17,6 +17,8 @@ public class ModernFixEarlyConfig { public static final boolean OPTIFINE_PRESENT; + private File configFile; + static { boolean hasOfClass = false; try { @@ -34,7 +36,8 @@ public class ModernFixEarlyConfig { return FMLLoader.getLoadingModList().getModFileById(modId) != null; } - private ModernFixEarlyConfig() { + private ModernFixEarlyConfig(File file) { + this.configFile = file; // Defines the default rules which can be configured by the user or other mods. // You must manually add a rule for any new mixins not covered by an existing package rule. this.addMixinRule("core", true); // TODO: Don't actually allow the user to disable this @@ -188,7 +191,7 @@ public class ModernFixEarlyConfig { * created. The file on disk will then be updated to include any new options. */ public static ModernFixEarlyConfig load(File file) { - ModernFixEarlyConfig config = new ModernFixEarlyConfig(); + ModernFixEarlyConfig config = new ModernFixEarlyConfig(file); Properties props = new Properties(); if(file.exists()) { try (FileInputStream fin = new FileInputStream(file)){ @@ -200,7 +203,7 @@ public class ModernFixEarlyConfig { } try { - config.writeConfig(file, props); + config.save(); } catch (IOException e) { LOGGER.warn("Could not write configuration file", e); } @@ -208,8 +211,8 @@ public class ModernFixEarlyConfig { return config; } - private void writeConfig(File file, Properties props) throws IOException { - File dir = file.getParentFile(); + public void save() throws IOException { + File dir = configFile.getParentFile(); if (!dir.exists()) { if (!dir.mkdirs()) { @@ -219,23 +222,24 @@ public class ModernFixEarlyConfig { throw new IOException("The parent file is not a directory"); } - try (Writer writer = new FileWriter(file)) { + try (Writer writer = new FileWriter(configFile)) { writer.write("# This is the configuration file for ModernFix.\n"); writer.write("#\n"); writer.write("# The following options can be enabled or disabled if there is a compatibility issue.\n"); writer.write("# Add a line mixin.example_name=true/false without the # sign to enable/disable a rule.\n"); - List lines = this.options.keySet().stream() + List keys = this.options.keySet().stream() .filter(key -> !key.equals("mixin.core")) .sorted() - .map(key -> "# " + key + "\n") .collect(Collectors.toList()); - for(String line : lines) { - writer.write(line); + for(String line : keys) { + if(!line.equals("mixin.core")) + writer.write("# " + line + "\n"); } - for (Map.Entry entry : props.entrySet()) { - String key = (String) entry.getKey(); - String value = (String) entry.getValue(); - writer.write(key + "=" + value + "\n"); + + for (String key : keys) { + Option option = this.options.get(key); + if(option.isUserDefined()) + writer.write(key + "=" + option.isEnabled() + "\n"); } } } @@ -254,4 +258,8 @@ public class ModernFixEarlyConfig { .filter(Option::isOverridden) .count(); } + + public Map getOptionMap() { + return Collections.unmodifiableMap(this.options); + } } diff --git a/src/main/java/org/embeddedt/modernfix/core/config/Option.java b/src/main/java/org/embeddedt/modernfix/core/config/Option.java index e268ff29..f370e8bc 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/Option.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/Option.java @@ -19,11 +19,15 @@ public class Option { } public void setEnabled(boolean enabled, boolean userDefined) { + if(this.enabled == enabled) + return; this.enabled = enabled; this.userDefined = userDefined; } public void addModOverride(boolean enabled, String modId) { + if(this.enabled == enabled) + return; this.enabled = enabled; if (this.modDefined == null) { @@ -57,6 +61,10 @@ public class Option { this.modDefined = null; } + public void clearUserDefined() { + this.userDefined = false; + } + public Collection getDefiningMods() { return this.modDefined != null ? Collections.unmodifiableCollection(this.modDefined) : Collections.emptyList(); } diff --git a/src/main/java/org/embeddedt/modernfix/screen/ModernFixConfigScreen.java b/src/main/java/org/embeddedt/modernfix/screen/ModernFixConfigScreen.java new file mode 100644 index 00000000..ca3e50bf --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/screen/ModernFixConfigScreen.java @@ -0,0 +1,41 @@ +package org.embeddedt.modernfix.screen; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TranslatableComponent; + +import javax.annotation.Nullable; + +public class ModernFixConfigScreen extends Screen { + private OptionList optionList; + private Screen lastScreen; + + public boolean madeChanges = false; + private Button doneButton; + public ModernFixConfigScreen(@Nullable Screen lastScreen) { + super(new TranslatableComponent("modernfix.config")); + this.lastScreen = lastScreen; + } + + @Override + protected void init() { + this.optionList = new OptionList(this, this.minecraft); + this.addWidget(this.optionList); + this.doneButton = new Button(this.width / 2 - 100, this.height - 29, 200, 20, CommonComponents.GUI_DONE, (arg) -> { + this.minecraft.setScreen(lastScreen); + }); + this.addRenderableWidget(this.doneButton); + } + + @Override + public void render(PoseStack poseStack, int mouseX, int mouseY, float partialTicks) { + this.renderBackground(poseStack); + this.optionList.render(poseStack, mouseX, mouseY, partialTicks); + drawCenteredString(poseStack, this.font, this.title, this.width / 2, 8, 16777215); + this.doneButton.setMessage(madeChanges ? new TranslatableComponent("modernfix.config.done_restart") : CommonComponents.GUI_DONE); + super.render(poseStack, mouseX, mouseY, partialTicks); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/screen/OptionList.java b/src/main/java/org/embeddedt/modernfix/screen/OptionList.java new file mode 100644 index 00000000..c3ccbe6a --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/screen/OptionList.java @@ -0,0 +1,125 @@ +package org.embeddedt.modernfix.screen; + +import com.google.common.collect.ImmutableList; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.ChatFormatting; +import net.minecraft.client.KeyMapping; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.ContainerObjectSelectionList; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.components.toasts.SystemToast; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.network.chat.TranslatableComponent; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.core.ModernFixMixinPlugin; +import org.embeddedt.modernfix.core.config.Option; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +public class OptionList extends ContainerObjectSelectionList { + private final int maxNameWidth; + + private static final Component OPTION_ON = new TranslatableComponent("modernfix.option.on").withStyle(style -> style.withColor(ChatFormatting.GREEN)); + private static final Component OPTION_OFF = new TranslatableComponent("modernfix.option.off").withStyle(style -> style.withColor(ChatFormatting.RED)); + + private ModernFixConfigScreen mainScreen; + + + public OptionList(ModernFixConfigScreen arg, Minecraft arg2) { + super(arg2,arg.width + 45, arg.height, 43, arg.height - 32, 20); + + this.mainScreen = arg; + + int maxW = 0; + Map optionMap = ModernFixMixinPlugin.config.getOptionMap(); + List sortedKeys = optionMap.keySet().stream().filter(key -> !key.equals("mixin.core")).sorted().collect(Collectors.toList()); + for(String key : sortedKeys) { + Option option = optionMap.get(key); + int w = this.minecraft.font.width(key); + maxW = Math.max(w, maxW); + this.addEntry(new OptionEntry(key, option)); + } + this.maxNameWidth = maxW; + } + + protected int getScrollbarPosition() { + return super.getScrollbarPosition() + 15 + 20; + } + + public int getRowWidth() { + return super.getRowWidth() + 32; + } + + class OptionEntry extends Entry { + private final String name; + + private final Button toggleButton; + private final Option option; + + public OptionEntry(String optionName, Option option) { + this.name = optionName; + this.option = option; + this.toggleButton = new Button(0, 0, 95, 20, new TextComponent(""), (arg) -> { + this.option.setEnabled(!this.option.isEnabled(), !this.option.isUserDefined()); + try { + ModernFixMixinPlugin.config.save(); + if(!OptionList.this.mainScreen.madeChanges) { + OptionList.this.mainScreen.madeChanges = true; + } + } catch(IOException e) { + // revert + this.option.setEnabled(!this.option.isEnabled(), !this.option.isUserDefined()); + ModernFix.LOGGER.error("Unable to save config", e); + } + }); + } + + @Override + public void render(PoseStack matrixStack, int index, int top, int left, int width, int height, int mouseX, int mouseY, boolean isMouseOver, float partialTicks) { + MutableComponent nameComponent = new TextComponent(this.name); + if(this.option.isUserDefined()) + nameComponent = nameComponent.withStyle(ChatFormatting.ITALIC).append(new TranslatableComponent("modernfix.config.not_default")); + OptionList.this.minecraft.font.draw(matrixStack, nameComponent, (float)(left + 160 - OptionList.this.maxNameWidth), (float)(top + height / 2 - 4), 16777215); + this.toggleButton.x = left + 175; + this.toggleButton.y = top; + this.toggleButton.setMessage(getOptionMessage(this.option)); + this.toggleButton.render(matrixStack, mouseX, mouseY, partialTicks); + } + + private Component getOptionMessage(Option option) { + return option.isEnabled() ? OPTION_ON : OPTION_OFF; + } + + @Override + public List children() { + return ImmutableList.of(this.toggleButton); + } + + public boolean mouseClicked(double mouseX, double mouseY, int button) { + return this.toggleButton.mouseClicked(mouseX, mouseY, button); + } + + public boolean mouseReleased(double mouseX, double mouseY, int button) { + return this.toggleButton.mouseReleased(mouseX, mouseY, button); + } + + @Override + public List narratables() { + return Collections.emptyList(); + } + } + + public abstract static class Entry extends ContainerObjectSelectionList.Entry { + public Entry() { + } + } +} diff --git a/src/main/resources/assets/modernfix/lang/en_us.json b/src/main/resources/assets/modernfix/lang/en_us.json index b757242b..ddd8c10b 100644 --- a/src/main/resources/assets/modernfix/lang/en_us.json +++ b/src/main/resources/assets/modernfix/lang/en_us.json @@ -1,6 +1,13 @@ { + "key.modernfix": "ModernFix", + "key.modernfix.config": "Open config screen", "modernfix.jei_load": "Loading JEI, this may take a while", "modernfix.no_lazydfu": "ModernFix detected that DFU rules were compiled on startup. This slows down game launching. Installing LazyDFU to resolve this is highly recommended.", + "modernfix.config": "ModernFix mixin config", + "modernfix.config.done_restart": "Done (restart required)", + "modernfix.option.on": "on", + "modernfix.option.off": "off", + "modernfix.config.not_default": " (modified)", "asynclocator.map.locating": "Map (Locating...)", "asynclocator.map.none": "Map (No nearby feature found)" }