From eaec528f5d8744f43ada93f0b4a53b28c2b1ffd3 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 31 Jul 2022 20:28:37 +0200 Subject: [PATCH] the grand config refactor --- .../com/falsepattern/lib/config/Config.java | 162 ++++++- .../lib/config/ConfigurationManager.java | 60 ++- .../lib/config/event/AllConfigSyncEvent.java | 68 +++ .../lib/config/event/ConfigSyncEvent.java | 89 ++++ .../config/event/ConfigSyncRequestEvent.java | 68 +++ .../event/ConfigValidationFailureEvent.java | 204 +++++++++ .../lib/internal/FalsePatternLib.java | 3 + .../lib/internal/config/LibraryConfig.java | 9 + .../impl/config/ConfigurationManagerImpl.java | 405 ++++++++++-------- .../impl/config/ParsedConfiguration.java | 223 ++++++++++ .../impl/config/fields/AConfigField.java | 114 +++++ .../impl/config/fields/AListConfigField.java | 114 +++++ .../config/fields/BooleanConfigField.java | 115 +++++ .../config/fields/BooleanListConfigField.java | 114 +++++ .../impl/config/fields/DoubleConfigField.java | 128 ++++++ .../config/fields/DoubleListConfigField.java | 131 ++++++ .../impl/config/fields/EnumConfigField.java | 160 +++++++ .../impl/config/fields/FloatConfigField.java | 137 ++++++ .../impl/config/fields/IntConfigField.java | 128 ++++++ .../config/fields/IntListConfigField.java | 131 ++++++ .../impl/config/fields/StringConfigField.java | 151 +++++++ .../config/fields/StringListConfigField.java | 145 +++++++ .../impl/config/net/CompressedMessage.java | 81 ++++ .../internal/impl/config/net/SyncPrompt.java | 37 ++ .../impl/config/net/SyncPromptHandler.java | 38 ++ .../internal/impl/config/net/SyncReply.java | 42 ++ .../impl/config/net/SyncReplyHandler.java | 47 ++ .../internal/impl/config/net/SyncRequest.java | 42 ++ .../impl/config/net/SyncRequestHandler.java | 52 +++ .../lib/internal/proxy/ClientProxy.java | 12 + .../lib/internal/proxy/CommonProxy.java | 16 +- .../assets/falsepatternlib/lang/en_US.lang | 2 + 32 files changed, 3038 insertions(+), 190 deletions(-) create mode 100644 src/main/java/com/falsepattern/lib/config/event/AllConfigSyncEvent.java create mode 100644 src/main/java/com/falsepattern/lib/config/event/ConfigSyncEvent.java create mode 100644 src/main/java/com/falsepattern/lib/config/event/ConfigSyncRequestEvent.java create mode 100644 src/main/java/com/falsepattern/lib/config/event/ConfigValidationFailureEvent.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/ParsedConfiguration.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/fields/AConfigField.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/fields/AListConfigField.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/fields/BooleanConfigField.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/fields/BooleanListConfigField.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/fields/DoubleConfigField.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/fields/DoubleListConfigField.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/fields/EnumConfigField.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/fields/FloatConfigField.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/fields/IntConfigField.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/fields/IntListConfigField.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/fields/StringConfigField.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/fields/StringListConfigField.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/net/CompressedMessage.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncPrompt.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncPromptHandler.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncReply.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncReplyHandler.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncRequest.java create mode 100644 src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncRequestHandler.java diff --git a/src/main/java/com/falsepattern/lib/config/Config.java b/src/main/java/com/falsepattern/lib/config/Config.java index 8bfad44..c23fd22 100644 --- a/src/main/java/com/falsepattern/lib/config/Config.java +++ b/src/main/java/com/falsepattern/lib/config/Config.java @@ -20,23 +20,25 @@ */ package com.falsepattern.lib.config; +import com.falsepattern.lib.DeprecationDetails; import com.falsepattern.lib.StableAPI; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +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 - * {@link com.falsepattern.lib.config.ConfigurationManager#registerConfig}! + * {@link ConfigurationManager#initialize(Class[])} or {@link ConfigurationManager#initialize(BiConsumer, Class[])}! */ +@StableAPI(since = "0.6.0") @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) -@StableAPI(since = "0.6.0") public @interface Config { /** * The mod id that this configuration is associated with. @@ -51,6 +53,7 @@ public @interface Config { /** * The lang file key of this configuration. Used in config GUIs. */ + @StableAPI(since = "0.6.0") @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) @@ -61,6 +64,7 @@ public @interface Config { /** * The description of the configuration. */ + @StableAPI(since = "0.6.0") @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @@ -72,6 +76,7 @@ public @interface Config { * If you have extra fields in the config class used for anything else other than configuring, you must annotate * the using this so that the config engine doesn't pick them up. */ + @StableAPI(since = "0.6.0") @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @@ -82,6 +87,7 @@ public @interface Config { * 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) @@ -92,6 +98,7 @@ public @interface Config { /** * The range of possible values an int config can have. */ + @StableAPI(since = "0.6.0") @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @@ -105,6 +112,7 @@ public @interface Config { * 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) @@ -114,12 +122,16 @@ public @interface Config { /** * The range of possible values a float config can have. + * Notice: float configs are deprecated! Use double configs instead! */ + @Deprecated + @DeprecationDetails(stableSince = "0.6.0", + deprecatedSince = "0.10.0") @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface RangeFloat { - float min() default Float.MIN_VALUE; + float min() default -Float.MAX_VALUE; float max() default Float.MAX_VALUE; } @@ -127,7 +139,11 @@ public @interface Config { /** * The default value for a float field. Not having a default is deprecated since 0.10, and will be strongly * enforced in 0.11+! + * Notice: float configs are deprecated! Use double configs instead! */ + @Deprecated + @DeprecationDetails(stableSince = "0.6.0", + deprecatedSince = "0.10.0") @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @@ -135,10 +151,36 @@ public @interface Config { float 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 { + double min() default -Double.MAX_VALUE; + + 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 { + 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) @@ -146,9 +188,27 @@ public @interface Config { 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 { + int value(); + } + /** * A regex pattern for restricting the allowed strings. */ + @StableAPI(since = "0.6.0") @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @@ -160,6 +220,7 @@ public @interface Config { * 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) @@ -171,6 +232,7 @@ public @interface Config { * 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) @@ -178,9 +240,74 @@ public @interface Config { 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 { + 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 { + int[] value(); + } + + /** + * The default value for an 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 { + 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 { + int value(); + } + + + /** * The name of this config property in the config file. If not specified, the field's name will be used instead. */ + @StableAPI(since = "0.6.0") @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @@ -191,6 +318,7 @@ public @interface Config { /** * Whether the specific configuration needs a minecraft restart to be applied. */ + @StableAPI(since = "0.6.0") @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) @@ -200,6 +328,7 @@ public @interface Config { /** * Whether the specific configuration needs a world/server rejoin to be applied. */ + @StableAPI(since = "0.6.0") @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) @@ -207,7 +336,7 @@ public @interface Config { } /** - * Signals that this configuration entry/class should be synchronized between the client and the server when + * Signals that this configuration class should be synchronized between the client and the server when * joining a multiplayer instance. * * Note that synchronization ALWAYS happens FROM the server TO the client. The server should NEVER attempt to get @@ -216,20 +345,17 @@ public @interface Config { @StableAPI(since = "0.10.0") @Documented @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD, ElementType.TYPE}) + @Target(ElementType.TYPE) @interface Synchronize { - /** - * This is a limit on how many bytes the client will accept from the server. If this limit is exceeded, - * the client shall refuse to connect to the server. For String[]-s, the amount of bytes is the sum of all - * strings inside the array.

- * - * This limitation option exists to avoid malicious servers flooding clients with data.

- * - * By default, the client will accept any amount of bytes.

- * - * This only applies for String and String[] configurations, and is ignored on the other config types. - * For enums, this value is computed automatically. - */ - int maxLength(); + } + + /** + * Use this to mark config fields you don't want to synchronize in a class marked with {@link Synchronize}. + */ + @StableAPI(since = "0.10.0") + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @interface NoSync { } } \ No newline at end of file diff --git a/src/main/java/com/falsepattern/lib/config/ConfigurationManager.java b/src/main/java/com/falsepattern/lib/config/ConfigurationManager.java index 115b563..393d7fd 100644 --- a/src/main/java/com/falsepattern/lib/config/ConfigurationManager.java +++ b/src/main/java/com/falsepattern/lib/config/ConfigurationManager.java @@ -20,7 +20,9 @@ */ package com.falsepattern.lib.config; +import com.falsepattern.lib.DeprecationDetails; import com.falsepattern.lib.StableAPI; +import com.falsepattern.lib.internal.FalsePatternLib; import com.falsepattern.lib.internal.impl.config.ConfigurationManagerImpl; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -28,9 +30,11 @@ import lombok.val; import cpw.mods.fml.client.config.IConfigElement; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.BiConsumer; /** * Class for controlling the loading of configuration files. @@ -39,13 +43,64 @@ import java.util.List; @StableAPI(since = "0.6.0") public class ConfigurationManager { + @StableAPI(since = "0.10.0") + public static void initialize(Class... configClasses) throws ConfigException { + initialize((a, b) -> {}, configClasses); + } + + @StableAPI(since = "0.10.0") + public static void initialize(BiConsumer, Field> validatorErrorCallback, Class... configClasses) throws ConfigException { + for (val clazz: configClasses) { + ConfigurationManagerImpl.register(clazz); + ConfigurationManagerImpl.load(clazz); + ConfigurationManagerImpl.validateFields(validatorErrorCallback, clazz, true); + ConfigurationManagerImpl.save(clazz); + } + } + @StableAPI(since = "0.10.0") + public static boolean validate(boolean resetInvalid, Class... configClasses) + throws ConfigException { + return validate((x, y) -> {}, resetInvalid, configClasses); + } + + @StableAPI(since = "0.10.0") + public static boolean validate(BiConsumer, Field> validatorErrorCallback, boolean resetInvalid, Class... configClasses) + throws ConfigException { + boolean valid = true; + for (val clazz: configClasses) { + valid &= ConfigurationManagerImpl.validateFields(validatorErrorCallback, clazz, resetInvalid); + } + return valid; + } + + @StableAPI(since = "0.10.0") + public static void loadFromFile(Class... configClasses) throws ConfigException { + for (val clazz: configClasses) { + ConfigurationManagerImpl.load(clazz); + } + } + + @StableAPI(since = "0.10.0") + public static void saveToFile(boolean validateAndResetInvalid, Class... configClasses) throws ConfigException { + for (val clazz: configClasses) { + if (validateAndResetInvalid) { + ConfigurationManagerImpl.validateFields((a, b) -> {}, clazz, true); + } + ConfigurationManagerImpl.save(clazz); + } + } + /** - * Registers a configuration class to be loaded. This should be done in preInit. + * Registers a configuration class to be loaded. This should be done early on. * * @param configClass The class to register. */ + @Deprecated + @DeprecationDetails(stableSince = "0.6.0", + deprecatedSince = "0.10.0") public static void registerConfig(Class configClass) throws ConfigException { - ConfigurationManagerImpl.registerConfig(configClass); + FalsePatternLib.getLog().warn("A mod is using the deprecated config registration method! The following exception contains the stacktrace.", new Exception()); + ConfigurationManagerImpl.registerLoadSaveConfig(configClass); } /** @@ -56,6 +111,7 @@ public class ConfigurationManager { * @return The configuration elements. */ @SuppressWarnings("rawtypes") + @StableAPI(since = "0.6.0") public static List getConfigElements(Class configClass) throws ConfigException { return ConfigurationManagerImpl.getConfigElements(configClass); } diff --git a/src/main/java/com/falsepattern/lib/config/event/AllConfigSyncEvent.java b/src/main/java/com/falsepattern/lib/config/event/AllConfigSyncEvent.java new file mode 100644 index 0000000..acdf292 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/config/event/AllConfigSyncEvent.java @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + * + * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + * + * This program 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. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.falsepattern.lib.config.event; + +import com.falsepattern.lib.StableAPI; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.val; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.eventhandler.Event; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +/** + * This event is pushed on the FMLCommonHandler event bus once all the config synchronizations have finished. + * + * This is a client-only event, and is never triggered serverside! + */ +@SideOnly(Side.CLIENT) +@StableAPI(since = "0.10.0") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AllConfigSyncEvent extends Event { + + public static boolean postStart() { + val event = new Start(); + FMLCommonHandler.instance().bus().post(event); + return !event.isCanceled(); + } + + public static void postEnd() { + FMLCommonHandler.instance().bus().post(new End()); + } + + @SideOnly(Side.CLIENT) + @StableAPI(since = "0.10.0") + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class Start extends AllConfigSyncEvent { + @Override + public boolean isCancelable() { + return true; + } + } + + @SideOnly(Side.CLIENT) + @StableAPI(since = "0.10.0") + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class End extends AllConfigSyncEvent { + } +} diff --git a/src/main/java/com/falsepattern/lib/config/event/ConfigSyncEvent.java b/src/main/java/com/falsepattern/lib/config/event/ConfigSyncEvent.java new file mode 100644 index 0000000..e0dd27f --- /dev/null +++ b/src/main/java/com/falsepattern/lib/config/event/ConfigSyncEvent.java @@ -0,0 +1,89 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + * + * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + * + * This program 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. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.falsepattern.lib.config.event; + +import com.falsepattern.lib.StableAPI; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.val; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.eventhandler.Event; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +/** + * This event is pushed on the FMLCommonHandler event bus once each time a single config synchronization has finished. + * + * This is a client-only event, and is never triggered serverside! + */ +@SideOnly(Side.CLIENT) +@StableAPI(since = "0.10.0") +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +public class ConfigSyncEvent extends Event { + @StableAPI(since = "0.10.0") + public final Class configClass; + + public static boolean postStart(Class configClass) { + val event = new Start(configClass); + FMLCommonHandler.instance().bus().post(event); + return !event.isCanceled(); + } + + public static void postEndSuccess(Class configClass) { + FMLCommonHandler.instance().bus().post(new End(configClass, true, null)); + } + + public static void postEndFailure(Class configClass, Throwable error) { + FMLCommonHandler.instance().bus().post(new End(configClass, false, error)); + } + + @SideOnly(Side.CLIENT) + @StableAPI(since = "0.10.0") + public static class Start extends ConfigSyncEvent { + private Start(Class configClass) { + super(configClass); + } + + @Override + public boolean isCancelable() { + return true; + } + } + + @SideOnly(Side.CLIENT) + @StableAPI(since = "0.10.0") + public static class End extends ConfigSyncEvent { + @StableAPI(since = "0.10.0") + public final boolean successful; + /** + * null if successful == true. + */ + @StableAPI(since = "0.10.0") + public final Throwable error; + + private End(Class configClass, boolean successful, Throwable error) { + super(configClass); + this.successful = successful; + this.error = error; + } + } +} diff --git a/src/main/java/com/falsepattern/lib/config/event/ConfigSyncRequestEvent.java b/src/main/java/com/falsepattern/lib/config/event/ConfigSyncRequestEvent.java new file mode 100644 index 0000000..fff967a --- /dev/null +++ b/src/main/java/com/falsepattern/lib/config/event/ConfigSyncRequestEvent.java @@ -0,0 +1,68 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + * + * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + * + * This program 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. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.falsepattern.lib.config.event; + +import com.falsepattern.lib.StableAPI; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; + +import net.minecraft.entity.player.EntityPlayerMP; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.eventhandler.Event; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * You can use this event to manually trigger a synchronization request. The two different flavors are used on the two + * different game sides. If you want to trigger this on the server, use {@link #postServer}, and if on the client, use + * {@link #postClient} + * + */ +@StableAPI(since = "0.10.0") +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +public class ConfigSyncRequestEvent extends Event { + @StableAPI(since = "0.10.0") + public static class Client extends ConfigSyncRequestEvent { + + } + + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + @StableAPI(since = "0.10.0") + public static class Server extends ConfigSyncRequestEvent { + private final List players; + + public List getPlayers() { + return Collections.unmodifiableList(players); + } + } + + @StableAPI(since = "0.10.0") + public static void postClient() { + FMLCommonHandler.instance().bus().post(new Client()); + } + + @StableAPI(since = "0.10.0") + public static void postServer(List players) { + FMLCommonHandler.instance().bus().post(new Server(new ArrayList<>(players))); + } +} diff --git a/src/main/java/com/falsepattern/lib/config/event/ConfigValidationFailureEvent.java b/src/main/java/com/falsepattern/lib/config/event/ConfigValidationFailureEvent.java new file mode 100644 index 0000000..a509a7f --- /dev/null +++ b/src/main/java/com/falsepattern/lib/config/event/ConfigValidationFailureEvent.java @@ -0,0 +1,204 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.falsepattern.lib.config.event; + +import com.falsepattern.lib.StableAPI; +import com.falsepattern.lib.config.Config; +import com.falsepattern.lib.internal.FalsePatternLib; +import com.falsepattern.lib.text.FormattedText; +import com.falsepattern.lib.toasts.GuiToast; +import com.falsepattern.lib.toasts.SimpleToast; +import com.falsepattern.lib.toasts.icon.ToastBG; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.val; + +import net.minecraft.util.EnumChatFormatting; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.eventhandler.Event; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +import java.lang.reflect.Field; + +@StableAPI(since = "0.10.0") +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class ConfigValidationFailureEvent extends Event { + @StableAPI(since = "0.10.0") + public final String reason; + @StableAPI(since = "0.10.0") + public final Class configClass; + @StableAPI(since = "0.10.0") + public final String fieldName; + @StableAPI(since = "0.10.0") + public final boolean listElement; + @StableAPI(since = "0.10.0") + public final int listIndex; + + public static void postNumericRangeOutOfBounds(Field field, int listIndex, String value, String min, String max) { + FMLCommonHandler.instance().bus().post(new NumericRangeOutOfBounds(field.getDeclaringClass(), field.getName(), listIndex, value, min, max)); + } + + public static void postSize(Field field, int requestedSize, boolean fixedSize, int maxSize, int defaultSize) { + FMLCommonHandler.instance().bus().post(new ListSizeOutOfBounds(field.getDeclaringClass(), field.getName(), requestedSize, fixedSize, maxSize, defaultSize)); + } + + public static void postStringSizeOutOfBounds(Field field, int listIndex, String text, int maxSize) { + FMLCommonHandler.instance().bus().post(new StringSizeOutOfBounds(field.getDeclaringClass(), field.getName(), listIndex, text, maxSize)); + } + + public static void fieldIsNull(Field field, int listIndex) { + FMLCommonHandler.instance().bus().post(new FieldIsNull(field.getDeclaringClass(), field.getName(), listIndex)); + } + + public static void postStringPatternMismatch(Field field, int listIndex, String text, String pattern) { + FMLCommonHandler.instance().bus().post(new StringPatternMismatch(field.getDeclaringClass(), field.getName(), listIndex, text, pattern)); + } + + @StableAPI(since = "0.10.0") + public static class NumericRangeOutOfBounds extends ConfigValidationFailureEvent { + @StableAPI(since = "0.10.0") + public final String value; + @StableAPI(since = "0.10.0") + public final String min; + @StableAPI(since = "0.10.0") + public final String max; + + public NumericRangeOutOfBounds(Class configClass, String fieldName, int listIndex, String value, String min, String max) { + super("Number range out of bounds", configClass, fieldName, listIndex >= 0, listIndex); + this.value = value; + this.min = min; + this.max = max; + } + + @Override + protected void customText(StringBuilder b) { + b.append("\nValue: ").append(value) + .append("\nMin: ").append(min) + .append("\nMax: ").append(max); + } + } + + @StableAPI(since = "0.10.0") + public static class ListSizeOutOfBounds extends ConfigValidationFailureEvent { + @StableAPI(since = "0.10.0") + public final int size; + @StableAPI(since = "0.10.0") + public final boolean fixedSize; + @StableAPI(since = "0.10.0") + public final int maxSize; + @StableAPI(since = "0.10.0") + public final int defaultSize; + + private ListSizeOutOfBounds(Class configClass, String fieldName, int size, boolean fixedSize, int maxSize, int defaultSize) { + super("Array size out of bounds", configClass, fieldName, false, -1); + this.size = size; + this.fixedSize = fixedSize; + this.maxSize = maxSize; + this.defaultSize = defaultSize; + } + + @Override + protected void customText(StringBuilder b) { + b.append("\nSize: ").append(size); + if (fixedSize) { + b.append("\nRequired size: ").append(defaultSize); + } + b.append("\nMaximum size: ").append(maxSize); + } + } + + @StableAPI(since = "0.10.0") + public static class StringSizeOutOfBounds extends ConfigValidationFailureEvent { + @StableAPI(since = "0.10.0") + public final String text; + @StableAPI(since = "0.10.0") + public final int maxSize; + private StringSizeOutOfBounds(Class configClass, String fieldName, int listIndex, String text, int maxSize) { + super("String size out of bounds", configClass, fieldName, listIndex >= 0, listIndex); + this.text = text; + this.maxSize = maxSize; + } + + @Override + protected void customText(StringBuilder b) { + b.append("\nText: ").append(text) + .append("\nSize: ").append(text.length()) + .append("\nMax size:").append(maxSize); + } + } + + @StableAPI(since = "0.10.0") + public static class FieldIsNull extends ConfigValidationFailureEvent { + private FieldIsNull(Class configClass, String fieldName, int listIndex) { + super("Unexpected null", configClass, fieldName, listIndex >= 0, listIndex); + } + + @Override + protected void customText(StringBuilder b) { + + } + } + + @StableAPI(since = "0.10.0") + public static class StringPatternMismatch extends ConfigValidationFailureEvent { + @StableAPI(since = "0.10.0") + public final String text; + @StableAPI(since = "0.10.0") + public final String pattern; + + private StringPatternMismatch(Class configClass, String fieldName, int listIndex, String text, String pattern) { + super("String pattern mismatch", configClass, fieldName, listIndex >= 0, listIndex); + this.text = text; + this.pattern = pattern; + } + + @Override + protected void customText(StringBuilder b) { + b.append("\nText: ").append(text) + .append("\nPattern: ").append(pattern); + } + } + + @SideOnly(Side.CLIENT) + public void toast() { + val ann = configClass.getAnnotation(Config.class); + val toast = new SimpleToast(ToastBG.TOAST_DARK, null, + FormattedText.parse(EnumChatFormatting.RED + "Config validation failed").toChatText().get(0), + FormattedText.parse(ann.modid() + ":" + ann.category()).toChatText().get(0), + false, 2000); + GuiToast.add(toast); + } + + protected abstract void customText(StringBuilder b); + + public void logWarn() { + val errorString = new StringBuilder("Error validating configuration field!"); + errorString.append("\nReason: ").append(reason); + errorString.append("\nClass: ").append(configClass.getName()) + .append("\nField: ").append(fieldName); + if (listElement) { + errorString.append("\nArray index: ").append(listIndex); + } + customText(errorString); + FalsePatternLib.getLog().error(errorString.toString()); + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/FalsePatternLib.java b/src/main/java/com/falsepattern/lib/internal/FalsePatternLib.java index 8b990d4..8766889 100644 --- a/src/main/java/com/falsepattern/lib/internal/FalsePatternLib.java +++ b/src/main/java/com/falsepattern/lib/internal/FalsePatternLib.java @@ -26,6 +26,8 @@ import cpw.mods.fml.common.*; import cpw.mods.fml.common.event.FMLConstructionEvent; import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.common.network.NetworkRegistry; +import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper; import lombok.Getter; @@ -46,6 +48,7 @@ import org.apache.logging.log4j.Logger; acceptableRemoteVersions = "*") public class FalsePatternLib { public static final String UPDATE_URL = "https://falsepattern.com/mc/versions.json"; + public static final SimpleNetworkWrapper NETWORK = NetworkRegistry.INSTANCE.newSimpleChannel(Tags.MODID); @Getter private static final Logger log = LogManager.getLogger(Tags.MODNAME); 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 e648872..fcc4992 100644 --- a/src/main/java/com/falsepattern/lib/internal/config/LibraryConfig.java +++ b/src/main/java/com/falsepattern/lib/internal/config/LibraryConfig.java @@ -38,4 +38,13 @@ public class LibraryConfig { @Config.LangKey("config.falsepatternlib.disableinternet") @Config.DefaultBoolean(true) public static boolean ENABLE_LIBRARY_DOWNLOADS; + + @Config.Comment("How \"loud\" the config error logging should be.") + @Config.LangKey("config.falsepatternlib.configlogging") + @Config.DefaultEnum("Log") + public static ValidationLogging CONFIG_ERROR_LOUDNESS; + + public enum ValidationLogging { + None, Log, LogAndToast + } } 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 b77bc7a..f9df8ce 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 @@ -22,34 +22,53 @@ package com.falsepattern.lib.internal.impl.config; import com.falsepattern.lib.config.Config; import com.falsepattern.lib.config.ConfigException; -import com.falsepattern.lib.config.IConfigElementProxy; +import com.falsepattern.lib.config.event.AllConfigSyncEvent; +import com.falsepattern.lib.config.event.ConfigSyncEvent; +import com.falsepattern.lib.config.event.ConfigSyncRequestEvent; +import com.falsepattern.lib.config.event.ConfigValidationFailureEvent; import com.falsepattern.lib.internal.FalsePatternLib; +import com.falsepattern.lib.internal.config.LibraryConfig; +import com.falsepattern.lib.internal.impl.config.net.SyncPrompt; +import com.falsepattern.lib.internal.impl.config.net.SyncRequest; +import com.falsepattern.lib.text.FormattedText; +import com.falsepattern.lib.toasts.GuiToast; +import com.falsepattern.lib.toasts.SimpleToast; +import com.falsepattern.lib.toasts.icon.ToastBG; import com.falsepattern.lib.util.FileUtil; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.SneakyThrows; import lombok.val; -import lombok.var; -import net.minecraftforge.common.config.ConfigElement; +import net.minecraft.client.entity.EntityClientPlayerMP; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.util.EnumChatFormatting; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.event.entity.EntityJoinWorldEvent; import cpw.mods.fml.client.config.IConfigElement; import cpw.mods.fml.client.event.ConfigChangedEvent; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; -import java.util.Arrays; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; -import java.util.regex.Pattern; -import java.util.stream.Collectors; +import java.util.function.BiConsumer; /** * The actual implementation of ConfigurationManager. Migrated stuff here so that we don't unnecessarily expose @@ -61,173 +80,166 @@ import java.util.stream.Collectors; public class ConfigurationManagerImpl { 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 ConfigurationManagerImpl instance = new ConfigurationManagerImpl(); private static boolean initialized = false; private static Path configDir; - public static void registerConfig(Class configClass) throws ConfigException { + public static void register(Class configClass) throws ConfigException { init(); - val cfg = Optional.ofNullable(configClass.getAnnotation(Config.class)) - .orElseThrow(() -> new ConfigException( - "Class " + configClass.getName() + " does not have a @Config annotation!")); - val category = Optional.of(cfg.category().trim()) - .map((cat) -> cat.length() == 0 ? null : cat) - .orElseThrow(() -> new ConfigException( - "Config class " + configClass.getName() + " has an empty category!")); - val rawConfig = configs.computeIfAbsent(cfg.modid(), (ignored) -> { - val c = new Configuration(configDir.resolve(cfg.modid() + ".cfg").toFile()); + if (!parsedConfigMap.containsKey(configClass)) { + val parsedConfig = ParsedConfiguration.parseConfig(configClass); + configToClassMap.computeIfAbsent(parsedConfig.rawConfig, (ignored) -> new HashSet<>()).add(configClass); + parsedConfigMap.put(configClass, ParsedConfiguration.parseConfig(configClass)); + } + } + + static Configuration getForgeConfig(String modid) { + return configs.computeIfAbsent(modid, (ignored) -> { + val c = new Configuration(configDir.resolve(modid + ".cfg").toFile()); c.load(); return c; }); - configToClassMap.computeIfAbsent(rawConfig, (ignored) -> new HashSet<>()).add(configClass); - try { - processConfigInternal(configClass, category, rawConfig); - rawConfig.save(); - } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException | NoSuchFieldException e) { - throw new ConfigException(e); - } } - @SuppressWarnings({"rawtypes", "unchecked"}) - public static List getConfigElements(Class configClass) throws ConfigException { - init(); - val cfg = Optional.ofNullable(configClass.getAnnotation(Config.class)) - .orElseThrow(() -> new ConfigException( - "Class " + configClass.getName() + " does not have a @Config annotation!")); - val rawConfig = Optional.ofNullable(configs.get(cfg.modid())) - .map((conf) -> Optional.ofNullable(configToClassMap.get(conf)) - .map((l) -> l.contains(configClass)) - .orElse(false) ? conf : null) - .orElseThrow(() -> new ConfigException( - "Tried to get config elements for non-registered config class!")); - val category = cfg.category(); - val elements = new ConfigElement<>(rawConfig.getCategory(category)).getChildElements(); - return elements.stream().map((element) -> new IConfigElementProxy(element, () -> { - try { - processConfigInternal(configClass, category, rawConfig); - rawConfig.save(); - } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException | NoSuchFieldException | - ConfigException e) { - e.printStackTrace(); + public static void load(Class configClass) throws ConfigException { + if (!parsedConfigMap.containsKey(configClass)) { + throw new ConfigException("Class " + configClass.getName() + " is not a registered configuration!"); + } + parsedConfigMap.get(configClass).load(); + } + + 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(); + } + + public static boolean validateFields(BiConsumer, Field> invalidFieldHandler, Class configClass, boolean resetInvalid) throws ConfigException { + if (!parsedConfigMap.containsKey(configClass)) { + throw new ConfigException("Class " + configClass.getName() + " is not a registered configuration!"); + } + val parsed = parsedConfigMap.get(configClass); + return parsed.validate(invalidFieldHandler, resetInvalid); + } + + @Deprecated + public static void registerLoadSaveConfig(Class configClass) throws ConfigException { + register(configClass); + load(configClass); + save(configClass); + } + + public static void sendRequest(DataOutput output) throws IOException { + val synced = new ArrayList>(); + for (val entry: parsedConfigMap.entrySet()) { + if (entry.getValue().sync) { + synced.add(entry.getKey()); } - })).collect(Collectors.toList()); + } + output.writeInt(synced.size()); + for (val clazz: synced) { + output.writeUTF(clazz.getName()); + } } - private static void processConfigInternal(Class configClass, String category, Configuration rawConfig) - throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException, - ConfigException { - val cat = rawConfig.getCategory(category); - if (configClass.isAnnotationPresent(Config.RequiresWorldRestart.class)) { - cat.setRequiresWorldRestart(true); + public static List> receiveRequest(DataInput input) throws IOException { + val result = new ArrayList>(); + val count = input.readInt(); + val classNames = new HashSet(); + for (int i = 0; i < count; i++) { + classNames.add(input.readUTF()); } - if (configClass.isAssignableFrom(Config.RequiresMcRestart.class)) { - cat.setRequiresMcRestart(true); + for (val entry: parsedConfigMap.keySet()) { + if (classNames.contains(entry.getName())) { + result.add(entry); + } } - for (val field : configClass.getDeclaredFields()) { - if (field.getAnnotation(Config.Ignore.class) != null) { + return result; + } + + public static void sendReply(DataOutput output, List> requestedClasses) throws IOException { + val syncEntries = new HashMap<>(parsedConfigMap); + for (val entry: parsedConfigMap.entrySet()) { + if (!entry.getValue().sync || !requestedClasses.contains(entry.getKey())) { + syncEntries.remove(entry.getKey()); + } + } + output.writeInt(syncEntries.size()); + for (val entry: syncEntries.entrySet()) { + output.writeUTF(entry.getKey().getName()); + val b = new ByteArrayOutputStream(); + val bo = new DataOutputStream(b); + entry.getValue().transmit(bo); + bo.close(); + val bytes = b.toByteArray(); + output.writeInt(bytes.length); + output.write(bytes); + } + } + + public static void receiveReply(DataInput input) throws IOException { + if (!AllConfigSyncEvent.postStart()) { + FalsePatternLib.getLog().warn("Config synchronization was cancelled by event."); + } + int count = input.readInt(); + for (int i = 0; i < count; i++) { + String className = input.readUTF(); + int dataSize = input.readInt(); + val opt = parsedConfigMap.keySet().stream().filter((clazz) -> clazz.getName().equals(className)).findFirst(); + if (!opt.isPresent()) { + input.skipBytes(dataSize); + FalsePatternLib.getLog().warn("Server tried to sync config not registered on our side: " + className); continue; } - field.setAccessible(true); - val comment = Optional.ofNullable(field.getAnnotation(Config.Comment.class)) - .map(Config.Comment::value) - .map((lines) -> String.join("\n", lines)) - .orElse(""); - val name = Optional.ofNullable(field.getAnnotation(Config.Name.class)) - .map(Config.Name::value) - .orElse(field.getName()); - val langKey = Optional.ofNullable(field.getAnnotation(Config.LangKey.class)) - .map(Config.LangKey::value) - .orElse(name); - val fieldClass = field.getType(); - var boxed = false; - if ((boxed = fieldClass.equals(Boolean.class)) || fieldClass.equals(boolean.class)) { - val defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultBoolean.class)) - .map(Config.DefaultBoolean::value) - .orElse(boxed ? (Boolean) field.get(null) : field.getBoolean(null)); - field.setBoolean(null, rawConfig.getBoolean(name, category, defaultValue, comment, langKey)); - } else if ((boxed = fieldClass.equals(Integer.class)) || fieldClass.equals(int.class)) { - val range = Optional.ofNullable(field.getAnnotation(Config.RangeInt.class)); - val min = range.map(Config.RangeInt::min).orElse(Integer.MIN_VALUE); - val max = range.map(Config.RangeInt::max).orElse(Integer.MAX_VALUE); - val defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultInt.class)) - .map(Config.DefaultInt::value) - .orElse(boxed ? (Integer) field.get(null) : field.getInt(null)); - field.setInt(null, rawConfig.getInt(name, category, defaultValue, min, max, comment, langKey)); - } else if ((boxed = fieldClass.equals(Float.class)) || fieldClass.equals(float.class)) { - val range = Optional.ofNullable(field.getAnnotation(Config.RangeFloat.class)); - val min = range.map(Config.RangeFloat::min).orElse(Float.MIN_VALUE); - val max = range.map(Config.RangeFloat::max).orElse(Float.MAX_VALUE); - val defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultFloat.class)) - .map(Config.DefaultFloat::value) - .orElse(boxed ? (Float) field.get(null) : field.getFloat(null)); - field.setFloat(null, rawConfig.getFloat(name, category, defaultValue, min, max, comment, langKey)); - } else if (fieldClass.equals(String.class)) { - val defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultString.class)) - .map(Config.DefaultString::value) - .orElse((String) field.get(null)); - val pattern = Optional.ofNullable(field.getAnnotation(Config.Pattern.class)) - .map(Config.Pattern::value) - .map(Pattern::compile) - .orElse(null); - field.set(null, rawConfig.getString(name, category, defaultValue, comment, langKey, pattern)); - } else if (fieldClass.isEnum()) { - val enumValues = Arrays.stream((Object[]) fieldClass.getDeclaredMethod("values").invoke(null)) - .map((obj) -> (Enum) obj) - .collect(Collectors.toList()); - val defaultValue = (Enum) Optional.ofNullable(field.getAnnotation(Config.DefaultEnum.class)) - .map(Config.DefaultEnum::value) - .map((defName) -> extractField(fieldClass, defName)) - .map(ConfigurationManagerImpl::extractValue) - .orElse(field.get(null)); - val possibleValues = enumValues.stream().map(Enum::name).toArray(String[]::new); - var value = rawConfig.getString(name, category, defaultValue.name(), - comment + "\nPossible values: " + Arrays.toString(possibleValues) + - "\n", possibleValues, langKey); + val clazz = opt.get(); + val config = parsedConfigMap.get(clazz); + if (!config.sync) { + input.skipBytes(dataSize); + FalsePatternLib.getLog().warn("Server tried to sync config without @Synchronize annotation on our side: " + className); + continue; + } + if (!ConfigSyncEvent.postStart(clazz)) { + input.skipBytes(dataSize); + FalsePatternLib.getLog().warn("Config synchronization was cancelled by event for: " + className); + continue; + } + val bytes = new byte[dataSize]; + input.readFully(bytes); + val b = new ByteArrayInputStream(bytes); + val bi = new DataInputStream(b); + try { + config.receive(bi); + config.validate((x, y) -> {}, true); + ConfigSyncEvent.postEndSuccess(clazz); + } catch (Throwable e) { + ConfigSyncEvent.postEndFailure(clazz, e); + } + bi.close(); + } + AllConfigSyncEvent.postEnd(); + } - try { - if (!Arrays.asList(possibleValues).contains(value)) { - throw new NoSuchFieldException(); - } - val enumField = fieldClass.getDeclaredField(value); - if (!enumField.isEnumConstant()) { - throw new NoSuchFieldException(); - } - field.set(null, enumField.get(null)); - } catch (NoSuchFieldException e) { - FalsePatternLib.getLog() - .warn("Invalid value " + value + " for enum configuration field " + field.getName() + - " of type " + fieldClass.getName() + " in config class " + - configClass.getName() + "! Using default value of " + defaultValue + "!"); - field.set(null, defaultValue); - } - } else if (fieldClass.isArray() && fieldClass.getComponentType().equals(String.class)) { - val defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultStringList.class)) - .map(Config.DefaultStringList::value) - .orElse((String[]) field.get(null)); - var value = rawConfig.getStringList(name, category, defaultValue, comment, null, langKey); - field.set(null, value); - } else { - throw new ConfigException("Illegal config field: " + field.getName() + " in " + configClass.getName() + - ": Unsupported type " + fieldClass.getName() + - "! Did you forget an @Ignore annotation?"); - } - if (field.isAnnotationPresent(Config.RequiresMcRestart.class)) { - cat.setRequiresMcRestart(true); - } - if (field.isAnnotationPresent(Config.RequiresWorldRestart.class)) { - cat.setRequiresWorldRestart(true); + public static void loadRawConfig(Configuration rawConfig) throws ConfigException { + rawConfig.load(); + for (val configClass: configToClassMap.get(rawConfig)) { + val config = parsedConfigMap.get(configClass); + try { + config.reloadFields(); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ConfigException(e); } } } - @SneakyThrows - private static Field extractField(Class clazz, String field) { - return clazz.getDeclaredField(field); - } - - @SneakyThrows - private static Object extractValue(Field field) { - return field.get(null); + @SuppressWarnings({"rawtypes"}) + public static List getConfigElements(Class configClass) throws ConfigException { + if (!parsedConfigMap.containsKey(configClass)) { + throw new ConfigException("Class " + configClass.getName() + " is not a registered configuration!"); + } + val config = parsedConfigMap.get(configClass); + return config.getConfigElements(); } private static void init() { @@ -240,26 +252,85 @@ public class ConfigurationManagerImpl { public static void registerBus() { FMLCommonHandler.instance().bus().register(instance); + MinecraftForge.EVENT_BUS.register(instance); + } + + private static void sendSyncRequest() throws IOException { + val event = new SyncRequest(); + event.transmit(); + FalsePatternLib.NETWORK.sendToServer(event); + } + + @SneakyThrows + @SubscribeEvent + public void onJoinWorld(EntityJoinWorldEvent e) { + if (e.world.isRemote && e.entity instanceof EntityClientPlayerMP) { + sendSyncRequest(); + } + } + + @SideOnly(Side.CLIENT) + @SneakyThrows + @SubscribeEvent + public void onSyncRequestClient(ConfigSyncRequestEvent.Client e) { + sendSyncRequest(); + } + + @SubscribeEvent + public void onSyncRequestServer(ConfigSyncRequestEvent.Server e) { + val players = e.getPlayers(); + if (players.size() == 0) { + FalsePatternLib.NETWORK.sendToAll(new SyncPrompt()); + } else { + for (EntityPlayerMP player : e.getPlayers()) { + FalsePatternLib.NETWORK.sendTo(new SyncPrompt(), player); + } + } } @SubscribeEvent public void onConfigChanged(ConfigChangedEvent.OnConfigChangedEvent event) { init(); - val config = configs.get(event.modID); - if (config == null) { + val rawConfig = configs.get(event.modID); + if (rawConfig == null) { return; } - val configClasses = configToClassMap.get(config); - configClasses.forEach((configClass) -> { - try { - val category = Optional.ofNullable(configClass.getAnnotation(Config.class)) - .map(Config::category) - .orElseThrow(() -> new ConfigException( - "Failed to get config category for class " + configClass.getName())); - processConfigInternal(configClass, category, config); - } catch (Exception e) { - e.printStackTrace(); - } - }); + val configClasses = configToClassMap.get(rawConfig); + for (val clazz: configClasses) { + val config = parsedConfigMap.get(clazz); + config.configChanged(); + } + } + + @SubscribeEvent + public void onValidationErrorLog(ConfigValidationFailureEvent e) { + if (LibraryConfig.CONFIG_ERROR_LOUDNESS != LibraryConfig.ValidationLogging.None) { + e.logWarn(); + } + } + + @SubscribeEvent + @SideOnly(Side.CLIENT) + public void onValidationErrorToast(ConfigValidationFailureEvent e) { + if (LibraryConfig.CONFIG_ERROR_LOUDNESS == LibraryConfig.ValidationLogging.LogAndToast) { + e.toast(); + } + } + + @SubscribeEvent + @SideOnly(Side.CLIENT) + public void onConfigSyncFinished(ConfigSyncEvent.End e) { + val cfg = e.configClass.getAnnotation(Config.class); + if (e.successful) { + GuiToast.add(new SimpleToast(ToastBG.TOAST_DARK, null, + FormattedText.parse(EnumChatFormatting.GREEN + "Synced config").toChatText().get(0), + FormattedText.parse(cfg.modid() + ":" + cfg.category()).toChatText().get(0), + false, 5000)); + } else { + GuiToast.add(new SimpleToast(ToastBG.TOAST_DARK, null, + FormattedText.parse(EnumChatFormatting.RED + "Failed to sync config").toChatText().get(0), + FormattedText.parse(cfg.modid() + ":" + cfg.category()).toChatText().get(0), + false, 5000)); + } } } 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 new file mode 100644 index 0000000..2c09771 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/ParsedConfiguration.java @@ -0,0 +1,223 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + * + * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + * + * This program 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. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.falsepattern.lib.internal.impl.config; + +import com.falsepattern.lib.config.Config; +import com.falsepattern.lib.config.ConfigException; +import com.falsepattern.lib.internal.impl.config.fields.AConfigField; +import com.falsepattern.lib.internal.impl.config.fields.BooleanConfigField; +import com.falsepattern.lib.internal.impl.config.fields.DoubleListConfigField; +import com.falsepattern.lib.internal.impl.config.fields.EnumConfigField; +import com.falsepattern.lib.internal.impl.config.fields.FloatConfigField; +import com.falsepattern.lib.internal.impl.config.fields.IntConfigField; +import com.falsepattern.lib.internal.impl.config.fields.IntListConfigField; +import com.falsepattern.lib.internal.impl.config.fields.StringConfigField; +import com.falsepattern.lib.internal.impl.config.fields.StringListConfigField; +import com.falsepattern.lib.util.ReflectionUtil; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.val; + +import net.minecraftforge.common.config.ConfigElement; +import net.minecraftforge.common.config.Configuration; + +import cpw.mods.fml.client.config.IConfigElement; + +import java.io.DataInput; +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.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiConsumer; + +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class ParsedConfiguration { + private static final Field propField; + + public final Class configClass; + public final String modid; + public final String category; + public final Configuration rawConfig; + public final boolean sync; + private final Map> fields = new HashMap<>(); + private final Map> elements = new HashMap<>(); + private int maxFieldNameLength; + + static { + try { + propField = ConfigElement.class.getDeclaredField("prop"); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + ReflectionUtil.jailBreak(propField); + } + + public static ParsedConfiguration parseConfig(Class configClass) throws ConfigException { + val cfg = Optional.ofNullable(configClass.getAnnotation(Config.class)) + .orElseThrow(() -> new ConfigException( + "Class " + configClass.getName() + " does not have a @Config annotation!")); + val category = Optional.of(cfg.category().trim()) + .map((cat) -> cat.length() == 0 ? null : cat) + .orElseThrow(() -> new ConfigException( + "Config class " + configClass.getName() + " has an empty category!")); + val rawConfig = ConfigurationManagerImpl.getForgeConfig(cfg.modid()); + val parsedConfig = new ParsedConfiguration(configClass, cfg.modid(), category, rawConfig, configClass.isAnnotationPresent(Config.Synchronize.class)); + try { + parsedConfig.reloadFields(); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ConfigException(e); + } + return parsedConfig; + } + + public void save() { + for (val field : fields.values()) { + field.save(); + } + rawConfig.save(); + } + + //Happens when changed through the gui + public void configChanged() { + for (val field: fields.values()) { + field.load(); + } + rawConfig.save(); + } + + public void load() throws ConfigException { + ConfigurationManagerImpl.loadRawConfig(rawConfig); + for (val field: fields.values()) { + field.load(); + } + } + + public void receive(DataInput input) throws IOException { + if (sync) { + val syncFields = new HashMap<>(fields); + for (val key: fields.keySet()) { + if (syncFields.get(key).noSync) { + syncFields.remove(key); + } + } + while (syncFields.size() > 0) { + val fieldName = StringConfigField.receiveString(input, maxFieldNameLength, "field name", configClass.getName()); + if (!syncFields.containsKey(fieldName)) { + throw new IOException("Invalid sync field name received: " + fieldName + " for config class " + configClass.getName()); + } + syncFields.remove(fieldName).receive(input); + } + } + } + + public void transmit(DataOutput output) throws IOException { + if (sync) { + val syncFields = new HashMap<>(fields); + for (val key: fields.keySet()) { + if (syncFields.get(key).noSync) { + syncFields.remove(key); + } + } + for (val field: syncFields.entrySet()) { + StringConfigField.transmitString(output, field.getKey()); + field.getValue().transmit(output); + } + } + } + + public void reloadFields() throws ConfigException, NoSuchFieldException, IllegalAccessException { + fields.clear(); + maxFieldNameLength = 0; + val cat = rawConfig.getCategory(category); + if (configClass.isAnnotationPresent(Config.RequiresWorldRestart.class)) { + cat.setRequiresWorldRestart(true); + } + if (configClass.isAssignableFrom(Config.RequiresMcRestart.class)) { + cat.setRequiresMcRestart(true); + } + for (val field : configClass.getDeclaredFields()) { + if (field.getAnnotation(Config.Ignore.class) != null && (field.getModifiers() & Modifier.FINAL) != 0) { + continue; + } + field.setAccessible(true); + maxFieldNameLength = Math.max(maxFieldNameLength, field.getName().length()); + val fieldClass = field.getType(); + val name = field.getName(); + if (fieldClass.equals(Boolean.class) || fieldClass.equals(boolean.class)) { + fields.put(name, new BooleanConfigField(field, rawConfig, category)); + } else if (fieldClass.equals(Integer.class) || fieldClass.equals(int.class)) { + fields.put(name, new IntConfigField(field, rawConfig, category)); + } else if (fieldClass.equals(Float.class) || fieldClass.equals(float.class)) { + //noinspection deprecation + fields.put(name, new FloatConfigField(field, rawConfig, category)); + } else if (fieldClass.equals(String.class)) { + fields.put(name, new StringConfigField(field, rawConfig, category)); + } else if (fieldClass.isEnum()) { + fields.put(name, new EnumConfigField<>(field, rawConfig, category)); + } else if (fieldClass.isArray() && fieldClass.getComponentType().equals(boolean.class)) { + fields.put(name, new StringListConfigField(field, rawConfig, category)); + } else if (fieldClass.isArray() && fieldClass.getComponentType().equals(int.class)) { + fields.put(name, new IntListConfigField(field, rawConfig, category)); + } else if (fieldClass.isArray() && fieldClass.getComponentType().equals(double.class)) { + fields.put(name, new DoubleListConfigField(field, rawConfig, category)); + } else if (fieldClass.isArray() && fieldClass.getComponentType().equals(String.class)) { + fields.put(name, new StringListConfigField(field, rawConfig, category)); + } else { + throw new ConfigException("Illegal config field: " + field.getName() + " in " + configClass.getName() + + ": Unsupported type " + fieldClass.getName() + + "! Did you forget an @Ignore annotation?"); + } + if (field.isAnnotationPresent(Config.RequiresMcRestart.class)) { + cat.setRequiresMcRestart(true); + } + if (field.isAnnotationPresent(Config.RequiresWorldRestart.class)) { + cat.setRequiresWorldRestart(true); + } + val prop = fields.get(name).getProperty(); + val configElement = elements.computeIfAbsent(name, (name2) -> new ConfigElement<>(prop)); + propField.set(configElement, prop); + } + } + + @SuppressWarnings("rawtypes") + public List getConfigElements() { + return new ArrayList<>(elements.values()); + } + + public boolean validate(BiConsumer, Field> invalidFieldHandler, boolean resetInvalid) { + boolean valid = true; + for (val field: fields.values()) { + if (!field.validateField()) { + if (resetInvalid) { + field.setToDefault(); + } + invalidFieldHandler.accept(configClass, field.getJavaField()); + valid = false; + } + } + return valid; + } +} 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 new file mode 100644 index 0000000..cb9da41 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/AConfigField.java @@ -0,0 +1,114 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.falsepattern.lib.internal.impl.config.fields; + +import com.falsepattern.lib.config.Config; + +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Optional; + +public abstract class AConfigField { + public final boolean noSync; + protected final Field field; + protected final Configuration configuration; + protected final String category; + protected final String name; + protected final String langKey; + protected final Property.Type type; + protected final Property property; + protected String comment; + + protected AConfigField(Field field, Configuration configuration, String category, Property.Type type) { + this(field, configuration, category, type, false); + } + + protected AConfigField(Field field, Configuration configuration, String category, Property.Type type, boolean isList) { + this.field = field; + this.configuration = configuration; + this.category = 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()); + langKey = + Optional.ofNullable(field.getAnnotation(Config.LangKey.class)).map(Config.LangKey::value).orElse(name); + this.type = type; + if (isList) { + property = configuration.get(category, name, new String[0], comment, type); + } else { + property = configuration.get(category, name, "", comment, type); + } + property.setLanguageKey(langKey); + noSync = field.isAnnotationPresent(Config.NoSync.class); + } + + protected abstract T getField(); + + protected abstract void putField(T value); + + protected abstract T getConfig(); + + protected abstract void putConfig(T value); + + protected abstract T getDefault(); + + public void save() { + if (!validateField()) { + setToDefault(); + } + putConfig(getField()); + } + + public void load() { + putField(getConfig()); + if (!validateField()) { + setToDefault(); + } + } + + public void setToDefault() { + putField(getDefault()); + putConfig(getDefault()); + } + + public Property getProperty() { + return property; + } + + public abstract boolean validateField(); + + public abstract void transmit(DataOutput output) throws IOException; + + public abstract void receive(DataInput input) throws IOException; + + public Field getJavaField() { + return field; + } +} 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 new file mode 100644 index 0000000..88ac9a7 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/AListConfigField.java @@ -0,0 +1,114 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +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 lombok.SneakyThrows; +import lombok.val; + +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Optional; + +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) + throws ConfigException { + super(field, configuration, category, type, true); + fixedLength = field.isAnnotationPresent(Config.ListFixedLength.class); + maxLength = Optional.ofNullable(field.getAnnotation(Config.ListMaxLength.class)) + .map(Config.ListMaxLength::value) + .orElse(256); + if (maxLength < 0) { + throw new ConfigException( + "Negative length list configurations are not supported!\n" + "Field name: " + field.getName() + + ", class: " + field.getDeclaringClass().getName()); + } + property.setIsListLengthFixed(fixedLength); + property.setMaxListLength(maxLength); + property.comment += "\n[fixed length: " + (fixedLength ? "yes" : "no") + ", max length: " + maxLength + "]"; + } + + protected abstract int length(T arr); + + protected abstract T arrayCopy(T arr); + + protected abstract void transmitElements(DataOutput output, T arr) throws IOException; + + protected abstract void receiveElements(DataInput input, T arr) throws IOException; + + protected abstract T createArray(int length); + + + @SuppressWarnings("unchecked") + @SneakyThrows + @Override + protected T getField() { + return arrayCopy((T) field.get(null)); + } + + @SneakyThrows + @Override + protected void putField(T value) { + field.set(null, arrayCopy(value)); + } + + @Override + public boolean validateField() { + val f = getField(); + if (f == null) { + ConfigValidationFailureEvent.fieldIsNull(field, -1); + return false; + } + if ((fixedLength && length(getDefault()) != length(f)) || (maxLength >= 0 && length(f) > maxLength)) { + ConfigValidationFailureEvent.postSize(field, length(f), fixedLength, maxLength, length(getDefault())); + return false; + } + return true; + } + + @Override + public void transmit(DataOutput output) throws IOException { + val f = getField(); + output.writeInt(length(f)); + transmitElements(output, f); + } + + @Override + public void receive(DataInput input) throws IOException { + int length = input.readInt(); + if (length > maxLength || fixedLength && length(getDefault()) != length) { + throw new IOException("Error while retrieving config value for field " + field.getName() + " in class " + + field.getDeclaringClass().getName() + ":\n" + "Illegal array length received!"); + } + val arr = createArray(length); + receiveElements(input, arr); + } +} 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 new file mode 100644 index 0000000..bcf2ec7 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/BooleanConfigField.java @@ -0,0 +1,115 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +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.FalsePatternLib; +import lombok.SneakyThrows; + +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Optional; + +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); + primitive = field.getType().isPrimitive(); + defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultBoolean.class)) + .map(Config.DefaultBoolean::value) + .orElseGet(() -> { + FalsePatternLib.getLog() + .warn("The field " + field.getName() + " in class " + + field.getDeclaringClass().getName() + + " has no DefaultBoolean annotation!\nThis will be a crash in FalsePatternLib 0.11, update your code!"); + try { + return field.getType().isPrimitive() ? field.getBoolean(null) + : (Boolean) field.get(null); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + property.setDefaultValue(defaultValue); + if (!property.isBooleanValue()) { + setToDefault(); + } + property.comment += "\n[default: " + defaultValue + "]"; + } + + @SneakyThrows + @Override + protected Boolean getField() { + return primitive ? field.getBoolean(null) : (Boolean) field.get(null); + } + + @SneakyThrows + @Override + protected void putField(Boolean value) { + if (primitive) { + field.setBoolean(null, value); + } else { + field.set(null, value); + } + } + + @Override + protected Boolean getConfig() { + return property.getBoolean(); + } + + @Override + protected void putConfig(Boolean value) { + property.set(value); + } + + @Override + protected Boolean getDefault() { + return defaultValue; + } + + @SneakyThrows + @Override + public boolean validateField() { + if (!primitive && field.get(null) == null) { + ConfigValidationFailureEvent.fieldIsNull(field, -1); + return false; + } + return true; + } + + @Override + public void transmit(DataOutput output) throws IOException { + output.writeBoolean(getField()); + } + + @Override + public void receive(DataInput input) throws IOException { + putField(input.readBoolean()); + } +} 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 new file mode 100644 index 0000000..10f7154 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/BooleanListConfigField.java @@ -0,0 +1,114 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +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.FalsePatternLib; +import lombok.val; + +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Optional; + +public class BooleanListConfigField extends AListConfigField { + private final boolean[] defaultValue; + + protected BooleanListConfigField(Field field, Configuration configuration, String category) throws ConfigException { + super(field, configuration, category, Property.Type.BOOLEAN); + defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultBooleanList.class)) + .map(Config.DefaultBooleanList::value) + .orElseGet(() -> { + FalsePatternLib.getLog() + .warn("The field " + field.getName() + " in class " + + field.getDeclaringClass().getName() + + " has no DefaultBooleanList annotation!\nThis will be a crash in FalsePatternLib 0.11, update your code!"); + try { + return (boolean[]) field.get(null); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + property.setDefaultValues(defaultValue); + if (!property.isBooleanList()) { + setToDefault(); + } + property.comment += "\n[default: " + Arrays.toString(defaultValue) + "]"; + } + + @Override + protected int length(boolean[] arr) { + return arr.length; + } + + @Override + protected boolean[] arrayCopy(boolean[] arr) { + return Arrays.copyOf(arr, arr.length); + } + + @Override + protected void transmitElements(DataOutput output, boolean[] arr) throws IOException { + val transfer = new byte[(arr.length + 7) >>> 3]; + for (int i = 0; i < arr.length; i++) { + transfer[i >>> 3] |= arr[i] ? 1 << (i & 0x7) : 0; + } + for (val i : transfer) { + output.writeByte(i); + } + } + + @Override + protected void receiveElements(DataInput input, boolean[] arr) throws IOException { + val transfer = new byte[(arr.length + 7) >>> 3]; + for (int i = 0; i < transfer.length; i++) { + transfer[i] = input.readByte(); + } + for (int i = 0; i < arr.length; i++) { + arr[i] = (transfer[i >>> 3] & (1 << (i & 0x7))) != 0; + } + } + + @Override + protected boolean[] createArray(int length) { + return new boolean[length]; + } + + @Override + protected boolean[] getConfig() { + return property.getBooleanList(); + } + + @Override + protected void putConfig(boolean[] value) { + property.set(value); + } + + @Override + protected boolean[] getDefault() { + return Arrays.copyOf(defaultValue, defaultValue.length); + } +} 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 new file mode 100644 index 0000000..5e1b569 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/DoubleConfigField.java @@ -0,0 +1,128 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +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.FalsePatternLib; +import lombok.SneakyThrows; +import lombok.val; + +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Optional; + +public class DoubleConfigField extends AConfigField { + private final boolean primitive; + private final double defaultValue; + private final double min; + private final double max; + + public DoubleConfigField(Field field, Configuration configuration, String category) { + super(field, configuration, category, 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); + max = range.map(Config.RangeDouble::max).orElse(Double.MAX_VALUE); + defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultDouble.class)) + .map(Config.DefaultDouble::value) + .orElseGet(() -> { + FalsePatternLib.getLog() + .warn("The field " + field.getName() + " in class " + + field.getDeclaringClass().getName() + + " has no DefaultDouble annotation!\nThis will be a crash in FalsePatternLib 0.11, update your code!"); + try { + return field.getType().isPrimitive() ? field.getInt(null) + : (Double) field.get(null); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + property.setDefaultValue(defaultValue); + property.setMinValue(min); + property.setMaxValue(max); + if (!property.isDoubleValue()) { + setToDefault(); + } + property.comment += "\n[range: " + min + " ~ " + max + ", default: " + defaultValue + "]"; + } + + @SneakyThrows + @Override + protected Double getField() { + return primitive ? field.getDouble(null) : (Double) field.get(null); + } + + @SneakyThrows + @Override + protected void putField(Double value) { + if (primitive) { + field.setDouble(null, value); + } else { + field.set(null, value); + } + } + + @Override + protected Double getConfig() { + return property.getDouble(); + } + + @Override + protected void putConfig(Double value) { + property.set(value); + } + + @Override + protected Double getDefault() { + return defaultValue; + } + + @SneakyThrows + @Override + public boolean validateField() { + if (!primitive && field.get(null) == null) { + ConfigValidationFailureEvent.fieldIsNull(field, -1); + return false; + } + val value = (double) getField(); + if (value >= min && value <= max) { + return true; + } + ConfigValidationFailureEvent.postNumericRangeOutOfBounds(field, -1, Double.toString(value), Double.toString(min), Double.toString(max)); + return false; + } + + @Override + public void transmit(DataOutput output) throws IOException { + output.writeDouble(getField()); + } + + @Override + public void receive(DataInput input) throws IOException { + putField(input.readDouble()); + } +} 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 new file mode 100644 index 0000000..9b3d229 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/DoubleListConfigField.java @@ -0,0 +1,131 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +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.FalsePatternLib; +import lombok.val; + +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Optional; + +public class DoubleListConfigField extends AListConfigField { + private final double min; + private final double max; + private final double[] defaultValue; + + public DoubleListConfigField(Field field, Configuration configuration, String category) throws ConfigException { + super(field, configuration, category, Property.Type.INTEGER); + 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); + defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultDoubleList.class)) + .map(Config.DefaultDoubleList::value) + .orElseGet(() -> { + FalsePatternLib.getLog() + .warn("The field " + field.getName() + " in class " + + field.getDeclaringClass().getName() + + " has no DefaultDoubleList annotation!\nThis will be a crash in FalsePatternLib 0.11, update your code!"); + try { + return (double[]) field.get(null); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + property.setDefaultValues(defaultValue); + property.setMinValue(min); + property.setMaxValue(max); + if (!property.isIntList()) { + setToDefault(); + } + property.comment += "\n[range: " + min + " ~ " + max + ", default: " + Arrays.toString(defaultValue) + "]"; + } + + @Override + protected int length(double[] arr) { + return arr.length; + } + + @Override + protected double[] arrayCopy(double[] arr) { + return Arrays.copyOf(arr, arr.length); + } + + @Override + protected void transmitElements(DataOutput output, double[] arr) throws IOException { + for (val i : arr) { + output.writeDouble(i); + } + } + + @Override + protected void receiveElements(DataInput input, double[] arr) throws IOException { + for (int i = 0; i < arr.length; i++) { + arr[i] = input.readInt(); + } + } + + @Override + protected double[] createArray(int length) { + return new double[length]; + } + + @Override + public boolean validateField() { + if (!super.validateField()) { + return false; + } + double[] doubles = getField(); + boolean valid = true; + for (int j = 0; j < doubles.length; j++) { + double d = doubles[j]; + if (d < min || d > max) { + ConfigValidationFailureEvent.postNumericRangeOutOfBounds(field, j, Double.toString(d), Double.toString(min), Double.toString(max)); + valid = false; + } + } + return valid; + } + + @Override + protected double[] getConfig() { + return property.getDoubleList(); + } + + @Override + protected void putConfig(double[] value) { + property.set(value); + } + + @Override + protected double[] getDefault() { + return Arrays.copyOf(defaultValue, defaultValue.length); + } +} 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 new file mode 100644 index 0000000..a2ae305 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/EnumConfigField.java @@ -0,0 +1,160 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +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.FalsePatternLib; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import lombok.SneakyThrows; +import lombok.val; + +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Optional; + +public class EnumConfigField> extends AConfigField { + private final int maxLength; + private final Class enumClass; + private final T defaultValue; + private final BiMap enumNameMap = HashBiMap.create(); + + @SuppressWarnings("unchecked") + public EnumConfigField(Field field, Configuration configuration, String category) { + super(field, configuration, category, Property.Type.STRING); + enumClass = (Class) field.getType(); + for (val e : enumClass.getEnumConstants()) { + enumNameMap.put(e.name(), e); + } + defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultEnum.class)) + .map(Config.DefaultEnum::value) + .map((defName) -> enumNameMap.values() + .stream() + .filter((e) -> e.name().equals(defName)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("Default value \"" + defName + "\" was not found in enum " + enumClass.getName()))) + .orElseGet(() -> { + FalsePatternLib.getLog() + .warn("The field " + field.getName() + " in class " + + field.getDeclaringClass().getName() + + " has no DefaultEnum annotation!\nThis will be a crash in FalsePatternLib 0.11, update your code!"); + try { + return enumClass.cast(field.get(null)); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + maxLength = enumNameMap.keySet().stream().mapToInt(String::length).max().orElse(0); + property.setDefaultValue(defaultValue.name()); + property.setValidValues(enumNameMap.keySet().toArray(new String[0])); + if (!enumNameMap.containsValue(getConfig())) { + setToDefault(); + } + property.comment += "\n[default: " + defaultValue + ", possible values: " + + Arrays.toString(enumNameMap.keySet().toArray(new String[0])) + "]"; + } + + public static void transmitString(DataOutput output, String value) throws IOException { + output.writeInt(value.length()); + output.writeChars(value); + } + + public static String receiveString(DataInput input, int maxLength, String fieldName, String className) + throws IOException { + val length = input.readInt(); + if (length > maxLength || length < 0) { + throw new IOException( + "Error while retrieving config value for field " + fieldName + " in class " + className + ":\n" + + "Illegal string length received!"); + } + val arr = new char[length]; + for (int i = 0; i < arr.length; i++) { + arr[i] = input.readChar(); + } + return new String(arr); + } + + private T getEnumByName(String name) { + if (!enumNameMap.containsKey(name)) { + FalsePatternLib.getLog() + .warn("Invalid value " + name + " for enum configuration field " + field.getName() + + " of type " + enumClass.getName() + " in config class " + + field.getDeclaringClass().getName() + "! Using default value of " + + defaultValue.name() + "!"); + return defaultValue; + } + return enumNameMap.get(name); + } + + @SneakyThrows + @Override + protected T getField() { + return enumClass.cast(field.get(null)); + } + + @SneakyThrows + @Override + protected void putField(T value) { + field.set(null, value); + } + + @Override + protected T getConfig() { + return getEnumByName(property.getString()); + } + + @Override + protected void putConfig(T value) { + property.set(value.name()); + } + + @Override + protected T getDefault() { + return defaultValue; + } + + @SneakyThrows + @Override + public boolean validateField() { + if (getField() == null) { + ConfigValidationFailureEvent.fieldIsNull(field, -1); + return false; + } + return true; + } + + @Override + public void transmit(DataOutput output) throws IOException { + transmitString(output, getField().name()); + } + + @Override + public void receive(DataInput input) throws IOException { + putField(getEnumByName(receiveString(input, maxLength, field.getName(), field.getDeclaringClass().getName()))); + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/FloatConfigField.java b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/FloatConfigField.java new file mode 100644 index 0000000..db8975c --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/FloatConfigField.java @@ -0,0 +1,137 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +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.FalsePatternLib; +import com.falsepattern.lib.util.MathUtil; +import lombok.SneakyThrows; +import lombok.val; + +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Optional; + +@SuppressWarnings("DeprecatedIsStillUsed") +@Deprecated +public class FloatConfigField extends AConfigField { + private final boolean primitive; + private final float defaultValue; + private final boolean hasMinMax; + private final float min; + private final float max; + + public FloatConfigField(Field field, Configuration configuration, String category) { + super(field, configuration, category, Property.Type.STRING); + FalsePatternLib.getLog() + .warn("Warning: float configuration properties are DEPRECATED! Use doubles instead!\n" + + "Field name: " + field.getName() + ", location: " + field.getDeclaringClass().getName()); + primitive = field.getType().isPrimitive(); + val range = Optional.ofNullable(field.getAnnotation(Config.RangeFloat.class)); + hasMinMax = range.isPresent(); + min = hasMinMax ? range.map(Config.RangeFloat::min).orElse(-Float.MAX_VALUE) : 0; + max = hasMinMax ? range.map(Config.RangeFloat::max).orElse(Float.MAX_VALUE) : 0; + defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultFloat.class)) + .map(Config.DefaultFloat::value) + .orElseGet(() -> { + try { + return field.getType().isPrimitive() ? field.getFloat(null) + : (Float) field.get(null); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + property.setDefaultValue(defaultValue); + property.setMinValue(min); + property.setMaxValue(max); + property.comment += "\n[range: " + min + " ~ " + max + ", default: " + defaultValue + "]"; + } + + @SneakyThrows + @Override + protected Float getField() { + return primitive ? field.getFloat(null) : (Integer) field.get(null); + } + + @SneakyThrows + @Override + protected void putField(Float value) { + if (primitive) { + field.setFloat(null, value); + } else { + field.set(null, value); + } + } + + @Override + protected Float getConfig() { + try { + return MathUtil.clamp(Float.parseFloat(property.getString()), min, max); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + @Override + protected void putConfig(Float value) { + property.set(Float.toString(value)); + } + + @Override + protected Float getDefault() { + return defaultValue; + } + + @SneakyThrows + @Override + public boolean validateField() { + if (!primitive && field.get(null) == null) { + ConfigValidationFailureEvent.fieldIsNull(field, -1); + return false; + } + if (hasMinMax) { + val value = (float) getField(); + if (value >= min && value <= max) { + return true; + } + ConfigValidationFailureEvent.postNumericRangeOutOfBounds(field, -1, Float.toString(value), Float.toString(min), Float.toString(max)); + return false; + } else { + return true; + } + } + + @Override + public void transmit(DataOutput output) throws IOException { + output.writeFloat(getField()); + } + + @Override + public void receive(DataInput input) throws IOException { + putField(input.readFloat()); + } +} 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 new file mode 100644 index 0000000..696aeb4 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/IntConfigField.java @@ -0,0 +1,128 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +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.FalsePatternLib; +import lombok.SneakyThrows; +import lombok.val; + +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Optional; + +public class IntConfigField extends AConfigField { + private final boolean primitive; + private final int defaultValue; + private final int min; + private final int max; + + public IntConfigField(Field field, Configuration configuration, String category) { + super(field, configuration, category, 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); + max = range.map(Config.RangeInt::max).orElse(Integer.MAX_VALUE); + defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultInt.class)) + .map(Config.DefaultInt::value) + .orElseGet(() -> { + FalsePatternLib.getLog() + .warn("The field " + field.getName() + " in class " + + field.getDeclaringClass().getName() + + " has no DefaultInt annotation!\nThis will be a crash in FalsePatternLib 0.11, update your code!"); + try { + return field.getType().isPrimitive() ? field.getInt(null) + : (Integer) field.get(null); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + property.setDefaultValue(defaultValue); + property.setMinValue(min); + property.setMaxValue(max); + if (!property.isIntValue()) { + setToDefault(); + } + property.comment += "\n[range: " + min + " ~ " + max + ", default: " + defaultValue + "]"; + } + + @SneakyThrows + @Override + protected Integer getField() { + return primitive ? field.getInt(null) : (Integer) field.get(null); + } + + @SneakyThrows + @Override + protected void putField(Integer value) { + if (primitive) { + field.setInt(null, value); + } else { + field.set(null, value); + } + } + + @Override + protected Integer getConfig() { + return property.getInt(); + } + + @Override + protected void putConfig(Integer value) { + property.set(value); + } + + @Override + protected Integer getDefault() { + return defaultValue; + } + + @SneakyThrows + @Override + public boolean validateField() { + if (!primitive && field.get(null) == null) { + ConfigValidationFailureEvent.fieldIsNull(field, -1); + return false; + } + val value = (int) getField(); + if (value >= min && value <= max) { + return true; + } + ConfigValidationFailureEvent.postNumericRangeOutOfBounds(field, -1, Integer.toString(value), Integer.toString(min), Integer.toString(max)); + return false; + } + + @Override + public void transmit(DataOutput output) throws IOException { + output.writeInt(getField()); + } + + @Override + public void receive(DataInput input) throws IOException { + putField(input.readInt()); + } +} 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 new file mode 100644 index 0000000..6e14b63 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/IntListConfigField.java @@ -0,0 +1,131 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +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.FalsePatternLib; +import lombok.val; + +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Optional; + +public class IntListConfigField extends AListConfigField { + private final int min; + 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); + 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); + defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultIntList.class)) + .map(Config.DefaultIntList::value) + .orElseGet(() -> { + FalsePatternLib.getLog() + .warn("The field " + field.getName() + " in class " + + field.getDeclaringClass().getName() + + " has no DefaultIntList annotation!\nThis will be a crash in FalsePatternLib 0.11, update your code!"); + try { + return (int[]) field.get(null); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + property.setDefaultValues(defaultValue); + property.setMinValue(min); + property.setMaxValue(max); + if (!property.isIntList()) { + setToDefault(); + } + property.comment += "\n[range: " + min + " ~ " + max + ", default: " + Arrays.toString(defaultValue) + "]"; + } + + @Override + protected int length(int[] arr) { + return arr.length; + } + + @Override + protected int[] arrayCopy(int[] arr) { + return Arrays.copyOf(arr, arr.length); + } + + @Override + protected void transmitElements(DataOutput output, int[] arr) throws IOException { + for (val i : arr) { + output.writeInt(i); + } + } + + @Override + protected void receiveElements(DataInput input, int[] arr) throws IOException { + for (int i = 0; i < arr.length; i++) { + arr[i] = input.readInt(); + } + } + + @Override + protected int[] createArray(int length) { + return new int[length]; + } + + @Override + public boolean validateField() { + if (!super.validateField()) { + return false; + } + int[] ints = getField(); + boolean valid = true; + for (int j = 0; j < ints.length; j++) { + int i = ints[j]; + if (i < min || i > max) { + ConfigValidationFailureEvent.postNumericRangeOutOfBounds(field, j, Integer.toString(i), Integer.toString(min), Integer.toString(max)); + valid = false; + } + } + return valid; + } + + @Override + protected int[] getConfig() { + return property.getIntList(); + } + + @Override + protected void putConfig(int[] value) { + property.set(value); + } + + @Override + protected int[] getDefault() { + return Arrays.copyOf(defaultValue, defaultValue.length); + } +} 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 new file mode 100644 index 0000000..903b8ad --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/StringConfigField.java @@ -0,0 +1,151 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +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.FalsePatternLib; +import lombok.SneakyThrows; +import lombok.val; + +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Optional; +import java.util.regex.Pattern; + +public class StringConfigField extends AConfigField { + private final int maxLength; + private final Pattern pattern; + private final String defaultValue; + + public StringConfigField(Field field, Configuration configuration, String category) { + super(field, configuration, category, Property.Type.STRING); + pattern = Optional.ofNullable(field.getAnnotation(Config.Pattern.class)) + .map((ptr) -> Pattern.compile(ptr.value())) + .orElse(null); + defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultString.class)) + .map(Config.DefaultString::value) + .orElseGet(() -> { + FalsePatternLib.getLog() + .warn("The field " + field.getName() + " in class " + + field.getDeclaringClass().getName() + + " has no DefaultDouble annotation!\nThis will be a crash in FalsePatternLib 0.11, update your code!"); + try { + return (String) field.get(null); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + maxLength = Optional.ofNullable(field.getAnnotation(Config.StringMaxLength.class)) + .map(Config.StringMaxLength::value) + .orElse(256); + property.setDefaultValue(defaultValue); + if (!property.isDoubleValue()) { + setToDefault(); + } + property.comment += + "\n[max length: " + maxLength + (pattern != null ? ", pattern: \"" + pattern.pattern() + "\"" : "") + + ", default: \"" + defaultValue + "\"]"; + } + + public static boolean validateString(String value, int maxLength, Pattern pattern, Field field, int listIndex) { + if (value == null) { + ConfigValidationFailureEvent.fieldIsNull(field, listIndex); + return false; + } + if (value.length() <= maxLength) { + ConfigValidationFailureEvent.postStringSizeOutOfBounds(field, listIndex, value, maxLength); + return false; + } + if (pattern != null && !pattern.matcher(value).matches()) { + ConfigValidationFailureEvent.postStringPatternMismatch(field, listIndex, value, pattern.pattern()); + return false; + } + return true; + } + + public static void transmitString(DataOutput output, String value) throws IOException { + output.writeInt(value.length()); + output.writeChars(value); + } + + public static String receiveString(DataInput input, int maxLength, String valueName, String className) + throws IOException { + val length = input.readInt(); + if (length > maxLength || length < 0) { + throw new IOException("Error while retrieving value for " + valueName + " in class " + className + ":\n" + + "Illegal string length received!"); + } + val arr = new char[length]; + for (int i = 0; i < arr.length; i++) { + arr[i] = input.readChar(); + } + return new String(arr); + } + + @SneakyThrows + @Override + protected String getField() { + return (String) field.get(null); + } + + @SneakyThrows + @Override + protected void putField(String value) { + field.set(null, value); + } + + @Override + protected String getConfig() { + return property.getString(); + } + + @Override + protected void putConfig(String value) { + property.set(value); + } + + @Override + protected String getDefault() { + return defaultValue; + } + + @SneakyThrows + @Override + public boolean validateField() { + return validateString(getField(), maxLength, pattern, field, -1); + } + + @Override + public void transmit(DataOutput output) throws IOException { + transmitString(output, getField()); + } + + @Override + public void receive(DataInput input) throws IOException { + putField(receiveString(input, maxLength, field.getName(), field.getDeclaringClass().getName())); + } +} 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 new file mode 100644 index 0000000..92dd975 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/StringListConfigField.java @@ -0,0 +1,145 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +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.FalsePatternLib; +import lombok.val; + +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Pattern; + +public class StringListConfigField extends AListConfigField { + private final String[] defaultValue; + 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); + pattern = Optional.ofNullable(field.getAnnotation(Config.Pattern.class)) + .map((ptr) -> Pattern.compile(ptr.value())) + .orElse(null); + defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultStringList.class)) + .map(Config.DefaultStringList::value) + .orElseGet(() -> { + FalsePatternLib.getLog() + .warn("The field " + field.getName() + " in class " + + field.getDeclaringClass().getName() + + " has no DefaultStringList annotation!\nThis will be a crash in FalsePatternLib 0.11, update your code!"); + try { + return (String[]) field.get(null); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + maxStringLength = Optional.ofNullable(field.getAnnotation(Config.StringMaxLength.class)) + .map(Config.StringMaxLength::value) + .orElse(256); + property.setDefaultValues(defaultValue); + if (!property.isList()) { + setToDefault(); + } + property.comment += "\n[max string length: " + maxLength + + (pattern != null ? ", pattern: \"" + pattern.pattern() + "\"" : "") + ", default: \"" + + stringify(defaultValue) + "\"]"; + } + + private static String stringify(String[] arr) { + val result = new StringBuilder("["); + if (arr.length > 0) { + result.append('"').append(arr[0]).append('"'); + for (int i = 1; i < arr.length; i++) { + result.append(", \"").append(arr[i]).append('"'); + } + } + return result.append(']').toString(); + } + + @Override + protected int length(String[] arr) { + return arr.length; + } + + @Override + protected String[] arrayCopy(String[] arr) { + return Arrays.copyOf(arr, arr.length); + } + + @Override + protected void transmitElements(DataOutput output, String[] arr) throws IOException { + for (val str : arr) { + StringConfigField.transmitString(output, str); + } + } + + @Override + protected void receiveElements(DataInput input, String[] arr) throws IOException { + for (int i = 0; i < arr.length; i++) { + arr[i] = StringConfigField.receiveString(input, maxStringLength, field.getName(), + field.getDeclaringClass().getName()); + } + } + + @Override + public boolean validateField() { + if (!super.validateField()) { + return false; + } + String[] strings = getField(); + boolean valid = true; + for (int i = 0; i < strings.length; i++) { + String str = strings[i]; + if (!StringConfigField.validateString(str, maxStringLength, pattern, field, i)) { + valid = false; + } + } + return valid; + } + + @Override + protected String[] createArray(int length) { + return new String[length]; + } + + @Override + protected String[] getConfig() { + return property.getStringList(); + } + + @Override + protected void putConfig(String[] value) { + property.set(value); + } + + @Override + protected String[] getDefault() { + return Arrays.copyOf(defaultValue, defaultValue.length); + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/net/CompressedMessage.java b/src/main/java/com/falsepattern/lib/internal/impl/config/net/CompressedMessage.java new file mode 100644 index 0000000..d7947ca --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/net/CompressedMessage.java @@ -0,0 +1,81 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.falsepattern.lib.internal.impl.config.net; + +import io.netty.buffer.ByteBuf; +import lombok.val; + +import cpw.mods.fml.common.network.simpleimpl.IMessage; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +public abstract class CompressedMessage implements IMessage { + private byte[] compressed; + + public CompressedMessage() { + compressed = null; + } + + protected abstract void transmit(DataOutput output) throws IOException; + + protected abstract void receive(DataInput input) throws IOException; + + public void transmit() throws IOException { + val output = new ByteArrayOutputStream(); + val deflateOut = new DeflaterOutputStream(output, new Deflater(Deflater.BEST_COMPRESSION, false)); + val dataOut = new DataOutputStream(deflateOut); + transmit(dataOut); + dataOut.close(); + compressed = output.toByteArray(); + } + + public void receive() throws IOException { + val input = new ByteArrayInputStream(compressed); + val deflateIn = new InflaterInputStream(input, new Inflater(false)); + val dataIn = new DataInputStream(deflateIn); + receive(dataIn); + dataIn.close(); + compressed = null; + } + + @Override + public void fromBytes(ByteBuf buf) { + int length = buf.readInt(); + compressed = new byte[length]; + buf.readBytes(compressed); + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(compressed.length); + buf.writeBytes(compressed); + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncPrompt.java b/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncPrompt.java new file mode 100644 index 0000000..456689c --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncPrompt.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.falsepattern.lib.internal.impl.config.net; + +import io.netty.buffer.ByteBuf; + +import cpw.mods.fml.common.network.simpleimpl.IMessage; + +public class SyncPrompt implements IMessage { + @Override + public void fromBytes(ByteBuf buf) { + + } + + @Override + public void toBytes(ByteBuf buf) { + + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncPromptHandler.java b/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncPromptHandler.java new file mode 100644 index 0000000..f28bb29 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncPromptHandler.java @@ -0,0 +1,38 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.falsepattern.lib.internal.impl.config.net; + +import com.falsepattern.lib.config.event.ConfigSyncRequestEvent; + +import cpw.mods.fml.common.network.simpleimpl.IMessage; +import cpw.mods.fml.common.network.simpleimpl.IMessageHandler; +import cpw.mods.fml.common.network.simpleimpl.MessageContext; +import cpw.mods.fml.relauncher.Side; + +public class SyncPromptHandler implements IMessageHandler { + @Override + public IMessage onMessage(SyncPrompt message, MessageContext ctx) { + if (ctx.side == Side.CLIENT) { + ConfigSyncRequestEvent.postClient(); + } + return null; + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncReply.java b/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncReply.java new file mode 100644 index 0000000..f58964c --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncReply.java @@ -0,0 +1,42 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.falsepattern.lib.internal.impl.config.net; + +import com.falsepattern.lib.internal.impl.config.ConfigurationManagerImpl; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.List; + +public class SyncReply extends CompressedMessage { + public List> matchingClassesOnOurSide; + + @Override + protected void transmit(DataOutput output) throws IOException { + ConfigurationManagerImpl.sendReply(output, matchingClassesOnOurSide); + } + + @Override + protected void receive(DataInput input) throws IOException { + ConfigurationManagerImpl.receiveReply(input); + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncReplyHandler.java b/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncReplyHandler.java new file mode 100644 index 0000000..8064d95 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncReplyHandler.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.falsepattern.lib.internal.impl.config.net; + +import com.falsepattern.lib.internal.FalsePatternLib; + +import cpw.mods.fml.common.network.simpleimpl.IMessage; +import cpw.mods.fml.common.network.simpleimpl.IMessageHandler; +import cpw.mods.fml.common.network.simpleimpl.MessageContext; +import cpw.mods.fml.relauncher.Side; + +import java.io.IOException; + +public class SyncReplyHandler implements IMessageHandler { + + @Override + public IMessage onMessage(SyncReply message, MessageContext ctx) { + if (ctx.side == Side.SERVER) { + //Do not sync client to server + return null; + } + try { + message.receive(); + } catch (IOException e) { + FalsePatternLib.getLog().error("Failed to sync config", e); + } + return null; + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncRequest.java b/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncRequest.java new file mode 100644 index 0000000..2fda9de --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncRequest.java @@ -0,0 +1,42 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.falsepattern.lib.internal.impl.config.net; + +import com.falsepattern.lib.internal.impl.config.ConfigurationManagerImpl; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.List; + +public class SyncRequest extends CompressedMessage { + public List> matchingClassesOnOurSide; + + @Override + protected void transmit(DataOutput output) throws IOException { + ConfigurationManagerImpl.sendRequest(output); + } + + @Override + protected void receive(DataInput input) throws IOException { + matchingClassesOnOurSide = ConfigurationManagerImpl.receiveRequest(input); + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncRequestHandler.java b/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncRequestHandler.java new file mode 100644 index 0000000..47e1c22 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncRequestHandler.java @@ -0,0 +1,52 @@ +/** + * Copyright (C) 2022 FalsePattern + * All Rights Reserved + *

+ * The above copyright notice, this permission notice and the word "SNEED" + * shall be included in all copies or substantial portions of the Software. + *

+ * This program 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. + *

+ * This program 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 General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.falsepattern.lib.internal.impl.config.net; + +import com.falsepattern.lib.internal.FalsePatternLib; +import lombok.val; + +import cpw.mods.fml.common.network.simpleimpl.IMessage; +import cpw.mods.fml.common.network.simpleimpl.IMessageHandler; +import cpw.mods.fml.common.network.simpleimpl.MessageContext; +import cpw.mods.fml.relauncher.Side; + +import java.io.IOException; + +public class SyncRequestHandler implements IMessageHandler { + @Override + public IMessage onMessage(SyncRequest message, MessageContext ctx) { + if (ctx.side == Side.CLIENT) { + //Do not sync client to server + return null; + } + try { + message.receive(); + val reply = new SyncReply(); + reply.matchingClassesOnOurSide = message.matchingClassesOnOurSide; + reply.transmit(); + FalsePatternLib.NETWORK.sendTo(reply, ctx.getServerHandler().playerEntity); + return null; + } catch (IOException e) { + FalsePatternLib.getLog().error("Failed to sync config", e); + return null; + } + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/proxy/ClientProxy.java b/src/main/java/com/falsepattern/lib/internal/proxy/ClientProxy.java index cd1ab8b..83a443d 100644 --- a/src/main/java/com/falsepattern/lib/internal/proxy/ClientProxy.java +++ b/src/main/java/com/falsepattern/lib/internal/proxy/ClientProxy.java @@ -20,8 +20,15 @@ */ package com.falsepattern.lib.internal.proxy; +import com.falsepattern.lib.config.ConfigException; +import com.falsepattern.lib.config.ConfigurationManager; import com.falsepattern.lib.internal.FalsePatternLib; import com.falsepattern.lib.internal.Tags; +import com.falsepattern.lib.internal.config.ToastConfig; +import com.falsepattern.lib.internal.impl.config.net.SyncPrompt; +import com.falsepattern.lib.internal.impl.config.net.SyncPromptHandler; +import com.falsepattern.lib.internal.impl.config.net.SyncReply; +import com.falsepattern.lib.internal.impl.config.net.SyncReplyHandler; import com.falsepattern.lib.internal.impl.toast.GuiToastImpl; import com.falsepattern.lib.updates.UpdateChecker; import lombok.val; @@ -55,6 +62,11 @@ public class ClientProxy extends CommonProxy { @Override public void preInit(FMLPreInitializationEvent e) { super.preInit(e); + try { + ConfigurationManager.initialize(ToastConfig.class); + } catch (ConfigException ex) { + throw new RuntimeException(ex); + } MinecraftForge.EVENT_BUS.register(this); } diff --git a/src/main/java/com/falsepattern/lib/internal/proxy/CommonProxy.java b/src/main/java/com/falsepattern/lib/internal/proxy/CommonProxy.java index a6f4795..9edee62 100644 --- a/src/main/java/com/falsepattern/lib/internal/proxy/CommonProxy.java +++ b/src/main/java/com/falsepattern/lib/internal/proxy/CommonProxy.java @@ -26,12 +26,20 @@ import com.falsepattern.lib.internal.FalsePatternLib; import com.falsepattern.lib.internal.config.LibraryConfig; import com.falsepattern.lib.internal.config.ToastConfig; import com.falsepattern.lib.internal.impl.config.ConfigurationManagerImpl; +import com.falsepattern.lib.internal.impl.config.net.SyncPrompt; +import com.falsepattern.lib.internal.impl.config.net.SyncPromptHandler; +import com.falsepattern.lib.internal.impl.config.net.SyncReply; +import com.falsepattern.lib.internal.impl.config.net.SyncReplyHandler; +import com.falsepattern.lib.internal.impl.config.net.SyncRequest; +import com.falsepattern.lib.internal.impl.config.net.SyncRequestHandler; import com.falsepattern.lib.updates.ModUpdateInfo; import com.falsepattern.lib.updates.UpdateChecker; import cpw.mods.fml.common.event.FMLConstructionEvent; import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.relauncher.Side; + import lombok.val; import java.util.Collections; @@ -43,15 +51,17 @@ public class CommonProxy { protected CompletableFuture> updatesFuture; public void construct(FMLConstructionEvent e) { + FalsePatternLib.NETWORK.registerMessage(SyncRequestHandler.class, SyncRequest.class, 0, Side.SERVER); + FalsePatternLib.NETWORK.registerMessage(SyncReplyHandler.class, SyncReply.class, 1, Side.CLIENT); + FalsePatternLib.NETWORK.registerMessage(SyncPromptHandler.class, SyncPrompt.class, 2, Side.CLIENT); } public void preInit(FMLPreInitializationEvent e) { ConfigurationManagerImpl.registerBus(); try { - ConfigurationManager.registerConfig(LibraryConfig.class); - ConfigurationManager.registerConfig(ToastConfig.class); + ConfigurationManager.initialize(LibraryConfig.class); } catch (ConfigException ex) { - FalsePatternLib.getLog().error("Failed to register config!", ex); + throw new RuntimeException(ex); } if (LibraryConfig.ENABLE_UPDATE_CHECKER) { FalsePatternLib.getLog().info("Launching asynchronous update check."); diff --git a/src/main/resources/assets/falsepatternlib/lang/en_US.lang b/src/main/resources/assets/falsepatternlib/lang/en_US.lang index d6134cf..30ffc77 100644 --- a/src/main/resources/assets/falsepatternlib/lang/en_US.lang +++ b/src/main/resources/assets/falsepatternlib/lang/en_US.lang @@ -4,6 +4,8 @@ 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