tablist

Discussion in 'Spigot Plugin Development' started by TheFuzzyHead5, Jun 9, 2017.

  1. Please... for the sake of my sanity would somebody provide me a link which works in creating a tablist header and footer.

    I have tried so many tutorials and none have worked for me. The most updated/working one would work wonders!
     
  2. You will need to send the player a packet when they join.
    Code (Text):

    PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter();
    packet.header = new ComponentBuilder("You are playing on").color(ChatColor.AQUA).append(" SERVER.COM").color(ChatColor.YELLOW).bold(true).create();

    packet.footer =  new ComponentBuilder("You are playing on").color(ChatColor.AQUA).append(" THIS IS THE FOOTER").color(ChatColor.YELLOW).bold(true).create();

      CraftPlayer cp = (CraftPlayer) player;
      PlayerConnection con = cp.getHandle().playerConnection;

      con.sendPacket(packet);
    You will need to that for every player.
    I have not tested this, let me know if it doesn't work.
     
  3. There's an obvious error and I'm not sure how to proceed. packet.header and packet.footer, not sure if you meant it like that but it is the bit im struggling on. I did actually have that packet but didn't know how to define it to header or footer, how do I do this?
     
  4. what he posted may worked in earlier versions, basically the packet has two constructor fields ( IChatBaseComponent )

    IChatBaseComponent header;
    IChatBaseComponent footer;

    -> PacketPlayOutPlayerListHeaderFooter headerFooter = new PacketPlayOutPlayerListHeaderFooter(header,footer);
     
    • Informative Informative x 1
  5. Don't think it does, on my tools (1.12) seems the packet has no paramaters.
     
  6. then you have to set it with some reflection ( field a & b )
    [​IMG]
     
  7. I'm completely new at packets. Could you walk me through this quickly?
     
  8. Well, i will try to, but note this won't work by using copy & paste ^^

    First define everything.

    Code (Text):

    int versionId = Integer.parseInt(Bukkit.getBukkitVersion().split("-")[0].replace(".","#").split("#")[1]);


                   Class<?> craftPlayer = Reflection.getCraftClass("entity.CraftPlayer");
                   Class<?> packet = Reflection.getNMSClass("Packet");
                   Class<?> headerFooterPacket = Reflection.getNMSClass("PacketPlayOutPlayerListHeaderFooter");
                   Class<?> chatBaseComponent = Reflection.getNMSClass("IChatBaseComponent");
                   Class<?> chatSerializer = Reflection.getNMSClass("IChatBaseComponent$ChatSerializer");
                   Field header;
                   Field footer;

                    getHandle = craftPlayer.getDeclaredMethod("getHandle");

                    header = headerFooterPacket.getDeclaredField("a");
                    footer = headerFooterPacket.getDeclaredField("b");

     
    Then apply everything ( header )

    Code (Text):


     Object object = headerFooterPacket.newInstance();
                    header.setAccessible(true);
                   
    if(versionId >= 12){
                        header.set(object, chatSerializer.getMethod("a", String.class).invoke(null, "{" + '"' + "text" + '"' + ": " + '"' + headerMessage + '"' + "}"));
                    }else {
                        header.set(object, chatSerializer.getMethod("a", String.class).invoke(null, "{'text': '" + headerMessage + "'}"));
                    }
                    Object handle = getHandle.invoke(player);
                    Object connection = Reflection.getValue(handle, "playerConnection");
                    Method send = Reflection.getMethod(connection, "sendPacket", packet);
                    send.invoke(connection, object);


     
    & for the footer
    Then apply everything ( header )


    Code (Text):


     Object object = headerFooterPacket.newInstance();
                    footer.setAccessible(true);
                   
    if(versionId >= 12){
                        footer.set(object, chatSerializer.getMethod("a", String.class).invoke(null, "{" + '"' + "text" + '"' + ": " + '"' + footerMessage + '"' + "}"));
                    }else {
                        footer.set(object, chatSerializer.getMethod("a", String.class).invoke(null, "{'text': '" + footerMessage + "'}"));
                    }
                    Object handle = getHandle.invoke(player);
                    Object connection = Reflection.getValue(handle, "playerConnection");
                    Method send = Reflection.getMethod(connection, "sendPacket", packet);
                    send.invoke(connection, object);


     
    Note, this is untested and doesn't contain some methods that were used
     
    #9 Phloxz, Jun 10, 2017
    Last edited: Jun 10, 2017
  9. Hmm I am using PaperSpigot so they may have changed the variables and made them public.

    Here is how you can simply achieve this, although I will not recommend doing it like this, it works, and I hope it makes more sense.

    You will have issues supporting multiple versions like this which is why the above method is recommended.

    Code (Text):
    package com.nokoa.test;

    import net.minecraft.server.v1_12_R1.IChatBaseComponent;
    import net.minecraft.server.v1_12_R1.PacketPlayOutPlayerListHeaderFooter;
    import org.bukkit.Bukkit;
    import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer;
    import org.bukkit.entity.Player;
    import org.bukkit.event.EventHandler;
    import org.bukkit.event.Listener;
    import org.bukkit.event.player.PlayerJoinEvent;
    import org.bukkit.plugin.java.JavaPlugin;

    import java.lang.reflect.Field;

    /**
    * Created by nokoa on 6/9/2017.
    */
    public class Plugin extends JavaPlugin implements Listener {
        public void onEnable() {
            Bukkit.getServer().getPluginManager().registerEvents(this, this);

        }

        @EventHandler
        public void playerJoin(PlayerJoinEvent event) {
            Player player = event.getPlayer();

            PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter();

            /*
            Now because the variables for the header(a) and footer(b) are private in the
            PacketPlayOutPlayerListHeaderFooter class we will have to make them public using reflection
             */
            try {
                // Get the header field named a
                Field headerField = packet.getClass().getDeclaredField("a");
                // make that filed accessible to us so we can set it
                headerField.setAccessible(true);
                // Create the header
                IChatBaseComponent header = IChatBaseComponent.ChatSerializer.a("{\"text\":\"" + "Your header here" + "\"}");
                // Set the headerto the header field
                headerField.set(packet, header);

                // Get the footer field named b
                Field footerField = packet.getClass().getDeclaredField("b");
                // make that filed accessible to us so we can set it
                footerField.setAccessible(true);
                // Create the footer
                IChatBaseComponent footer = IChatBaseComponent.ChatSerializer.a("{\"text\":\"" + "Your footer here" + "\"}");
                // Set the footer to the footer field
                footerField.set(packet, footer);

                /*
                Now we we got our packet ready and we will send it to the player
                We need to get the player connection to do that
                 */
                ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet);


            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        }
    }
     
    [​IMG]
     
  10. Why are all these solutions so difficult? I use the following and it works great.

    Code (Text):
        public static void sendHeaderFooter(Player player, String header, String footer) {
            IChatBaseComponent tabHeader = ChatSerializer.a("{\"text\": \"" + header + "\"}");
            IChatBaseComponent tabFooter = ChatSerializer.a("{\"text\": \"" + footer + "\"}");
            PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter();
            try
            {
                Field headerField = packet.getClass().getDeclaredField("a");
                headerField.setAccessible(true);
                headerField.set(packet, tabHeader);
                headerField.setAccessible(false);
                Field footerField = packet.getClass().getDeclaredField("b");
                footerField.setAccessible(true);
                footerField.set(packet, tabFooter);
                footerField.setAccessible(false);
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }

            ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet);
        }
    You would use it like this.

    Code (Text):
    sendHeaderFooter(player, "Header", "Footer");
     
  11. They are so difficult because ( for example my solution ) is version independent ( Your Solution requires a specific version )
     
  12. Reflection
     
  13. Ahh I see. My bad.