diff options
18 files changed, 177 insertions, 62 deletions
diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomDescription.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomDescription.java new file mode 100644 index 0000000..08624b4 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomDescription.java @@ -0,0 +1,12 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface CustomDescription { + String[] value() default ""; +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OverrideFormatter.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomFormat.java index f97395c..15f6336 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OverrideFormatter.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomFormat.java @@ -12,6 +12,6 @@ import java.lang.annotation.Target; */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) -public @interface OverrideFormatter { +public @interface CustomFormat { Class<? extends ValueFormatter<?>> value(); } diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomImage.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomImage.java new file mode 100644 index 0000000..d193f42 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomImage.java @@ -0,0 +1,69 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import dev.isxander.yacl3.config.v2.api.ConfigField; +import dev.isxander.yacl3.config.v2.impl.autogen.EmptyCustomImageFactory; +import dev.isxander.yacl3.gui.image.ImageRenderer; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +/** + * Defines a custom image for an option. + * Without this annotation, the option factory will look + * for the resource {@code modid:textures/yacl3/$config_id_path/$fieldName.webp}. + * WEBP was chosen as the default format because file sizes are greatly reduced, + * which is important to keep your JAR size down, if you're so bothered. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface CustomImage { + /** + * The resource path to the image, a {@link net.minecraft.resources.ResourceLocation} + * is constructed with the namespace being the modid of the config, and the path being + * this value. + * <p> + * The following file formats are supported: + * <ul> + * <li>{@code .png}</li> + * <li>{@code .webp}</li> + * <li>{@code .jpg}, {@code .jpeg}</li> + * <li>{@code .gif} - <strong>HIGHLY DISCOURAGED DUE TO LARGE FILE SIZE</strong></li> + * </ul> + * <p> + * If left blank, then {@link CustomImage#factory()} is used. + */ + String value() default ""; + + /** + * The width of the image, in pixels. + * <strong>This is only required when using a PNG with {@link CustomImage#value()}</strong> + */ + int width() default 0; + + /** + * The width of the image, in pixels. + * <strong>This is only required when using a PNG with {@link CustomImage#value()}</strong> + */ + int height() default 0; + + /** + * The factory to create the image with. + * For the average user, this should not be used as it breaks out of the + * API-safe environment where things could change at any time, but required + * when creating anything advanced with the {@link ImageRenderer}. + * <p> + * The factory should contain a public, no-args constructor that will be + * invoked via reflection. + * + * @return the class of the factory + */ + Class<? extends CustomImageFactory<?>> factory() default EmptyCustomImageFactory.class; + + interface CustomImageFactory<T> { + CompletableFuture<ImageRenderer> createImage(T value, ConfigField<T> field, OptionAccess access); + } +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OverrideName.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomName.java index 8b73cec..aa235bb 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OverrideName.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/CustomName.java @@ -10,7 +10,7 @@ import java.lang.annotation.Target; */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) -public @interface OverrideName { +public @interface CustomName { /** * The translation key to use for the option's name. */ diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FormatTranslation.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FormatTranslation.java new file mode 100644 index 0000000..7cc4ded --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/FormatTranslation.java @@ -0,0 +1,25 @@ +package dev.isxander.yacl3.config.v2.api.autogen; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Allows you to specify a custom value formatter + * in the form of a translation key. + * <p> + * Without this annotation, the value will be formatted + * according to the option factory, implementation details + * for that should be found in the javadoc for the factory. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface FormatTranslation { + /** + * The translation key for the value formatter. + * One parameter is passed to this key: the option's value, + * using {@link Object#toString()}. + */ + String value() default ""; +} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OverrideImage.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OverrideImage.java deleted file mode 100644 index 5a33884..0000000 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/OverrideImage.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.isxander.yacl3.config.v2.api.autogen; - -import dev.isxander.yacl3.config.v2.api.ConfigField; -import dev.isxander.yacl3.config.v2.impl.autogen.EmptyCustomImageFactory; -import dev.isxander.yacl3.gui.image.ImageRenderer; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) -public @interface OverrideImage { - String value() default ""; - - int width() default 0; - int height() default 0; - - Class<? extends CustomImageFactory<?>> factory() default EmptyCustomImageFactory.class; - - interface CustomImageFactory<T> { - CompletableFuture<Optional<ImageRenderer>> createImage(T value, ConfigField<T> field, OptionAccess access); - } -} diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/SimpleOptionFactory.java b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/SimpleOptionFactory.java index 0ab6369..fc2957d 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/SimpleOptionFactory.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/api/autogen/SimpleOptionFactory.java @@ -30,8 +30,7 @@ public abstract class SimpleOptionFactory<A extends Annotation, T> implements Op .controller(opt -> { ControllerBuilder<T> builder = this.createController(annotation, field, optionAccess, opt); - Optional<OverrideFormatter> customFormatter = field.access().getAnnotation(OverrideFormatter.class); - AutoGenUtils.addCustomFormatterToController(builder, customFormatter, field.access()); + AutoGenUtils.addCustomFormatterToController(builder, field.access()); return builder; }) @@ -47,8 +46,8 @@ public abstract class SimpleOptionFactory<A extends Annotation, T> implements Op protected abstract ControllerBuilder<T> createController(A annotation, ConfigField<T> field, OptionAccess storage, Option<T> option); protected MutableComponent name(A annotation, ConfigField<T> field, OptionAccess storage) { - Optional<OverrideName> customName = field.access().getAnnotation(OverrideName.class); - return Component.translatable(customName.map(OverrideName::value).orElse(this.getTranslationKey(field, null))); + Optional<CustomName> customName = field.access().getAnnotation(CustomName.class); + return Component.translatable(customName.map(CustomName::value).orElse(this.getTranslationKey(field, null))); } protected OptionDescription.Builder description(T value, A annotation, ConfigField<T> field, OptionAccess storage) { @@ -65,14 +64,20 @@ public abstract class SimpleOptionFactory<A extends Annotation, T> implements Op } } - Optional<OverrideImage> imageOverrideOpt = field.access().getAnnotation(OverrideImage.class); + field.access().getAnnotation(CustomDescription.class).ifPresent(customDescription -> { + for (String line : customDescription.value()) { + builder.text(Component.translatable(line)); + } + }); + + Optional<CustomImage> imageOverrideOpt = field.access().getAnnotation(CustomImage.class); if (imageOverrideOpt.isPresent()) { - OverrideImage imageOverride = imageOverrideOpt.get(); + CustomImage imageOverride = imageOverrideOpt.get(); if (!imageOverride.factory().equals(EmptyCustomImageFactory.class)) { - OverrideImage.CustomImageFactory<T> imageFactory; + CustomImage.CustomImageFactory<T> imageFactory; try { - imageFactory = (OverrideImage.CustomImageFactory<T>) AutoGenUtils.constructNoArgsClass( + imageFactory = (CustomImage.CustomImageFactory<T>) AutoGenUtils.constructNoArgsClass( imageOverride.factory(), () -> "'%s': The factory class on @OverrideImage has no no-args constructor.".formatted(field.access().name()), () -> "'%s': Failed to instantiate factory class %s.".formatted(field.access().name(), imageOverride.factory().getName()) @@ -81,7 +86,7 @@ public abstract class SimpleOptionFactory<A extends Annotation, T> implements Op throw new YACLAutoGenException("'%s': The factory class on @OverrideImage is of incorrect type. Expected %s, got %s.".formatted(field.access().name(), field.access().type().getTypeName(), imageOverride.factory().getTypeParameters()[0].getName())); } - builder.customImage(imageFactory.createImage(value, field, storage)); + builder.customImage(imageFactory.createImage(value, field, storage).thenApply(Optional::of)); } else if (!imageOverride.value().isEmpty()) { String path = imageOverride.value(); ResourceLocation imageLocation = new ResourceLocation(field.parent().id().getNamespace(), path); diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/AutoGenUtils.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/AutoGenUtils.java index 6bde931..6f614c1 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/AutoGenUtils.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/AutoGenUtils.java @@ -4,35 +4,42 @@ 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.OverrideFormatter; +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.Consumer; -import java.util.function.Function; import java.util.function.Supplier; @ApiStatus.Internal public final class AutoGenUtils { - public static <T> void addCustomFormatterToController(ControllerBuilder<T> controller, Optional<OverrideFormatter> formatter, ReadOnlyFieldAccess<T> field) { + 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 -> { - if (controller instanceof ValueFormattableController<?,?>) { - ValueFormattableController<T, ?> typedBuilder; - try { - typedBuilder = (ValueFormattableController<T, ?>) controller; - } catch (ClassCastException e) { - throw new YACLAutoGenException("'%s': The formatter class on @CustomFormatter is of incorrect type. Expected %s, got %s.".formatted(field.name(), field.type().getTypeName(), formatterClass.value().getTypeParameters()[0].getName())); - } - - 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); - } - } else { - throw new YACLAutoGenException("Attempted to use @CustomFormatter on an option factory for field '%s' that uses a controller that does not support this.".formatted(field.name())); + 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) { diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleFieldImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleFieldImpl.java index d53d0f6..6445141 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleFieldImpl.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleFieldImpl.java @@ -22,6 +22,9 @@ public class DoubleFieldImpl extends SimpleOptionFactory<DoubleField, Double> { 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/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java index 5221012..e6dd05d 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/DoubleSliderImpl.java @@ -22,6 +22,9 @@ public class DoubleSliderImpl extends SimpleOptionFactory<DoubleSlider, Double> 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/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/EmptyCustomImageFactory.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/EmptyCustomImageFactory.java index 421de82..1500864 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/EmptyCustomImageFactory.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/EmptyCustomImageFactory.java @@ -2,16 +2,16 @@ package dev.isxander.yacl3.config.v2.impl.autogen; import dev.isxander.yacl3.config.v2.api.ConfigField; import dev.isxander.yacl3.config.v2.api.autogen.OptionAccess; -import dev.isxander.yacl3.config.v2.api.autogen.OverrideImage; +import dev.isxander.yacl3.config.v2.api.autogen.CustomImage; import dev.isxander.yacl3.gui.image.ImageRenderer; import java.util.Optional; import java.util.concurrent.CompletableFuture; -public class EmptyCustomImageFactory implements OverrideImage.CustomImageFactory<Object> { +public class EmptyCustomImageFactory implements CustomImage.CustomImageFactory<Object> { @Override - public CompletableFuture<Optional<ImageRenderer>> createImage(Object value, ConfigField<Object> field, OptionAccess access) { + public CompletableFuture<ImageRenderer> createImage(Object value, ConfigField<Object> field, OptionAccess access) { throw new IllegalStateException(); } } diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatFieldImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatFieldImpl.java index def5169..acdabd6 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatFieldImpl.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatFieldImpl.java @@ -22,6 +22,9 @@ public class FloatFieldImpl extends SimpleOptionFactory<FloatField, Float> { 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/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatSliderImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatSliderImpl.java index 6424bea..f22302f 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatSliderImpl.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/FloatSliderImpl.java @@ -22,6 +22,9 @@ public class FloatSliderImpl extends SimpleOptionFactory<FloatSlider, Float> { 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/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntFieldImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntFieldImpl.java index e5f4e71..a3b759a 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntFieldImpl.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntFieldImpl.java @@ -18,6 +18,9 @@ public class IntFieldImpl extends SimpleOptionFactory<IntField, Integer> { String key = getTranslationKey(field, "fmt." + v); if (Language.getInstance().has(key)) return Component.translatable(key); + key = getTranslationKey(field, "fmt"); + if (Language.getInstance().has(key)) + return Component.translatable(key, v); return Component.literal(Integer.toString(v)); }) .range(annotation.min(), annotation.max()); diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntSliderImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntSliderImpl.java index d650837..b570b44 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntSliderImpl.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/IntSliderImpl.java @@ -18,6 +18,9 @@ public class IntSliderImpl extends SimpleOptionFactory<IntSlider, Integer> { String key = getTranslationKey(field, "fmt." + v); if (Language.getInstance().has(key)) return Component.translatable(key); + key = getTranslationKey(field, "fmt"); + if (Language.getInstance().has(key)) + return Component.translatable(key, v); return Component.literal(Integer.toString(v)); }) .range(annotation.min(), annotation.max()) diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LongFieldImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LongFieldImpl.java index 41d20ca..5da7d20 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LongFieldImpl.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LongFieldImpl.java @@ -18,6 +18,9 @@ public class LongFieldImpl extends SimpleOptionFactory<LongField, Long> { String key = getTranslationKey(field, "fmt." + v); if (Language.getInstance().has(key)) return Component.translatable(key); + key = getTranslationKey(field, "fmt"); + if (Language.getInstance().has(key)) + return Component.translatable(key, v); return Component.literal(Long.toString(v)); }) .range(annotation.min(), annotation.max()); diff --git a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LongSliderImpl.java b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LongSliderImpl.java index 3c1f778..95c5254 100644 --- a/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LongSliderImpl.java +++ b/common/src/main/java/dev/isxander/yacl3/config/v2/impl/autogen/LongSliderImpl.java @@ -18,6 +18,9 @@ public class LongSliderImpl extends SimpleOptionFactory<LongSlider, Long> { String key = getTranslationKey(field, "fmt." + v); if (Language.getInstance().has(key)) return Component.translatable(key); + key = getTranslationKey(field, "fmt"); + if (Language.getInstance().has(key)) + return Component.translatable(key, v); return Component.literal(Long.toString(v)); }) .range(annotation.min(), annotation.max()) diff --git a/test-common/src/main/java/dev/isxander/yacl3/test/AutogenConfigTest.java b/test-common/src/main/java/dev/isxander/yacl3/test/AutogenConfigTest.java index 15ab195..828aa73 100644 --- a/test-common/src/main/java/dev/isxander/yacl3/test/AutogenConfigTest.java +++ b/test-common/src/main/java/dev/isxander/yacl3/test/AutogenConfigTest.java @@ -54,8 +54,8 @@ public class AutogenConfigTest { @AutoGen(category = "test", group = "master_test") @FloatSlider(min = 0.0f, max = 1f, step = 0.01f) - @OverrideFormatter(ValueFormatters.PercentFormatter.class) - @OverrideName("A cool percentage.") + @CustomFormat(ValueFormatters.PercentFormatter.class) + @CustomName("A cool percentage.") @SerialEntry public float testFloat = 0.1f; @AutoGen(category = "test", group = "master_test") |