package net.minecraft.client; import com.google.common.collect.ImmutableList; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.DoubleFunction; import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.IntSupplier; import java.util.function.Supplier; import java.util.function.ToDoubleFunction; import java.util.function.ToIntFunction; import java.util.stream.IntStream; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.Util; import net.minecraft.client.OptionInstance.CycleableValueSet.ValueSetter; import net.minecraft.client.OptionInstance.IntRangeBase.1; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.AbstractOptionSliderButton; import net.minecraft.client.gui.components.AbstractWidget; import net.minecraft.client.gui.components.CycleButton; import net.minecraft.client.gui.components.Tooltip; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import net.minecraft.util.Mth; import net.minecraft.util.OptionEnum; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @Environment(EnvType.CLIENT) public final class OptionInstance { private static final Logger LOGGER = LogUtils.getLogger(); public static final OptionInstance.Enum BOOLEAN_VALUES = new OptionInstance.Enum<>(ImmutableList.of(Boolean.TRUE, Boolean.FALSE), Codec.BOOL); public static final OptionInstance.CaptionBasedToString BOOLEAN_TO_STRING = (component, boolean_) -> boolean_ ? CommonComponents.OPTION_ON : CommonComponents.OPTION_OFF; private final OptionInstance.TooltipSupplier tooltip; final Function toString; private final OptionInstance.ValueSet values; private final Codec codec; private final T initialValue; private final Consumer onValueUpdate; final Component caption; T value; public static OptionInstance createBoolean(String key, boolean initialValue, Consumer onValueUpdate) { return createBoolean(key, noTooltip(), initialValue, onValueUpdate); } public static OptionInstance createBoolean(String key, boolean initialValue) { return createBoolean(key, noTooltip(), initialValue, boolean_ -> {}); } public static OptionInstance createBoolean(String caption, OptionInstance.TooltipSupplier tooltip, boolean initialValue) { return createBoolean(caption, tooltip, initialValue, boolean_ -> {}); } public static OptionInstance createBoolean( String caption, OptionInstance.TooltipSupplier tooltip, boolean initialValue, Consumer onValueUpdate ) { return createBoolean(caption, tooltip, BOOLEAN_TO_STRING, initialValue, onValueUpdate); } public static OptionInstance createBoolean( String caption, OptionInstance.TooltipSupplier tooltip, OptionInstance.CaptionBasedToString valueStringifier, boolean initialValue, Consumer onValueUpdate ) { return new OptionInstance<>(caption, tooltip, valueStringifier, BOOLEAN_VALUES, initialValue, onValueUpdate); } public OptionInstance( String caption, OptionInstance.TooltipSupplier tooltip, OptionInstance.CaptionBasedToString valueStringifier, OptionInstance.ValueSet values, T initialValue, Consumer onValueUpdate ) { this(caption, tooltip, valueStringifier, values, values.codec(), initialValue, onValueUpdate); } public OptionInstance( String caption, OptionInstance.TooltipSupplier tooltip, OptionInstance.CaptionBasedToString valueStringifier, OptionInstance.ValueSet values, Codec codec, T initialValue, Consumer onValueUpdate ) { this.caption = Component.translatable(caption); this.tooltip = tooltip; this.toString = object -> valueStringifier.toString(this.caption, (T)object); this.values = values; this.codec = codec; this.initialValue = initialValue; this.onValueUpdate = onValueUpdate; this.value = this.initialValue; } public static OptionInstance.TooltipSupplier noTooltip() { return object -> null; } public static OptionInstance.TooltipSupplier cachedConstantTooltip(Component message) { return object -> Tooltip.create(message); } public static OptionInstance.CaptionBasedToString forOptionEnum() { return (component, optionEnum) -> optionEnum.getCaption(); } public AbstractWidget createButton(Options options) { return this.createButton(options, 0, 0, 150); } public AbstractWidget createButton(Options options, int x, int y, int width) { return this.createButton(options, x, y, width, object -> {}); } public AbstractWidget createButton(Options options, int x, int y, int width, Consumer onValueChanged) { return (AbstractWidget)this.values.createButton(this.tooltip, options, x, y, width, onValueChanged).apply(this); } public T get() { return this.value; } public Codec codec() { return this.codec; } public String toString() { return this.caption.getString(); } public void set(T value) { T object = (T)this.values.validateValue(value).orElseGet(() -> { LOGGER.error("Illegal option value " + value + " for " + this.caption); return this.initialValue; }); if (!Minecraft.getInstance().isRunning()) { this.value = object; } else { if (!Objects.equals(this.value, object)) { this.value = object; this.onValueUpdate.accept(this.value); } } } public OptionInstance.ValueSet values() { return this.values; } @Environment(EnvType.CLIENT) public record AltEnum(List values, List altValues, BooleanSupplier altCondition, ValueSetter valueSetter, Codec codec) implements OptionInstance.CycleableValueSet { @Override public CycleButton.ValueListSupplier valueListSupplier() { return CycleButton.ValueListSupplier.create(this.altCondition, this.values, this.altValues); } @Override public Optional validateValue(T value) { return (this.altCondition.getAsBoolean() ? this.altValues : this.values).contains(value) ? Optional.of(value) : Optional.empty(); } } @Environment(EnvType.CLIENT) public interface CaptionBasedToString { Component toString(Component component, T object); } @Environment(EnvType.CLIENT) public record ClampingLazyMaxIntRange(int minInclusive, IntSupplier maxSupplier, int encodableMaxInclusive) implements OptionInstance.IntRangeBase, OptionInstance.SliderableOrCyclableValueSet { public Optional validateValue(Integer integer) { return Optional.of(Mth.clamp(integer, this.minInclusive(), this.maxInclusive())); } @Override public int maxInclusive() { return this.maxSupplier.getAsInt(); } @Override public Codec codec() { return Codec.INT .validate( integer -> { int i = this.encodableMaxInclusive + 1; return integer.compareTo(this.minInclusive) >= 0 && integer.compareTo(i) <= 0 ? DataResult.success(integer) : DataResult.error(() -> "Value " + integer + " outside of range [" + this.minInclusive + ":" + i + "]", integer); } ); } @Override public boolean createCycleButton() { return true; } @Override public CycleButton.ValueListSupplier valueListSupplier() { return CycleButton.ValueListSupplier.create(IntStream.range(this.minInclusive, this.maxInclusive() + 1).boxed().toList()); } } @Environment(EnvType.CLIENT) interface CycleableValueSet extends OptionInstance.ValueSet { CycleButton.ValueListSupplier valueListSupplier(); default ValueSetter valueSetter() { return OptionInstance::set; } @Override default Function, AbstractWidget> createButton( OptionInstance.TooltipSupplier tooltipSupplier, Options options, int x, int y, int width, Consumer onValueChanged ) { return optionInstance -> CycleButton.builder(optionInstance.toString) .withValues(this.valueListSupplier()) .withTooltip(tooltipSupplier) .withInitialValue(optionInstance.value) .create(x, y, width, 20, optionInstance.caption, (cycleButton, object) -> { this.valueSetter().set(optionInstance, object); options.save(); onValueChanged.accept(object); }); } } @Environment(EnvType.CLIENT) public record Enum(List values, Codec codec) implements OptionInstance.CycleableValueSet { @Override public Optional validateValue(T value) { return this.values.contains(value) ? Optional.of(value) : Optional.empty(); } @Override public CycleButton.ValueListSupplier valueListSupplier() { return CycleButton.ValueListSupplier.create(this.values); } } @Environment(EnvType.CLIENT) public record IntRange(int minInclusive, int maxInclusive, boolean applyValueImmediately) implements OptionInstance.IntRangeBase { public IntRange(int minInclusive, int maxInclusive) { this(minInclusive, maxInclusive, true); } public Optional validateValue(Integer integer) { return integer.compareTo(this.minInclusive()) >= 0 && integer.compareTo(this.maxInclusive()) <= 0 ? Optional.of(integer) : Optional.empty(); } @Override public Codec codec() { return Codec.intRange(this.minInclusive, this.maxInclusive + 1); } } @Environment(EnvType.CLIENT) interface IntRangeBase extends OptionInstance.SliderableValueSet { int minInclusive(); int maxInclusive(); default double toSliderValue(Integer integer) { if (integer == this.minInclusive()) { return 0.0; } else { return integer == this.maxInclusive() ? 1.0 : Mth.map(integer.intValue() + 0.5, (double)this.minInclusive(), this.maxInclusive() + 1.0, 0.0, 1.0); } } default Integer fromSliderValue(double d) { if (d >= 1.0) { d = 0.99999F; } return Mth.floor(Mth.map(d, 0.0, 1.0, (double)this.minInclusive(), this.maxInclusive() + 1.0)); } default OptionInstance.SliderableValueSet xmap(IntFunction to, ToIntFunction from) { return new 1(this, from, to); } } @Environment(EnvType.CLIENT) public record LazyEnum(Supplier> values, Function> validateValue, Codec codec) implements OptionInstance.CycleableValueSet { @Override public Optional validateValue(T value) { return (Optional)this.validateValue.apply(value); } @Override public CycleButton.ValueListSupplier valueListSupplier() { return CycleButton.ValueListSupplier.create((Collection)this.values.get()); } } @Environment(EnvType.CLIENT) public static final class OptionInstanceSliderButton extends AbstractOptionSliderButton { private final OptionInstance instance; private final OptionInstance.SliderableValueSet values; private final OptionInstance.TooltipSupplier tooltipSupplier; private final Consumer onValueChanged; @Nullable private Long delayedApplyAt; private final boolean applyValueImmediately; OptionInstanceSliderButton( Options options, int x, int y, int width, int height, OptionInstance instance, OptionInstance.SliderableValueSet values, OptionInstance.TooltipSupplier tooltipSupplier, Consumer onValueChanged, boolean applyValueImmediately ) { super(options, x, y, width, height, values.toSliderValue(instance.get())); this.instance = instance; this.values = values; this.tooltipSupplier = tooltipSupplier; this.onValueChanged = onValueChanged; this.applyValueImmediately = applyValueImmediately; this.updateMessage(); } @Override protected void updateMessage() { this.setMessage((Component)this.instance.toString.apply(this.values.fromSliderValue(this.value))); this.setTooltip(this.tooltipSupplier.apply(this.values.fromSliderValue(this.value))); } @Override protected void applyValue() { if (this.applyValueImmediately) { this.applyUnsavedValue(); } else { this.delayedApplyAt = Util.getMillis() + 600L; } } public void applyUnsavedValue() { N object = this.values.fromSliderValue(this.value); if (!Objects.equals(object, this.instance.get())) { this.instance.set(object); this.onValueChanged.accept(this.instance.get()); } } @Override public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { super.renderWidget(guiGraphics, mouseX, mouseY, partialTick); if (this.delayedApplyAt != null && Util.getMillis() >= this.delayedApplyAt) { this.delayedApplyAt = null; this.applyUnsavedValue(); } } } @Environment(EnvType.CLIENT) interface SliderableOrCyclableValueSet extends OptionInstance.CycleableValueSet, OptionInstance.SliderableValueSet { boolean createCycleButton(); @Override default Function, AbstractWidget> createButton( OptionInstance.TooltipSupplier tooltipSupplier, Options options, int x, int y, int width, Consumer onValueChanged ) { return this.createCycleButton() ? OptionInstance.CycleableValueSet.super.createButton(tooltipSupplier, options, x, y, width, onValueChanged) : OptionInstance.SliderableValueSet.super.createButton(tooltipSupplier, options, x, y, width, onValueChanged); } } @Environment(EnvType.CLIENT) interface SliderableValueSet extends OptionInstance.ValueSet { double toSliderValue(T value); T fromSliderValue(double value); default boolean applyValueImmediately() { return true; } @Override default Function, AbstractWidget> createButton( OptionInstance.TooltipSupplier tooltipSupplier, Options options, int x, int y, int width, Consumer onValueChanged ) { return optionInstance -> new OptionInstance.OptionInstanceSliderButton<>( options, x, y, width, 20, optionInstance, this, tooltipSupplier, onValueChanged, this.applyValueImmediately() ); } } @FunctionalInterface @Environment(EnvType.CLIENT) public interface TooltipSupplier { @Nullable Tooltip apply(T object); } @Environment(EnvType.CLIENT) public static enum UnitDouble implements OptionInstance.SliderableValueSet { INSTANCE; public Optional validateValue(Double double_) { return double_ >= 0.0 && double_ <= 1.0 ? Optional.of(double_) : Optional.empty(); } public double toSliderValue(Double double_) { return double_; } public Double fromSliderValue(double d) { return d; } public OptionInstance.SliderableValueSet xmap(DoubleFunction encoder, ToDoubleFunction decoder) { return new net.minecraft.client.OptionInstance.UnitDouble.1(this, decoder, encoder); } @Override public Codec codec() { return Codec.withAlternative(Codec.doubleRange(0.0, 1.0), Codec.BOOL, boolean_ -> boolean_ ? 1.0 : 0.0); } } @Environment(EnvType.CLIENT) interface ValueSet { Function, AbstractWidget> createButton( OptionInstance.TooltipSupplier tooltipSupplier, Options options, int x, int y, int width, Consumer onValueChanged ); Optional validateValue(T value); Codec codec(); } }