diff --git a/docs/additional/Migrations.md b/docs/additional/Migrations.md index 642503c..e2a4c95 100644 --- a/docs/additional/Migrations.md +++ b/docs/additional/Migrations.md @@ -80,7 +80,12 @@ Corresponds to version 4.4.0 - Stricter enforcement of legal entry names: instead of sanitization, unsupported names are logged and ignored ## v11 -Corresponds to version 4.5.0 +Corresponds to version 4.5.0-4.5.1 - Directory RPOs in subdirectories are respected. Previously, only the innermost directory RPO would be used -- Multiple replacements when resolving fallbacks for directory RPOs are prevented \ No newline at end of file +- Multiple replacements when resolving fallbacks for directory RPOs are prevented + +## v12 +Corresponds to version 4.6.0 + +- Support for whole numbers (not integers!) \ No newline at end of file diff --git a/docs/setup/AdvancedConfig.md b/docs/setup/AdvancedConfig.md index 51d08a2..d7e5afa 100644 --- a/docs/setup/AdvancedConfig.md +++ b/docs/setup/AdvancedConfig.md @@ -13,20 +13,26 @@ Every entry can contain the following properties, regardless of its type: ## Numbers There are two ways to display numbers: input boxes and sliders. Any number input that provides a minimum and maximum value will be displayed as a slider instead of a box. +Their type is `number` for fractions and `integer` for whole numbers. ### Example: + ```json { - id: "examplePack", - version: 9, - capabilities: ["FileFilter", "DirFilter"], - conf: { - someOption: { - default: 5, - min: 0, - max: 10 - } + id: "examplePack", + version: 9, + capabilities: [ + "FileFilter", + "DirFilter" + ], + conf: { + someOption: { + type: "number", + default: 5, + min: 0, + max: 10 } + } } ``` ### Result: diff --git a/src/main/java/io/gitlab/jfronny/respackopts/Respackopts.java b/src/main/java/io/gitlab/jfronny/respackopts/Respackopts.java index 986d1f0..82d8858 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/Respackopts.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/Respackopts.java @@ -28,7 +28,7 @@ import java.util.concurrent.CompletableFuture; import java.util.regex.Pattern; public class Respackopts implements ModInitializer, SaveHook { - public static final Integer META_VERSION = 11; + public static final Integer META_VERSION = 12; public static final String FILE_EXTENSION = ".rpo"; public static final Gson GSON = new GsonBuilder() .registerTypeAdapter(ConfigEnumEntry.class, new EnumEntrySerializer()) diff --git a/src/main/java/io/gitlab/jfronny/respackopts/gson/entry/ConfigBranchSerializer.java b/src/main/java/io/gitlab/jfronny/respackopts/gson/entry/ConfigBranchSerializer.java index 773f72c..6df4df6 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/gson/entry/ConfigBranchSerializer.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/gson/entry/ConfigBranchSerializer.java @@ -56,6 +56,7 @@ public class ConfigBranchSerializer implements JsonSerializer, Jso } ConfigEntry entry = switch (type.toLowerCase()) { case "slider", "number", "numeric" -> context.deserialize(j, ConfigNumericEntry.class); + case "integer", "int", "long", "whole" -> context.deserialize(j, ConfigNumericEntry.class).asInteger(); case "boolean", "toggle" -> context.deserialize(j, ConfigBooleanEntry.class); case "enum", "select" -> context.deserialize(j, ConfigEnumEntry.class); default -> throw new JsonSyntaxException("Unknown entry type: " + type); diff --git a/src/main/java/io/gitlab/jfronny/respackopts/gson/entry/NumericEntrySerializer.java b/src/main/java/io/gitlab/jfronny/respackopts/gson/entry/NumericEntrySerializer.java index e1492f2..4c301dc 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/gson/entry/NumericEntrySerializer.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/gson/entry/NumericEntrySerializer.java @@ -25,13 +25,13 @@ public class NumericEntrySerializer implements JsonSerializer implements DNumber { - public Double min = null; - public Double max = null; + private Double min = null; + private Double max = null; + private boolean integer = false; public ConfigNumericEntry() { super(Double.class); setValue(0d); } + public ConfigNumericEntry asInteger() { + this.integer = true; + if (min != null && (min % 1.0 != 0)) { + this.integer = false; + throw new RuntimeException("Minimum value (" + min + ") is not integer for integer entry"); + } + if (max != null && (max % 1.0 != 0)) { + this.integer = false; + throw new RuntimeException("Minimum value (" + max + ") is not integer for integer entry"); + } + return this; + } + + public void setMin(Double value) { + if (value != null && integer && (value % 1.0 != 0)) + throw new RuntimeException("Minimum value (" + value + ") is not integer for integer entry"); + this.min = value; + } + + public void setMax(Double value) { + if (value != null && integer && (value % 1.0 != 0)) + throw new RuntimeException("Maximum value (" + value + ") is not integer for integer entry"); + this.max = value; + } + @Override public Double setDefault(Double value) { + if (integer && (value % 1.0 != 0)) + throw new RuntimeException("Default value (" + value + ") is not integer for integer entry"); if ((min != null && value < min) || (max != null && value > max)) throw new RuntimeException("Default value (" + value + ") out of range in number entry definition (" + min + "-" + max + ")"); return super.setDefault(value); @@ -30,26 +58,25 @@ public class ConfigNumericEntry extends ConfigEntry implements DNumber { super.sync(source, mode); ConfigNumericEntry n = (ConfigNumericEntry) source; if (mode == ConfigSyncMode.RESPACK_LOAD) { - if (n.min != null) - min = n.min; - if (n.max != null) - max = n.max; + min = n.min; + max = n.max; + integer = n.integer; } } @Override public void appendString(IndentingStringBuilder sb) { - sb.line(getValue() + " (" + getDefault() + ", " + min + "-" + max + ")"); + sb.line(getValue() + " (" + getDefault() + ", " + min + "-" + max + (integer ? ", integer" : "") + ")"); } @Override public ConfigEntry clone() { ConfigNumericEntry ce = new ConfigNumericEntry(); - ce.max = max; - ce.min = min; + ce.setMax(max); + ce.setMin(min); ce.setValue(getValue()); ce.setDefault(getDefault()); - return ce; + return integer ? ce.asInteger() : ce; } @Override @@ -62,13 +89,43 @@ public class ConfigNumericEntry extends ConfigEntry implements DNumber { @Override public CategoryBuilder buildEntry(GuiEntryBuilderParam args) { - return args.builder().value(args.name(), getDefault(), min == null ? Double.NEGATIVE_INFINITY : min, max == null ? Double.POSITIVE_INFINITY : max, this::getValue, v -> { - if (!Objects.equals(getValue(), v)) { - if (RespackoptsConfig.debugLogs) Respackopts.LOGGER.info("ConfigNumericEntryNormal SaveCallback"); - args.saveCallback(); - } - setValue(v); - }); + double min = this.min == null ? Double.NEGATIVE_INFINITY : this.min; + double max = this.max == null ? Double.NEGATIVE_INFINITY : this.max; + if (integer) { + return args.builder().value( + args.name(), + asLong(getDefault()), + min, + max, + () -> asLong(getValue()), + v -> { + if (!Objects.equals(asLong(getValue()), v)) { + if (RespackoptsConfig.debugLogs) Respackopts.LOGGER.info("ConfigNumericEntryNormal SaveCallback"); + args.saveCallback(); + } + setValue(v == null ? null : v.doubleValue()); + } + ); + } else { + return args.builder().value( + args.name(), + getDefault(), + min, + max, + this::getValue, + v -> { + if (!Objects.equals(getValue(), v)) { + if (RespackoptsConfig.debugLogs) Respackopts.LOGGER.info("ConfigNumericEntryNormal SaveCallback"); + args.saveCallback(); + } + setValue(v); + } + ); + } + } + + private static Long asLong(Double d) { + return d == null ? null : d.longValue(); } @Override @@ -76,11 +133,15 @@ public class ConfigNumericEntry extends ConfigEntry implements DNumber { if (this == o) return true; if (!(o instanceof ConfigNumericEntry that)) return false; if (!super.equals(o)) return false; - return Objects.equals(getValue(), that.getValue()) && Objects.equals(getDefault(), that.getDefault()) && Objects.equals(min, that.min) && Objects.equals(max, that.max); + return Objects.equals(getValue(), that.getValue()) + && Objects.equals(getDefault(), that.getDefault()) + && Objects.equals(min, that.min) + && Objects.equals(max, that.max) + && integer == that.integer; } @Override public int hashCode() { - return Objects.hash(super.hashCode(), getValue(), getDefault(), min, max); + return Objects.hash(super.hashCode(), getValue(), getDefault(), min, max, integer); } }