diff --git a/modules/API/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/modules/API/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 06035825..fe892f61 100644 --- a/modules/API/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/modules/API/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -17,9 +17,6 @@ package com.comphenix.protocol.events; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.UnpooledByteBufAllocator; - import java.io.DataInput; import java.io.DataInputStream; import java.io.DataOutput; @@ -105,11 +102,16 @@ import com.comphenix.protocol.wrappers.WrappedServerPing; import com.comphenix.protocol.wrappers.WrappedStatistic; import com.comphenix.protocol.wrappers.WrappedWatchableObject; import com.comphenix.protocol.wrappers.nbt.NbtBase; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; +import com.comphenix.protocol.wrappers.nbt.NbtFactory; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.UnpooledByteBufAllocator; + /** * Represents a Minecraft packet indirectly. * @@ -659,15 +661,19 @@ public class PacketContainer implements Serializable { /** * Retrieves a read/write structure for arrays of chat components. *

- * This modifier will automatically marshall between WrappedChatComponent and the - * internal Minecraft IChatBaseComponent. + * This modifier will automatically marshal between WrappedChatComponent and the + * internal Minecraft IChatBaseComponent (1.9.2 and below) or the internal + * NBTCompound (1.9.4 and up). + *

+ * Note that in 1.9.4 and up this modifier only works properly with sign + * tile entities. * @return A modifier for ChatComponent array fields. */ public StructureModifier getChatComponentArrays() { // Convert to and from the Bukkit wrapper return structureModifier.withType( - MinecraftReflection.getIChatBaseComponentArrayClass(), - BukkitConverters.getIgnoreNull(new WrappedChatComponentArrayConverter())); + ComponentArrayConverter.getGenericType(), + BukkitConverters.getIgnoreNull(new ComponentArrayConverter())); } /** @@ -923,7 +929,7 @@ public class PacketContainer implements Serializable { */ public > StructureModifier getEnumModifier(Class enumClass, int index) { return getEnumModifier(enumClass, structureModifier.getField(index).getType()); - } + } /** * Retrieves the ID of this packet. @@ -1076,7 +1082,7 @@ public class PacketContainer implements Serializable { * Construct a new packet data serializer. * @return The packet data serializer. */ - private ByteBuf createPacketBuffer() { + public static ByteBuf createPacketBuffer() { ByteBuf buffer = UnpooledByteBufAllocator.DEFAULT.buffer(); Class packetSerializer = MinecraftReflection.getPacketDataSerializerClass(); @@ -1224,11 +1230,11 @@ public class PacketContainer implements Serializable { * Represents an equivalent converter for ChatComponent arrays. * @author dmulloy2 */ - private static class WrappedChatComponentArrayConverter implements EquivalentConverter { + private static class LegacyComponentConverter implements EquivalentConverter { final EquivalentConverter componentConverter = BukkitConverters.getWrappedChatComponentConverter(); @Override - public Object getGeneric(ClassgenericType, WrappedChatComponent[] specific) { + public Object getGeneric(Class genericType, WrappedChatComponent[] specific) { Class nmsComponent = MinecraftReflection.getIChatBaseComponentClass(); Object[] result = (Object[]) Array.newInstance(nmsComponent, specific.length); @@ -1257,6 +1263,94 @@ public class PacketContainer implements Serializable { } } + /** + * Converts from NBT to WrappedChatComponent arrays + * @author dmulloy2 + */ + private static class NBTComponentConverter implements EquivalentConverter { + private EquivalentConverter> nbtConverter = BukkitConverters.getNbtConverter(); + private final int lines = 4; + + @Override + public WrappedChatComponent[] getSpecific(Object generic) { + NbtBase nbtBase = nbtConverter.getSpecific(generic); + NbtCompound compound = (NbtCompound) nbtBase; + + WrappedChatComponent[] components = new WrappedChatComponent[lines]; + for (int i = 0; i < lines; i++) { + if (compound.containsKey("Text" + (i + 1))) { + components[i] = WrappedChatComponent.fromJson(compound.getString("Text" + (i + 1))); + } else { + components[i] = WrappedChatComponent.fromText(""); + } + } + + return components; + } + + @Override + public Object getGeneric(Class genericType, WrappedChatComponent[] specific) { + NbtCompound compound = NbtFactory.ofCompound(""); + + for (int i = 0; i < lines; i++) { + WrappedChatComponent component; + if (i < specific.length && specific[i] != null) { + component = specific[i]; + } else { + component = WrappedChatComponent.fromText(""); + } + + compound.put("Text" + (i + 1), component.getJson()); + } + + return nbtConverter.getGeneric(genericType, compound); + } + + @Override + public Class getSpecificType() { + return WrappedChatComponent[].class; + } + } + + /** + * A delegated converter that supports NBT to Component Array and regular Component Array + * @author dmulloy2 + */ + private static class ComponentArrayConverter implements EquivalentConverter { + private static final EquivalentConverter DELEGATE; + static { + Class packetClass = PacketType.Play.Server.UPDATE_SIGN.getPacketClass(); + if (packetClass.getName().contains("Sign")) { + DELEGATE = new LegacyComponentConverter(); + } else { + DELEGATE = new NBTComponentConverter(); + } + } + + @Override + public WrappedChatComponent[] getSpecific(Object generic) { + return DELEGATE.getSpecific(generic); + } + + @Override + public Object getGeneric(Class genericType, WrappedChatComponent[] specific) { + return DELEGATE.getGeneric(genericType, specific); + } + + @Override + public Class getSpecificType() { + return DELEGATE.getSpecificType(); + } + + public static Class getGenericType() { + if (DELEGATE instanceof NBTComponentConverter) { + return MinecraftReflection.getNBTCompoundClass(); + } else { + return MinecraftReflection.getIChatBaseComponentArrayClass(); + } + } + } + @Override public String toString() { return "PacketContainer[type=" + type + ", structureModifier=" + structureModifier + "]"; diff --git a/modules/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/modules/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java index 075ba5d9..ef238b7c 100644 --- a/modules/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java +++ b/modules/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -520,6 +520,19 @@ public class PacketContainerTest { UPDATE_PROPERTIES; } + @Test + public void testComponentArrays() { + PacketContainer signChange = new PacketContainer(PacketType.Play.Server.TILE_ENTITY_DATA); + WrappedChatComponent[] components = new WrappedChatComponent[] { + WrappedChatComponent.fromText("hello world"), WrappedChatComponent.fromText(""), + WrappedChatComponent.fromText(""), WrappedChatComponent.fromText("") + }; + signChange.getChatComponentArrays().write(0, components); + + WrappedChatComponent[] back = signChange.getChatComponentArrays().read(0); + assertArrayEquals(components, back); + } + private static final List BLACKLISTED = Util.asList( PacketType.Play.Client.CUSTOM_PAYLOAD, PacketType.Play.Server.CUSTOM_PAYLOAD, PacketType.Play.Server.SET_COOLDOWN