658 lines
27 KiB
Java
658 lines
27 KiB
Java
package net.minecraft.util;
|
|
|
|
import com.google.common.collect.BiMap;
|
|
import com.google.common.collect.HashBiMap;
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.google.common.collect.ImmutableMap.Builder;
|
|
import com.google.common.primitives.UnsignedBytes;
|
|
import com.google.gson.JsonElement;
|
|
import com.mojang.authlib.GameProfile;
|
|
import com.mojang.authlib.properties.Property;
|
|
import com.mojang.authlib.properties.PropertyMap;
|
|
import com.mojang.datafixers.util.Either;
|
|
import com.mojang.datafixers.util.Pair;
|
|
import com.mojang.serialization.Codec;
|
|
import com.mojang.serialization.DataResult;
|
|
import com.mojang.serialization.Decoder;
|
|
import com.mojang.serialization.Dynamic;
|
|
import com.mojang.serialization.DynamicOps;
|
|
import com.mojang.serialization.JavaOps;
|
|
import com.mojang.serialization.JsonOps;
|
|
import com.mojang.serialization.Lifecycle;
|
|
import com.mojang.serialization.MapCodec;
|
|
import com.mojang.serialization.MapLike;
|
|
import com.mojang.serialization.RecordBuilder;
|
|
import com.mojang.serialization.Codec.ResultFunction;
|
|
import com.mojang.serialization.DataResult.Error;
|
|
import com.mojang.serialization.codecs.BaseMapCodec;
|
|
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
|
import it.unimi.dsi.fastutil.floats.FloatArrayList;
|
|
import it.unimi.dsi.fastutil.floats.FloatList;
|
|
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
|
|
import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
|
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
|
import java.net.URI;
|
|
import java.net.URISyntaxException;
|
|
import java.time.Instant;
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.time.temporal.TemporalAccessor;
|
|
import java.util.Arrays;
|
|
import java.util.Base64;
|
|
import java.util.BitSet;
|
|
import java.util.Collection;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.OptionalLong;
|
|
import java.util.function.BiFunction;
|
|
import java.util.function.Function;
|
|
import java.util.function.IntFunction;
|
|
import java.util.function.ToIntFunction;
|
|
import java.util.regex.Pattern;
|
|
import java.util.regex.PatternSyntaxException;
|
|
import java.util.stream.Stream;
|
|
import net.minecraft.Util;
|
|
import net.minecraft.core.HolderSet;
|
|
import net.minecraft.core.UUIDUtil;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import org.apache.commons.lang3.StringEscapeUtils;
|
|
import org.apache.commons.lang3.mutable.MutableObject;
|
|
import org.joml.AxisAngle4f;
|
|
import org.joml.Matrix4f;
|
|
import org.joml.Matrix4fc;
|
|
import org.joml.Quaternionf;
|
|
import org.joml.Vector3f;
|
|
import org.joml.Vector4f;
|
|
|
|
public class ExtraCodecs {
|
|
public static final Codec<JsonElement> JSON = converter(JsonOps.INSTANCE);
|
|
public static final Codec<Object> JAVA = converter(JavaOps.INSTANCE);
|
|
public static final Codec<Vector3f> VECTOR3F = Codec.FLOAT
|
|
.listOf()
|
|
.comapFlatMap(
|
|
list -> Util.fixedSize(list, 3).map(listx -> new Vector3f((Float)listx.get(0), (Float)listx.get(1), (Float)listx.get(2))),
|
|
vector3f -> List.of(vector3f.x(), vector3f.y(), vector3f.z())
|
|
);
|
|
public static final Codec<Vector4f> VECTOR4F = Codec.FLOAT
|
|
.listOf()
|
|
.comapFlatMap(
|
|
list -> Util.fixedSize(list, 4).map(listx -> new Vector4f((Float)listx.get(0), (Float)listx.get(1), (Float)listx.get(2), (Float)listx.get(3))),
|
|
vector4f -> List.of(vector4f.x(), vector4f.y(), vector4f.z(), vector4f.w())
|
|
);
|
|
public static final Codec<Quaternionf> QUATERNIONF_COMPONENTS = Codec.FLOAT
|
|
.listOf()
|
|
.comapFlatMap(
|
|
list -> Util.fixedSize(list, 4)
|
|
.map(listx -> new Quaternionf((Float)listx.get(0), (Float)listx.get(1), (Float)listx.get(2), (Float)listx.get(3)).normalize()),
|
|
quaternionf -> List.of(quaternionf.x, quaternionf.y, quaternionf.z, quaternionf.w)
|
|
);
|
|
public static final Codec<AxisAngle4f> AXISANGLE4F = RecordCodecBuilder.create(
|
|
instance -> instance.group(
|
|
Codec.FLOAT.fieldOf("angle").forGetter(axisAngle4f -> axisAngle4f.angle),
|
|
VECTOR3F.fieldOf("axis").forGetter(axisAngle4f -> new Vector3f(axisAngle4f.x, axisAngle4f.y, axisAngle4f.z))
|
|
)
|
|
.apply(instance, AxisAngle4f::new)
|
|
);
|
|
public static final Codec<Quaternionf> QUATERNIONF = Codec.withAlternative(QUATERNIONF_COMPONENTS, AXISANGLE4F.xmap(Quaternionf::new, AxisAngle4f::new));
|
|
public static final Codec<Matrix4fc> MATRIX4F = Codec.FLOAT.listOf().comapFlatMap(list -> Util.fixedSize(list, 16).map(listx -> {
|
|
Matrix4f matrix4f = new Matrix4f();
|
|
|
|
for (int i = 0; i < listx.size(); i++) {
|
|
matrix4f.setRowColumn(i >> 2, i & 3, (Float)listx.get(i));
|
|
}
|
|
|
|
return matrix4f.determineProperties();
|
|
}), matrix4fc -> {
|
|
FloatList floatList = new FloatArrayList(16);
|
|
|
|
for (int i = 0; i < 16; i++) {
|
|
floatList.add(matrix4fc.getRowColumn(i >> 2, i & 3));
|
|
}
|
|
|
|
return floatList;
|
|
});
|
|
public static final Codec<Integer> RGB_COLOR_CODEC = Codec.withAlternative(
|
|
Codec.INT, VECTOR3F, vector3f -> ARGB.colorFromFloat(1.0F, vector3f.x(), vector3f.y(), vector3f.z())
|
|
);
|
|
public static final Codec<Integer> ARGB_COLOR_CODEC = Codec.withAlternative(
|
|
Codec.INT, VECTOR4F, vector4f -> ARGB.colorFromFloat(vector4f.w(), vector4f.x(), vector4f.y(), vector4f.z())
|
|
);
|
|
public static final Codec<Integer> UNSIGNED_BYTE = Codec.BYTE
|
|
.flatComapMap(
|
|
UnsignedBytes::toInt,
|
|
integer -> integer > 255 ? DataResult.error(() -> "Unsigned byte was too large: " + integer + " > 255") : DataResult.success(integer.byteValue())
|
|
);
|
|
public static final Codec<Integer> NON_NEGATIVE_INT = intRangeWithMessage(0, Integer.MAX_VALUE, integer -> "Value must be non-negative: " + integer);
|
|
public static final Codec<Integer> POSITIVE_INT = intRangeWithMessage(1, Integer.MAX_VALUE, integer -> "Value must be positive: " + integer);
|
|
public static final Codec<Float> NON_NEGATIVE_FLOAT = floatRangeMinInclusiveWithMessage(
|
|
0.0F, Float.MAX_VALUE, float_ -> "Value must be non-negative: " + float_
|
|
);
|
|
public static final Codec<Float> POSITIVE_FLOAT = floatRangeMinExclusiveWithMessage(0.0F, Float.MAX_VALUE, float_ -> "Value must be positive: " + float_);
|
|
public static final Codec<Pattern> PATTERN = Codec.STRING.comapFlatMap(string -> {
|
|
try {
|
|
return DataResult.success(Pattern.compile(string));
|
|
} catch (PatternSyntaxException var2) {
|
|
return DataResult.error(() -> "Invalid regex pattern '" + string + "': " + var2.getMessage());
|
|
}
|
|
}, Pattern::pattern);
|
|
public static final Codec<Instant> INSTANT_ISO8601 = temporalCodec(DateTimeFormatter.ISO_INSTANT).xmap(Instant::from, Function.identity());
|
|
public static final Codec<byte[]> BASE64_STRING = Codec.STRING.comapFlatMap(string -> {
|
|
try {
|
|
return DataResult.success(Base64.getDecoder().decode(string));
|
|
} catch (IllegalArgumentException var2) {
|
|
return DataResult.error(() -> "Malformed base64 string");
|
|
}
|
|
}, bs -> Base64.getEncoder().encodeToString(bs));
|
|
public static final Codec<String> ESCAPED_STRING = Codec.STRING
|
|
.comapFlatMap(string -> DataResult.success(StringEscapeUtils.unescapeJava(string)), StringEscapeUtils::escapeJava);
|
|
public static final Codec<ExtraCodecs.TagOrElementLocation> TAG_OR_ELEMENT_ID = Codec.STRING
|
|
.comapFlatMap(
|
|
string -> string.startsWith("#")
|
|
? ResourceLocation.read(string.substring(1)).map(resourceLocation -> new ExtraCodecs.TagOrElementLocation(resourceLocation, true))
|
|
: ResourceLocation.read(string).map(resourceLocation -> new ExtraCodecs.TagOrElementLocation(resourceLocation, false)),
|
|
ExtraCodecs.TagOrElementLocation::decoratedId
|
|
);
|
|
public static final Function<Optional<Long>, OptionalLong> toOptionalLong = optional -> (OptionalLong)optional.map(OptionalLong::of)
|
|
.orElseGet(OptionalLong::empty);
|
|
public static final Function<OptionalLong, Optional<Long>> fromOptionalLong = optionalLong -> optionalLong.isPresent()
|
|
? Optional.of(optionalLong.getAsLong())
|
|
: Optional.empty();
|
|
public static final Codec<BitSet> BIT_SET = Codec.LONG_STREAM
|
|
.xmap(longStream -> BitSet.valueOf(longStream.toArray()), bitSet -> Arrays.stream(bitSet.toLongArray()));
|
|
private static final Codec<Property> PROPERTY = RecordCodecBuilder.create(
|
|
instance -> instance.group(
|
|
Codec.STRING.fieldOf("name").forGetter(Property::name),
|
|
Codec.STRING.fieldOf("value").forGetter(Property::value),
|
|
Codec.STRING.lenientOptionalFieldOf("signature").forGetter(property -> Optional.ofNullable(property.signature()))
|
|
)
|
|
.apply(instance, (string, string2, optional) -> new Property(string, string2, (String)optional.orElse(null)))
|
|
);
|
|
public static final Codec<PropertyMap> PROPERTY_MAP = Codec.either(Codec.unboundedMap(Codec.STRING, Codec.STRING.listOf()), PROPERTY.listOf())
|
|
.xmap(either -> {
|
|
PropertyMap propertyMap = new PropertyMap();
|
|
either.ifLeft(map -> map.forEach((string, list) -> {
|
|
for (String string2 : list) {
|
|
propertyMap.put(string, new Property(string, string2));
|
|
}
|
|
})).ifRight(list -> {
|
|
for (Property property : list) {
|
|
propertyMap.put(property.name(), property);
|
|
}
|
|
});
|
|
return propertyMap;
|
|
}, propertyMap -> Either.right(propertyMap.values().stream().toList()));
|
|
public static final Codec<String> PLAYER_NAME = Codec.string(0, 16)
|
|
.validate(
|
|
string -> StringUtil.isValidPlayerName(string)
|
|
? DataResult.success(string)
|
|
: DataResult.error(() -> "Player name contained disallowed characters: '" + string + "'")
|
|
);
|
|
private static final MapCodec<GameProfile> GAME_PROFILE_WITHOUT_PROPERTIES = RecordCodecBuilder.mapCodec(
|
|
instance -> instance.group(UUIDUtil.AUTHLIB_CODEC.fieldOf("id").forGetter(GameProfile::getId), PLAYER_NAME.fieldOf("name").forGetter(GameProfile::getName))
|
|
.apply(instance, GameProfile::new)
|
|
);
|
|
public static final Codec<GameProfile> GAME_PROFILE = RecordCodecBuilder.create(
|
|
instance -> instance.group(
|
|
GAME_PROFILE_WITHOUT_PROPERTIES.forGetter(Function.identity()),
|
|
PROPERTY_MAP.lenientOptionalFieldOf("properties", new PropertyMap()).forGetter(GameProfile::getProperties)
|
|
)
|
|
.apply(instance, (gameProfile, propertyMap) -> {
|
|
propertyMap.forEach((string, property) -> gameProfile.getProperties().put(string, property));
|
|
return gameProfile;
|
|
})
|
|
);
|
|
public static final Codec<String> NON_EMPTY_STRING = Codec.STRING
|
|
.validate(string -> string.isEmpty() ? DataResult.error(() -> "Expected non-empty string") : DataResult.success(string));
|
|
public static final Codec<Integer> CODEPOINT = Codec.STRING.comapFlatMap(string -> {
|
|
int[] is = string.codePoints().toArray();
|
|
return is.length != 1 ? DataResult.error(() -> "Expected one codepoint, got: " + string) : DataResult.success(is[0]);
|
|
}, Character::toString);
|
|
public static final Codec<String> RESOURCE_PATH_CODEC = Codec.STRING
|
|
.validate(
|
|
string -> !ResourceLocation.isValidPath(string)
|
|
? DataResult.error(() -> "Invalid string to use as a resource path element: " + string)
|
|
: DataResult.success(string)
|
|
);
|
|
public static final Codec<URI> UNTRUSTED_URI = Codec.STRING.comapFlatMap(string -> {
|
|
try {
|
|
return DataResult.success(Util.parseAndValidateUntrustedUri(string));
|
|
} catch (URISyntaxException var2) {
|
|
return DataResult.error(var2::getMessage);
|
|
}
|
|
}, URI::toString);
|
|
public static final Codec<String> CHAT_STRING = Codec.STRING.validate(string -> {
|
|
for (int i = 0; i < string.length(); i++) {
|
|
char c = string.charAt(i);
|
|
if (!StringUtil.isAllowedChatCharacter(c)) {
|
|
return DataResult.error(() -> "Disallowed chat character: '" + c + "'");
|
|
}
|
|
}
|
|
|
|
return DataResult.success(string);
|
|
});
|
|
|
|
public static <T> Codec<T> converter(DynamicOps<T> ops) {
|
|
return Codec.PASSTHROUGH.xmap(dynamic -> dynamic.convert(ops).getValue(), object -> new Dynamic<>(ops, (T)object));
|
|
}
|
|
|
|
public static <P, I> Codec<I> intervalCodec(
|
|
Codec<P> codec, String minFieldName, String maxFieldName, BiFunction<P, P, DataResult<I>> factory, Function<I, P> minGetter, Function<I, P> maxGetter
|
|
) {
|
|
Codec<I> codec2 = Codec.list(codec).comapFlatMap(list -> Util.fixedSize(list, 2).flatMap(listx -> {
|
|
P object = (P)listx.get(0);
|
|
P object2 = (P)listx.get(1);
|
|
return (DataResult)factory.apply(object, object2);
|
|
}), object -> ImmutableList.of(minGetter.apply(object), maxGetter.apply(object)));
|
|
Codec<I> codec3 = RecordCodecBuilder.create(
|
|
instance -> instance.group(codec.fieldOf(minFieldName).forGetter(Pair::getFirst), codec.fieldOf(maxFieldName).forGetter(Pair::getSecond))
|
|
.apply(instance, Pair::of)
|
|
)
|
|
.comapFlatMap(pair -> (DataResult)factory.apply(pair.getFirst(), pair.getSecond()), object -> Pair.of(minGetter.apply(object), maxGetter.apply(object)));
|
|
Codec<I> codec4 = Codec.withAlternative(codec2, codec3);
|
|
return Codec.either(codec, codec4).comapFlatMap(either -> either.map(object -> (DataResult)factory.apply(object, object), DataResult::success), object -> {
|
|
P object2 = (P)minGetter.apply(object);
|
|
P object3 = (P)maxGetter.apply(object);
|
|
return Objects.equals(object2, object3) ? Either.left(object2) : Either.right(object);
|
|
});
|
|
}
|
|
|
|
public static <A> ResultFunction<A> orElsePartial(A value) {
|
|
return new ResultFunction<A>() {
|
|
@Override
|
|
public <T> DataResult<Pair<A, T>> apply(DynamicOps<T> dynamicOps, T object, DataResult<Pair<A, T>> dataResult) {
|
|
MutableObject<String> mutableObject = new MutableObject<>();
|
|
Optional<Pair<A, T>> optional = dataResult.resultOrPartial(mutableObject::setValue);
|
|
return optional.isPresent() ? dataResult : DataResult.error(() -> "(" + mutableObject.getValue() + " -> using default)", Pair.of(value, object));
|
|
}
|
|
|
|
@Override
|
|
public <T> DataResult<T> coApply(DynamicOps<T> dynamicOps, A object, DataResult<T> dataResult) {
|
|
return dataResult;
|
|
}
|
|
|
|
public String toString() {
|
|
return "OrElsePartial[" + value + "]";
|
|
}
|
|
};
|
|
}
|
|
|
|
public static <E> Codec<E> idResolverCodec(ToIntFunction<E> encoder, IntFunction<E> decoder, int notFoundValue) {
|
|
return Codec.INT
|
|
.flatXmap(
|
|
integer -> (DataResult)Optional.ofNullable(decoder.apply(integer))
|
|
.map(DataResult::success)
|
|
.orElseGet(() -> DataResult.error(() -> "Unknown element id: " + integer)),
|
|
object -> {
|
|
int j = encoder.applyAsInt(object);
|
|
return j == notFoundValue ? DataResult.error(() -> "Element with unknown id: " + object) : DataResult.success(j);
|
|
}
|
|
);
|
|
}
|
|
|
|
public static <I, E> Codec<E> idResolverCodec(Codec<I> idCodec, Function<I, E> idToValue, Function<E, I> valueToId) {
|
|
return idCodec.flatXmap(object -> {
|
|
E object2 = (E)idToValue.apply(object);
|
|
return object2 == null ? DataResult.error(() -> "Unknown element id: " + object) : DataResult.success(object2);
|
|
}, object -> {
|
|
I object2 = (I)valueToId.apply(object);
|
|
return object2 == null ? DataResult.error(() -> "Element with unknown id: " + object) : DataResult.success(object2);
|
|
});
|
|
}
|
|
|
|
public static <E> Codec<E> orCompressed(Codec<E> first, Codec<E> second) {
|
|
return new Codec<E>() {
|
|
@Override
|
|
public <T> DataResult<T> encode(E object, DynamicOps<T> dynamicOps, T object2) {
|
|
return dynamicOps.compressMaps() ? second.encode(object, dynamicOps, object2) : first.encode(object, dynamicOps, object2);
|
|
}
|
|
|
|
@Override
|
|
public <T> DataResult<Pair<E, T>> decode(DynamicOps<T> dynamicOps, T object) {
|
|
return dynamicOps.compressMaps() ? second.decode(dynamicOps, object) : first.decode(dynamicOps, object);
|
|
}
|
|
|
|
public String toString() {
|
|
return first + " orCompressed " + second;
|
|
}
|
|
};
|
|
}
|
|
|
|
public static <E> MapCodec<E> orCompressed(MapCodec<E> first, MapCodec<E> second) {
|
|
return new MapCodec<E>() {
|
|
@Override
|
|
public <T> RecordBuilder<T> encode(E object, DynamicOps<T> dynamicOps, RecordBuilder<T> recordBuilder) {
|
|
return dynamicOps.compressMaps() ? second.encode(object, dynamicOps, recordBuilder) : first.encode(object, dynamicOps, recordBuilder);
|
|
}
|
|
|
|
@Override
|
|
public <T> DataResult<E> decode(DynamicOps<T> dynamicOps, MapLike<T> mapLike) {
|
|
return dynamicOps.compressMaps() ? second.decode(dynamicOps, mapLike) : first.decode(dynamicOps, mapLike);
|
|
}
|
|
|
|
@Override
|
|
public <T> Stream<T> keys(DynamicOps<T> dynamicOps) {
|
|
return second.keys(dynamicOps);
|
|
}
|
|
|
|
public String toString() {
|
|
return first + " orCompressed " + second;
|
|
}
|
|
};
|
|
}
|
|
|
|
public static <E> Codec<E> overrideLifecycle(Codec<E> codec, Function<E, Lifecycle> applyLifecycle, Function<E, Lifecycle> coApplyLifecycle) {
|
|
return codec.mapResult(
|
|
new ResultFunction<E>() {
|
|
@Override
|
|
public <T> DataResult<Pair<E, T>> apply(DynamicOps<T> dynamicOps, T object, DataResult<Pair<E, T>> dataResult) {
|
|
return (DataResult<Pair<E, T>>)dataResult.result()
|
|
.map(pair -> dataResult.setLifecycle((Lifecycle)applyLifecycle.apply(pair.getFirst())))
|
|
.orElse(dataResult);
|
|
}
|
|
|
|
@Override
|
|
public <T> DataResult<T> coApply(DynamicOps<T> dynamicOps, E object, DataResult<T> dataResult) {
|
|
return dataResult.setLifecycle((Lifecycle)coApplyLifecycle.apply(object));
|
|
}
|
|
|
|
public String toString() {
|
|
return "WithLifecycle[" + applyLifecycle + " " + coApplyLifecycle + "]";
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
public static <E> Codec<E> overrideLifecycle(Codec<E> codec, Function<E, Lifecycle> lifecycleGetter) {
|
|
return overrideLifecycle(codec, lifecycleGetter, lifecycleGetter);
|
|
}
|
|
|
|
public static <K, V> ExtraCodecs.StrictUnboundedMapCodec<K, V> strictUnboundedMap(Codec<K> key, Codec<V> value) {
|
|
return new ExtraCodecs.StrictUnboundedMapCodec<>(key, value);
|
|
}
|
|
|
|
public static <E> Codec<List<E>> compactListCodec(Codec<E> elementCodec) {
|
|
return compactListCodec(elementCodec, elementCodec.listOf());
|
|
}
|
|
|
|
public static <E> Codec<List<E>> compactListCodec(Codec<E> elementCodec, Codec<List<E>> listCodec) {
|
|
return Codec.either(listCodec, elementCodec)
|
|
.xmap(either -> either.map(list -> list, List::of), list -> list.size() == 1 ? Either.right(list.getFirst()) : Either.left(list));
|
|
}
|
|
|
|
private static Codec<Integer> intRangeWithMessage(int min, int max, Function<Integer, String> errorMessage) {
|
|
return Codec.INT
|
|
.validate(
|
|
integer -> integer.compareTo(min) >= 0 && integer.compareTo(max) <= 0
|
|
? DataResult.success(integer)
|
|
: DataResult.error(() -> (String)errorMessage.apply(integer))
|
|
);
|
|
}
|
|
|
|
public static Codec<Integer> intRange(int min, int max) {
|
|
return intRangeWithMessage(min, max, integer -> "Value must be within range [" + min + ";" + max + "]: " + integer);
|
|
}
|
|
|
|
private static Codec<Float> floatRangeMinInclusiveWithMessage(float min, float max, Function<Float, String> errorMessage) {
|
|
return Codec.FLOAT
|
|
.validate(
|
|
float_ -> float_.compareTo(min) >= 0 && float_.compareTo(max) <= 0
|
|
? DataResult.success(float_)
|
|
: DataResult.error(() -> (String)errorMessage.apply(float_))
|
|
);
|
|
}
|
|
|
|
private static Codec<Float> floatRangeMinExclusiveWithMessage(float min, float max, Function<Float, String> errorMessage) {
|
|
return Codec.FLOAT
|
|
.validate(
|
|
float_ -> float_.compareTo(min) > 0 && float_.compareTo(max) <= 0 ? DataResult.success(float_) : DataResult.error(() -> (String)errorMessage.apply(float_))
|
|
);
|
|
}
|
|
|
|
public static Codec<Float> floatRange(float min, float max) {
|
|
return floatRangeMinInclusiveWithMessage(min, max, float_ -> "Value must be within range [" + min + ";" + max + "]: " + float_);
|
|
}
|
|
|
|
public static <T> Codec<List<T>> nonEmptyList(Codec<List<T>> codec) {
|
|
return codec.validate(list -> list.isEmpty() ? DataResult.error(() -> "List must have contents") : DataResult.success(list));
|
|
}
|
|
|
|
public static <T> Codec<HolderSet<T>> nonEmptyHolderSet(Codec<HolderSet<T>> codec) {
|
|
return codec.validate(
|
|
holderSet -> holderSet.unwrap().right().filter(List::isEmpty).isPresent()
|
|
? DataResult.error(() -> "List must have contents")
|
|
: DataResult.success(holderSet)
|
|
);
|
|
}
|
|
|
|
public static <M extends Map<?, ?>> Codec<M> nonEmptyMap(Codec<M> mapCodec) {
|
|
return mapCodec.validate(map -> map.isEmpty() ? DataResult.error(() -> "Map must have contents") : DataResult.success(map));
|
|
}
|
|
|
|
public static <E> MapCodec<E> retrieveContext(Function<DynamicOps<?>, DataResult<E>> retriever) {
|
|
class ContextRetrievalCodec extends MapCodec<E> {
|
|
@Override
|
|
public <T> RecordBuilder<T> encode(E object, DynamicOps<T> dynamicOps, RecordBuilder<T> recordBuilder) {
|
|
return recordBuilder;
|
|
}
|
|
|
|
@Override
|
|
public <T> DataResult<E> decode(DynamicOps<T> dynamicOps, MapLike<T> mapLike) {
|
|
return (DataResult<E>)retriever.apply(dynamicOps);
|
|
}
|
|
|
|
public String toString() {
|
|
return "ContextRetrievalCodec[" + retriever + "]";
|
|
}
|
|
|
|
@Override
|
|
public <T> Stream<T> keys(DynamicOps<T> dynamicOps) {
|
|
return Stream.empty();
|
|
}
|
|
}
|
|
|
|
return new ContextRetrievalCodec();
|
|
}
|
|
|
|
public static <E, L extends Collection<E>, T> Function<L, DataResult<L>> ensureHomogenous(Function<E, T> typeGetter) {
|
|
return collection -> {
|
|
Iterator<E> iterator = collection.iterator();
|
|
if (iterator.hasNext()) {
|
|
T object = (T)typeGetter.apply(iterator.next());
|
|
|
|
while (iterator.hasNext()) {
|
|
E object2 = (E)iterator.next();
|
|
T object3 = (T)typeGetter.apply(object2);
|
|
if (object3 != object) {
|
|
return DataResult.error(() -> "Mixed type list: element " + object2 + " had type " + object3 + ", but list is of type " + object);
|
|
}
|
|
}
|
|
}
|
|
|
|
return DataResult.success(collection, Lifecycle.stable());
|
|
};
|
|
}
|
|
|
|
public static <A> Codec<A> catchDecoderException(Codec<A> codec) {
|
|
return Codec.of(codec, new Decoder<A>() {
|
|
@Override
|
|
public <T> DataResult<Pair<A, T>> decode(DynamicOps<T> dynamicOps, T object) {
|
|
try {
|
|
return codec.decode(dynamicOps, object);
|
|
} catch (Exception var4) {
|
|
return DataResult.error(() -> "Caught exception decoding " + object + ": " + var4.getMessage());
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
public static Codec<TemporalAccessor> temporalCodec(DateTimeFormatter dateTimeFormatter) {
|
|
return Codec.STRING.comapFlatMap(string -> {
|
|
try {
|
|
return DataResult.success(dateTimeFormatter.parse(string));
|
|
} catch (Exception var3) {
|
|
return DataResult.error(var3::getMessage);
|
|
}
|
|
}, dateTimeFormatter::format);
|
|
}
|
|
|
|
public static MapCodec<OptionalLong> asOptionalLong(MapCodec<Optional<Long>> codec) {
|
|
return codec.xmap(toOptionalLong, fromOptionalLong);
|
|
}
|
|
|
|
public static <K, V> Codec<Map<K, V>> sizeLimitedMap(Codec<Map<K, V>> mapCodec, int maxSize) {
|
|
return mapCodec.validate(
|
|
map -> map.size() > maxSize ? DataResult.error(() -> "Map is too long: " + map.size() + ", expected range [0-" + maxSize + "]") : DataResult.success(map)
|
|
);
|
|
}
|
|
|
|
public static <T> Codec<Object2BooleanMap<T>> object2BooleanMap(Codec<T> codec) {
|
|
return Codec.unboundedMap(codec, Codec.BOOL).xmap(Object2BooleanOpenHashMap::new, Object2ObjectOpenHashMap::new);
|
|
}
|
|
|
|
@Deprecated
|
|
public static <K, V> MapCodec<V> dispatchOptionalValue(
|
|
String key1, String key2, Codec<K> codec, Function<? super V, ? extends K> keyGetter, Function<? super K, ? extends Codec<? extends V>> codecGetter
|
|
) {
|
|
return new MapCodec<V>() {
|
|
@Override
|
|
public <T> Stream<T> keys(DynamicOps<T> dynamicOps) {
|
|
return Stream.of(dynamicOps.createString(key1), dynamicOps.createString(key2));
|
|
}
|
|
|
|
@Override
|
|
public <T> DataResult<V> decode(DynamicOps<T> dynamicOps, MapLike<T> mapLike) {
|
|
T object = mapLike.get(key1);
|
|
return object == null ? DataResult.error(() -> "Missing \"" + key1 + "\" in: " + mapLike) : codec.decode(dynamicOps, object).flatMap(pair -> {
|
|
T objectx = (T)Objects.requireNonNullElseGet(mapLike.get(key2), dynamicOps::emptyMap);
|
|
return ((Codec)codecGetter.apply(pair.getFirst())).decode(dynamicOps, objectx).map(Pair::getFirst);
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public <T> RecordBuilder<T> encode(V object, DynamicOps<T> dynamicOps, RecordBuilder<T> recordBuilder) {
|
|
K object2 = (K)keyGetter.apply(object);
|
|
recordBuilder.add(key1, codec.encodeStart(dynamicOps, object2));
|
|
DataResult<T> dataResult = this.encode((Codec)codecGetter.apply(object2), object, dynamicOps);
|
|
if (dataResult.result().isEmpty() || !Objects.equals(dataResult.result().get(), dynamicOps.emptyMap())) {
|
|
recordBuilder.add(key2, dataResult);
|
|
}
|
|
|
|
return recordBuilder;
|
|
}
|
|
|
|
private <T, V2 extends V> DataResult<T> encode(Codec<V2> valueCodec, V value, DynamicOps<T> ops) {
|
|
return valueCodec.encodeStart(ops, (V2)value);
|
|
}
|
|
};
|
|
}
|
|
|
|
public static <A> Codec<Optional<A>> optionalEmptyMap(Codec<A> codec) {
|
|
return new Codec<Optional<A>>() {
|
|
@Override
|
|
public <T> DataResult<Pair<Optional<A>, T>> decode(DynamicOps<T> dynamicOps, T object) {
|
|
return isEmptyMap(dynamicOps, object)
|
|
? DataResult.success(Pair.of(Optional.empty(), object))
|
|
: codec.decode(dynamicOps, object).map(pair -> pair.mapFirst(Optional::of));
|
|
}
|
|
|
|
private static <T> boolean isEmptyMap(DynamicOps<T> ops, T value) {
|
|
Optional<MapLike<T>> optional = ops.getMap(value).result();
|
|
return optional.isPresent() && ((MapLike)optional.get()).entries().findAny().isEmpty();
|
|
}
|
|
|
|
public <T> DataResult<T> encode(Optional<A> input, DynamicOps<T> ops, T value) {
|
|
return input.isEmpty() ? DataResult.success(ops.emptyMap()) : codec.encode((A)input.get(), ops, value);
|
|
}
|
|
};
|
|
}
|
|
|
|
@Deprecated
|
|
public static <E extends Enum<E>> Codec<E> legacyEnum(Function<String, E> fromString) {
|
|
return Codec.STRING.comapFlatMap(string -> {
|
|
try {
|
|
return DataResult.success((Enum)fromString.apply(string));
|
|
} catch (IllegalArgumentException var3) {
|
|
return DataResult.error(() -> "No value with id: " + string);
|
|
}
|
|
}, Enum::toString);
|
|
}
|
|
|
|
public static class LateBoundIdMapper<I, V> {
|
|
private final BiMap<I, V> idToValue = HashBiMap.create();
|
|
|
|
public Codec<V> codec(Codec<I> idCodec) {
|
|
BiMap<V, I> biMap = this.idToValue.inverse();
|
|
return ExtraCodecs.idResolverCodec(idCodec, this.idToValue::get, biMap::get);
|
|
}
|
|
|
|
public ExtraCodecs.LateBoundIdMapper<I, V> put(I id, V value) {
|
|
Objects.requireNonNull(value, () -> "Value for " + id + " is null");
|
|
this.idToValue.put(id, value);
|
|
return this;
|
|
}
|
|
}
|
|
|
|
public record StrictUnboundedMapCodec<K, V>(Codec<K> a, Codec<V> b) implements Codec<Map<K, V>>, BaseMapCodec<K, V> {
|
|
@Override
|
|
public <T> DataResult<Map<K, V>> decode(DynamicOps<T> dynamicOps, MapLike<T> mapLike) {
|
|
Builder<K, V> builder = ImmutableMap.builder();
|
|
|
|
for (Pair<T, T> pair : mapLike.entries().toList()) {
|
|
DataResult<K> dataResult = this.keyCodec().parse(dynamicOps, pair.getFirst());
|
|
DataResult<V> dataResult2 = this.elementCodec().parse(dynamicOps, pair.getSecond());
|
|
DataResult<Pair<K, V>> dataResult3 = dataResult.apply2stable(Pair::of, dataResult2);
|
|
Optional<Error<Pair<K, V>>> optional = dataResult3.error();
|
|
if (optional.isPresent()) {
|
|
String string = ((Error)optional.get()).message();
|
|
return DataResult.error(() -> dataResult.result().isPresent() ? "Map entry '" + dataResult.result().get() + "' : " + string : string);
|
|
}
|
|
|
|
if (!dataResult3.result().isPresent()) {
|
|
return DataResult.error(() -> "Empty or invalid map contents are not allowed");
|
|
}
|
|
|
|
Pair<K, V> pair2 = (Pair<K, V>)dataResult3.result().get();
|
|
builder.put(pair2.getFirst(), pair2.getSecond());
|
|
}
|
|
|
|
Map<K, V> map = builder.build();
|
|
return DataResult.success(map);
|
|
}
|
|
|
|
@Override
|
|
public <T> DataResult<Pair<Map<K, V>, T>> decode(DynamicOps<T> dynamicOps, T object) {
|
|
return dynamicOps.getMap(object).setLifecycle(Lifecycle.stable()).flatMap(mapLike -> this.decode(dynamicOps, mapLike)).map(map -> Pair.of(map, object));
|
|
}
|
|
|
|
public <T> DataResult<T> encode(Map<K, V> input, DynamicOps<T> ops, T value) {
|
|
return this.encode(input, ops, ops.mapBuilder()).build(value);
|
|
}
|
|
|
|
public String toString() {
|
|
return "StrictUnboundedMapCodec[" + this.a + " -> " + this.b + "]";
|
|
}
|
|
|
|
@Override
|
|
public Codec<K> keyCodec() {
|
|
return this.a;
|
|
}
|
|
|
|
@Override
|
|
public Codec<V> elementCodec() {
|
|
return this.b;
|
|
}
|
|
}
|
|
|
|
public record TagOrElementLocation(ResourceLocation id, boolean tag) {
|
|
public String toString() {
|
|
return this.decoratedId();
|
|
}
|
|
|
|
private String decoratedId() {
|
|
return this.tag ? "#" + this.id : this.id.toString();
|
|
}
|
|
}
|
|
}
|