Resource Refreshing the player (Applying skin texture example used)

Discussion in 'Spigot Plugin Development' started by PixieVice, Sep 9, 2021.

  1. Steps index:
    1 - 5: Changing player's skin in 1.8 - 1.16
    6: Changing player's skin in 1.17
    7: Re-spawn packet 1.8 - 1.13
    8: Re-spawn packet 1.14
    9: Re-spawn packet 1.15
    10: Re-spawn packet 1.16
    11: Re-spawn packet 1.17

    Hello everyone, I've been working on a skin plugin for 1.8 - 1.17 which is more of a learning experience for me. Hopefully this can help someone or at least put them on the right tracks.

    Before we get started, I suggest you have an instance of your main class ready to go. If you don't here's what you need
    Code (Java):
    private static Main instance;

        @Override
        public void onEnable() {
            instance = this;
        }

        public static Main getInstance() {
            return instance;
        }
    I'm using PacketPlayOutPlayerInfo to apply the texture to a player and then using PacketPlayOutRespawn to refresh the player. You can use player#spigot()#respawn() to refresh the skin though too, but keep in mind if you do use this you will have to set the player's health to 0 before the skin refreshes.

    1) We will need to import the CraftPlayer for whatever version you are using (this goes for 1.8 - 1.17)
    In the example I'm be using the v_1_8_R3 CraftPlayer, make sure to import the correct one for your own project. Along with some other imports.
    Code (Java):
    import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
    import org.bukkit.entity.Player;
    import com.mojang.authlib.GameProfile;
    import com.mojang.authlib.properties.Property;
    import net.minecraft.server.v1_8_R3.PacketPlayOutPlayerInfo;
    import net.minecraft.server.v1_8_R3.PlayerConnection;

    2) We need to create a method and two variables, change the method and variable names as you wish.
    Code (Java):
        public void changeSkin(Player player) {
            GameProfile profile = ((CraftPlayer) player).getHandle().getProfile();
            PlayerConnection connection = ((CraftPlayer) player).getHandle().playerConnection;
        }
    3) Getting textures is the next step. You will need the Texture, and Signature of the skin. If you want to get the skin of an actual player, you can find the texture and signature here https://api.minetools.eu/profile/trimmed-uuid. When you're on the page you will be looking for "signature" and "value" Keep those ready for later.

    If you are having trouble finding the trimmed uuid of a player, go here https://mcuuid.net/ and enter the username of the player you want to get the trimmed UUID of.

    3.1) Getting texture from a custom skin. https://mineskin.org/ is a great place to find skins, you can even upload your own if you'd like. In MineSkin, go to the Gallery and click or search for a skin, when a new page opens you'll want to scroll down a bit until you find a section called "Texture Signature" and "Texture Data"Keep those ready for later

    4) Back to writing the code, let's make a method to get the texture and signature just to be a bit cleaner.
    Create a new method and call it as you please. Replace T with the texture you got from the past two steps, and replace S with the signature you got from the past two steps.
    Code (Java):
        private static Property getSkin() {
            String texture = "T";
            String signature = "S";
            return new Property("textures", texture, signature);
        }
     
    5) Back in our changeSkin method from step 2, we need to add the remove the player, add the texture, then add the player back.
    Code (Java):
            connection.sendPacket(new PacketPlayOutPlayerInfo(
                    PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER,
                        ((CraftPlayer) player).getHandle()));
           
                    profile.getProperties().removeAll("textures");
                    profile.getProperties().put("textures", getSkin());
                   
            connection.sendPacket(new PacketPlayOutPlayerInfo(
                    PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER,
                        ((CraftPlayer) player).getHandle()));
    End result.
    Code (Text):
    package net.example.random

    import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
    import org.bukkit.entity.Player;
    import com.mojang.authlib.GameProfile;
    import com.mojang.authlib.properties.Property;
    import net.minecraft.server.v1_8_R3.PacketPlayOutPlayerInfo;
    import net.minecraft.server.v1_8_R3.PlayerConnection;

    public class ExampleSkin {
       
        public void changeSkin(Player player) {
            GameProfile profile = ((CraftPlayer) player).getHandle().getProfile();
            PlayerConnection connection = ((CraftPlayer) player).getHandle().playerConnection;
           
            connection.sendPacket(new PacketPlayOutPlayerInfo(
                    PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER,
                        ((CraftPlayer) player).getHandle()));
           
                    profile.getProperties().removeAll("textures");
                    profile.getProperties().put("textures", getSkin());
                   
            connection.sendPacket(new PacketPlayOutPlayerInfo(
                    PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER,
                        ((CraftPlayer) player).getHandle()));
        }
       
        private static Property getSkin() {
            String texture = "T"
            String signature = "S";
            return new Property("textures", texture, signature);
        }

    }

    6) The names for certain packets are name differently in 1.17 so the way to do this is just slightly different.
    (the getSkin method does not change)
    End result.
    Code (Java):
    package net.example.random;

    import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;
    import org.bukkit.entity.Player;
    import com.mojang.authlib.GameProfile;
    import com.mojang.authlib.properties.Property;
    import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo;
    import net.minecraft.server.network.PlayerConnection;

    public class Example {
       
        public void changeSkin(Player player) {
            GameProfile profile = ((CraftPlayer) player).getHandle().getProfile();
            PlayerConnection connection = ((CraftPlayer) player).getHandle().b;
           
            connection.sendPacket(new PacketPlayOutPlayerInfo(
                    PacketPlayOutPlayerInfo.EnumPlayerInfoAction.e, ((CraftPlayer) player).getHandle()));
           
                    profile.getProperties().removeAll("textures");
                    profile.getProperties().put("textures", getSkin());
                   
            connection.sendPacket(new PacketPlayOutPlayerInfo(
                    PacketPlayOutPlayerInfo.EnumPlayerInfoAction.a, ((CraftPlayer) player).getHandle()));
        }
       
        private static Property getSkin() {
            String texture = "T";
            String signature = "S"'
            return new Property("textures", texture, signature);
        }

    }

    If you're in for some learning. b is playerConnection, e is REMOVE_PLAYER, and a is ADD_PLAYER

    7) This is a Bukkit Runnable, like in step one, make sure you have the correct imports for what ever version you are using, in case you're confused why this is important it's because each version it's own imports.

    Code (Java):
        public void refreshPlayer(Player player) {

            Location loc = player.getLocation();
            loc.setYaw(player.getLocation().getYaw());
            loc.setPitch(player.getLocation().getPitch());
            EntityPlayer ep = ((CraftPlayer) player).getHandle();
            new BukkitRunnable() {
                @Override
                public void run() {
                    ep.playerConnection.sendPacket(new PacketPlayOutRespawn(ep.dimension, ep.getWorld().getWorldData().getType(), ep.playerInteractManager.getGameMode()));
                    player.teleport(loc);
            }
            }.runTaskLater(Main.getInstance(), 5);
        }
    If you want to refresh the player ONLY for other player's, use this.

    Code (Java):
            for (Player online : Bukkit.getOnlinePlayers()) {
                online.hidePlayer(player);
                online.showPlayer(player);

    8) In 1.14 the re-spawn packet removes the need for the world difficulty.

    Code (Java):
        public void refreshPlayer(Player player) {
     
            Location loc = player.getLocation();
            loc.setYaw(player.getLocation().getYaw());
            loc.setPitch(player.getLocation().getPitch());
            EntityPlayer ep = ((CraftPlayer) player).getHandle();
            new BukkitRunnable() {
                @Override
                public void run() {
                    ep.playerConnection.sendPacket(new PacketPlayOutRespawn(ep.dimension, ep.getWorld().getWorldData().getType(), ep.playerInteractManager.getGameMode()));
                    player.teleport(loc);
            }
            }.runTaskLater(Main.getInstance(), 5);
        }
     
    9) In 1.15 it now requires a long, aka the seed.

    Code (Java):
        public void resetSkin(Player player) {
     
            Location loc = player.getLocation();
            loc.setYaw(player.getLocation().getYaw());
            loc.setPitch(player.getLocation().getPitch());
            EntityPlayer ep = ((CraftPlayer) player).getHandle();
            new BukkitRunnable() {
                @Override
                public void run() {
                    ep.playerConnection.sendPacket(new PacketPlayOutRespawn(ep.dimension, ep.getWorld().getSeed(), ep.getWorld().getWorldData().getType(), ep.playerInteractManager.getGameMode()));
                    player.teleport(loc);
            }
            }.runTaskLater(Main.getInstance(), 5);
        }
    10) In 1.16 nothing much changed, except the way to get the seed if different and there are three booleans at the end.

    Code (Java):
        public void resetSkin(Player player) {
               
            Location loc = player.getLocation();
            loc.setYaw(player.getLocation().getYaw());
            loc.setPitch(player.getLocation().getPitch());
           
            long seEd = player.getWorld().getSeed();
           
            EntityPlayer ep = ((CraftPlayer) player).getHandle();
            World world = ep.getWorld();
            new BukkitRunnable() {
                @Override
                public void run() {
                    ep.playerConnection.sendPacket(new PacketPlayOutRespawn(ep.getWorld().getDimensionManager(), world.getDimensionKey(), seEd,
                            ep.playerInteractManager.getGameMode(), ep.playerInteractManager.getGameMode(), false, false, true));
                    player.teleport(loc);
            }
            }.runTaskLater(Main.getInstance(), 5);
        }
    11) Wow that was a lot, and finally, in 1.17 as I mentioned earlier the way packets are sent was changed.

    Code (Java):
        public void resetSkin(Player player) {
         
            Location loc = player.getLocation();
            loc.setYaw(player.getLocation().getYaw());
            loc.setPitch(player.getLocation().getPitch());
           
            long seEd = player.getWorld().getSeed();
           
            EntityPlayer ep = ((CraftPlayer) player).getHandle();
            World world = ep.getWorld();
            new BukkitRunnable() {
                @Override
                public void run() {
                    ep.b.sendPacket(new PacketPlayOutRespawn(ep.getWorld().getDimensionManager(), world.getDimensionKey(), seEd,
                            ep.d.getGameMode(), ep.d.getGameMode(), false, false, true));
                    player.teleport(loc);
            }
            }.runTaskLater(Main.getInstance(), 5);
        }
    If you're in for some learning. b is playerConnection, and d is playerInteractManager.

    Did I miss something? Did this help you in some way? Is this the worst thing you've seen? Let me know!
     
    • Useful Useful x 1
  2. (In case someone is using Paper, Paper has created an API to change a player's skin. At least in 1.16+, didn't check all versions)
     
    • Like Like x 2
    • Useful Useful x 1