package io.gitlab.jfronny.libjf.config; import io.gitlab.jfronny.libjf.LibJf; import io.gitlab.jfronny.libjf.config.entry.Entry; import io.gitlab.jfronny.libjf.gson.GsonHidden; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.gui.widget.*; import net.minecraft.text.*; import net.minecraft.util.Formatting; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Pattern; /** Based on https://github.com/TeamMidnightDust/MidnightLib which is based on https://github.com/Minenash/TinyConfig * Credits to TeamMidnightDust and Minenash */ @SuppressWarnings("unchecked") public class Config { private static final Pattern INTEGER_ONLY = Pattern.compile("(-?[0-9]*)"); private static final Pattern DECIMAL_ONLY = Pattern.compile("-?([\\d]+\\.?[\\d]*|[\\d]*\\.?[\\d]+|\\.)"); public final List entries = new ArrayList<>(); public Path path; public final String modid; public final Class configClass; public Config(String modid, Class config) { this.modid = modid; configClass = config; path = FabricLoader.getInstance().getConfigDir().resolve(modid + ".json"); for (Field field : config.getFields()) { EntryInfo info = new EntryInfo(); info.field = field; if (field.isAnnotationPresent(io.gitlab.jfronny.libjf.config.entry.Entry.class)) try { info.defaultValue = field.get(null); } catch (IllegalAccessException ignored) {} entries.add(info); } try { LibJf.GSON.fromJson(Files.newBufferedReader(path), config); } catch (Exception e) { write(); } } @Environment(EnvType.CLIENT) public void initClient(EntryInfo info) { if (!(info.field.isAnnotationPresent(io.gitlab.jfronny.libjf.config.entry.Entry.class) || info.field.isAnnotationPresent(GsonHidden.class))) return; Class type = info.field.getType(); io.gitlab.jfronny.libjf.config.entry.Entry e = info.field.getAnnotation(Entry.class); info.width = e != null ? e.width() : 0; if (e == null) return; if (type == int.class) textField(info, Integer::parseInt, INTEGER_ONLY, e.min(), e.max(), true); else if (type == double.class) textField(info, Double::parseDouble, DECIMAL_ONLY, e.min(), e.max(),false); else if (type == String.class) textField(info, String::length, null, Math.min(e.min(),0), Math.max(e.max(),1),true); else if (type == boolean.class) { Function func = value -> new LiteralText((Boolean) value ? "True" : "False").formatted((Boolean) value ? Formatting.GREEN : Formatting.RED); info.widget = new AbstractMap.SimpleEntry>(button -> { info.value = !(Boolean) info.value; button.setMessage(func.apply(info.value)); }, func); } else if (type.isEnum()) { List values = Arrays.asList(info.field.getType().getEnumConstants()); Function func = value -> new TranslatableText(modid + ".jfconfig." + "enum." + type.getSimpleName() + "." + info.value.toString()); info.widget = new AbstractMap.SimpleEntry>( button -> { int index = values.indexOf(info.value) + 1; info.value = values.get(index >= values.size()? 0 : index); button.setMessage(func.apply(info.value)); }, func); } try { info.value = info.field.get(null); info.tempValue = info.value.toString(); } catch (IllegalAccessException ignored) { } } private void textField(EntryInfo info, Function f, Pattern pattern, double min, double max, boolean cast) { boolean isNumber = pattern != null; info.widget = (BiFunction>) (t, b) -> s -> { s = s.trim(); if (!(s.isEmpty() || !isNumber || pattern.matcher(s).matches())) return false; Number value = 0; boolean inLimits = false; System.out.println(((isNumber ^ s.isEmpty()))); System.out.println(!s.equals("-") && !s.equals(".")); info.error = null; if (!(isNumber && s.isEmpty()) && !s.equals("-") && !s.equals(".")) { value = f.apply(s); inLimits = value.doubleValue() >= min && value.doubleValue() <= max; info.error = inLimits? null : new AbstractMap.SimpleEntry<>(t, new LiteralText(value.doubleValue() < min ? "§cMinimum " + (isNumber? "value" : "length") + (cast? " is " + (int)min : " is " + min) : "§cMaximum " + (isNumber? "value" : "length") + (cast? " is " + (int)max : " is " + max))); } info.tempValue = s; t.setEditableColor(inLimits? 0xFFFFFFFF : 0xFFFF7777); info.inLimits = inLimits; b.active = entries.stream().allMatch(e -> e.inLimits); if (inLimits) info.value = isNumber? value : s; return true; }; } public void write() { path = FabricLoader.getInstance().getConfigDir().resolve(modid + ".json"); try { if (!Files.exists(path)) Files.createFile(path); Files.write(path, LibJf.GSON.toJson(configClass.getDeclaredConstructor().newInstance()).getBytes()); } catch (Exception e) { e.printStackTrace(); } } }