diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java index 24e8fa6c2..ca7921f9f 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java @@ -20,9 +20,11 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -33,9 +35,12 @@ import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; +import com.comphenix.protocol.wrappers.EnumWrappers.Direction; +import com.comphenix.protocol.wrappers.EnumWrappers.EntityPose; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; -import com.google.common.base.Optional; import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableMap; import org.apache.commons.lang.Validate; import org.bukkit.entity.Entity; import org.bukkit.inventory.ItemStack; @@ -200,7 +205,7 @@ public void clear() { impl.clear(); } - // ---- Object Getters + // ---- 0: byte /** * Get a watched byte. @@ -212,6 +217,12 @@ public Byte getByte(int index) { return (Byte) getObject(index); } + public void setByte(int index, byte value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(Byte.class)), value, update); + } + + // ---- short (unused) + /** * Get a watched short. * @@ -222,6 +233,8 @@ public Short getShort(int index) { return (Short) getObject(index); } + // ---- 1: varint (integer) + /** * Get a watched integer. * @@ -232,6 +245,22 @@ public Integer getInteger(int index) { return (Integer) getObject(index); } + public void setInteger(int index, Integer value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(Integer.class)), value, update); + } + + // ---- 2: varlong (long) + + public Long getLong(int index) { + return (Long) getObject(index); + } + + public void setLong(int index, Long value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(Long.class)), value, update); + } + + // ---- 3: float + /** * Get a watched float. * @@ -242,6 +271,12 @@ public Float getFloat(int index) { return (Float) getObject(index); } + public void setFloat(int index, Float value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(Float.class)), value, update); + } + + // ---- 4: string + /** * Get a watched string. * @@ -252,6 +287,32 @@ public String getString(int index) { return (String) getObject(index); } + public void setString(int index, String value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(String.class)), value, update); + } + + // ---- 5: text component + + public WrappedChatComponent getChatComponent(int index) { + return (WrappedChatComponent) getObject(index); + } + + public void setChatComponent(int index, WrappedChatComponent value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getChatComponentSerializer()), value, update); + } + + // ---- 6: optional text component + + public Optional getOptionalChatComponent(int index) { + return (Optional) getObject(index); + } + + public void setOptionalChatComponent(int index, Optional value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getChatComponentSerializer(true)), value, update); + } + + // ---- 7: slot (item stack) + /** * Get a watched string. * @@ -262,6 +323,158 @@ public ItemStack getItemStack(int index) { return (ItemStack) getObject(index); } + public void setItemStack(int index, ItemStack itemStack, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getItemStackSerializer(false)), itemStack, update); + } + + // ---- 8: boolean + + public Boolean getBoolean(int index) { + return (Boolean) getObject(index); + } + + public void setBoolean(int index, Boolean value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(Boolean.class)), value, update); + } + + // ---- 9: rotations + // TODO + + // ---- 10: position + + public BlockPosition getPosition(int index) { + return (BlockPosition) getObject(index); + } + + public void setPosition(int index, BlockPosition position, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getBlockPositionSerializer(false)), position, update); + } + + // ---- 11: optional position + + public Optional getOptionalPosition(int index) { + return (Optional) getObject(index); + } + + public void setOptionalPosition(int index, java.util.Optional position, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getBlockPositionSerializer(true)), position, update); + } + + // ---- 12: direction + + public Direction getDirection(int index) { + return (Direction) getObject(index); + } + + public void setDirection(int index, Direction direction, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getDirectionSerializer()), direction, update); + } + + // ---- 13: optional uuid + + public Optional getOptionalUUID(int index) { + return (Optional) getObject(index); + } + + public void setOptionalUUID(int index, Optional uuid, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getUUIDSerializer(true)), uuid, update); + } + + // ---- 14: block state + + public WrappedBlockData getBlockState(int index) { + return (WrappedBlockData) getObject(index); + } + + public void setBlockState(int index, WrappedBlockData blockData, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getBlockDataSerializer(false)), blockData, update); + } + + // ---- 15: optional block state + + public Optional getOptionalBlockState(int index) { + return (Optional) getObject(index); + } + + public void setOptionalBlockState(int index, Optional value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getBlockDataSerializer(true)), value, update); + } + + // ---- 16: NBT + + public NbtCompound getNBTCompound(int index) { + return (NbtCompound) getObject(index); + } + + public void setNBTCompound(int index, NbtCompound nbt, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.getNBTCompoundSerializer()), nbt, update); + } + + // ---- 17: particle + + public WrappedParticle getParticle(int index) { + return (WrappedParticle) getObject(index); + } + + public void setParticle(int index, WrappedParticle particle, boolean update) { + // TODO: is ParticleParam correct? + setObject(new WrappedDataWatcherObject(index, Registry.get(MinecraftReflection.getParticleParam(), false)), particle, update); + } + + // ---- 18: villager data + + public WrappedVillagerData getVillagerData(int index) { + return (WrappedVillagerData) getObject(index); + } + + public void setVillagerData(int index, WrappedVillagerData data, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(WrappedVillagerData.getNmsClass(), false)), data, update); + } + + // ---- 19: optional varint (int) + + public Optional getOptionalInteger(int index) { + return (Optional) getObject(index); + } + + public void setOptionalInteger(int index, Optional value, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(Integer.class, true)), value, update); + } + + // ---- 20: pose + + public EntityPose getPose(int index) { + return (EntityPose) getObject(index); + } + + public void setPose(int index, EntityPose pose, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(EnumWrappers.getEntityPoseClass(), false)), pose, update); + } + + // TODO: 21-25, 27 + + // ---- 21: cat variant + + // ---- 22: frog variant + + // ---- 23: optional global position + + // ---- 24: painting variant + + // ---- 25: sniffer state + + // ---- 26: vector3 + + public Vector3F getVector3F(int index) { + return (Vector3F) getObject(index); + } + + public void setVector3F(int index, Vector3F vector, boolean update) { + setObject(new WrappedDataWatcherObject(index, Registry.get(Vector3F.getMinecraftClass(), false)), vector, update); + } + + // ---- 27: quaternion + /** * Retrieve a watchable object by index. * @@ -439,6 +652,22 @@ public void setEntity(Entity entity) { impl.setEntity(entity); } + /** + * Exports the contents of this data watcher to a list of WrappedDataValues + * for use in the ENTITY_METADATA packet + * @return The data value collection + */ + public List toDataValueCollection() { + List objects = impl.getWatchableObjects(); + List values = new ArrayList<>(objects.size()); + for (WrappedWatchableObject object : objects) { + WrappedDataWatcherObject watcherObj = object.getWatcherObject(); + Object value = WrappedWatchableObject.getUnwrapped(object.getRawValue()); + values.add(new WrappedDataValue(watcherObj.getIndex(), watcherObj.getSerializer(), value)); + } + return values; + } + private static final ImmutableBiMap, Integer> CLASS_TO_ID = new ImmutableBiMap.Builder, Integer>() .put(Byte.class, 0) .put(Short.class, 1) @@ -756,7 +985,9 @@ public String toString() { */ public static class Registry { private static boolean INITIALIZED = false; - private static List REGISTRY = new ArrayList<>(); + + private static Map, Serializer> RAW_REGISTRY = null; + private static Map, Serializer> OPTIONAL_REGISTRY = null; /** * Gets the first serializer associated with a given class. @@ -774,13 +1005,13 @@ public static Serializer get(Class clazz) { Validate.notNull(clazz,"Class cannot be null!"); initialize(); - for (Serializer serializer : REGISTRY) { - if (serializer.getType().equals(clazz)) { - return serializer; - } + Serializer serializer = RAW_REGISTRY.getOrDefault(clazz, + OPTIONAL_REGISTRY.getOrDefault(clazz, null)); + if (serializer == null) { + throw new IllegalArgumentException("No serializer found for " + clazz); } - throw new IllegalArgumentException("No serializer found for " + clazz); + return serializer; } /** @@ -797,16 +1028,12 @@ public static Serializer get(Class clazz, boolean optional) { Validate.notNull(clazz, "Class cannot be null!"); initialize(); - Validate.notEmpty(REGISTRY, "Registry has no elements!"); - - for (Serializer serializer : REGISTRY) { - if (serializer.getType().equals(clazz) - && serializer.isOptional() == optional) { - return serializer; - } + Serializer serializer = optional ? OPTIONAL_REGISTRY.get(clazz) : RAW_REGISTRY.get(clazz); + if (serializer == null) { + throw new IllegalArgumentException("No serializer found for " + (optional ? "Optional<" + clazz + ">" : clazz)); } - throw new IllegalArgumentException("No serializer found for " + (optional ? "Optional<" + clazz + ">" : clazz)); + return serializer; } /** @@ -818,7 +1045,13 @@ public static Serializer fromHandle(Object handle) { Validate.notNull(handle, "handle cannot be null!"); initialize(); - for (Serializer serializer : REGISTRY) { + for (Serializer serializer : RAW_REGISTRY.values()) { + if (serializer.getHandle().equals(handle)) { + return serializer; + } + } + + for (Serializer serializer : OPTIONAL_REGISTRY.values()) { if (serializer.getHandle().equals(handle)) { return serializer; } @@ -834,6 +1067,9 @@ private static void initialize() { return; } + Map, Serializer> rawRegistry = new HashMap<>(); + Map, Serializer> optionalRegistry = new HashMap<>(); + List candidates = FuzzyReflection.fromClass(MinecraftReflection.getDataWatcherRegistryClass(), true) .getFieldListByType(MinecraftReflection.getDataWatcherSerializerClass()); for (Field candidate : candidates) { @@ -867,9 +1103,16 @@ private static void initialize() { throw new RuntimeException("Failed to read serializer: " + candidate.getName()); } - REGISTRY.add(new Serializer(innerClass, serializer, optional)); + if (optional) { + optionalRegistry.put(innerClass, new Serializer(innerClass, serializer, true)); + } else { + rawRegistry.put(innerClass, new Serializer(innerClass, serializer, false)); + } } } + + RAW_REGISTRY = ImmutableMap.copyOf(rawRegistry); + OPTIONAL_REGISTRY = ImmutableMap.copyOf(optionalRegistry); } // ---- Helper methods diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java index 37fcac340..e77bfd141 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java @@ -14,6 +14,8 @@ */ package com.comphenix.protocol.wrappers; +import java.util.Optional; + import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; @@ -23,9 +25,8 @@ import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject; import com.comphenix.protocol.wrappers.nbt.NbtCompound; import com.comphenix.protocol.wrappers.nbt.NbtFactory; -import org.bukkit.inventory.ItemStack; -import java.util.Optional; +import org.bukkit.inventory.ItemStack; import static com.comphenix.protocol.utility.MinecraftReflection.is; @@ -80,11 +81,13 @@ private static Object newHandle(WrappedDataWatcherObject watcherObject, Object v constructor = Accessors.getConstructorAccessor(HANDLE_TYPE.getConstructors()[0]); } + Object nmsValue = getUnwrapped(value); + if (MinecraftReflection.watcherObjectExists()) { - return constructor.invoke(watcherObject.getHandle(), value); + return constructor.invoke(watcherObject.getHandle(), nmsValue); } else { // new WatchableObject(classId, index, value) - return constructor.invoke(WrappedDataWatcher.getTypeID(value.getClass()), watcherObject.getIndex(), value); + return constructor.invoke(WrappedDataWatcher.getTypeID(value.getClass()), watcherObject.getIndex(), nmsValue); } } diff --git a/src/test/java/com/comphenix/protocol/wrappers/WrappedDataWatcherTest.java b/src/test/java/com/comphenix/protocol/wrappers/WrappedDataWatcherTest.java index eac512cc9..91e6e5735 100644 --- a/src/test/java/com/comphenix/protocol/wrappers/WrappedDataWatcherTest.java +++ b/src/test/java/com/comphenix/protocol/wrappers/WrappedDataWatcherTest.java @@ -14,16 +14,23 @@ */ package com.comphenix.protocol.wrappers; -import java.util.UUID; +import java.util.List; +import java.util.Optional; import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry; -import com.comphenix.protocol.wrappers.WrappedDataWatcher.Serializer; import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject; +import com.comphenix.protocol.wrappers.nbt.NbtBase; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; +import com.comphenix.protocol.wrappers.nbt.NbtFactory; import net.minecraft.world.entity.projectile.EntityEgg; +import org.bukkit.Material; +import org.bukkit.Particle; import org.bukkit.craftbukkit.v1_20_R4.entity.CraftEgg; import org.bukkit.entity.Entity; +import org.bukkit.inventory.ItemStack; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -44,7 +51,7 @@ public static void prepare() { } @Test - public void testBytes() { + public void testFromEntity() { WrappedDataWatcher watcher = WrappedDataWatcher.getEntityWatcher(mockEntity); WrappedWatchableObject watchable = watcher.getWatchableObject(0); @@ -54,49 +61,160 @@ public void testBytes() { assertEquals(object.getSerializer(), Registry.get(Byte.class)); // Make sure we can set existing objects - watcher.setObject(0, (byte) 21); - assertEquals(21, (byte) watcher.getByte(0)); + watcher.setByte(0, (byte) 21, false); + assertEquals((byte) 21, watcher.getByte(0)); + + assertTrue(watcher.hasIndex(1)); + assertFalse(watcher.hasIndex(9999)); + } + + @Test + public void testPrimitives() { + WrappedDataWatcher watcher = new WrappedDataWatcher(); + + watcher.setByte(0, (byte) 21, false); + assertEquals((byte) 21, watcher.getByte(0)); + + watcher.setInteger(1, 37, false); + assertEquals(37, watcher.getInteger(1)); + + watcher.setFloat(2, 42.1F, false); + assertEquals(42.1F, watcher.getFloat(2)); + + watcher.setLong(3, 69L, false); + assertEquals(69L, watcher.getLong(3)); + + watcher.setBoolean(4, true, false); + assertTrue(watcher.getBoolean(4)); + + assertTrue(watcher.hasIndex(4)); + assertFalse(watcher.hasIndex(5)); + assertEquals(5, watcher.size()); + + List dataValues = watcher.toDataValueCollection(); + assertEquals(5, dataValues.size()); + + assertEquals((byte) 21, dataValues.get(0).getValue()); + assertEquals((byte) 21, dataValues.get(0).getRawValue()); + + assertEquals(37, dataValues.get(1).getValue()); + assertEquals(37, dataValues.get(1).getRawValue()); + + assertEquals(42.1F, dataValues.get(2).getValue()); + assertEquals(42.1F, dataValues.get(2).getRawValue()); + + assertEquals(69L, dataValues.get(3).getValue()); + assertEquals(69L, dataValues.get(3).getRawValue()); + + assertEquals(true, dataValues.get(4).getValue()); + assertEquals(true, dataValues.get(4).getRawValue()); } @Test public void testStrings() { WrappedDataWatcher watcher = WrappedDataWatcher.getEntityWatcher(mockEntity); - // Make sure we can create watcher objects - Serializer serializer = Registry.get(String.class); - WrappedDataWatcherObject object = new WrappedDataWatcherObject(3, serializer); - watcher.setObject(object, "Test"); + watcher.setString(0, "Test", false); - assertEquals(watcher.getString(3), "Test"); + assertEquals("Test", watcher.getString(0)); + + WrappedDataValue wdv = watcher.toDataValueCollection().get(0); + assertEquals("Test", wdv.getValue()); + assertEquals("Test", wdv.getRawValue()); + + assertEquals(0, wdv.getIndex()); + assertNotNull(wdv.getSerializer()); + assertFalse(wdv.getSerializer().isOptional()); } @Test - public void testFloats() { + public void testChatComponent() { WrappedDataWatcher wrapper = new WrappedDataWatcher(); + wrapper.setChatComponent(0, WrappedChatComponent.fromText("Test"), false); + wrapper.setOptionalChatComponent(1, Optional.of(WrappedChatComponent.fromText("Test")), false); + + WrappedChatComponent expected = WrappedChatComponent.fromText("Test"); + assertEquals(expected, wrapper.getChatComponent(0)); + assertTrue(wrapper.getOptionalChatComponent(1).isPresent()); + assertEquals(expected, wrapper.getOptionalChatComponent(1).get()); - // Make sure we can add new entries - Serializer serializer = Registry.get(Float.class); - WrappedDataWatcherObject object = new WrappedDataWatcherObject(10, serializer); - wrapper.setObject(object, 21.0F); + List wdvs = wrapper.toDataValueCollection(); + WrappedDataValue wdv1 = wdvs.get(0); + assertEquals(expected, wdv1.getValue()); - assertTrue(wrapper.hasIndex(10)); + WrappedDataValue wdv2 = wdvs.get(1); + assertEquals(expected, ((Optional) wdv2.getValue()).get()); + + assertEquals("literal{Test}", wdv1.getRawValue().toString()); } @Test - public void testSerializers() { - Serializer blockPos = Registry.get(net.minecraft.core.BlockPosition.class, false); - Serializer optionalBlockPos = Registry.get(net.minecraft.core.BlockPosition.class, true); - assertNotSame(blockPos, optionalBlockPos); + public void testItemStacks() { + WrappedDataWatcher wrapper = new WrappedDataWatcher(); + wrapper.setItemStack(0, new ItemStack(Material.ACACIA_FENCE), false); + assertEquals(Material.ACACIA_FENCE, wrapper.getItemStack(0).getType()); - // assertNull(Registry.get(ItemStack.class, false)); // TODO - assertNotNull(Registry.get(UUID.class, true)); + assertEquals(MinecraftReflection.getItemStackClass(), + wrapper.getWatchableObject(0).getRawValue().getClass()); + + WrappedDataValue wdv = wrapper.toDataValueCollection().get(0); + assertEquals(Material.ACACIA_FENCE, ((ItemStack) wdv.getValue()).getType()); + + Object raw = wdv.getRawValue(); + assertEquals(MinecraftReflection.getItemStackClass(), raw.getClass()); } @Test - public void testHasIndex() { - WrappedDataWatcher watcher = WrappedDataWatcher.getEntityWatcher(mockEntity); - assertTrue(watcher.hasIndex(1)); - assertFalse(watcher.hasIndex(9999)); + public void testMinecraftObjects() { + WrappedDataWatcher watcher = new WrappedDataWatcher(); + + watcher.setPosition(0, new BlockPosition(1, 2, 3), false); + assertEquals(new BlockPosition(1, 2, 3), watcher.getPosition(0)); + + watcher.setOptionalPosition(1, Optional.of(new BlockPosition(4, 5, 6)), false); + assertEquals(Optional.of(new BlockPosition(4, 5, 6)), watcher.getOptionalPosition(1)); + + watcher.setDirection(2, EnumWrappers.Direction.EAST, false); + assertEquals(EnumWrappers.Direction.EAST, watcher.getDirection(2)); + + watcher.setBlockState(3, WrappedBlockData.createData(Material.ACACIA_FENCE), false); + assertEquals(Material.ACACIA_FENCE, watcher.getBlockState(3).getType()); + + watcher.setOptionalBlockState(4, Optional.of(WrappedBlockData.createData(Material.ACACIA_FENCE)), false); + assertEquals(Optional.of(WrappedBlockData.createData(Material.ACACIA_FENCE)), watcher.getOptionalBlockState(4)); + + watcher.setParticle(5, WrappedParticle.create(Particle.CAMPFIRE_COSY_SMOKE, null), false); + assertEquals(Particle.CAMPFIRE_COSY_SMOKE, watcher.getParticle(5).getParticle()); + + watcher.setParticle(6, WrappedParticle.create(Particle.BLOCK, WrappedBlockData.createData(Material.AZALEA)), false); + assertEquals(Material.AZALEA, ((WrappedParticle) watcher.getParticle(6)).getData().getType()); + + assertEquals(7, watcher.size()); + + List wdvs = watcher.toDataValueCollection(); + assertEquals(7, wdvs.size()); + + assertEquals(new BlockPosition(1, 2, 3), wdvs.get(0).getValue()); + assertEquals(Optional.of(new BlockPosition(4, 5, 6)), wdvs.get(1).getValue()); + assertEquals(EnumWrappers.Direction.EAST, wdvs.get(2).getValue()); + assertEquals(Material.ACACIA_FENCE, ((WrappedBlockData) wdvs.get(3).getValue()).getType()); + assertEquals(Particle.CAMPFIRE_COSY_SMOKE, ((WrappedParticle) wdvs.get(5).getValue()).getParticle()); + } + + @Test + public void testNBT() { + WrappedDataWatcher watcher = new WrappedDataWatcher(); + + NbtBase nbt = NbtFactory.of("testTag", 17); + NbtCompound compound = NbtFactory.ofCompound("testCompound", List.of(nbt)); + watcher.setNBTCompound(0, compound, false); + + NbtCompound roundTrip = watcher.getNBTCompound(0); + assertEquals(17, roundTrip.getInteger("testTag")); + + List wdvs = watcher.toDataValueCollection(); + Object rawValue = wdvs.get(0).getRawValue(); + assertEquals(MinecraftReflection.getNBTCompoundClass(), rawValue.getClass()); } @Test @@ -105,6 +223,10 @@ public void testDeepClone() { WrappedDataWatcher cloned = watcher.deepClone(); assertEquals(watcher.size(), cloned.size()); - assertEquals(watcher.getObject(1), cloned.getObject(1)); + + int size = watcher.size(); + for (int i = 0; i < size; i++) { + assertEquals(watcher.getObject(i), cloned.getObject(i)); + } } }