aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/anthonyhilyard/iceberg/util/ConfigMenusForgeHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/anthonyhilyard/iceberg/util/ConfigMenusForgeHelper.java')
-rw-r--r--src/main/java/com/anthonyhilyard/iceberg/util/ConfigMenusForgeHelper.java266
1 files changed, 266 insertions, 0 deletions
diff --git a/src/main/java/com/anthonyhilyard/iceberg/util/ConfigMenusForgeHelper.java b/src/main/java/com/anthonyhilyard/iceberg/util/ConfigMenusForgeHelper.java
new file mode 100644
index 0000000..ad4f8d5
--- /dev/null
+++ b/src/main/java/com/anthonyhilyard/iceberg/util/ConfigMenusForgeHelper.java
@@ -0,0 +1,266 @@
+package com.anthonyhilyard.iceberg.util;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import com.anthonyhilyard.iceberg.Loader;
+import com.electronwill.nightconfig.core.CommentedConfig;
+import com.electronwill.nightconfig.core.Config;
+import com.electronwill.nightconfig.core.UnmodifiableConfig;
+import com.google.common.collect.Maps;
+import com.google.common.base.Objects;
+import com.google.common.collect.Iterators;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
+import fuzs.configmenusforge.client.gui.data.EntryData;
+import fuzs.configmenusforge.client.gui.data.IEntryData;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.TextComponent;
+import net.minecraftforge.common.ForgeConfigSpec;
+import net.minecraftforge.fml.unsafe.UnsafeHacks;
+
+public class ConfigMenusForgeHelper
+{
+ private final static Map<Class<?>, List<MethodHandle>> configSpecMethodHandles = Maps.newHashMap();
+ private final static Map<Class<?>, Boolean> cachedConfigSpecClasses = Maps.newHashMap();
+ private final static MethodType getValuesMethodType = MethodType.methodType(UnmodifiableConfig.class);
+ private final static MethodType isLoadedMethodType = MethodType.methodType(boolean.class);
+ private final static MethodType saveMethodType = MethodType.methodType(void.class);
+
+ private static Object callMethod(UnmodifiableConfig spec, int methodIndex)
+ {
+ Class<?> specClass = spec.getClass();
+ if (!cachedConfigSpecClasses.containsKey(specClass))
+ {
+ cacheClass(specClass);
+ }
+
+ if (configSpecMethodHandles.containsKey(specClass))
+ {
+ try
+ {
+ return configSpecMethodHandles.get(specClass).get(methodIndex).invoke(spec);
+ }
+ catch (Throwable e)
+ {
+ Loader.LOGGER.warn(ExceptionUtils.getStackTrace(e));
+ }
+ }
+ return null;
+ }
+
+ public static UnmodifiableConfig getValues(UnmodifiableConfig spec)
+ {
+ return (UnmodifiableConfig) callMethod(spec, 0);
+ }
+
+ public static boolean isLoaded(UnmodifiableConfig spec)
+ {
+ return (Boolean) callMethod(spec, 1);
+ }
+
+ public static void save(UnmodifiableConfig spec)
+ {
+ callMethod(spec, 2);
+ }
+
+ public static Boolean cachedValidity(Class<?> specClass)
+ {
+ return cachedConfigSpecClasses.getOrDefault(specClass, null);
+ }
+
+ public static void cacheClass(Class<?> specClass)
+ {
+ MethodHandle getValuesMethod = null;
+ MethodHandle isLoadedMethod = null;
+ MethodHandle saveMethod = null;
+ try
+ {
+ getValuesMethod = MethodHandles.lookup().findVirtual(specClass, "getValues", getValuesMethodType);
+ isLoadedMethod = MethodHandles.lookup().findVirtual(specClass, "isLoaded", isLoadedMethodType);
+ saveMethod = MethodHandles.lookup().findVirtual(specClass, "save", saveMethodType);
+ }
+ catch (Throwable e)
+ {
+ Loader.LOGGER.warn(ExceptionUtils.getStackTrace(e));
+ }
+
+ // If we found valid getValues, isLoaded and save methods, add them to the cache.
+ if (getValuesMethod != null && isLoadedMethod != null && saveMethod != null)
+ {
+ cachedConfigSpecClasses.put(specClass, true);
+ configSpecMethodHandles.put(specClass, List.of(getValuesMethod, isLoadedMethod, saveMethod));
+ }
+ else
+ {
+ cachedConfigSpecClasses.put(specClass, false);
+ }
+ }
+
+ /**
+ * Changed spec from a ForgeConfigSpec to an UnmodifiableConfig.
+ */
+ public static void makeValueToDataMap(UnmodifiableConfig spec, UnmodifiableConfig values, CommentedConfig comments, Map<Object, IEntryData> allData, String basePath)
+ {
+ for (String path : values.valueMap().keySet())
+ {
+ String currentPath = basePath.isEmpty() ? path : basePath + "." + path;
+ Object value = values.valueMap().get(path);
+ if (value instanceof UnmodifiableConfig category)
+ {
+ final EntryData.CategoryEntryData data = new EntryData.CategoryEntryData(path, category, comments.getComment(path));
+ allData.put(category, data);
+ makeValueToDataMap(spec, category, (CommentedConfig) comments.valueMap().get(path), allData, currentPath);
+ }
+ else if (value instanceof ForgeConfigSpec.ConfigValue<?> configValue && configValue.get() instanceof UnmodifiableConfig category)
+ {
+ final EntryData.CategoryEntryData data = new DynamicCategoryEntryData(path, category, comments.getComment(path));
+ allData.put(category, data);
+ makeValueToDataMap(spec, category, (CommentedConfig) comments.valueMap().get(path), allData, currentPath);
+ }
+ else if (value instanceof ForgeConfigSpec.ConfigValue<?> configValue)
+ {
+ final EntryData.ConfigEntryData<?> data = new EntryData.ConfigEntryData<>(path, configValue, spec.getRaw(configValue.getPath()));
+ allData.put(configValue, data);
+ }
+ // Allow non-configvalue values if the parent is a dynamic subconfig.
+ else if (!(value instanceof ForgeConfigSpec.ConfigValue<?>) && allData.containsKey(values) && allData.get(values) instanceof DynamicCategoryEntryData)
+ {
+ final EntryData.ConfigEntryData<?> data = new DynamicConfigEntryData<>(List.of(currentPath.split("\\.")), value, spec.getRaw(currentPath), spec);
+ allData.put(value, data);
+ }
+ }
+ }
+
+ public static class DynamicCategoryEntryData extends EntryData.CategoryEntryData
+ {
+ public DynamicCategoryEntryData(String path, UnmodifiableConfig config, String comment) {
+ super(path, config, comment);
+ }
+ }
+
+ public static class DynamicConfigEntryData<T> extends EntryData.ConfigEntryData<T>
+ {
+ private final ForgeConfigSpec.ValueSpec valueSpec;
+ private T currentValue;
+ private T configValue;
+ private final List<String> fullPath;
+ private final UnmodifiableConfig spec;
+
+ private final static ForgeConfigSpec.ConfigValue<?> dummyConfigValue;
+
+ private Component title;
+
+ static
+ {
+ dummyConfigValue = UnsafeHacks.newInstance(ForgeConfigSpec.ConfigValue.class);
+ try
+ {
+ Field specField = ForgeConfigSpec.ConfigValue.class.getDeclaredField("spec");
+ UnsafeHacks.setField(specField, dummyConfigValue, UnsafeHacks.newInstance(ForgeConfigSpec.class));
+ Field defaultSupplierField = ForgeConfigSpec.ConfigValue.class.getDeclaredField("defaultSupplier");
+ UnsafeHacks.setField(defaultSupplierField, dummyConfigValue, (Supplier<?>)(() -> null));
+ }
+ catch (Exception e) { }
+ }
+
+ @SuppressWarnings("unchecked")
+ public DynamicConfigEntryData(List<String> fullPath, T configValue, ForgeConfigSpec.ValueSpec valueSpec, UnmodifiableConfig spec)
+ {
+ super(fullPath.get(fullPath.size() - 1), (ForgeConfigSpec.ConfigValue<T>) dummyConfigValue, valueSpec);
+ this.configValue = configValue;
+ this.currentValue = configValue;
+ this.valueSpec = valueSpec;
+ this.fullPath = fullPath;
+ this.spec = spec;
+
+ // We will override the normal title functionality since we want it to be unformatted.
+ this.title = new TextComponent(getPath());
+ }
+
+ @Override
+ public Component getTitle()
+ {
+ return this.title;
+ }
+
+ @Override
+ public boolean mayResetValue()
+ {
+ return !listSafeEquals(currentValue, getDefaultValue());
+ }
+
+ @Override
+ public boolean mayDiscardChanges()
+ {
+ return listSafeEquals(configValue, currentValue);
+ }
+
+ private static <T> boolean listSafeEquals(T o1, T o2)
+ {
+ // Attempts to solve an issue where types of lists won't match when one is read from file
+ // (due to enum being converted to string, long to int)
+ if (o1 instanceof List<?> list1 && o2 instanceof List<?> list2)
+ {
+ final Stream<String> stream1 = list1.stream().map(o -> o instanceof Enum<?> e ? e.name() : o.toString());
+ final Stream<String> stream2 = list2.stream().map(o -> o instanceof Enum<?> e ? e.name() : o.toString());
+ return Iterators.elementsEqual(stream1.iterator(), stream2.iterator());
+ }
+ return Objects.equal(o1, o2);
+ }
+
+ @Override
+ public void resetCurrentValue()
+ {
+ currentValue = getDefaultValue();
+ }
+
+ @Override
+ public void discardCurrentValue()
+ {
+ currentValue = configValue;
+ }
+
+ @Override
+ public void saveConfigValue()
+ {
+ try
+ {
+ Field childConfigField = spec.getClass().getDeclaredField("childConfig");
+ Config childConfig = UnsafeHacks.getField(childConfigField, spec);
+ childConfig.set(fullPath, currentValue);
+ }
+ catch (Exception e) { }
+ configValue = currentValue;
+ }
+
+ @SuppressWarnings("unchecked")
+ public T getDefaultValue()
+ {
+ return (T) valueSpec.getDefault();
+ }
+
+ public T getCurrentValue()
+ {
+ return currentValue;
+ }
+
+ public void setCurrentValue(T newValue)
+ {
+ currentValue = newValue;
+ }
+
+ @Override
+ public List<String> getFullPath()
+ {
+ return fullPath;
+ }
+ }
+} \ No newline at end of file