Files
common/src/main/java/org/saturnclient/config/manager/Property.java
T
2026-03-06 00:42:16 +01:00

320 lines
8.6 KiB
Java

package org.saturnclient.config.manager;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.saturnclient.common.minecraft.MinecraftProvider;
import java.util.Map;
public class Property<T> {
public static class NamedProperty<T> {
public String name;
public Property<T> prop;
public NamedProperty(String name, Property<T> value) {
this.name = name;
this.prop = value;
}
}
public enum PropertyType {
BOOLEAN,
INTEGER,
FLOAT,
STRING,
HEX,
NAMESPACE,
SELECT,
KEYBINDING,
}
public T value;
public boolean isReset = false;
private T defaultValue;
private String[] availableValues;
private final PropertyType type;
private boolean wasPressedLastTick = false;
private Property(T value, PropertyType type) {
this.value = value;
this.defaultValue = value;
this.type = type;
}
// ---------- Factory Methods ----------
public static <T> Property<T> from(T value) {
if (value instanceof Boolean)
return new Property<>(value, PropertyType.BOOLEAN);
if (value instanceof Integer)
return new Property<>(value, PropertyType.INTEGER);
if (value instanceof Float)
return new Property<>(value, PropertyType.FLOAT);
if (value instanceof String)
return new Property<>(value, PropertyType.STRING);
if (valueIsNamespace(value))
return new Property<>(value, PropertyType.NAMESPACE);
return null;
}
public static Property<Integer> font(int value) {
return select(value, "Default", "Inter", "Inter bold");
}
public static Property<Integer> select(Integer value, String... availableValues) {
Property<Integer> property = new Property<>(value, PropertyType.SELECT);
property.availableValues = availableValues;
return property;
}
public static Property<Map<String, Property<?>>> namespace(Map<String, Property<?>> value) {
return new Property<>(value, PropertyType.NAMESPACE);
}
public static Property<Boolean> bool(boolean value) {
return new Property<>(value, PropertyType.BOOLEAN);
}
public static Property<Integer> integer(int value) {
return new Property<>(value, PropertyType.INTEGER);
}
public static Property<Float> floatProp(float value) {
return new Property<>(value, PropertyType.FLOAT);
}
public static Property<String> string(String value) {
return new Property<>(value, PropertyType.STRING);
}
public static Property<Integer> color(int value) {
return new Property<>(value, PropertyType.HEX);
}
public static Property<Integer> keybinding(int value) {
return new Property<>(value, PropertyType.KEYBINDING);
}
// ---------- Select Helpers ----------
public void next() {
if (type == PropertyType.SELECT) {
int i = (Integer) value;
setValue((i < availableValues.length - 1) ? i + 1 : 0);
}
}
public void prev() {
if (type == PropertyType.SELECT) {
int i = (Integer) value;
setValue((i > 0) ? i - 1 : availableValues.length - 1);
}
}
public void setSelection(int selection) {
if (type == PropertyType.SELECT && selection >= 0 && selection < availableValues.length) {
setValue(selection);
}
}
public String getSelection() {
return (type == PropertyType.SELECT) ? availableValues[(Integer) value] : null;
}
// ---------- Lifecycle ----------
public void reset() {
value = defaultValue;
isReset = true;
}
public PropertyType getType() {
return type;
}
@SuppressWarnings("unchecked")
public Map<String, Property<?>> getNamespaceValue() {
if (type == PropertyType.NAMESPACE && value instanceof Map) {
return (Map<String, Property<?>>) value;
}
throw new IllegalStateException("Property is not a namespace");
}
// ---------- JSON Serialization ----------
public JsonNode toJson() {
switch (type) {
case BOOLEAN:
return JsonNodeFactory.instance.booleanNode((Boolean) value);
case INTEGER:
return JsonNodeFactory.instance.numberNode((Integer) value);
case FLOAT:
return JsonNodeFactory.instance.numberNode((Float) value);
case STRING:
return JsonNodeFactory.instance.textNode((String) value);
case HEX:
return JsonNodeFactory.instance.textNode(
String.format("#%08X", (Integer) value));
case KEYBINDING:
case SELECT:
return JsonNodeFactory.instance.numberNode((Integer) value);
case NAMESPACE:
ObjectNode nested = JsonNodeFactory.instance.objectNode();
getNamespaceValue().forEach((k, v) -> {
nested.set(k, v.toJson());
});
return nested;
default:
return JsonNodeFactory.instance.textNode(String.valueOf(value));
}
}
public void loadFromJson(JsonNode element) {
if (element == null)
return;
switch (type) {
case BOOLEAN:
if (element.isBoolean())
setValue(element.booleanValue());
break;
case INTEGER:
if (element.isInt())
setValue(element.intValue());
else if (element.isTextual())
setValue(parseHexToInt(element.textValue()));
break;
case FLOAT:
if (element.isNumber())
setValue(element.floatValue());
break;
case STRING:
if (element.isTextual())
setValue(element.textValue());
break;
case HEX:
if (element.isTextual())
setValue(parseHexToInt(element.textValue()));
break;
case NAMESPACE:
if (element.isObject()) {
ObjectNode obj = (ObjectNode) element;
getNamespaceValue().forEach((k, v) -> {
v.loadFromJson(obj.get(k));
});
}
break;
case SELECT:
if (element.isInt())
setSelection(element.intValue());
break;
case KEYBINDING:
if (element.isInt())
setValue(element.intValue());
break;
}
}
// ---------- Utility ----------
@Override
public String toString() {
return String.valueOf(value);
}
public static boolean valueIsNamespace(Object obj) {
return obj instanceof Map;
}
@SuppressWarnings("unchecked")
public void setValue(Object value) {
this.value = (T) value;
}
@SuppressWarnings("unchecked")
public void setValue(Object value, Object defaultValue) {
this.value = (T) value;
this.defaultValue = (T) defaultValue;
}
public NamedProperty<T> named(String name) {
return new NamedProperty<>(name, this);
}
// ---------- Keybindings (no SaturnClient) ----------
public boolean isKeyPressed() {
return (Integer) value != -1 &&
MinecraftProvider.PROVIDER.isKeyPressed((Integer) value);
}
public boolean wasKeyPressed() {
boolean pressed = isKeyPressed();
boolean result = pressed && !wasPressedLastTick;
wasPressedLastTick = pressed;
return result;
}
// ---------- HEX ----------
public static int parseHexToInt(String hex) {
hex = hex.replace("#", "");
if (hex.length() == 6)
hex = "FF" + hex;
if (hex.length() != 8)
throw new IllegalArgumentException(
"Hex must be 6 or 8 chars long, got '" + hex + "'");
return (int) Long.parseLong(hex, 16);
}
// ---------- Misc ----------
public Property<T> copy() {
return new Property<>(this.value, this.type);
}
public boolean isNamespace() {
return type == PropertyType.NAMESPACE;
}
@SuppressWarnings("unchecked")
public Map<String, Property<?>> getNamespaceMap() {
return isNamespace() ? (Map<String, Property<?>>) value : null;
}
}