diff options
| author | isxander <xander@isxander.dev> | 2024-04-11 18:43:06 +0100 |
|---|---|---|
| committer | isxander <xander@isxander.dev> | 2024-04-11 18:43:06 +0100 |
| commit | 04fe933f4c24817100f3101f088accf55a621f8a (patch) | |
| tree | feff94ca3ab4484160e69a24f4ee38522381950e /src/main/java/dev/isxander/yacl3/config/v2/impl | |
| parent | 831b894fdb7fe3e173d81387c8f6a2402b8ccfa9 (diff) | |
| download | YetAnotherConfigLib-04fe933f4c24817100f3101f088accf55a621f8a.tar.gz YetAnotherConfigLib-04fe933f4c24817100f3101f088accf55a621f8a.tar.bz2 YetAnotherConfigLib-04fe933f4c24817100f3101f088accf55a621f8a.zip | |
Extremely fragile and broken multiversion build with stonecutter
Diffstat (limited to 'src/main/java/dev/isxander/yacl3/config/v2/impl')
28 files changed, 1426 insertions, 0 deletions
diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigClassHandlerImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigClassHandlerImpl.java new file mode 100644 index 0000000..813b3ab --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigClassHandlerImpl.java @@ -0,0 +1,274 @@ +package dev.isxander.yacl3.config.v2.impl; + +import dev.isxander.yacl3.api.*; +import dev.isxander.yacl3.config.ConfigEntry; +import dev.isxander.yacl3.config.v2.api.*; +import dev.isxander.yacl3.config.v2.api.autogen.AutoGen; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.config.v2.impl.autogen.OptionFactoryRegistry; +import dev.isxander.yacl3.config.v2.impl.autogen.OptionAccessImpl; +import dev.isxander.yacl3.config.v2.impl.autogen.YACLAutoGenException; +import dev.isxander.yacl3.impl.utils.YACLConstants; +import dev.isxander.yacl3.platform.YACLPlatform; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.Validate; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class ConfigClassHandlerImpl<T> implements ConfigClassHandler<T> { + private final Class<T> configClass; + private final ResourceLocation id; + private final boolean supportsAutoGen; + private final ConfigSerializer<T> serializer; + private final ConfigFieldImpl<?>[] fields; + + private T instance; + private final T defaults; + private final Constructor<T> noArgsConstructor; + + public ConfigClassHandlerImpl(Class<T> configClass, ResourceLocation id, Function<ConfigClassHandler<T>, ConfigSerializer<T>> serializerFactory) { + this.configClass = configClass; + this.id = id; + this.supportsAutoGen = id != null && YACLPlatform.getEnvironment().isClient(); + + try { + noArgsConstructor = configClass.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new YACLAutoGenException("Failed to find no-args constructor for config class %s.".formatted(configClass.getName()), e); + } + this.instance = createNewObject(); + this.defaults = createNewObject(); + + detectOldAnnotation(configClass.getDeclaredFields()); + + this.fields = discoverFields(); + this.serializer = serializerFactory.apply(this); + } + + private ConfigFieldImpl<?>[] discoverFields() { + return Arrays.stream(configClass.getDeclaredFields()) + .peek(field -> field.setAccessible(true)) + .filter(field -> field.isAnnotationPresent(SerialEntry.class) || field.isAnnotationPresent(AutoGen.class)) + .map(field -> new ConfigFieldImpl<>( + new ReflectionFieldAccess<>(field, instance), + new ReflectionFieldAccess<>(field, defaults), + this, + field.getAnnotation(SerialEntry.class), + field.getAnnotation(AutoGen.class) + )) + .toArray(ConfigFieldImpl[]::new); + } + + @Override + public T instance() { + return this.instance; + } + + @Override + public T defaults() { + return this.defaults; + } + + @Override + public Class<T> configClass() { + return this.configClass; + } + + @Override + public ConfigFieldImpl<?>[] fields() { + return this.fields; + } + + @Override + public ResourceLocation id() { + return this.id; + } + + @Override + public boolean supportsAutoGen() { + return this.supportsAutoGen; + } + + @Override + public YetAnotherConfigLib generateGui() { + if (!supportsAutoGen()) { + throw new YACLAutoGenException("Auto GUI generation is not supported for this config class. You either need to enable it in the builder or you are attempting to create a GUI in a dedicated server environment."); + } + + boolean hasAutoGenFields = Arrays.stream(fields()).anyMatch(field -> field.autoGen().isPresent()); + + if (!hasAutoGenFields) { + throw new YACLAutoGenException("No fields in this config class are annotated with @AutoGen. You must annotate at least one field with @AutoGen to generate a GUI."); + } + + OptionAccessImpl storage = new OptionAccessImpl(); + Map<String, CategoryAndGroups> categories = new LinkedHashMap<>(); + for (ConfigField<?> configField : fields()) { + configField.autoGen().ifPresent(autoGen -> { + CategoryAndGroups groups = categories.computeIfAbsent( + autoGen.category(), + k -> new CategoryAndGroups( + ConfigCategory.createBuilder() + .name(Component.translatable("yacl3.config.%s.category.%s".formatted(id().toString(), k))), + new LinkedHashMap<>() + ) + ); + OptionAddable group = groups.groups().computeIfAbsent(autoGen.group().orElse(""), k -> { + if (k.isEmpty()) + return groups.category(); + return OptionGroup.createBuilder() + .name(Component.translatable("yacl3.config.%s.category.%s.group.%s".formatted(id().toString(), autoGen.category(), k))); + }); + + Option<?> option; + try { + option = createOption(configField, storage); + } catch (Exception e) { + throw new YACLAutoGenException("Failed to create option for field '%s'".formatted(configField.access().name()), e); + } + + storage.putOption(configField.access().name(), option); + group.option(option); + }); + } + storage.checkBadOperations(); + categories.values().forEach(CategoryAndGroups::finaliseGroups); + + YetAnotherConfigLib.Builder yaclBuilder = YetAnotherConfigLib.createBuilder() + .save(this.serializer()::save) + .title(Component.translatable("yacl3.config.%s.title".formatted(this.id().toString()))); + categories.values().forEach(category -> yaclBuilder.category(category.category().build())); + + return yaclBuilder.build(); + } + + private <U> Option<U> createOption(ConfigField<U> configField, OptionAccess storage) { + return OptionFactoryRegistry.createOption(((ReflectionFieldAccess<?>) configField.access()).field(), configField, storage) + .orElseThrow(() -> new YACLAutoGenException("Failed to create option for field %s".formatted(configField.access().name()))); + } + + @Override + public ConfigSerializer<T> serializer() { + return this.serializer; + } + + @Override + public boolean load() { + // create a new instance to load into + T newInstance = createNewObject(); + + // create field accesses for the new object + Map<ConfigFieldImpl<?>, ReflectionFieldAccess<?>> accessBufferImpl = Arrays.stream(fields()) + .map(field -> new AbstractMap.SimpleImmutableEntry<>( + field, + new ReflectionFieldAccess<>(field.access().field(), newInstance) + )) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + // convert the map into API safe field accesses + Map<ConfigField<?>, FieldAccess<?>> accessBuffer = accessBufferImpl.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + // attempt to load the config + ConfigSerializer.LoadResult loadResult = ConfigSerializer.LoadResult.FAILURE; + Throwable error = null; + try { + loadResult = this.serializer().loadSafely(accessBuffer); + } catch (Throwable e) { + // handle any errors later in the loadResult switch case + error = e; + } + + switch (loadResult) { + case DIRTY: + case SUCCESS: + // replace the instance with the newly created one + this.instance = newInstance; + for (ConfigFieldImpl<?> field : fields()) { + // update the field accesses to point to the correct object + ((ConfigFieldImpl<Object>) field).setFieldAccess((ReflectionFieldAccess<Object>) accessBufferImpl.get(field)); + } + + if (loadResult == ConfigSerializer.LoadResult.DIRTY) { + // if the load result is dirty, we need to save the config again + this.save(); + } + case NO_CHANGE: + return true; + case FAILURE: + YACLConstants.LOGGER.error( + "Unsuccessful load of config class '{}'. The load will be abandoned and config remains unchanged.", + configClass.getSimpleName(), error + ); + } + + return false; + } + + @Override + public void save() { + serializer().save(); + } + + private T createNewObject() { + try { + return noArgsConstructor.newInstance(); + } catch (Exception e) { + throw new YACLAutoGenException("Failed to create instance of config class '%s' with no-args constructor.".formatted(configClass.getName()), e); + } + } + + private void detectOldAnnotation(Field[] fields) { + boolean hasOldConfigEntry = Arrays.stream(fields) + .anyMatch(field -> field.isAnnotationPresent(ConfigEntry.class)); + + Validate.isTrue(!hasOldConfigEntry, "At least one field in %s is still annotated with the deprecated @ConfigEntry annotation. This is incorrect. Use @SerialEntry.".formatted(configClass.getName())); + } + + public static class BuilderImpl<T> implements Builder<T> { + private final Class<T> configClass; + private ResourceLocation id; + private Function<ConfigClassHandler<T>, ConfigSerializer<T>> serializerFactory; + + public BuilderImpl(Class<T> configClass) { + this.configClass = configClass; + } + + @Override + public Builder<T> id(ResourceLocation id) { + this.id = id; + return this; + } + + @Override + public Builder<T> serializer(Function<ConfigClassHandler<T>, ConfigSerializer<T>> serializerFactory) { + this.serializerFactory = serializerFactory; + return this; + } + + @Override + public ConfigClassHandler<T> build() { + Validate.notNull(serializerFactory, "serializerFactory must not be null"); + Validate.notNull(configClass, "configClass must not be null"); + + return new ConfigClassHandlerImpl<>(configClass, id, serializerFactory); + } + } + + private record CategoryAndGroups(ConfigCategory.Builder category, Map<String, OptionAddable> groups) { + private void finaliseGroups() { + groups.forEach((name, group) -> { + if (group instanceof OptionGroup.Builder groupBuilder) { + category.group(groupBuilder.build()); + } + }); + } + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigFieldImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigFieldImpl.java new file mode 100644 index 0000000..aeed5ac --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/ConfigFieldImpl.java @@ -0,0 +1,75 @@ +package dev.isxander.yacl3.config.v2.impl; + +import dev.isxander.yacl3.config.v2.api.*; +import dev.isxander.yacl3.config.v2.api.autogen.AutoGen; +import dev.isxander.yacl3.config.v2.api.autogen.AutoGenField; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +public class ConfigFieldImpl<T> implements ConfigField<T> { + private ReflectionFieldAccess<T> field; + private final ReflectionFieldAccess<T> defaultField; + private final ConfigClassHandler<?> parent; + private final Optional<SerialField> serial; + private final Optional<AutoGenField> autoGen; + + public ConfigFieldImpl(ReflectionFieldAccess<T> field, ReflectionFieldAccess<T> defaultField, ConfigClassHandler<?> parent, @Nullable SerialEntry config, @Nullable AutoGen autoGen) { + this.field = field; + this.defaultField = defaultField; + this.parent = parent; + + this.serial = config != null + ? Optional.of( + new SerialFieldImpl( + "".equals(config.value()) ? field.name() : config.value(), + "".equals(config.comment()) ? Optional.empty() : Optional.of(config.comment()), + config.required(), + config.nullable() + ) + ) + : Optional.empty(); + this.autoGen = autoGen != null + ? Optional.of( + new AutoGenFieldImpl<>( + autoGen.category(), + "".equals(autoGen.group()) ? Optional.empty() : Optional.of(autoGen.group()) + ) + ) + : Optional.empty(); + } + + @Override + public ReflectionFieldAccess<T> access() { + return field; + } + + public void setFieldAccess(ReflectionFieldAccess<T> field) { + this.field = field; + } + + @Override + public ReflectionFieldAccess<T> defaultAccess() { + return defaultField; + } + + @Override + public ConfigClassHandler<?> parent() { + return parent; + } + + @Override + public Optional<SerialField> serial() { + return this.serial; + } + + @Override + public Optional<AutoGenField> autoGen() { + return this.autoGen; + } + + private record SerialFieldImpl(String serialName, Optional<String> comment, boolean required, boolean nullable) implements SerialField { + } + private record AutoGenFieldImpl<T>(String category, Optional<String> group) implements AutoGenField { + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/FieldBackedBinding.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/FieldBackedBinding.java new file mode 100644 index 0000000..f2f36e7 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/FieldBackedBinding.java @@ -0,0 +1,22 @@ +package dev.isxander.yacl3.config.v2.impl; + +import dev.isxander.yacl3.api.Binding; +import dev.isxander.yacl3.config.v2.api.FieldAccess; +import dev.isxander.yacl3.config.v2.api.ReadOnlyFieldAccess; + +public record FieldBackedBinding<T>(FieldAccess<T> field, ReadOnlyFieldAccess<T> defaultField) implements Binding<T> { + @Override + public T getValue() { + return field.get(); + } + + @Override + public void setValue(T value) { + field.set(value); + } + + @Override + public T defaultValue() { + return defaultField.get(); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/ReflectionFieldAccess.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/ReflectionFieldAccess.java new file mode 100644 index 0000000..e102344 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/ReflectionFieldAccess.java @@ -0,0 +1,49 @@ +package dev.isxander.yacl3.config.v2.impl; + +import dev.isxander.yacl3.config.v2.api.FieldAccess; +import dev.isxander.yacl3.config.v2.impl.autogen.YACLAutoGenException; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.util.Optional; + +public record ReflectionFieldAccess<T>(Field field, Object instance) implements FieldAccess<T> { + @Override + public T get() { + try { + return (T) field.get(instance); + } catch (IllegalAccessException e) { + throw new YACLAutoGenException("Failed to access field '%s'".formatted(name()), e); + } + } + + @Override + public void set(T value) { + try { + field.set(instance, value); + } catch (IllegalAccessException e) { + throw new YACLAutoGenException("Failed to set field '%s'".formatted(name()), e); + } + } + + @Override + public String name() { + return field.getName(); + } + + @Override + public Type type() { + return field.getGenericType(); + } + + @Override + public Class<T> typeClass() { + return (Class<T>) field.getType(); + } + + @Override + public <A extends Annotation> Optional<A> getAnnotation(Class<A> annotationClass) { + return Optional.ofNullable(field.getAnnotation(annotationClass)); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/AutoGenUtils.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/AutoGenUtils.java new file mode 100644 index 0000000..6f614c1 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/AutoGenUtils.java @@ -0,0 +1,54 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.ValueFormattableController; +import dev.isxander.yacl3.api.controller.ValueFormatter; +import dev.isxander.yacl3.config.v2.api.ReadOnlyFieldAccess; +import dev.isxander.yacl3.config.v2.api.autogen.CustomFormat; +import dev.isxander.yacl3.config.v2.api.autogen.FormatTranslation; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.ApiStatus; + +import java.util.Optional; +import java.util.function.Supplier; + +@ApiStatus.Internal +public final class AutoGenUtils { + public static <T> void addCustomFormatterToController(ControllerBuilder<T> controller, ReadOnlyFieldAccess<T> field) { + Optional<CustomFormat> formatter = field.getAnnotation(CustomFormat.class); + Optional<FormatTranslation> translation = field.getAnnotation(FormatTranslation.class); + + if (formatter.isPresent() && translation.isPresent()) { + throw new YACLAutoGenException("'%s': Cannot use both @CustomFormatter and @FormatTranslation on the same field.".formatted(field.name())); + } else if (formatter.isEmpty() && translation.isEmpty()) { + return; + } + + if (!(controller instanceof ValueFormattableController<?, ?>)) { + throw new YACLAutoGenException("Attempted to use @CustomFormatter or @FormatTranslation on an option factory for field '%s' that uses a controller that does not support this.".formatted(field.name())); + } + + ValueFormattableController<T, ?> typedBuilder = (ValueFormattableController<T, ?>) controller; + + formatter.ifPresent(formatterClass -> { + try { + typedBuilder.formatValue((ValueFormatter<T>) formatterClass.value().getConstructor().newInstance()); + } catch (Exception e) { + throw new YACLAutoGenException("'%s': Failed to instantiate formatter class %s.".formatted(field.name(), formatterClass.value().getName()), e); + } + }); + + translation.ifPresent(annotation -> + typedBuilder.formatValue(v -> Component.translatable(annotation.value(), v))); + } + + public static <T> T constructNoArgsClass(Class<T> clazz, Supplier<String> constructorNotFoundConsumer, Supplier<String> constructorFailedConsumer) { + try { + return clazz.getConstructor().newInstance(); + } catch (NoSuchMethodException e) { + throw new YACLAutoGenException(constructorNotFoundConsumer.get(), e); + } catch (Exception e) { + throw new YACLAutoGenException(constructorFailedConsumer.get(), e); + } + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/BooleanImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/BooleanImpl.java new file mode 100644 index 0000000..b41836a --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/BooleanImpl.java @@ -0,0 +1,25 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.BooleanControllerBuilder; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.Boolean; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import net.minecraft.network.chat.Component; + +public class BooleanImpl extends SimpleOptionFactory<Boolean, java.lang.Boolean> { + @Override + protected ControllerBuilder<java.lang.Boolean> createController(Boolean annotation, ConfigField<java.lang.Boolean> field, OptionAccess storage, Option<java.lang.Boolean> option) { + var builder = BooleanControllerBuilder.create(option) + .coloured(annotation.colored()); + switch (annotation.formatter()) { + case ON_OFF -> builder.onOffFormatter(); + case YES_NO -> builder.yesNoFormatter(); + case TRUE_FALSE -> builder.trueFalseFormatter(); + case CUSTOM -> builder.formatValue(v -> Component.translatable(getTranslationKey(field, "fmt." + v))); + } + return builder; + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ColorFieldImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ColorFieldImpl.java new file mode 100644 index 0000000..7910c59 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/ColorFieldImpl.java @@ -0,0 +1,19 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ColorControllerBuilder; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.ColorField; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; + +import java.awt.Color; + +public class ColorFieldImpl extends SimpleOptionFactory<ColorField, Color> { + @Override + protected ControllerBuilder<Color> createController(ColorField annotation, ConfigField<Color> field, OptionAccess storage, Option<Color> option) { + return ColorControllerBuilder.create(option) + .allowAlpha(annotation.allowAlpha()); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleFieldImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleFieldImpl.java new file mode 100644 index 0000000..6445141 --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleFieldImpl.java @@ -0,0 +1,32 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.DoubleFieldControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.DoubleField; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; + +public class DoubleFieldImpl extends SimpleOptionFactory<DoubleField, Double> { + @Override + protected ControllerBuilder<Double> createController(DoubleField annotation, ConfigField<Double> field, OptionAccess storage, Option<Double> option) { + return DoubleFieldControllerBuilder.create(option) + .formatValue(v -> { + String key = null; + if (v == annotation.min()) + key = getTranslationKey(field, "fmt.min"); + else if (v == annotation.max()) + key = getTranslationKey(field, "fmt.max"); + if (key != null && Language.getInstance().has(key)) + return Component.translatable(key); + key = getTranslationKey(field, "fmt"); + if (Language.getInstance().has(key)) + return Component.translatable(key, v); + return Component.translatable(String.format(annotation.format(), v)); + }) + .range(annotation.min(), annotation.max()); + } +} diff --git a/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java new file mode 100644 index 0000000..e6dd05d --- /dev/null +++ b/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java @@ -0,0 +1,33 @@ +package dev.isxander.yacl3.config.v2.impl.autogen; + +import dev.isxander.yacl3.api.Option; +import dev.isxander.yacl3.api.controller.ControllerBuilder; +import dev.isxander.yacl3.api.controller.DoubleSliderControllerBuilder; +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.api.autogen.SimpleOptionFactory; +import dev.isxander.yacl3.config.v2.api.autogen.DoubleSlider; +import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; + +public class DoubleSliderImpl extends SimpleOptionFactory<DoubleSlider, Double> { + @Override + protected ControllerBuilder<Double> createController(DoubleSlider annotation, ConfigField<Double> field, OptionAccess storage, Option<Double> option) { + return DoubleSliderControllerBuilder.create(option) + .formatValue(v -> { + String key = null; + if (v == annotation.min()) + key = getTranslationKey(field, "fmt.min"); + else if (v == annotation.max()) + key = getTranslationKey(field, "fmt.max"); + if (key != null && Language.getInstance().has(key)) + return Component.translatable(key); + key = getTranslationKey(field, "fmt"); + if (Language.getIn |
