mirror of
https://github.com/Minestom/Minestom.git
synced 2025-03-02 11:21:15 +01:00
Entity metadata cleanup
Signed-off-by: TheMode <themode@outlook.fr>
This commit is contained in:
parent
d87a8f72c7
commit
4b7f8213aa
@ -6,121 +6,121 @@ import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.network.packet.server.play.EntityMetaDataPacket;
|
||||
import net.minestom.server.utils.Direction;
|
||||
import net.minestom.server.utils.collection.ObjectArray;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import net.minestom.server.utils.binary.Writeable;
|
||||
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 org.jglrxavpok.hephaistos.nbt.NBTEnd;
|
||||
import space.vectrix.flare.fastutil.Short2ObjectSyncMap;
|
||||
import space.vectrix.flare.fastutil.Int2ObjectSyncMap;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
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 final class Metadata {
|
||||
public static Entry<Byte> Byte(byte value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_BYTE, value, BinaryWriter::writeByte, BinaryReader::readByte);
|
||||
}
|
||||
|
||||
public static Value<Integer> VarInt(int value) {
|
||||
return new Value<>(TYPE_VARINT, value, writer -> writer.writeVarInt(value), BinaryReader::readVarInt);
|
||||
public static Entry<Integer> VarInt(int value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_VARINT, value, BinaryWriter::writeVarInt, BinaryReader::readVarInt);
|
||||
}
|
||||
|
||||
public static Value<Float> Float(float value) {
|
||||
return new Value<>(TYPE_FLOAT, value, writer -> writer.writeFloat(value), BinaryReader::readFloat);
|
||||
public static Entry<Float> Float(float value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_FLOAT, value, BinaryWriter::writeFloat, BinaryReader::readFloat);
|
||||
}
|
||||
|
||||
public static Value<String> String(@NotNull String value) {
|
||||
return new Value<>(TYPE_STRING, value, writer -> writer.writeSizedString(value), BinaryReader::readSizedString);
|
||||
public static Entry<String> String(@NotNull String value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_STRING, value, BinaryWriter::writeSizedString, BinaryReader::readSizedString);
|
||||
}
|
||||
|
||||
public static Value<Component> Chat(@NotNull Component value) {
|
||||
return new Value<>(TYPE_CHAT, value, writer -> writer.writeComponent(value), BinaryReader::readComponent);
|
||||
public static Entry<Component> Chat(@NotNull Component value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_CHAT, value, BinaryWriter::writeComponent, BinaryReader::readComponent);
|
||||
}
|
||||
|
||||
public static Value<Component> OptChat(@Nullable Component value) {
|
||||
return new Value<>(TYPE_OPTCHAT, value, writer -> {
|
||||
writer.writeBoolean(value != null);
|
||||
if (value != null) writer.writeComponent(value);
|
||||
public static Entry<Component> OptChat(@Nullable Component value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_OPTCHAT, value, (writer, v) -> {
|
||||
writer.writeBoolean(v != null);
|
||||
if (v != null) writer.writeComponent(v);
|
||||
}, reader -> reader.readBoolean() ? reader.readComponent() : null);
|
||||
}
|
||||
|
||||
public static Value<ItemStack> Slot(@NotNull ItemStack value) {
|
||||
return new Value<>(TYPE_SLOT, value, writer -> writer.writeItemStack(value), BinaryReader::readItemStack);
|
||||
public static Entry<ItemStack> Slot(@NotNull ItemStack value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_SLOT, value, BinaryWriter::writeItemStack, BinaryReader::readItemStack);
|
||||
}
|
||||
|
||||
public static Value<Boolean> Boolean(boolean value) {
|
||||
return new Value<>(TYPE_BOOLEAN, value, writer -> writer.writeBoolean(value), BinaryReader::readBoolean);
|
||||
public static Entry<Boolean> Boolean(boolean value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_BOOLEAN, value, BinaryWriter::writeBoolean, BinaryReader::readBoolean);
|
||||
}
|
||||
|
||||
public static Value<Point> Rotation(@NotNull Point value) {
|
||||
return new Value<>(TYPE_ROTATION, value, writer -> {
|
||||
writer.writeFloat((float) value.x());
|
||||
writer.writeFloat((float) value.y());
|
||||
writer.writeFloat((float) value.z());
|
||||
public static Entry<Point> Rotation(@NotNull Point value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_ROTATION, value, (writer, v) -> {
|
||||
writer.writeFloat((float) v.x());
|
||||
writer.writeFloat((float) v.y());
|
||||
writer.writeFloat((float) v.z());
|
||||
}, reader -> new Vec(reader.readFloat(), reader.readFloat(), reader.readFloat()));
|
||||
}
|
||||
|
||||
public static Value<Point> Position(@NotNull Point value) {
|
||||
return new Value<>(TYPE_POSITION, value, writer -> writer.writeBlockPosition(value), BinaryReader::readBlockPosition);
|
||||
public static Entry<Point> Position(@NotNull Point value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_POSITION, value, BinaryWriter::writeBlockPosition, BinaryReader::readBlockPosition);
|
||||
}
|
||||
|
||||
public static Value<Point> OptPosition(@Nullable Point value) {
|
||||
return new Value<>(TYPE_OPTPOSITION, value, writer -> {
|
||||
writer.writeBoolean(value != null);
|
||||
if (value != null) writer.writeBlockPosition(value);
|
||||
public static Entry<Point> OptPosition(@Nullable Point value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_OPTPOSITION, value, (writer, v) -> {
|
||||
writer.writeBoolean(v != null);
|
||||
if (v != null) writer.writeBlockPosition(v);
|
||||
}, reader -> reader.readBoolean() ? reader.readBlockPosition() : null);
|
||||
}
|
||||
|
||||
public static Value<Direction> Direction(@NotNull Direction value) {
|
||||
return new Value<>(TYPE_DIRECTION, value,
|
||||
writer -> writer.writeVarInt(value.ordinal()),
|
||||
public static Entry<Direction> Direction(@NotNull Direction value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_DIRECTION, value,
|
||||
(writer, v) -> writer.writeVarInt(v.ordinal()),
|
||||
reader -> Direction.values()[reader.readVarInt()]);
|
||||
}
|
||||
|
||||
public static Value<UUID> OptUUID(@Nullable UUID value) {
|
||||
return new Value<>(TYPE_OPTUUID, value, writer -> {
|
||||
writer.writeBoolean(value != null);
|
||||
if (value != null) writer.writeUuid(value);
|
||||
public static Entry<UUID> OptUUID(@Nullable UUID value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_OPTUUID, value, (writer, v) -> {
|
||||
writer.writeBoolean(v != null);
|
||||
if (v != null) writer.writeUuid(v);
|
||||
}, reader -> reader.readBoolean() ? reader.readUuid() : null);
|
||||
}
|
||||
|
||||
public static Value<Integer> OptBlockID(@Nullable Integer value) {
|
||||
return new Value<>(TYPE_OPTBLOCKID, value,
|
||||
writer -> writer.writeVarInt(value != null ? value : 0),
|
||||
public static Entry<Integer> OptBlockID(@Nullable Integer value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_OPTBLOCKID, value,
|
||||
(writer, v) -> writer.writeVarInt(v != null ? v : 0),
|
||||
reader -> reader.readBoolean() ? reader.readVarInt() : null);
|
||||
}
|
||||
|
||||
public static Value<NBT> NBT(@NotNull NBT nbt) {
|
||||
return new Value<>(TYPE_NBT, nbt, writer -> writer.writeNBT("", nbt), BinaryReader::readTag);
|
||||
public static Entry<NBT> NBT(@NotNull NBT nbt) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_NBT, nbt, (writer, v) -> writer.writeNBT("", v), BinaryReader::readTag);
|
||||
}
|
||||
|
||||
public static Value<int[]> VillagerData(int villagerType,
|
||||
public static Entry<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()});
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_VILLAGERDATA, new int[]{villagerType, villagerProfession, level},
|
||||
(writer, v) -> {
|
||||
writer.writeVarInt(v[0]);
|
||||
writer.writeVarInt(v[1]);
|
||||
writer.writeVarInt(v[2]);
|
||||
},
|
||||
reader -> new int[]{reader.readVarInt(), reader.readVarInt(), reader.readVarInt()});
|
||||
}
|
||||
|
||||
public static Value<Integer> OptVarInt(@Nullable Integer value) {
|
||||
return new Value<>(TYPE_OPTVARINT,
|
||||
value, writer -> writer.writeVarInt(value != null ? value + 1 : 0),
|
||||
public static Entry<Integer> OptVarInt(@Nullable Integer value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_OPTVARINT,
|
||||
value, (writer, v) -> writer.writeVarInt(v != null ? v + 1 : 0),
|
||||
reader -> reader.readBoolean() ? reader.readVarInt() : null);
|
||||
}
|
||||
|
||||
public static Value<Entity.Pose> Pose(@NotNull Entity.Pose value) {
|
||||
return new Value<>(TYPE_POSE, value,
|
||||
writer -> writer.writeVarInt(value.ordinal()),
|
||||
public static Entry<Entity.Pose> Pose(@NotNull Entity.Pose value) {
|
||||
return new MetadataImpl.EntryImpl<>(TYPE_POSE, value,
|
||||
(writer, v) -> writer.writeVarInt(v.ordinal()),
|
||||
reader -> Entity.Pose.values()[reader.readVarInt()]);
|
||||
}
|
||||
|
||||
@ -144,37 +144,22 @@ public class Metadata {
|
||||
public static final byte TYPE_OPTVARINT = 17;
|
||||
public static final byte TYPE_POSE = 18;
|
||||
|
||||
private static final ObjectArray<Value<?>> EMPTY_VALUES = new ObjectArray<>(20);
|
||||
private static final VarHandle NOTIFIED_CHANGES;
|
||||
|
||||
static {
|
||||
EMPTY_VALUES.set(TYPE_BYTE, Byte((byte) 0));
|
||||
EMPTY_VALUES.set(TYPE_VARINT, VarInt(0));
|
||||
EMPTY_VALUES.set(TYPE_FLOAT, Float(0f));
|
||||
EMPTY_VALUES.set(TYPE_STRING, String(""));
|
||||
EMPTY_VALUES.set(TYPE_CHAT, Chat(Component.empty()));
|
||||
EMPTY_VALUES.set(TYPE_OPTCHAT, OptChat(null));
|
||||
EMPTY_VALUES.set(TYPE_SLOT, Slot(ItemStack.AIR));
|
||||
EMPTY_VALUES.set(TYPE_BOOLEAN, Boolean(false));
|
||||
EMPTY_VALUES.set(TYPE_ROTATION, Rotation(Vec.ZERO));
|
||||
EMPTY_VALUES.set(TYPE_POSITION, Position(Vec.ZERO));
|
||||
EMPTY_VALUES.set(TYPE_OPTPOSITION, OptPosition(null));
|
||||
EMPTY_VALUES.set(TYPE_DIRECTION, Direction(Direction.DOWN));
|
||||
EMPTY_VALUES.set(TYPE_OPTUUID, OptUUID(null));
|
||||
EMPTY_VALUES.set(TYPE_OPTBLOCKID, OptBlockID(null));
|
||||
EMPTY_VALUES.set(TYPE_NBT, NBT(NBTEnd.INSTANCE));
|
||||
//EMPTY_VALUES.set(TYPE_PARTICLE -> throw new UnsupportedOperationException();
|
||||
EMPTY_VALUES.set(TYPE_VILLAGERDATA, VillagerData(0, 0, 0));
|
||||
EMPTY_VALUES.set(TYPE_OPTVARINT, OptVarInt(null));
|
||||
EMPTY_VALUES.set(TYPE_POSE, Pose(Entity.Pose.STANDING));
|
||||
|
||||
EMPTY_VALUES.trim();
|
||||
try {
|
||||
NOTIFIED_CHANGES = MethodHandles.lookup().findVarHandle(Metadata.class, "notifyAboutChanges", boolean.class);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private final Entity entity;
|
||||
private final Short2ObjectSyncMap<Entry<?>> metadataMap = Short2ObjectSyncMap.hashmap();
|
||||
private final Int2ObjectSyncMap<Entry<?>> metadataMap = Int2ObjectSyncMap.hashmap();
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
private volatile boolean notifyAboutChanges = true;
|
||||
private final Map<Byte, Entry<?>> notNotifiedChanges = new HashMap<>();
|
||||
private final Map<Integer, Entry<?>> notNotifiedChanges = new HashMap<>();
|
||||
|
||||
public Metadata(@Nullable Entity entity) {
|
||||
this.entity = entity;
|
||||
@ -182,83 +167,57 @@ public class Metadata {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getIndex(int index, @Nullable T defaultValue) {
|
||||
Entry<?> entry = this.metadataMap.get((byte) index);
|
||||
return entry != null ? (T) entry.value().content : defaultValue;
|
||||
final Entry<?> entry = this.metadataMap.get(index);
|
||||
return entry != null ? (T) entry.value() : defaultValue;
|
||||
}
|
||||
|
||||
public void setIndex(int index, @NotNull Value<?> value) {
|
||||
final Entry<?> entry = new Entry<>((byte) index, value);
|
||||
this.metadataMap.put((short) index, entry);
|
||||
|
||||
public void setIndex(int index, @NotNull Entry<?> entry) {
|
||||
this.metadataMap.put(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);
|
||||
this.notNotifiedChanges.put(index, entry);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
this.entity.sendPacketToViewersAndSelf(new EntityMetaDataPacket(entity.getEntityId(), Map.of(index, entry)));
|
||||
}
|
||||
this.entity.sendPacketToViewersAndSelf(new EntityMetaDataPacket(entity.getEntityId(), Collections.singleton(entry)));
|
||||
}
|
||||
}
|
||||
|
||||
public void setNotifyAboutChanges(boolean notifyAboutChanges) {
|
||||
if (this.notifyAboutChanges == notifyAboutChanges) {
|
||||
if (!NOTIFIED_CHANGES.compareAndSet(this, !notifyAboutChanges, notifyAboutChanges)) {
|
||||
return;
|
||||
}
|
||||
Collection<Entry<?>> entries = null;
|
||||
if (!notifyAboutChanges) {
|
||||
// Ask future metadata changes to be cached
|
||||
return;
|
||||
}
|
||||
Map<Integer, Entry<?>> entries;
|
||||
synchronized (this.notNotifiedChanges) {
|
||||
this.notifyAboutChanges = notifyAboutChanges;
|
||||
if (notifyAboutChanges) {
|
||||
entries = this.notNotifiedChanges.values();
|
||||
if (entries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Map<Integer, Entry<?>> awaitingChanges = this.notNotifiedChanges;
|
||||
if (awaitingChanges.isEmpty()) return;
|
||||
entries = Map.copyOf(awaitingChanges);
|
||||
awaitingChanges.clear();
|
||||
}
|
||||
if (entries == null || this.entity == null || !this.entity.isActive()) {
|
||||
return;
|
||||
}
|
||||
this.entity.sendPacketToViewersAndSelf(new EntityMetaDataPacket(entity.getEntityId(), entries));
|
||||
this.notNotifiedChanges.clear();
|
||||
}
|
||||
|
||||
public @NotNull Collection<Entry<?>> getEntries() {
|
||||
return metadataMap.values();
|
||||
public @NotNull Map<Integer, Entry<?>> getEntries() {
|
||||
return metadataMap;
|
||||
}
|
||||
|
||||
public record Entry<T>(byte index, @NotNull Value<T> value) implements Writeable {
|
||||
public Entry(BinaryReader reader) {
|
||||
this(reader.readByte(), readValue(reader));
|
||||
}
|
||||
public interface Entry<T> extends Writeable {
|
||||
int type();
|
||||
|
||||
@Override
|
||||
public void write(@NotNull BinaryWriter writer) {
|
||||
writer.writeByte(index);
|
||||
writer.write(value);
|
||||
}
|
||||
@UnknownNullability T value();
|
||||
|
||||
private static <T> Value<T> readValue(BinaryReader reader) {
|
||||
final int type = reader.readVarInt();
|
||||
final Value<?> value = EMPTY_VALUES.get(type);
|
||||
if (value == null)
|
||||
throw new UnsupportedOperationException("Unknown value type: " + type);
|
||||
//noinspection unchecked
|
||||
return (Value<T>) value.withValue(reader);
|
||||
}
|
||||
}
|
||||
|
||||
public record Value<T>(int type, @UnknownNullability T content,
|
||||
@NotNull Consumer<BinaryWriter> writer,
|
||||
@NotNull Function<BinaryReader, T> reader) implements Writeable {
|
||||
@Override
|
||||
public void write(@NotNull BinaryWriter writer) {
|
||||
writer.writeVarInt(type);
|
||||
this.writer.accept(writer);
|
||||
}
|
||||
|
||||
private Value<T> withValue(@NotNull BinaryReader reader) {
|
||||
return new Value<>(type, this.reader.apply(reader), writer, this.reader);
|
||||
@ApiStatus.Internal
|
||||
static @NotNull Entry<?> read(int type, @NotNull BinaryReader reader) {
|
||||
return MetadataImpl.EntryImpl.read(type, reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
68
src/main/java/net/minestom/server/entity/MetadataImpl.java
Normal file
68
src/main/java/net/minestom/server/entity/MetadataImpl.java
Normal file
@ -0,0 +1,68 @@
|
||||
package net.minestom.server.entity;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.utils.Direction;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import net.minestom.server.utils.collection.ObjectArray;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.UnknownNullability;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTEnd;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static net.minestom.server.entity.Metadata.Boolean;
|
||||
import static net.minestom.server.entity.Metadata.Byte;
|
||||
import static net.minestom.server.entity.Metadata.Float;
|
||||
import static net.minestom.server.entity.Metadata.String;
|
||||
import static net.minestom.server.entity.Metadata.*;
|
||||
|
||||
final class MetadataImpl {
|
||||
static final ObjectArray<Metadata.Entry<?>> EMPTY_VALUES = new ObjectArray<>(20);
|
||||
|
||||
static {
|
||||
EMPTY_VALUES.set(TYPE_BYTE, Byte((byte) 0));
|
||||
EMPTY_VALUES.set(TYPE_VARINT, VarInt(0));
|
||||
EMPTY_VALUES.set(TYPE_FLOAT, Float(0f));
|
||||
EMPTY_VALUES.set(TYPE_STRING, String(""));
|
||||
EMPTY_VALUES.set(TYPE_CHAT, Chat(Component.empty()));
|
||||
EMPTY_VALUES.set(TYPE_OPTCHAT, OptChat(null));
|
||||
EMPTY_VALUES.set(TYPE_SLOT, Slot(ItemStack.AIR));
|
||||
EMPTY_VALUES.set(TYPE_BOOLEAN, Boolean(false));
|
||||
EMPTY_VALUES.set(TYPE_ROTATION, Rotation(Vec.ZERO));
|
||||
EMPTY_VALUES.set(TYPE_POSITION, Position(Vec.ZERO));
|
||||
EMPTY_VALUES.set(TYPE_OPTPOSITION, OptPosition(null));
|
||||
EMPTY_VALUES.set(TYPE_DIRECTION, Direction(Direction.DOWN));
|
||||
EMPTY_VALUES.set(TYPE_OPTUUID, OptUUID(null));
|
||||
EMPTY_VALUES.set(TYPE_OPTBLOCKID, OptBlockID(null));
|
||||
EMPTY_VALUES.set(TYPE_NBT, NBT(NBTEnd.INSTANCE));
|
||||
//EMPTY_VALUES.set(TYPE_PARTICLE -> throw new UnsupportedOperationException();
|
||||
EMPTY_VALUES.set(TYPE_VILLAGERDATA, VillagerData(0, 0, 0));
|
||||
EMPTY_VALUES.set(TYPE_OPTVARINT, OptVarInt(null));
|
||||
EMPTY_VALUES.set(TYPE_POSE, Pose(Entity.Pose.STANDING));
|
||||
EMPTY_VALUES.trim();
|
||||
}
|
||||
|
||||
record EntryImpl<T>(int type, @UnknownNullability T value,
|
||||
@NotNull BiConsumer<BinaryWriter, T> writer,
|
||||
@NotNull Function<BinaryReader, T> reader) implements Metadata.Entry<T> {
|
||||
static Entry<?> read(int type, @NotNull BinaryReader reader) {
|
||||
final EntryImpl<?> value = (EntryImpl<?>) EMPTY_VALUES.get(type);
|
||||
if (value == null) throw new UnsupportedOperationException("Unknown value type: " + type);
|
||||
return value.withValue(reader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NotNull BinaryWriter writer) {
|
||||
writer.writeVarInt(type);
|
||||
this.writer.accept(writer, value);
|
||||
}
|
||||
|
||||
private EntryImpl<T> withValue(@NotNull BinaryReader reader) {
|
||||
return new EntryImpl<>(type, this.reader.apply(reader), writer, this.reader);
|
||||
}
|
||||
}
|
||||
}
|
@ -7,14 +7,13 @@ import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public record EntityMetaDataPacket(int entityId,
|
||||
@NotNull Collection<Metadata.Entry<?>> entries) implements ServerPacket {
|
||||
@NotNull Map<Integer, Metadata.Entry<?>> entries) implements ServerPacket {
|
||||
public EntityMetaDataPacket {
|
||||
entries = List.copyOf(entries);
|
||||
entries = Map.copyOf(entries);
|
||||
}
|
||||
|
||||
public EntityMetaDataPacket(BinaryReader reader) {
|
||||
@ -24,18 +23,22 @@ public record EntityMetaDataPacket(int entityId,
|
||||
@Override
|
||||
public void write(@NotNull BinaryWriter writer) {
|
||||
writer.writeVarInt(entityId);
|
||||
this.entries.forEach(writer::write);
|
||||
for (var entry : entries.entrySet()) {
|
||||
writer.writeByte(entry.getKey().byteValue());
|
||||
writer.write(entry.getValue());
|
||||
}
|
||||
writer.writeByte((byte) 0xFF); // End
|
||||
}
|
||||
|
||||
private static Collection<Metadata.Entry<?>> readEntries(BinaryReader reader) {
|
||||
Collection<Metadata.Entry<?>> entries = new ArrayList<>();
|
||||
private static Map<Integer, Metadata.Entry<?>> readEntries(BinaryReader reader) {
|
||||
Map<Integer, Metadata.Entry<?>> entries = new HashMap<>();
|
||||
while (true) {
|
||||
byte index = reader.readByte();
|
||||
final byte index = reader.readByte();
|
||||
if (index == (byte) 0xFF) { // reached the end
|
||||
break;
|
||||
}
|
||||
entries.add(new Metadata.Entry<>(reader));
|
||||
final int type = reader.readVarInt();
|
||||
entries.put((int) index, Metadata.Entry.read(type, reader));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@EnvTest
|
||||
public class EntityMetaIntegrationTest {
|
||||
@ -41,16 +40,12 @@ public class EntityMetaIntegrationTest {
|
||||
// Two packets should be received: One for the player, one for the viewer
|
||||
assertEquals(2, packets.size());
|
||||
validMetaDataPackets(packets, player.getEntityId(), entry -> {
|
||||
// Magic values are confusing? https://wiki.vg/Entity_metadata#Entity_Metadata_Format
|
||||
switch(entry.value().type()) {
|
||||
case 0 -> // Zero means "read one byte", sadly, the byte 34 is a magic value.
|
||||
assertEquals((byte) 34, entry.value().content());
|
||||
case 7 -> // Seven means "read one boolean"
|
||||
assertTrue((boolean)entry.value().content());
|
||||
case 18 -> // Entity sneaking info
|
||||
assertEquals(Entity.Pose.SNEAKING, entry.value().content());
|
||||
default ->
|
||||
Assertions.fail("Invalid MetaData entry");
|
||||
final Object content = entry.value();
|
||||
switch (entry.type()) {
|
||||
case Metadata.TYPE_BYTE -> assertEquals((byte) 34, content);
|
||||
case Metadata.TYPE_BOOLEAN -> assertTrue((boolean) content);
|
||||
case Metadata.TYPE_POSE -> assertEquals(Entity.Pose.SNEAKING, content);
|
||||
default -> Assertions.fail("Invalid MetaData entry");
|
||||
}
|
||||
});
|
||||
|
||||
@ -61,17 +56,12 @@ public class EntityMetaIntegrationTest {
|
||||
player.setSneaking(false);
|
||||
packets = incomingPackets.collect();
|
||||
validMetaDataPackets(packets, player.getEntityId(), entry -> {
|
||||
var content = entry.value().content();
|
||||
// Magic values are confusing? https://wiki.vg/Entity_metadata#Entity_Metadata_Format
|
||||
switch(entry.value().type()) {
|
||||
case 0 -> // Zero means "read one byte", here, the bytes 2 and 0 are magic values :(
|
||||
assertTrue(content.equals((byte)2) || content.equals((byte)0));
|
||||
case 7 -> // Seven means "read one boolean".
|
||||
assertFalse((boolean)content);
|
||||
case 18 -> // Entity sneaking info
|
||||
assertEquals(Entity.Pose.STANDING, content);
|
||||
default ->
|
||||
Assertions.fail("Invalid MetaData entry");
|
||||
final Object content = entry.value();
|
||||
switch (entry.type()) {
|
||||
case Metadata.TYPE_BYTE -> assertTrue(content.equals((byte) 2) || content.equals((byte) 0));
|
||||
case Metadata.TYPE_BOOLEAN -> assertFalse((boolean) content);
|
||||
case Metadata.TYPE_POSE -> assertEquals(Entity.Pose.STANDING, content);
|
||||
default -> Assertions.fail("Invalid MetaData entry");
|
||||
}
|
||||
});
|
||||
// 4 changes, for two viewers
|
||||
@ -81,10 +71,9 @@ public class EntityMetaIntegrationTest {
|
||||
private void validMetaDataPackets(List<EntityMetaDataPacket> packets, int entityId, Consumer<Metadata.Entry<?>> contentChecker) {
|
||||
for (var packet : packets) {
|
||||
assertEquals(packet.entityId(), entityId);
|
||||
for (var entry : packet.entries()) {
|
||||
for (var entry : packet.entries().values()) {
|
||||
contentChecker.accept(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import net.kyori.adventure.text.Component;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.entity.EquipmentSlot;
|
||||
import net.minestom.server.entity.GameMode;
|
||||
import net.minestom.server.entity.Metadata;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.message.ChatPosition;
|
||||
@ -18,6 +19,7 @@ import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
|
||||
import net.minestom.server.network.packet.server.login.SetCompressionPacket;
|
||||
import net.minestom.server.network.packet.server.play.*;
|
||||
import net.minestom.server.network.packet.server.play.DeclareRecipesPacket.Ingredient;
|
||||
import net.minestom.server.network.packet.server.status.PongPacket;
|
||||
import net.minestom.server.utils.binary.BinaryReader;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
@ -26,8 +28,6 @@ import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import net.minestom.server.network.packet.server.play.DeclareRecipesPacket.Ingredient;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -84,19 +84,19 @@ public class PacketWriteReadTest {
|
||||
SERVER_PACKETS.add(new DeathCombatEventPacket(5, 5, COMPONENT));
|
||||
SERVER_PACKETS.add(new DeclareRecipesPacket(
|
||||
List.of(new DeclareRecipesPacket.DeclaredShapelessCraftingRecipe(
|
||||
"minecraft:sticks",
|
||||
"sticks",
|
||||
List.of(new Ingredient(List.of(ItemStack.of(Material.OAK_PLANKS)))),
|
||||
ItemStack.of(Material.STICK)
|
||||
"minecraft:sticks",
|
||||
"sticks",
|
||||
List.of(new Ingredient(List.of(ItemStack.of(Material.OAK_PLANKS)))),
|
||||
ItemStack.of(Material.STICK)
|
||||
),
|
||||
new DeclareRecipesPacket.DeclaredShapedCraftingRecipe(
|
||||
"minecraft:torch",
|
||||
1,
|
||||
2,
|
||||
"",
|
||||
List.of(new Ingredient(List.of(ItemStack.of(Material.COAL))),
|
||||
new Ingredient(List.of(ItemStack.of(Material.STICK)))),
|
||||
ItemStack.of(Material.TORCH)
|
||||
"minecraft:torch",
|
||||
1,
|
||||
2,
|
||||
"",
|
||||
List.of(new Ingredient(List.of(ItemStack.of(Material.COAL))),
|
||||
new Ingredient(List.of(ItemStack.of(Material.STICK)))),
|
||||
ItemStack.of(Material.TORCH)
|
||||
))));
|
||||
|
||||
SERVER_PACKETS.add(new DestroyEntitiesPacket(List.of(5, 5, 5)));
|
||||
@ -108,7 +108,8 @@ public class PacketWriteReadTest {
|
||||
SERVER_PACKETS.add(new EntityAnimationPacket(5, EntityAnimationPacket.Animation.TAKE_DAMAGE));
|
||||
SERVER_PACKETS.add(new EntityEquipmentPacket(6, Map.of(EquipmentSlot.MAIN_HAND, ItemStack.of(Material.DIAMOND_SWORD))));
|
||||
SERVER_PACKETS.add(new EntityHeadLookPacket(5, 90f));
|
||||
SERVER_PACKETS.add(new EntityMetaDataPacket(5, List.of()));
|
||||
SERVER_PACKETS.add(new EntityMetaDataPacket(5, Map.of()));
|
||||
SERVER_PACKETS.add(new EntityMetaDataPacket(5, Map.of(1, Metadata.VarInt(5))));
|
||||
SERVER_PACKETS.add(new EntityPositionAndRotationPacket(5, (short) 0, (short) 0, (short) 0, 45f, 45f, false));
|
||||
SERVER_PACKETS.add(new EntityPositionPacket(5, (short) 0, (short) 0, (short) 0, true));
|
||||
SERVER_PACKETS.add(new EntityPropertiesPacket(5, List.of()));
|
||||
@ -154,7 +155,7 @@ public class PacketWriteReadTest {
|
||||
var createdPacket = readerConstructor.newInstance(reader);
|
||||
assertEquals(writeable, createdPacket);
|
||||
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException
|
||||
| IllegalAccessException e) {
|
||||
| IllegalAccessException e) {
|
||||
fail(writeable.toString(), e);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user