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 { public static class NamedProperty { public String name; public Property prop; public NamedProperty(String name, Property 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 Property 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 font(int value) { return select(value, "Default", "Inter", "Inter bold"); } public static Property select(Integer value, String... availableValues) { Property property = new Property<>(value, PropertyType.SELECT); property.availableValues = availableValues; return property; } public static Property>> namespace(Map> value) { return new Property<>(value, PropertyType.NAMESPACE); } public static Property bool(boolean value) { return new Property<>(value, PropertyType.BOOLEAN); } public static Property integer(int value) { return new Property<>(value, PropertyType.INTEGER); } public static Property floatProp(float value) { return new Property<>(value, PropertyType.FLOAT); } public static Property string(String value) { return new Property<>(value, PropertyType.STRING); } public static Property color(int value) { return new Property<>(value, PropertyType.HEX); } public static Property 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> getNamespaceValue() { if (type == PropertyType.NAMESPACE && value instanceof Map) { return (Map>) 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 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 copy() { return new Property<>(this.value, this.type); } public boolean isNamespace() { return type == PropertyType.NAMESPACE; } @SuppressWarnings("unchecked") public Map> getNamespaceMap() { return isNamespace() ? (Map>) value : null; } }