From 975d898ac618ccc8c187b785d09678efb38f5f7c Mon Sep 17 00:00:00 2001 From: zelophed Date: Fri, 28 May 2021 14:42:27 +0200 Subject: [PATCH] all the configs - make it easier for addon devs to hook into create's config ui - change the config command to allow for: - opening any mod's registered config - changing single values at a time - possibly solve incompatibility with CalemiUtils mod and placement helpers --- .../simibubi/create/events/ClientEvents.java | 6 +- .../foundation/command/ConfigCommand.java | 73 ++++++++- .../command/SConfigureConfigPacket.java | 58 ++++++- .../config/ui/BaseConfigScreen.java | 132 +++++++++++++--- .../config/ui/CConfigureConfigPacket.java | 41 +++-- .../foundation/config/ui/ConfigHelper.java | 142 ++++++++++++++++++ .../foundation/config/ui/ConfigScreen.java | 1 + .../config/ui/OpenConfigButton.java | 12 +- .../config/ui/SubMenuConfigScreen.java | 46 +++++- .../config/ui/entries/ValueEntry.java | 12 +- .../foundation/gui/ConfirmationScreen.java | 13 +- .../utility/placement/PlacementOffset.java | 8 +- 12 files changed, 468 insertions(+), 76 deletions(-) create mode 100644 src/main/java/com/simibubi/create/foundation/config/ui/ConfigHelper.java diff --git a/src/main/java/com/simibubi/create/events/ClientEvents.java b/src/main/java/com/simibubi/create/events/ClientEvents.java index 30db2de2a..cdfb70268 100644 --- a/src/main/java/com/simibubi/create/events/ClientEvents.java +++ b/src/main/java/com/simibubi/create/events/ClientEvents.java @@ -106,7 +106,7 @@ public class ClientEvents { AirCurrent.tickClientPlayerSounds(); return; } - + SoundScapes.tick(); AnimationTickHolder.tick(); @@ -339,8 +339,8 @@ public class ClientEvents { } public static void loadCompleted(FMLLoadCompleteEvent event) { - ModContainer createContainer = ModList.get().getModContainerById("create").orElseThrow(() -> new IllegalStateException("Create Mod Container missing after loadCompleted")); - createContainer.registerExtensionPoint(ExtensionPoint.CONFIGGUIFACTORY, () -> (mc, previousScreen) -> new BaseConfigScreen(previousScreen)); + ModContainer createContainer = ModList.get().getModContainerById(Create.ID).orElseThrow(() -> new IllegalStateException("Create Mod Container missing after loadCompleted")); + createContainer.registerExtensionPoint(ExtensionPoint.CONFIGGUIFACTORY, () -> (mc, previousScreen) -> BaseConfigScreen.forCreate(previousScreen)); } } diff --git a/src/main/java/com/simibubi/create/foundation/command/ConfigCommand.java b/src/main/java/com/simibubi/create/foundation/command/ConfigCommand.java index 24a45110a..5115d9d6f 100644 --- a/src/main/java/com/simibubi/create/foundation/command/ConfigCommand.java +++ b/src/main/java/com/simibubi/create/foundation/command/ConfigCommand.java @@ -1,14 +1,25 @@ package com.simibubi.create.foundation.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.simibubi.create.Create; +import com.simibubi.create.foundation.config.ui.ConfigHelper; +import com.simibubi.create.foundation.networking.AllPackets; + import net.minecraft.command.CommandSource; import net.minecraft.command.Commands; import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.util.text.StringTextComponent; +import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.network.PacketDistributor; -import com.mojang.brigadier.Command; -import com.mojang.brigadier.builder.ArgumentBuilder; -import com.simibubi.create.foundation.networking.AllPackets; - +/** + * Examples: + * /create config client - to open Create's ConfigGui with the client config already selected + * /create config "botania:common" - to open Create's ConfigGui with Botania's common config already selected + * /create config "create:client.client.rainbowDebug" set false - to disable Create's rainbow debug for the sender + */ public class ConfigCommand { public static ArgumentBuilder register() { @@ -21,7 +32,59 @@ public class ConfigCommand { ); return Command.SINGLE_SUCCESS; - }); + }) + .then(Commands.argument("path", StringArgumentType.string()) + .executes(ctx -> { + ServerPlayerEntity player = ctx.getSource().asPlayer(); + AllPackets.channel.send( + PacketDistributor.PLAYER.with(() -> player), + new SConfigureConfigPacket(SConfigureConfigPacket.Actions.configScreen.name(), StringArgumentType.getString(ctx, "path")) + ); + + return Command.SINGLE_SUCCESS; + }) + .then(Commands.literal("set") + .requires(cs -> cs.hasPermissionLevel(2)) + .then(Commands.argument("value", StringArgumentType.string()) + .executes(ctx -> { + String path = StringArgumentType.getString(ctx, "path"); + String value = StringArgumentType.getString(ctx, "value"); + + + ConfigHelper.ConfigPath configPath; + try { + configPath = ConfigHelper.ConfigPath.parse(path); + } catch (IllegalArgumentException e) { + ctx.getSource().sendErrorMessage(new StringTextComponent(e.getMessage())); + return 0; + } + + if (configPath.getType() == ModConfig.Type.CLIENT) { + ServerPlayerEntity player = ctx.getSource().asPlayer(); + AllPackets.channel.send( + PacketDistributor.PLAYER.with(() -> player), + new SConfigureConfigPacket("SET" + path, value) + ); + + return Command.SINGLE_SUCCESS; + } + + try { + ConfigHelper.setConfigValue(configPath, value); + ctx.getSource().sendFeedback(new StringTextComponent("Great Success!"), false); + return Command.SINGLE_SUCCESS; + } catch (ConfigHelper.InvalidValueException e) { + ctx.getSource().sendErrorMessage(new StringTextComponent("Config could not be set the the specified value!")); + return 0; + } catch (Exception e) { + ctx.getSource().sendErrorMessage(new StringTextComponent("Something went wrong while trying to set config value. Check the server logs for more information")); + Create.LOGGER.warn("Exception during server-side config value set:", e); + return 0; + } + }) + ) + ) + ); } } diff --git a/src/main/java/com/simibubi/create/foundation/command/SConfigureConfigPacket.java b/src/main/java/com/simibubi/create/foundation/command/SConfigureConfigPacket.java index d4e1a6115..55283402c 100644 --- a/src/main/java/com/simibubi/create/foundation/command/SConfigureConfigPacket.java +++ b/src/main/java/com/simibubi/create/foundation/command/SConfigureConfigPacket.java @@ -9,6 +9,8 @@ import com.simibubi.create.Create; import com.simibubi.create.content.contraptions.goggles.GoggleConfigScreen; import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.config.ui.BaseConfigScreen; +import com.simibubi.create.foundation.config.ui.ConfigHelper; +import com.simibubi.create.foundation.config.ui.SubMenuConfigScreen; import com.simibubi.create.foundation.gui.ScreenOpener; import com.simibubi.create.foundation.networking.SimplePacketBase; import com.simibubi.create.foundation.ponder.PonderRegistry; @@ -30,6 +32,7 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.common.ForgeConfig; import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.network.NetworkEvent; public class SConfigureConfigPacket extends SimplePacketBase { @@ -57,6 +60,11 @@ public class SConfigureConfigPacket extends SimplePacketBase { public void handle(Supplier ctx) { ctx.get() .enqueueWork(() -> DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { + if (option.startsWith("SET")) { + trySetConfig(option.substring(3), value); + return; + } + try { Actions.valueOf(option) .performAction(value); @@ -70,6 +78,36 @@ public class SConfigureConfigPacket extends SimplePacketBase { .setPacketHandled(true); } + private static void trySetConfig(String option, String value) { + ClientPlayerEntity player = Minecraft.getInstance().player; + if (player == null) + return; + + ConfigHelper.ConfigPath configPath; + try { + configPath = ConfigHelper.ConfigPath.parse(option); + } catch (IllegalArgumentException e) { + player.sendStatusMessage(new StringTextComponent(e.getMessage()), false); + return; + } + + if (configPath.getType() != ModConfig.Type.CLIENT) { + Create.LOGGER.warn("Received type-mismatched config packet on client"); + return; + } + + try { + ConfigHelper.setConfigValue(configPath, value); + player.sendStatusMessage(new StringTextComponent("Great Success!"), false); + } catch (ConfigHelper.InvalidValueException e) { + player.sendStatusMessage(new StringTextComponent("Config could not be set the the specified value!"), false); + } catch (Exception e) { + player.sendStatusMessage(new StringTextComponent("Something went wrong while trying to set config value. Check the client logs for more information"), false); + Create.LOGGER.warn("Exception during client-side config value set:", e); + } + + } + public enum Actions { configScreen(() -> Actions::configScreen), rainbowDebug(() -> Actions::rainbowDebug), @@ -95,7 +133,25 @@ public class SConfigureConfigPacket extends SimplePacketBase { @OnlyIn(Dist.CLIENT) private static void configScreen(String value) { - ScreenOpener.open(new BaseConfigScreen(null)); + if (value.equals("")) { + ScreenOpener.open(BaseConfigScreen.forCreate(null)); + return; + } + + ClientPlayerEntity player = Minecraft.getInstance().player; + ConfigHelper.ConfigPath configPath; + try { + configPath = ConfigHelper.ConfigPath.parse(value); + } catch (IllegalArgumentException e) { + player.sendStatusMessage(new StringTextComponent(e.getMessage()), false); + return; + } + + try { + ScreenOpener.open(SubMenuConfigScreen.find(configPath)); + } catch (Exception e) { + player.sendStatusMessage(new StringTextComponent("Unable to find the specified config"), false); + } } @OnlyIn(Dist.CLIENT) diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/BaseConfigScreen.java b/src/main/java/com/simibubi/create/foundation/config/ui/BaseConfigScreen.java index 915abcb38..6507c2820 100644 --- a/src/main/java/com/simibubi/create/foundation/config/ui/BaseConfigScreen.java +++ b/src/main/java/com/simibubi/create/foundation/config/ui/BaseConfigScreen.java @@ -1,6 +1,11 @@ package com.simibubi.create.foundation.config.ui; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.simibubi.create.Create; import com.simibubi.create.foundation.config.AllConfigs; +import com.simibubi.create.foundation.gui.DelegatedStencilElement; import com.simibubi.create.foundation.gui.ScreenOpener; import com.simibubi.create.foundation.gui.TextStencilElement; import com.simibubi.create.foundation.gui.Theme; @@ -11,49 +16,134 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screen.Screen; import net.minecraft.util.text.StringTextComponent; import net.minecraft.util.text.TextFormatting; +import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.fml.config.ModConfig; public class BaseConfigScreen extends ConfigScreen { + private static final DelegatedStencilElement.ElementRenderer DISABLED_RENDERER = (ms, width, height, alpha) -> UIRenderHelper.angledGradient(ms, 0, 0, height / 2, height, width, Theme.i(Theme.Key.BUTTON_DISABLE, true), Theme.i(Theme.Key.BUTTON_DISABLE, false) | 0x40_000000); + + public static BaseConfigScreen forCreate(Screen parent) { + return new BaseConfigScreen(parent) + .withTitles("Client Settings", "World Generation Settings", "Gameplay Settings") + .withSpecs(AllConfigs.CLIENT.specification, AllConfigs.COMMON.specification, AllConfigs.SERVER.specification); + } + BoxWidget clientConfigWidget; BoxWidget commonConfigWidget; BoxWidget serverConfigWidget; - public BaseConfigScreen(Screen parent) { + ForgeConfigSpec clientSpec; + ForgeConfigSpec commonSpec; + ForgeConfigSpec serverSpec; + String clientTile = "CLIENT CONFIG"; + String commonTile = "COMMON CONFIG"; + String serverTile = "SERVER CONFIG"; + String modID = Create.ID; + + /** + * If you are a Create Addon dev and want to make use of the same GUI + * for your mod's config, use this Constructor to create a entry point + * + * @param parent the previously opened screen + * @param modID the modID of your addon/mod + */ + public BaseConfigScreen(Screen parent, @Nonnull String modID) { + this(parent); + this.modID = modID; + } + + private BaseConfigScreen(Screen parent) { super(parent); } + /** + * If you have static references to your Configs or ConfigSpecs (like Create does in {@link AllConfigs}), + * please use {@link #withSpecs(ForgeConfigSpec, ForgeConfigSpec, ForgeConfigSpec)} instead + */ + public BaseConfigScreen searchForSpecsInModContainer() { + try { + clientSpec = ConfigHelper.findConfigSpecFor(ModConfig.Type.CLIENT, this.modID); + } catch (Exception e) { + Create.LOGGER.warn("Unable to find ClientConfigSpec for mod: " + this.modID); + } + + try { + commonSpec = ConfigHelper.findConfigSpecFor(ModConfig.Type.COMMON, this.modID); + } catch (Exception e) { + Create.LOGGER.warn("Unable to find CommonConfigSpec for mod: " + this.modID, e); + } + + try { + serverSpec = ConfigHelper.findConfigSpecFor(ModConfig.Type.SERVER, this.modID); + } catch (Exception e) { + Create.LOGGER.warn("Unable to find ServerConfigSpec for mod: " + this.modID, e); + } + + return this; + } + + public BaseConfigScreen withSpecs(@Nullable ForgeConfigSpec client, @Nullable ForgeConfigSpec common, @Nullable ForgeConfigSpec server) { + clientSpec = client; + commonSpec = common; + serverSpec = server; + return this; + } + + public BaseConfigScreen withTitles(@Nullable String client, @Nullable String common, @Nullable String server) { + if (client != null) + clientTile = client; + + if (common != null) + commonTile = common; + + if (server != null) + serverTile = server; + + return this; + } + @Override protected void init() { widgets.clear(); super.init(); - TextStencilElement text = new TextStencilElement(client.fontRenderer, new StringTextComponent("Client Settings").formatted(TextFormatting.BOLD)).centered(true, true); - widgets.add(clientConfigWidget = new BoxWidget(width / 2 - 100, height / 2 - 15 - 30, 200, 16) - .showingElement(text) - .withCallback(() -> ScreenOpener.open(new SubMenuConfigScreen(this, ModConfig.Type.CLIENT, AllConfigs.CLIENT.specification))) - ); - text.withElementRenderer(BoxWidget.gradientFactory.apply(clientConfigWidget)); + TextStencilElement clientText = new TextStencilElement(client.fontRenderer, new StringTextComponent(clientTile).formatted(TextFormatting.BOLD)).centered(true, true); + widgets.add(clientConfigWidget = new BoxWidget(width / 2 - 100, height / 2 - 15 - 30, 200, 16).showingElement(clientText)); - TextStencilElement text2 = new TextStencilElement(client.fontRenderer, new StringTextComponent("World Generation Settings").formatted(TextFormatting.BOLD)).centered(true, true); - widgets.add(commonConfigWidget = new BoxWidget(width / 2 - 100, height / 2 - 15, 200, 16) - .showingElement(text2) - .withCallback(() -> ScreenOpener.open(new SubMenuConfigScreen(this, ModConfig.Type.COMMON, AllConfigs.COMMON.specification))) - ); - text2.withElementRenderer(BoxWidget.gradientFactory.apply(commonConfigWidget)); + if (clientSpec != null) { + clientConfigWidget.withCallback(() -> ScreenOpener.open(new SubMenuConfigScreen(this, ModConfig.Type.CLIENT, clientSpec))); + clientText.withElementRenderer(BoxWidget.gradientFactory.apply(clientConfigWidget)); + } else { + clientConfigWidget.active = false; + clientConfigWidget.updateColorsFromState(); + clientText.withElementRenderer(DISABLED_RENDERER); + } - TextStencilElement text3 = new TextStencilElement(client.fontRenderer, new StringTextComponent("Gameplay Settings").formatted(TextFormatting.BOLD)).centered(true, true); - widgets.add(serverConfigWidget = new BoxWidget(width / 2 - 100, height / 2 - 15 + 30, 200, 16) - .showingElement(text3) - ); + TextStencilElement commonText = new TextStencilElement(client.fontRenderer, new StringTextComponent(commonTile).formatted(TextFormatting.BOLD)).centered(true, true); + widgets.add(commonConfigWidget = new BoxWidget(width / 2 - 100, height / 2 - 15, 200, 16).showingElement(commonText)); - if (Minecraft.getInstance().world != null) { - serverConfigWidget.withCallback(() -> ScreenOpener.open(new SubMenuConfigScreen(this, ModConfig.Type.SERVER, AllConfigs.SERVER.specification))); - text3.withElementRenderer(BoxWidget.gradientFactory.apply(serverConfigWidget)); + if (commonSpec != null) { + commonConfigWidget.withCallback(() -> ScreenOpener.open(new SubMenuConfigScreen(this, ModConfig.Type.COMMON, commonSpec))); + commonText.withElementRenderer(BoxWidget.gradientFactory.apply(commonConfigWidget)); + } else { + commonConfigWidget.active = false; + commonConfigWidget.updateColorsFromState(); + commonText.withElementRenderer(DISABLED_RENDERER); + } + + TextStencilElement serverText = new TextStencilElement(client.fontRenderer, new StringTextComponent(serverTile).formatted(TextFormatting.BOLD)).centered(true, true); + widgets.add(serverConfigWidget = new BoxWidget(width / 2 - 100, height / 2 - 15 + 30, 200, 16).showingElement(serverText)); + + if (serverSpec != null && Minecraft.getInstance().world != null) { + serverConfigWidget.withCallback(() -> ScreenOpener.open(new SubMenuConfigScreen(this, ModConfig.Type.SERVER, serverSpec))); + serverText.withElementRenderer(BoxWidget.gradientFactory.apply(serverConfigWidget)); } else { serverConfigWidget.active = false; serverConfigWidget.updateColorsFromState(); - text3.withElementRenderer((ms, width, height, alpha) -> UIRenderHelper.angledGradient(ms, 0, 0, height / 2, height, width, Theme.i(Theme.Key.BUTTON_DISABLE, true), Theme.i(Theme.Key.BUTTON_DISABLE, false) | 0x40_000000)); + serverText.withElementRenderer(DISABLED_RENDERER); } + + ConfigScreen.modID = this.modID; } } diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/CConfigureConfigPacket.java b/src/main/java/com/simibubi/create/foundation/config/ui/CConfigureConfigPacket.java index c1a475d29..b472b0a70 100644 --- a/src/main/java/com/simibubi/create/foundation/config/ui/CConfigureConfigPacket.java +++ b/src/main/java/com/simibubi/create/foundation/config/ui/CConfigureConfigPacket.java @@ -1,54 +1,65 @@ package com.simibubi.create.foundation.config.ui; +import java.util.Objects; import java.util.function.Supplier; -import com.simibubi.create.foundation.command.SConfigureConfigPacket; -import com.simibubi.create.foundation.config.AllConfigs; -import com.simibubi.create.foundation.networking.AllPackets; +import com.simibubi.create.Create; import com.simibubi.create.foundation.networking.SimplePacketBase; import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.network.PacketBuffer; import net.minecraftforge.common.ForgeConfigSpec; +import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.network.NetworkEvent; -import net.minecraftforge.fml.network.PacketDistributor; public class CConfigureConfigPacket extends SimplePacketBase { + private String modID; private String path; private String value; - public CConfigureConfigPacket(String path, T value) { + public CConfigureConfigPacket(String modID, String path, T value) { + this.modID = Objects.requireNonNull(modID); this.path = path; this.value = serialize(value); } public CConfigureConfigPacket(PacketBuffer buffer) { + this.modID = buffer.readString(32767); this.path = buffer.readString(32767); this.value = buffer.readString(32767); } @Override public void write(PacketBuffer buffer) { + buffer.writeString(modID); buffer.writeString(path); buffer.writeString(value); } @Override public void handle(Supplier context) { - ServerPlayerEntity sender = context.get().getSender(); - if (sender == null || !sender.hasPermissionLevel(2)) - return; + context.get().enqueueWork(() -> { + try { + ServerPlayerEntity sender = context.get().getSender(); + if (sender == null || !sender.hasPermissionLevel(2)) + return; - ForgeConfigSpec.ValueSpec valueSpec = AllConfigs.SERVER.specification.getRaw(path); - ForgeConfigSpec.ConfigValue configValue = AllConfigs.SERVER.specification.getValues().get(path); + ForgeConfigSpec spec = ConfigHelper.findConfigSpecFor(ModConfig.Type.SERVER, modID); + ForgeConfigSpec.ValueSpec valueSpec = spec.getRaw(path); + ForgeConfigSpec.ConfigValue configValue = spec.getValues().get(path); - T v = (T) deserialize(configValue.get(), value); - if (!valueSpec.test(v)) - return; + T v = (T) deserialize(configValue.get(), value); + if (!valueSpec.test(v)) + return; - configValue.set(v); + configValue.set(v); + } catch (Exception e) { + Create.LOGGER.warn("Unable to handle ConfigureConfig Packet. ", e); + } + }); + context.get().setPacketHandled(true); } public String serialize(T value) { @@ -66,7 +77,7 @@ public class CConfigureConfigPacket extends SimplePacketBase { throw new IllegalArgumentException("unknown type " + value + ": " + value.getClass().getSimpleName()); } - public Object deserialize(Object type, String sValue) { + public static Object deserialize(Object type, String sValue) { if (type instanceof Boolean) return Boolean.parseBoolean(sValue); if (type instanceof Enum) diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/ConfigHelper.java b/src/main/java/com/simibubi/create/foundation/config/ui/ConfigHelper.java new file mode 100644 index 000000000..57a52417f --- /dev/null +++ b/src/main/java/com/simibubi/create/foundation/config/ui/ConfigHelper.java @@ -0,0 +1,142 @@ +package com.simibubi.create.foundation.config.ui; + +import java.util.Arrays; +import java.util.EnumMap; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nonnull; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.simibubi.create.Create; +import com.simibubi.create.foundation.config.AllConfigs; + +import net.minecraftforge.common.ForgeConfigSpec; +import net.minecraftforge.fml.ModContainer; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; +import net.minecraftforge.fml.config.ModConfig; + +public class ConfigHelper { + + private static final LoadingCache> configCache = CacheBuilder.newBuilder().expireAfterAccess(15, TimeUnit.SECONDS).build( + new CacheLoader>() { + @Override + public EnumMap load(@Nonnull String key) { + return findModConfigsUncached(key); + } + } + ); + + private static EnumMap findModConfigsUncached(String modID) { + ModContainer modContainer = ModList.get().getModContainerById(modID).orElseThrow(() -> new IllegalArgumentException("Unable to find ModContainer for id: " + modID)); + EnumMap configs = ObfuscationReflectionHelper.getPrivateValue(ModContainer.class, modContainer, "configs"); + return Objects.requireNonNull(configs); + } + + public static ForgeConfigSpec findConfigSpecFor(ModConfig.Type type, String modID) { + if (!modID.equals(Create.ID)) + return configCache.getUnchecked(modID).get(type).getSpec(); + + switch (type) { + case COMMON: + return AllConfigs.COMMON.specification; + case CLIENT: + return AllConfigs.CLIENT.specification; + case SERVER: + return AllConfigs.SERVER.specification; + } + + return null; + } + + //Directly set a value + public static void setConfigValue(ConfigPath path, String value) throws InvalidValueException { + ForgeConfigSpec spec = findConfigSpecFor(path.getType(), path.getModID()); + List pathList = Arrays.asList(path.getPath()); + ForgeConfigSpec.ValueSpec valueSpec = spec.getRaw(pathList); + ForgeConfigSpec.ConfigValue configValue = spec.getValues().get(pathList); + T v = (T) CConfigureConfigPacket.deserialize(configValue.get(), value); + if (!valueSpec.test(v)) + throw new InvalidValueException(); + + configValue.set(v); + } + + //Add a value to the current UI's changes list + public static void setValue(String path, ForgeConfigSpec.ConfigValue configValue, T value) { + if (value.equals(configValue.get())) { + ConfigScreen.changes.remove(path); + } else { + ConfigScreen.changes.put(path, value); + } + } + + //Get a value from the current UI's changes list or the config value, if its unchanged + public static T getValue(String path, ForgeConfigSpec.ConfigValue configValue) { + //noinspection unchecked + return (T) ConfigScreen.changes.getOrDefault(path, configValue.get()); + } + + public static class ConfigPath { + private String modID = Create.ID; + private ModConfig.Type type = ModConfig.Type.CLIENT; + private String[] path; + + public static ConfigPath parse(String string) { + ConfigPath cp = new ConfigPath(); + String p = string; + int index = string.indexOf(":"); + if (index >= 0) { + p = string.substring(index + 1); + if (index >= 1) { + cp.modID = string.substring(0, index); + } + } + String[] split = p.split("\\."); + try { + cp.type = ModConfig.Type.valueOf(split[0].toUpperCase(Locale.ROOT)); + } catch (Exception e) { + throw new IllegalArgumentException("path must start with either 'client.', 'common.' or 'server.'"); + } + + cp.path = new String[split.length - 1]; + System.arraycopy(split, 1, cp.path, 0, cp.path.length); + + return cp; + } + + public ConfigPath setID(String modID) { + this.modID = modID; + return this; + } + + public ConfigPath setType(ModConfig.Type type) { + this.type = type; + return this; + } + + public ConfigPath setPath(String[] path) { + this.path = path; + return this; + } + + public String getModID() { + return modID; + } + + public ModConfig.Type getType() { + return type; + } + + public String[] getPath() { + return path; + } + } + + public static class InvalidValueException extends Exception {} +} diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/ConfigScreen.java b/src/main/java/com/simibubi/create/foundation/config/ui/ConfigScreen.java index 98086a786..ecdcad3eb 100644 --- a/src/main/java/com/simibubi/create/foundation/config/ui/ConfigScreen.java +++ b/src/main/java/com/simibubi/create/foundation/config/ui/ConfigScreen.java @@ -41,6 +41,7 @@ public abstract class ConfigScreen extends AbstractSimiScreen { public static final PhysicalFloat cogSpin = PhysicalFloat.create().withDrag(0.3).addForce(new Force.Static(.2f)); public static final BlockState cogwheelState = AllBlocks.LARGE_COGWHEEL.getDefaultState().with(CogWheelBlock.AXIS, Direction.Axis.Y); public static final Map changes = new HashMap<>(); + public static String modID = null; protected final Screen parent; public ConfigScreen(Screen parent) { diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/OpenConfigButton.java b/src/main/java/com/simibubi/create/foundation/config/ui/OpenConfigButton.java index 07dd44942..52c571796 100644 --- a/src/main/java/com/simibubi/create/foundation/config/ui/OpenConfigButton.java +++ b/src/main/java/com/simibubi/create/foundation/config/ui/OpenConfigButton.java @@ -1,9 +1,11 @@ package com.simibubi.create.foundation.config.ui; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + import com.mojang.blaze3d.matrix.MatrixStack; - import com.simibubi.create.AllItems; - import com.simibubi.create.foundation.config.AllConfigs; import net.minecraft.client.Minecraft; @@ -19,10 +21,6 @@ import net.minecraftforge.client.event.GuiScreenEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod.EventBusSubscriber; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - public class OpenConfigButton extends Button { public static ItemStack icon = AllItems.GOGGLES.asStack(); @@ -38,7 +36,7 @@ public class OpenConfigButton extends Button { } public static void click(Button b) { - Minecraft.getInstance().displayGuiScreen(new BaseConfigScreen(Minecraft.getInstance().currentScreen)); + Minecraft.getInstance().displayGuiScreen(BaseConfigScreen.forCreate(Minecraft.getInstance().currentScreen)); } public static class SingleMenuRow { diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/SubMenuConfigScreen.java b/src/main/java/com/simibubi/create/foundation/config/ui/SubMenuConfigScreen.java index e27502281..a8adcdc1f 100644 --- a/src/main/java/com/simibubi/create/foundation/config/ui/SubMenuConfigScreen.java +++ b/src/main/java/com/simibubi/create/foundation/config/ui/SubMenuConfigScreen.java @@ -1,6 +1,9 @@ package com.simibubi.create.foundation.config.ui; import java.awt.Color; +import java.util.List; +import java.util.Locale; +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -9,6 +12,7 @@ import org.lwjgl.glfw.GLFW; import com.electronwill.nightconfig.core.AbstractConfig; import com.electronwill.nightconfig.core.UnmodifiableConfig; +import com.google.common.collect.Lists; import com.mojang.blaze3d.matrix.MatrixStack; import com.simibubi.create.foundation.config.ui.entries.BooleanEntry; import com.simibubi.create.foundation.config.ui.entries.EnumEntry; @@ -50,6 +54,37 @@ public class SubMenuConfigScreen extends ConfigScreen { protected int listWidth; protected String title; + public static SubMenuConfigScreen find(ConfigHelper.ConfigPath path) { + ForgeConfigSpec spec = ConfigHelper.findConfigSpecFor(path.getType(), path.getModID()); + UnmodifiableConfig values = spec.getValues(); + BaseConfigScreen base = new BaseConfigScreen(null, path.getModID()).searchForSpecsInModContainer(); + SubMenuConfigScreen screen = new SubMenuConfigScreen(base, "root", path.getType(), spec, values); + List remainingPath = Lists.newArrayList(path.getPath()); + + path: while (!remainingPath.isEmpty()) { + String next = remainingPath.remove(0); + for (Map.Entry entry : values.valueMap().entrySet()) { + String key = entry.getKey(); + Object obj = entry.getValue(); + if (!key.equalsIgnoreCase(next)) + continue; + + if (!(obj instanceof AbstractConfig)) { + //highlight entry + continue; + } + + values = (UnmodifiableConfig) obj; + screen = new SubMenuConfigScreen(screen, toHumanReadable(key), path.getType(), spec, values); + continue path; + } + + break; + } + + ConfigScreen.modID = path.getModID(); + return screen; + } public SubMenuConfigScreen(Screen parent, String title, ModConfig.Type type, ForgeConfigSpec configSpec, UnmodifiableConfig configGroup) { super(parent); @@ -81,7 +116,7 @@ public class SubMenuConfigScreen extends ConfigScreen { ForgeConfigSpec.ConfigValue configValue = values.get(path); configValue.set(value); if (type == ModConfig.Type.SERVER) { - AllPackets.channel.sendToServer(new CConfigureConfigPacket<>(path, value)); + AllPackets.channel.sendToServer(new CConfigureConfigPacket<>(ConfigScreen.modID, path, value)); } }); clearChanges(); @@ -92,11 +127,10 @@ public class SubMenuConfigScreen extends ConfigScreen { if (obj instanceof AbstractConfig) { resetConfig((UnmodifiableConfig) obj); } else if (obj instanceof ForgeConfigSpec.ConfigValue) { - ForgeConfigSpec.ConfigValue configValue = (ForgeConfigSpec.ConfigValue) obj; - ForgeConfigSpec.ValueSpec valueSpec = spec.getRaw(configValue.getPath()); + ForgeConfigSpec.ConfigValue configValue = (ForgeConfigSpec.ConfigValue) obj; + ForgeConfigSpec.ValueSpec valueSpec = spec.getRaw((List) configValue.getPath()); - if (!configValue.get().equals(valueSpec.getDefault())) - changes.put(String.join(".", configValue.getPath()), valueSpec.getDefault()); + ConfigHelper.setValue(String.join(".", configValue.getPath()), configValue, valueSpec.getDefault()); } }); @@ -265,7 +299,7 @@ public class SubMenuConfigScreen extends ConfigScreen { super.renderWindow(ms, mouseX, mouseY, partialTicks); int x = width/2; - drawCenteredString(ms, client.fontRenderer, "Editing config: " + type.toString() + "@" + title, x, 15, Theme.i(Theme.Key.TEXT)); + drawCenteredString(ms, client.fontRenderer, "Editing config: " + ConfigScreen.modID + ":" + type.toString().toLowerCase(Locale.ROOT) + "@" + title, x, 15, Theme.i(Theme.Key.TEXT)); list.render(ms, mouseX, mouseY, partialTicks); } diff --git a/src/main/java/com/simibubi/create/foundation/config/ui/entries/ValueEntry.java b/src/main/java/com/simibubi/create/foundation/config/ui/entries/ValueEntry.java index 6f6f89093..48d1d86a0 100644 --- a/src/main/java/com/simibubi/create/foundation/config/ui/entries/ValueEntry.java +++ b/src/main/java/com/simibubi/create/foundation/config/ui/entries/ValueEntry.java @@ -11,6 +11,7 @@ import javax.annotation.Nonnull; import org.apache.commons.lang3.ArrayUtils; import com.mojang.blaze3d.matrix.MatrixStack; +import com.simibubi.create.foundation.config.ui.ConfigHelper; import com.simibubi.create.foundation.config.ui.ConfigScreen; import com.simibubi.create.foundation.config.ui.ConfigScreenList; import com.simibubi.create.foundation.gui.AllIcons; @@ -115,20 +116,13 @@ public class ValueEntry extends ConfigScreenList.LabeledEntry { } public void setValue(@Nonnull T value) { - if (value.equals(this.value.get())) { - ConfigScreen.changes.remove(path); - onValueChange(value); - return; - } - - ConfigScreen.changes.put(path, value); + ConfigHelper.setValue(path, this.value, value); onValueChange(value); } @Nonnull public T getValue() { - //noinspection unchecked - return (T) ConfigScreen.changes.getOrDefault(path, this.value.get()); + return ConfigHelper.getValue(path, this.value); } protected boolean isCurrentValueChanged() { diff --git a/src/main/java/com/simibubi/create/foundation/gui/ConfirmationScreen.java b/src/main/java/com/simibubi/create/foundation/gui/ConfirmationScreen.java index e7b5b48cf..b0cfe2ffa 100644 --- a/src/main/java/com/simibubi/create/foundation/gui/ConfirmationScreen.java +++ b/src/main/java/com/simibubi/create/foundation/gui/ConfirmationScreen.java @@ -99,6 +99,14 @@ public class ConfirmationScreen extends AbstractSimiScreen { textHeight = text.size() * (client.fontRenderer.FONT_HEIGHT + 1) + 4; textWidth = 300; + if (centered) { + x = width/2 - textWidth/2 - 2; + y = height/2 - textHeight/2 - 16; + } else { + x = Math.max(0, x - textWidth / 2); + y = Math.max(0, y -= textHeight); + } + if (x + textWidth > width) { x = width - textWidth; } @@ -107,11 +115,6 @@ public class ConfirmationScreen extends AbstractSimiScreen { y = height - textHeight - 30; } - if (centered) { - x = width/2 - textWidth/2 - 2; - y = height/2 - textHeight/2 - 16; - } - TextStencilElement confirmText = new TextStencilElement(client.fontRenderer, "Confirm").centered(true, true); confirm = new BoxWidget(x + 4, y + textHeight + 2 , textWidth/2 - 10, 20) .withCallback(() -> accept(true)); diff --git a/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementOffset.java b/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementOffset.java index ee7fa842e..17fd6505c 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementOffset.java +++ b/src/main/java/com/simibubi/create/foundation/utility/placement/PlacementOffset.java @@ -102,12 +102,15 @@ public class PlacementOffset { return world.getBlockState(new BlockPos(pos)).getMaterial().isReplaceable(); } - + public ActionResultType placeInWorld(World world, BlockItem blockItem, PlayerEntity player, Hand hand, BlockRayTraceResult ray) { if (!isReplaceable(world)) return ActionResultType.PASS; + if (world.isRemote) + return ActionResultType.SUCCESS; + ItemUseContext context = new ItemUseContext(player, hand, ray); BlockPos newPos = new BlockPos(pos); @@ -135,9 +138,6 @@ public class PlacementOffset { player.addStat(Stats.ITEM_USED.get(blockItem)); - if (world.isRemote) - return ActionResultType.SUCCESS; - if (player instanceof ServerPlayerEntity) CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayerEntity) player, newPos, context.getItem());