diff --git a/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 3b665ad6..6c742a46 100644 --- a/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -923,7 +923,7 @@ public class PacketContainer implements Serializable { private static final boolean NEW_DIMENSIONS = MinecraftVersion.NETHER_UPDATE.atOrAbove(); /** - * Retrive a read/write structure for dimension IDs in 1.13.1+ + * Retrieve a read/write structure for dimension IDs in 1.13.1+ * @return A modifier for dimension IDs */ public StructureModifier getDimensions() { @@ -938,6 +938,17 @@ public class PacketContainer implements Serializable { ); } + /** + * Retrieve a read/write structure for ItemSlot/ItemStack pair lists in 1.16+ + * @return The Structure Modifier + */ + public StructureModifier>> getSlotStackPairLists() { + return getLists(BukkitConverters.getPairConverter( + EnumWrappers.getItemSlotConverter(), + BukkitConverters.getItemStackConverter() + )); + } + /** * Retrieve a read/write structure for the Map class. * @param keyConverter Converter for map keys diff --git a/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java b/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java index 092ff873..81a88c3f 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java +++ b/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java @@ -319,6 +319,36 @@ public class BukkitConverters { }); } + @SuppressWarnings("rawtypes") + public static EquivalentConverter> getPairConverter(final EquivalentConverter firstConverter, + final EquivalentConverter secondConverter) { + return ignoreNull(new EquivalentConverter>() { + @Override + public Object getGeneric(Pair specific) { + Object first = firstConverter.getGeneric(specific.getFirst()); + Object second = secondConverter.getGeneric(specific.getSecond()); + + return new com.mojang.datafixers.util.Pair(first, second); + } + + @Override + public Pair getSpecific(Object generic) { + com.mojang.datafixers.util.Pair mjPair = (com.mojang.datafixers.util.Pair) generic; + + A first = firstConverter.getSpecific(mjPair.getFirst()); + B second = secondConverter.getSpecific(mjPair.getSecond()); + + return new Pair(first, second); + } + + @Override + public Class> getSpecificType() { + Class dummy = Pair.class; + return (Class>) dummy; + } + }); + } + /** * @deprecated While this solution is not as abhorrent as I had imagined, I still highly recommend switching to the * new conversion API. diff --git a/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java b/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java index c8bb4f04..b290f70c 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java +++ b/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java @@ -1,9 +1,7 @@ package com.comphenix.protocol.wrappers; import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import com.comphenix.protocol.PacketType; @@ -151,14 +149,23 @@ public abstract class EnumWrappers { ENTITY_DIED } - public enum PlayerDigType { + public enum PlayerDigType implements AliasedEnum{ START_DESTROY_BLOCK, ABORT_DESTROY_BLOCK, STOP_DESTROY_BLOCK, DROP_ALL_ITEMS, DROP_ITEM, RELEASE_USE_ITEM, - SWAP_HELD_ITEMS + SWAP_HELD_ITEMS("SWAP_ITEM_WITH_OFFHAND"); + + String[] aliases; + PlayerDigType(String... aliases) { + this.aliases = aliases; + } + @Override + public String[] getAliases() { + return aliases; + } } public enum PlayerAction implements AliasedEnum { @@ -360,6 +367,45 @@ public abstract class EnumWrappers { return (byte) ordinal(); } } + + /** + * Wrapped EntityPose enum for use in Entity Metadata Packet.
+ * + * @apiNote Remember to use {@link #toNms()} when adding to a {@link WrappedDataWatcher}.
+ * Serializer is obtained using Registry.get(EnumWrappers.getEntityPoseClass()) + * @since 1.13 + * @author Lewys Davies (Lew_) + */ + public enum EntityPose { + STANDING, + FALL_FLYING, + SLEEPING, + SWIMMING, + SPIN_ATTACK, + CROUCHING, + DYING; + + private final static EquivalentConverter POSE_CONVERTER = EnumWrappers.getEntityPoseConverter(); + + /** + * @param nms net.minecraft.server.EntityPose Object + * @return Wrapped {@link EntityPose} + */ + public static EntityPose fromNms(Object nms) { + if(POSE_CONVERTER == null) { + throw new IllegalStateException("EntityPose is only available in Minecraft version 1.13 +"); + } + return POSE_CONVERTER.getSpecific(nms); + } + + /** @return net.minecraft.server.EntityPose enum equivalent to this wrapper enum */ + public Object toNms() { + if(POSE_CONVERTER == null) { + throw new IllegalStateException("EntityPose is only available in Minecraft version 1.13 +"); + } + return POSE_CONVERTER.getGeneric(this); + } + } public enum Dimension { OVERWORLD(0), @@ -406,10 +452,12 @@ public abstract class EnumWrappers { private static Class HAND_CLASS = null; private static Class DIRECTION_CLASS = null; private static Class CHAT_TYPE_CLASS = null; + private static Class ENTITY_POSE_CLASS = null; private static boolean INITIALIZED = false; private static Map, EquivalentConverter> FROM_NATIVE = Maps.newHashMap(); private static Map, EquivalentConverter> FROM_WRAPPER = Maps.newHashMap(); + static Set INVALID = new HashSet<>(); /** * Initialize the wrappers, if we haven't already. @@ -445,10 +493,18 @@ public abstract class EnumWrappers { SCOREBOARD_ACTION_CLASS = getEnum(PacketType.Play.Server.SCOREBOARD_SCORE.getPacketClass(), 0); PARTICLE_CLASS = getEnum(PacketType.Play.Server.WORLD_PARTICLES.getPacketClass(), 0); SOUND_CATEGORY_CLASS = getEnum(PacketType.Play.Server.CUSTOM_SOUND_EFFECT.getPacketClass(), 0); - ITEM_SLOT_CLASS = getEnum(PacketType.Play.Server.ENTITY_EQUIPMENT.getPacketClass(), 0); + + try { + // TODO enum names are more stable than their packet associations + ITEM_SLOT_CLASS = MinecraftReflection.getMinecraftClass("EnumItemSlot"); + } catch (Exception ex) { + ITEM_SLOT_CLASS = getEnum(PacketType.Play.Server.ENTITY_EQUIPMENT.getPacketClass(), 0); + } + HAND_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 1); DIRECTION_CLASS = getEnum(PacketType.Play.Server.SPAWN_ENTITY_PAINTING.getPacketClass(), 0); CHAT_TYPE_CLASS = getEnum(PacketType.Play.Server.CHAT.getPacketClass(), 0); + ENTITY_POSE_CLASS = MinecraftReflection.getNullableNMS("EntityPose"); associate(PROTOCOL_CLASS, Protocol.class, getClientCommandConverter()); associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter()); @@ -470,6 +526,11 @@ public abstract class EnumWrappers { associate(HAND_CLASS, Hand.class, getHandConverter()); associate(DIRECTION_CLASS, Direction.class, getDirectionConverter()); associate(CHAT_TYPE_CLASS, ChatType.class, getChatTypeConverter()); + + if (ENTITY_POSE_CLASS != null) { + associate(ENTITY_POSE_CLASS, EntityPose.class, getEntityPoseConverter()); + } + INITIALIZED = true; } @@ -477,6 +538,8 @@ public abstract class EnumWrappers { if (nativeClass != null) { FROM_NATIVE.put(nativeClass, converter); FROM_WRAPPER.put(wrapperClass, converter); + } else { + INVALID.add(wrapperClass.getSimpleName()); } } @@ -603,6 +666,11 @@ public abstract class EnumWrappers { initialize(); return CHAT_TYPE_CLASS; } + + public static Class getEntityPoseClass() { + initialize(); + return ENTITY_POSE_CLASS; + } // Get the converters public static EquivalentConverter getProtocolConverter() { @@ -650,7 +718,7 @@ public abstract class EnumWrappers { } public static EquivalentConverter getPlayerDiggingActionConverter() { - return new EnumConverter<>(getPlayerDigTypeClass(), PlayerDigType.class); + return new AliasedEnumConverter<>(getPlayerDigTypeClass(), PlayerDigType.class); } public static EquivalentConverter getEntityActionConverter() { @@ -684,6 +752,15 @@ public abstract class EnumWrappers { public static EquivalentConverter getChatTypeConverter() { return new EnumConverter<>(getChatTypeClass(), ChatType.class); } + + /** + * @since 1.13+ + * @return {@link EnumConverter} or null (if bellow 1.13 / nms EnumPose class cannot be found) + */ + public static EquivalentConverter getEntityPoseConverter() { + if(getEntityPoseClass() == null) return null; + return new EnumConverter<>(getEntityPoseClass(), EntityPose.class); + } /** * Retrieve a generic enum converter for use with StructureModifiers. diff --git a/src/main/java/com/comphenix/protocol/wrappers/Pair.java b/src/main/java/com/comphenix/protocol/wrappers/Pair.java new file mode 100644 index 00000000..6a466fca --- /dev/null +++ b/src/main/java/com/comphenix/protocol/wrappers/Pair.java @@ -0,0 +1,43 @@ +package com.comphenix.protocol.wrappers; + +import java.util.Objects; + +public class Pair { + private A first; + private B second; + + public Pair(A first, B second) { + this.first = first; + this.second = second; + } + + public A getFirst() { + return first; + } + + public B getSecond() { + return second; + } + + public void setFirst(A first) { + this.first = first; + } + + public void setSecond(B second) { + this.second = second; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Pair pair = (Pair) o; + return Objects.equals(first, pair.first) && + Objects.equals(second, pair.second); + } + + @Override + public int hashCode() { + return Objects.hash(first, second); + } +} diff --git a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java index e1142551..e95ebf9a 100644 --- a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java +++ b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -507,6 +507,20 @@ public class PacketContainerTest { assertEquals((Object) 1, container.getDimensions().read(0)); } + @Test + public void testEntityEquipment() { + PacketContainer container = new PacketContainer(PacketType.Play.Server.ENTITY_EQUIPMENT); + + List> data = new ArrayList<>(); + data.add(new Pair<>(EnumWrappers.ItemSlot.CHEST, new ItemStack(Material.NETHERITE_CHESTPLATE))); + data.add(new Pair<>(EnumWrappers.ItemSlot.LEGS, new ItemStack(Material.GOLDEN_LEGGINGS))); + + container.getSlotStackPairLists().write(0, data); + + List> written = container.getSlotStackPairLists().read(0); + assertEquals(data, written); + } + /** * Actions from the outbound Boss packet. Used for testing generic enums. * @author dmulloy2 diff --git a/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java b/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java index 6f49671c..e1a662e1 100644 --- a/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java +++ b/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java @@ -2,6 +2,7 @@ package com.comphenix.protocol.wrappers; import static org.junit.Assert.assertEquals; +import com.google.common.collect.Sets; import net.minecraft.server.v1_16_R1.EnumChatVisibility; import net.minecraft.server.v1_16_R1.EnumDifficulty; import net.minecraft.server.v1_16_R1.EnumGamemode; @@ -17,6 +18,10 @@ import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + public class EnumWrappersTest { private static class EnumClass { public EnumProtocol protocol; @@ -58,4 +63,11 @@ public class EnumWrappersTest { converter.getSpecific(accessor.get(target)) ); } + + private static final Set KNOWN_INVALID = Sets.newHashSet("Particle"); + + @Test + public void testValidity() { + assertEquals(EnumWrappers.INVALID, KNOWN_INVALID); + } }