diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ClassAnalyser.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ClassAnalyser.java index 5d72c54f..3f9049fc 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ClassAnalyser.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ClassAnalyser.java @@ -4,25 +4,50 @@ import java.io.IOException; import java.lang.reflect.Method; import java.util.List; +import com.comphenix.protocol.reflect.ClassAnalyser.AsmMethod.AsmOpcodes; import com.comphenix.protocol.reflect.compiler.EmptyClassVisitor; import com.comphenix.protocol.reflect.compiler.EmptyMethodVisitor; import com.google.common.collect.Lists; import net.sf.cglib.asm.ClassReader; import net.sf.cglib.asm.MethodVisitor; +import net.sf.cglib.asm.Opcodes; import net.sf.cglib.asm.Type; public class ClassAnalyser { /** * Represents a method in ASM. + *
+ * Keep in mind that this may also invoke a constructor. * @author Kristian */ public static class AsmMethod { + public enum AsmOpcodes { + INVOKE_VIRTUAL, + INVOKE_SPECIAL, + INVOKE_STATIC, + INVOKE_INTERFACE, + INVOKE_DYNAMIC; + + public static AsmOpcodes fromIntOpcode(int opcode) { + switch (opcode) { + case Opcodes.INVOKEVIRTUAL: return AsmOpcodes.INVOKE_VIRTUAL; + case Opcodes.INVOKESPECIAL: return AsmOpcodes.INVOKE_SPECIAL; + case Opcodes.INVOKESTATIC: return AsmOpcodes.INVOKE_STATIC; + case Opcodes.INVOKEINTERFACE: return AsmOpcodes.INVOKE_INTERFACE; + case Opcodes.INVOKEDYNAMIC: return AsmOpcodes.INVOKE_DYNAMIC; + default: throw new IllegalArgumentException("Unknown opcode: " + opcode); + } + } + } + + private final AsmOpcodes opcode; private final String ownerClass; private final String methodName; private final String signature; - public AsmMethod(String ownerClass, String methodName, String signature) { + public AsmMethod(AsmOpcodes opcode, String ownerClass, String methodName, String signature) { + this.opcode = opcode; this.ownerClass = ownerClass; this.methodName = methodName; this.signature = signature; @@ -32,6 +57,14 @@ public class ClassAnalyser { return ownerClass; } + /** + * Retrieve the opcode used to invoke this method or constructor. + * @return The opcode. + */ + public AsmOpcodes getOpcode() { + return opcode; + } + /** * Retrieve the associated owner class. * @return The owner class. @@ -92,7 +125,7 @@ public class ClassAnalyser { return new EmptyMethodVisitor() { @Override public void visitMethodInsn(int opcode, String owner, String name, String desc) { - output.add(new AsmMethod(owner, name, desc)); + output.add(new AsmMethod(AsmOpcodes.fromIntOpcode(opcode), owner, methodName, desc)); } }; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index dfd0b7de..569695bb 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -670,6 +670,33 @@ public class MinecraftReflection { } } + /** + * Retrieve the NMS chat component text class. + * @return The chat component class. + */ + public static Class> getChatComponentTextClass() { + try { + return getMinecraftClass("ChatComponentText"); + } catch (RuntimeException e) { + try { + Method getScoreboardDisplayName = FuzzyReflection.fromClass(getEntityClass()). + getMethodByParameters("getScoreboardDisplayName", getIChatBaseComponentClass(), new Class>[0]); + Class> baseClass = getIChatBaseComponentClass(); + + for (AsmMethod method : ClassAnalyser.getDefault().getMethodCalls(getScoreboardDisplayName)) { + Class> owner = method.getOwnerClass(); + + if (isMinecraftClass(owner) && baseClass.isAssignableFrom(owner)) { + return setMinecraftClass("ChatComponentText", owner); + } + } + } catch (Exception e1) { + throw new IllegalStateException("Cannot find ChatComponentText class.", e); + } + } + throw new IllegalStateException("Cannot find ChatComponentText class."); + } + /** * Attempt to find the ChatSerializer class. * @return The serializer class. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChatComponent.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChatComponent.java index 00567d97..43870b3f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChatComponent.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChatComponent.java @@ -4,8 +4,10 @@ import org.bukkit.ChatColor; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.utility.MinecraftReflection; +import com.google.common.base.Preconditions; /** * Represents a chat component added in Minecraft 1.7.2 @@ -17,6 +19,7 @@ public class WrappedChatComponent extends AbstractWrapper { private static MethodAccessor SERIALIZE_COMPONENT = null; private static MethodAccessor DESERIALIZE_COMPONENT = null; private static MethodAccessor CONSTRUCT_COMPONENT = null; + private static ConstructorAccessor CONSTRUCT_TEXT_COMPONENT = null; static { FuzzyReflection fuzzy = FuzzyReflection.fromClass(SERIALIZER); @@ -30,6 +33,10 @@ public class WrappedChatComponent extends AbstractWrapper { // Get a component from a standard Minecraft message CONSTRUCT_COMPONENT = Accessors.getMethodAccessor( MinecraftReflection.getCraftChatMessage(), "fromString", String.class); + + // And the component text constructor + CONSTRUCT_TEXT_COMPONENT = Accessors.getConstructorAccessor( + MinecraftReflection.getChatComponentTextClass(), String.class); } private transient String cache; @@ -58,6 +65,16 @@ public class WrappedChatComponent extends AbstractWrapper { return new WrappedChatComponent(DESERIALIZE_COMPONENT.invoke(null, json), json); } + /** + * Construct a wrapper around a new text chat component with the given text. + * @param text - the text of the text chat component. + * @return The wrapper around the new chat component. + */ + public static WrappedChatComponent fromText(String text) { + Preconditions.checkNotNull(text, "text cannot be NULL."); + return fromHandle(CONSTRUCT_TEXT_COMPONENT.invoke(text)); + } + /** * Construct an array of chat components from a standard Minecraft message. *
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedServerPing.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedServerPing.java index 7ed52bb2..a5283a6c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedServerPing.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedServerPing.java @@ -134,13 +134,11 @@ public class WrappedServerPing extends AbstractWrapper { } /** - * Set the message of the day. - *
- * Warning: Only the first line will be transmitted. + * Set the message of the day. * @param description - the message. */ public void setMotD(String message) { - setMotD(WrappedChatComponent.fromChatMessage(message)[0]); + setMotD(WrappedChatComponent.fromText(message)); } /** diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java index 457c4ed3..43aceb7f 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java @@ -2,6 +2,7 @@ package com.comphenix.protocol.utility; import static org.junit.Assert.*; +import net.minecraft.server.v1_7_R1.ChatComponentText; import net.minecraft.server.v1_7_R1.ChatSerializer; import net.minecraft.server.v1_7_R1.ChunkCoordIntPair; import net.minecraft.server.v1_7_R1.IChatBaseComponent; @@ -44,6 +45,11 @@ public class MinecraftReflectionTest { assertEquals(IChatBaseComponent.class, MinecraftReflection.getIChatBaseComponentClass()); } + @Test + public void testChatComponentText() { + assertEquals(ChatComponentText.class, MinecraftReflection.getChatComponentTextClass()); + } + @Test public void testChatSerializer() { assertEquals(ChatSerializer.class, MinecraftReflection.getChatSerializerClass()); diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/WrappedChatComponentTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/WrappedChatComponentTest.java new file mode 100644 index 00000000..c5b65c6f --- /dev/null +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/WrappedChatComponentTest.java @@ -0,0 +1,23 @@ +package com.comphenix.protocol.wrappers; + +import static org.junit.Assert.*; + +import org.junit.BeforeClass; +import org.junit.Test; + +import com.comphenix.protocol.BukkitInitialization; + +public class WrappedChatComponentTest { + @BeforeClass + public static void initializeBukkit() throws IllegalAccessException { + BukkitInitialization.initializePackage(); + } + + @Test + public void testText() { + WrappedChatComponent test = WrappedChatComponent.fromText("Hello."); + String json = test.getJson(); + + assertNotNull(json); + } +}