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
This commit is contained in:
zelophed 2021-05-28 14:42:27 +02:00
parent 2e37807e7f
commit 975d898ac6
12 changed files with 468 additions and 76 deletions

View file

@ -339,8 +339,8 @@ public class ClientEvents {
} }
public static void loadCompleted(FMLLoadCompleteEvent event) { public static void loadCompleted(FMLLoadCompleteEvent event) {
ModContainer createContainer = ModList.get().getModContainerById("create").orElseThrow(() -> new IllegalStateException("Create Mod Container missing after loadCompleted")); ModContainer createContainer = ModList.get().getModContainerById(Create.ID).orElseThrow(() -> new IllegalStateException("Create Mod Container missing after loadCompleted"));
createContainer.registerExtensionPoint(ExtensionPoint.CONFIGGUIFACTORY, () -> (mc, previousScreen) -> new BaseConfigScreen(previousScreen)); createContainer.registerExtensionPoint(ExtensionPoint.CONFIGGUIFACTORY, () -> (mc, previousScreen) -> BaseConfigScreen.forCreate(previousScreen));
} }
} }

View file

@ -1,14 +1,25 @@
package com.simibubi.create.foundation.command; 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.CommandSource;
import net.minecraft.command.Commands; import net.minecraft.command.Commands;
import net.minecraft.entity.player.ServerPlayerEntity; 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 net.minecraftforge.fml.network.PacketDistributor;
import com.mojang.brigadier.Command; /**
import com.mojang.brigadier.builder.ArgumentBuilder; * Examples:
import com.simibubi.create.foundation.networking.AllPackets; * /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 class ConfigCommand {
public static ArgumentBuilder<CommandSource, ?> register() { public static ArgumentBuilder<CommandSource, ?> register() {
@ -21,7 +32,59 @@ public class ConfigCommand {
); );
return Command.SINGLE_SUCCESS; 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;
}
})
)
)
);
} }
} }

View file

@ -9,6 +9,8 @@ import com.simibubi.create.Create;
import com.simibubi.create.content.contraptions.goggles.GoggleConfigScreen; import com.simibubi.create.content.contraptions.goggles.GoggleConfigScreen;
import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.config.ui.BaseConfigScreen; 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.gui.ScreenOpener;
import com.simibubi.create.foundation.networking.SimplePacketBase; import com.simibubi.create.foundation.networking.SimplePacketBase;
import com.simibubi.create.foundation.ponder.PonderRegistry; 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.api.distmarker.OnlyIn;
import net.minecraftforge.common.ForgeConfig; import net.minecraftforge.common.ForgeConfig;
import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.network.NetworkEvent; import net.minecraftforge.fml.network.NetworkEvent;
public class SConfigureConfigPacket extends SimplePacketBase { public class SConfigureConfigPacket extends SimplePacketBase {
@ -57,6 +60,11 @@ public class SConfigureConfigPacket extends SimplePacketBase {
public void handle(Supplier<NetworkEvent.Context> ctx) { public void handle(Supplier<NetworkEvent.Context> ctx) {
ctx.get() ctx.get()
.enqueueWork(() -> DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { .enqueueWork(() -> DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> {
if (option.startsWith("SET")) {
trySetConfig(option.substring(3), value);
return;
}
try { try {
Actions.valueOf(option) Actions.valueOf(option)
.performAction(value); .performAction(value);
@ -70,6 +78,36 @@ public class SConfigureConfigPacket extends SimplePacketBase {
.setPacketHandled(true); .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 { public enum Actions {
configScreen(() -> Actions::configScreen), configScreen(() -> Actions::configScreen),
rainbowDebug(() -> Actions::rainbowDebug), rainbowDebug(() -> Actions::rainbowDebug),
@ -95,7 +133,25 @@ public class SConfigureConfigPacket extends SimplePacketBase {
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
private static void configScreen(String value) { 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) @OnlyIn(Dist.CLIENT)

View file

@ -1,6 +1,11 @@
package com.simibubi.create.foundation.config.ui; 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.config.AllConfigs;
import com.simibubi.create.foundation.gui.DelegatedStencilElement;
import com.simibubi.create.foundation.gui.ScreenOpener; import com.simibubi.create.foundation.gui.ScreenOpener;
import com.simibubi.create.foundation.gui.TextStencilElement; import com.simibubi.create.foundation.gui.TextStencilElement;
import com.simibubi.create.foundation.gui.Theme; 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.client.gui.screen.Screen;
import net.minecraft.util.text.StringTextComponent; import net.minecraft.util.text.StringTextComponent;
import net.minecraft.util.text.TextFormatting; import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.config.ModConfig;
public class BaseConfigScreen extends ConfigScreen { 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 clientConfigWidget;
BoxWidget commonConfigWidget; BoxWidget commonConfigWidget;
BoxWidget serverConfigWidget; 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); 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 @Override
protected void init() { protected void init() {
widgets.clear(); widgets.clear();
super.init(); super.init();
TextStencilElement text = new TextStencilElement(client.fontRenderer, new StringTextComponent("Client Settings").formatted(TextFormatting.BOLD)).centered(true, true); 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) widgets.add(clientConfigWidget = new BoxWidget(width / 2 - 100, height / 2 - 15 - 30, 200, 16).showingElement(clientText));
.showingElement(text)
.withCallback(() -> ScreenOpener.open(new SubMenuConfigScreen(this, ModConfig.Type.CLIENT, AllConfigs.CLIENT.specification)))
);
text.withElementRenderer(BoxWidget.gradientFactory.apply(clientConfigWidget));
TextStencilElement text2 = new TextStencilElement(client.fontRenderer, new StringTextComponent("World Generation Settings").formatted(TextFormatting.BOLD)).centered(true, true); if (clientSpec != null) {
widgets.add(commonConfigWidget = new BoxWidget(width / 2 - 100, height / 2 - 15, 200, 16) clientConfigWidget.withCallback(() -> ScreenOpener.open(new SubMenuConfigScreen(this, ModConfig.Type.CLIENT, clientSpec)));
.showingElement(text2) clientText.withElementRenderer(BoxWidget.gradientFactory.apply(clientConfigWidget));
.withCallback(() -> ScreenOpener.open(new SubMenuConfigScreen(this, ModConfig.Type.COMMON, AllConfigs.COMMON.specification))) } else {
); clientConfigWidget.active = false;
text2.withElementRenderer(BoxWidget.gradientFactory.apply(commonConfigWidget)); clientConfigWidget.updateColorsFromState();
clientText.withElementRenderer(DISABLED_RENDERER);
}
TextStencilElement text3 = new TextStencilElement(client.fontRenderer, new StringTextComponent("Gameplay Settings").formatted(TextFormatting.BOLD)).centered(true, true); TextStencilElement commonText = new TextStencilElement(client.fontRenderer, new StringTextComponent(commonTile).formatted(TextFormatting.BOLD)).centered(true, true);
widgets.add(serverConfigWidget = new BoxWidget(width / 2 - 100, height / 2 - 15 + 30, 200, 16) widgets.add(commonConfigWidget = new BoxWidget(width / 2 - 100, height / 2 - 15, 200, 16).showingElement(commonText));
.showingElement(text3)
);
if (Minecraft.getInstance().world != null) { if (commonSpec != null) {
serverConfigWidget.withCallback(() -> ScreenOpener.open(new SubMenuConfigScreen(this, ModConfig.Type.SERVER, AllConfigs.SERVER.specification))); commonConfigWidget.withCallback(() -> ScreenOpener.open(new SubMenuConfigScreen(this, ModConfig.Type.COMMON, commonSpec)));
text3.withElementRenderer(BoxWidget.gradientFactory.apply(serverConfigWidget)); 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 { } else {
serverConfigWidget.active = false; serverConfigWidget.active = false;
serverConfigWidget.updateColorsFromState(); 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;
} }
} }

View file

@ -1,54 +1,65 @@
package com.simibubi.create.foundation.config.ui; package com.simibubi.create.foundation.config.ui;
import java.util.Objects;
import java.util.function.Supplier; import java.util.function.Supplier;
import com.simibubi.create.foundation.command.SConfigureConfigPacket; import com.simibubi.create.Create;
import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.networking.AllPackets;
import com.simibubi.create.foundation.networking.SimplePacketBase; import com.simibubi.create.foundation.networking.SimplePacketBase;
import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.network.PacketBuffer; import net.minecraft.network.PacketBuffer;
import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.network.NetworkEvent; import net.minecraftforge.fml.network.NetworkEvent;
import net.minecraftforge.fml.network.PacketDistributor;
public class CConfigureConfigPacket<T> extends SimplePacketBase { public class CConfigureConfigPacket<T> extends SimplePacketBase {
private String modID;
private String path; private String path;
private String value; 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.path = path;
this.value = serialize(value); this.value = serialize(value);
} }
public CConfigureConfigPacket(PacketBuffer buffer) { public CConfigureConfigPacket(PacketBuffer buffer) {
this.modID = buffer.readString(32767);
this.path = buffer.readString(32767); this.path = buffer.readString(32767);
this.value = buffer.readString(32767); this.value = buffer.readString(32767);
} }
@Override @Override
public void write(PacketBuffer buffer) { public void write(PacketBuffer buffer) {
buffer.writeString(modID);
buffer.writeString(path); buffer.writeString(path);
buffer.writeString(value); buffer.writeString(value);
} }
@Override @Override
public void handle(Supplier<NetworkEvent.Context> context) { public void handle(Supplier<NetworkEvent.Context> context) {
ServerPlayerEntity sender = context.get().getSender(); context.get().enqueueWork(() -> {
if (sender == null || !sender.hasPermissionLevel(2)) try {
return; ServerPlayerEntity sender = context.get().getSender();
if (sender == null || !sender.hasPermissionLevel(2))
return;
ForgeConfigSpec.ValueSpec valueSpec = AllConfigs.SERVER.specification.getRaw(path); ForgeConfigSpec spec = ConfigHelper.findConfigSpecFor(ModConfig.Type.SERVER, modID);
ForgeConfigSpec.ConfigValue<T> configValue = AllConfigs.SERVER.specification.getValues().get(path); ForgeConfigSpec.ValueSpec valueSpec = spec.getRaw(path);
ForgeConfigSpec.ConfigValue<T> configValue = spec.getValues().get(path);
T v = (T) deserialize(configValue.get(), value); T v = (T) deserialize(configValue.get(), value);
if (!valueSpec.test(v)) if (!valueSpec.test(v))
return; 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) { public String serialize(T value) {
@ -66,7 +77,7 @@ public class CConfigureConfigPacket<T> extends SimplePacketBase {
throw new IllegalArgumentException("unknown type " + value + ": " + value.getClass().getSimpleName()); 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) if (type instanceof Boolean)
return Boolean.parseBoolean(sValue); return Boolean.parseBoolean(sValue);
if (type instanceof Enum<?>) if (type instanceof Enum<?>)

View file

@ -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<String, EnumMap<ModConfig.Type, ModConfig>> configCache = CacheBuilder.newBuilder().expireAfterAccess(15, TimeUnit.SECONDS).build(
new CacheLoader<String, EnumMap<ModConfig.Type, ModConfig>>() {
@Override
public EnumMap<ModConfig.Type, ModConfig> load(@Nonnull String key) {
return findModConfigsUncached(key);
}
}
);
private static EnumMap<ModConfig.Type, ModConfig> findModConfigsUncached(String modID) {
ModContainer modContainer = ModList.get().getModContainerById(modID).orElseThrow(() -> new IllegalArgumentException("Unable to find ModContainer for id: " + modID));
EnumMap<ModConfig.Type, ModConfig> 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 <T> void setConfigValue(ConfigPath path, String value) throws InvalidValueException {
ForgeConfigSpec spec = findConfigSpecFor(path.getType(), path.getModID());
List<String> pathList = Arrays.asList(path.getPath());
ForgeConfigSpec.ValueSpec valueSpec = spec.getRaw(pathList);
ForgeConfigSpec.ConfigValue<T> 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 <T> void setValue(String path, ForgeConfigSpec.ConfigValue<T> 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> T getValue(String path, ForgeConfigSpec.ConfigValue<T> 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 {}
}

View file

@ -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 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 BlockState cogwheelState = AllBlocks.LARGE_COGWHEEL.getDefaultState().with(CogWheelBlock.AXIS, Direction.Axis.Y);
public static final Map<String, Object> changes = new HashMap<>(); public static final Map<String, Object> changes = new HashMap<>();
public static String modID = null;
protected final Screen parent; protected final Screen parent;
public ConfigScreen(Screen parent) { public ConfigScreen(Screen parent) {

View file

@ -1,9 +1,11 @@
package com.simibubi.create.foundation.config.ui; 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.mojang.blaze3d.matrix.MatrixStack;
import com.simibubi.create.AllItems; import com.simibubi.create.AllItems;
import com.simibubi.create.foundation.config.AllConfigs; import com.simibubi.create.foundation.config.AllConfigs;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@ -19,10 +21,6 @@ import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber; 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 class OpenConfigButton extends Button {
public static ItemStack icon = AllItems.GOGGLES.asStack(); public static ItemStack icon = AllItems.GOGGLES.asStack();
@ -38,7 +36,7 @@ public class OpenConfigButton extends Button {
} }
public static void click(Button b) { 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 { public static class SingleMenuRow {

View file

@ -1,6 +1,9 @@
package com.simibubi.create.foundation.config.ui; package com.simibubi.create.foundation.config.ui;
import java.awt.Color; import java.awt.Color;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -9,6 +12,7 @@ import org.lwjgl.glfw.GLFW;
import com.electronwill.nightconfig.core.AbstractConfig; import com.electronwill.nightconfig.core.AbstractConfig;
import com.electronwill.nightconfig.core.UnmodifiableConfig; import com.electronwill.nightconfig.core.UnmodifiableConfig;
import com.google.common.collect.Lists;
import com.mojang.blaze3d.matrix.MatrixStack; import com.mojang.blaze3d.matrix.MatrixStack;
import com.simibubi.create.foundation.config.ui.entries.BooleanEntry; import com.simibubi.create.foundation.config.ui.entries.BooleanEntry;
import com.simibubi.create.foundation.config.ui.entries.EnumEntry; import com.simibubi.create.foundation.config.ui.entries.EnumEntry;
@ -50,6 +54,37 @@ public class SubMenuConfigScreen extends ConfigScreen {
protected int listWidth; protected int listWidth;
protected String title; 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<String> remainingPath = Lists.newArrayList(path.getPath());
path: while (!remainingPath.isEmpty()) {
String next = remainingPath.remove(0);
for (Map.Entry<String, Object> 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) { public SubMenuConfigScreen(Screen parent, String title, ModConfig.Type type, ForgeConfigSpec configSpec, UnmodifiableConfig configGroup) {
super(parent); super(parent);
@ -81,7 +116,7 @@ public class SubMenuConfigScreen extends ConfigScreen {
ForgeConfigSpec.ConfigValue configValue = values.get(path); ForgeConfigSpec.ConfigValue configValue = values.get(path);
configValue.set(value); configValue.set(value);
if (type == ModConfig.Type.SERVER) { if (type == ModConfig.Type.SERVER) {
AllPackets.channel.sendToServer(new CConfigureConfigPacket<>(path, value)); AllPackets.channel.sendToServer(new CConfigureConfigPacket<>(ConfigScreen.modID, path, value));
} }
}); });
clearChanges(); clearChanges();
@ -92,11 +127,10 @@ public class SubMenuConfigScreen extends ConfigScreen {
if (obj instanceof AbstractConfig) { if (obj instanceof AbstractConfig) {
resetConfig((UnmodifiableConfig) obj); resetConfig((UnmodifiableConfig) obj);
} else if (obj instanceof ForgeConfigSpec.ConfigValue<?>) { } else if (obj instanceof ForgeConfigSpec.ConfigValue<?>) {
ForgeConfigSpec.ConfigValue<?> configValue = (ForgeConfigSpec.ConfigValue<?>) obj; ForgeConfigSpec.ConfigValue configValue = (ForgeConfigSpec.ConfigValue<?>) obj;
ForgeConfigSpec.ValueSpec valueSpec = spec.getRaw(configValue.getPath()); ForgeConfigSpec.ValueSpec valueSpec = spec.getRaw((List<String>) configValue.getPath());
if (!configValue.get().equals(valueSpec.getDefault())) ConfigHelper.setValue(String.join(".", configValue.getPath()), configValue, valueSpec.getDefault());
changes.put(String.join(".", configValue.getPath()), valueSpec.getDefault());
} }
}); });
@ -265,7 +299,7 @@ public class SubMenuConfigScreen extends ConfigScreen {
super.renderWindow(ms, mouseX, mouseY, partialTicks); super.renderWindow(ms, mouseX, mouseY, partialTicks);
int x = width/2; 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); list.render(ms, mouseX, mouseY, partialTicks);
} }

View file

@ -11,6 +11,7 @@ import javax.annotation.Nonnull;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import com.mojang.blaze3d.matrix.MatrixStack; 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.ConfigScreen;
import com.simibubi.create.foundation.config.ui.ConfigScreenList; import com.simibubi.create.foundation.config.ui.ConfigScreenList;
import com.simibubi.create.foundation.gui.AllIcons; import com.simibubi.create.foundation.gui.AllIcons;
@ -115,20 +116,13 @@ public class ValueEntry<T> extends ConfigScreenList.LabeledEntry {
} }
public void setValue(@Nonnull T value) { public void setValue(@Nonnull T value) {
if (value.equals(this.value.get())) { ConfigHelper.setValue(path, this.value, value);
ConfigScreen.changes.remove(path);
onValueChange(value);
return;
}
ConfigScreen.changes.put(path, value);
onValueChange(value); onValueChange(value);
} }
@Nonnull @Nonnull
public T getValue() { public T getValue() {
//noinspection unchecked return ConfigHelper.getValue(path, this.value);
return (T) ConfigScreen.changes.getOrDefault(path, this.value.get());
} }
protected boolean isCurrentValueChanged() { protected boolean isCurrentValueChanged() {

View file

@ -99,6 +99,14 @@ public class ConfirmationScreen extends AbstractSimiScreen {
textHeight = text.size() * (client.fontRenderer.FONT_HEIGHT + 1) + 4; textHeight = text.size() * (client.fontRenderer.FONT_HEIGHT + 1) + 4;
textWidth = 300; 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) { if (x + textWidth > width) {
x = width - textWidth; x = width - textWidth;
} }
@ -107,11 +115,6 @@ public class ConfirmationScreen extends AbstractSimiScreen {
y = height - textHeight - 30; 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); TextStencilElement confirmText = new TextStencilElement(client.fontRenderer, "Confirm").centered(true, true);
confirm = new BoxWidget(x + 4, y + textHeight + 2 , textWidth/2 - 10, 20) confirm = new BoxWidget(x + 4, y + textHeight + 2 , textWidth/2 - 10, 20)
.withCallback(() -> accept(true)); .withCallback(() -> accept(true));

View file

@ -108,6 +108,9 @@ public class PlacementOffset {
if (!isReplaceable(world)) if (!isReplaceable(world))
return ActionResultType.PASS; return ActionResultType.PASS;
if (world.isRemote)
return ActionResultType.SUCCESS;
ItemUseContext context = new ItemUseContext(player, hand, ray); ItemUseContext context = new ItemUseContext(player, hand, ray);
BlockPos newPos = new BlockPos(pos); BlockPos newPos = new BlockPos(pos);
@ -135,9 +138,6 @@ public class PlacementOffset {
player.addStat(Stats.ITEM_USED.get(blockItem)); player.addStat(Stats.ITEM_USED.get(blockItem));
if (world.isRemote)
return ActionResultType.SUCCESS;
if (player instanceof ServerPlayerEntity) if (player instanceof ServerPlayerEntity)
CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayerEntity) player, newPos, context.getItem()); CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayerEntity) player, newPos, context.getItem());