mirror of
https://github.com/TeamMidnightDust/MidnightLib.git
synced 2025-12-15 09:05:08 +01:00
Merge pull request #93 from Jaffe2718/architectury-1.21.4
Fix bugs & add Multi-Conditions Feature
This commit is contained in:
@@ -19,7 +19,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import javax.swing.*; import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
|
||||
import java.lang.annotation.*;
|
||||
import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType;
|
||||
import java.nio.file.Files; import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -39,13 +39,16 @@ public abstract class MidnightConfig {
|
||||
private static final Pattern DECIMAL_ONLY = Pattern.compile("-?(\\d+\\.?\\d*|\\d*\\.?\\d+|\\.)");
|
||||
private static final Pattern HEXADECIMAL_ONLY = Pattern.compile("(-?[#0-9a-fA-F]*)");
|
||||
|
||||
private static final List<EntryInfo> entries = new ArrayList<>();
|
||||
// private static final List<EntryInfo> entries = new ArrayList<>();
|
||||
private static final Hashtable<String, EntryInfo> entries = new Hashtable<>(); // modid:fieldName -> EntryInfo
|
||||
private static final List<String> entryOrder = Lists.newArrayList(); // ordered list of entries
|
||||
private static boolean reloadScreen = false;
|
||||
|
||||
public static class EntryInfo {
|
||||
public Entry entry;
|
||||
public Comment comment;
|
||||
public Condition condition;
|
||||
// @ApiStatus.Obsolete public Condition condition;
|
||||
public Condition[] conditions;
|
||||
public final Field field;
|
||||
public final Class<?> dataType;
|
||||
public final String modid, fieldName;
|
||||
@@ -61,8 +64,11 @@ public abstract class MidnightConfig {
|
||||
public EntryInfo(Field field, String modid) {
|
||||
this.field = field; this.modid = modid;
|
||||
if (field != null) {
|
||||
this.fieldName = field.getName(); this.dataType = getUnderlyingType(field);
|
||||
this.entry = field.getAnnotation(Entry.class); this.comment = field.getAnnotation(Comment.class); this.condition = field.getAnnotation(Condition.class);
|
||||
this.fieldName = field.getName();
|
||||
this.dataType = getUnderlyingType(field);
|
||||
this.entry = field.getAnnotation(Entry.class);
|
||||
this.comment = field.getAnnotation(Comment.class);
|
||||
this.conditions = field.getAnnotationsByType(Condition.class);
|
||||
} else {
|
||||
this.fieldName = ""; this.dataType = null;
|
||||
}
|
||||
@@ -80,19 +86,26 @@ public abstract class MidnightConfig {
|
||||
else try { return ((List<?>) this.value).get(this.listIndex).toString(); } catch (Exception ignored) {return "";}
|
||||
}
|
||||
public void updateFieldValue() {
|
||||
try { if (this.field.get(null) != value) updateConditions(tempValue);
|
||||
try {
|
||||
if (this.field.get(null) != value) entries.values().forEach(EntryInfo::updateConditions);
|
||||
this.field.set(null, this.value);
|
||||
} catch (IllegalAccessException ignored) {}
|
||||
}
|
||||
@SuppressWarnings("ConstantValue") //pertains to requiredModLoaded
|
||||
public void updateConditions(String newTempValue) {
|
||||
for (EntryInfo info : entries) {
|
||||
boolean prevConditionState = info.conditionsMet;
|
||||
if (info.condition != null && ((info.condition.requiredOption().contains(":") ? "" : info.modid + ":") + info.condition.requiredOption()).equals(this.modid + ":" + this.fieldName))
|
||||
info.conditionsMet = Objects.equals(info.condition.requiredValue(), newTempValue);
|
||||
if (info.condition != null && !info.condition.requiredModId().isEmpty() && !PlatformFunctions.isModLoaded(info.condition.requiredModId())) info.conditionsMet = false;
|
||||
if (prevConditionState != info.conditionsMet) reloadScreen = true;
|
||||
public void updateConditions() {
|
||||
boolean prevConditionState = this.conditionsMet;
|
||||
if (this.conditions.length > 0) this.conditionsMet = true; // reset conditions
|
||||
for (Condition condition : this.conditions) {
|
||||
if (!condition.requiredModId().isEmpty() && !PlatformFunctions.isModLoaded(condition.requiredModId())) {
|
||||
this.conditionsMet = false;
|
||||
}
|
||||
String requiredOption = condition.requiredOption().contains(":") ? condition.requiredOption() : (this.modid + ":" + condition.requiredOption());
|
||||
if (entries.get(requiredOption) instanceof EntryInfo info) {
|
||||
this.conditionsMet &= condition.requiredValue().equals(info.tempValue);
|
||||
}
|
||||
if (!this.conditionsMet) break;
|
||||
}
|
||||
if (prevConditionState != this.conditionsMet) reloadScreen = true;
|
||||
}
|
||||
public <T> void writeList(int index, T value) {
|
||||
var list = (List<T>) this.value;
|
||||
@@ -114,19 +127,21 @@ public abstract class MidnightConfig {
|
||||
|
||||
@SuppressWarnings("unused") // Utility for mod authors
|
||||
public static @Nullable Object getDefaultValue(String modid, String entry) {
|
||||
for (EntryInfo e : entries) {
|
||||
if (modid.equals(e.modid) && entry.equals(e.field.getName())) return e.defaultValue;
|
||||
} return null;
|
||||
String key = modid + ":" + entry;
|
||||
return entries.containsKey(key) ? entries.get(key).defaultValue : null;
|
||||
}
|
||||
public static void loadValuesFromJson(String modid) {
|
||||
try { gson.fromJson(Files.newBufferedReader(path), configClass.get(modid)); }
|
||||
catch (Exception e) { write(modid); }
|
||||
|
||||
for (EntryInfo info : entries) if (info.field != null && info.entry != null) {
|
||||
try { info.value = info.field.get(null); info.tempValue = info.toTemporaryValue();
|
||||
info.updateConditions(info.tempValue);
|
||||
} catch (IllegalAccessException ignored) {}
|
||||
}
|
||||
entries.values().forEach(info -> {
|
||||
if (info.field != null && info.entry != null) {
|
||||
try {
|
||||
info.value = info.field.get(null);
|
||||
info.tempValue = info.toTemporaryValue();
|
||||
info.updateConditions();
|
||||
} catch (IllegalAccessException ignored) {}
|
||||
}
|
||||
});
|
||||
}
|
||||
public static void init(String modid, Class<? extends MidnightConfig> config) {
|
||||
path = PlatformFunctions.getConfigDirectory().resolve(modid + ".json");
|
||||
@@ -145,6 +160,7 @@ public abstract class MidnightConfig {
|
||||
@Environment(EnvType.CLIENT)
|
||||
private static void initClient(String modid, Field field, EntryInfo info) {
|
||||
Entry e = info.entry;
|
||||
String key = modid + ":" + field.getName();
|
||||
if (e != null) {
|
||||
if (info.dataType == int.class) textField(info, Integer::parseInt, INTEGER_ONLY, (int) e.min(), (int) e.max(), true);
|
||||
else if (info.dataType == float.class) textField(info, Float::parseFloat, DECIMAL_ONLY, (float) e.min(), (float) e.max(), false);
|
||||
@@ -163,11 +179,14 @@ public abstract class MidnightConfig {
|
||||
};
|
||||
info.function = new AbstractMap.SimpleEntry<ButtonWidget.PressAction, Function<Object, Text>>(button -> {
|
||||
int index = values.indexOf(info.value) + 1;
|
||||
info.value = values.get(index >= values.size() ? 0 : index); button.setMessage(func.apply(info.value));
|
||||
//info.value = values.get(index >= values.size() ? 0 : index); button.setMessage(func.apply(info.value));
|
||||
info.setValue(values.get(index >= values.size() ? 0 : index));
|
||||
button.setMessage(func.apply(info.value));
|
||||
}, func);
|
||||
}
|
||||
}
|
||||
entries.add(info);
|
||||
entries.put(key, info);
|
||||
entryOrder.add(key);
|
||||
}
|
||||
public static Class<?> getUnderlyingType(Field field) {
|
||||
Class<?> rawType = field.getType();
|
||||
@@ -200,7 +219,7 @@ public abstract class MidnightConfig {
|
||||
info.tempValue = s;
|
||||
t.setEditableColor(inLimits? 0xFFFFFFFF : 0xFFFF7777);
|
||||
info.inLimits = inLimits;
|
||||
b.active = entries.stream().allMatch(e -> e.inLimits);
|
||||
b.active = entries.values().stream().allMatch(e -> e.inLimits);
|
||||
|
||||
if (inLimits) {
|
||||
if (info.dataType == Identifier.class) info.setValue(Identifier.tryParse(s));
|
||||
@@ -237,17 +256,18 @@ public abstract class MidnightConfig {
|
||||
this.parent = parent; this.modid = modid;
|
||||
this.translationPrefix = modid + ".midnightconfig.";
|
||||
loadValuesFromJson(modid);
|
||||
|
||||
for (EntryInfo e : entries) if (e.modid.equals(modid)) {
|
||||
String tabId = e.entry != null ? e.entry.category() : e.comment.category();
|
||||
String name = translationPrefix + "category." + tabId;
|
||||
if (!I18n.hasTranslation(name) && tabId.equals("default"))
|
||||
name = translationPrefix + "title";
|
||||
if (!tabs.containsKey(name)) {
|
||||
Tab tab = new GridScreenTab(Text.translatable(name));
|
||||
e.tab = tab; tabs.put(name, tab);
|
||||
} else e.tab = tabs.get(name);
|
||||
}
|
||||
entryOrder.stream().map(entries::get).forEach(info -> {
|
||||
if (info.modid.equals(modid)) {
|
||||
String tabId = info.entry != null ? info.entry.category() : info.comment.category();
|
||||
String name = translationPrefix + "category." + tabId;
|
||||
if (!I18n.hasTranslation(name) && tabId.equals("default"))
|
||||
name = translationPrefix + "title";
|
||||
if (!tabs.containsKey(name)) {
|
||||
Tab tab = new GridScreenTab(Text.translatable(name));
|
||||
info.tab = tab; tabs.put(name, tab);
|
||||
} else info.tab = tabs.get(name);
|
||||
}
|
||||
});
|
||||
tabNavigation = TabNavigationWidget.builder(tabManager, this.width).tabs(tabs.values().toArray(new Tab[0])).build();
|
||||
tabNavigation.selectTab(0, false);
|
||||
tabNavigation.init();
|
||||
@@ -272,7 +292,7 @@ public abstract class MidnightConfig {
|
||||
updateList(); list.setScrollY(0);
|
||||
}
|
||||
scrollProgress = list.getScrollY();
|
||||
for (EntryInfo info : entries) info.updateFieldValue();
|
||||
for (EntryInfo info : entries.values()) info.updateFieldValue();
|
||||
updateButtons();
|
||||
if (reloadScreen) { updateList(); reloadScreen = false; }
|
||||
}
|
||||
@@ -296,7 +316,7 @@ public abstract class MidnightConfig {
|
||||
Objects.requireNonNull(client).setScreen(parent);
|
||||
}
|
||||
private void cleanup() {
|
||||
entries.forEach(info -> {
|
||||
entries.values().forEach(info -> {
|
||||
info.error = null; info.value = null; info.tempValue = null; info.actionButton = null; info.listIndex = 0; info.tab = null; info.inLimits = true;
|
||||
});
|
||||
}
|
||||
@@ -308,7 +328,7 @@ public abstract class MidnightConfig {
|
||||
|
||||
this.addDrawableChild(ButtonWidget.builder(ScreenTexts.CANCEL, button -> this.close()).dimensions(this.width / 2 - 154, this.height - 26, 150, 20).build());
|
||||
done = this.addDrawableChild(ButtonWidget.builder(ScreenTexts.DONE, (button) -> {
|
||||
for (EntryInfo info : entries) if (info.modid.equals(modid)) info.updateFieldValue();
|
||||
for (EntryInfo info : entries.values()) if (info.modid.equals(modid)) info.updateFieldValue();
|
||||
write(modid); cleanup();
|
||||
Objects.requireNonNull(client).setScreen(parent);
|
||||
}).dimensions(this.width / 2 + 4, this.height - 26, 150, 20).build());
|
||||
@@ -321,8 +341,14 @@ public abstract class MidnightConfig {
|
||||
this.list.clear(); fillList();
|
||||
}
|
||||
public void fillList() {
|
||||
for (EntryInfo info : entries) {
|
||||
if (!info.conditionsMet && info.condition != null && !info.condition.visibleButLocked()) continue;
|
||||
for (EntryInfo info : entryOrder.stream().map(entries::get).toList()) {
|
||||
if (!info.conditionsMet) {
|
||||
boolean visibleButLocked = false;
|
||||
for (Condition condition : info.conditions) {
|
||||
visibleButLocked |= condition.visibleButLocked();
|
||||
}
|
||||
if (!visibleButLocked) continue;
|
||||
}
|
||||
if (info.modid.equals(modid) && (info.tab == null || info.tab == tabManager.getCurrentTab())) {
|
||||
Text name = Objects.requireNonNullElseGet(info.name, () -> Text.translatable(translationPrefix + info.fieldName));
|
||||
TextIconButtonWidget resetButton = TextIconButtonWidget.builder(Text.translatable("controls.reset"), (button -> {
|
||||
@@ -565,6 +591,7 @@ public abstract class MidnightConfig {
|
||||
* <code>false</code> – Option is completely hidden
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Repeatable(Conditions.class)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Condition {
|
||||
String requiredModId() default "";
|
||||
@@ -572,4 +599,10 @@ public abstract class MidnightConfig {
|
||||
String requiredValue() default "true";
|
||||
boolean visibleButLocked() default false;
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Conditions {
|
||||
Condition[] value();
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ yarn_mappings=1.21.4+build.1
|
||||
enabled_platforms=fabric,neoforge
|
||||
|
||||
archives_base_name=midnightlib
|
||||
mod_version=1.7.0
|
||||
mod_version=1.7.1-rc.2
|
||||
maven_group=eu.midnightdust
|
||||
release_type=release
|
||||
curseforge_id=488090
|
||||
@@ -18,6 +18,4 @@ fabric_api_version=0.110.5+1.21.4
|
||||
neoforge_version=21.4.3-beta
|
||||
yarn_mappings_patch_neoforge_version = 1.21+build.4
|
||||
|
||||
quilt_loader_version=0.19.0-beta.18
|
||||
quilt_fabric_api_version=7.0.1+0.83.0-1.20
|
||||
mod_menu_version = 9.0.0
|
||||
@@ -10,7 +10,7 @@ import java.util.List;
|
||||
|
||||
/** Every option in a MidnightConfig class has to be public and static, so we can access it from other classes.
|
||||
* The config class also has to extend MidnightConfig*/
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class MidnightConfigExample extends MidnightConfig {
|
||||
public static final String TEXT = "text";
|
||||
public static final String NUMBERS = "numbers";
|
||||
@@ -18,6 +18,7 @@ public class MidnightConfigExample extends MidnightConfig {
|
||||
public static final String LISTS = "lists";
|
||||
public static final String FILES = "files";
|
||||
public static final String CONDITIONS = "conditions";
|
||||
public static final String MULTI_CONDITIONS = "multiConditions";
|
||||
|
||||
@Comment(category = TEXT) public static Comment text1; // Comments are rendered like an option without a button and are excluded from the config file
|
||||
@Comment(category = TEXT, centered = true) public static Comment text2; // Centered comments are the same as normal ones - just centered!
|
||||
@@ -27,9 +28,9 @@ public class MidnightConfigExample extends MidnightConfig {
|
||||
@Entry(category = TEXT) public static String name = "Hello World!"; // Example for a string option, which is in a category!
|
||||
@Entry(category = TEXT, width = 7, min = 7, isColor = true, name = "I am a color!") public static String titleColor = "#ffffff"; // The isColor property adds a color chooser for a hexadecimal color
|
||||
@Entry(category = TEXT, idMode = 0) public static Identifier id = Identifier.ofVanilla("diamond"); // Example for an identifier with matching items displayed next to it!
|
||||
@Entry(category = TEXT) public static TestEnum testEnum = TestEnum.FABRIC; // Example for an enum option
|
||||
public enum TestEnum { // Enums allow the user to cycle through predefined options
|
||||
QUILT, FABRIC, FORGE
|
||||
@Entry(category = TEXT) public static ModPlatform modPlatform = ModPlatform.FABRIC; // Example for an enum option
|
||||
public enum ModPlatform { // Enums allow the user to cycle through predefined options
|
||||
QUILT, FABRIC, FORGE, NEOFORGE, VANILLA
|
||||
}
|
||||
@Entry(category = NUMBERS) public static int fabric = 16777215; // Example for an int option
|
||||
@Entry(category = NUMBERS) public static double world = 1.4D; // Example for a double option
|
||||
@@ -74,7 +75,7 @@ public class MidnightConfigExample extends MidnightConfig {
|
||||
@Entry(category = CONDITIONS, name="Turn me on!")
|
||||
public static boolean turnMeOn = false;
|
||||
@Condition(requiredOption = "modid:turnMeOn", visibleButLocked = true)
|
||||
@Entry(category = CONDITIONS, name="Turn me off!")
|
||||
@Entry(category = CONDITIONS, name="Turn me off (locked if modid:turnMeOn is false)!")
|
||||
public static Boolean turnMeOff = true;
|
||||
@Condition(requiredOption = "modid:turnMeOff", requiredValue = "false")
|
||||
@Entry(category = CONDITIONS, name="Which is the best modloader?")
|
||||
@@ -97,5 +98,34 @@ public class MidnightConfigExample extends MidnightConfig {
|
||||
@Condition(requiredOption = "midnightlib:config_screen_list", requiredValue = "FALSE")
|
||||
@Comment(category = CONDITIONS, name="You disabled MidnightLib's config screen list. Why? :(", centered = true) public static Comment why;
|
||||
|
||||
|
||||
// Multi-Conditions are also supported!
|
||||
public enum Arch {X86, X86_64, AARCH64, RISCV64}
|
||||
public enum OS {WINDOWS, MAC, LINUX}
|
||||
@Entry(category = MULTI_CONDITIONS, name = "Arch") public static Arch myArch = Arch.X86;
|
||||
@Entry(category = MULTI_CONDITIONS, name = "OS") public static OS myOS = OS.WINDOWS;
|
||||
@Entry(category = MULTI_CONDITIONS, name = "Mod Platform") public static ModPlatform myPlatform = ModPlatform.FABRIC;
|
||||
|
||||
@Condition(requiredOption = "modid:myArch", requiredValue = "X86_64")
|
||||
@Condition(requiredOption = "modid:myOS", requiredValue = "WINDOWS")
|
||||
@Condition(requiredOption = "modid:myPlatform", requiredValue = "FABRIC")
|
||||
@Comment(category = MULTI_CONDITIONS, name = "MidnightLib can be used on Windows x86_64 with Fabric!", centered = true) public static Comment x86_64_windows_fabric;
|
||||
|
||||
@Condition(requiredOption = "modid:myArch", requiredValue = "X86_64")
|
||||
@Condition(requiredOption = "modid:myOS", requiredValue = "LINUX")
|
||||
@Condition(requiredOption = "modid:myPlatform", requiredValue = "FABRIC")
|
||||
@Comment(category = MULTI_CONDITIONS, name = "MidnightLib can be used on Linux x86_64 with Fabric!", centered = true) public static Comment x86_64_linux_fabric;
|
||||
|
||||
@Condition(requiredOption = "modid:myArch", requiredValue = "X86_64")
|
||||
@Condition(requiredOption = "modid:myOS", requiredValue = "WINDOWS")
|
||||
@Condition(requiredOption = "modid:myPlatform", requiredValue = "NEOFORGE")
|
||||
@Comment(category = MULTI_CONDITIONS, name = "MidnightLib can be used on Windows x86_64 with NeoForge!", centered = true) public static Comment x86_64_windows_neoforge;
|
||||
|
||||
@Condition(requiredOption = "modid:myArch", requiredValue = "X86_64")
|
||||
@Condition(requiredOption = "modid:myOS", requiredValue = "LINUX")
|
||||
@Condition(requiredOption = "modid:myPlatform", requiredValue = "NEOFORGE")
|
||||
@Comment(category = MULTI_CONDITIONS, name = "MidnightLib can be used on Linux x86_64 with NeoForge!", centered = true) public static Comment x86_64_linux_neoforge;
|
||||
|
||||
|
||||
public static int imposter = 16777215; // - Entries without an @Entry or @Comment annotation are ignored
|
||||
}
|
||||
@@ -10,10 +10,19 @@
|
||||
"modid.midnightconfig.showInfo":"I am a boolean",
|
||||
"modid.midnightconfig.hello":"I am a limited int!",
|
||||
"modid.midnightconfig.id":"I am an Item Identifier!",
|
||||
"modid.midnightconfig.testEnum":"I am an enum!",
|
||||
"modid.midnightconfig.enum.TestEnum.FORGE":"Slow",
|
||||
"modid.midnightconfig.enum.TestEnum.FABRIC":"Fancy",
|
||||
"modid.midnightconfig.enum.TestEnum.QUILT":"Fabulous",
|
||||
"modid.midnightconfig.modPlatform":"I am an enum!",
|
||||
"modid.midnightconfig.enum.Arch.X86":"X86",
|
||||
"modid.midnightconfig.enum.Arch.X86_64":"X86_64",
|
||||
"modid.midnightconfig.enum.Arch.AARCH64":"AARCH64",
|
||||
"modid.midnightconfig.enum.Arch.RISCV64":"RISCV64",
|
||||
"modid.midnightconfig.enum.OS.LINUX":"Linux",
|
||||
"modid.midnightconfig.enum.OS.WINDOWS":"Windows",
|
||||
"modid.midnightconfig.enum.OS.MAC":"MacOS",
|
||||
"modid.midnightconfig.enum.ModPlatform.FORGE":"Forge",
|
||||
"modid.midnightconfig.enum.ModPlatform.FABRIC":"Fabric",
|
||||
"modid.midnightconfig.enum.ModPlatform.QUILT":"Quilt",
|
||||
"modid.midnightconfig.enum.ModPlatform.NEOFORGE":"NeoForge",
|
||||
"modid.midnightconfig.enum.ModPlatform.VANILLA":"Vanilla",
|
||||
"modid.midnightconfig.myFileOrDirectory.fileChooser": "Select an image or directory",
|
||||
"modid.midnightconfig.myFileOrDirectory.fileFilter": "Supported Images (.png, .jpg, .jpeg)",
|
||||
"modid.midnightconfig.category.numbers": "Numbers",
|
||||
@@ -21,5 +30,6 @@
|
||||
"modid.midnightconfig.category.sliders": "Sliders",
|
||||
"modid.midnightconfig.category.lists": "Lists",
|
||||
"modid.midnightconfig.category.files": "Files",
|
||||
"modid.midnightconfig.category.conditions": "Quiz"
|
||||
"modid.midnightconfig.category.conditions": "Quiz",
|
||||
"modid.midnightconfig.category.multiConditions": "Multi-Conditions"
|
||||
}
|
||||
Reference in New Issue
Block a user