2022-05-06 23:48:24 +02:00
|
|
|
package com.falsepattern.lib.config;
|
|
|
|
|
|
2022-05-06 23:53:11 +02:00
|
|
|
import com.falsepattern.lib.StableAPI;
|
2022-05-07 12:40:35 +02:00
|
|
|
import com.falsepattern.lib.internal.FalsePatternLib;
|
2022-06-15 18:39:15 +02:00
|
|
|
import com.falsepattern.lib.util.FileUtil;
|
2022-05-07 16:43:34 +02:00
|
|
|
import cpw.mods.fml.client.config.IConfigElement;
|
2022-05-06 23:48:24 +02:00
|
|
|
import cpw.mods.fml.client.event.ConfigChangedEvent;
|
2022-05-07 16:43:34 +02:00
|
|
|
import cpw.mods.fml.common.FMLCommonHandler;
|
2022-05-06 23:48:24 +02:00
|
|
|
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
2022-06-15 18:39:15 +02:00
|
|
|
|
|
|
|
|
import java.io.File;
|
2022-05-07 11:45:57 +02:00
|
|
|
import java.lang.reflect.Field;
|
|
|
|
|
import java.lang.reflect.InvocationTargetException;
|
2022-05-06 23:48:24 +02:00
|
|
|
import java.nio.file.Path;
|
2022-06-08 16:01:06 +02:00
|
|
|
import java.util.Arrays;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.Optional;
|
|
|
|
|
import java.util.Set;
|
2022-05-07 01:33:04 +02:00
|
|
|
import java.util.regex.Pattern;
|
2022-05-07 11:45:57 +02:00
|
|
|
import java.util.stream.Collectors;
|
2022-06-08 16:01:06 +02:00
|
|
|
import lombok.AccessLevel;
|
|
|
|
|
import lombok.NoArgsConstructor;
|
|
|
|
|
import lombok.SneakyThrows;
|
|
|
|
|
import lombok.val;
|
|
|
|
|
import lombok.var;
|
|
|
|
|
import net.minecraftforge.common.config.ConfigElement;
|
|
|
|
|
import net.minecraftforge.common.config.Configuration;
|
2022-05-06 23:48:24 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Class for controlling the loading of configuration files.
|
|
|
|
|
*/
|
|
|
|
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
2022-05-06 23:53:11 +02:00
|
|
|
@StableAPI(since = "0.6.0")
|
2022-05-06 23:48:24 +02:00
|
|
|
public class ConfigurationManager {
|
2022-05-07 16:43:34 +02:00
|
|
|
private static final Map<String, Configuration> configs = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
private static final Map<Configuration, Set<Class<?>>> configToClassMap = new HashMap<>();
|
2022-05-06 23:48:24 +02:00
|
|
|
|
|
|
|
|
private static final ConfigurationManager instance = new ConfigurationManager();
|
|
|
|
|
|
|
|
|
|
private static boolean initialized = false;
|
|
|
|
|
|
|
|
|
|
private static Path configDir;
|
|
|
|
|
|
|
|
|
|
/**
|
2022-05-07 16:43:34 +02:00
|
|
|
* Registers a configuration class to be loaded. This should be done in preInit.
|
2022-06-08 16:01:06 +02:00
|
|
|
*
|
2022-05-07 16:43:34 +02:00
|
|
|
* @param configClass The class to register.
|
2022-05-06 23:48:24 +02:00
|
|
|
*/
|
2022-05-07 16:43:34 +02:00
|
|
|
public static void registerConfig(Class<?> configClass) throws ConfigException {
|
2022-06-15 18:39:15 +02:00
|
|
|
init();
|
2022-06-08 16:01:06 +02:00
|
|
|
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!"));
|
2022-05-07 16:43:34 +02:00
|
|
|
val rawConfig = configs.computeIfAbsent(cfg.modid(), (ignored) -> {
|
|
|
|
|
val c = new Configuration(configDir.resolve(cfg.modid() + ".cfg").toFile());
|
|
|
|
|
c.load();
|
|
|
|
|
return c;
|
|
|
|
|
});
|
|
|
|
|
configToClassMap.computeIfAbsent(rawConfig, (ignored) -> new HashSet<>()).add(configClass);
|
2022-05-07 11:45:57 +02:00
|
|
|
try {
|
2022-05-07 16:43:34 +02:00
|
|
|
processConfigInternal(configClass, category, rawConfig);
|
|
|
|
|
rawConfig.save();
|
2022-05-07 11:45:57 +02:00
|
|
|
} catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException | NoSuchFieldException e) {
|
|
|
|
|
throw new ConfigException(e);
|
|
|
|
|
}
|
2022-05-06 23:48:24 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-08 16:01:06 +02:00
|
|
|
private static void processConfigInternal(Class<?> configClass, String category, Configuration rawConfig)
|
|
|
|
|
throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException,
|
|
|
|
|
ConfigException {
|
2022-05-06 23:48:24 +02:00
|
|
|
val cat = rawConfig.getCategory(category);
|
2022-06-08 16:01:06 +02:00
|
|
|
for (val field : configClass.getDeclaredFields()) {
|
|
|
|
|
if (field.getAnnotation(Config.Ignore.class) != null) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2022-05-06 23:48:24 +02:00
|
|
|
field.setAccessible(true);
|
2022-06-08 16:01:06 +02:00
|
|
|
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);
|
2022-05-07 12:16:05 +02:00
|
|
|
val fieldClass = field.getType();
|
2022-05-06 23:48:24 +02:00
|
|
|
var boxed = false;
|
2022-05-07 12:16:05 +02:00
|
|
|
if ((boxed = fieldClass.equals(Boolean.class)) || fieldClass.equals(boolean.class)) {
|
2022-06-08 16:01:06 +02:00
|
|
|
val defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultBoolean.class))
|
|
|
|
|
.map(Config.DefaultBoolean::value)
|
|
|
|
|
.orElse(boxed ? (Boolean) field.get(null) : field.getBoolean(null));
|
2022-05-07 01:33:04 +02:00
|
|
|
field.setBoolean(null, rawConfig.getBoolean(name, category, defaultValue, comment, langKey));
|
2022-05-07 12:16:05 +02:00
|
|
|
} else if ((boxed = fieldClass.equals(Integer.class)) || fieldClass.equals(int.class)) {
|
2022-05-07 01:14:55 +02:00
|
|
|
val range = Optional.ofNullable(field.getAnnotation(Config.RangeInt.class));
|
2022-05-07 01:33:04 +02:00
|
|
|
val min = range.map(Config.RangeInt::min).orElse(Integer.MIN_VALUE);
|
|
|
|
|
val max = range.map(Config.RangeInt::max).orElse(Integer.MAX_VALUE);
|
2022-06-08 16:01:06 +02:00
|
|
|
val defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultInt.class))
|
|
|
|
|
.map(Config.DefaultInt::value)
|
|
|
|
|
.orElse(boxed ? (Integer) field.get(null) : field.getInt(null));
|
2022-05-07 01:33:04 +02:00
|
|
|
field.setInt(null, rawConfig.getInt(name, category, defaultValue, min, max, comment, langKey));
|
2022-05-07 12:16:05 +02:00
|
|
|
} else if ((boxed = fieldClass.equals(Float.class)) || fieldClass.equals(float.class)) {
|
2022-05-07 01:33:04 +02:00
|
|
|
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);
|
2022-06-08 16:01:06 +02:00
|
|
|
val defaultValue = Optional.ofNullable(field.getAnnotation(Config.DefaultFloat.class))
|
|
|
|
|
.map(Config.DefaultFloat::value)
|
|
|
|
|
.orElse(boxed ? (Float) field.get(null) : field.getFloat(null));
|
2022-05-07 21:05:52 +02:00
|
|
|
field.setFloat(null, rawConfig.getFloat(name, category, defaultValue, min, max, comment, langKey));
|
2022-05-07 12:16:05 +02:00
|
|
|
} else if (fieldClass.equals(String.class)) {
|
2022-06-08 16:01:06 +02:00
|
|
|
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);
|
2022-05-07 01:33:04 +02:00
|
|
|
field.set(null, rawConfig.getString(name, category, defaultValue, comment, langKey, pattern));
|
2022-05-07 12:16:05 +02:00
|
|
|
} else if (fieldClass.isEnum()) {
|
2022-06-08 16:01:06 +02:00
|
|
|
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(ConfigurationManager::extractValue)
|
|
|
|
|
.orElse(field.get(null));
|
2022-05-07 11:45:57 +02:00
|
|
|
val possibleValues = enumValues.stream().map(Enum::name).toArray(String[]::new);
|
2022-06-08 16:01:06 +02:00
|
|
|
var value = rawConfig.getString(name,
|
|
|
|
|
category,
|
|
|
|
|
defaultValue.name(),
|
|
|
|
|
comment + "\nPossible values: " + Arrays.toString(possibleValues) +
|
|
|
|
|
"\n",
|
|
|
|
|
possibleValues,
|
|
|
|
|
langKey);
|
2022-05-07 12:40:35 +02:00
|
|
|
|
|
|
|
|
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) {
|
2022-06-08 16:01:06 +02:00
|
|
|
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 + "!");
|
2022-05-07 12:40:35 +02:00
|
|
|
field.set(null, defaultValue);
|
|
|
|
|
}
|
2022-06-07 21:10:21 -04:00
|
|
|
} 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);
|
2022-05-07 11:45:57 +02:00
|
|
|
} else {
|
2022-06-08 16:01:06 +02:00
|
|
|
throw new ConfigException("Illegal config field: " + field.getName() + " in " + configClass.getName() +
|
|
|
|
|
": Unsupported type " + fieldClass.getName() +
|
|
|
|
|
"! Did you forget an @Ignore annotation?");
|
2022-05-06 23:48:24 +02:00
|
|
|
}
|
|
|
|
|
if (field.isAnnotationPresent(Config.RequiresMcRestart.class)) {
|
|
|
|
|
cat.setRequiresMcRestart(true);
|
|
|
|
|
}
|
|
|
|
|
if (field.isAnnotationPresent(Config.RequiresWorldRestart.class)) {
|
|
|
|
|
cat.setRequiresWorldRestart(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-07 11:45:57 +02:00
|
|
|
|
|
|
|
|
@SneakyThrows
|
|
|
|
|
private static Field extractField(Class<?> clazz, String field) {
|
|
|
|
|
return clazz.getDeclaredField(field);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@SneakyThrows
|
|
|
|
|
private static Object extractValue(Field field) {
|
|
|
|
|
return field.get(null);
|
|
|
|
|
}
|
2022-06-08 16:01:06 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Process the configuration into a list of config elements usable in config GUI code.
|
|
|
|
|
*
|
|
|
|
|
* @param configClass The class to process.
|
|
|
|
|
*
|
|
|
|
|
* @return The configuration elements.
|
|
|
|
|
*/
|
|
|
|
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
|
|
|
public static List<IConfigElement> getConfigElements(Class<?> configClass) throws ConfigException {
|
2022-06-15 18:39:15 +02:00
|
|
|
init();
|
2022-06-08 16:01:06 +02:00
|
|
|
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(
|
2022-06-15 19:18:07 +02:00
|
|
|
"Tried to get config elements for non-registered config class!"));
|
2022-06-08 16:01:06 +02:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
})).collect(Collectors.toList());
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-15 18:39:15 +02:00
|
|
|
private static void init() {
|
2022-06-08 16:01:06 +02:00
|
|
|
if (initialized) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-06-15 18:39:15 +02:00
|
|
|
configDir = FileUtil.getMinecraftHome().toPath().resolve("config");
|
2022-06-08 16:01:06 +02:00
|
|
|
initialized = true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-15 19:17:05 +02:00
|
|
|
/**
|
|
|
|
|
* Internal, do not use.
|
|
|
|
|
*/
|
|
|
|
|
public static void registerBus() {
|
|
|
|
|
FMLCommonHandler.instance().bus().register(instance);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-08 16:01:06 +02:00
|
|
|
/**
|
|
|
|
|
* Internal, do not use.
|
|
|
|
|
*
|
|
|
|
|
* @param event The event.
|
|
|
|
|
*/
|
|
|
|
|
@SubscribeEvent
|
|
|
|
|
public void onConfigChanged(ConfigChangedEvent.OnConfigChangedEvent event) {
|
2022-06-15 18:39:15 +02:00
|
|
|
init();
|
2022-06-08 16:01:06 +02:00
|
|
|
val config = configs.get(event.modID);
|
|
|
|
|
if (config == 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();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2022-05-06 23:48:24 +02:00
|
|
|
}
|