diff --git a/Spigot-API-Patches/0126-Extend-player-profile-API-to-support-skin-changes.patch b/Spigot-API-Patches/0126-Extend-player-profile-API-to-support-skin-changes.patch new file mode 100644 index 000000000..6e851c0fe --- /dev/null +++ b/Spigot-API-Patches/0126-Extend-player-profile-API-to-support-skin-changes.patch @@ -0,0 +1,29 @@ +From c54292b8620985a83368e984581d4835604512e1 Mon Sep 17 00:00:00 2001 +From: NickAcPT <32451103+NickAcPT@users.noreply.github.com> +Date: Sat, 21 Jul 2018 01:30:41 +0100 +Subject: [PATCH] Extend player profile API to support skin changes + +Added code that refreshes the player's skin by sending packets with a special order, telling the client to respawn the player and re-apply the game profile + +diff --git a/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java b/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java +index e060c38a..98eade06 100644 +--- a/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java ++++ b/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java +@@ -132,6 +132,14 @@ public interface PlayerProfile { + * @return If the profile is now complete (has UUID and Name) (if you get rate limited, this operation may fail) + */ + boolean complete(boolean textures); ++ /** ++ * If this profile is not complete, then make the API call to complete it. ++ * This is a blocking operation and should be done asynchronously. ++ * ++ * Optionally will also fill textures. ++ * @return If the profile is now complete (has UUID and Name) (if you get rate limited, this operation may fail) ++ */ ++ boolean complete(boolean textures, boolean force); + + /** + * Whether or not this Profile has textures associated to it +-- +2.16.2.windows.1 + diff --git a/Spigot-Server-Patches/0336-Extend-player-profile-API-to-support-skin-changes.patch b/Spigot-Server-Patches/0336-Extend-player-profile-API-to-support-skin-changes.patch new file mode 100644 index 000000000..ab79ee794 --- /dev/null +++ b/Spigot-Server-Patches/0336-Extend-player-profile-API-to-support-skin-changes.patch @@ -0,0 +1,163 @@ +From e709f6448451449a488b16d4039b25ff467cc956 Mon Sep 17 00:00:00 2001 +From: NickAcPT <32451103+NickAcPT@users.noreply.github.com> +Date: Sat, 21 Jul 2018 01:30:30 +0100 +Subject: [PATCH] Extend player profile API to support skin changes + +Added code that refreshes the player's skin by sending packets with a special order, telling the client to respawn the player and re-apply the game profile + +diff --git a/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java +index 9ad5853d..dda24052 100644 +--- a/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java ++++ b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java +@@ -170,17 +170,21 @@ public class CraftPlayerProfile implements PlayerProfile { + } + + public boolean complete(boolean textures) { ++ return complete(textures, false); ++ } ++ ++ public boolean complete(boolean textures, boolean force) { + MinecraftServer server = MinecraftServer.getServer(); + + boolean isOnlineMode = server.getOnlineMode() || (SpigotConfig.bungee && PaperConfig.bungeeOnlineMode); + boolean isCompleteFromCache = this.completeFromCache(true); +- if (isOnlineMode && (!isCompleteFromCache || textures && !hasTextures())) { ++ if ((isOnlineMode || force) && (!isCompleteFromCache || textures && !hasTextures())) { + GameProfile result = server.getSessionService().fillProfileProperties(profile, true); + if (result != null) { + this.profile = result; + } + } +- return profile.isComplete() && (!isOnlineMode || !textures || hasTextures()); ++ return profile.isComplete() && (!(isOnlineMode || force) || !textures || hasTextures()); + } + + private static void copyProfileProperties(GameProfile source, GameProfile target) { +diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java +index dd78a87b..e3f7be28 100644 +--- a/src/main/java/net/minecraft/server/WorldServer.java ++++ b/src/main/java/net/minecraft/server/WorldServer.java +@@ -135,6 +135,12 @@ public class WorldServer extends World implements IAsyncTaskHandler { + return this; + } + ++ // Paper start - Provide dimension number of world ++ public int getDimension() { ++ return dimension; ++ } ++ // Paper end ++ + // CraftBukkit start + @Override + public TileEntity getTileEntity(BlockPosition pos) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 210e3bc4..b0335684 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -101,6 +101,12 @@ public class CraftWorld implements World { + } + // Paper end + ++ // Paper start - Provide dimension number of world ++ public int getDimension() { ++ return world.dimension; ++ } ++ // Paper end ++ + private static final Random rand = new Random(); + + public CraftWorld(WorldServer world, ChunkGenerator gen, Environment env) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 6cbf429f..bd743be5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -55,6 +55,7 @@ import org.bukkit.craftbukkit.CraftStatistic; + import org.bukkit.craftbukkit.CraftWorld; + import org.bukkit.craftbukkit.advancement.CraftAdvancement; + import org.bukkit.craftbukkit.advancement.CraftAdvancementProgress; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; + import org.bukkit.craftbukkit.map.CraftMapView; + import org.bukkit.craftbukkit.map.RenderData; + import org.bukkit.craftbukkit.scoreboard.CraftScoreboard; +@@ -1172,14 +1173,102 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + public void setPlayerProfile(PlayerProfile profile) { + EntityPlayer self = getHandle(); + self.setProfile(CraftPlayerProfile.asAuthlibCopy(profile)); +- List players = server.getServer().getPlayerList().players; +- for (EntityPlayer player : players) { +- player.getBukkitEntity().reregisterPlayer(self); +- } ++ refreshPlayer(); + } + public PlayerProfile getPlayerProfile() { + return new CraftPlayerProfile(this).clone(); + } ++ ++ private void refreshPlayer() { ++ int dimension = getWorld().getEnvironment().getId(); ++ Packet respawn = new PacketPlayOutRespawn(dimension, ((WorldServer)getHandle().getWorld()).worldData.getDifficulty(), ((WorldServer)getHandle().getWorld()).worldData.getType(), EnumGamemode.getById(getHandle().playerInteractManager.getGameMode().getId())); ++ Location l = getLocation(); ++ ++ Packet pos = new PacketPlayOutPosition(l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), new HashSet<>(), 0); ++ Packet mainhand = new PacketPlayOutEntityEquipment(getEntityId(), EnumItemSlot.MAINHAND, CraftItemStack.asNMSCopy(getInventory().getItemInMainHand())); ++ Packet offhand = new PacketPlayOutEntityEquipment(getEntityId(), EnumItemSlot.OFFHAND, CraftItemStack.asNMSCopy(getInventory().getItemInOffHand())); ++ Packet helm = new PacketPlayOutEntityEquipment(getEntityId(), EnumItemSlot.HEAD, CraftItemStack.asNMSCopy(getInventory().getHelmet())); ++ Packet chest = new PacketPlayOutEntityEquipment(getEntityId(), EnumItemSlot.CHEST, CraftItemStack.asNMSCopy(getInventory().getChestplate())); ++ Packet legs = new PacketPlayOutEntityEquipment(getEntityId(), EnumItemSlot.LEGS, CraftItemStack.asNMSCopy(getInventory().getLeggings())); ++ Packet feet = new PacketPlayOutEntityEquipment(getEntityId(), EnumItemSlot.FEET, CraftItemStack.asNMSCopy(getInventory().getBoots())); ++ Packet slot = new PacketPlayOutHeldItemSlot(getInventory().getHeldItemSlot()); ++ ++ for (Player pOnline : Bukkit.getOnlinePlayers()) { ++ EntityPlayer handle = ((CraftPlayer) pOnline).getHandle(); ++ PlayerConnection playerCon = handle.playerConnection; ++ if (pOnline.equals(this)) { ++ reregisterPlayer(handle); ++ ++ //Respawn the player then update their position and selected slot ++ handle.playerConnection.sendPacket(respawn); ++ handle.updateAbilities(); ++ handle.playerConnection.sendPacket(pos); ++ handle.playerConnection.sendPacket(slot); ++ ++ ((CraftPlayer) pOnline).updateScaledHealth(); ++ pOnline.updateInventory(); ++ ++ handle.triggerHealthUpdate(); ++ ++ if (pOnline.isOp()) { ++ pOnline.setOp(false); ++ pOnline.setOp(true); ++ } ++ continue; ++ } ++ if (pOnline.getWorld().equals(getWorld()) && pOnline.canSee(this) && isOnline()) { ++ PacketPlayOutEntityDestroy removeEntity = new PacketPlayOutEntityDestroy(new int[]{getEntityId()}); ++ PacketPlayOutNamedEntitySpawn addNamed = new PacketPlayOutNamedEntitySpawn(getHandle()); ++ ++ //Remove and reregister the player ++ handle.playerConnection.sendPacket(removeEntity); ++ ((CraftPlayer) pOnline).reregisterPlayer(this.getHandle()); ++ handle.playerConnection.sendPacket(addNamed); ++ ++ //Send hand items ++ handle.playerConnection.sendPacket(mainhand); ++ handle.playerConnection.sendPacket(offhand); ++ ++ //Send armor ++ handle.playerConnection.sendPacket(helm); ++ handle.playerConnection.sendPacket(chest); ++ handle.playerConnection.sendPacket(legs); ++ handle.playerConnection.sendPacket(feet); ++ } else { ++ //Just send player update ++ ((CraftPlayer) pOnline).reregisterPlayer(this.getHandle()); ++ } ++ } ++ ++ } + // Paper end + + public void removeDisconnectingPlayer(Player player) { +-- +2.16.2.windows.1 +