From 33a8615aad4096b34cdb43cbf6785a2c8e76ac82 Mon Sep 17 00:00:00 2001 From: Jitse Boonstra Date: Sun, 12 Apr 2020 17:01:18 +0200 Subject: [PATCH] Added support for LabyMod EmoteAPI. --- api/pom.xml | 2 +- .../main/java/net/jitse/npclib/api/NPC.java | 16 +- .../net/jitse/npclib/internal/NPCBase.java | 21 ++- .../java/net/labymod/utilities/LMCUtils.java | 152 ++++++++++++++++++ nms/pom.xml | 2 +- nms/v1_10_R1/pom.xml | 2 +- nms/v1_11_R1/pom.xml | 2 +- nms/v1_12_R1/pom.xml | 2 +- nms/v1_13_R1/pom.xml | 2 +- nms/v1_13_R2/pom.xml | 2 +- nms/v1_14_R1/pom.xml | 2 +- nms/v1_15_R1/pom.xml | 2 +- nms/v1_8_R2/pom.xml | 2 +- nms/v1_8_R3/pom.xml | 2 +- nms/v1_9_R1/pom.xml | 2 +- nms/v1_9_R2/pom.xml | 2 +- plugin/pom.xml | 2 +- pom.xml | 2 +- 18 files changed, 198 insertions(+), 21 deletions(-) create mode 100644 api/src/main/java/net/labymod/utilities/LMCUtils.java diff --git a/api/pom.xml b/api/pom.xml index 45cec94..6f746de 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -8,7 +8,7 @@ npclib net.jitse - 2.4.2-SNAPSHOT + 2.5-SNAPSHOT npclib-api diff --git a/api/src/main/java/net/jitse/npclib/api/NPC.java b/api/src/main/java/net/jitse/npclib/api/NPC.java index 4caae2b..c3db5ac 100644 --- a/api/src/main/java/net/jitse/npclib/api/NPC.java +++ b/api/src/main/java/net/jitse/npclib/api/NPC.java @@ -101,7 +101,7 @@ public interface NPC { * @return Object instance. */ NPC toggleState(NPCState state); - + /** * Get state of NPC. * @@ -120,14 +120,14 @@ public interface NPC { NPC setItem(NPCSlot slot, ItemStack item); NPC setText(List text); - + /** * Get the text of an NPC * * @return List text */ List getText(); - + /** * Get a NPC's item. * @@ -135,4 +135,14 @@ public interface NPC { * @return ItemStack item. */ ItemStack getItem(NPCSlot slot); + + /** + * LABYMOD ONLY + * Let the NPC play an emote. + * (https://docs.labymod.net/pages/server/emote_api/) + * + * @param receiver The player who should see the emote. + * @param emoteId The emote id (see link). + */ + void forceLabyModEmote(Player receiver, int emoteId); } diff --git a/api/src/main/java/net/jitse/npclib/internal/NPCBase.java b/api/src/main/java/net/jitse/npclib/internal/NPCBase.java index 48347e7..58c50b1 100644 --- a/api/src/main/java/net/jitse/npclib/internal/NPCBase.java +++ b/api/src/main/java/net/jitse/npclib/internal/NPCBase.java @@ -4,6 +4,8 @@ package net.jitse.npclib.internal; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; import net.jitse.npclib.NPCLib; @@ -14,6 +16,7 @@ import net.jitse.npclib.api.skin.Skin; import net.jitse.npclib.api.state.NPCSlot; import net.jitse.npclib.api.state.NPCState; import net.jitse.npclib.hologram.Hologram; +import net.labymod.utilities.LMCUtils; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; @@ -25,10 +28,7 @@ import java.util.*; public abstract class NPCBase implements NPC, NPCPacketHandler { - protected final UUID uuid = UUID.randomUUID(); protected final int entityId = Integer.MAX_VALUE - NPCManager.getAllNPCs().size(); - protected final String name = uuid.toString().replace("-", "").substring(0, 10); - protected final GameProfile gameProfile = new GameProfile(uuid, name); protected final Set hasTeamRegistered = new HashSet<>(); protected final Set activeStates = EnumSet.noneOf(NPCState.class); @@ -36,6 +36,11 @@ public abstract class NPCBase implements NPC, NPCPacketHandler { private final Set autoHidden = new HashSet<>(); protected double cosFOV = Math.cos(Math.toRadians(60)); + // 12/4/20, JMB: Changed the UUID in order to enable LabyMod Emotes: + // This gives a format similar to: 528086a2-4f5f-2ec2-0000-000000000000 + protected UUID uuid = new UUID(new Random().nextLong(), 0); + protected String name = uuid.toString().replace("-", "").substring(0, 10); + protected GameProfile gameProfile = new GameProfile(uuid, name); protected NPCLib instance; protected List text; @@ -86,6 +91,16 @@ public abstract class NPCBase implements NPC, NPCPacketHandler { } } + @Override + public void forceLabyModEmote(Player receiver, int emoteId) { + JsonArray array = new JsonArray(); + JsonObject forcedEmote = new JsonObject(); + forcedEmote.addProperty("uuid", uuid.toString()); + forcedEmote.addProperty("emote_id", emoteId); + array.add(forcedEmote); + LMCUtils.sendLMCMessage(receiver, "emote_api", array.getAsJsonObject()); + } + public void disableFOV() { this.cosFOV = 0; // Or equals Math.cos(1/2 * Math.PI). } diff --git a/api/src/main/java/net/labymod/utilities/LMCUtils.java b/api/src/main/java/net/labymod/utilities/LMCUtils.java new file mode 100644 index 0000000..9320905 --- /dev/null +++ b/api/src/main/java/net/labymod/utilities/LMCUtils.java @@ -0,0 +1,152 @@ +package net.labymod.utilities; + +import com.comphenix.tinyprotocol.Reflection; +import com.google.gson.JsonObject; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.EncoderException; +import org.bukkit.entity.Player; + +import java.nio.charset.Charset; + +public class LMCUtils { + + private static final Class CRAFT_PLAYER_CLASS = Reflection.getCraftBukkitClass("CraftPlayer"); + private static final Reflection.MethodInvoker GET_HANDLE_METHOD = Reflection.getMethod(CRAFT_PLAYER_CLASS, "getHandle"); + private static final Class PLAYER_CONNECTION_CLASS = Reflection.getMinecraftClass("PlayerConnection"); + private static final Reflection.FieldAccessor PLAYER_CONNECTION_FIELD = Reflection.getField(Reflection.getMinecraftClass("EntityPlayer"), + "playerConnection", PLAYER_CONNECTION_CLASS); + private static final Reflection.MethodInvoker SEND_PACKET_METHOD = Reflection.getMethod(PLAYER_CONNECTION_CLASS, + "sendPacket", Reflection.getMinecraftClass("Packet")); + private static final Class PACKET_DATA_SERIALIZER_CLASS = Reflection.getMinecraftClass("PacketDataSerializer"); + private static final Reflection.ConstructorInvoker PACKET_DATA_SERIALIZER_CONSTRUCTOR = Reflection.getConstructor( + PACKET_DATA_SERIALIZER_CLASS, ByteBuf.class); + private static final Reflection.ConstructorInvoker PACKET_PLAY_OUT_CUSTOM_PAYLOAD_CONSTRUCTOR = Reflection.getConstructor( + Reflection.getMinecraftClass("PacketPlayOutCustomPayload"), String.class, PACKET_DATA_SERIALIZER_CLASS); + + /** + * Send a LMC message to the minecraft client + * + * @param player Minecraft Client + * @param key LMC message key + * @param messageContent json object content + */ + public static void sendLMCMessage(Player player, String key, JsonObject messageContent) { + byte[] bytes = LMCUtils.getBytesToSend(key, messageContent.toString()); + + // 12/4/20, JMB: Converted into reflections for multi-version support: +// PacketDataSerializer pds = new PacketDataSerializer(Unpooled.wrappedBuffer(bytes)); +// PacketPlayOutCustomPayload payloadPacket = new PacketPlayOutCustomPayload("LMC", pds); +// ((CraftPlayer) player).getHandle().playerConnection.sendPacket(payloadPacket); + Object pds = PACKET_DATA_SERIALIZER_CONSTRUCTOR.invoke(Unpooled.wrappedBuffer(bytes)); + Object payloadPacket = PACKET_PLAY_OUT_CUSTOM_PAYLOAD_CONSTRUCTOR.invoke("LMC", pds); + SEND_PACKET_METHOD.invoke(PLAYER_CONNECTION_FIELD.get(GET_HANDLE_METHOD.invoke(CRAFT_PLAYER_CLASS.cast(player))), payloadPacket); + } + + /** + * Gets the bytes that are required to send the given message + * + * @param messageKey the message's key + * @param messageContents the message's contents + * @return the byte array that should be the payload + */ + public static byte[] getBytesToSend(String messageKey, String messageContents) { + // Getting an empty buffer + ByteBuf byteBuf = Unpooled.buffer(); + + // Writing the message-key to the buffer + writeString(byteBuf, messageKey); + + // Writing the contents to the buffer + writeString(byteBuf, messageContents); + + // Copying the buffer's bytes to the byte array + byte[] bytes = new byte[byteBuf.readableBytes()]; + byteBuf.readBytes(bytes); + + // Returning the byte array + return bytes; + } + + /** + * Writes a varint to the given byte buffer + * + * @param buf the byte buffer the int should be written to + * @param input the int that should be written to the buffer + */ + private static void writeVarIntToBuffer(ByteBuf buf, int input) { + while ((input & -128) != 0) { + buf.writeByte(input & 127 | 128); + input >>>= 7; + } + + buf.writeByte(input); + } + + /** + * Writes a string to the given byte buffer + * + * @param buf the byte buffer the string should be written to + * @param string the string that should be written to the buffer + */ + private static void writeString(ByteBuf buf, String string) { + byte[] abyte = string.getBytes(Charset.forName("UTF-8")); + + if (abyte.length > Short.MAX_VALUE) { + throw new EncoderException("String too big (was " + string.length() + " bytes encoded, max " + Short.MAX_VALUE + ")"); + } else { + writeVarIntToBuffer(buf, abyte.length); + buf.writeBytes(abyte); + } + } + + /** + * Reads a varint from the given byte buffer + * + * @param buf the byte buffer the varint should be read from + * @return the int read + */ + public static int readVarIntFromBuffer(ByteBuf buf) { + int i = 0; + int j = 0; + + byte b0; + do { + b0 = buf.readByte(); + i |= (b0 & 127) << j++ * 7; + if (j > 5) { + throw new RuntimeException("VarInt too big"); + } + } while ((b0 & 128) == 128); + + return i; + } + + /** + * Reads a string from the given byte buffer + * + * @param buf the byte buffer the string should be read from + * @param maxLength the string's max-length + * @return the string read + */ + public static String readString(ByteBuf buf, int maxLength) { + int i = readVarIntFromBuffer(buf); + + if (i > maxLength * 4) { + throw new DecoderException("The received encoded string buffer length is longer than maximum allowed (" + i + " > " + maxLength * 4 + ")"); + } else if (i < 0) { + throw new DecoderException("The received encoded string buffer length is less than zero! Weird string!"); + } else { + byte[] bytes = new byte[i]; + buf.readBytes(bytes); + + String s = new String(bytes, Charset.forName("UTF-8")); + if (s.length() > maxLength) { + throw new DecoderException("The received string length is longer than maximum allowed (" + i + " > " + maxLength + ")"); + } else { + return s; + } + } + } +} \ No newline at end of file diff --git a/nms/pom.xml b/nms/pom.xml index 6009070..159bd4c 100644 --- a/nms/pom.xml +++ b/nms/pom.xml @@ -8,7 +8,7 @@ net.jitse npclib - 2.4.2-SNAPSHOT + 2.5-SNAPSHOT npclib-nms diff --git a/nms/v1_10_R1/pom.xml b/nms/v1_10_R1/pom.xml index 72f34db..9f81f64 100755 --- a/nms/v1_10_R1/pom.xml +++ b/nms/v1_10_R1/pom.xml @@ -8,7 +8,7 @@ net.jitse npclib-nms - 2.4.2-SNAPSHOT + 2.5-SNAPSHOT npclib-nms-v1_10_R1 diff --git a/nms/v1_11_R1/pom.xml b/nms/v1_11_R1/pom.xml index 35f571d..cbdf03b 100755 --- a/nms/v1_11_R1/pom.xml +++ b/nms/v1_11_R1/pom.xml @@ -8,7 +8,7 @@ net.jitse npclib-nms - 2.4.2-SNAPSHOT + 2.5-SNAPSHOT npclib-nms-v1_11_R1 diff --git a/nms/v1_12_R1/pom.xml b/nms/v1_12_R1/pom.xml index 4c17ae7..d0e6915 100755 --- a/nms/v1_12_R1/pom.xml +++ b/nms/v1_12_R1/pom.xml @@ -8,7 +8,7 @@ net.jitse npclib-nms - 2.4.2-SNAPSHOT + 2.5-SNAPSHOT npclib-nms-v1_12_R1 diff --git a/nms/v1_13_R1/pom.xml b/nms/v1_13_R1/pom.xml index 5d3e4ca..9a7f215 100755 --- a/nms/v1_13_R1/pom.xml +++ b/nms/v1_13_R1/pom.xml @@ -8,7 +8,7 @@ net.jitse npclib-nms - 2.4.2-SNAPSHOT + 2.5-SNAPSHOT npclib-nms-v1_13_R1 diff --git a/nms/v1_13_R2/pom.xml b/nms/v1_13_R2/pom.xml index 61f707d..e1f863f 100755 --- a/nms/v1_13_R2/pom.xml +++ b/nms/v1_13_R2/pom.xml @@ -8,7 +8,7 @@ net.jitse npclib-nms - 2.4.2-SNAPSHOT + 2.5-SNAPSHOT npclib-nms-v1_13_R2 diff --git a/nms/v1_14_R1/pom.xml b/nms/v1_14_R1/pom.xml index 9c2dc1f..6f4188d 100755 --- a/nms/v1_14_R1/pom.xml +++ b/nms/v1_14_R1/pom.xml @@ -8,7 +8,7 @@ net.jitse npclib-nms - 2.4.2-SNAPSHOT + 2.5-SNAPSHOT npclib-nms-v1_14_R1 diff --git a/nms/v1_15_R1/pom.xml b/nms/v1_15_R1/pom.xml index 6fe5267..d0a1b99 100644 --- a/nms/v1_15_R1/pom.xml +++ b/nms/v1_15_R1/pom.xml @@ -8,7 +8,7 @@ net.jitse npclib-nms - 2.4.2-SNAPSHOT + 2.5-SNAPSHOT npclib-nms-v1_15_R1 diff --git a/nms/v1_8_R2/pom.xml b/nms/v1_8_R2/pom.xml index 36f0496..2b04bbd 100755 --- a/nms/v1_8_R2/pom.xml +++ b/nms/v1_8_R2/pom.xml @@ -8,7 +8,7 @@ net.jitse npclib-nms - 2.4.2-SNAPSHOT + 2.5-SNAPSHOT npclib-nms-v1_8_R2 diff --git a/nms/v1_8_R3/pom.xml b/nms/v1_8_R3/pom.xml index d04e6a7..8ea9690 100755 --- a/nms/v1_8_R3/pom.xml +++ b/nms/v1_8_R3/pom.xml @@ -8,7 +8,7 @@ net.jitse npclib-nms - 2.4.2-SNAPSHOT + 2.5-SNAPSHOT npclib-nms-v1_8_R3 diff --git a/nms/v1_9_R1/pom.xml b/nms/v1_9_R1/pom.xml index c99a7b6..b452b25 100755 --- a/nms/v1_9_R1/pom.xml +++ b/nms/v1_9_R1/pom.xml @@ -8,7 +8,7 @@ net.jitse npclib-nms - 2.4.2-SNAPSHOT + 2.5-SNAPSHOT npclib-nms-v1_9_R1 diff --git a/nms/v1_9_R2/pom.xml b/nms/v1_9_R2/pom.xml index 590a908..6e7bdcb 100755 --- a/nms/v1_9_R2/pom.xml +++ b/nms/v1_9_R2/pom.xml @@ -8,7 +8,7 @@ net.jitse npclib-nms - 2.4.2-SNAPSHOT + 2.5-SNAPSHOT npclib-nms-v1_9_R2 diff --git a/plugin/pom.xml b/plugin/pom.xml index 5eb3040..5947c44 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -8,7 +8,7 @@ net.jitse npclib - 2.4.2-SNAPSHOT + 2.5-SNAPSHOT npclib-plugin diff --git a/pom.xml b/pom.xml index dbf7d46..e62072a 100755 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ net.jitse npclib - 2.4.2-SNAPSHOT + 2.5-SNAPSHOT UTF-8