Util Custom Player Lists: Create your own tab list display.

Discussion in 'Resources' started by Zombie_Striker, Aug 19, 2016.

Thread Status:
Not open for further replies.
  1. Offline

    Zombie_Striker

    @Blackwing_Forged
    Are you sure you created a playerlist for that player before you called that method?
     
  2. This is the code
    Code:
        @EventHandler
        public void onJoin(PlayerJoinEvent e)
        {
            Player p = e.getPlayer();
          
            PlayerList.getPlayerList(p).clearCustomTabs();//NullPointerException
          
            int index = 0;
            PlayerList.getPlayerList(p).addValue(5, "TEST");
            for(Player player : Bukkit.getWorld("Faction").getPlayers())
            {
                PlayerList.getPlayerList(player).addValue(index, player.getName());
                index++;
            }
        }
    
    i took some things out because its unrelevant to the error
     
    Last edited: Aug 9, 2017
  3. Offline

    Zombie_Striker

    @Blackwing_Forged
    You need to create a new instance of PlayerList before you try to access the playerlist; the getPlayerList method is only intended for getting existing playerlists. Just create a new PlayerList for the player before you call clear custom tabs.
     
  4. Offline

    Zombie_Striker

    Updated code. Most notably, the getPlayerList method will now create a new playerlist if the player does not have one. Most of the other changes were just code cleanup.
     
  5. Offline

    The_Spaceman


    I want to use this in a runnable, so it updates every 1 second, but when I send for the second time a tablist this will merge with the old one. how should I reset the list every time I will update the list?
     
  6. Offline

    Zombie_Striker

    @The_Spaceman
    Don't reset the tablist every second. That may cause the tablist to flicker. Instead, update the tabs that have changed.
     
  7. Offline

    The_Spaceman


    i have this:
    Code:
    PlayerList list = new PlayerList(player, 1);
    list.initTable();
    list.addValue(2, "test");
    
    this works, but when I update I get the list:
    ""
    "test"
    "hai"
    Code:
    PlayerList list = new PlayerList(player, 1);
    list.updateSlot(2, "hai");
    
    its like its merging the list together

    im in 1.12.2 if that matters
     
  8. Offline

    Zombie_Striker

    @The_Spaceman
    Yes, that is exactly what is happening. Creating a new player list does not override the existing player list. For what you want, you need to get the existing player list and modify it (you can use PlayerList#getPlayerList(Player) if you don't have access to that instance anymore).
     
  9. Offline

    The_Spaceman

    it works :D

    maybe you can add an header and footer option

    Code:
    PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter();
    IChatBaseComponent header = IChatBaseComponent.ChatSerializer.a("{\"text\":\"hei fggt\"}");
    IChatBaseComponent footer = IChatBaseComponent.ChatSerializer.a("{\"text\":\"lol\"}");
    try {
        Field a = packet.getClass().getDeclaredField("a");
        a.setAccessible(true);
        a.set(packet, header);
        Field b = packet.getClass().getDeclaredField("b");
        b.setAccessible(true);
        b.set(packet, footer);
    } catch (Exception e1) {
        e1.printStackTrace();
    }
    ((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet);
    
    
    and if you can that you can choose and image as player skin to use, that would be awesome
     
  10. Offline

    Zombie_Striker

    @The_Spaceman
    1. I'll try to add the header and footer.
    2. Skins are loaded based off of the tab's UUID if the UUID is already on the server. I'll try to see if there is a way around this, but I don't think there are any bukkit methods for loading skins.
     
  11. @Zombie_Striker
    It is possible to load skins using mojang's servers. I recently implemented this functionality into my NameTagChanger lib. It's quite messy, but it works. You can have a look at that system here:
    https://github.com/Alvin-LB/NameTag...gholm/nametagchanger/NameTagChanger.java#L392

    The key part of it is that you get the base64 data (you also need the signed base64, otherwise the client won't accept it), either from mojang's servers, or from already existing players. What you then do is add that data as a property to the GameProfiles you instantiate for the TabList, which will make the client load the skin.
     
  12. Offline

    The_Spaceman

    its only because constantly alex and steve are a bit annoying when you have an empty tab list
    but cool

    @Zombie_Striker

    when I do this
    Code:
    PlayerList list = new PlayerList(player, 1);
    list.initTable();
    list.addValue(2, "hello world");
    
    you can't use f3+n to go into gm3/gm1 and when you are in gamemode 3 you can't fly through walls and adjust your fly speed. how do I fix this?

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Sep 27, 2017
  13. Offline

    Zombie_Striker

    @AlvinB
    I'll try using the class. [Edit] Wow, that's a lot of references. Where would I find the 1.2 MojangAPIUtil class?

    @The_Spaceman
    I don't know why this would affect the f3n action, nor why it would affect spectator mode. Does it work without the tablist?
     
    Last edited: Sep 27, 2017
  14. Offline

    The_Spaceman

    it only happens when I use this, but now it will fix it selfs when you use the command for some reason.


    Code:
    for (int i = 0; i < onlineIslandPlayers.size(); i++) {
        list.addExistingPlayer(i, ChatColor.GREEN + onlineIslandPlayers.get(i),
        Bukkit.getOfflinePlayer(Bukkit.getPlayerExact(onlineIslandPlayers.get(i)).getUniqueId()));
    }
    for (int i = onlineIslandPlayers.size(); i < offlineIslandPlayers.size() + onlineIslandPlayers.size(); i++) {
        list.updateSlot(i, ChatColor.RED + offlineIslandPlayers.get(i - onlineIslandPlayers.size()) + i);
    }
    for (int i = offlineIslandPlayers.size() + onlineIslandPlayers.size(); i < 19; i++) {
        list.updateSlot(i, "test " + i);
    }
    
    this takes quit a but of time to run this, is there any way this proces can be faster?
     
  15. Offline

    Zombie_Striker

    @AlvinB
    Are you sure this is the most update to date code? I see you used this line in the setPlayerSkin Method:
    Code:
        profile.getProperties().put("textures", new GameProfileWrapper.PropertyWrapper("textures", skin.getBase64(), skin.getSignedBase64()));
    
    But when I use it, it says that PropertyWrapper is not a Property. Is something else supposed to go there when setting the texture?
     
    Last edited: Sep 27, 2017
  16. @Zombie_Striker
    'PropertyWrapper' is my own wrapped class for GameProfile. In order to use it on a regular GameProfile you have to use the 'Property' class.
     
  17. Offline

    Zombie_Striker

    @AlvinB
    I made some modifications to your skin class that you may to include in your project. When I was setting multiple tabs in the same tick, with the same username, it would lag for some skins and some of the skins would not even load. After some debugging, I found it was because all the requests to the mojang server happened at the same time, and the requests for the same user were duplicated. I made a small modification so that callbacks are stored in a Map that the runnable can access once it found the skin. This reduces the amount of calls sent for the same user. I only made the changes for the String version, so you would need to add some method for storing the callbacks if you wish to add this feature for UUIDs.
    Code:
    
        static Map<UUID,String> callbacksUUID = new HashMap<UUID,String>();
        static Map<String,List<SkinCallBack>> callbacks = new HashMap<String, List<SkinCallBack>>();
    
        /**
         * Gets the skin for a username.
         * <p>
         * Since fetching this skin requires making asynchronous requests to
         * Mojang's servers, a call back mechanism using the SkinCallBack class is
         * implemented. This call back allows you to also handle any errors that
         * might have occurred while fetching the skin. If no users with the
         * specified username can be found, the skin passed to the callback will be
         * Skin.EMPTY_SKIN.
         * <p>
         * The call back will always be fired on the main thread.
         *
         * @param username
         *            the username to get the skin of
         * @param callBack
         *            the call back to handle the result of the request
         */
        public static void getSkin(String username, SkinCallBack callBack) {
            boolean newcall = false;
            if(!callbacks.containsKey(username)){
                callbacks.put(username, new ArrayList<SkinCallBack>());
                newcall=true;
            }
            callbacks.get(username).add(callBack);
           
            if(newcall){
            new BukkitRunnable() {
                String u = username;
                @Override
                public void run() {
                    MojangAPIUtil.Result<Map<String, MojangAPIUtil.Profile>> result = MojangAPIUtil
                            .getUUID(Collections.singletonList(username));
                    if (result.wasSuccessful()) {
                        if (result.getValue() == null
                                || result.getValue().isEmpty()) {
                            new BukkitRunnable() {
                                @Override
                                public void run() {
                                    List<SkinCallBack> calls =callbacks.get(u);
                                    callbacks.remove(u);
                                    for(SkinCallBack s : calls){
                                        s.callBack(Skin.EMPTY_SKIN, true, null);                                   
                                    }
                                }
                            }.runTask(PlayerList.plugin);
                            return;
                        }
                        for (Map.Entry<String, MojangAPIUtil.Profile> entry : result
                                .getValue().entrySet()) {
                            if (entry.getKey().equalsIgnoreCase(username)) {
                                callbacksUUID.put(entry.getValue().getUUID(),u);
                                getSkin(entry.getValue().getUUID(), callBack);
                                return;
                            }
                        }
                    } else {
                        new BukkitRunnable() {
                            @Override
                            public void run() {
                                List<SkinCallBack> calls =callbacks.get(u);
                                callbacks.remove(u);
                                for(SkinCallBack s : calls){
                                s.callBack(null, false,
                                        result.getException());
                                }
                            }
                        }.runTask(PlayerList.plugin);
                    }
                }
            }.runTaskAsynchronously(PlayerList.plugin);
            }
        }
    
        /**
         * Gets the skin for a UUID.
         * <p>
         * Since fetching this skin might require making asynchronous requests to
         * Mojang's servers, a call back mechanism using the SkinCallBack class is
         * implemented. This call back allows you to also handle any errors that
         * might have occurred while fetching the skin.
         * <p>
         * The call back will always be fired on the main thread.
         *
         * @param uuid
         *            the uuid to get the skin of
         * @param callBack
         *            the call back to handle the result of the request
         */
        public static void getSkin(UUID uuid, SkinCallBack callBack) {
            Map<UUID, Skin> asMap = SKIN_CACHE.asMap();
            if (asMap.containsKey(uuid)) {
                for(SkinCallBack s : callbacks.get(callbacksUUID.get(uuid))){
                    s.callBack(asMap.get(uuid), true, null);
                }
            } else {
                new BukkitRunnable() {
                    @Override
                    public void run() {
                        try {
                            Skin skin = SKIN_CACHE.get(uuid);
                            new BukkitRunnable() {
                                @Override
                                public void run() {
                                    for(SkinCallBack s : callbacks.get(callbacksUUID.get(uuid))){
                                    s.callBack(skin, true, null);
                                    }
                                }
                            }.runTask(PlayerList.plugin);
                        } catch (ExecutionException e) {
                            new BukkitRunnable() {
                                @Override
                                public void run() {
                                    for(SkinCallBack s : callbacks.get(callbacksUUID.get(uuid))){
                                    s.callBack(null, false, e);
                                    }
                                }
                            }.runTask(PlayerList.plugin);
                        }
                    }
                }.runTaskAsynchronously(PlayerList.plugin);
            }
        }

    @The_Spaceman
    I just updated the code. The method setHeaderFooter now will set the header and footer for the tablist. Also, now skins are support. If you use the updateSlot(int,String) method, then all the users will have stone for their head texture. If you wish to add a user's head texture, use updateSlot(int, String, boolean) if you want to get the texture of a user that.

    Also, in case you used the examples on the main page, do not use addValue anymore, since they only add values without removing existing ones.
     
Thread Status:
Not open for further replies.

Share This Page