2
0
Эх сурвалжийг харах

Add UUID support! - zreed, slipcor, t00thpick1

This commit updates our database managers to use UUIDs instead
of usernames for persistent storage.

Fixes #1979
TfT_02 11 жил өмнө
parent
commit
c10525ada9

+ 1 - 0
Changelog.txt

@@ -9,6 +9,7 @@ Key:
 
 
 Version 1.5.01-dev
 Version 1.5.01-dev
  + Added new child skill; Salvage
  + Added new child skill; Salvage
+ + Added UUID support!
  + Added new feature to Herbalism. Instantly-regrown crops are protected from being broken for 1 second
  + Added new feature to Herbalism. Instantly-regrown crops are protected from being broken for 1 second
  + Added option to config.yml to show the /mcstats scoreboard automatically after logging in
  + Added option to config.yml to show the /mcstats scoreboard automatically after logging in
  + Added option to config.yml for Alchemy. Skills.Alchemy.Prevent_Hopper_Transfer_Bottles
  + Added option to config.yml for Alchemy. Skills.Alchemy.Prevent_Hopper_Transfer_Bottles

+ 284 - 2
src/main/java/com/gmail/nossr50/api/ExperienceAPI.java

@@ -1,6 +1,7 @@
 package com.gmail.nossr50.api;
 package com.gmail.nossr50.api;
 
 
 import java.util.Set;
 import java.util.Set;
+import java.util.UUID;
 
 
 import org.bukkit.entity.Player;
 import org.bukkit.entity.Player;
 
 
@@ -86,6 +87,14 @@ public final class ExperienceAPI {
         UserManager.getPlayer(player).applyXpGain(getSkillType(skillType), XP, getXPGainReason(xpGainReason));
         UserManager.getPlayer(player).applyXpGain(getSkillType(skillType), XP, getXPGainReason(xpGainReason));
     }
     }
 
 
+    /**
+     * Adds raw XP to an offline player.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @deprecated We're using float for our XP values now
+     * replaced by {@link #addRawXPOffline(String playerName, String skillType, float XP)}
+     */
     @Deprecated
     @Deprecated
     public static void addRawXPOffline(String playerName, String skillType, int XP) {
     public static void addRawXPOffline(String playerName, String skillType, int XP) {
         addRawXPOffline(playerName, skillType, (float) XP);
         addRawXPOffline(playerName, skillType, (float) XP);
@@ -96,6 +105,9 @@ public final class ExperienceAPI {
      * </br>
      * </br>
      * This function is designed for API usage.
      * This function is designed for API usage.
      *
      *
+     * @deprecated We're using uuids to get an offline player
+     * replaced by {@link #addRawXPOffline(UUID uuid, String skillType, float XP)}
+     *
      * @param playerName The player to add XP to
      * @param playerName The player to add XP to
      * @param skillType The skill to add XP to
      * @param skillType The skill to add XP to
      * @param XP The amount of XP to add
      * @param XP The amount of XP to add
@@ -103,10 +115,27 @@ public final class ExperienceAPI {
      * @throws InvalidSkillException if the given skill is not valid
      * @throws InvalidSkillException if the given skill is not valid
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws InvalidPlayerException if the given player does not exist in the database
      */
      */
+    @Deprecated
     public static void addRawXPOffline(String playerName, String skillType, float XP) {
     public static void addRawXPOffline(String playerName, String skillType, float XP) {
         addOfflineXP(playerName, getSkillType(skillType), (int) Math.floor(XP));
         addOfflineXP(playerName, getSkillType(skillType), (int) Math.floor(XP));
     }
     }
 
 
+    /**
+     * Adds raw XP to an offline player.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param uuid The UUID of player to add XP to
+     * @param skillType The skill to add XP to
+     * @param XP The amount of XP to add
+     *
+     * @throws InvalidSkillException if the given skill is not valid
+     * @throws InvalidPlayerException if the given player does not exist in the database
+     */
+    public static void addRawXPOffline(UUID uuid, String skillType, float XP) {
+        addOfflineXP(uuid, getSkillType(skillType), (int) Math.floor(XP));
+    }
+
     /**
     /**
      * Adds XP to the player, calculates for XP Rate only.
      * Adds XP to the player, calculates for XP Rate only.
      * </br>
      * </br>
@@ -152,6 +181,7 @@ public final class ExperienceAPI {
      * @throws InvalidSkillException if the given skill is not valid
      * @throws InvalidSkillException if the given skill is not valid
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws InvalidPlayerException if the given player does not exist in the database
      */
      */
+    @Deprecated
     public static void addMultipliedXPOffline(String playerName, String skillType, int XP) {
     public static void addMultipliedXPOffline(String playerName, String skillType, int XP) {
         addOfflineXP(playerName, getSkillType(skillType), (int) (XP * ExperienceConfig.getInstance().getExperienceGainsGlobalMultiplier()));
         addOfflineXP(playerName, getSkillType(skillType), (int) (XP * ExperienceConfig.getInstance().getExperienceGainsGlobalMultiplier()));
     }
     }
@@ -203,6 +233,7 @@ public final class ExperienceAPI {
      * @throws InvalidSkillException if the given skill is not valid
      * @throws InvalidSkillException if the given skill is not valid
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws InvalidPlayerException if the given player does not exist in the database
      */
      */
+    @Deprecated
     public static void addModifiedXPOffline(String playerName, String skillType, int XP) {
     public static void addModifiedXPOffline(String playerName, String skillType, int XP) {
         SkillType skill = getSkillType(skillType);
         SkillType skill = getSkillType(skillType);
 
 
@@ -273,10 +304,28 @@ public final class ExperienceAPI {
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws UnsupportedOperationException if the given skill is a child skill
      * @throws UnsupportedOperationException if the given skill is a child skill
      */
      */
+    @Deprecated
     public static int getOfflineXP(String playerName, String skillType) {
     public static int getOfflineXP(String playerName, String skillType) {
         return getOfflineProfile(playerName).getSkillXpLevel(getNonChildSkillType(skillType));
         return getOfflineProfile(playerName).getSkillXpLevel(getNonChildSkillType(skillType));
     }
     }
 
 
+    /**
+     * Get the amount of XP an offline player has in a specific skill.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param uuid The player to get XP for
+     * @param skillType The skill to get XP for
+     * @return the amount of XP in a given skill
+     *
+     * @throws InvalidSkillException if the given skill is not valid
+     * @throws InvalidPlayerException if the given player does not exist in the database
+     * @throws UnsupportedOperationException if the given skill is a child skill
+     */
+    public static int getOfflineXP(UUID uuid, String skillType) {
+        return getOfflineProfile(uuid).getSkillXpLevel(getNonChildSkillType(skillType));
+    }
+
     /**
     /**
      * Get the raw amount of XP a player has in a specific skill.
      * Get the raw amount of XP a player has in a specific skill.
      * </br>
      * </br>
@@ -306,10 +355,28 @@ public final class ExperienceAPI {
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws UnsupportedOperationException if the given skill is a child skill
      * @throws UnsupportedOperationException if the given skill is a child skill
      */
      */
+    @Deprecated
     public static float getOfflineXPRaw(String playerName, String skillType) {
     public static float getOfflineXPRaw(String playerName, String skillType) {
         return getOfflineProfile(playerName).getSkillXpLevelRaw(getNonChildSkillType(skillType));
         return getOfflineProfile(playerName).getSkillXpLevelRaw(getNonChildSkillType(skillType));
     }
     }
 
 
+    /**
+     * Get the raw amount of XP an offline player has in a specific skill.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param uuid The player to get XP for
+     * @param skillType The skill to get XP for
+     * @return the amount of XP in a given skill
+     *
+     * @throws InvalidSkillException if the given skill is not valid
+     * @throws InvalidPlayerException if the given player does not exist in the database
+     * @throws UnsupportedOperationException if the given skill is a child skill
+     */
+    public static float getOfflineXPRaw(UUID uuid, String skillType) {
+        return getOfflineProfile(uuid).getSkillXpLevelRaw(getNonChildSkillType(skillType));
+    }
+
     /**
     /**
      * Get the total amount of XP needed to reach the next level.
      * Get the total amount of XP needed to reach the next level.
      * </br>
      * </br>
@@ -339,10 +406,28 @@ public final class ExperienceAPI {
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws UnsupportedOperationException if the given skill is a child skill
      * @throws UnsupportedOperationException if the given skill is a child skill
      */
      */
+    @Deprecated
     public static int getOfflineXPToNextLevel(String playerName, String skillType) {
     public static int getOfflineXPToNextLevel(String playerName, String skillType) {
         return getOfflineProfile(playerName).getXpToLevel(getNonChildSkillType(skillType));
         return getOfflineProfile(playerName).getXpToLevel(getNonChildSkillType(skillType));
     }
     }
 
 
+    /**
+     * Get the total amount of XP an offline player needs to reach the next level.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param uuid The player to get XP for
+     * @param skillType The skill to get XP for
+     * @return the total amount of XP needed to reach the next level
+     *
+     * @throws InvalidSkillException if the given skill is not valid
+     * @throws InvalidPlayerException if the given player does not exist in the database
+     * @throws UnsupportedOperationException if the given skill is a child skill
+     */
+    public static int getOfflineXPToNextLevel(UUID uuid, String skillType) {
+        return getOfflineProfile(uuid).getXpToLevel(getNonChildSkillType(skillType));
+    }
+
     /**
     /**
      * Get the amount of XP remaining until the next level.
      * Get the amount of XP remaining until the next level.
      * </br>
      * </br>
@@ -376,14 +461,34 @@ public final class ExperienceAPI {
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws UnsupportedOperationException if the given skill is a child skill
      * @throws UnsupportedOperationException if the given skill is a child skill
      */
      */
+    @Deprecated
     public static int getOfflineXPRemaining(String playerName, String skillType) {
     public static int getOfflineXPRemaining(String playerName, String skillType) {
         SkillType skill = getNonChildSkillType(skillType);
         SkillType skill = getNonChildSkillType(skillType);
-
         PlayerProfile profile = getOfflineProfile(playerName);
         PlayerProfile profile = getOfflineProfile(playerName);
 
 
         return profile.getXpToLevel(skill) - profile.getSkillXpLevel(skill);
         return profile.getXpToLevel(skill) - profile.getSkillXpLevel(skill);
     }
     }
 
 
+    /**
+     * Get the amount of XP an offline player has left before leveling up.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param uuid The player to get XP for
+     * @param skillType The skill to get XP for
+     * @return the amount of XP needed to reach the next level
+     *
+     * @throws InvalidSkillException if the given skill is not valid
+     * @throws InvalidPlayerException if the given player does not exist in the database
+     * @throws UnsupportedOperationException if the given skill is a child skill
+     */
+    public static float getOfflineXPRemaining(UUID uuid, String skillType) {
+        SkillType skill = getNonChildSkillType(skillType);
+        PlayerProfile profile = getOfflineProfile(uuid);
+
+        return profile.getXpToLevel(skill) - profile.getSkillXpLevelRaw(skill);
+    }
+
     /**
     /**
      * Add levels to a skill.
      * Add levels to a skill.
      * </br>
      * </br>
@@ -411,6 +516,7 @@ public final class ExperienceAPI {
      * @throws InvalidSkillException if the given skill is not valid
      * @throws InvalidSkillException if the given skill is not valid
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws InvalidPlayerException if the given player does not exist in the database
      */
      */
+    @Deprecated
     public static void addLevelOffline(String playerName, String skillType, int levels) {
     public static void addLevelOffline(String playerName, String skillType, int levels) {
         PlayerProfile profile = getOfflineProfile(playerName);
         PlayerProfile profile = getOfflineProfile(playerName);
         SkillType skill = getSkillType(skillType);
         SkillType skill = getSkillType(skillType);
@@ -430,6 +536,37 @@ public final class ExperienceAPI {
         profile.scheduleAsyncSave();
         profile.scheduleAsyncSave();
     }
     }
 
 
+    /**
+     * Add levels to a skill for an offline player.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param uuid The player to add levels to
+     * @param skillType Type of skill to add levels to
+     * @param levels Number of levels to add
+     *
+     * @throws InvalidSkillException if the given skill is not valid
+     * @throws InvalidPlayerException if the given player does not exist in the database
+     */
+    public static void addLevelOffline(UUID uuid, String skillType, int levels) {
+        PlayerProfile profile = getOfflineProfile(uuid);
+        SkillType skill = getSkillType(skillType);
+
+        if (skill.isChildSkill()) {
+            Set<SkillType> parentSkills = FamilyTree.getParents(skill);
+
+            for (SkillType parentSkill : parentSkills) {
+                profile.addLevels(parentSkill, (levels / parentSkills.size()));
+            }
+
+            profile.scheduleAsyncSave();
+            return;
+        }
+
+        profile.addLevels(skill, levels);
+        profile.scheduleAsyncSave();
+    }
+
     /**
     /**
      * Get the level a player has in a specific skill.
      * Get the level a player has in a specific skill.
      * </br>
      * </br>
@@ -457,10 +594,27 @@ public final class ExperienceAPI {
      * @throws InvalidSkillException if the given skill is not valid
      * @throws InvalidSkillException if the given skill is not valid
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws InvalidPlayerException if the given player does not exist in the database
      */
      */
+    @Deprecated
     public static int getLevelOffline(String playerName, String skillType) {
     public static int getLevelOffline(String playerName, String skillType) {
         return getOfflineProfile(playerName).getSkillLevel(getSkillType(skillType));
         return getOfflineProfile(playerName).getSkillLevel(getSkillType(skillType));
     }
     }
 
 
+    /**
+     * Get the level an offline player has in a specific skill.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param uuid The player to get the level for
+     * @param skillType The skill to get the level for
+     * @return the level of a given skill
+     *
+     * @throws InvalidSkillException if the given skill is not valid
+     * @throws InvalidPlayerException if the given player does not exist in the database
+     */
+    public static int getLevelOffline(UUID uuid, String skillType) {
+        return getOfflineProfile(uuid).getSkillLevel(getSkillType(skillType));
+    }
+
     /**
     /**
      * Gets the power level of a player.
      * Gets the power level of a player.
      * </br>
      * </br>
@@ -483,6 +637,7 @@ public final class ExperienceAPI {
      *
      *
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws InvalidPlayerException if the given player does not exist in the database
      */
      */
+    @Deprecated
     public static int getPowerLevelOffline(String playerName) {
     public static int getPowerLevelOffline(String playerName) {
         int powerLevel = 0;
         int powerLevel = 0;
         PlayerProfile profile = getOfflineProfile(playerName);
         PlayerProfile profile = getOfflineProfile(playerName);
@@ -494,6 +649,27 @@ public final class ExperienceAPI {
         return powerLevel;
         return powerLevel;
     }
     }
 
 
+    /**
+     * Gets the power level of an offline player.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param uuid The player to get the power level for
+     * @return the power level of the player
+     *
+     * @throws InvalidPlayerException if the given player does not exist in the database
+     */
+    public static int getPowerLevelOffline(UUID uuid) {
+        int powerLevel = 0;
+        PlayerProfile profile = getOfflineProfile(uuid);
+
+        for (SkillType type : SkillType.NON_CHILD_SKILLS) {
+            powerLevel += profile.getSkillLevel(type);
+        }
+
+        return powerLevel;
+    }
+
     /**
     /**
      * Get the level cap of a specific skill.
      * Get the level cap of a specific skill.
      * </br>
      * </br>
@@ -533,10 +709,28 @@ public final class ExperienceAPI {
      *
      *
      * @return the position on the leaderboard
      * @return the position on the leaderboard
      */
      */
+    @Deprecated
     public static int getPlayerRankSkill(String playerName, String skillType) {
     public static int getPlayerRankSkill(String playerName, String skillType) {
         return mcMMO.getDatabaseManager().readRank(getOfflineProfile(playerName).getPlayerName()).get(getNonChildSkillType(skillType));
         return mcMMO.getDatabaseManager().readRank(getOfflineProfile(playerName).getPlayerName()).get(getNonChildSkillType(skillType));
     }
     }
 
 
+    /**
+     * Get the position on the leaderboard of a player.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param uuid The name of the player to check
+     * @param skillType The skill to check
+     *
+     * @throws InvalidSkillException if the given skill is not valid
+     * @throws InvalidPlayerException if the given player does not exist in the database
+     * @throws UnsupportedOperationException if the given skill is a child skill
+     *
+     * @return the position on the leaderboard
+     */
+    public static int getPlayerRankSkill(UUID uuid, String skillType) {
+        return mcMMO.getDatabaseManager().readRank(getOfflineProfile(uuid).getPlayerName()).get(getNonChildSkillType(skillType));
+    }
 
 
     /**
     /**
      * Get the position on the power level leaderboard of a player.
      * Get the position on the power level leaderboard of a player.
@@ -549,10 +743,26 @@ public final class ExperienceAPI {
      *
      *
      * @return the position on the power level leaderboard
      * @return the position on the power level leaderboard
      */
      */
+    @Deprecated
     public static int getPlayerRankOverall(String playerName) {
     public static int getPlayerRankOverall(String playerName) {
         return mcMMO.getDatabaseManager().readRank(getOfflineProfile(playerName).getPlayerName()).get(null);
         return mcMMO.getDatabaseManager().readRank(getOfflineProfile(playerName).getPlayerName()).get(null);
     }
     }
 
 
+    /**
+     * Get the position on the power level leaderboard of a player.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param uuid The name of the player to check
+     *
+     * @throws InvalidPlayerException if the given player does not exist in the database
+     *
+     * @return the position on the power level leaderboard
+     */
+    public static int getPlayerRankOverall(UUID uuid) {
+        return mcMMO.getDatabaseManager().readRank(getOfflineProfile(uuid).getPlayerName()).get(null);
+    }
+
     /**
     /**
      * Sets the level of a player in a specific skill type.
      * Sets the level of a player in a specific skill type.
      * </br>
      * </br>
@@ -580,10 +790,27 @@ public final class ExperienceAPI {
      * @throws InvalidSkillException if the given skill is not valid
      * @throws InvalidSkillException if the given skill is not valid
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws InvalidPlayerException if the given player does not exist in the database
      */
      */
+    @Deprecated
     public static void setLevelOffline(String playerName, String skillType, int skillLevel) {
     public static void setLevelOffline(String playerName, String skillType, int skillLevel) {
         getOfflineProfile(playerName).modifySkill(getSkillType(skillType), skillLevel);
         getOfflineProfile(playerName).modifySkill(getSkillType(skillType), skillLevel);
     }
     }
 
 
+    /**
+     * Sets the level of an offline player in a specific skill type.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param uuid The player to set the level of
+     * @param skillType The skill to set the level for
+     * @param skillLevel The value to set the level to
+     *
+     * @throws InvalidSkillException if the given skill is not valid
+     * @throws InvalidPlayerException if the given player does not exist in the database
+     */
+    public static void setLevelOffline(UUID uuid, String skillType, int skillLevel) {
+        getOfflineProfile(uuid).modifySkill(getSkillType(skillType), skillLevel);
+    }
+
     /**
     /**
      * Sets the XP of a player in a specific skill type.
      * Sets the XP of a player in a specific skill type.
      * </br>
      * </br>
@@ -613,10 +840,28 @@ public final class ExperienceAPI {
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws UnsupportedOperationException if the given skill is a child skill
      * @throws UnsupportedOperationException if the given skill is a child skill
      */
      */
+    @Deprecated
     public static void setXPOffline(String playerName, String skillType, int newValue) {
     public static void setXPOffline(String playerName, String skillType, int newValue) {
         getOfflineProfile(playerName).setSkillXpLevel(getNonChildSkillType(skillType), newValue);
         getOfflineProfile(playerName).setSkillXpLevel(getNonChildSkillType(skillType), newValue);
     }
     }
 
 
+    /**
+     * Sets the XP of an offline player in a specific skill type.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param uuid The player to set the XP of
+     * @param skillType The skill to set the XP for
+     * @param newValue The value to set the XP to
+     *
+     * @throws InvalidSkillException if the given skill is not valid
+     * @throws InvalidPlayerException if the given player does not exist in the database
+     * @throws UnsupportedOperationException if the given skill is a child skill
+     */
+    public static void setXPOffline(UUID uuid, String skillType, int newValue) {
+        getOfflineProfile(uuid).setSkillXpLevel(getNonChildSkillType(skillType), newValue);
+    }
+
     /**
     /**
      * Removes XP from a player in a specific skill type.
      * Removes XP from a player in a specific skill type.
      * </br>
      * </br>
@@ -646,12 +891,37 @@ public final class ExperienceAPI {
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws InvalidPlayerException if the given player does not exist in the database
      * @throws UnsupportedOperationException if the given skill is a child skill
      * @throws UnsupportedOperationException if the given skill is a child skill
      */
      */
+    @Deprecated
     public static void removeXPOffline(String playerName, String skillType, int xp) {
     public static void removeXPOffline(String playerName, String skillType, int xp) {
         getOfflineProfile(playerName).removeXp(getNonChildSkillType(skillType), xp);
         getOfflineProfile(playerName).removeXp(getNonChildSkillType(skillType), xp);
     }
     }
 
 
+    /**
+     * Removes XP from an offline player in a specific skill type.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param uuid The player to change the XP of
+     * @param skillType The skill to change the XP for
+     * @param xp The amount of XP to remove
+     *
+     * @throws InvalidSkillException if the given skill is not valid
+     * @throws InvalidPlayerException if the given player does not exist in the database
+     * @throws UnsupportedOperationException if the given skill is a child skill
+     */
+    public static void removeXPOffline(UUID uuid, String skillType, int xp) {
+        getOfflineProfile(uuid).removeXp(getNonChildSkillType(skillType), xp);
+    }
+
     // Utility methods follow.
     // Utility methods follow.
+    private static void addOfflineXP(UUID playerUniqueId, SkillType skill, int XP) {
+        PlayerProfile profile = getOfflineProfile(playerUniqueId);
+
+        profile.addXp(skill, XP);
+        profile.save();
+    }
 
 
+    @Deprecated
     private static void addOfflineXP(String playerName, SkillType skill, int XP) {
     private static void addOfflineXP(String playerName, SkillType skill, int XP) {
         PlayerProfile profile = getOfflineProfile(playerName);
         PlayerProfile profile = getOfflineProfile(playerName);
 
 
@@ -659,8 +929,20 @@ public final class ExperienceAPI {
         profile.scheduleAsyncSave();
         profile.scheduleAsyncSave();
     }
     }
 
 
+    private static PlayerProfile getOfflineProfile(UUID uuid) {
+        PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, false);
+
+        if (!profile.isLoaded()) {
+            throw new InvalidPlayerException();
+        }
+
+        return profile;
+    }
+
+    @Deprecated
     private static PlayerProfile getOfflineProfile(String playerName) {
     private static PlayerProfile getOfflineProfile(String playerName) {
-        PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(playerName, false);
+        UUID uuid = mcMMO.p.getServer().getOfflinePlayer(playerName).getUniqueId();
+        PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, false);
 
 
         if (!profile.isLoaded()) {
         if (!profile.isLoaded()) {
             throw new InvalidPlayerException();
             throw new InvalidPlayerException();

+ 1 - 1
src/main/java/com/gmail/nossr50/commands/database/ConvertDatabaseCommand.java

@@ -55,7 +55,7 @@ public class ConvertDatabaseCommand implements CommandExecutor {
                 UserManager.clearAll();
                 UserManager.clearAll();
 
 
                 for (Player player : mcMMO.p.getServer().getOnlinePlayers()) {
                 for (Player player : mcMMO.p.getServer().getOnlinePlayers()) {
-                    PlayerProfile profile = oldDatabase.loadPlayerProfile(player.getName(), false);
+                    PlayerProfile profile = oldDatabase.loadPlayerProfile(player.getUniqueId(), false);
 
 
                     if (profile.isLoaded()) {
                     if (profile.isLoaded()) {
                         mcMMO.getDatabaseManager().saveUser(profile);
                         mcMMO.getDatabaseManager().saveUser(profile);

+ 6 - 0
src/main/java/com/gmail/nossr50/config/HiddenConfig.java

@@ -12,6 +12,7 @@ public class HiddenConfig {
     private static int conversionRate;
     private static int conversionRate;
     private static boolean useEnchantmentBuffs;
     private static boolean useEnchantmentBuffs;
     private static boolean resendChunksAfterBlockAbility;
     private static boolean resendChunksAfterBlockAbility;
+    private static int uuidConvertAmount;
 
 
     public HiddenConfig(String fileName) {
     public HiddenConfig(String fileName) {
         HiddenConfig.fileName = fileName;
         HiddenConfig.fileName = fileName;
@@ -33,6 +34,7 @@ public class HiddenConfig {
             conversionRate = config.getInt("Options.ConversionRate", 1);
             conversionRate = config.getInt("Options.ConversionRate", 1);
             useEnchantmentBuffs = config.getBoolean("Options.EnchantmentBuffs", true);
             useEnchantmentBuffs = config.getBoolean("Options.EnchantmentBuffs", true);
             resendChunksAfterBlockAbility = config.getBoolean("Options.RefreshChunks", false);
             resendChunksAfterBlockAbility = config.getBoolean("Options.RefreshChunks", false);
+            uuidConvertAmount = config.getInt("Options.UUIDConvertAmount", 5);
         }
         }
     }
     }
 
 
@@ -51,4 +53,8 @@ public class HiddenConfig {
     public boolean resendChunksAfterBlockAbility() {
     public boolean resendChunksAfterBlockAbility() {
         return resendChunksAfterBlockAbility;
         return resendChunksAfterBlockAbility;
     }
     }
+
+    public int getUUIDConvertAmount() {
+        return uuidConvertAmount;
+    }
 }
 }

+ 32 - 1
src/main/java/com/gmail/nossr50/database/DatabaseManager.java

@@ -2,6 +2,7 @@ package com.gmail.nossr50.database;
 
 
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.UUID;
 
 
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.datatypes.database.DatabaseType;
 import com.gmail.nossr50.datatypes.database.DatabaseType;
@@ -67,19 +68,45 @@ public interface DatabaseManager {
      *
      *
      * @param playerName The name of the player to be added to the database
      * @param playerName The name of the player to be added to the database
      */
      */
-    public void newUser(String playerName);
+    public void newUser(String playerName, String uuid);
 
 
     /**
     /**
      * Load a player from the database.
      * Load a player from the database.
      *
      *
+     * @deprecated replaced by {@link #loadPlayerProfile(UUID uuid, boolean createNew)}
+     *
      * @param playerName The name of the player to load from the database
      * @param playerName The name of the player to load from the database
      * @param createNew Whether to create a new record if the player is not
      * @param createNew Whether to create a new record if the player is not
      *          found
      *          found
      * @return The player's data, or an unloaded PlayerProfile if not found
      * @return The player's data, or an unloaded PlayerProfile if not found
      *          and createNew is false
      *          and createNew is false
      */
      */
+    @Deprecated
     public PlayerProfile loadPlayerProfile(String playerName, boolean createNew);
     public PlayerProfile loadPlayerProfile(String playerName, boolean createNew);
 
 
+    /**
+     * Load a player from the database.
+     *
+     * @param uuid The uuid of the player to load from the database
+     * @param createNew Whether to create a new record if the player is not
+     *          found
+     * @return The player's data, or an unloaded PlayerProfile if not found
+     *          and createNew is false
+     */
+    public PlayerProfile loadPlayerProfile(UUID uuid, boolean createNew);
+
+    /**
+     * Load a player from the database. Attempt to use uuid, fall back on playername
+     *
+     * @param playerName The name of the player to load from the database
+     * @param uuid The uuid of the player to load from the database
+     * @param createNew Whether to create a new record if the player is not
+     *          found
+     * @return The player's data, or an unloaded PlayerProfile if not found
+     *          and createNew is false
+     */
+    public PlayerProfile loadPlayerProfile(String playerName, UUID uuid, boolean createNew);
+
     /**
     /**
      * Get all users currently stored in the database.
      * Get all users currently stored in the database.
      *
      *
@@ -95,6 +122,10 @@ public interface DatabaseManager {
      */
      */
     public void convertUsers(DatabaseManager destination);
     public void convertUsers(DatabaseManager destination);
 
 
+    public boolean saveUserUUID(String userName, UUID uuid);
+
+    public boolean saveUserUUIDs(Map<String, UUID> fetchedUUIDs);
+
     /**
     /**
      * Retrieve the type of database in use. Custom databases should return CUSTOM.
      * Retrieve the type of database in use. Custom databases should return CUSTOM.
      *
      *

+ 161 - 15
src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java

@@ -14,6 +14,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.UUID;
 
 
 import org.bukkit.OfflinePlayer;
 import org.bukkit.OfflinePlayer;
 
 
@@ -26,6 +27,7 @@ import com.gmail.nossr50.datatypes.database.UpgradeType;
 import com.gmail.nossr50.datatypes.player.PlayerProfile;
 import com.gmail.nossr50.datatypes.player.PlayerProfile;
 import com.gmail.nossr50.datatypes.skills.AbilityType;
 import com.gmail.nossr50.datatypes.skills.AbilityType;
 import com.gmail.nossr50.datatypes.skills.SkillType;
 import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.runnables.database.UUIDUpdateAsyncTask;
 import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.StringUtils;
 import com.gmail.nossr50.util.StringUtils;
 
 
@@ -44,6 +46,10 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
         usersFile = new File(mcMMO.getUsersFilePath());
         usersFile = new File(mcMMO.getUsersFilePath());
         checkStructure();
         checkStructure();
         updateLeaderboards();
         updateLeaderboards();
+
+        if (mcMMO.getUpgradeManager().shouldUpgrade(UpgradeType.ADD_UUIDS)) {
+            new UUIDUpdateAsyncTask(mcMMO.p, getStoredUsers()).runTaskAsynchronously(mcMMO.p);
+        }
     }
     }
 
 
     public void purgePowerlessUsers() {
     public void purgePowerlessUsers() {
@@ -209,6 +215,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
 
 
     public boolean saveUser(PlayerProfile profile) {
     public boolean saveUser(PlayerProfile profile) {
         String playerName = profile.getPlayerName();
         String playerName = profile.getPlayerName();
+        UUID uuid = profile.getUniqueId();
 
 
         BufferedReader in = null;
         BufferedReader in = null;
         FileWriter out = null;
         FileWriter out = null;
@@ -223,8 +230,9 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
 
 
                 // While not at the end of the file
                 // While not at the end of the file
                 while ((line = in.readLine()) != null) {
                 while ((line = in.readLine()) != null) {
-                    // Read the line in and copy it to the output it's not the player we want to edit
-                    if (!line.split(":")[0].equalsIgnoreCase(playerName)) {
+                    // Read the line in and copy it to the output if it's not the player we want to edit
+                    String[] character = line.split(":");
+                    if (!character[41].equalsIgnoreCase(uuid.toString()) && !character[0].equalsIgnoreCase(playerName)) {
                         writer.append(line).append("\r\n");
                         writer.append(line).append("\r\n");
                     }
                     }
                     else {
                     else {
@@ -271,6 +279,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                         writer.append(mobHealthbarType == null ? Config.getInstance().getMobHealthbarDefault().toString() : mobHealthbarType.toString()).append(":");
                         writer.append(mobHealthbarType == null ? Config.getInstance().getMobHealthbarDefault().toString() : mobHealthbarType.toString()).append(":");
                         writer.append(profile.getSkillLevel(SkillType.ALCHEMY)).append(":");
                         writer.append(profile.getSkillLevel(SkillType.ALCHEMY)).append(":");
                         writer.append(profile.getSkillXpLevel(SkillType.ALCHEMY)).append(":");
                         writer.append(profile.getSkillXpLevel(SkillType.ALCHEMY)).append(":");
+                        writer.append(uuid.toString()).append(":");
                         writer.append("\r\n");
                         writer.append("\r\n");
                     }
                     }
                 }
                 }
@@ -313,7 +322,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
         return skills;
         return skills;
     }
     }
 
 
-    public void newUser(String playerName) {
+    public void newUser(String playerName, String uuid) {
         BufferedWriter out = null;
         BufferedWriter out = null;
         synchronized (fileWritingLock) {
         synchronized (fileWritingLock) {
             try {
             try {
@@ -362,7 +371,8 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                 out.append(Config.getInstance().getMobHealthbarDefault().toString()).append(":"); // Mob Healthbar HUD
                 out.append(Config.getInstance().getMobHealthbarDefault().toString()).append(":"); // Mob Healthbar HUD
                 out.append("0:"); // Alchemy
                 out.append("0:"); // Alchemy
                 out.append("0:"); // AlchemyXp
                 out.append("0:"); // AlchemyXp
-                
+                out.append(uuid).append(":"); // UUID
+
                 // Add more in the same format as the line above
                 // Add more in the same format as the line above
 
 
                 out.newLine();
                 out.newLine();
@@ -376,7 +386,20 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
         }
         }
     }
     }
 
 
+    @Deprecated
     public PlayerProfile loadPlayerProfile(String playerName, boolean create) {
     public PlayerProfile loadPlayerProfile(String playerName, boolean create) {
+        return loadPlayerProfile(playerName, "", create);
+    }
+
+    public PlayerProfile loadPlayerProfile(UUID uuid, boolean create) {
+        return loadPlayerProfile("", uuid.toString(), create);
+    }
+
+    public PlayerProfile loadPlayerProfile(String playerName, UUID uuid, boolean create) {
+        return loadPlayerProfile(playerName, uuid.toString(), create);
+    }
+
+    private PlayerProfile loadPlayerProfile(String playerName, String uuid, boolean create) {
         BufferedReader in = null;
         BufferedReader in = null;
         String usersFilePath = mcMMO.getUsersFilePath();
         String usersFilePath = mcMMO.getUsersFilePath();
 
 
@@ -390,17 +413,28 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                     // Find if the line contains the player we want.
                     // Find if the line contains the player we want.
                     String[] character = line.split(":");
                     String[] character = line.split(":");
 
 
-                    if (!character[0].equalsIgnoreCase(playerName)) {
+                    if (!character[41].equalsIgnoreCase(uuid) && !character[0].equalsIgnoreCase(playerName)) {
                         continue;
                         continue;
                     }
                     }
 
 
+                    // Update playerName in database after name change
+                    if (!character[0].equalsIgnoreCase(playerName)) {
+                        mcMMO.p.debug("Name change detected: " + character[0] + " => " + playerName);
+                        character[0] = playerName;
+                    }
+
                     return loadFromLine(character);
                     return loadFromLine(character);
                 }
                 }
 
 
                 // Didn't find the player, create a new one
                 // Didn't find the player, create a new one
                 if (create) {
                 if (create) {
-                    newUser(playerName);
-                    return new PlayerProfile(playerName, true);
+                    if (uuid.isEmpty()) {
+                        newUser(playerName, uuid);
+                        return new PlayerProfile(playerName, true);
+                    }
+
+                    newUser(playerName, uuid);
+                    return new PlayerProfile(playerName, UUID.fromString(uuid), true);
                 }
                 }
             }
             }
             catch (Exception e) {
             catch (Exception e) {
@@ -421,7 +455,11 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
         }
         }
 
 
         // Return unloaded profile
         // Return unloaded profile
-        return new PlayerProfile(playerName);
+        if (uuid.isEmpty()) {
+            return new PlayerProfile(playerName);
+        }
+
+        return new PlayerProfile(playerName, UUID.fromString(uuid));
     }
     }
 
 
     public void convertUsers(DatabaseManager destination) {
     public void convertUsers(DatabaseManager destination) {
@@ -458,6 +496,91 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
         }
         }
     }
     }
 
 
+    public boolean saveUserUUID(String userName, UUID uuid) {
+        boolean worked = false;
+
+        BufferedReader in = null;
+        FileWriter out = null;
+        String usersFilePath = mcMMO.getUsersFilePath();
+
+        synchronized (fileWritingLock) {
+            try {
+                in = new BufferedReader(new FileReader(usersFilePath));
+                StringBuilder writer = new StringBuilder();
+                String line;
+
+                while ((line = in.readLine()) != null) {
+                    String[] character = line.split(":");
+                    if (!worked && character[0].equalsIgnoreCase(userName)) {
+                        if (character.length < 42) {
+                            mcMMO.p.getLogger().severe("Could not update UUID for " + userName + "!");
+                            mcMMO.p.getLogger().severe("Database entry is invalid.");
+                            break;
+                        }
+
+                        line = line.replace(character[41], uuid.toString());
+                        worked = true;
+                    }
+
+                    writer.append(line).append("\r\n");
+                }
+
+                out = new FileWriter(usersFilePath); // Write out the new file
+                out.write(writer.toString());
+            }
+            catch (Exception e) {
+                mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
+            }
+            finally {
+                tryClose(in);
+                tryClose(out);
+            }
+        }
+
+        return worked;
+    }
+
+    public boolean saveUserUUIDs(Map<String, UUID> fetchedUUIDs) {
+        BufferedReader in = null;
+        FileWriter out = null;
+        String usersFilePath = mcMMO.getUsersFilePath();
+
+        synchronized (fileWritingLock) {
+            try {
+                in = new BufferedReader(new FileReader(usersFilePath));
+                StringBuilder writer = new StringBuilder();
+                String line;
+
+                while (((line = in.readLine()) != null) && !fetchedUUIDs.isEmpty()) {
+                    String[] character = line.split(":");
+                    if (fetchedUUIDs.containsKey(character[0])) {
+                        if (character.length < 42) {
+                            mcMMO.p.getLogger().severe("Could not update UUID for " + character[0] + "!");
+                            mcMMO.p.getLogger().severe("Database entry is invalid.");
+                            return false;
+                        }
+
+                        line = line.replace(character[41], fetchedUUIDs.remove(character[0]).toString());
+                    }
+
+                    writer.append(line).append("\r\n");
+                }
+
+                out = new FileWriter(usersFilePath); // Write out the new file
+                out.write(writer.toString());
+            }
+            catch (Exception e) {
+                mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString());
+            }
+            finally {
+                tryClose(in);
+                tryClose(out);
+            }
+        }
+
+        return true;
+    }
+
     public List<String> getStoredUsers() {
     public List<String> getStoredUsers() {
         ArrayList<String> users = new ArrayList<String>();
         ArrayList<String> users = new ArrayList<String>();
         BufferedReader in = null;
         BufferedReader in = null;
@@ -598,6 +721,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                     in = new BufferedReader(new FileReader(usersFilePath));
                     in = new BufferedReader(new FileReader(usersFilePath));
                     StringBuilder writer = new StringBuilder();
                     StringBuilder writer = new StringBuilder();
                     String line;
                     String line;
+                    HashSet<String> usernames = new HashSet<String>();
                     HashSet<String> players = new HashSet<String>();
                     HashSet<String> players = new HashSet<String>();
 
 
                     while ((line = in.readLine()) != null) {
                     while ((line = in.readLine()) != null) {
@@ -612,8 +736,13 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                         }
                         }
                         String[] character = line.split(":");
                         String[] character = line.split(":");
 
 
+                        // Prevent the same username from being present multiple times
+                        if (!usernames.add(character[0])) {
+                            continue;
+                        }
+
                         // Prevent the same player from being present multiple times
                         // Prevent the same player from being present multiple times
-                        if (!players.add(character[0])) {
+                        if (character.length == 42 && (!character[41].isEmpty() && !players.add(character[41]))) {
                             continue;
                             continue;
                         }
                         }
 
 
@@ -645,7 +774,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                         }
                         }
 
 
                         // If they're valid, rewrite them to the file.
                         // If they're valid, rewrite them to the file.
-                        if (character.length == 41) {
+                        if (character.length == 42) {
                             writer.append(line).append("\r\n");
                             writer.append(line).append("\r\n");
                             continue;
                             continue;
                         }
                         }
@@ -699,16 +828,25 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                                 oldVersion = "1.4.08";
                                 oldVersion = "1.4.08";
                             }
                             }
                         }
                         }
+                        if (character.length <= 41) {
+                            // Addition of UUIDs
+                            // Version 1.5.01
+                            // Add a space because otherwise it gets removed
+                            newLine.append(" :");
+                            if (oldVersion == null) {
+                                oldVersion = "1.5.01";
+                            }
+                        }
 
 
                         // Remove any blanks that shouldn't be there, and validate the other fields
                         // Remove any blanks that shouldn't be there, and validate the other fields
                         String[] newCharacter = newLine.toString().split(":");
                         String[] newCharacter = newLine.toString().split(":");
                         boolean corrupted = false;
                         boolean corrupted = false;
 
 
                         for (int i = 0; i < newCharacter.length; i++) {
                         for (int i = 0; i < newCharacter.length; i++) {
-                            if (newCharacter[i].isEmpty() && !(i == 2 || i == 3 || i == 23 || i == 33)) {
+                            if (newCharacter[i].isEmpty() && !(i == 2 || i == 3 || i == 23 || i == 33 || i == 41)) {
                                 corrupted = true;
                                 corrupted = true;
 
 
-                                if (newCharacter.length != 41) {
+                                if (newCharacter.length != 42) {
                                     newCharacter = (String[]) ArrayUtils.remove(newCharacter, i);
                                     newCharacter = (String[]) ArrayUtils.remove(newCharacter, i);
                                 }
                                 }
                                 else {
                                 else {
@@ -729,7 +867,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                                 newCharacter[i] = Config.getInstance().getMobHealthbarDefault().toString();
                                 newCharacter[i] = Config.getInstance().getMobHealthbarDefault().toString();
                             }
                             }
 
 
-                            if (!StringUtils.isInt(newCharacter[i]) && !(i == 0 || i == 2 || i == 3 || i == 23 || i == 33 || i == 38)) {
+                            if (!StringUtils.isInt(newCharacter[i]) && !(i == 0 || i == 2 || i == 3 || i == 23 || i == 33 || i == 38 || i == 41)) {
                                 corrupted = true;
                                 corrupted = true;
                                 newCharacter[i] = "0";
                                 newCharacter[i] = "0";
                             }
                             }
@@ -740,7 +878,7 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
                         }
                         }
 
 
                         if (oldVersion != null) {
                         if (oldVersion != null) {
-                            mcMMO.p.debug("Updating database line for player " + character[0] + " from before version " + oldVersion);
+                            mcMMO.p.debug("Updating database line from before version " + oldVersion + " for player " + character[0]);
                         }
                         }
 
 
                         if (corrupted || oldVersion != null) {
                         if (corrupted || oldVersion != null) {
@@ -869,7 +1007,15 @@ public final class FlatfileDatabaseManager implements DatabaseManager {
             mobHealthbarType = Config.getInstance().getMobHealthbarDefault();
             mobHealthbarType = Config.getInstance().getMobHealthbarDefault();
         }
         }
 
 
-        return new PlayerProfile(character[0], skills, skillsXp, skillsDATS, mobHealthbarType);
+        UUID uuid;
+        try {
+            uuid = UUID.fromString(character[41]);
+        }
+        catch (Exception e) {
+            uuid = null;
+        }
+
+        return new PlayerProfile(character[0], uuid, skills, skillsXp, skillsDATS, mobHealthbarType);
     }
     }
 
 
     private Map<SkillType, Integer> getSkillMapFromLine(String[] character) {
     private Map<SkillType, Integer> getSkillMapFromLine(String[] character) {

+ 473 - 115
src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java

@@ -13,6 +13,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Properties;
+import java.util.UUID;
 
 
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.Config;
@@ -25,6 +26,7 @@ import com.gmail.nossr50.datatypes.skills.AbilityType;
 import com.gmail.nossr50.datatypes.skills.SkillType;
 import com.gmail.nossr50.datatypes.skills.SkillType;
 import com.gmail.nossr50.runnables.database.SQLDatabaseKeepaliveTask;
 import com.gmail.nossr50.runnables.database.SQLDatabaseKeepaliveTask;
 import com.gmail.nossr50.runnables.database.SQLReconnectTask;
 import com.gmail.nossr50.runnables.database.SQLReconnectTask;
+import com.gmail.nossr50.runnables.database.UUIDUpdateAsyncTask;
 import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.Misc;
 
 
 public final class SQLDatabaseManager implements DatabaseManager {
 public final class SQLDatabaseManager implements DatabaseManager {
@@ -123,7 +125,7 @@ public final class SQLDatabaseManager implements DatabaseManager {
 
 
         int userId = readId(profile.getPlayerName());
         int userId = readId(profile.getPlayerName());
         if (userId == -1) {
         if (userId == -1) {
-            newUser(profile.getPlayerName());
+            newUser(profile.getPlayerName(), profile.getUniqueId().toString());
             userId = readId(profile.getPlayerName());
             userId = readId(profile.getPlayerName());
             if (userId == -1) {
             if (userId == -1) {
                 return false;
                 return false;
@@ -132,6 +134,7 @@ public final class SQLDatabaseManager implements DatabaseManager {
         boolean success = true;
         boolean success = true;
         MobHealthbarType mobHealthbarType = profile.getMobHealthbarType();
         MobHealthbarType mobHealthbarType = profile.getMobHealthbarType();
 
 
+        success &= saveUniqueId(userId, profile.getUniqueId().toString());
         success &= saveLogin(userId, ((int) (System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR)));
         success &= saveLogin(userId, ((int) (System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR)));
         success &= saveHuds(userId, (mobHealthbarType == null ? Config.getInstance().getMobHealthbarDefault().toString() : mobHealthbarType.toString()));
         success &= saveHuds(userId, (mobHealthbarType == null ? Config.getInstance().getMobHealthbarDefault().toString() : mobHealthbarType.toString()));
         success &= saveLongs(
         success &= saveLongs(
@@ -317,7 +320,7 @@ public final class SQLDatabaseManager implements DatabaseManager {
         return skills;
         return skills;
     }
     }
 
 
-    public void newUser(String playerName) {
+    public void newUser(String playerName, String uuid) {
         if (!checkConnected()) {
         if (!checkConnected()) {
             return;
             return;
         }
         }
@@ -325,9 +328,10 @@ public final class SQLDatabaseManager implements DatabaseManager {
         PreparedStatement statement = null;
         PreparedStatement statement = null;
 
 
         try {
         try {
-            statement = connection.prepareStatement("INSERT INTO " + tablePrefix + "users (user, lastlogin) VALUES (?, ?)", Statement.RETURN_GENERATED_KEYS);
+            statement = connection.prepareStatement("INSERT INTO " + tablePrefix + "users (user, uuid, lastlogin) VALUES (?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
             statement.setString(1, playerName);
             statement.setString(1, playerName);
-            statement.setLong(2, System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR);
+            statement.setString(2, uuid);
+            statement.setLong(3, System.currentTimeMillis() / Misc.TIME_CONVERSION_FACTOR);
             statement.execute();
             statement.execute();
 
 
             int id = readId(playerName);
             int id = readId(playerName);
@@ -348,13 +352,18 @@ public final class SQLDatabaseManager implements DatabaseManager {
         }
         }
     }
     }
 
 
-    public PlayerProfile loadPlayerProfile(String playerName, boolean create) {
-        return loadPlayerProfile(playerName, create, true);
-    }
-
-    private PlayerProfile loadPlayerProfile(String playerName, boolean create, boolean retry) {
+    /**
+     * This is a fallback method to provide the old way of getting a PlayerProfile
+     * in case there is no UUID match found
+     */
+    private PlayerProfile loadPlayerNameProfile(String playerName, String uuid, boolean create, boolean retry) {
         if (!checkConnected()) {
         if (!checkConnected()) {
-            return new PlayerProfile(playerName, false); // return fake profile if not connected
+            // return fake profile if not connected
+            if (uuid.isEmpty()) {
+                return new PlayerProfile(playerName, false);
+            }
+
+            return new PlayerProfile(playerName, UUID.fromString(uuid), false);
         }
         }
 
 
         PreparedStatement statement = null;
         PreparedStatement statement = null;
@@ -365,7 +374,7 @@ public final class SQLDatabaseManager implements DatabaseManager {
                             + "s.taming, s.mining, s.repair, s.woodcutting, s.unarmed, s.herbalism, s.excavation, s.archery, s.swords, s.axes, s.acrobatics, s.fishing, s.alchemy, "
                             + "s.taming, s.mining, s.repair, s.woodcutting, s.unarmed, s.herbalism, s.excavation, s.archery, s.swords, s.axes, s.acrobatics, s.fishing, s.alchemy, "
                             + "e.taming, e.mining, e.repair, e.woodcutting, e.unarmed, e.herbalism, e.excavation, e.archery, e.swords, e.axes, e.acrobatics, e.fishing, e.alchemy, "
                             + "e.taming, e.mining, e.repair, e.woodcutting, e.unarmed, e.herbalism, e.excavation, e.archery, e.swords, e.axes, e.acrobatics, e.fishing, e.alchemy, "
                             + "c.taming, c.mining, c.repair, c.woodcutting, c.unarmed, c.herbalism, c.excavation, c.archery, c.swords, c.axes, c.acrobatics, c.blast_mining, "
                             + "c.taming, c.mining, c.repair, c.woodcutting, c.unarmed, c.herbalism, c.excavation, c.archery, c.swords, c.axes, c.acrobatics, c.blast_mining, "
-                            + "h.mobhealthbar "
+                            + "h.mobhealthbar, u.uuid "
                             + "FROM " + tablePrefix + "users u "
                             + "FROM " + tablePrefix + "users u "
                             + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) "
                             + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) "
                             + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) "
                             + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) "
@@ -415,8 +424,8 @@ public final class SQLDatabaseManager implements DatabaseManager {
         if (id == -1) {
         if (id == -1) {
             // There is no such user
             // There is no such user
             if (create) {
             if (create) {
-                newUser(playerName);
-                return loadPlayerProfile(playerName, false, false);
+                newUser(playerName, uuid);
+                return loadPlayerNameProfile(playerName, uuid, false, false);
             }
             }
 
 
             // Return unloaded profile if can't create
             // Return unloaded profile if can't create
@@ -425,7 +434,113 @@ public final class SQLDatabaseManager implements DatabaseManager {
         // There is such a user
         // There is such a user
         writeMissingRows(id);
         writeMissingRows(id);
         // Retry, and abort on re-failure
         // Retry, and abort on re-failure
-        return loadPlayerProfile(playerName, create, false);
+        return loadPlayerNameProfile(playerName, uuid, create, false);
+    }
+
+    @Deprecated
+    public PlayerProfile loadPlayerProfile(String playerName, boolean create) {
+        return loadPlayerProfile(playerName, "", create, true);
+    }
+
+    public PlayerProfile loadPlayerProfile(UUID uuid, boolean create) {
+        return loadPlayerProfile("", uuid.toString(), create, true);
+    }
+
+    public PlayerProfile loadPlayerProfile(String playerName, UUID uuid, boolean create) {
+        return loadPlayerProfile(playerName, uuid.toString(), create, true);
+    }
+
+    private PlayerProfile loadPlayerProfile(String playerName, String uuid, boolean create, boolean retry) {
+        if (!checkConnected()) {
+            // return fake profile if not connected
+            if (uuid.isEmpty()) {
+                return new PlayerProfile(playerName, false);
+            }
+
+            return new PlayerProfile(playerName, UUID.fromString(uuid), false);
+        }
+
+        PreparedStatement statement = null;
+
+        try {
+            statement = connection.prepareStatement(
+                    "SELECT "
+                            + "s.taming, s.mining, s.repair, s.woodcutting, s.unarmed, s.herbalism, s.excavation, s.archery, s.swords, s.axes, s.acrobatics, s.fishing, s.alchemy, "
+                            + "e.taming, e.mining, e.repair, e.woodcutting, e.unarmed, e.herbalism, e.excavation, e.archery, e.swords, e.axes, e.acrobatics, e.fishing, e.alchemy, "
+                            + "c.taming, c.mining, c.repair, c.woodcutting, c.unarmed, c.herbalism, c.excavation, c.archery, c.swords, c.axes, c.acrobatics, c.blast_mining, "
+                            + "h.mobhealthbar, u.uuid "
+                            + "FROM " + tablePrefix + "users u "
+                            + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) "
+                            + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) "
+                            + "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) "
+                            + "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) "
+                            + "WHERE u.UUID = ?");
+            statement.setString(1, uuid);
+
+            ResultSet result = statement.executeQuery();
+
+            if (result.next()) {
+                try {
+                    PlayerProfile profile = loadFromResult(playerName, result);
+                    result.close();
+
+                    if (!playerName.isEmpty() && !profile.getPlayerName().isEmpty()) {
+                        statement = connection.prepareStatement(
+                                "UPDATE `" + tablePrefix + "users` "
+                                        + "SET user = ? "
+                                        + "WHERE UUID = ?");
+                        statement.setString(1, playerName);
+                        statement.setString(2, uuid);
+                        result = statement.executeQuery();
+                        result.close();
+                    }
+
+                    return profile;
+                }
+                catch (SQLException e) {
+                }
+            }
+            result.close();
+        }
+        catch (SQLException ex) {
+            printErrors(ex);
+        }
+        finally {
+            if (statement != null) {
+                try {
+                    statement.close();
+                }
+                catch (SQLException e) {
+                    // Ignore
+                }
+            }
+        }
+
+        // Problem, nothing was returned
+
+        // Retry the old fashioned way if this is second time around
+        if (!retry) {
+            return loadPlayerNameProfile(playerName, uuid, create, true);
+        }
+
+        // First, read User Id - this is to check for orphans
+
+        int id = readId(playerName);
+
+        if (id == -1) {
+            // There is no such user
+            if (create) {
+                newUser(playerName, uuid);
+                return loadPlayerProfile(playerName, uuid, false, false);
+            }
+
+            // Return unloaded profile if can't create
+            return new PlayerProfile(playerName, false);
+        }
+        // There is such a user
+        writeMissingRows(id);
+        // Retry, and abort on re-failure
+        return loadPlayerProfile(playerName, uuid, create, false);
     }
     }
 
 
     public void convertUsers(DatabaseManager destination) {
     public void convertUsers(DatabaseManager destination) {
@@ -441,7 +556,7 @@ public final class SQLDatabaseManager implements DatabaseManager {
                             + "s.taming, s.mining, s.repair, s.woodcutting, s.unarmed, s.herbalism, s.excavation, s.archery, s.swords, s.axes, s.acrobatics, s.fishing, s.alchemy, "
                             + "s.taming, s.mining, s.repair, s.woodcutting, s.unarmed, s.herbalism, s.excavation, s.archery, s.swords, s.axes, s.acrobatics, s.fishing, s.alchemy, "
                             + "e.taming, e.mining, e.repair, e.woodcutting, e.unarmed, e.herbalism, e.excavation, e.archery, e.swords, e.axes, e.acrobatics, e.fishing, e.alchemy, "
                             + "e.taming, e.mining, e.repair, e.woodcutting, e.unarmed, e.herbalism, e.excavation, e.archery, e.swords, e.axes, e.acrobatics, e.fishing, e.alchemy, "
                             + "c.taming, c.mining, c.repair, c.woodcutting, c.unarmed, c.herbalism, c.excavation, c.archery, c.swords, c.axes, c.acrobatics, c.blast_mining, "
                             + "c.taming, c.mining, c.repair, c.woodcutting, c.unarmed, c.herbalism, c.excavation, c.archery, c.swords, c.axes, c.acrobatics, c.blast_mining, "
-                            + "h.mobhealthbar "
+                            + "h.mobhealthbar, u.uuid "
                             + "FROM " + tablePrefix + "users u "
                             + "FROM " + tablePrefix + "users u "
                             + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) "
                             + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) "
                             + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) "
                             + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) "
@@ -483,6 +598,88 @@ public final class SQLDatabaseManager implements DatabaseManager {
 
 
     }
     }
 
 
+    public boolean saveUserUUID(String userName, UUID uuid) {
+        if (!checkConnected()) {
+            // return false
+            return false;
+        }
+
+        PreparedStatement statement = null;
+
+        try {
+            statement = connection.prepareStatement(
+                    "UPDATE `" + tablePrefix + "users` SET "
+                            + "  uuid = ? WHERE user = ?");
+            statement.setString(1, uuid.toString());
+            statement.setString(2, userName);
+            statement.execute();
+            return true;
+        }
+        catch (SQLException ex) {
+            printErrors(ex);
+            return false;
+        }
+        finally {
+            if (statement != null) {
+                try {
+                    statement.close();
+                }
+                catch (SQLException e) {
+                    // Ignore
+                }
+            }
+        }
+
+        // Problem, nothing was returned
+    }
+
+    public boolean saveUserUUIDs(Map<String, UUID> fetchedUUIDs) {
+        if (!checkConnected()) {
+            return false;
+        }
+
+        PreparedStatement statement = null;
+        int count = 0;
+
+        try {
+            statement = connection.prepareStatement("UPDATE " + tablePrefix + "users SET uuid = ? WHERE user = ?");
+
+            for (Map.Entry<String, UUID> entry : fetchedUUIDs.entrySet()) {
+                statement.setString(1, entry.getValue().toString());
+                statement.setString(2, entry.getKey());
+
+                statement.addBatch();
+
+                count++;
+
+                if ((count % 500) == 0) {
+                    statement.executeBatch();
+                    count = 0;
+                }
+            }
+
+            if (count != 0) {
+                statement.executeBatch();
+            }
+
+            return true;
+        }
+        catch (SQLException ex) {
+            printErrors(ex);
+            return false;
+        }
+        finally {
+            if (statement != null) {
+                try {
+                    statement.close();
+                }
+                catch (SQLException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
     /**
     /**
      * Check connection status and re-establish if dead or stale.
      * Check connection status and re-establish if dead or stale.
      * <p/>
      * <p/>
@@ -651,6 +848,7 @@ public final class SQLDatabaseManager implements DatabaseManager {
         write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "users` ("
         write("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "users` ("
                 + "`id` int(10) unsigned NOT NULL AUTO_INCREMENT,"
                 + "`id` int(10) unsigned NOT NULL AUTO_INCREMENT,"
                 + "`user` varchar(40) NOT NULL,"
                 + "`user` varchar(40) NOT NULL,"
+                + "`uuid` varchar(36) NOT NULL DEFAULT '',"
                 + "`lastlogin` int(32) unsigned NOT NULL,"
                 + "`lastlogin` int(32) unsigned NOT NULL,"
                 + "PRIMARY KEY (`id`),"
                 + "PRIMARY KEY (`id`),"
                 + "UNIQUE KEY `user` (`user`)) DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;");
                 + "UNIQUE KEY `user` (`user`)) DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;");
@@ -737,122 +935,43 @@ public final class SQLDatabaseManager implements DatabaseManager {
         }
         }
 
 
         Statement statement = null;
         Statement statement = null;
-        ResultSet resultSet = null;
 
 
         try {
         try {
             statement = connection.createStatement();
             statement = connection.createStatement();
 
 
             switch (upgrade) {
             switch (upgrade) {
                 case ADD_FISHING:
                 case ADD_FISHING:
-                    try {
-                        statement.executeQuery("SELECT `fishing` FROM `" + tablePrefix + "skills` LIMIT 1");
-                    }
-                    catch (SQLException ex) {
-                        mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Fishing...");
-                        statement.executeQuery("ALTER TABLE `" + tablePrefix + "skills` ADD `fishing` int(10) NOT NULL DEFAULT '0'");
-                        statement.executeQuery("ALTER TABLE `" + tablePrefix + "experience` ADD `fishing` int(10) NOT NULL DEFAULT '0'");
-                    }
+                    checkUpgradeAddFishing(statement);
                     break;
                     break;
 
 
                 case ADD_BLAST_MINING_COOLDOWN:
                 case ADD_BLAST_MINING_COOLDOWN:
-                    try {
-                        statement.executeQuery("SELECT `blast_mining` FROM `" + tablePrefix + "cooldowns` LIMIT 1");
-                    }
-                    catch (SQLException ex) {
-                        mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Blast Mining...");
-                        statement.executeQuery("ALTER TABLE `" + tablePrefix + "cooldowns` ADD `blast_mining` int(32) NOT NULL DEFAULT '0'");
-                    }
+                    checkUpgradeAddBlastMiningCooldown(statement);
                     break;
                     break;
 
 
                 case ADD_SQL_INDEXES:
                 case ADD_SQL_INDEXES:
-                    resultSet = statement.executeQuery("SHOW INDEX FROM `" + tablePrefix + "skills` WHERE `Key_name` LIKE 'idx\\_%'");
-                    resultSet.last();
-
-                    if (resultSet.getRow() != SkillType.NON_CHILD_SKILLS.size()) {
-                        mcMMO.p.getLogger().info("Indexing tables, this may take a while on larger databases");
-
-                        for (SkillType skill : SkillType.NON_CHILD_SKILLS) {
-                            String skill_name = skill.name().toLowerCase();
-
-                            try {
-                                statement.executeUpdate("ALTER TABLE `" + tablePrefix + "skills` ADD INDEX `idx_" + skill_name + "` (`" + skill_name + "`) USING BTREE");
-                            }
-                            catch (SQLException ex) {
-                                // Ignore
-                            }
-                        }
-                    }
+                    checkUpgradeAddSQLIndexes(statement);
                     break;
                     break;
 
 
                 case ADD_MOB_HEALTHBARS:
                 case ADD_MOB_HEALTHBARS:
-                    try {
-                        statement.executeQuery("SELECT `mobhealthbar` FROM `" + tablePrefix + "huds` LIMIT 1");
-                    }
-                    catch (SQLException ex) {
-                        mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for mob healthbars...");
-                        statement.executeQuery("ALTER TABLE `" + tablePrefix + "huds` ADD `mobhealthbar` varchar(50) NOT NULL DEFAULT '" + Config.getInstance().getMobHealthbarDefault() + "'");
-                    }
+                    checkUpgradeAddMobHealthbars(statement);
                     break;
                     break;
 
 
                 case DROP_SQL_PARTY_NAMES:
                 case DROP_SQL_PARTY_NAMES:
-                    try {
-                        resultSet = statement.executeQuery("SELECT * FROM `" + tablePrefix + "users` LIMIT 1");
-
-                        ResultSetMetaData rsmeta = resultSet.getMetaData();
-                        boolean column_exists = false;
-
-                        for (int i = 1; i <= rsmeta.getColumnCount(); i++) {
-                            if (rsmeta.getColumnName(i).equalsIgnoreCase("party")) {
-                                column_exists = true;
-                                break;
-                            }
-                        }
-
-                        if (column_exists) {
-                            mcMMO.p.getLogger().info("Removing party name from users table...");
-                            statement.executeQuery("ALTER TABLE `" + tablePrefix + "users` DROP COLUMN `party`");
-                        }
-                    }
-                    catch (SQLException ex) {
-                        // Ignore
-                    }
+                    checkUpgradeDropPartyNames(statement);
                     break;
                     break;
 
 
                 case DROP_SPOUT:
                 case DROP_SPOUT:
-                    try {
-                        resultSet = statement.executeQuery("SELECT * FROM `" + tablePrefix + "huds` LIMIT 1");
-
-                        ResultSetMetaData rsmeta = resultSet.getMetaData();
-                        boolean column_exists = false;
-
-                        for (int i = 1; i <= rsmeta.getColumnCount(); i++) {
-                            if (rsmeta.getColumnName(i).equalsIgnoreCase("hudtype")) {
-                                column_exists = true;
-                                break;
-                            }
-                        }
-
-                        if (column_exists) {
-                            mcMMO.p.getLogger().info("Removing Spout HUD type from huds table...");
-                            statement.executeQuery("ALTER TABLE `" + tablePrefix + "huds` DROP COLUMN `hudtype`");
-                        }
-                    }
-                    catch (SQLException ex) {
-                        // Ignore
-                    }
+                    checkUpgradeDropSpout(statement);
                     break;
                     break;
 
 
                 case ADD_ALCHEMY:
                 case ADD_ALCHEMY:
-                    try {
-                        statement.executeQuery("SELECT `alchemy` FROM `" + tablePrefix + "skills` LIMIT 1");
-                    }
-                    catch (SQLException ex) {
-                        mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Alchemy...");
-                        statement.executeQuery("ALTER TABLE `" + tablePrefix + "skills` ADD `alchemy` int(10) NOT NULL DEFAULT '0'");
-                        statement.executeQuery("ALTER TABLE `" + tablePrefix + "experience` ADD `alchemy` int(10) NOT NULL DEFAULT '0'");
-                    }
+                    checkUpgradeAddAlchemy(statement);
                     break;
                     break;
 
 
+                case ADD_UUIDS:
+                    checkUpgradeAddUUIDs(statement);
+                    return;
+
                 default:
                 default:
                     break;
                     break;
 
 
@@ -861,17 +980,9 @@ public final class SQLDatabaseManager implements DatabaseManager {
             mcMMO.getUpgradeManager().setUpgradeCompleted(upgrade);
             mcMMO.getUpgradeManager().setUpgradeCompleted(upgrade);
         }
         }
         catch (SQLException ex) {
         catch (SQLException ex) {
-
+            printErrors(ex);
         }
         }
         finally {
         finally {
-            if (resultSet != null) {
-                try {
-                    resultSet.close();
-                }
-                catch (SQLException e) {
-                    // Ignore
-                }
-            }
             if (statement != null) {
             if (statement != null) {
                 try {
                 try {
                     statement.close();
                     statement.close();
@@ -1166,6 +1277,32 @@ public final class SQLDatabaseManager implements DatabaseManager {
         return id;
         return id;
     }
     }
 
 
+    private boolean saveUniqueId(int id, String uuid) {
+        PreparedStatement statement = null;
+
+        try {
+            statement = connection.prepareStatement("UPDATE " + tablePrefix + "users SET uuid = ? WHERE id = ?");
+            statement.setString(1, uuid);
+            statement.setInt(2, id);
+            statement.execute();
+            return true;
+        }
+        catch (SQLException ex) {
+            printErrors(ex);
+            return false;
+        }
+        finally {
+            if (statement != null) {
+                try {
+                    statement.close();
+                }
+                catch (SQLException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
     private boolean saveLogin(int id, long login) {
     private boolean saveLogin(int id, long login) {
         PreparedStatement statement = null;
         PreparedStatement statement = null;
 
 
@@ -1223,6 +1360,7 @@ public final class SQLDatabaseManager implements DatabaseManager {
         Map<SkillType, Float> skillsXp = new HashMap<SkillType, Float>();     // Skill & XP
         Map<SkillType, Float> skillsXp = new HashMap<SkillType, Float>();     // Skill & XP
         Map<AbilityType, Integer> skillsDATS = new HashMap<AbilityType, Integer>(); // Ability & Cooldown
         Map<AbilityType, Integer> skillsDATS = new HashMap<AbilityType, Integer>(); // Ability & Cooldown
         MobHealthbarType mobHealthbarType;
         MobHealthbarType mobHealthbarType;
+        UUID uuid;
 
 
         final int OFFSET_SKILLS = 0; // TODO update these numbers when the query changes (a new skill is added)
         final int OFFSET_SKILLS = 0; // TODO update these numbers when the query changes (a new skill is added)
         final int OFFSET_XP = 13;
         final int OFFSET_XP = 13;
@@ -1277,7 +1415,14 @@ public final class SQLDatabaseManager implements DatabaseManager {
             mobHealthbarType = Config.getInstance().getMobHealthbarDefault();
             mobHealthbarType = Config.getInstance().getMobHealthbarDefault();
         }
         }
 
 
-        return new PlayerProfile(playerName, skills, skillsXp, skillsDATS, mobHealthbarType);
+        try {
+            uuid = UUID.fromString(result.getString(OFFSET_OTHER + 3));
+        }
+        catch (Exception e) {
+            uuid = null;
+        }
+
+        return new PlayerProfile(playerName, uuid, skills, skillsXp, skillsDATS, mobHealthbarType);
     }
     }
 
 
     private void printErrors(SQLException ex) {
     private void printErrors(SQLException ex) {
@@ -1289,4 +1434,217 @@ public final class SQLDatabaseManager implements DatabaseManager {
     public DatabaseType getDatabaseType() {
     public DatabaseType getDatabaseType() {
         return DatabaseType.SQL;
         return DatabaseType.SQL;
     }
     }
+
+    private void checkUpgradeAddAlchemy(final Statement statement) throws SQLException {
+        try {
+            statement.executeQuery("SELECT `alchemy` FROM `" + tablePrefix + "skills` LIMIT 1");
+        }
+        catch (SQLException ex) {
+            mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Alchemy...");
+            statement.executeUpdate("ALTER TABLE `" + tablePrefix + "skills` ADD `alchemy` int(10) NOT NULL DEFAULT '0'");
+            statement.executeUpdate("ALTER TABLE `" + tablePrefix + "experience` ADD `alchemy` int(10) NOT NULL DEFAULT '0'");
+        }
+    }
+
+    private void checkUpgradeAddBlastMiningCooldown(final Statement statement) throws SQLException {
+        try {
+            statement.executeQuery("SELECT `blast_mining` FROM `" + tablePrefix + "cooldowns` LIMIT 1");
+        }
+        catch (SQLException ex) {
+            mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Blast Mining...");
+            statement.executeUpdate("ALTER TABLE `" + tablePrefix + "cooldowns` ADD `blast_mining` int(32) NOT NULL DEFAULT '0'");
+        }
+    }
+
+    private void checkUpgradeAddFishing(final Statement statement) throws SQLException {
+        try {
+            statement.executeQuery("SELECT `fishing` FROM `" + tablePrefix + "skills` LIMIT 1");
+        }
+        catch (SQLException ex) {
+            mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Fishing...");
+            statement.executeUpdate("ALTER TABLE `" + tablePrefix + "skills` ADD `fishing` int(10) NOT NULL DEFAULT '0'");
+            statement.executeUpdate("ALTER TABLE `" + tablePrefix + "experience` ADD `fishing` int(10) NOT NULL DEFAULT '0'");
+        }
+    }
+
+    private void checkUpgradeAddMobHealthbars(final Statement statement) throws SQLException {
+        try {
+            statement.executeQuery("SELECT `mobhealthbar` FROM `" + tablePrefix + "huds` LIMIT 1");
+        }
+        catch (SQLException ex) {
+            mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for mob healthbars...");
+            statement.executeUpdate("ALTER TABLE `" + tablePrefix + "huds` ADD `mobhealthbar` varchar(50) NOT NULL DEFAULT '" + Config.getInstance().getMobHealthbarDefault() + "'");
+        }
+    }
+
+    private void checkUpgradeAddSQLIndexes(final Statement statement) throws SQLException {
+        ResultSet resultSet = null;
+
+        try {
+            resultSet = statement.executeQuery("SHOW INDEX FROM `" + tablePrefix + "skills` WHERE `Key_name` LIKE 'idx\\_%'");
+            resultSet.last();
+
+            if (resultSet.getRow() != SkillType.NON_CHILD_SKILLS.size()) {
+                mcMMO.p.getLogger().info("Indexing tables, this may take a while on larger databases");
+
+                for (SkillType skill : SkillType.NON_CHILD_SKILLS) {
+                    String skill_name = skill.name().toLowerCase();
+
+                    try {
+                        statement.executeUpdate("ALTER TABLE `" + tablePrefix + "skills` ADD INDEX `idx_" + skill_name + "` (`" + skill_name + "`) USING BTREE");
+                    }
+                    catch (SQLException ex) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+        catch (SQLException ex) {
+            printErrors(ex);
+        }
+        finally {
+            if (resultSet != null) {
+                try {
+                    resultSet.close();
+                }
+                catch (SQLException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
+    private void checkUpgradeAddUUIDs(final Statement statement) {
+        List<String> names = new ArrayList<String>();
+        ResultSet resultSet = null;
+
+        try {
+            resultSet = statement.executeQuery("SELECT * FROM `" + tablePrefix + "users` LIMIT 1");
+
+            ResultSetMetaData rsmeta = resultSet.getMetaData();
+            boolean column_exists = false;
+
+            for (int i = 1; i <= rsmeta.getColumnCount(); i++) {
+                if (rsmeta.getColumnName(i).equalsIgnoreCase("uuid")) {
+                    column_exists = true;
+                    break;
+                }
+            }
+
+            if (!column_exists) {
+                mcMMO.p.getLogger().info("Adding UUIDs to mcMMO MySQL user table...");
+                statement.executeUpdate("ALTER TABLE `" + tablePrefix + "users` ADD `uuid` varchar(36) NOT NULL DEFAULT ''");
+            }
+        }
+        catch (SQLException ex) {
+            printErrors(ex);
+        }
+        finally {
+            if (resultSet != null) {
+                try {
+                    resultSet.close();
+                }
+                catch (SQLException e) {
+                    // Ignore
+                }
+            }
+        }
+
+        try {
+            resultSet = statement.executeQuery("SELECT `user` FROM `" + tablePrefix + "users` WHERE `uuid` = ''");
+
+            while (resultSet.next()) {
+                names.add(resultSet.getString("user"));
+            }
+        }
+        catch (SQLException ex) {
+            printErrors(ex);
+        }
+        finally {
+            if (resultSet != null) {
+                try {
+                    resultSet.close();
+                }
+                catch (SQLException e) {
+                    // Ignore
+                }
+            }
+        }
+
+        if (!names.isEmpty()) {
+            new UUIDUpdateAsyncTask(mcMMO.p, names).runTaskAsynchronously(mcMMO.p);
+        }
+    }
+
+    private void checkUpgradeDropPartyNames(final Statement statement) {
+        ResultSet resultSet = null;
+
+        try {
+            resultSet = statement.executeQuery("SELECT * FROM `" + tablePrefix + "users` LIMIT 1");
+
+            ResultSetMetaData rsmeta = resultSet.getMetaData();
+            boolean column_exists = false;
+
+            for (int i = 1; i <= rsmeta.getColumnCount(); i++) {
+                if (rsmeta.getColumnName(i).equalsIgnoreCase("party")) {
+                    column_exists = true;
+                    break;
+                }
+            }
+
+            if (column_exists) {
+                mcMMO.p.getLogger().info("Removing party name from users table...");
+                statement.executeUpdate("ALTER TABLE `" + tablePrefix + "users` DROP COLUMN `party`");
+            }
+        }
+        catch (SQLException ex) {
+            printErrors(ex);
+        }
+        finally {
+            if (resultSet != null) {
+                try {
+                    resultSet.close();
+                }
+                catch (SQLException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
+    private void checkUpgradeDropSpout(final Statement statement) {
+        ResultSet resultSet = null;
+
+        try {
+            resultSet = statement.executeQuery("SELECT * FROM `" + tablePrefix + "huds` LIMIT 1");
+
+            ResultSetMetaData rsmeta = resultSet.getMetaData();
+            boolean column_exists = false;
+
+            for (int i = 1; i <= rsmeta.getColumnCount(); i++) {
+                if (rsmeta.getColumnName(i).equalsIgnoreCase("hudtype")) {
+                    column_exists = true;
+                    break;
+                }
+            }
+
+            if (column_exists) {
+                mcMMO.p.getLogger().info("Removing Spout HUD type from huds table...");
+                statement.executeUpdate("ALTER TABLE `" + tablePrefix + "huds` DROP COLUMN `hudtype`");
+            }
+        }
+        catch (SQLException ex) {
+            printErrors(ex);
+        }
+        finally {
+            if (resultSet != null) {
+                try {
+                    resultSet.close();
+                }
+                catch (SQLException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
 }
 }

+ 2 - 1
src/main/java/com/gmail/nossr50/datatypes/database/UpgradeType.java

@@ -7,5 +7,6 @@ public enum UpgradeType {
     ADD_MOB_HEALTHBARS,
     ADD_MOB_HEALTHBARS,
     DROP_SQL_PARTY_NAMES,
     DROP_SQL_PARTY_NAMES,
     DROP_SPOUT,
     DROP_SPOUT,
-    ADD_ALCHEMY;
+    ADD_ALCHEMY,
+    ADD_UUIDS;
 }
 }

+ 9 - 2
src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java

@@ -3,6 +3,7 @@ package com.gmail.nossr50.datatypes.player;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map;
 import java.util.Set;
 import java.util.Set;
+import java.util.UUID;
 
 
 import org.bukkit.GameMode;
 import org.bukkit.GameMode;
 import org.bukkit.Location;
 import org.bukkit.Location;
@@ -94,13 +95,18 @@ public class McMMOPlayer {
 
 
     public McMMOPlayer(Player player) {
     public McMMOPlayer(Player player) {
         String playerName = player.getName();
         String playerName = player.getName();
+        UUID uuid = player.getUniqueId();
 
 
         this.player = player;
         this.player = player;
         playerMetadata = new FixedMetadataValue(mcMMO.p, playerName);
         playerMetadata = new FixedMetadataValue(mcMMO.p, playerName);
-        profile = mcMMO.getDatabaseManager().loadPlayerProfile(playerName, true);
+        profile = mcMMO.getDatabaseManager().loadPlayerProfile(playerName, uuid, true);
         party = PartyManager.getPlayerParty(playerName);
         party = PartyManager.getPlayerParty(playerName);
         ptpRecord = new PartyTeleportRecord();
         ptpRecord = new PartyTeleportRecord();
 
 
+        if (profile.getUniqueId() == null) {
+            profile.setUniqueId(uuid);
+        }
+
         /*
         /*
          * I'm using this method because it makes code shorter and safer (we don't have to add all SkillTypes manually),
          * I'm using this method because it makes code shorter and safer (we don't have to add all SkillTypes manually),
          * but I actually have no idea about the performance impact, if there is any.
          * but I actually have no idea about the performance impact, if there is any.
@@ -134,6 +140,7 @@ public class McMMOPlayer {
     private class RetryProfileLoadingTask extends BukkitRunnable {
     private class RetryProfileLoadingTask extends BukkitRunnable {
         private static final int MAX_TRIES = 5;
         private static final int MAX_TRIES = 5;
         private final String playerName = McMMOPlayer.this.player.getName();
         private final String playerName = McMMOPlayer.this.player.getName();
+        private final UUID uniqueId = McMMOPlayer.this.player.getUniqueId();
         private int attempt = 0;
         private int attempt = 0;
 
 
         // WARNING: ASYNC TASK
         // WARNING: ASYNC TASK
@@ -154,7 +161,7 @@ public class McMMOPlayer {
 
 
             // Increment attempt counter and try
             // Increment attempt counter and try
             attempt++;
             attempt++;
-            PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(playerName, true);
+            PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uniqueId, true);
             // If successful, schedule the apply
             // If successful, schedule the apply
             if (profile.isLoaded()) {
             if (profile.isLoaded()) {
                 new ApplySuccessfulProfile(profile).runTask(mcMMO.p);
                 new ApplySuccessfulProfile(profile).runTask(mcMMO.p);

+ 28 - 3
src/main/java/com/gmail/nossr50/datatypes/player/PlayerProfile.java

@@ -3,6 +3,7 @@ package com.gmail.nossr50.datatypes.player;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map;
 import java.util.Set;
 import java.util.Set;
+import java.util.UUID;
 
 
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.Config;
@@ -19,6 +20,7 @@ import com.google.common.collect.ImmutableMap;
 
 
 public class PlayerProfile {
 public class PlayerProfile {
     private final String playerName;
     private final String playerName;
+    private UUID uuid;
     private boolean loaded;
     private boolean loaded;
     private volatile boolean changed;
     private volatile boolean changed;
 
 
@@ -30,7 +32,13 @@ public class PlayerProfile {
     private final Map<SkillType, Float>     skillsXp   = new HashMap<SkillType, Float>();     // Skill & XP
     private final Map<SkillType, Float>     skillsXp   = new HashMap<SkillType, Float>();     // Skill & XP
     private final Map<AbilityType, Integer> abilityDATS = new HashMap<AbilityType, Integer>(); // Ability & Cooldown
     private final Map<AbilityType, Integer> abilityDATS = new HashMap<AbilityType, Integer>(); // Ability & Cooldown
 
 
+    @Deprecated
     public PlayerProfile(String playerName) {
     public PlayerProfile(String playerName) {
+        this(playerName, null);
+    }
+
+    public PlayerProfile(String playerName, UUID uuid) {
+        this.uuid = uuid;
         this.playerName = playerName;
         this.playerName = playerName;
 
 
         mobHealthbarType = Config.getInstance().getMobHealthbarDefault();
         mobHealthbarType = Config.getInstance().getMobHealthbarDefault();
@@ -45,13 +53,20 @@ public class PlayerProfile {
         }
         }
     }
     }
 
 
+    @Deprecated
     public PlayerProfile(String playerName, boolean isLoaded) {
     public PlayerProfile(String playerName, boolean isLoaded) {
         this(playerName);
         this(playerName);
         this.loaded = isLoaded;
         this.loaded = isLoaded;
     }
     }
 
 
-    public PlayerProfile(String playerName, Map<SkillType, Integer> levelData, Map<SkillType, Float> xpData, Map<AbilityType, Integer> cooldownData, MobHealthbarType mobHealthbarType) {
+    public PlayerProfile(String playerName, UUID uuid, boolean isLoaded) {
+        this(playerName, uuid);
+        this.loaded = isLoaded;
+    }
+
+    public PlayerProfile(String playerName, UUID uuid, Map<SkillType, Integer> levelData, Map<SkillType, Float> xpData, Map<AbilityType, Integer> cooldownData, MobHealthbarType mobHealthbarType) {
         this.playerName = playerName;
         this.playerName = playerName;
+        this.uuid = uuid;
         this.mobHealthbarType = mobHealthbarType;
         this.mobHealthbarType = mobHealthbarType;
 
 
         skills.putAll(levelData);
         skills.putAll(levelData);
@@ -71,11 +86,11 @@ public class PlayerProfile {
         }
         }
 
 
         // TODO should this part be synchronized?
         // TODO should this part be synchronized?
-        PlayerProfile profileCopy = new PlayerProfile(playerName, ImmutableMap.copyOf(skills), ImmutableMap.copyOf(skillsXp), ImmutableMap.copyOf(abilityDATS), mobHealthbarType);
+        PlayerProfile profileCopy = new PlayerProfile(playerName, uuid, ImmutableMap.copyOf(skills), ImmutableMap.copyOf(skillsXp), ImmutableMap.copyOf(abilityDATS), mobHealthbarType);
         changed = !mcMMO.getDatabaseManager().saveUser(profileCopy);
         changed = !mcMMO.getDatabaseManager().saveUser(profileCopy);
 
 
         if (changed) {
         if (changed) {
-            mcMMO.p.getLogger().warning("PlayerProfile for " + playerName + " failed to save");
+            mcMMO.p.getLogger().warning("PlayerProfile saving failed for player: " + playerName + " " + uuid);
         }
         }
     }
     }
 
 
@@ -83,6 +98,16 @@ public class PlayerProfile {
         return playerName;
         return playerName;
     }
     }
 
 
+    public UUID getUniqueId() {
+        return uuid;
+    }
+
+    public void setUniqueId(UUID uuid) {
+        changed = true;
+
+        this.uuid = uuid;
+    }
+
     public boolean isLoaded() {
     public boolean isLoaded() {
         return loaded;
         return loaded;
     }
     }

+ 51 - 0
src/main/java/com/gmail/nossr50/runnables/database/UUIDFetcherRunnable.java

@@ -0,0 +1,51 @@
+package com.gmail.nossr50.runnables.database;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+
+import org.bukkit.scheduler.BukkitRunnable;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.util.uuid.UUIDFetcher;
+
+public class UUIDFetcherRunnable extends BukkitRunnable {
+    private List<String> names;
+
+    public UUIDFetcherRunnable(List<String> names) {
+        this.names = names;
+    }
+
+    public UUIDFetcherRunnable(String name) {
+        this.names = new ArrayList<String>();
+        this.names.add(name);
+    }
+
+    @Override
+    public void run() {
+        try {
+            Map<String, UUID> returns = new UUIDFetcher(this.names).call();
+            new CacheReturnedNames(returns).runTask(mcMMO.p);
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private class CacheReturnedNames extends BukkitRunnable {
+        private Map<String, UUID> returns;
+
+        public CacheReturnedNames(Map<String, UUID> returns) {
+            this.returns = returns;
+        }
+
+        @Override
+        public void run() {
+            for (Entry<String, UUID> entry : this.returns.entrySet()) {
+                mcMMO.getDatabaseManager().saveUserUUID(entry.getKey(), entry.getValue());
+            }
+        }
+    }
+}

+ 73 - 0
src/main/java/com/gmail/nossr50/runnables/database/UUIDUpdateAsyncTask.java

@@ -0,0 +1,73 @@
+package com.gmail.nossr50.runnables.database;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.bukkit.scheduler.BukkitRunnable;
+
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.config.HiddenConfig;
+import com.gmail.nossr50.database.DatabaseManager;
+import com.gmail.nossr50.datatypes.database.UpgradeType;
+import com.gmail.nossr50.util.Misc;
+import com.gmail.nossr50.util.uuid.UUIDFetcher;
+
+public class UUIDUpdateAsyncTask extends BukkitRunnable {
+    private mcMMO plugin;
+    private static final int MAX_LOOKUP = Math.max(HiddenConfig.getInstance().getUUIDConvertAmount(), 100);
+
+    private List<String> userNames;
+    private int size;
+    private int checkedUsers;
+    private long startMillis;
+
+    public UUIDUpdateAsyncTask(mcMMO plugin, List<String> userNames) {
+        this.plugin = plugin;
+        this.userNames = userNames;
+
+        this.checkedUsers = 0;
+        this.startMillis = System.currentTimeMillis();
+    }
+
+    @Override
+    public void run() {
+        size = userNames.size();
+
+        plugin.getLogger().info("Starting to check and update UUIDs, total amount of users: " + size);
+
+        List<String> userNamesSection;
+        Map<String, UUID> fetchedUUIDs = new HashMap<String, UUID>();
+
+        while (size != 0) {
+            if (size > MAX_LOOKUP) {
+                userNamesSection = userNames.subList(size - MAX_LOOKUP, size);
+                size -= MAX_LOOKUP;
+            }
+            else {
+                userNamesSection = userNames.subList(0, size);
+                size = 0;
+            }
+
+            try {
+                fetchedUUIDs.putAll(new UUIDFetcher(userNamesSection).call());
+            }
+            catch (Exception e) {
+                plugin.getLogger().severe("Unable to fetch UUIDs!");
+                return;
+            }
+
+            checkedUsers += userNamesSection.size();
+            userNamesSection.clear();
+            size = userNames.size();
+
+            Misc.printProgress(checkedUsers, DatabaseManager.progressInterval, startMillis);
+        }
+
+        if (mcMMO.getDatabaseManager().saveUserUUIDs(fetchedUUIDs)) {
+            mcMMO.getUpgradeManager().setUpgradeCompleted(UpgradeType.ADD_UUIDS);
+            plugin.getLogger().info("UUID upgrade completed!");
+        }
+    }
+}

+ 100 - 0
src/main/java/com/gmail/nossr50/util/uuid/UUIDFetcher.java

@@ -0,0 +1,100 @@
+package com.gmail.nossr50.util.uuid;
+
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+import com.google.common.collect.ImmutableList;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+
+public class UUIDFetcher implements Callable<Map<String, UUID>> {
+    private static final double PROFILES_PER_REQUEST = 100;
+    private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft";
+    private final JSONParser jsonParser = new JSONParser();
+    private final List<String> names;
+    private final boolean rateLimiting;
+
+    public UUIDFetcher(List<String> names, boolean rateLimiting) {
+        this.names = ImmutableList.copyOf(names);
+        this.rateLimiting = rateLimiting;
+    }
+
+    public UUIDFetcher(List<String> names) {
+        this(names, true);
+    }
+
+    public Map<String, UUID> call() throws Exception {
+        Map<String, UUID> uuidMap = new HashMap<String, UUID>();
+        int requests = (int) Math.ceil(names.size() / PROFILES_PER_REQUEST);
+        for (int i = 0; i < requests; i++) {
+            HttpURLConnection connection = createConnection();
+            String body = JSONArray.toJSONString(names.subList(i * 100, Math.min((i + 1) * 100, names.size())));
+            writeBody(connection, body);
+            JSONArray array = (JSONArray) jsonParser.parse(new InputStreamReader(connection.getInputStream()));
+            for (Object profile : array) {
+                JSONObject jsonProfile = (JSONObject) profile;
+                String id = (String) jsonProfile.get("id");
+                String name = (String) jsonProfile.get("name");
+                UUID uuid = UUIDFetcher.getUUID(id);
+                uuidMap.put(name, uuid);
+            }
+            if (rateLimiting && i != requests - 1) {
+                Thread.sleep(100L);
+            }
+        }
+        return uuidMap;
+    }
+
+    private static void writeBody(HttpURLConnection connection, String body) throws Exception {
+        OutputStream stream = connection.getOutputStream();
+        stream.write(body.getBytes());
+        stream.flush();
+        stream.close();
+    }
+
+    private static HttpURLConnection createConnection() throws Exception {
+        URL url = new URL(PROFILE_URL);
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        connection.setRequestMethod("POST");
+        connection.setRequestProperty("Content-Type", "application/json");
+        connection.setUseCaches(false);
+        connection.setDoInput(true);
+        connection.setDoOutput(true);
+        return connection;
+    }
+
+    private static UUID getUUID(String id) {
+        return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" + id.substring(20, 32));
+    }
+
+    public static byte[] toBytes(UUID uuid) {
+        ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]);
+        byteBuffer.putLong(uuid.getMostSignificantBits());
+        byteBuffer.putLong(uuid.getLeastSignificantBits());
+        return byteBuffer.array();
+    }
+
+    public static UUID fromBytes(byte[] array) {
+        if (array.length != 16) {
+            throw new IllegalArgumentException("Illegal byte array length: " + array.length);
+        }
+        ByteBuffer byteBuffer = ByteBuffer.wrap(array);
+        long mostSignificant = byteBuffer.getLong();
+        long leastSignificant = byteBuffer.getLong();
+        return new UUID(mostSignificant, leastSignificant);
+    }
+
+    public static UUID getUUIDOf(String name) throws Exception {
+        return new UUIDFetcher(Arrays.asList(name)).call().get(name);
+    }
+}

+ 3 - 0
src/main/resources/hidden.yml

@@ -11,3 +11,6 @@ Options:
     EnchantmentBuffs: true
     EnchantmentBuffs: true
     # true to enable refreshing of chunks around a player at the end of Super Breaker, Giga Drill Breaker, and Berserk.  This should fix blocks being broken client side, but not server-side
     # true to enable refreshing of chunks around a player at the end of Super Breaker, Giga Drill Breaker, and Berserk.  This should fix blocks being broken client side, but not server-side
     RefreshChunks: false
     RefreshChunks: false
+
+    # Amount of users to convert every interval
+    UUIDConvertAmount: 100