diff --git a/src/main/java/com/falsepattern/lib/config/Config.java b/src/main/java/com/falsepattern/lib/config/Config.java index 6ff3779..c892b83 100644 --- a/src/main/java/com/falsepattern/lib/config/Config.java +++ b/src/main/java/com/falsepattern/lib/config/Config.java @@ -33,9 +33,47 @@ import java.util.function.BiConsumer; /** * A modern configuration system to replace the old and obtuse forge config system. - *

- * Note that just annotating a configuration class with {@link Config} is not enough, you must also register it using + *
+ * Note that just annotating a configuration class with {@link Config} is not enough, you MUST also register it using * {@link ConfigurationManager#initialize(Class[])} or {@link ConfigurationManager#initialize(BiConsumer, Class[])}! + *
+ * For each config field, the following annotations are available: + *
+ *
+ * @Default* - you MUST add these annotations, they define the default value of the given config + * (not having a default is an error.) + *
+ * {@link Comment} - you SHOULD add this annotation to every config field and the config class itself, it will + * add a comment to the config file for users. + *
+ * {@link LangKey} - you MAY add this to provide localization support. + *
+ * {@link Ignore} - you MUST add this annotation to additional fields you don't want put in the config file + *
+ * {@link RangeInt}, {@link RangeDouble} - you SHOULD add this to Int/IntList, Double/DoubleList configs respectively + * to define upper/lower bounds for the values. + *
+ * {@link StringMaxLength} - you MAY add this to String/StringList configs if you know a definite maximum length + * for them. + *
+ * {@link Pattern} - you MAY add this to String/StringList configs to restrict the values using regex. + *
+ * {@link ListMaxLength} - you MAY add this to *List configs to limit their maximum length. + *
+ * {@link ListFixedLength} - you MAY add this to *List configs to lock them to a fixed length. + *
+ * {@link Name} - you SHOULD add this to config properties to give them user-friendly names in the config. SHOULD be + * "camelCase". + *
+ * {@link RequiresMcRestart}, {@link RequiresWorldRestart} - You MUST add these to configs if they require a game or + * world reload accordingly, to avoid broken state! + * You MUST NOT add these to configs that are meant to be changed by the user while actively playing. + *
+ * {@link Synchronize} - You MAY add this to the config class. + * Allows the config to be synchronized from server to client. + * Useful for keeping gameplay-specific state consistent. + *
+ * {@link NoSync} - You MUST add this to fields that you don't want to be synchronized by {@link Synchronize}. */ @StableAPI(since = "0.6.0") @Documented @@ -44,34 +82,82 @@ import java.util.function.BiConsumer; public @interface Config { /** * The mod id that this configuration is associated with. + * If {@link #customPath()} is not set, this is used for the config file name (.minecraft/config/modid.cfg) + * @see #customPath() */ @StableAPI.Expose String modid(); /** - * Root element category, defaults to "general". You must not specify an empty string. + * Root element category, defaults to "general". MUST NOT be an empty string. */ @StableAPI.Expose String category() default "general"; + /** + * Define category migrations here. + * Used to rename config categories without losing data. + * @see #pathMigrations() + */ + @StableAPI.Expose(since = "1.5.0") String[] categoryMigrations() default {}; + + /** + * A custom configuration file path. Relative to the .minecraft/config folder. + * The .cfg is automatically appended if no file extension is provided. + *
+ * Examples: + *
+ * customPath = "MyMod/magic" -> .minecraft/config/MyMod/magic.cfg + *
+ * customPath = "mymod.config" -> .minecraft/config/mymod.config + *
+ * @see #modid() + */ + @StableAPI.Expose(since = "1.5.0") String customPath() default ""; + + /** + * Define file migration paths here. + * Used to relocate config files from older versions of the mod to a new location. + *
+ * NOTE: This only moves the current category, so you can even use this to split up/combine files. If every + * category from an old config file has been migrated, the old config file is automatically deleted. + *
+ * Only the FIRST successful migration is executed, and no migration happens if the new config file already contains + * the category of this config. + * @see #customPath() + * @see #categoryMigrations() + */ + @StableAPI.Expose(since = "1.5.0") String[] pathMigrations() default {}; + + // region Common + + /** + * The description of the configuration or the category. Should be in the primary language of the mod. + * @see LangKey + */ + @StableAPI(since = "0.6.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.TYPE}) + @interface Comment { + @StableAPI.Expose String[] value(); + } + /** * The lang file key of this configuration. Used in config GUIs. + *
+ * {@link LangKey#value()} + ".tooltip" is used for the hover tooltips. If the localization is not + * present, defaults to {@link Comment#value()}. + *
+ * If left empty. it's autogenerated from the modid, category, and config name. (config.MODID.CATEGORY.NAME) + *
+ * Also applies to categories, where it gets autogenerated from the modid and category (config.MODID.CATEGORY) + * @see Comment */ @StableAPI(since = "0.6.0") @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) @interface LangKey { - @StableAPI.Expose String value(); - } - - /** - * The description of the configuration. - */ - @StableAPI(since = "0.6.0") - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - @interface Comment { - @StableAPI.Expose String[] value(); + @StableAPI.Expose String value() default ""; } /** @@ -84,194 +170,6 @@ public @interface Config { @Target(ElementType.FIELD) @interface Ignore {} - /** - * The default value for a boolean field. Not having a default is deprecated since 0.10, and will be strongly - * enforced in 0.11+! - */ - @StableAPI(since = "0.6.0") - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - @interface DefaultBoolean { - @StableAPI.Expose boolean value(); - } - - /** - * The range of possible values an int config can have. - */ - @StableAPI(since = "0.6.0") - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - @interface RangeInt { - @StableAPI.Expose int min() default Integer.MIN_VALUE; - - @StableAPI.Expose int max() default Integer.MAX_VALUE; - } - - /** - * The default value for an int field. Not having a default is deprecated since 0.10, and will be strongly - * enforced in 0.11+! - */ - @StableAPI(since = "0.6.0") - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - @interface DefaultInt { - @StableAPI.Expose int value(); - } - - /** - * The range of possible values a double config can have. - */ - @StableAPI(since = "0.10.0") - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - @interface RangeDouble { - @StableAPI.Expose double min() default -Double.MAX_VALUE; - - @StableAPI.Expose double max() default Double.MAX_VALUE; - } - - /** - * The default value for a double field. Not having a default is deprecated since 0.10, and will be strongly - * enforced in 0.11+! - */ - @StableAPI(since = "0.10.0") - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - @interface DefaultDouble { - @StableAPI.Expose double value(); - } - - /** - * The default value for a String field. Not having a default is deprecated since 0.10, and will be strongly - * enforced in 0.11+! - */ - @StableAPI(since = "0.6.0") - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - @interface DefaultString { - @StableAPI.Expose String value(); - } - - - /** - * This annotation limits the maximum number of characters present in a string configuration. - *

- * Note: If this annotation is not present, the maximum length will be implicitly set to 256 to avoid malicious - * synchronizations that would make clients run out of memory! - *

- * When used with a string list, this limit will apply to each element individually, not to the size of the list as a whole. - */ - @StableAPI(since = "0.10.0") - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - @interface StringMaxLength { - @StableAPI.Expose int value(); - } - - /** - * A regex pattern for restricting the allowed strings. - */ - @StableAPI(since = "0.6.0") - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - @interface Pattern { - @StableAPI.Expose String value(); - } - - /** - * The default value for an Enum field. Not having a default is deprecated since 0.10, and will be strongly - * enforced in 0.11+! - */ - @StableAPI(since = "0.6.0") - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - @interface DefaultEnum { - @StableAPI.Expose String value(); - } - - /** - * The default value for a string array field. Not having a default is deprecated since 0.10, and will be strongly - * enforced in 0.11+! - */ - @StableAPI(since = "0.6.0") - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - @interface DefaultStringList { - @StableAPI.Expose String[] value(); - } - - /** - * The default value for a double array field. Not having a default is deprecated since 0.10, and will be strongly - * enforced in 0.11+! - */ - @StableAPI(since = "0.10.0") - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - @interface DefaultDoubleList { - @StableAPI.Expose double[] value(); - } - - /** - * The default value for an int array field. Not having a default is deprecated since 0.10, and will be strongly - * enforced in 0.11+! - */ - @StableAPI(since = "0.10.0") - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - @interface DefaultIntList { - @StableAPI.Expose int[] value(); - } - - /** - * The default value for a boolean array field. Not having a default is deprecated since 0.10, and will be strongly - * enforced in 0.11+! - */ - @StableAPI(since = "0.10.0") - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - @interface DefaultBooleanList { - @StableAPI.Expose boolean[] value(); - } - - /** - * If this annotation is present, the list in the config will be forced to have exactly the amount of elements as - * the default value. - */ - @StableAPI(since = "0.10.0") - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - @interface ListFixedLength {} - - /** - * This annotation limits the maximum number of elements present in an array configuration. Only effective if - * {@link ListFixedLength} is NOT present. - *

- * Note: If this annotation is not present, the maximum length will be implicitly set to 256 to avoid malicious - * synchronizations that would make clients run out of memory! - */ - @StableAPI(since = "0.10.0") - @Documented - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - @interface ListMaxLength { - @StableAPI.Expose int value(); - } - - /** * The name of this config property in the config file. If not specified, the field's name will be used instead. */ @@ -281,6 +179,11 @@ public @interface Config { @Target(ElementType.FIELD) @interface Name { @StableAPI.Expose String value(); + + /** + * Use this to migrate from old config names. "" is resolved to the name of the field + */ + @StableAPI.Expose(since = "1.5.0") String[] migrations() default {}; } /** @@ -301,6 +204,10 @@ public @interface Config { @Target({ElementType.FIELD, ElementType.TYPE}) @interface RequiresWorldRestart {} + // endregion + + // region Sync + /** * Signals that this configuration class should be synchronized between the client and the server when * joining a multiplayer instance. @@ -322,4 +229,221 @@ public @interface Config { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface NoSync {} + + // endregion + + // region Boolean + + /** + * The default value for a boolean field. Not having a default is deprecated since 0.10, and will be strongly + * enforced in 0.11+! + */ + @StableAPI(since = "0.6.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface DefaultBoolean { + @StableAPI.Expose boolean value(); + } + + // endregion + + // region Int + + /** + * The default value for an int field. + * @see RangeInt + */ + @StableAPI(since = "0.6.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface DefaultInt { + @StableAPI.Expose int value(); + } + + /** + * The range of possible values an int config can have. + */ + @StableAPI(since = "0.6.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface RangeInt { + @StableAPI.Expose int min() default Integer.MIN_VALUE; + + @StableAPI.Expose int max() default Integer.MAX_VALUE; + } + + // endregion + + // region Double + + /** + * The default value for a double field. + * @see RangeDouble + */ + @StableAPI(since = "0.10.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface DefaultDouble { + @StableAPI.Expose double value(); + } + + /** + * The range of possible values a double config can have. + */ + @StableAPI(since = "0.10.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface RangeDouble { + @StableAPI.Expose double min() default -Double.MAX_VALUE; + + @StableAPI.Expose double max() default Double.MAX_VALUE; + } + + // endregion + + // region String + + /** + * The default value for a String field. + * @see StringMaxLength + * @see Pattern + */ + @StableAPI(since = "0.6.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface DefaultString { + @StableAPI.Expose String value(); + } + + /** + * This annotation limits the maximum number of characters present in a string configuration. + *

+ * When used with a string list, this limit will apply to each element individually, not to the size of the list as a whole. + */ + @StableAPI(since = "0.10.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface StringMaxLength { + @StableAPI.Expose int value(); + } + + /** + * A regex pattern for restricting the allowed strings. + */ + @StableAPI(since = "0.6.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface Pattern { + @StableAPI.Expose String value(); + } + + // endregion + + // region Enum + + /** + * The default value for an Enum field. + */ + @StableAPI(since = "0.6.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface DefaultEnum { + @StableAPI.Expose String value(); + } + + // endregion + + // region Lists + + /** + * The default value for a boolean array field. + * @see ListFixedLength + * @see ListMaxLength + */ + @StableAPI(since = "0.10.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface DefaultBooleanList { + @StableAPI.Expose boolean[] value(); + } + + /** + * The default value for an int array field. + * @see ListFixedLength + * @see ListMaxLength + * @see RangeInt + */ + @StableAPI(since = "0.10.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface DefaultIntList { + @StableAPI.Expose int[] value(); + } + + /** + * The default value for a double array field. + * @see ListFixedLength + * @see ListMaxLength + * @see RangeDouble + */ + @StableAPI(since = "0.10.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface DefaultDoubleList { + @StableAPI.Expose double[] value(); + } + + /** + * The default value for a string array field. + * @see ListFixedLength + * @see ListMaxLength + * @see StringMaxLength + * @see Pattern + */ + @StableAPI(since = "0.6.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface DefaultStringList { + @StableAPI.Expose String[] value(); + } + + /** + * If this annotation is present, the list in the config will be forced to have exactly the amount of elements as + * the default value. + * @see ListMaxLength + */ + @StableAPI(since = "0.10.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface ListFixedLength {} + + /** + * This annotation limits the maximum number of elements present in an array configuration. Only effective if + * {@link ListFixedLength} is NOT present. + */ + @StableAPI(since = "0.10.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface ListMaxLength { + @StableAPI.Expose int value(); + } + + // endregion + + } \ No newline at end of file diff --git a/src/main/java/com/falsepattern/lib/config/ConfigException.java b/src/main/java/com/falsepattern/lib/config/ConfigException.java index 216b010..0612777 100644 --- a/src/main/java/com/falsepattern/lib/config/ConfigException.java +++ b/src/main/java/com/falsepattern/lib/config/ConfigException.java @@ -35,6 +35,11 @@ public class ConfigException extends Exception { super(message); } + @StableAPI.Internal + public ConfigException(String message, Throwable cause) { + super(message, cause); + } + @StableAPI.Internal public ConfigException(Throwable cause) { super(cause); diff --git a/src/main/java/com/falsepattern/lib/config/ConfigurationManager.java b/src/main/java/com/falsepattern/lib/config/ConfigurationManager.java index 67a1b36..bfcaf41 100644 --- a/src/main/java/com/falsepattern/lib/config/ConfigurationManager.java +++ b/src/main/java/com/falsepattern/lib/config/ConfigurationManager.java @@ -30,6 +30,7 @@ import lombok.NoArgsConstructor; import lombok.SneakyThrows; import lombok.val; +import cpw.mods.fml.client.config.DummyConfigElement; import cpw.mods.fml.client.config.IConfigElement; import java.lang.reflect.Field; @@ -138,4 +139,20 @@ public class ConfigurationManager { return result; } } + /** + * @return The configuration elements, structured with DummyCategoryElement groups per config class + * @see #getConfigElementsMulti(Class[]) + */ + @SuppressWarnings("rawtypes") + @StableAPI.Expose(since = "1.5.0") + public static List getConfigElementsMultiStructured(Class... configClasses) throws ConfigException { + if (configClasses.length == 0) { + return Collections.emptyList(); + } + val result = new ArrayList(); + for (val configClass: configClasses) { + result.add(ConfigurationManagerImpl.getConfigCategoryElement(configClass)); + } + return result; + } } diff --git a/src/main/java/com/falsepattern/lib/config/SimpleGuiConfig.java b/src/main/java/com/falsepattern/lib/config/SimpleGuiConfig.java index 917eebe..107458c 100644 --- a/src/main/java/com/falsepattern/lib/config/SimpleGuiConfig.java +++ b/src/main/java/com/falsepattern/lib/config/SimpleGuiConfig.java @@ -40,14 +40,20 @@ public class SimpleGuiConfig extends GuiConfig { } @StableAPI.Expose(since = "0.10.0") - public SimpleGuiConfig(GuiScreen parent, String modID, String modName, Class... configClasses) + public SimpleGuiConfig(GuiScreen parent, String modID, String title, Class... configClasses) + throws ConfigException { + this(parent, modID, title, null, configClasses); + } + + @StableAPI.Expose(since = "1.5.0") + public SimpleGuiConfig(GuiScreen parent, String modID, String title, String titleLine2, Class... configClasses) throws ConfigException { super(parent, - ConfigurationManager.getConfigElementsMulti(configClasses), + ConfigurationManager.getConfigElementsMultiStructured(configClasses), modID, false, false, - modName + " Configuration", - I18n.format("falsepatternlib.gui.config.description")); + title, + titleLine2); } } diff --git a/src/main/java/com/falsepattern/lib/internal/config/LibraryConfig.java b/src/main/java/com/falsepattern/lib/internal/config/LibraryConfig.java index 0445d11..15d4791 100644 --- a/src/main/java/com/falsepattern/lib/internal/config/LibraryConfig.java +++ b/src/main/java/com/falsepattern/lib/internal/config/LibraryConfig.java @@ -27,7 +27,11 @@ import com.falsepattern.lib.config.Config; import com.falsepattern.lib.config.ConfigurationManager; import com.falsepattern.lib.internal.Tags; -@Config(modid = Tags.MODID) +@Config.Comment("Miscellaneous FalsePatternLib settings") +@Config.LangKey +@Config(modid = Tags.MODID, + category = "misc", + categoryMigrations = "general") public class LibraryConfig { static { ConfigurationManager.selfInit(); @@ -36,14 +40,19 @@ public class LibraryConfig { @Config.Comment({"Fixes the mod options menu in-game.", "By default, the mod options when in already in a game will show \"Test1, Test2, DISABLED\" in bright red.", "This replaces that interface with the one from the main menu."}) - @Config.LangKey("config.falsepatternlib.ingamemodoptionsfix") + @Config.LangKey @Config.DefaultBoolean(true) + @Config.Name(value = "inGameModOptionsFix", + migrations = "") public static boolean IN_GAME_MOD_OPTIONS_FIX; - @Config.Comment("How \"loud\" the config error logging should be.") - @Config.LangKey("config.falsepatternlib.configlogging") + @Config.Comment({"How the config error logging should be set up.", + "[None] disables all built-in config validation error logging"}) + @Config.LangKey @Config.DefaultEnum("Log") - public static ValidationLogging CONFIG_ERROR_LOUDNESS; + @Config.Name(value = "configErrorLogging", + migrations = "CONFIG_ERROR_LOUDNESS") + public static ValidationLogging CONFIG_ERROR_LOGGING; public enum ValidationLogging { @StableAPI.Expose(since = "__INTERNAL__") None, diff --git a/src/main/java/com/falsepattern/lib/internal/config/ToastConfig.java b/src/main/java/com/falsepattern/lib/internal/config/ToastConfig.java index 16fcea0..a0d2187 100644 --- a/src/main/java/com/falsepattern/lib/internal/config/ToastConfig.java +++ b/src/main/java/com/falsepattern/lib/internal/config/ToastConfig.java @@ -27,10 +27,8 @@ import com.falsepattern.lib.config.Config; import com.falsepattern.lib.config.ConfigurationManager; import com.falsepattern.lib.internal.Tags; -import cpw.mods.fml.relauncher.Side; -import cpw.mods.fml.relauncher.SideOnly; - -@SideOnly(Side.CLIENT) +@Config.Comment("Toast notification settings") +@Config.LangKey @Config(modid = Tags.MODID, category = "toasts") public class ToastConfig { @@ -38,23 +36,29 @@ public class ToastConfig { ConfigurationManager.selfInit(); } - @Config.Comment("The maximum amount of toasts to show on the screen") - @Config.LangKey("config.falsepatternlib.maxtoasts") + @Config.Comment("The maximum amount of toasts to show on the screen.") + @Config.LangKey @Config.DefaultInt(5) @Config.RangeInt(min = 1, max = 10) + @Config.Name(value = "maxVisible", + migrations = "") public static int MAX_VISIBLE; - @Config.Comment("The amount of empty space from the top of the screen in pixels") - @Config.LangKey("config.falsepatternlib.toastoffset") + @Config.Comment("The amount of empty space from the top of the screen in pixels.") + @Config.LangKey @Config.DefaultInt(0) @Config.RangeInt(min = 0, max = 10000) + @Config.Name(value = "yOffset", + migrations = "") public static int Y_OFFSET; - @Config.Comment("Which side of the screen should toasts show up on") - @Config.LangKey("config.falsepatternlib.toastalign") + @Config.Comment("Which side of the screen should toasts show up on.") + @Config.LangKey @Config.DefaultEnum("Right") + @Config.Name(value = "align", + migrations = "") public static Side ALIGN; public static boolean leftAlign() { diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/ConfigElementProxy.java b/src/main/java/com/falsepattern/lib/internal/impl/config/ConfigElementProxy.java index 6f42957..a6dede1 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/ConfigElementProxy.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/ConfigElementProxy.java @@ -35,10 +35,12 @@ import java.util.regex.Pattern; public class ConfigElementProxy implements IConfigElement { private final ConfigElement element; + private final String comment; private final Runnable syncCallback; - public ConfigElementProxy(Property prop, Runnable syncCallback) { + public ConfigElementProxy(Property prop, String comment, Runnable syncCallback) { this.element = new ConfigElement<>(prop); + this.comment = comment; this.syncCallback = syncCallback; } @@ -74,7 +76,7 @@ public class ConfigElementProxy implements IConfigElement { @Override public String getComment() { - return element.getComment(); + return comment; } @Override @@ -82,6 +84,7 @@ public class ConfigElementProxy implements IConfigElement { return element.getChildElements(); } + // Caller sensitive @Override public ConfigGuiType getType() { return element.getType(); diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/ConfigFieldParameters.java b/src/main/java/com/falsepattern/lib/internal/impl/config/ConfigFieldParameters.java new file mode 100644 index 0000000..828efef --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/ConfigFieldParameters.java @@ -0,0 +1,38 @@ +/* + * This file is part of FalsePatternLib. + * + * Copyright (C) 2022-2024 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * FalsePatternLib is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * FalsePatternLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FalsePatternLib. If not, see . + */ + +package com.falsepattern.lib.internal.impl.config; + +import com.github.bsideup.jabel.Desugar; + +import net.minecraftforge.common.config.Configuration; + +import java.lang.reflect.Field; + +@Desugar +public record ConfigFieldParameters( + Field field, + Configuration configuration, + String modid, + String category +) {} diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/ConfigurationManagerImpl.java b/src/main/java/com/falsepattern/lib/internal/impl/config/ConfigurationManagerImpl.java index 551ef5a..aff731a 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/ConfigurationManagerImpl.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/ConfigurationManagerImpl.java @@ -34,8 +34,11 @@ import com.google.common.collect.HashBiMap; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.val; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import net.minecraftforge.common.config.Configuration; +import cpw.mods.fml.client.config.DummyConfigElement; import cpw.mods.fml.client.config.IConfigElement; import cpw.mods.fml.client.event.ConfigChangedEvent; @@ -47,6 +50,7 @@ import java.io.DataOutput; import java.io.DataOutputStream; import java.io.IOException; import java.lang.reflect.Field; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; @@ -64,7 +68,7 @@ import java.util.function.BiConsumer; */ @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class ConfigurationManagerImpl { - private static final Map configs = new HashMap<>(); + private static final Map configs = new HashMap<>(); private static final Map>> configToClassMap = new HashMap<>(); private static final Map, ParsedConfiguration> parsedConfigMap = new HashMap<>(); private static final BiMap> serializedNames = HashBiMap.create(); @@ -81,9 +85,33 @@ public final class ConfigurationManagerImpl { } } - static Configuration getForgeConfig(String modid) { - return configs.computeIfAbsent(modid, (ignored) -> { - val c = new Configuration(configDir.resolve(modid + ".cfg").toFile()); + private static @Nullable Path resolveConfigFile(String pathSuffix, boolean create) throws ConfigException { + var thePath = configDir.resolve(pathSuffix); + val parent = thePath.getParent(); + if (!Files.exists(parent)) { + if (!create) { + return null; + } + try { + Files.createDirectories(parent); + } catch (IOException e) { + throw new ConfigException(String.format("Failed to create config parent directory at %s", parent), e); + } + } + val fileName = thePath.getFileName().toString(); + if (!fileName.contains(".")) { + thePath = parent.resolve(fileName + ".cfg"); + } + return thePath; + } + + static @NotNull Configuration getForgeConfig(String pathSuffix, boolean create) throws ConfigException { + val thePath = resolveConfigFile(pathSuffix, create); + if (thePath == null || !(create || Files.exists(thePath))) { + throw new ConfigException(String.format("Could not find config file at %s", pathSuffix)); + } + return configs.computeIfAbsent(thePath, (ignored) -> { + val c = new Configuration(thePath.toFile()); c.load(); return c; }); @@ -93,14 +121,14 @@ public final class ConfigurationManagerImpl { if (!parsedConfigMap.containsKey(configClass)) { throw new ConfigException("Class " + configClass.getName() + " is not a registered configuration!"); } - parsedConfigMap.get(configClass).load(); + parsedConfigMap.get(configClass).loadFile(); } public static void save(Class configClass) throws ConfigException { if (!parsedConfigMap.containsKey(configClass)) { throw new ConfigException("Class " + configClass.getName() + " is not a registered configuration!"); } - parsedConfigMap.get(configClass).save(); + parsedConfigMap.get(configClass).saveFile(); } public static boolean validateFields(BiConsumer, Field> invalidFieldHandler, Class configClass, boolean resetInvalid) @@ -235,6 +263,21 @@ public final class ConfigurationManagerImpl { initialized = true; } + public static DummyConfigElement.DummyCategoryElement getConfigCategoryElement(Class configClass) throws ConfigException { + if (!parsedConfigMap.containsKey(configClass)) { + throw new ConfigException("Class " + configClass.getName() + " is not a registered configuration!"); + } + val config = parsedConfigMap.get(configClass); + val entry = new DummyConfigElement.DummyCategoryElement<>( + config.category, + config.langKey, + config.getConfigElements() + ); + entry.setRequiresWorldRestart(config.requiresWorldRestart()); + entry.setRequiresMcRestart(config.requiresMcRestart()); + return entry; + } + public static void sendSyncRequest() throws IOException { val event = new SyncRequest(); event.transmit(); diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/ParsedConfiguration.java b/src/main/java/com/falsepattern/lib/internal/impl/config/ParsedConfiguration.java index c5cfd87..037e641 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/ParsedConfiguration.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/ParsedConfiguration.java @@ -46,8 +46,11 @@ import java.io.DataOutput; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -76,6 +79,8 @@ public class ParsedConfiguration { public final Class configClass; public final String modid; public final String category; + public final String comment; + public final String langKey; public final Configuration rawConfig; public final boolean sync; private final Map> fields = new HashMap<>(); @@ -88,14 +93,76 @@ public class ParsedConfiguration { + configClass.getName() + " does not have a @Config annotation!")); val category = Optional.of(cfg.category().trim()) - .map((cat) -> cat.length() == 0 ? null : cat) + .map((cat) -> cat.isEmpty() ? null : cat) .orElseThrow(() -> new ConfigException("Config class " + configClass.getName() + " has an empty category!")); - val rawConfig = ConfigurationManagerImpl.getForgeConfig(cfg.modid()); + val comment = Optional.ofNullable(configClass.getAnnotation(Config.Comment.class)) + .map(Config.Comment::value) + .map((lines) -> String.join("\n", lines)) + .orElse(""); + val langKey = Optional.ofNullable(configClass.getAnnotation(Config.LangKey.class)) + .map(Config.LangKey::value) + .map(x -> x.isEmpty() ? "config." + cfg.modid() + "." + category : x) + .orElse(category); + val path = Optional.of(cfg.customPath().trim()) + .map(p -> p.isEmpty() ? null : p) + .orElse(cfg.modid()); + val rawConfig = ConfigurationManagerImpl.getForgeConfig(path, true); + if (!rawConfig.hasCategory(category)) { + // Process migrations + migrate: + { + val categoryCandidates = new ArrayList<>(Arrays.asList(cfg.categoryMigrations())); + categoryCandidates.add(0, category); + for (var migration : cfg.pathMigrations()) { + migration = migration.trim(); + try { + val oldConfig = ConfigurationManagerImpl.getForgeConfig(migration, false); + for (val fromCategory : categoryCandidates) { + if (oldConfig.hasCategory(fromCategory)) { + val oldCat = oldConfig.getCategory(fromCategory); + val newCat = rawConfig.getCategory(category); + val entries = oldCat.keySet(); + for (val entry : entries) { + newCat.put(entry, oldCat.get(entry)); + } + rawConfig.save(); + oldConfig.removeCategory(oldConfig.getCategory(category)); + if (oldConfig.getCategoryNames().isEmpty()) { + oldConfig.getConfigFile().delete(); + } else { + oldConfig.save(); + } + break migrate; + } + } + } catch (ConfigException ignored) { + } + } + // Try to migrate category in local file + categoryCandidates.remove(0); + for (val fromCategory : categoryCandidates) { + if (rawConfig.hasCategory(fromCategory)) { + val oldCategory = rawConfig.getCategory(fromCategory); + val newCategory = rawConfig.getCategory(category); + val entries = oldCategory.keySet(); + for (val entry: entries) { + newCategory.put(entry, oldCategory.get(entry)); + } + rawConfig.removeCategory(oldCategory); + break migrate; + } + } + } + } + rawConfig.setCategoryComment(category, comment); + rawConfig.setCategoryLanguageKey(category, langKey); val parsedConfig = new ParsedConfiguration(configClass, cfg.modid(), category, + comment, + langKey, rawConfig, configClass.isAnnotationPresent(Config.Synchronize.class)); try { @@ -106,11 +173,15 @@ public class ParsedConfiguration { return parsedConfig; } - public void save() { + public void saveFile() { + saveFields(); + rawConfig.save(); + } + + public void saveFields() { for (val field : fields.values()) { field.save(); } - rawConfig.save(); } //Happens when changed through the gui @@ -121,8 +192,12 @@ public class ParsedConfiguration { rawConfig.save(); } - public void load() throws ConfigException { + public void loadFile() throws ConfigException { ConfigurationManagerImpl.loadRawConfig(rawConfig); + loadFields(); + } + + public void loadFields() { for (val field : fields.values()) { field.load(); } @@ -136,7 +211,7 @@ public class ParsedConfiguration { syncFields.remove(key); } } - while (syncFields.size() > 0) { + while (!syncFields.isEmpty()) { val fieldName = StringConfigField.receiveString(input, maxFieldNameLength, "field name", configClass.getName()); if (!syncFields.containsKey(fieldName)) { @@ -175,6 +250,7 @@ public class ParsedConfiguration { if (configClass.isAssignableFrom(Config.RequiresMcRestart.class)) { cat.setRequiresMcRestart(true); } + val nonFoundKeys = new HashSet<>(rawConfig.getCategory(category).keySet()); for (val field : configClass.getDeclaredFields()) { if (field.getAnnotation(Config.Ignore.class) != null || (field.getModifiers() & Modifier.FINAL) != 0) { continue; @@ -182,14 +258,32 @@ public class ParsedConfiguration { field.setAccessible(true); maxFieldNameLength = Math.max(maxFieldNameLength, field.getName().length()); val fieldClass = field.getType(); - val name = Optional.ofNullable(field.getAnnotation(Config.Name.class)) - .map(Config.Name::value) - .orElse(field.getName()); + val nameAnnotation = Optional.ofNullable(field.getAnnotation(Config.Name.class)); + val name = nameAnnotation.map(Config.Name::value) + .orElse(field.getName()); + if (!cat.containsKey(name) && nameAnnotation.isPresent()) { + val migrations = nameAnnotation.get().migrations(); + for (var migration: migrations) { + if (migration.isEmpty()) { + migration = field.getName(); + } + if (cat.containsKey(migration)) { + val prop = cat.remove(migration); + prop.setName(name); + cat.put(name, prop); + nonFoundKeys.remove(migration); + break; + } + } + } else { + nonFoundKeys.remove(name); + } AConfigField configField; + val params = new ConfigFieldParameters(field, rawConfig, modid, category); if (constructors.containsKey(fieldClass)) { - fields.put(name, configField = constructors.get(fieldClass).construct(field, rawConfig, category)); + fields.put(name, configField = constructors.get(fieldClass).construct(params)); } else if (fieldClass.isEnum()) { - fields.put(name, configField = new EnumConfigField<>(field, rawConfig, category)); + fields.put(name, configField = new EnumConfigField<>(params)); } else { throw new ConfigException("Illegal config field: " + field.getName() @@ -206,11 +300,16 @@ public class ParsedConfiguration { cat.setRequiresWorldRestart(true); } configField.init(); - elements.computeIfAbsent(name, (name2) -> new ConfigElementProxy<>(configField.getProperty(), () -> { + elements.computeIfAbsent(name, (name2) -> new ConfigElementProxy<>(configField.getProperty(), configField.getComment(), () -> { configField.load(); - save(); + configField.save(); })); } + val rawCategory = rawConfig.getCategory(category); + for (val key : nonFoundKeys) { + rawCategory.remove(key); + } + saveFile(); rawConfig.setCategoryPropertyOrder(category, fieldsSorted().map((prop) -> prop.name).collect(Collectors.toList())); } @@ -224,6 +323,14 @@ public class ParsedConfiguration { return fieldsSorted().map((field) -> elements.get(field.name)).collect(Collectors.toList()); } + public boolean requiresWorldRestart() { + return rawConfig.getCategory(category).requiresWorldRestart(); + } + + public boolean requiresMcRestart() { + return rawConfig.getCategory(category).requiresMcRestart(); + } + public boolean validate(BiConsumer, Field> invalidFieldHandler, boolean resetInvalid) { boolean valid = true; for (val field : fields.values()) { @@ -239,6 +346,6 @@ public class ParsedConfiguration { } private interface FieldRefConstructor { - AConfigField construct(Field field, Configuration configuration, String category) throws ConfigException; + AConfigField construct(ConfigFieldParameters params) throws ConfigException; } } diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/event/ClientEventHandlerPre.java b/src/main/java/com/falsepattern/lib/internal/impl/config/event/ClientEventHandlerPre.java index 78c4a22..fb6eb2e 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/event/ClientEventHandlerPre.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/event/ClientEventHandlerPre.java @@ -53,7 +53,7 @@ public class ClientEventHandlerPre { @SubscribeEvent @SideOnly(Side.CLIENT) public void onValidationErrorToast(ConfigValidationFailureEvent e) { - if (LibraryConfig.CONFIG_ERROR_LOUDNESS == LibraryConfig.ValidationLogging.LogAndToast) { + if (LibraryConfig.CONFIG_ERROR_LOGGING == LibraryConfig.ValidationLogging.LogAndToast) { e.toast(); } } diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/event/CommonEventHandlerPre.java b/src/main/java/com/falsepattern/lib/internal/impl/config/event/CommonEventHandlerPre.java index 82ebf7e..8a15f76 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/event/CommonEventHandlerPre.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/event/CommonEventHandlerPre.java @@ -48,7 +48,7 @@ public class CommonEventHandlerPre { @SubscribeEvent public void onValidationErrorLog(ConfigValidationFailureEvent e) { - if (LibraryConfig.CONFIG_ERROR_LOUDNESS != LibraryConfig.ValidationLogging.None) { + if (LibraryConfig.CONFIG_ERROR_LOGGING != LibraryConfig.ValidationLogging.None) { e.logWarn(); } } diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/AConfigField.java b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/AConfigField.java index d62d7e0..7854c5f 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/AConfigField.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/AConfigField.java @@ -23,7 +23,9 @@ package com.falsepattern.lib.internal.impl.config.fields; import com.falsepattern.lib.config.Config; +import com.falsepattern.lib.internal.impl.config.ConfigFieldParameters; import com.falsepattern.lib.internal.impl.config.DeclOrderInternal; +import lombok.Getter; import lombok.val; import net.minecraftforge.common.config.Configuration; @@ -44,7 +46,9 @@ public abstract class AConfigField { protected final String category; protected final String langKey; protected final Property.Type type; + @Getter protected final Property property; + @Getter protected final String comment; private boolean uninitialized; @@ -58,23 +62,26 @@ public abstract class AConfigField { + " annotation!"); } - protected AConfigField(Field field, Configuration configuration, String category, Property.Type type) { - this(field, configuration, category, type, false); + protected AConfigField(ConfigFieldParameters params, Property.Type type) { + this(params, type, false); } - protected AConfigField(Field field, Configuration configuration, String category, Property.Type type, boolean isList) { - this.field = field; - this.configuration = configuration; - this.category = category; + protected AConfigField(ConfigFieldParameters params, Property.Type type, boolean isList) { + this.field = params.field(); + this.configuration = params.configuration(); + this.category = params.category(); comment = Optional.ofNullable(field.getAnnotation(Config.Comment.class)) .map(Config.Comment::value) .map((lines) -> String.join("\n", lines)) .orElse(""); - name = Optional.ofNullable(field.getAnnotation(Config.Name.class)) - .map(Config.Name::value) - .orElse(field.getName()); + val nameAnnotation = Optional.ofNullable(field.getAnnotation(Config.Name.class)); + name = nameAnnotation.map(Config.Name::value) + .orElse(field.getName()); langKey = - Optional.ofNullable(field.getAnnotation(Config.LangKey.class)).map(Config.LangKey::value).orElse(name); + Optional.ofNullable(field.getAnnotation(Config.LangKey.class)) + .map(Config.LangKey::value) + .map(x -> x.isEmpty() ? "config." + params.modid() + "." + params.category() + "." + name : x) + .orElse(name); this.type = type; val cat = configuration.getCategory(category); uninitialized = !cat.containsKey(name); @@ -125,13 +132,11 @@ public abstract class AConfigField { uninitialized = false; putField(getDefault()); putConfig(getDefault()); + } else { + load(); } } - public Property getProperty() { - return property; - } - @SuppressWarnings("BooleanMethodIsAlwaysInverted") public abstract boolean validateField(); diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/AListConfigField.java b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/AListConfigField.java index 05a29ab..7da7ab4 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/AListConfigField.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/AListConfigField.java @@ -25,6 +25,7 @@ package com.falsepattern.lib.internal.impl.config.fields; import com.falsepattern.lib.config.Config; import com.falsepattern.lib.config.ConfigException; import com.falsepattern.lib.config.event.ConfigValidationFailureEvent; +import com.falsepattern.lib.internal.impl.config.ConfigFieldParameters; import lombok.SneakyThrows; import lombok.val; @@ -41,9 +42,9 @@ public abstract class AListConfigField extends AConfigField { protected final boolean fixedLength; protected final int maxLength; - protected AListConfigField(Field field, Configuration configuration, String category, Property.Type type) + protected AListConfigField(ConfigFieldParameters params, Property.Type type) throws ConfigException { - super(field, configuration, category, type, true); + super(params, type, true); fixedLength = field.isAnnotationPresent(Config.ListFixedLength.class); maxLength = Optional.ofNullable(field.getAnnotation(Config.ListMaxLength.class)) .map(Config.ListMaxLength::value) diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/BooleanConfigField.java b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/BooleanConfigField.java index b02fb7d..8bbf852 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/BooleanConfigField.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/BooleanConfigField.java @@ -24,6 +24,7 @@ package com.falsepattern.lib.internal.impl.config.fields; import com.falsepattern.lib.config.Config; import com.falsepattern.lib.config.event.ConfigValidationFailureEvent; +import com.falsepattern.lib.internal.impl.config.ConfigFieldParameters; import lombok.SneakyThrows; import net.minecraftforge.common.config.Configuration; @@ -39,8 +40,8 @@ public class BooleanConfigField extends AConfigField { private final boolean primitive; private final boolean defaultValue; - public BooleanConfigField(Field field, Configuration configuration, String category) { - super(field, configuration, category, Property.Type.BOOLEAN); + public BooleanConfigField(ConfigFieldParameters params) { + super(params, Property.Type.BOOLEAN); primitive = field.getType().isPrimitive(); defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultBoolean.class)) .map(Config.DefaultBoolean::value) diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/BooleanListConfigField.java b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/BooleanListConfigField.java index aa01bd9..8bccab2 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/BooleanListConfigField.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/BooleanListConfigField.java @@ -24,6 +24,7 @@ package com.falsepattern.lib.internal.impl.config.fields; import com.falsepattern.lib.config.Config; import com.falsepattern.lib.config.ConfigException; +import com.falsepattern.lib.internal.impl.config.ConfigFieldParameters; import lombok.val; import net.minecraftforge.common.config.Configuration; @@ -39,8 +40,8 @@ import java.util.Optional; public class BooleanListConfigField extends AListConfigField { private final boolean[] defaultValue; - public BooleanListConfigField(Field field, Configuration configuration, String category) throws ConfigException { - super(field, configuration, category, Property.Type.BOOLEAN); + public BooleanListConfigField(ConfigFieldParameters params) throws ConfigException { + super(params, Property.Type.BOOLEAN); defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultBooleanList.class)) .map(Config.DefaultBooleanList::value) .orElseThrow(() -> noDefault(field, "DefaultBooleanList")); diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/DoubleConfigField.java b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/DoubleConfigField.java index 0bdb186..1aad486 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/DoubleConfigField.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/DoubleConfigField.java @@ -24,6 +24,7 @@ package com.falsepattern.lib.internal.impl.config.fields; import com.falsepattern.lib.config.Config; import com.falsepattern.lib.config.event.ConfigValidationFailureEvent; +import com.falsepattern.lib.internal.impl.config.ConfigFieldParameters; import lombok.SneakyThrows; import lombok.val; @@ -42,8 +43,8 @@ public class DoubleConfigField extends AConfigField { private final double min; private final double max; - public DoubleConfigField(Field field, Configuration configuration, String category) { - super(field, configuration, category, Property.Type.DOUBLE); + public DoubleConfigField(ConfigFieldParameters params) { + super(params, Property.Type.DOUBLE); primitive = field.getType().isPrimitive(); val range = Optional.ofNullable(field.getAnnotation(Config.RangeDouble.class)); min = range.map(Config.RangeDouble::min).orElse(-Double.MAX_VALUE); diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/DoubleListConfigField.java b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/DoubleListConfigField.java index dc0d0f8..d7efa8b 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/DoubleListConfigField.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/DoubleListConfigField.java @@ -25,6 +25,7 @@ package com.falsepattern.lib.internal.impl.config.fields; import com.falsepattern.lib.config.Config; import com.falsepattern.lib.config.ConfigException; import com.falsepattern.lib.config.event.ConfigValidationFailureEvent; +import com.falsepattern.lib.internal.impl.config.ConfigFieldParameters; import lombok.val; import net.minecraftforge.common.config.Configuration; @@ -42,8 +43,8 @@ public class DoubleListConfigField extends AListConfigField { private final double max; private final double[] defaultValue; - public DoubleListConfigField(Field field, Configuration configuration, String category) throws ConfigException { - super(field, configuration, category, Property.Type.DOUBLE); + public DoubleListConfigField(ConfigFieldParameters params) throws ConfigException { + super(params, Property.Type.DOUBLE); val range = Optional.ofNullable(field.getAnnotation(Config.RangeDouble.class)); min = range.map(Config.RangeDouble::min).orElse(-Double.MAX_VALUE); max = range.map(Config.RangeDouble::max).orElse(Double.MAX_VALUE); diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/EnumConfigField.java b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/EnumConfigField.java index 5ea0d22..f3fbbbd 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/EnumConfigField.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/EnumConfigField.java @@ -25,6 +25,7 @@ package com.falsepattern.lib.internal.impl.config.fields; import com.falsepattern.lib.config.Config; import com.falsepattern.lib.config.event.ConfigValidationFailureEvent; import com.falsepattern.lib.internal.FPLog; +import com.falsepattern.lib.internal.impl.config.ConfigFieldParameters; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import lombok.SneakyThrows; @@ -48,8 +49,8 @@ public class EnumConfigField> extends AConfigField { private final BiMap enumNameMap = HashBiMap.create(); @SuppressWarnings("unchecked") - public EnumConfigField(Field field, Configuration configuration, String category) { - super(field, configuration, category, Property.Type.STRING); + public EnumConfigField(ConfigFieldParameters params) { + super(params, Property.Type.STRING); enumClass = (Class) field.getType(); val sj = new StringJoiner(", ", "[", "]"); diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/IntConfigField.java b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/IntConfigField.java index 756a90b..3e3b5af 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/IntConfigField.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/IntConfigField.java @@ -24,6 +24,7 @@ package com.falsepattern.lib.internal.impl.config.fields; import com.falsepattern.lib.config.Config; import com.falsepattern.lib.config.event.ConfigValidationFailureEvent; +import com.falsepattern.lib.internal.impl.config.ConfigFieldParameters; import lombok.SneakyThrows; import lombok.val; @@ -42,8 +43,8 @@ public class IntConfigField extends AConfigField { private final int min; private final int max; - public IntConfigField(Field field, Configuration configuration, String category) { - super(field, configuration, category, Property.Type.INTEGER); + public IntConfigField(ConfigFieldParameters params) { + super(params, Property.Type.INTEGER); primitive = field.getType().isPrimitive(); val range = Optional.ofNullable(field.getAnnotation(Config.RangeInt.class)); min = range.map(Config.RangeInt::min).orElse(Integer.MIN_VALUE); diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/IntListConfigField.java b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/IntListConfigField.java index d8ac49e..8649571 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/IntListConfigField.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/IntListConfigField.java @@ -25,6 +25,7 @@ package com.falsepattern.lib.internal.impl.config.fields; import com.falsepattern.lib.config.Config; import com.falsepattern.lib.config.ConfigException; import com.falsepattern.lib.config.event.ConfigValidationFailureEvent; +import com.falsepattern.lib.internal.impl.config.ConfigFieldParameters; import lombok.val; import net.minecraftforge.common.config.Configuration; @@ -42,8 +43,8 @@ public class IntListConfigField extends AListConfigField { private final int max; private final int[] defaultValue; - public IntListConfigField(Field field, Configuration configuration, String category) throws ConfigException { - super(field, configuration, category, Property.Type.INTEGER); + public IntListConfigField(ConfigFieldParameters params) throws ConfigException { + super(params, Property.Type.INTEGER); val range = Optional.ofNullable(field.getAnnotation(Config.RangeInt.class)); min = range.map(Config.RangeInt::min).orElse(Integer.MIN_VALUE); max = range.map(Config.RangeInt::max).orElse(Integer.MAX_VALUE); diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/StringConfigField.java b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/StringConfigField.java index ea0383e..337bc1f 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/StringConfigField.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/StringConfigField.java @@ -24,6 +24,7 @@ package com.falsepattern.lib.internal.impl.config.fields; import com.falsepattern.lib.config.Config; import com.falsepattern.lib.config.event.ConfigValidationFailureEvent; +import com.falsepattern.lib.internal.impl.config.ConfigFieldParameters; import lombok.SneakyThrows; import lombok.val; @@ -42,8 +43,8 @@ public class StringConfigField extends AConfigField { private final Pattern pattern; private final String defaultValue; - public StringConfigField(Field field, Configuration configuration, String category) { - super(field, configuration, category, Property.Type.STRING); + public StringConfigField(ConfigFieldParameters params) { + super(params, Property.Type.STRING); pattern = Optional.ofNullable(field.getAnnotation(Config.Pattern.class)) .map((ptr) -> Pattern.compile(ptr.value())) .orElse(null); diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/StringListConfigField.java b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/StringListConfigField.java index 1da6193..e3eba57 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/StringListConfigField.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/StringListConfigField.java @@ -24,6 +24,7 @@ package com.falsepattern.lib.internal.impl.config.fields; import com.falsepattern.lib.config.Config; import com.falsepattern.lib.config.ConfigException; +import com.falsepattern.lib.internal.impl.config.ConfigFieldParameters; import lombok.val; import net.minecraftforge.common.config.Configuration; @@ -42,8 +43,8 @@ public class StringListConfigField extends AListConfigField { private final Pattern pattern; private final int maxStringLength; - public StringListConfigField(Field field, Configuration configuration, String category) throws ConfigException { - super(field, configuration, category, Property.Type.STRING); + public StringListConfigField(ConfigFieldParameters params) throws ConfigException { + super(params, Property.Type.STRING); pattern = Optional.ofNullable(field.getAnnotation(Config.Pattern.class)) .map((ptr) -> Pattern.compile(ptr.value())) .orElse(null); diff --git a/src/main/resources/assets/falsepatternlib/lang/en_US.lang b/src/main/resources/assets/falsepatternlib/lang/en_US.lang index 8f542af..29e69a3 100644 --- a/src/main/resources/assets/falsepatternlib/lang/en_US.lang +++ b/src/main/resources/assets/falsepatternlib/lang/en_US.lang @@ -1,23 +1,15 @@ -config.falsepatternlib.maxtoasts=Max Toasts -config.falsepatternlib.maxtoasts.tooltip=The maximum amount of toasts to show on the screen -config.falsepatternlib.toastoffset=Toast Y Offset -config.falsepatternlib.toastoffset.tooltip=The amount of empty space from the top of the screen in pixels -config.falsepatternlib.toastalign=Toast Alignment -config.falsepatternlib.toastalign.tooltip=Which side of the screen should toasts show up on -config.falsepatternlib.configlogging=Config error loudness -config.falsepatternlib.configlogging.tooltip=How loud the config error logging should be. None disables all built-in config validation error logging -config.falsepatternlib.updatecheck=Enable update checks -config.falsepatternlib.updatecheck.tooltip=Used to control whether FalsePatternLib should check for outdated mods. -config.falsepatternlib.disableinternet=FalsePatternLib Offline Mode -config.falsepatternlib.disableinternet.tooltip=Used to control whether FalsePatternLib should be allowed to use the internet. If this is enabled, update checks and library downloads will be completely blocked. -config.falsepatternlib.ingamemodoptionsfix=In-Game Mod Options Fix -config.falsepatternlib.ingamemodoptionsfix.tooltip=Fixes the mod options menu in-game. By default, the mod options when in already in a game will show: "Test1, Test2, DISABLED\" in bright red. This replaces that interface with the one from the main menu. -falsepatternlib.gui.config.description=Hover over a setting for more information. -falsepatternlib.chat.updatesavailable=§4Outdated mods detected by %s! §r -falsepatternlib.chat.modname=§b§l[%s]§r -falsepatternlib.chat.currentversion= Current Version: §6%s§r -falsepatternlib.chat.latestversion= Latest Version: §2%s§r -falsepatternlib.chat.updateurlpre= Click -falsepatternlib.chat.updateurl= §n§o§dHERE§r -falsepatternlib.chat.updateurlpost= to visit update page -falsepatternlib.chat.updateavailablefor=§1§l[%s]§r Current version: §6%s§r Latest version: §2%s§r +config.falsepatternlib.misc=Miscellaneous +config.falsepatternlib.misc.tooltip=Settings that don't fit into other categories +config.falsepatternlib.misc.configErrorLogging=Config error logging +config.falsepatternlib.misc.configErrorLogging.tooltip=How the config error logging should be set up.\n[None] disables all built-in config validation error logging. +config.falsepatternlib.misc.inGameModOptionsFix=In-Game Mod Options Fix +config.falsepatternlib.misc.inGameModOptionsFix.tooltip=Fixes the mod options menu in-game. + +config.falsepatternlib.toasts=Toast notifications +config.falsepatternlib.toasts.tooltip=Common toast notification settings +config.falsepatternlib.toasts.maxVisible=Max Toasts +config.falsepatternlib.toasts.maxVisible.tooltip=The maximum amount of toasts to show on the screen. +config.falsepatternlib.toasts.yOffset=Toast Y Offset +config.falsepatternlib.toasts.yOffset.tooltip=The amount of empty space from the top of the screen in pixels. +config.falsepatternlib.toasts.align=Toast Alignment +config.falsepatternlib.toasts.align.tooltip=Which side of the screen should toasts show up on. diff --git a/src/main/resources/assets/forge/lang/en_US.lang b/src/main/resources/assets/forge/lang/en_US.lang new file mode 100644 index 0000000..def7261 --- /dev/null +++ b/src/main/resources/assets/forge/lang/en_US.lang @@ -0,0 +1,2 @@ +fml.configgui.tooltip.default=[default: %s] +fml.configgui.tooltip.defaultNumeric=[range: %s ~ %s, default: %s] \ No newline at end of file