package net.minestom.server.entity; import net.kyori.adventure.text.Component; import net.minestom.server.coordinate.Point; import net.minestom.server.entity.metadata.animal.FrogMeta; import net.minestom.server.entity.metadata.animal.SnifferMeta; import net.minestom.server.entity.metadata.animal.tameable.CatMeta; import net.minestom.server.entity.metadata.other.PaintingMeta; import net.minestom.server.item.ItemStack; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.play.EntityMetaDataPacket; import net.minestom.server.particle.Particle; import net.minestom.server.utils.Direction; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; import org.jglrxavpok.hephaistos.nbt.NBT; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.UUID; public final class Metadata { public static Entry Byte(byte value) { return new MetadataImpl.EntryImpl<>(TYPE_BYTE, value, NetworkBuffer.BYTE); } public static Entry VarInt(int value) { return new MetadataImpl.EntryImpl<>(TYPE_VARINT, value, NetworkBuffer.VAR_INT); } public static Entry Long(long value) { return new MetadataImpl.EntryImpl<>(TYPE_LONG, value, NetworkBuffer.VAR_LONG); } public static Entry Float(float value) { return new MetadataImpl.EntryImpl<>(TYPE_FLOAT, value, NetworkBuffer.FLOAT); } public static Entry String(@NotNull String value) { return new MetadataImpl.EntryImpl<>(TYPE_STRING, value, NetworkBuffer.STRING); } public static Entry Chat(@NotNull Component value) { return new MetadataImpl.EntryImpl<>(TYPE_CHAT, value, NetworkBuffer.COMPONENT); } public static Entry OptChat(@Nullable Component value) { return new MetadataImpl.EntryImpl<>(TYPE_OPTCHAT, value, NetworkBuffer.OPT_CHAT); } public static Entry Slot(@NotNull ItemStack value) { return new MetadataImpl.EntryImpl<>(TYPE_SLOT, value, NetworkBuffer.ITEM); } public static Entry Boolean(boolean value) { return new MetadataImpl.EntryImpl<>(TYPE_BOOLEAN, value, NetworkBuffer.BOOLEAN); } public static Entry Rotation(@NotNull Point value) { return new MetadataImpl.EntryImpl<>(TYPE_ROTATION, value, NetworkBuffer.ROTATION); } public static Entry Position(@NotNull Point value) { return new MetadataImpl.EntryImpl<>(TYPE_POSITION, value, NetworkBuffer.BLOCK_POSITION); } public static Entry OptPosition(@Nullable Point value) { return new MetadataImpl.EntryImpl<>(TYPE_OPTPOSITION, value, NetworkBuffer.OPT_BLOCK_POSITION); } public static Entry Direction(@NotNull Direction value) { return new MetadataImpl.EntryImpl<>(TYPE_DIRECTION, value, NetworkBuffer.DIRECTION); } public static Entry OptUUID(@Nullable UUID value) { return new MetadataImpl.EntryImpl<>(TYPE_OPTUUID, value, NetworkBuffer.OPT_UUID); } public static Entry BlockState(@Nullable Integer value) { return new MetadataImpl.EntryImpl<>(TYPE_BLOCKSTATE, value, NetworkBuffer.BLOCK_STATE); } public static Entry OptBlockState(@Nullable Integer value) { return new MetadataImpl.EntryImpl<>(TYPE_OPTBLOCKSTATE, value, NetworkBuffer.OPT_BLOCK_STATE); } public static Entry NBT(@NotNull NBT nbt) { return new MetadataImpl.EntryImpl<>(TYPE_NBT, nbt, NetworkBuffer.NBT); } public static Entry VillagerData(int villagerType, int villagerProfession, int level) { return new MetadataImpl.EntryImpl<>(TYPE_VILLAGERDATA, new int[]{villagerType, villagerProfession, level}, NetworkBuffer.VILLAGER_DATA); } public static Entry OptVarInt(@Nullable Integer value) { return new MetadataImpl.EntryImpl<>(TYPE_OPTVARINT, value, NetworkBuffer.OPT_VAR_INT); } public static Entry Pose(@NotNull Entity.Pose value) { return new MetadataImpl.EntryImpl<>(TYPE_POSE, value, NetworkBuffer.POSE); } public static Entry CatVariant(@NotNull CatMeta.Variant value) { return new MetadataImpl.EntryImpl<>(TYPE_CAT_VARIANT, value, NetworkBuffer.CAT_VARIANT); } public static Entry FrogVariant(@NotNull FrogMeta.Variant value) { return new MetadataImpl.EntryImpl<>(TYPE_FROG_VARIANT, value, NetworkBuffer.FROG_VARIANT); } public static Entry PaintingVariant(@NotNull PaintingMeta.Variant value) { return new MetadataImpl.EntryImpl<>(TYPE_PAINTINGVARIANT, value, NetworkBuffer.PAINTING_VARIANT); } public static Entry SnifferState(@NotNull SnifferMeta.State value) { return new MetadataImpl.EntryImpl<>(TYPE_SNIFFER_STATE, value, NetworkBuffer.SNIFFER_STATE); } public static Entry Vector3(@NotNull Point value) { return new MetadataImpl.EntryImpl<>(TYPE_VECTOR3, value, NetworkBuffer.VECTOR3); } public static Entry Quaternion(float @NotNull[] value) { return new MetadataImpl.EntryImpl<>(TYPE_QUATERNION, value, NetworkBuffer.QUATERNION); } public static Entry Particle(@NotNull Particle particle) { return new MetadataImpl.EntryImpl<>(TYPE_PARTICLE, particle, NetworkBuffer.PARTICLE); } public static final byte TYPE_BYTE = 0; public static final byte TYPE_VARINT = 1; public static final byte TYPE_LONG = 2; public static final byte TYPE_FLOAT = 3; public static final byte TYPE_STRING = 4; public static final byte TYPE_CHAT = 5; public static final byte TYPE_OPTCHAT = 6; public static final byte TYPE_SLOT = 7; public static final byte TYPE_BOOLEAN = 8; public static final byte TYPE_ROTATION = 9; public static final byte TYPE_POSITION = 10; public static final byte TYPE_OPTPOSITION = 11; public static final byte TYPE_DIRECTION = 12; public static final byte TYPE_OPTUUID = 13; public static final byte TYPE_BLOCKSTATE = 14; public static final byte TYPE_OPTBLOCKSTATE = 15; public static final byte TYPE_NBT = 16; public static final byte TYPE_PARTICLE = 17; public static final byte TYPE_VILLAGERDATA = 18; public static final byte TYPE_OPTVARINT = 19; public static final byte TYPE_POSE = 20; public static final byte TYPE_CAT_VARIANT = 21; public static final byte TYPE_FROG_VARIANT = 22; public static final byte TYPE_OPTGLOBALPOS = 23; public static final byte TYPE_PAINTINGVARIANT = 24; public static final byte TYPE_SNIFFER_STATE = 25; public static final byte TYPE_VECTOR3 = 26; public static final byte TYPE_QUATERNION = 27; // Impl Note: Adding an entry here requires that a default value entry is added in MetadataImpl.EMPTY_VALUES private static final VarHandle NOTIFIED_CHANGES; static { try { NOTIFIED_CHANGES = MethodHandles.lookup().findVarHandle(Metadata.class, "notifyAboutChanges", boolean.class); } catch (NoSuchFieldException | IllegalAccessException e) { throw new IllegalStateException(e); } } private final Entity entity; private volatile Entry[] entries = new Entry[0]; private volatile Map> entryMap = null; @SuppressWarnings("FieldMayBeFinal") private volatile boolean notifyAboutChanges = true; private final Map> notNotifiedChanges = new HashMap<>(); public Metadata(@Nullable Entity entity) { this.entity = entity; } @SuppressWarnings("unchecked") public T getIndex(int index, @Nullable T defaultValue) { final Entry[] entries = this.entries; if (index < 0 || index >= entries.length) return defaultValue; final Entry entry = entries[index]; return entry != null ? (T) entry.value() : defaultValue; } public void setIndex(int index, @NotNull Entry entry) { Entry[] entries = this.entries; // Resize array if necessary if (index >= entries.length) { final int newLength = Math.max(entries.length * 2, index + 1); this.entries = entries = Arrays.copyOf(entries, newLength); } entries[index] = entry; this.entryMap = null; // Send metadata packet to update viewers and self final Entity entity = this.entity; if (entity != null && entity.isActive()) { if (!this.notifyAboutChanges) { synchronized (this.notNotifiedChanges) { this.notNotifiedChanges.put(index, entry); } } else { entity.sendPacketToViewersAndSelf(new EntityMetaDataPacket(entity.getEntityId(), Map.of(index, entry))); } } } public void setNotifyAboutChanges(boolean notifyAboutChanges) { if (!NOTIFIED_CHANGES.compareAndSet(this, !notifyAboutChanges, notifyAboutChanges)) return; if (!notifyAboutChanges) { // Ask future metadata changes to be cached return; } final Entity entity = this.entity; if (entity == null || !entity.isActive()) return; Map> entries; synchronized (this.notNotifiedChanges) { Map> awaitingChanges = this.notNotifiedChanges; if (awaitingChanges.isEmpty()) return; entries = Map.copyOf(awaitingChanges); awaitingChanges.clear(); } entity.sendPacketToViewersAndSelf(new EntityMetaDataPacket(entity.getEntityId(), entries)); } public @NotNull Map> getEntries() { Map> map = entryMap; if (map == null) { map = new HashMap<>(); final Entry[] entries = this.entries; for (int i = 0; i < entries.length; i++) { final Entry entry = entries[i]; if (entry != null) map.put(i, entry); } this.entryMap = Map.copyOf(map); } return map; } public sealed interface Entry extends NetworkBuffer.Writer permits MetadataImpl.EntryImpl { int type(); @UnknownNullability T value(); @ApiStatus.Internal static @NotNull Entry read(int type, @NotNull NetworkBuffer reader) { return MetadataImpl.EntryImpl.read(type, reader); } } }