package net.minecraft.advancements.predicates; import com.google.common.collect.Range; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.codecs.RecordCodecBuilder; import io.netty.buffer.ByteBuf; import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; import net.minecraft.network.chat.Component; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.util.Mth; public interface MinMaxBounds> { SimpleCommandExceptionType ERROR_EMPTY = new SimpleCommandExceptionType(Component.translatable("argument.range.empty")); SimpleCommandExceptionType ERROR_SWAPPED = new SimpleCommandExceptionType(Component.translatable("argument.range.swapped")); MinMaxBounds.Bounds bounds(); default Optional min() { return this.bounds().min; } default Optional max() { return this.bounds().max; } default boolean isAny() { return this.bounds().isAny(); } static , B extends MinMaxBounds> Function> validateContainedInRange(final MinMaxBounds allowed) { Range allowedRange = allowed.bounds().asRange(); return target -> { Range selfAsRange = target.bounds().asRange(); return !allowedRange.encloses(selfAsRange) ? DataResult.error(() -> "Range must be within " + allowedRange + ", but was " + selfAsRange) : DataResult.success(target); }; } public record Bounds>(Optional min, Optional max) { public boolean isAny() { return this.min().isEmpty() && this.max().isEmpty(); } public DataResult> validateSwappedBoundsInCodec() { return this.areSwapped() ? DataResult.error(() -> "Swapped bounds in range: " + this.min() + " is higher than " + this.max()) : DataResult.success(this); } public boolean areSwapped() { return this.min.isPresent() && this.max.isPresent() && ((Comparable)((Number)this.min.get())).compareTo((Number)this.max.get()) > 0; } public Range asRange() { if (this.min.isPresent()) { return this.max.isPresent() ? Range.closed((T)this.min.get(), (T)this.max.get()) : Range.atLeast((T)this.min.get()); } else { return this.max.isPresent() ? Range.atMost((T)this.max.get()) : Range.all(); } } public Optional asPoint() { Optional min = this.min(); Optional max = this.max(); return min.equals(max) ? min : Optional.empty(); } public static > MinMaxBounds.Bounds any() { return new MinMaxBounds.Bounds<>(Optional.empty(), Optional.empty()); } public static > MinMaxBounds.Bounds exactly(final T value) { Optional wrapped = Optional.of(value); return new MinMaxBounds.Bounds<>(wrapped, wrapped); } public static > MinMaxBounds.Bounds between(final T min, final T max) { return new MinMaxBounds.Bounds<>(Optional.of(min), Optional.of(max)); } public static > MinMaxBounds.Bounds atLeast(final T value) { return new MinMaxBounds.Bounds<>(Optional.of(value), Optional.empty()); } public static > MinMaxBounds.Bounds atMost(final T value) { return new MinMaxBounds.Bounds<>(Optional.empty(), Optional.of(value)); } public > MinMaxBounds.Bounds map(final Function mapper) { return new MinMaxBounds.Bounds<>(this.min.map(mapper), this.max.map(mapper)); } public static > Codec> createCodec(final Codec numberCodec) { Codec> rangeCodec = RecordCodecBuilder.create( i -> i.group(numberCodec.optionalFieldOf("min").forGetter(MinMaxBounds.Bounds::min), numberCodec.optionalFieldOf("max").forGetter(MinMaxBounds.Bounds::max)) .apply(i, MinMaxBounds.Bounds::new) ); return Codec.either(rangeCodec, numberCodec).xmap(either -> either.map(v -> v, x$0 -> exactly((T)x$0)), bounds -> { Optional point = bounds.asPoint(); return point.isPresent() ? Either.right((Number)point.get()) : Either.left(bounds); }); } public static > StreamCodec> createStreamCodec( final StreamCodec numberCodec ) { return new StreamCodec>() { private static final int MIN_FLAG = 1; private static final int MAX_FLAG = 2; public MinMaxBounds.Bounds decode(final B input) { byte flags = input.readByte(); Optional min = (flags & 1) != 0 ? Optional.of(numberCodec.decode(input)) : Optional.empty(); Optional max = (flags & 2) != 0 ? Optional.of(numberCodec.decode(input)) : Optional.empty(); return new MinMaxBounds.Bounds<>(min, max); } public void encode(final B output, final MinMaxBounds.Bounds value) { Optional min = value.min(); Optional max = value.max(); output.writeByte((min.isPresent() ? 1 : 0) | (max.isPresent() ? 2 : 0)); min.ifPresent(v -> numberCodec.encode(output, (T)v)); max.ifPresent(v -> numberCodec.encode(output, (T)v)); } }; } public static > MinMaxBounds.Bounds fromReader( final StringReader reader, final Function converter, final Supplier parseExc ) throws CommandSyntaxException { if (!reader.canRead()) { throw MinMaxBounds.ERROR_EMPTY.createWithContext(reader); } else { int start = reader.getCursor(); try { Optional min = readNumber(reader, converter, parseExc); Optional max; if (reader.canRead(2) && reader.peek() == '.' && reader.peek(1) == '.') { reader.skip(); reader.skip(); max = readNumber(reader, converter, parseExc); } else { max = min; } if (min.isEmpty() && max.isEmpty()) { throw MinMaxBounds.ERROR_EMPTY.createWithContext(reader); } else { return new MinMaxBounds.Bounds<>(min, max); } } catch (CommandSyntaxException var6) { reader.setCursor(start); throw new CommandSyntaxException(var6.getType(), var6.getRawMessage(), var6.getInput(), start); } } } private static Optional readNumber( final StringReader reader, final Function converter, final Supplier parseExc ) throws CommandSyntaxException { int start = reader.getCursor(); while (reader.canRead() && isAllowedInputChar(reader)) { reader.skip(); } String number = reader.getString().substring(start, reader.getCursor()); if (number.isEmpty()) { return Optional.empty(); } else { try { return Optional.of((Number)converter.apply(number)); } catch (NumberFormatException var6) { throw ((DynamicCommandExceptionType)parseExc.get()).createWithContext(reader, number); } } } private static boolean isAllowedInputChar(final StringReader reader) { char c = reader.peek(); if ((c < '0' || c > '9') && c != '-') { return c != '.' ? false : !reader.canRead(2) || reader.peek(1) != '.'; } else { return true; } } } public record Doubles(MinMaxBounds.Bounds bounds, MinMaxBounds.Bounds boundsSqr) implements MinMaxBounds { public static final MinMaxBounds.Doubles ANY = new MinMaxBounds.Doubles(MinMaxBounds.Bounds.any()); public static final Codec CODEC = MinMaxBounds.Bounds.createCodec((Codec)Codec.DOUBLE) .validate(MinMaxBounds.Bounds::validateSwappedBoundsInCodec) .xmap(MinMaxBounds.Doubles::new, MinMaxBounds.Doubles::bounds); public static final StreamCodec STREAM_CODEC = MinMaxBounds.Bounds.createStreamCodec( (StreamCodec)ByteBufCodecs.DOUBLE ) .map(MinMaxBounds.Doubles::new, MinMaxBounds.Doubles::bounds); private Doubles(final MinMaxBounds.Bounds bounds) { this(bounds, bounds.map(Mth::square)); } public static MinMaxBounds.Doubles exactly(final double value) { return new MinMaxBounds.Doubles(MinMaxBounds.Bounds.exactly((T)value)); } public static MinMaxBounds.Doubles between(final double min, final double max) { return new MinMaxBounds.Doubles(MinMaxBounds.Bounds.between((T)min, (T)max)); } public static MinMaxBounds.Doubles atLeast(final double value) { return new MinMaxBounds.Doubles(MinMaxBounds.Bounds.atLeast((T)value)); } public static MinMaxBounds.Doubles atMost(final double value) { return new MinMaxBounds.Doubles(MinMaxBounds.Bounds.atMost((T)value)); } public boolean matches(final double value) { return this.bounds.min.isPresent() && this.bounds.min.get() > value ? false : this.bounds.max.isEmpty() || !((Double)this.bounds.max.get() < value); } public boolean matchesSqr(final double valueSqr) { return this.boundsSqr.min.isPresent() && this.boundsSqr.min.get() > valueSqr ? false : this.boundsSqr.max.isEmpty() || !((Double)this.boundsSqr.max.get() < valueSqr); } public static MinMaxBounds.Doubles fromReader(final StringReader reader) throws CommandSyntaxException { int start = reader.getCursor(); MinMaxBounds.Bounds bounds = MinMaxBounds.Bounds.fromReader( reader, Double::parseDouble, CommandSyntaxException.BUILT_IN_EXCEPTIONS::readerInvalidDouble ); if (bounds.areSwapped()) { reader.setCursor(start); throw ERROR_SWAPPED.createWithContext(reader); } else { return new MinMaxBounds.Doubles(bounds); } } } public record FloatDegrees(MinMaxBounds.Bounds bounds) implements MinMaxBounds { public static final MinMaxBounds.FloatDegrees ANY = new MinMaxBounds.FloatDegrees(MinMaxBounds.Bounds.any()); public static final Codec CODEC = MinMaxBounds.Bounds.createCodec((Codec)Codec.FLOAT) .xmap(MinMaxBounds.FloatDegrees::new, MinMaxBounds.FloatDegrees::bounds); public static final StreamCodec STREAM_CODEC = MinMaxBounds.Bounds.createStreamCodec( (StreamCodec)ByteBufCodecs.FLOAT ) .map(MinMaxBounds.FloatDegrees::new, MinMaxBounds.FloatDegrees::bounds); public static MinMaxBounds.FloatDegrees fromReader(final StringReader reader) throws CommandSyntaxException { MinMaxBounds.Bounds bounds = MinMaxBounds.Bounds.fromReader(reader, Float::parseFloat, CommandSyntaxException.BUILT_IN_EXCEPTIONS::readerInvalidFloat); return new MinMaxBounds.FloatDegrees(bounds); } } public record Ints(MinMaxBounds.Bounds bounds, MinMaxBounds.Bounds boundsSqr) implements MinMaxBounds { public static final MinMaxBounds.Ints ANY = new MinMaxBounds.Ints(MinMaxBounds.Bounds.any()); public static final Codec CODEC = MinMaxBounds.Bounds.createCodec((Codec)Codec.INT) .validate(MinMaxBounds.Bounds::validateSwappedBoundsInCodec) .xmap(MinMaxBounds.Ints::new, MinMaxBounds.Ints::bounds); public static final StreamCodec STREAM_CODEC = MinMaxBounds.Bounds.createStreamCodec((StreamCodec)ByteBufCodecs.INT) .map(MinMaxBounds.Ints::new, MinMaxBounds.Ints::bounds); private Ints(final MinMaxBounds.Bounds bounds) { this(bounds, bounds.map(i -> Mth.square(i.longValue()))); } public static MinMaxBounds.Ints exactly(final int value) { return new MinMaxBounds.Ints(MinMaxBounds.Bounds.exactly((T)value)); } public static MinMaxBounds.Ints between(final int min, final int max) { return new MinMaxBounds.Ints(MinMaxBounds.Bounds.between((T)min, (T)max)); } public static MinMaxBounds.Ints atLeast(final int value) { return new MinMaxBounds.Ints(MinMaxBounds.Bounds.atLeast((T)value)); } public static MinMaxBounds.Ints atMost(final int value) { return new MinMaxBounds.Ints(MinMaxBounds.Bounds.atMost((T)value)); } public boolean matches(final int value) { return this.bounds.min.isPresent() && this.bounds.min.get() > value ? false : this.bounds.max.isEmpty() || (Integer)this.bounds.max.get() >= value; } public boolean matchesSqr(final long valueSqr) { return this.boundsSqr.min.isPresent() && this.boundsSqr.min.get() > valueSqr ? false : this.boundsSqr.max.isEmpty() || (Long)this.boundsSqr.max.get() >= valueSqr; } public static MinMaxBounds.Ints fromReader(final StringReader reader) throws CommandSyntaxException { int start = reader.getCursor(); MinMaxBounds.Bounds bounds = MinMaxBounds.Bounds.fromReader(reader, Integer::parseInt, CommandSyntaxException.BUILT_IN_EXCEPTIONS::readerInvalidInt); if (bounds.areSwapped()) { reader.setCursor(start); throw ERROR_SWAPPED.createWithContext(reader); } else { return new MinMaxBounds.Ints(bounds); } } } }