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(Class>genericType, 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