2021-05-17 12:34:45 +02:00
|
|
|
package net.minestom.server.tag;
|
|
|
|
|
2022-04-12 16:20:17 +02:00
|
|
|
import net.kyori.adventure.text.Component;
|
2022-03-20 01:47:57 +01:00
|
|
|
import net.minestom.server.item.ItemStack;
|
2022-05-16 08:39:18 +02:00
|
|
|
import net.minestom.server.utils.collection.AutoIncrementMap;
|
2021-05-17 17:46:56 +02:00
|
|
|
import org.jetbrains.annotations.ApiStatus;
|
2021-05-28 16:19:58 +02:00
|
|
|
import org.jetbrains.annotations.Contract;
|
2021-05-17 12:34:45 +02:00
|
|
|
import org.jetbrains.annotations.NotNull;
|
2021-05-17 12:44:22 +02:00
|
|
|
import org.jetbrains.annotations.Nullable;
|
2022-04-07 11:05:11 +02:00
|
|
|
import org.jglrxavpok.hephaistos.nbt.NBT;
|
|
|
|
import org.jglrxavpok.hephaistos.nbt.NBTCompoundLike;
|
|
|
|
import org.jglrxavpok.hephaistos.nbt.NBTList;
|
|
|
|
import org.jglrxavpok.hephaistos.nbt.NBTType;
|
2021-12-13 16:41:30 +01:00
|
|
|
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
|
2021-05-17 12:34:45 +02:00
|
|
|
|
2022-04-07 11:34:18 +02:00
|
|
|
import java.util.Arrays;
|
2022-03-24 05:42:01 +01:00
|
|
|
import java.util.List;
|
2022-04-10 10:01:39 +02:00
|
|
|
import java.util.Objects;
|
2022-04-12 19:16:11 +02:00
|
|
|
import java.util.UUID;
|
2021-05-17 12:34:45 +02:00
|
|
|
import java.util.function.Function;
|
2021-05-17 12:44:22 +02:00
|
|
|
import java.util.function.Supplier;
|
2022-03-24 09:03:30 +01:00
|
|
|
import java.util.function.UnaryOperator;
|
2021-05-17 12:34:45 +02:00
|
|
|
|
2021-05-17 15:14:16 +02:00
|
|
|
/**
|
|
|
|
* Represents a key to retrieve or change a value.
|
|
|
|
* <p>
|
|
|
|
* All tags are serializable.
|
|
|
|
*
|
|
|
|
* @param <T> the tag type
|
|
|
|
*/
|
2021-05-17 17:46:56 +02:00
|
|
|
@ApiStatus.NonExtendable
|
|
|
|
public class Tag<T> {
|
2022-05-16 08:39:18 +02:00
|
|
|
private static final AutoIncrementMap<String> INDEX_MAP = new AutoIncrementMap<>();
|
2021-06-22 02:51:04 +02:00
|
|
|
|
2022-03-24 05:42:01 +01:00
|
|
|
record PathEntry(String name, int index) {
|
|
|
|
}
|
|
|
|
|
2022-03-24 09:03:30 +01:00
|
|
|
final int index;
|
2021-05-17 12:34:45 +02:00
|
|
|
private final String key;
|
2022-04-07 11:05:11 +02:00
|
|
|
final Serializers.Entry<T, NBT> entry;
|
2021-05-28 16:19:58 +02:00
|
|
|
private final Supplier<T> defaultValue;
|
2021-05-17 12:44:22 +02:00
|
|
|
|
2022-04-07 11:05:11 +02:00
|
|
|
final Function<?, ?> readComparator;
|
2022-03-25 17:48:18 +01:00
|
|
|
// Optional properties
|
2022-03-26 15:26:46 +01:00
|
|
|
final PathEntry[] path;
|
2022-03-24 09:03:30 +01:00
|
|
|
final UnaryOperator<T> copy;
|
2022-03-25 17:48:18 +01:00
|
|
|
final int listScope;
|
2022-03-24 05:42:01 +01:00
|
|
|
|
2022-03-24 09:03:30 +01:00
|
|
|
Tag(int index, String key,
|
2022-04-07 11:05:11 +02:00
|
|
|
Function<?, ?> readComparator,
|
|
|
|
Serializers.Entry<T, NBT> entry,
|
2022-03-26 15:26:46 +01:00
|
|
|
Supplier<T> defaultValue, PathEntry[] path, UnaryOperator<T> copy, int listScope) {
|
2022-03-24 09:03:30 +01:00
|
|
|
assert index == INDEX_MAP.get(key);
|
|
|
|
this.index = index;
|
2021-05-17 12:34:45 +02:00
|
|
|
this.key = key;
|
2022-04-07 11:05:11 +02:00
|
|
|
this.readComparator = readComparator;
|
|
|
|
this.entry = entry;
|
2021-05-28 16:19:58 +02:00
|
|
|
this.defaultValue = defaultValue;
|
2022-03-24 05:42:01 +01:00
|
|
|
this.path = path;
|
2022-03-24 09:03:30 +01:00
|
|
|
this.copy = copy;
|
2022-03-25 17:48:18 +01:00
|
|
|
this.listScope = listScope;
|
2021-05-28 16:19:58 +02:00
|
|
|
}
|
|
|
|
|
2022-04-07 11:05:11 +02:00
|
|
|
static <T, N extends NBT> Tag<T> tag(@NotNull String key, @NotNull Serializers.Entry<T, N> entry) {
|
2022-04-20 17:19:45 +02:00
|
|
|
return new Tag<>(INDEX_MAP.get(key), key, entry.reader(), (Serializers.Entry<T, NBT>) entry,
|
2022-03-25 17:48:18 +01:00
|
|
|
null, null, null, 0);
|
2021-05-17 12:34:45 +02:00
|
|
|
}
|
|
|
|
|
2022-04-07 11:05:11 +02:00
|
|
|
static <T> Tag<T> fromSerializer(@NotNull String key, @NotNull TagSerializer<T> serializer) {
|
|
|
|
if (serializer instanceof TagRecord.Serializer recordSerializer) {
|
|
|
|
// Allow fast retrieval
|
2022-04-10 12:26:33 +02:00
|
|
|
//noinspection unchecked
|
2022-04-07 11:05:11 +02:00
|
|
|
return tag(key, recordSerializer.serializerEntry);
|
|
|
|
}
|
|
|
|
return tag(key, Serializers.fromTagSerializer(serializer));
|
|
|
|
}
|
|
|
|
|
2021-06-26 05:08:33 +02:00
|
|
|
/**
|
|
|
|
* Returns the key used to navigate inside the holder nbt.
|
|
|
|
*
|
|
|
|
* @return the tag key
|
|
|
|
*/
|
2022-03-20 01:47:57 +01:00
|
|
|
public @NotNull String getKey() {
|
2021-05-17 12:34:45 +02:00
|
|
|
return key;
|
|
|
|
}
|
|
|
|
|
2021-05-28 16:19:58 +02:00
|
|
|
@Contract(value = "_ -> new", pure = true)
|
2021-05-17 12:44:22 +02:00
|
|
|
public Tag<T> defaultValue(@NotNull Supplier<T> defaultValue) {
|
2022-04-07 11:05:11 +02:00
|
|
|
return new Tag<>(index, key, readComparator, entry, defaultValue, path, copy, listScope);
|
2021-05-17 12:44:22 +02:00
|
|
|
}
|
|
|
|
|
2021-05-28 16:19:58 +02:00
|
|
|
@Contract(value = "_ -> new", pure = true)
|
2021-05-17 12:44:22 +02:00
|
|
|
public Tag<T> defaultValue(@NotNull T defaultValue) {
|
2021-05-28 16:21:54 +02:00
|
|
|
return defaultValue(() -> defaultValue);
|
2021-05-28 16:19:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Contract(value = "_, _ -> new", pure = true)
|
|
|
|
public <R> Tag<R> map(@NotNull Function<T, R> readMap,
|
|
|
|
@NotNull Function<R, T> writeMap) {
|
2022-04-07 11:05:11 +02:00
|
|
|
var entry = this.entry;
|
2022-04-20 17:19:45 +02:00
|
|
|
final Function<NBT, R> readFunction = entry.reader().andThen(t -> {
|
2022-04-07 11:05:11 +02:00
|
|
|
if (t == null) return null;
|
|
|
|
return readMap.apply(t);
|
|
|
|
});
|
2022-04-20 17:19:45 +02:00
|
|
|
final Function<R, NBT> writeFunction = writeMap.andThen(entry.writer());
|
2022-04-07 11:05:11 +02:00
|
|
|
return new Tag<>(index, key, readMap,
|
2022-04-20 17:19:45 +02:00
|
|
|
new Serializers.Entry<>(entry.nbtType(), readFunction, writeFunction),
|
2021-05-28 16:19:58 +02:00
|
|
|
// Default value
|
2022-04-17 20:52:29 +02:00
|
|
|
() -> {
|
|
|
|
T defaultValue = createDefault();
|
|
|
|
if (defaultValue == null) return null;
|
|
|
|
return readMap.apply(defaultValue);
|
|
|
|
},
|
2022-03-25 17:48:18 +01:00
|
|
|
path, null, listScope);
|
2022-03-24 09:03:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@ApiStatus.Experimental
|
|
|
|
@Contract(value = "-> new", pure = true)
|
|
|
|
public Tag<List<T>> list() {
|
2022-04-07 11:05:11 +02:00
|
|
|
var entry = this.entry;
|
2022-04-20 17:19:45 +02:00
|
|
|
var readFunction = entry.reader();
|
|
|
|
var writeFunction = entry.writer();
|
2022-04-29 18:24:55 +02:00
|
|
|
var listEntry = new Serializers.Entry<List<T>, NBTList<?>>(
|
|
|
|
NBTType.TAG_List,
|
2022-03-24 09:03:30 +01:00
|
|
|
read -> {
|
2022-05-09 20:56:06 +02:00
|
|
|
if (read.isEmpty()) return List.of();
|
|
|
|
return read.asListView().stream().map(readFunction).toList();
|
2022-03-24 09:03:30 +01:00
|
|
|
},
|
|
|
|
write -> {
|
2022-05-09 20:56:06 +02:00
|
|
|
if (write.isEmpty())
|
|
|
|
return NBT.List(NBTType.TAG_String); // String is the default type for lists
|
|
|
|
final List<NBT> list = write.stream().map(writeFunction).toList();
|
|
|
|
final NBTType<?> type = list.get(0).getID();
|
|
|
|
return NBT.List(type, list);
|
2022-04-07 11:05:11 +02:00
|
|
|
});
|
|
|
|
UnaryOperator<List<T>> co = this.copy != null ? ts -> {
|
|
|
|
final int size = ts.size();
|
|
|
|
T[] array = (T[]) new Object[size];
|
|
|
|
boolean shallowCopy = true;
|
|
|
|
for (int i = 0; i < size; i++) {
|
|
|
|
final T t = ts.get(i);
|
|
|
|
final T copy = this.copy.apply(t);
|
|
|
|
if (shallowCopy && copy != t) shallowCopy = false;
|
|
|
|
array[i] = copy;
|
|
|
|
}
|
|
|
|
return shallowCopy ? List.copyOf(ts) : List.of(array);
|
|
|
|
} : List::copyOf;
|
2022-04-20 17:19:45 +02:00
|
|
|
return new Tag<>(index, key, readComparator, Serializers.Entry.class.cast(listEntry),
|
|
|
|
null, path, co, listScope + 1);
|
2022-03-24 05:42:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@ApiStatus.Experimental
|
|
|
|
@Contract(value = "_ -> new", pure = true)
|
|
|
|
public Tag<T> path(@NotNull String @Nullable ... path) {
|
2022-03-26 15:26:46 +01:00
|
|
|
if (path == null || path.length == 0) {
|
2022-04-07 11:05:11 +02:00
|
|
|
return new Tag<>(index, key, readComparator, entry, defaultValue, null, copy, listScope);
|
2022-03-26 15:26:46 +01:00
|
|
|
}
|
2022-04-07 11:05:11 +02:00
|
|
|
PathEntry[] pathEntries = new PathEntry[path.length];
|
2022-03-26 15:26:46 +01:00
|
|
|
for (int i = 0; i < path.length; i++) {
|
2022-04-07 11:34:18 +02:00
|
|
|
final String name = path[i];
|
|
|
|
if (name == null || name.isEmpty())
|
|
|
|
throw new IllegalArgumentException("Path must not be empty: " + Arrays.toString(path));
|
2022-04-07 11:05:11 +02:00
|
|
|
pathEntries[i] = new PathEntry(name, INDEX_MAP.get(name));
|
2022-03-26 15:26:46 +01:00
|
|
|
}
|
2022-04-07 11:05:11 +02:00
|
|
|
return new Tag<>(index, key, readComparator, entry, defaultValue, pathEntries, copy, listScope);
|
2021-05-17 12:44:22 +02:00
|
|
|
}
|
|
|
|
|
2022-03-20 01:47:57 +01:00
|
|
|
public @Nullable T read(@NotNull NBTCompoundLike nbt) {
|
2022-04-10 11:44:08 +02:00
|
|
|
final NBT readable = isView() ? nbt.toCompound() : nbt.get(key);
|
2022-03-23 06:51:36 +01:00
|
|
|
final T result;
|
|
|
|
try {
|
2022-04-20 17:19:45 +02:00
|
|
|
if (readable == null || (result = entry.read(readable)) == null)
|
2022-03-23 06:51:36 +01:00
|
|
|
return createDefault();
|
|
|
|
return result;
|
|
|
|
} catch (ClassCastException e) {
|
|
|
|
return createDefault();
|
2021-05-17 13:04:00 +02:00
|
|
|
}
|
2022-03-20 01:47:57 +01:00
|
|
|
}
|
|
|
|
|
2021-12-13 16:41:30 +01:00
|
|
|
public void write(@NotNull MutableNBTCompound nbtCompound, @Nullable T value) {
|
2022-03-20 01:47:57 +01:00
|
|
|
if (value != null) {
|
2022-04-20 17:19:45 +02:00
|
|
|
final NBT nbt = entry.write(value);
|
2022-04-10 11:44:08 +02:00
|
|
|
if (isView()) nbtCompound.copyFrom((NBTCompoundLike) nbt);
|
2022-03-20 01:47:57 +01:00
|
|
|
else nbtCompound.set(key, nbt);
|
2021-05-17 13:04:00 +02:00
|
|
|
} else {
|
2022-04-10 11:44:08 +02:00
|
|
|
if (isView()) nbtCompound.clear();
|
2022-03-20 01:47:57 +01:00
|
|
|
else nbtCompound.remove(key);
|
2021-05-17 13:04:00 +02:00
|
|
|
}
|
2021-05-17 12:34:45 +02:00
|
|
|
}
|
|
|
|
|
2021-12-13 16:41:30 +01:00
|
|
|
public void writeUnsafe(@NotNull MutableNBTCompound nbtCompound, @Nullable Object value) {
|
2021-12-16 01:22:42 +01:00
|
|
|
//noinspection unchecked
|
2021-06-22 02:51:04 +02:00
|
|
|
write(nbtCompound, (T) value);
|
|
|
|
}
|
|
|
|
|
2022-04-09 15:20:11 +02:00
|
|
|
final boolean isView() {
|
|
|
|
return key.isEmpty();
|
|
|
|
}
|
|
|
|
|
2022-03-24 11:23:16 +01:00
|
|
|
final boolean shareValue(@NotNull Tag<?> other) {
|
2022-04-07 11:05:11 +02:00
|
|
|
if (this == other) return true;
|
|
|
|
// Tags are not strictly the same, compare readers
|
|
|
|
if (this.listScope != other.listScope)
|
|
|
|
return false;
|
|
|
|
return this.readComparator == other.readComparator;
|
2022-03-24 11:23:16 +01:00
|
|
|
}
|
|
|
|
|
2022-04-09 15:20:11 +02:00
|
|
|
final T createDefault() {
|
2022-04-12 14:14:36 +02:00
|
|
|
final Supplier<T> supplier = defaultValue;
|
2022-04-09 15:20:11 +02:00
|
|
|
return supplier != null ? supplier.get() : null;
|
|
|
|
}
|
|
|
|
|
2022-06-25 10:36:50 +02:00
|
|
|
final T copyValue(@NotNull T value) {
|
|
|
|
final UnaryOperator<T> copier = copy;
|
|
|
|
return copier != null ? copier.apply(value) : value;
|
|
|
|
}
|
|
|
|
|
2022-04-10 10:01:39 +02:00
|
|
|
@Override
|
|
|
|
public boolean equals(Object o) {
|
|
|
|
if (this == o) return true;
|
|
|
|
if (!(o instanceof Tag<?> tag)) return false;
|
|
|
|
return index == tag.index &&
|
|
|
|
listScope == tag.listScope &&
|
|
|
|
readComparator.equals(tag.readComparator) &&
|
|
|
|
Objects.equals(defaultValue, tag.defaultValue) &&
|
|
|
|
Arrays.equals(path, tag.path) && Objects.equals(copy, tag.copy);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int hashCode() {
|
|
|
|
int result = Objects.hash(index, readComparator, defaultValue, copy, listScope);
|
|
|
|
result = 31 * result + Arrays.hashCode(path);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-05-17 12:34:45 +02:00
|
|
|
public static @NotNull Tag<Byte> Byte(@NotNull String key) {
|
2022-04-07 11:05:11 +02:00
|
|
|
return tag(key, Serializers.BYTE);
|
2021-05-17 12:34:45 +02:00
|
|
|
}
|
|
|
|
|
2022-04-10 15:38:13 +02:00
|
|
|
public static @NotNull Tag<Boolean> Boolean(@NotNull String key) {
|
|
|
|
return tag(key, Serializers.BOOLEAN);
|
|
|
|
}
|
|
|
|
|
2021-05-17 12:34:45 +02:00
|
|
|
public static @NotNull Tag<Short> Short(@NotNull String key) {
|
2022-04-07 11:05:11 +02:00
|
|
|
return tag(key, Serializers.SHORT);
|
2021-05-17 12:34:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static @NotNull Tag<Integer> Integer(@NotNull String key) {
|
2022-04-07 11:05:11 +02:00
|
|
|
return tag(key, Serializers.INT);
|
2021-05-17 12:34:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static @NotNull Tag<Long> Long(@NotNull String key) {
|
2022-04-07 11:05:11 +02:00
|
|
|
return tag(key, Serializers.LONG);
|
2021-05-17 12:34:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static @NotNull Tag<Float> Float(@NotNull String key) {
|
2022-04-07 11:05:11 +02:00
|
|
|
return tag(key, Serializers.FLOAT);
|
2021-05-17 12:34:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static @NotNull Tag<Double> Double(@NotNull String key) {
|
2022-04-07 11:05:11 +02:00
|
|
|
return tag(key, Serializers.DOUBLE);
|
2021-05-17 12:34:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static @NotNull Tag<String> String(@NotNull String key) {
|
2022-04-07 11:05:11 +02:00
|
|
|
return tag(key, Serializers.STRING);
|
2021-05-17 12:34:45 +02:00
|
|
|
}
|
|
|
|
|
2022-04-12 19:16:11 +02:00
|
|
|
@ApiStatus.Experimental
|
|
|
|
public static @NotNull Tag<UUID> UUID(@NotNull String key) {
|
|
|
|
return tag(key, Serializers.UUID);
|
|
|
|
}
|
|
|
|
|
2022-04-07 11:34:18 +02:00
|
|
|
public static @NotNull Tag<ItemStack> ItemStack(@NotNull String key) {
|
|
|
|
return tag(key, Serializers.ITEM);
|
|
|
|
}
|
|
|
|
|
2022-04-12 16:20:17 +02:00
|
|
|
public static @NotNull Tag<Component> Component(@NotNull String key) {
|
|
|
|
return tag(key, Serializers.COMPONENT);
|
|
|
|
}
|
|
|
|
|
2021-06-22 02:51:04 +02:00
|
|
|
/**
|
2022-04-09 16:17:24 +02:00
|
|
|
* Creates a flexible tag able to read and write any {@link NBT} objects.
|
|
|
|
* <p>
|
|
|
|
* Specialized tags are recommended if the type is known as conversion will be required both way (read and write).
|
|
|
|
*/
|
|
|
|
public static @NotNull Tag<NBT> NBT(@NotNull String key) {
|
|
|
|
return tag(key, Serializers.NBT_ENTRY);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a tag containing multiple fields.
|
|
|
|
* <p>
|
|
|
|
* Those fields cannot be modified from an outside tag. (This is to prevent the backed object from becoming out of sync)
|
2021-06-22 02:51:04 +02:00
|
|
|
*
|
|
|
|
* @param key the tag key
|
|
|
|
* @param serializer the tag serializer
|
|
|
|
* @param <T> the tag type
|
|
|
|
* @return the created tag
|
|
|
|
*/
|
|
|
|
public static <T> @NotNull Tag<T> Structure(@NotNull String key, @NotNull TagSerializer<T> serializer) {
|
2022-04-07 11:05:11 +02:00
|
|
|
return fromSerializer(key, serializer);
|
|
|
|
}
|
|
|
|
|
2022-04-09 16:17:24 +02:00
|
|
|
/**
|
|
|
|
* Specialized Structure tag affecting the src of the handler (i.e. overwrite all its data).
|
|
|
|
* <p>
|
|
|
|
* Must be used with care.
|
|
|
|
*/
|
2022-04-07 11:34:18 +02:00
|
|
|
public static <T> @NotNull Tag<T> View(@NotNull TagSerializer<T> serializer) {
|
|
|
|
return Structure("", serializer);
|
|
|
|
}
|
|
|
|
|
2022-04-07 11:05:11 +02:00
|
|
|
@ApiStatus.Experimental
|
|
|
|
public static <T extends Record> @NotNull Tag<T> Structure(@NotNull String key, @NotNull Class<T> type) {
|
2022-04-07 11:34:18 +02:00
|
|
|
return Structure(key, TagRecord.serializer(type));
|
2021-05-17 14:02:14 +02:00
|
|
|
}
|
2021-06-22 02:51:04 +02:00
|
|
|
|
2022-04-07 11:34:18 +02:00
|
|
|
@ApiStatus.Experimental
|
|
|
|
public static <T extends Record> @NotNull Tag<T> View(@NotNull Class<T> type) {
|
|
|
|
return View(TagRecord.serializer(type));
|
2021-06-22 03:05:22 +02:00
|
|
|
}
|
2021-05-17 12:34:45 +02:00
|
|
|
}
|