220 lines
7.1 KiB
Java
220 lines
7.1 KiB
Java
package net.montoyo.wd.config.annoconfg;
|
|
|
|
import net.minecraftforge.common.ForgeConfigSpec;
|
|
import net.minecraftforge.eventbus.api.IEventBus;
|
|
import net.minecraftforge.fml.ModLoadingContext;
|
|
import net.minecraftforge.fml.config.ModConfig;
|
|
import net.minecraftforge.fml.event.config.ModConfigEvent;
|
|
import net.montoyo.wd.config.annoconfg.annotation.format.*;
|
|
import net.montoyo.wd.config.annoconfg.annotation.value.Default;
|
|
import net.montoyo.wd.config.annoconfg.annotation.value.DoubleRange;
|
|
import net.montoyo.wd.config.annoconfg.annotation.value.IntRange;
|
|
import net.montoyo.wd.config.annoconfg.annotation.value.LongRange;
|
|
import net.montoyo.wd.config.annoconfg.handle.UnsafeHandle;
|
|
import net.montoyo.wd.config.annoconfg.util.EnumType;
|
|
|
|
import java.lang.reflect.AnnotatedElement;
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.Method;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.function.Supplier;
|
|
|
|
public class AnnoCFG {
|
|
private ForgeConfigSpec mySpec;
|
|
|
|
private final HashMap<String, ConfigEntry> handles = new HashMap<>();
|
|
|
|
private static final ArrayList<AnnoCFG> configs = new ArrayList<>();
|
|
private final Method postInit;
|
|
|
|
public AnnoCFG(IEventBus bus, Class<?> clazz) {
|
|
bus.addListener(this::onConfigChange);
|
|
ForgeConfigSpec.Builder configBuilder = new ForgeConfigSpec.Builder();
|
|
setup("", configBuilder, clazz);
|
|
configs.add(this);
|
|
|
|
Method m = null;
|
|
try {
|
|
m = clazz.getDeclaredMethod("postLoad");
|
|
} catch (Throwable ignored) {
|
|
}
|
|
postInit = m;
|
|
|
|
Config configDescriptor = clazz.getAnnotation(Config.class);
|
|
if (configDescriptor != null) {
|
|
String pth = configDescriptor.path();
|
|
if (!pth.isEmpty()) pth = pth + "/";
|
|
switch (configDescriptor.type()) {
|
|
case SERVER -> create(ModConfig.Type.SERVER, pth + ModLoadingContext.get().getActiveNamespace() + "_server.toml");
|
|
case CLIENT -> create(ModConfig.Type.CLIENT, pth + ModLoadingContext.get().getActiveNamespace() + "_client.toml");
|
|
case COMMON -> create(ModConfig.Type.COMMON, pth + ModLoadingContext.get().getActiveNamespace() + "_common.toml");
|
|
default -> throw new RuntimeException("wat");
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void setupCommentsAndTranslations(AnnotatedElement element, ForgeConfigSpec.Builder builder, String... additionalLines) {
|
|
Translation translation = element.getAnnotation(Translation.class);
|
|
Comment comment = element.getAnnotation(Comment.class);
|
|
|
|
StringBuilder builder1 = new StringBuilder();
|
|
if (comment != null) {
|
|
for (int i = 0; i < comment.value().length; i++) {
|
|
String s = comment.value()[i];
|
|
builder1.append(s);
|
|
if (i != comment.value().length - 1)
|
|
builder1.append("\n");
|
|
}
|
|
}
|
|
for (String additionalLine : additionalLines) builder1.append(additionalLine);
|
|
if (!builder1.isEmpty())
|
|
builder.comment(builder1.toString());
|
|
|
|
if (translation != null)
|
|
builder.translation(translation.value());
|
|
}
|
|
|
|
public void setup(String dir, ForgeConfigSpec.Builder builder, Class<?> clazz) {
|
|
if (dir.startsWith(".")) dir = dir.substring(1);
|
|
|
|
for (Field field : clazz.getFields()) {
|
|
if (field.canAccess(null)) {
|
|
Skip skip = field.getAnnotation(Skip.class);
|
|
if (skip != null) continue;
|
|
|
|
Name name = field.getAnnotation(Name.class);
|
|
|
|
String nameStr = field.getName();
|
|
if (name != null) nameStr = name.value();
|
|
|
|
setupCommentsAndTranslations(field, builder);
|
|
|
|
Supplier<?> value;
|
|
|
|
Default defaultValue = field.getAnnotation(Default.class);
|
|
try {
|
|
switch (EnumType.forClass(field.getType())) {
|
|
case INT -> {
|
|
IntRange range = field.getAnnotation(IntRange.class);
|
|
int v = defaultValue.valueI();
|
|
if (range != null) {
|
|
int min = range.minV();
|
|
int max = range.maxV();
|
|
|
|
value = builder.defineInRange(nameStr, v, min, max);
|
|
} else {
|
|
value = builder.define(nameStr, v);
|
|
}
|
|
}
|
|
case LONG -> {
|
|
LongRange range = field.getAnnotation(LongRange.class);
|
|
long v = defaultValue.valueL();
|
|
if (range != null) {
|
|
long min = range.minV();
|
|
long max = range.maxV();
|
|
|
|
value = builder.defineInRange(nameStr, v, min, max);
|
|
} else {
|
|
value = builder.define(nameStr, v);
|
|
}
|
|
}
|
|
case DOUBLE -> {
|
|
DoubleRange range = field.getAnnotation(DoubleRange.class);
|
|
double v = defaultValue.valueD();
|
|
if (range != null) {
|
|
double min = range.minV();
|
|
double max = range.maxV();
|
|
|
|
value = builder.defineInRange(nameStr, v, min, max);
|
|
} else {
|
|
value = builder.define(nameStr, v);
|
|
}
|
|
}
|
|
case BOOLEAN -> {
|
|
boolean b = defaultValue.valueBoolean();
|
|
value = builder.define(nameStr, b);
|
|
}
|
|
case OTHER -> {
|
|
Class<?> fieldType = field.getType();
|
|
if (fieldType.equals(String[].class)) {
|
|
Supplier<String> sup = builder.define(nameStr, defaultValue.valueStr());
|
|
value = () -> {
|
|
String v = sup.get();
|
|
return v.split(",");
|
|
};
|
|
} else if (fieldType.equals(String.class)) {
|
|
value = builder.define(nameStr, defaultValue.valueStr());
|
|
} else
|
|
throw new RuntimeException("NYI " + field.getType());
|
|
}
|
|
default -> throw new RuntimeException("NYI " + field.getType());
|
|
}
|
|
} catch (NullPointerException npe) {
|
|
String inf = "";
|
|
if (npe.getMessage().contains("\"value.Default\""))
|
|
inf = " this is likely due to a missing default.";
|
|
throw new RuntimeException("A null pointer occurred on " + field.getName() + inf, npe);
|
|
}
|
|
|
|
Object o;
|
|
try {
|
|
// without this line, this system freaks out due to using theUnsafe
|
|
//noinspection UnusedAssignment
|
|
o = field.get(null);
|
|
} catch (Throwable ignored) {
|
|
}
|
|
UnsafeHandle handle = new UnsafeHandle(field);
|
|
o = handle.get();
|
|
handle.set(o);
|
|
|
|
//noinspection FunctionalExpressionCanBeFolded
|
|
handles.put(dir + "." + nameStr, new ConfigEntry(
|
|
handle, value::get
|
|
));
|
|
}
|
|
}
|
|
|
|
// TODO: check if the nested class is a direct nesting
|
|
for (Class<?> nestMember : clazz.getClasses()) {
|
|
if (nestMember == clazz) continue;
|
|
if (!nestMember.getName().startsWith(clazz.getName())) continue;
|
|
CFGSegment segment = nestMember.getAnnotation(CFGSegment.class);
|
|
if (segment == null) {
|
|
System.out.println(nestMember);
|
|
throw new RuntimeException("NYI: default name");
|
|
}
|
|
String name = segment.value();
|
|
|
|
setupCommentsAndTranslations(nestMember, builder);
|
|
|
|
builder.push(name);
|
|
setup(dir + "." + name, builder, nestMember);
|
|
builder.pop();
|
|
}
|
|
|
|
mySpec = builder.build();
|
|
}
|
|
|
|
public void onConfigChange(ModConfigEvent event) {
|
|
if (
|
|
event.getConfig().getSpec().equals(mySpec) ||
|
|
event.getConfig().getSpec() == mySpec
|
|
) {
|
|
for (String s : handles.keySet()) {
|
|
ConfigEntry entry = handles.get(s);
|
|
entry.handle.set(entry.supplier.get());
|
|
}
|
|
}
|
|
try {
|
|
postInit.invoke(null);
|
|
} catch (Throwable err) {
|
|
err.printStackTrace();
|
|
}
|
|
}
|
|
|
|
public void create(ModConfig.Type type, String file) {
|
|
ModLoadingContext.get().registerConfig(type, mySpec, file);
|
|
}
|
|
}
|