diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/PacketType.java b/ProtocolLib/src/main/java/com/comphenix/protocol/PacketType.java index 21b3cbae..ab5d6720 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/PacketType.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/PacketType.java @@ -94,7 +94,7 @@ public class PacketType implements Serializable { * @author Kristian */ public static class Play { - private static final Protocol PROTOCOL = Protocol.GAME; + private static final Protocol PROTOCOL = Protocol.PLAY; /** * Outgoing packets. @@ -445,7 +445,7 @@ public class PacketType implements Serializable { */ public enum Protocol { HANDSHAKING, - GAME, + PLAY, STATUS, LOGIN, @@ -465,7 +465,7 @@ public class PacketType implements Serializable { if ("HANDSHAKING".equals(name)) return HANDSHAKING; if ("PLAY".equals(name)) - return GAME; + return PLAY; if ("STATUS".equals(name)) return STATUS; if ("LOGIN".equals(name)) @@ -590,6 +590,11 @@ public class PacketType implements Serializable { /** * Retrieve a packet type from a protocol, sender and packet ID. + *

+ * It is usually better to access the packet types statically, like so: + *

* @param protocol - the current protocol. * @param sender - the sender. * @param packetId - the packet ID. @@ -645,7 +650,7 @@ public class PacketType implements Serializable { switch (type.getProtocol()) { case HANDSHAKING: objEnum = type.isClient() ? Handshake.Client.getInstance() : Handshake.Server.getInstance(); break; - case GAME: + case PLAY: objEnum = type.isClient() ? Play.Client.getInstance() : Play.Server.getInstance(); break; case STATUS: objEnum = type.isClient() ? Status.Client.getInstance() : Status.Server.getInstance(); break; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/PacketTypeLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/PacketTypeLookup.java index 35be5f89..38d1602f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/PacketTypeLookup.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/PacketTypeLookup.java @@ -31,7 +31,7 @@ class PacketTypeLookup { switch (protocol) { case HANDSHAKING: return sender == Sender.CLIENT ? HANDSHAKE_CLIENT : HANDSHAKE_SERVER; - case GAME: + case PLAY: return sender == Sender.CLIENT ? GAME_CLIENT : GAME_SERVER; case STATUS: return sender == Sender.CLIENT ? STATUS_CLIENT : STATUS_SERVER; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 75b7266b..5522f6e1 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -45,6 +45,7 @@ import org.bukkit.entity.Entity; import org.bukkit.inventory.ItemStack; import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.PacketType.Protocol; import com.comphenix.protocol.injector.StructureCache; import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FuzzyReflection; @@ -65,12 +66,18 @@ import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.StreamSerializer; import com.comphenix.protocol.wrappers.BukkitConverters; import com.comphenix.protocol.wrappers.ChunkPosition; +import com.comphenix.protocol.wrappers.EnumWrappers; +import com.comphenix.protocol.wrappers.EnumWrappers.Difficulty; import com.comphenix.protocol.wrappers.WrappedAttribute; import com.comphenix.protocol.wrappers.WrappedChatComponent; import com.comphenix.protocol.wrappers.WrappedDataWatcher; import com.comphenix.protocol.wrappers.WrappedGameProfile; import com.comphenix.protocol.wrappers.WrappedServerPing; import com.comphenix.protocol.wrappers.WrappedWatchableObject; +import com.comphenix.protocol.wrappers.EnumWrappers.ChatVisibility; +import com.comphenix.protocol.wrappers.EnumWrappers.ClientCommand; +import com.comphenix.protocol.wrappers.EnumWrappers.EntityUseAction; +import com.comphenix.protocol.wrappers.EnumWrappers.NativeGameMode; import com.comphenix.protocol.wrappers.nbt.NbtBase; import com.google.common.base.Function; import com.google.common.base.Preconditions; @@ -518,6 +525,66 @@ public class PacketContainer implements Serializable { MinecraftReflection.getServerPingClass(), BukkitConverters.getWrappedServerPingConverter()); } + /** + * Retrieve a read/write structure for the Protocol enum. + * @return A modifier for Protocol enum fields. + */ + public StructureModifier getProtocols() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getProtocolClass(), EnumWrappers.getProtocolConverter()); + } + + /** + * Retrieve a read/write structure for the ClientCommand enum. + * @return A modifier for ClientCommand enum fields. + */ + public StructureModifier getClientCommands() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getClientCommandClass(), EnumWrappers.getClientCommandConverter()); + } + + /** + * Retrieve a read/write structure for the ChatVisibility enum. + * @return A modifier for ChatVisibility enum fields. + */ + public StructureModifier getChatVisibilities() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getChatVisibilityClass(), EnumWrappers.getChatVisibilityConverter()); + } + + /** + * Retrieve a read/write structure for the Difficulty enum. + * @return A modifier for Difficulty enum fields. + */ + public StructureModifier getDifficulties() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getDifficultyClass(), EnumWrappers.getDifficultyConverter()); + } + + /** + * Retrieve a read/write structure for the EntityUse enum. + * @return A modifier for EntityUse enum fields. + */ + public StructureModifier getEntityUseActions() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getEntityUseActionClass(), EnumWrappers.getEntityUseActionConverter()); + } + + /** + * Retrieve a read/write structure for the NativeGameMode enum. + * @return A modifier for NativeGameMode enum fields. + */ + public StructureModifier getGamemodes() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getGameModeClass(), EnumWrappers.getGameModeConverter()); + } + /** * Retrieves the ID of this packet. *

diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java index 0def87d1..ffb26d27 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import org.bukkit.World; import org.bukkit.WorldType; @@ -34,6 +35,7 @@ import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.PacketType.Protocol; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.injector.PacketConstructor; @@ -584,6 +586,8 @@ public class BukkitConverters { }; } + + /** * Retrieve the converter used to convert between a PotionEffect and the equivalent NMS Mobeffect. * @return The potion effect converter. @@ -706,6 +710,11 @@ public class BukkitConverters { if (MinecraftReflection.isUsingNetty()) { builder.put(WrappedGameProfile.class, (EquivalentConverter) getWrappedGameProfileConverter()); builder.put(WrappedChatComponent.class, (EquivalentConverter) getWrappedChatComponentConverter()); + builder.put(WrappedServerPing.class, (EquivalentConverter) getWrappedServerPingConverter()); + + for (Entry, EquivalentConverter> entry : EnumWrappers.getFromWrapperMap().entrySet()) { + builder.put((Class) entry.getKey(), (EquivalentConverter) entry.getValue()); + } } if (hasWorldType) @@ -743,6 +752,11 @@ public class BukkitConverters { if (MinecraftReflection.isUsingNetty()) { builder.put(MinecraftReflection.getGameProfileClass(), (EquivalentConverter) getWrappedGameProfileConverter()); builder.put(MinecraftReflection.getIChatBaseComponentClass(), (EquivalentConverter) getWrappedChatComponentConverter()); + builder.put(MinecraftReflection.getServerPingClass(), (EquivalentConverter) getWrappedServerPingConverter()); + + for (Entry, EquivalentConverter> entry : EnumWrappers.getFromNativeMap().entrySet()) { + builder.put((Class) entry.getKey(), (EquivalentConverter) entry.getValue()); + } } genericConverters = builder.build(); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java new file mode 100644 index 00000000..54c178b4 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java @@ -0,0 +1,183 @@ +package com.comphenix.protocol.wrappers; + +import java.util.Map; + +import org.bukkit.GameMode; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.PacketType.Protocol; +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.google.common.collect.Maps; + +/** + * Represents a generic enum converter. + * @author Kristian + */ +public abstract class EnumWrappers { + public enum ClientCommand { + PERFORM_RESPAWN, + REQUEST_STATS, + OPEN_INVENTORY_ACHIEVEMENT; + } + + public enum ChatVisibility { + FULL, + SYSTEM, + HIDDEN; + } + + public enum Difficulty { + PEACEFUL, + EASY, + NORMAL, + HARD; + } + + public enum EntityUseAction { + INTERACT, + ATTACK; + } + + /** + * Represents a native game mode in Minecraft. + *

+ * Not to be confused with {@link GameMode} in Bukkit. + * @author Kristian + */ + public enum NativeGameMode { + NONE, + SURVIVAL, + CREATIVE, + ADVENTURE; + } + + private static Class PROTOCOL_CLASS = null; + private static Class CLIENT_COMMAND_CLASS = null; + private static Class CHAT_VISIBILITY_CLASS = null; + private static Class DIFFICULTY_CLASS = null; + private static Class ENTITY_USE_ACTION_CLASS = null; + private static Class GAMEMODE_CLASS = null; + + private static Map, EquivalentConverter> FROM_NATIVE = Maps.newHashMap(); + private static Map, EquivalentConverter> FROM_WRAPPER = Maps.newHashMap(); + + /** + * Initialize the wrappers, if we haven't already. + */ + private static boolean initialize() { + if (MinecraftReflection.isUsingNetty()) + return false; + + PROTOCOL_CLASS = getEnum(PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass(), 0); + CLIENT_COMMAND_CLASS = getEnum(PacketType.Play.Client.CLIENT_COMMAND.getPacketClass(), 0); + CHAT_VISIBILITY_CLASS = getEnum(PacketType.Play.Client.SETTINGS.getPacketClass(), 0); + DIFFICULTY_CLASS = getEnum(PacketType.Play.Client.SETTINGS.getPacketClass(), 1); + ENTITY_USE_ACTION_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 0); + GAMEMODE_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 0); + + associate(PROTOCOL_CLASS, Protocol.class, getClientCommandConverter()); + associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter()); + associate(CHAT_VISIBILITY_CLASS, ChatVisibility.class, getChatVisibilityConverter()); + associate(DIFFICULTY_CLASS, Difficulty.class, getDifficultyConverter()); + associate(ENTITY_USE_ACTION_CLASS, EntityUseAction.class, getEntityUseActionConverter()); + associate(GAMEMODE_CLASS, NativeGameMode.class, getGameModeConverter()); + return true; + } + + private static void associate(Class nativeClass, Class wrapperClass, EquivalentConverter converter) { + FROM_NATIVE.put(nativeClass, converter); + FROM_WRAPPER.put(wrapperClass, converter); + } + + /** + * Retrieve the enum field with the given declaration index (in relation to the other enums). + * @param clazz - the declaration class. + * @param index - the enum index. + * @return The type of the enum field. + */ + private static Class getEnum(Class clazz, int index) { + return FuzzyReflection.fromClass(clazz, true).getFieldListByType(Enum.class).get(index).getType(); + } + + public static Map, EquivalentConverter> getFromNativeMap() { + return FROM_NATIVE; + } + + public static Map, EquivalentConverter> getFromWrapperMap() { + return FROM_WRAPPER; + } + + // Get the native enum classes + public static Class getProtocolClass() { + initialize(); + return PROTOCOL_CLASS; + } + public static Class getClientCommandClass() { + initialize(); + return CLIENT_COMMAND_CLASS; + } + public static Class getChatVisibilityClass() { + initialize(); + return CHAT_VISIBILITY_CLASS; + } + public static Class getDifficultyClass() { + initialize(); + return DIFFICULTY_CLASS; + } + public static Class getEntityUseActionClass() { + initialize(); + return ENTITY_USE_ACTION_CLASS; + } + public static Class getGameModeClass() { + initialize(); + return GAMEMODE_CLASS; + } + + // Get the converters + public static EquivalentConverter getProtocolConverter() { + return new EnumConverter(Protocol.class); + } + public static EquivalentConverter getClientCommandConverter() { + return new EnumConverter(ClientCommand.class); + } + public static EquivalentConverter getChatVisibilityConverter() { + return new EnumConverter(ChatVisibility.class); + } + public static EquivalentConverter getDifficultyConverter() { + return new EnumConverter(Difficulty.class); + } + public static EquivalentConverter getEntityUseActionConverter() { + return new EnumConverter(EntityUseAction.class); + } + public static EquivalentConverter getGameModeConverter() { + return new EnumConverter(NativeGameMode.class); + } + + // The common enum converter + @SuppressWarnings({"rawtypes", "unchecked"}) + private static class EnumConverter> implements EquivalentConverter { + private Class specificType; + + public EnumConverter(Class specificType) { + this.specificType = specificType; + } + + @Override + public T getSpecific(Object generic) { + // We know its an enum already! + return Enum.valueOf(specificType, ((Enum) generic).name()); + } + + @Override + public Object getGeneric(Class genericType, T specific) { + return Enum.valueOf((Class) genericType, specific.name()); + } + + @Override + public Class getSpecificType() { + return specificType; + } + } +} diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java new file mode 100644 index 00000000..3e1aae09 --- /dev/null +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java @@ -0,0 +1,62 @@ +package com.comphenix.protocol.wrappers; + +import static org.junit.Assert.*; + +import net.minecraft.server.v1_7_R1.EnumChatVisibility; +import net.minecraft.server.v1_7_R1.EnumClientCommand; +import net.minecraft.server.v1_7_R1.EnumDifficulty; +import net.minecraft.server.v1_7_R1.EnumEntityUseAction; +import net.minecraft.server.v1_7_R1.EnumGamemode; +import net.minecraft.server.v1_7_R1.EnumProtocol; + +import org.junit.BeforeClass; +import org.junit.Test; + +import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; + +public class EnumWrappersTest { + private static class EnumClass { + public EnumProtocol protocol; + public EnumClientCommand command; + public EnumChatVisibility visibility; + public EnumDifficulty difficulty; + public EnumEntityUseAction action; + public EnumGamemode mode; + } + + @BeforeClass + public static void initializeBukkit() throws IllegalAccessException { + BukkitInitialization.initializePackage(); + } + + @Test + public void testEnum() { + EnumClass obj = new EnumClass(); + obj.protocol = EnumProtocol.LOGIN; + obj.command = EnumClientCommand.PERFORM_RESPAWN; + obj.visibility = EnumChatVisibility.FULL; + obj.difficulty = EnumDifficulty.PEACEFUL; + obj.action = EnumEntityUseAction.INTERACT; + obj.mode = EnumGamemode.CREATIVE; + + assertEquals(obj.protocol, roundtrip(obj, "protocol", EnumWrappers.getProtocolConverter()) ); + assertEquals(obj.command, roundtrip(obj, "command", EnumWrappers.getClientCommandConverter()) ); + assertEquals(obj.visibility, roundtrip(obj, "visibility", EnumWrappers.getChatVisibilityConverter()) ); + assertEquals(obj.difficulty, roundtrip(obj, "difficulty", EnumWrappers.getDifficultyConverter()) ); + assertEquals(obj.action, roundtrip(obj, "action", EnumWrappers.getEntityUseActionConverter()) ); + assertEquals(obj.mode, roundtrip(obj, "mode", EnumWrappers.getGameModeConverter()) ); + } + + @SuppressWarnings("unchecked") + public > T roundtrip(Object target, String fieldName, EquivalentConverter converter) { + FieldAccessor accessor = Accessors.getFieldAccessor(target.getClass(), fieldName, true); + + return (T) converter.getGeneric( + accessor.getField().getType(), + converter.getSpecific(accessor.get(target)) + ); + } +}