From 4a519378aaf3ceca84a0e7f934e8600eb03e2c38 Mon Sep 17 00:00:00 2001
From: Thinkofdeath <thethinkofdeath@gmail.com>
Date: Sun, 20 Apr 2014 13:18:55 +0100
Subject: [PATCH] Convert player skulls async


diff --git a/src/main/java/net/minecraft/server/ItemStack.java b/src/main/java/net/minecraft/server/ItemStack.java
index 7d2d401..ee82aba 100644
--- a/src/main/java/net/minecraft/server/ItemStack.java
+++ b/src/main/java/net/minecraft/server/ItemStack.java
@@ -216,9 +216,61 @@ public final class ItemStack {
         if (nbttagcompound.hasKeyOfType("tag", 10)) {
             // CraftBukkit - make defensive copy as this data may be coming from the save thread
             this.tag = (NBTTagCompound) nbttagcompound.getCompound("tag").clone();
+            validateSkullSkin(); // Spigot
         }
     }
 
+    // Spigot start - make sure the tag is given the full gameprofile if it's a skull (async lookup)
+    public void validateSkullSkin()
+    {
+        if ( this.item == Items.SKULL && this.getData() == 3 )
+        {
+            String owner;
+            if ( this.tag.hasKeyOfType( "SkullOwner", 8 ) )
+            {
+                owner = this.tag.getString( "SkullOwner" );
+            } else if ( this.tag.hasKeyOfType( "SkullOwner", 10 ) )
+            {
+                net.minecraft.util.com.mojang.authlib.GameProfile profile = GameProfileSerializer.deserialize( this.tag.getCompound( "SkullOwner" ) );
+                if ( profile == null || !profile.getProperties().isEmpty() )
+                {
+                    return;
+                } else
+                {
+                    owner = profile.getName();
+                }
+            } else
+            {
+                return;
+            }
+
+            final String finalOwner = owner;
+            TileEntitySkull.executor.execute( new Runnable()
+            {
+                @Override
+                public void run()
+                {
+
+                    final net.minecraft.util.com.mojang.authlib.GameProfile profile = TileEntitySkull.skinCache.getUnchecked( finalOwner.toLowerCase() );
+                    if ( profile != null )
+                    {
+                        MinecraftServer.getServer().processQueue.add( new Runnable()
+                        {
+                            @Override
+                            public void run()
+                            {
+                                NBTTagCompound nbtProfile = new NBTTagCompound();
+                                GameProfileSerializer.serialize( nbtProfile, profile );
+                                ItemStack.this.tag.set( "SkullOwner", nbtProfile );
+                            }
+                        } );
+                    }
+                }
+            } );
+        }
+    }
+    // Spigot end
+    
     public int getMaxStackSize() {
         return this.getItem().getMaxStackSize();
     }
@@ -457,6 +509,7 @@ public final class ItemStack {
 
     public void setTag(NBTTagCompound nbttagcompound) {
         this.tag = nbttagcompound;
+        validateSkullSkin(); // Spigot
     }
 
     public String getName() {
diff --git a/src/main/java/net/minecraft/server/TileEntitySkull.java b/src/main/java/net/minecraft/server/TileEntitySkull.java
index 2a50db9..1796a56 100644
--- a/src/main/java/net/minecraft/server/TileEntitySkull.java
+++ b/src/main/java/net/minecraft/server/TileEntitySkull.java
@@ -6,11 +6,64 @@ import net.minecraft.util.com.google.common.collect.Iterables;
 import net.minecraft.util.com.mojang.authlib.GameProfile;
 import net.minecraft.util.com.mojang.authlib.properties.Property;
 
+// Spigot start
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import net.minecraft.util.com.mojang.authlib.Agent;
+// Spigot end
+
 public class TileEntitySkull extends TileEntity {
 
     private int a;
     private int i;
     private GameProfile j = null;
+    // Spigot start
+    public static final Executor executor = Executors.newFixedThreadPool(3,
+            new ThreadFactoryBuilder()
+                    .setNameFormat("Head Conversion Thread - %1$d")
+                    .build()
+    );
+    public static final Cache<String, GameProfile> skinCache = CacheBuilder.newBuilder()
+            .maximumSize( 5000 )
+            .expireAfterAccess( 60, TimeUnit.MINUTES )
+            .build( new CacheLoader<String, GameProfile>()
+            {
+                @Override
+                public GameProfile load(String key) throws Exception
+                {
+                    GameProfile[] profiles = new GameProfile[1];
+                    GameProfileLookup gameProfileLookup = new GameProfileLookup(profiles);
+
+                    MinecraftServer.getServer().getGameProfileRepository().findProfilesByNames(new String[] { key }, Agent.MINECRAFT, gameProfileLookup);
+
+                    GameProfile profile = profiles[ 0 ];
+                    if (profile == null) {
+                        UUID uuid = EntityHuman.a(new GameProfile(null, key));
+                        profile = new GameProfile(uuid, key);
+
+                        gameProfileLookup.onProfileLookupSucceeded(profile);
+                    } else
+                    {
+
+                        Property property = Iterables.getFirst( profile.getProperties().get( "textures" ), null );
+
+                        if ( property == null )
+                        {
+                            profile = MinecraftServer.getServer().av().fillProfileProperties( profile, true );
+                        }
+                    }
+
+
+                    return profile;
+                }
+            } );
+    // Spigot end
 
     public TileEntitySkull() {}
 
@@ -66,18 +119,38 @@ public class TileEntitySkull extends TileEntity {
     private void d() {
         if (this.j != null && !UtilColor.b(this.j.getName())) {
             if (!this.j.isComplete() || !this.j.getProperties().containsKey("textures")) {
-                GameProfile gameprofile = MinecraftServer.getServer().getUserCache().getProfile(this.j.getName());
-
-                if (gameprofile != null) {
-                    Property property = (Property) Iterables.getFirst(gameprofile.getProperties().get("textures"), null);
-
-                    if (property == null) {
-                        gameprofile = MinecraftServer.getServer().av().fillProfileProperties(gameprofile, true);
+                // Spigot start - Handle async
+                final String name = this.j.getName();
+                setSkullType( 0 ); // Work around a client bug
+                executor.execute(new Runnable() {
+                    @Override
+                    public void run() {
+
+                        GameProfile profile = skinCache.getUnchecked( name.toLowerCase() );
+
+                        if (profile != null) {
+                            final GameProfile finalProfile = profile;
+                            MinecraftServer.getServer().processQueue.add(new Runnable() {
+                                @Override
+                                public void run() {
+                                    a = 3;
+                                    j = finalProfile;
+                                    world.notify( x, y, z );
+                                }
+                            });
+                        } else {
+                            MinecraftServer.getServer().processQueue.add(new Runnable() {
+                                @Override
+                                public void run() {
+                                    a = 3;
+                                    j = new GameProfile( null, name );
+                                    world.notify( x, y, z );
+                                }
+                            });
+                        }
                     }
-
-                    this.j = gameprofile;
-                    this.update();
-                }
+                });
+                // Spigot end
             }
         }
     }
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java
index d648d05..e32bcb1 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java
@@ -46,13 +46,39 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta {
     }
 
     @Override
-    void applyToItem(NBTTagCompound tag) {
+    void applyToItem(final NBTTagCompound tag) { // Spigot - make final
         super.applyToItem(tag);
 
         if (hasOwner()) {
             NBTTagCompound owner = new NBTTagCompound();
             GameProfileSerializer.serialize(owner, profile);
-            tag.set(SKULL_OWNER.NBT, owner);
+            tag.set( SKULL_OWNER.NBT, owner );
+            // Spigot start - do an async lookup of the profile. 
+            // Unfortunately there is not way to refresh the holding
+            // inventory, so that responsibility is left to the user.
+            net.minecraft.server.TileEntitySkull.executor.execute( new Runnable()
+            {
+                @Override
+                public void run()
+                {
+
+                    final GameProfile profile = net.minecraft.server.TileEntitySkull.skinCache.getUnchecked( CraftMetaSkull.this.profile.getName().toLowerCase() );
+                    if ( profile != null )
+                    {
+                        MinecraftServer.getServer().processQueue.add( new Runnable()
+                        {
+                            @Override
+                            public void run()
+                            {
+                                NBTTagCompound owner = new NBTTagCompound();
+                                GameProfileSerializer.serialize( owner, profile );
+                                tag.set( SKULL_OWNER.NBT, owner );
+                            }
+                        } );
+                    }
+                }
+            } );
+            // Spigot end
         }
     }
 
-- 
1.9.1