Resource Version Independent Actionbar Message Utility [1.8 and up]

Discussion in 'Spigot Plugin Development' started by Benz56, May 18, 2018 at 9:58 AM.

  1. Benz56

    Supporter

    Actionbar Message Utility:

    There are many libraries available on Spigot regarding Actionbar, however, instead of relying on another project I personally like having a utility method such as this inside my project as it allows me to directly change, fix, optimize, and fiddle around with it.

    For the sake of simplicity, I've compacted/boiled this down to a single method at the cost of some clarity and performance. This could be "abstractified" for a cleaner look though since this is a utility that won't change much a simple compact method will do the trick. At least in my opinion.

    Code (Java):
        /**

         * Utility message for sending version independent actionbar messages as to be able to
         * support versions from 1.8 and up without having to disable a simple feature such as this.
         *
         * @param player the recipient of the actionbar message.
         * @param message the message to send. If it is empty ("") the actionbar is cleared.
         */

        public void sendActionbar(Player player, String message) {
            if (player == null || message == null) return;
            String nmsVersion = Bukkit.getServer().getClass().getPackage().getName();
            nmsVersion = nmsVersion.substring(nmsVersion.lastIndexOf(".") + 1);

            //1.10 and up
            if (!nmsVersion.startsWith("v1_9_R") && !nmsVersion.startsWith("v1_8_R")) {
                player.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent(message));
                return;
            }

            //1.8.x and 1.9.x
            try {
                Class<?> craftPlayerClass = Class.forName("org.bukkit.craftbukkit." + nmsVersion + ".entity.CraftPlayer");
                Object craftPlayer = craftPlayerClass.cast(player);

                Class<?> ppoc = Class.forName("net.minecraft.server." + nmsVersion + ".PacketPlayOutChat");
                Class<?> packet = Class.forName("net.minecraft.server." + nmsVersion + ".Packet");
                Object packetPlayOutChat;
                Class<?> chat = Class.forName("net.minecraft.server." + nmsVersion + (nmsVersion.equalsIgnoreCase("v1_8_R1") ? ".ChatSerializer" : ".ChatComponentText"));
                Class<?> chatBaseComponent = Class.forName("net.minecraft.server." + nmsVersion + ".IChatBaseComponent");

                Method method = null;
                if (nmsVersion.equalsIgnoreCase("v1_8_R1")) method = chat.getDeclaredMethod("a", String.class);

                Object object = nmsVersion.equalsIgnoreCase("v1_8_R1") ? chatBaseComponent.cast(method.invoke(chat, "{'text': '" + message + "'}")) : chat.getConstructor(new Class[]{String.class}).newInstance(message);
                packetPlayOutChat = ppoc.getConstructor(new Class[]{chatBaseComponent, Byte.TYPE}).newInstance(object, (byte) 2);

                Method handle = craftPlayerClass.getDeclaredMethod("getHandle");
                Object iCraftPlayer = handle.invoke(craftPlayer);
                Field playerConnectionField = iCraftPlayer.getClass().getDeclaredField("playerConnection");
                Object playerConnection = playerConnectionField.get(iCraftPlayer);
                Method sendPacket = playerConnection.getClass().getDeclaredMethod("sendPacket", packet);
                sendPacket.invoke(playerConnection, packetPlayOutChat);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
    https://pastebin.com/WcXnXQAy

    Any feedback is appreciated as I am not the strongest in NMS yet, though I am learning, which is also why I choose to post this on the forum as to be able to get some feedback :)

    I have a similar method laying around for version independent title messages.
    If desired I can post this in a thread comparable to this.


    N.B.
    This is not a tutorial on NMS. It is merely a quality of life utility method as to make someone's life easier. Feel free to use it in your project.
     
    #1 Benz56, May 18, 2018 at 9:58 AM
    Last edited: May 18, 2018 at 10:56 AM
    • Winner Winner x 1
  2. Caching the fields and methods is preferred when doing reflection.
     
    • Agree Agree x 1
  3. Yes. But not using reflection at all is preferred when using NMS :p Reflection doesn't exist to bypass package versions. A multi module project with one module per NMS version would be by far cleaner, easier to maintain and slightly faster. Besides that, there is not even a need to support unknown future versions here.
     
    • Agree Agree x 1
  4. Benz56

    Supporter

    I’ll look into that.

    As for the future support it will simply default to the 1.10 and up.
     
    #4 Benz56, May 18, 2018 at 10:50 AM
    Last edited: May 18, 2018 at 10:57 AM
  5. Welcome to Java 9, where this code is officially broken. (No reflection on not-public class members)
     
  6. What?
     
  7. Benz56

    Supporter

    I’m not upgrading though as the majority is still using Java 8 for servers as well as plugins.
     
    • Agree Agree x 1
  8. You actually don't need to use any NMS to do this at all, its supported in the TextComponent API, e.g:
    Code (Text):
    BaseComponent message = new TextComponent("hi");
    p.spigot().sendMessage(ChatMessageType.ACTION_BAR, message);
     
    • Agree Agree x 1
  9. Benz56

    Supporter

    Not in 1.8 and 1.9.
    That only works for 1.10 and up which is already in the current method.
     
  10. Oh my apologies, I guess that's what you call replying without reading the code properly first :p
     
  11. This statement is misleading in this context as reflection is still allowed on private class members as along as the module which contains the class is either automatic or open. It is to my knowledge that NMS is compiled against Java 8 and as such is treated as an automatic module in Java 9 and later which has the same exact behaviour as before. Whether Mojang will use the module system and open the modules if and when they updated to Java 9+ is another question though.
     
  12. If you are going to offer reflection based version independent module, you should not have to embed those magic numbers in your code.
     
  13. Personally, I prefer shading stuff like that into Maven, that way I don't have to maintain it myself/manually grab updates.
     

Share This Page