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