Minestom/src/main/java/net/minestom/server/entity/Metadata.java

419 lines
15 KiB
Java

package net.minestom.server.entity;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minestom.server.MinecraftServer;
import net.minestom.server.chat.ColoredText;
import net.minestom.server.chat.JsonMessage;
import net.minestom.server.item.ItemStack;
import net.minestom.server.network.packet.server.play.EntityMetaDataPacket;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.Direction;
import net.minestom.server.utils.Vector;
import net.minestom.server.utils.binary.BinaryReader;
import net.minestom.server.utils.binary.BinaryWriter;
import net.minestom.server.utils.binary.Readable;
import net.minestom.server.utils.binary.Writeable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTEnd;
import org.jglrxavpok.hephaistos.nbt.NBTException;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
public class Metadata {
// METADATA TYPES
public static Value<Byte> Byte(byte value) {
return new Value<>(TYPE_BYTE, value, writer -> writer.writeByte(value), BinaryReader::readByte);
}
public static Value<Integer> VarInt(int value) {
return new Value<>(TYPE_VARINT, value, writer -> writer.writeVarInt(value), BinaryReader::readVarInt);
}
public static Value<Float> Float(float value) {
return new Value<>(TYPE_FLOAT, value, writer -> writer.writeFloat(value), BinaryReader::readFloat);
}
public static Value<String> String(@NotNull String value) {
return new Value<>(TYPE_STRING, value, writer -> writer.writeSizedString(value), BinaryReader::readSizedString);
}
@Deprecated
public static Value<JsonMessage> Chat(@NotNull JsonMessage value) {
return new Value<>(TYPE_CHAT, value, writer -> writer.writeSizedString(value.toString()), reader -> reader.readJsonMessage(Integer.MAX_VALUE));
}
@Deprecated
public static Value<JsonMessage> OptChat(@Nullable JsonMessage value) {
return new Value<>(TYPE_OPTCHAT, value, writer -> {
final boolean present = value != null;
writer.writeBoolean(present);
if (present) {
writer.writeSizedString(value.toString());
}
},
reader -> {
boolean present = reader.readBoolean();
if (present) {
return reader.readJsonMessage(Integer.MAX_VALUE);
} else {
return null;
}
});
}
public static Value<Component> Chat(@NotNull Component value) {
return new Value<>(TYPE_CHAT, value, writer -> writer.writeComponent(value), BinaryReader::readComponent);
}
public static Value<Component> OptChat(@Nullable Component value) {
return new Value<>(TYPE_OPTCHAT, value, writer -> {
final boolean present = value != null;
writer.writeBoolean(present);
if (present) {
writer.writeComponent(value);
}
}, reader -> {
boolean present = reader.readBoolean();
if (present) {
return reader.readComponent();
}
return null;
});
}
public static Value<ItemStack> Slot(@NotNull ItemStack value) {
return new Value<>(TYPE_SLOT, value, writer -> writer.writeItemStack(value), BinaryReader::readItemStack);
}
public static Value<Boolean> Boolean(boolean value) {
return new Value<>(TYPE_BOOLEAN, value, writer -> writer.writeBoolean(value), BinaryReader::readBoolean);
}
public static Value<Vector> Rotation(@NotNull Vector value) {
return new Value<>(TYPE_ROTATION, value, writer -> {
writer.writeFloat((float) value.getX());
writer.writeFloat((float) value.getY());
writer.writeFloat((float) value.getZ());
}, reader -> new Vector(reader.readFloat(), reader.readFloat(), reader.readFloat()));
}
public static Value<BlockPosition> Position(@NotNull BlockPosition value) {
return new Value<>(TYPE_POSITION, value, writer -> writer.writeBlockPosition(value), BinaryReader::readBlockPosition);
}
public static Value<BlockPosition> OptPosition(@Nullable BlockPosition value) {
return new Value<>(TYPE_OPTPOSITION, value, writer -> {
final boolean present = value != null;
writer.writeBoolean(present);
if (present) {
writer.writeBlockPosition(value);
}
}, reader -> {
boolean present = reader.readBoolean();
if (present) {
return reader.readBlockPosition();
} else {
return null;
}
});
}
public static Value<Direction> Direction(@NotNull Direction value) {
return new Value<>(TYPE_DIRECTION, value, writer -> writer.writeVarInt(value.ordinal()), reader -> Direction.values()[reader.readVarInt()]);
}
public static Value<UUID> OptUUID(@Nullable UUID value) {
return new Value<>(TYPE_OPTUUID, value, writer -> {
final boolean present = value != null;
writer.writeBoolean(present);
if (present) {
writer.writeUuid(value);
}
}, reader -> {
boolean present = reader.readBoolean();
if (present) {
return reader.readUuid();
} else {
return null;
}
});
}
public static Value<Integer> OptBlockID(@Nullable Integer value) {
return new Value<>(TYPE_OPTBLOCKID, value, writer -> {
final boolean present = value != null;
writer.writeVarInt(present ? value : 0);
}, reader -> {
boolean present = reader.readBoolean();
if (present) {
return reader.readVarInt();
} else {
return null;
}
});
}
public static Value<NBT> NBT(@NotNull NBT nbt) {
return new Value<>(TYPE_NBT, nbt, writer ->
writer.writeNBT("", nbt), reader -> {
try {
return reader.readTag();
} catch (IOException | NBTException e) {
MinecraftServer.getExceptionManager().handleException(e);
return null;
}
});
}
public static Value<int[]> VillagerData(int villagerType,
int villagerProfession,
int level) {
return new Value<>(TYPE_VILLAGERDATA, new int[]{villagerType, villagerProfession, level}, writer -> {
writer.writeVarInt(villagerType);
writer.writeVarInt(villagerProfession);
writer.writeVarInt(level);
}, reader -> new int[]{
reader.readVarInt(),
reader.readVarInt(),
reader.readVarInt()
});
}
public static Value<Integer> OptVarInt(@Nullable Integer value) {
return new Value<>(TYPE_OPTVARINT, value, writer -> {
final boolean present = value != null;
writer.writeVarInt(present ? value + 1 : 0);
}, reader -> {
boolean present = reader.readBoolean();
if (present) {
return reader.readVarInt();
} else {
return null;
}
});
}
public static Value<Entity.Pose> Pose(@NotNull Entity.Pose value) {
return new Value<>(TYPE_POSE, value, writer -> writer.writeVarInt(value.ordinal()), reader -> Entity.Pose.values()[reader.readVarInt()]);
}
public static final byte TYPE_BYTE = 0;
public static final byte TYPE_VARINT = 1;
public static final byte TYPE_FLOAT = 2;
public static final byte TYPE_STRING = 3;
public static final byte TYPE_CHAT = 4;
public static final byte TYPE_OPTCHAT = 5;
public static final byte TYPE_SLOT = 6;
public static final byte TYPE_BOOLEAN = 7;
public static final byte TYPE_ROTATION = 8;
public static final byte TYPE_POSITION = 9;
public static final byte TYPE_OPTPOSITION = 10;
public static final byte TYPE_DIRECTION = 11;
public static final byte TYPE_OPTUUID = 12;
public static final byte TYPE_OPTBLOCKID = 13;
public static final byte TYPE_NBT = 14;
public static final byte TYPE_PARTICLE = 15;
public static final byte TYPE_VILLAGERDATA = 16;
public static final byte TYPE_OPTVARINT = 17;
public static final byte TYPE_POSE = 18;
private final Entity entity;
private final Map<Byte, Entry<?>> metadataMap = new ConcurrentHashMap<>();
private volatile boolean notifyAboutChanges = true;
private final Map<Byte, Entry<?>> notNotifiedChanges = new HashMap<>();
public Metadata(@Nullable Entity entity) {
this.entity = entity;
}
@SuppressWarnings("unchecked")
public <T> T getIndex(int index, @Nullable T defaultValue) {
Entry<?> value = this.metadataMap.get((byte) index);
return value != null ? (T) value.getMetaValue().value : defaultValue;
}
public void setIndex(int index, @NotNull Value<?> value) {
final Entry<?> entry = new Entry<>((byte) index, value);
this.metadataMap.put((byte) index, entry);
// Send metadata packet to update viewers and self
if (this.entity != null && this.entity.isActive()) {
if (!this.notifyAboutChanges) {
synchronized (this.notNotifiedChanges) {
this.notNotifiedChanges.put((byte) index, entry);
}
return;
}
EntityMetaDataPacket metaDataPacket = new EntityMetaDataPacket();
metaDataPacket.entityId = this.entity.getEntityId();
metaDataPacket.entries = Collections.singleton(entry);
this.entity.sendPacketToViewersAndSelf(metaDataPacket);
}
}
public void setNotifyAboutChanges(boolean notifyAboutChanges) {
if (this.notifyAboutChanges == notifyAboutChanges) {
return;
}
Collection<Entry<?>> entries = null;
synchronized (this.notNotifiedChanges) {
this.notifyAboutChanges = notifyAboutChanges;
if (notifyAboutChanges) {
entries = this.notNotifiedChanges.values();
if (entries.isEmpty()) {
return;
}
this.notNotifiedChanges.clear();
}
}
if (entries == null || this.entity == null || !this.entity.isActive()) {
return;
}
EntityMetaDataPacket metaDataPacket = new EntityMetaDataPacket();
metaDataPacket.entityId = this.entity.getEntityId();
metaDataPacket.entries = entries;
this.entity.sendPacketToViewersAndSelf(metaDataPacket);
}
@NotNull
public Collection<Entry<?>> getEntries() {
return metadataMap.values();
}
public static class Entry<T> implements Writeable {
protected byte index;
protected Value<T> value;
public Entry(byte index, @NotNull Value<T> value) {
this.index = index;
this.value = value;
}
public Entry(BinaryReader reader) {
this.index = reader.readByte();
int type = reader.readVarInt();
value = Metadata.read(type, reader);
}
@Override
public void write(@NotNull BinaryWriter writer) {
writer.writeByte(index);
this.value.write(writer);
}
public byte getIndex() {
return index;
}
@NotNull
public Value<T> getMetaValue() {
return value;
}
}
private static <T> Value<T> getCorrespondingNewEmptyValue(int type) {
switch (type) {
case TYPE_BYTE:
return (Value<T>) Byte((byte) 0);
case TYPE_VARINT:
return (Value<T>) VarInt(0);
case TYPE_FLOAT:
return (Value<T>) Float(0);
case TYPE_STRING:
return (Value<T>) String("");
case TYPE_CHAT:
return (Value<T>) Chat(ColoredText.of(""));
case TYPE_OPTCHAT:
return (Value<T>) OptChat((Component) null);
case TYPE_SLOT:
return (Value<T>) Slot(ItemStack.AIR);
case TYPE_BOOLEAN:
return (Value<T>) Boolean(false);
case TYPE_ROTATION:
return (Value<T>) Rotation(new Vector());
case TYPE_POSITION:
return (Value<T>) Position(new BlockPosition(0, 0, 0));
case TYPE_OPTPOSITION:
return (Value<T>) OptPosition(null);
case TYPE_DIRECTION:
return (Value<T>) Direction(Direction.DOWN);
case TYPE_OPTUUID:
return (Value<T>) OptUUID(null);
case TYPE_OPTBLOCKID:
return (Value<T>) OptBlockID(null);
case TYPE_NBT:
return (Value<T>) NBT(new NBTEnd());
case TYPE_PARTICLE:
throw new UnsupportedOperationException();
case TYPE_VILLAGERDATA:
return (Value<T>) VillagerData(0, 0, 0);
case TYPE_OPTVARINT:
return (Value<T>) OptVarInt(null);
case TYPE_POSE:
return (Value<T>) Pose(Entity.Pose.STANDING);
default:
throw new UnsupportedOperationException();
}
}
private static <T> Value<T> read(int type, BinaryReader reader) {
Value<T> value = getCorrespondingNewEmptyValue(type);
value.read(reader);
return value;
}
public static class Value<T> implements Writeable, Readable {
protected final int type;
protected T value;
protected final Consumer<BinaryWriter> valueWriter;
protected final Function<BinaryReader, T> readingFunction;
public Value(int type, T value, @NotNull Consumer<BinaryWriter> valueWriter, @NotNull Function<BinaryReader, T> readingFunction) {
this.type = type;
this.value = value;
this.valueWriter = valueWriter;
this.readingFunction = readingFunction;
}
@Override
public void write(@NotNull BinaryWriter writer) {
writer.writeVarInt(type);
this.valueWriter.accept(writer);
}
@Override
public void read(@NotNull BinaryReader reader) {
// skip type, will be read somewhere else
value = readingFunction.apply(reader);
}
public int getType() {
return type;
}
public T getValue() {
return value;
}
}
}