Browse Source

Fix another bug where usernames can be saved as null for FlatFileDB

nossr50 4 years ago
parent
commit
48de5057a4

+ 16 - 0
Changelog.txt

@@ -1,3 +1,19 @@
+Version 2.1.192
+    Removed some debug messages from FlatFileDatabaseManager
+    Fixed another bug where player names could be saved as null for FlatFileDB (they will update on the players next login at the next save interval)
+    (API) Removed deprecation from com.gmail.nossr50.api.ExperienceAPI.getOfflineProfile(java.lang.String)
+    (API) Added com.gmail.nossr50.api.DatabaseAPI.doesPlayerExistInDB(org.bukkit.OfflinePlayer)
+    (API) Added com.gmail.nossr50.api.ExperienceAPI.getOfflineProfile(org.bukkit.OfflinePlayer)
+    (API) Added com.gmail.nossr50.api.ExperienceAPI.getOfflineXP(org.bukkit.OfflinePlayer, java.lang.String)
+    (API) Added com.gmail.nossr50.api.ExperienceAPI.getOfflineXPRaw(org.bukkit.OfflinePlayer, com.gmail.nossr50.datatypes.skills.PrimarySkillType)
+    (API) Added com.gmail.nossr50.api.ExperienceAPI.getOfflineXPRaw(org.bukkit.OfflinePlayer, java.lang.String)
+    (API) Added com.gmail.nossr50.api.ExperienceAPI.getOfflineXPToNextLevel(org.bukkit.OfflinePlayer, java.lang.String)
+    (API) Added com.gmail.nossr50.api.DatabaseAPI.doesPlayerExistInDB(java.lang.String)
+    (Unit Tests) Added some more unit tests to FlatFileDB
+
+    NOTES:
+    I removed a lot of API that should honestly never have been added, this will break some plugins, those plugins will have to update.
+
 Version 2.1.191
     Fixed a bug related to our blocktracker
     Fixed a bug that prevented the leaderboards from working on FlatFile in some circumstances

+ 26 - 6
src/main/java/com/gmail/nossr50/api/DatabaseAPI.java

@@ -2,6 +2,8 @@ package com.gmail.nossr50.api;
 
 import com.gmail.nossr50.datatypes.player.PlayerProfile;
 import com.gmail.nossr50.mcMMO;
+import org.bukkit.OfflinePlayer;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.UUID;
 
@@ -9,20 +11,38 @@ public class DatabaseAPI {
 
     /**
      * Checks if a player exists in the mcMMO Database
-     * @param uuid player UUID
+     * @param offlinePlayer target player
      * @return true if the player exists in the DB, false if they do not
      */
-    public boolean doesPlayerExistInDB(String uuid) {
-        return doesPlayerExistInDB(UUID.fromString(uuid));
+    public boolean doesPlayerExistInDB(@NotNull OfflinePlayer offlinePlayer) {
+        PlayerProfile playerProfile = mcMMO.getDatabaseManager().loadPlayerProfile(offlinePlayer);
+
+        return playerProfile.isLoaded();
+    }
+
+    /**
+     * Checks if a player exists in the mcMMO Database
+     * @param uuid target player
+     * @return true if the player exists in the DB, false if they do not
+     */
+    public boolean doesPlayerExistInDB(@NotNull UUID uuid) {
+        PlayerProfile playerProfile = null;
+        try {
+            playerProfile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid);
+        } catch (Exception e) {
+            return false;
+        }
+
+        return playerProfile.isLoaded();
     }
 
     /**
      * Checks if a player exists in the mcMMO Database
-     * @param uuid player UUID
+     * @param playerName target player
      * @return true if the player exists in the DB, false if they do not
      */
-    public boolean doesPlayerExistInDB(UUID uuid) {
-        PlayerProfile playerProfile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, null);
+    public boolean doesPlayerExistInDB(@NotNull String playerName) {
+        PlayerProfile playerProfile = mcMMO.getDatabaseManager().loadPlayerProfile(playerName);
 
         return playerProfile.isLoaded();
     }

+ 96 - 14
src/main/java/com/gmail/nossr50/api/ExperienceAPI.java

@@ -13,11 +13,11 @@ import com.gmail.nossr50.skills.child.FamilyTree;
 import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.skills.CombatUtils;
 import com.gmail.nossr50.util.skills.SkillTools;
-import org.bukkit.Bukkit;
 import org.bukkit.OfflinePlayer;
 import org.bukkit.block.BlockState;
 import org.bukkit.entity.LivingEntity;
 import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.ArrayList;
 import java.util.Set;
@@ -432,6 +432,23 @@ public final class ExperienceAPI {
         return getOfflineProfile(uuid).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 offlinePlayer 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(@NotNull OfflinePlayer offlinePlayer, @NotNull String skillType) throws InvalidPlayerException {
+        return getOfflineProfile(offlinePlayer).getSkillXpLevel(getNonChildSkillType(skillType));
+    }
+
     /**
      * Get the raw amount of XP a player has in a specific skill.
      * </br>
@@ -483,6 +500,30 @@ public final class ExperienceAPI {
         return getOfflineProfile(uuid).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 offlinePlayer 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(@NotNull OfflinePlayer offlinePlayer, @NotNull String skillType) throws InvalidPlayerException, UnsupportedOperationException, InvalidSkillException {
+        return getOfflineProfile(offlinePlayer).getSkillXpLevelRaw(getNonChildSkillType(skillType));
+    }
+
+    public static float getOfflineXPRaw(@NotNull OfflinePlayer offlinePlayer, @NotNull PrimarySkillType skillType) throws InvalidPlayerException, UnsupportedOperationException {
+        if(SkillTools.isChildSkill(skillType))
+            throw new UnsupportedOperationException();
+
+        return getOfflineProfile(offlinePlayer).getSkillXpLevelRaw(skillType);
+    }
+
     /**
      * Get the total amount of XP needed to reach the next level.
      * </br>
@@ -530,10 +571,27 @@ public final class ExperienceAPI {
      * @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) {
+    public static int getOfflineXPToNextLevel(@NotNull UUID uuid, @NotNull String skillType) {
         return getOfflineProfile(uuid).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 offlinePlayer 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(@NotNull OfflinePlayer offlinePlayer, @NotNull String skillType) throws UnsupportedOperationException, InvalidSkillException, InvalidPlayerException {
+        return getOfflineProfile(offlinePlayer).getXpToLevel(getNonChildSkillType(skillType));
+    }
+
     /**
      * Get the amount of XP remaining until the next level.
      * </br>
@@ -595,6 +653,26 @@ public final class ExperienceAPI {
         return profile.getXpToLevel(skill) - profile.getSkillXpLevelRaw(skill);
     }
 
+    /**
+     * Get the amount of XP an offline player has left before leveling up.
+     * </br>
+     * This function is designed for API usage.
+     *
+     * @param offlinePlayer 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(OfflinePlayer offlinePlayer, String skillType) throws InvalidSkillException, InvalidPlayerException, UnsupportedOperationException {
+        PrimarySkillType skill = getNonChildSkillType(skillType);
+        PlayerProfile profile = getOfflineProfile(offlinePlayer);
+
+        return profile.getXpToLevel(skill) - profile.getSkillXpLevelRaw(skill);
+    }
+
     /**
      * Add levels to a skill.
      * </br>
@@ -1129,25 +1207,22 @@ public final class ExperienceAPI {
     }
 
     // Utility methods follow.
-    private static void addOfflineXP(UUID playerUniqueId, PrimarySkillType skill, int XP) {
+    private static void addOfflineXP(@NotNull UUID playerUniqueId, @NotNull PrimarySkillType skill, int XP) {
         PlayerProfile profile = getOfflineProfile(playerUniqueId);
 
         profile.addXp(skill, XP);
         profile.save(true);
     }
 
-    @Deprecated
-    private static void addOfflineXP(String playerName, PrimarySkillType skill, int XP) {
+    private static void addOfflineXP(@NotNull String playerName, @NotNull PrimarySkillType skill, int XP) {
         PlayerProfile profile = getOfflineProfile(playerName);
 
         profile.addXp(skill, XP);
         profile.scheduleAsyncSave();
     }
 
-    private static PlayerProfile getOfflineProfile(UUID uuid) throws InvalidPlayerException {
-        OfflinePlayer offlinePlayer = Bukkit.getServer().getOfflinePlayer(uuid);
-        String playerName = offlinePlayer.getName();
-        PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, playerName);
+    private static @NotNull PlayerProfile getOfflineProfile(@NotNull UUID uuid) throws InvalidPlayerException {
+        PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid);
 
         if (!profile.isLoaded()) {
             throw new InvalidPlayerException();
@@ -1156,11 +1231,18 @@ public final class ExperienceAPI {
         return profile;
     }
 
-    @Deprecated
-    private static PlayerProfile getOfflineProfile(String playerName) throws InvalidPlayerException {
-        OfflinePlayer offlinePlayer = Bukkit.getServer().getOfflinePlayer(playerName);
-        UUID uuid = offlinePlayer.getUniqueId();
-        PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, playerName);
+    private static @NotNull PlayerProfile getOfflineProfile(@NotNull OfflinePlayer offlinePlayer) throws InvalidPlayerException {
+        PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(offlinePlayer);
+
+        if (!profile.isLoaded()) {
+            throw new InvalidPlayerException();
+        }
+
+        return profile;
+    }
+
+    private static @NotNull PlayerProfile getOfflineProfile(@NotNull String playerName) throws InvalidPlayerException {
+        PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(playerName);
 
         if (!profile.isLoaded()) {
             throw new InvalidPlayerException();

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

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

+ 1 - 4
src/main/java/com/gmail/nossr50/commands/experience/ExperienceCommand.java

@@ -97,12 +97,9 @@ public abstract class ExperienceCommand implements TabExecutor {
 
                 // If the mcMMOPlayer doesn't exist, create a temporary profile and check if it's present in the database. If it's not, abort the process.
                 if (mcMMOPlayer == null) {
-                    UUID uuid = null;
-                    OfflinePlayer offlinePlayer = mcMMO.p.getServer().getOfflinePlayer(playerName);
                     PlayerProfile profile;
 
-                    uuid = offlinePlayer.getUniqueId();
-                    profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, null);
+                    profile = mcMMO.getDatabaseManager().loadPlayerProfile(playerName);
 
                     //Check loading by UUID
                     if (CommandUtils.unloadedProfile(sender, profile)) {

+ 2 - 5
src/main/java/com/gmail/nossr50/commands/experience/SkillresetCommand.java

@@ -79,11 +79,8 @@ public class SkillresetCommand implements TabExecutor {
 
                 // If the mcMMOPlayer doesn't exist, create a temporary profile and check if it's present in the database. If it's not, abort the process.
                 if (mcMMOPlayer == null) {
-                    UUID uuid = null;
-                    OfflinePlayer player = mcMMO.p.getServer().getOfflinePlayer(playerName);
-                    uuid = player.getUniqueId();
-
-                    PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(uuid, playerName);
+                    OfflinePlayer offlinePlayer = mcMMO.p.getServer().getOfflinePlayer(playerName);
+                    PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(offlinePlayer);
 
                     //Check loading by UUID
                     if (CommandUtils.unloadedProfile(sender, profile)) {

+ 2 - 12
src/main/java/com/gmail/nossr50/database/DatabaseManager.java

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.database;
 
+import com.gmail.nossr50.api.exceptions.InvalidPlayerException;
 import com.gmail.nossr50.api.exceptions.InvalidSkillException;
 import com.gmail.nossr50.datatypes.database.DatabaseType;
 import com.gmail.nossr50.datatypes.database.PlayerStat;
@@ -93,18 +94,7 @@ public interface DatabaseManager {
      */
     @NotNull PlayerProfile loadPlayerProfile(@NotNull String playerName);
 
-    default @NotNull PlayerProfile loadPlayerProfile(@NotNull OfflinePlayer offlinePlayer) {
-        return loadPlayerProfile(offlinePlayer.getUniqueId(), offlinePlayer.getName());
-    }
-
-    /**
-     * Load a player from the database.
-     * @param uuid The uuid of the player to load from the database
-     * @return The player's data, or an unloaded PlayerProfile if not found
-     * @deprecated Use {@link DatabaseManager#loadPlayerProfile(org.bukkit.OfflinePlayer)} or {@link DatabaseManager#loadPlayerProfile(java.util.UUID)} if possible
-     */
-    @Deprecated
-    @NotNull PlayerProfile loadPlayerProfile(@NotNull UUID uuid, @Nullable String playerName);
+    @NotNull PlayerProfile loadPlayerProfile(@NotNull OfflinePlayer offlinePlayer);
 
     @NotNull PlayerProfile loadPlayerProfile(@NotNull UUID uuid);
 

+ 132 - 63
src/main/java/com/gmail/nossr50/database/FlatFileDatabaseManager.java

@@ -17,6 +17,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.io.*;
+import java.security.InvalidParameterException;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
@@ -341,14 +342,8 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
                 String line;
 
                 boolean wroteUser = false;
-                if(testing) {
-                    System.out.println("-- saveUser bufferedreader feed --");
-                }
                 // While not at the end of the file
                 while ((line = in.readLine()) != null) {
-                    if(testing) {
-                        System.out.println(line);
-                    }
                     if(line.startsWith("#")) {
                         writer.append(line).append("\r\n");
                         continue;
@@ -384,7 +379,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
                         writer.append(line).append("\r\n"); //Not the user so write it to file and move on
                     } else {
                         //User found
-                        writeUserToLine(profile, playerName, uuid, writer);
+                        writeUserToLine(profile, writer);
                         wroteUser = true;
                     }
                 }
@@ -393,12 +388,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
                  * If we couldn't find the user in the DB we need to add him
                  */
                 if(!wroteUser) {
-                    writeUserToLine(profile, playerName, uuid, writer);
-                }
-
-                if(testing) {
-                    System.out.println("-- saveUser (FileWriter contents before save) --");
-                    System.out.println(writer.toString());
+                    writeUserToLine(profile, writer);
                 }
 
                 // Write the new file
@@ -431,8 +421,8 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
         }
     }
 
-    public void writeUserToLine(@NotNull PlayerProfile profile, @NotNull String playerName, @Nullable UUID uuid, @NotNull Appendable appendable) throws IOException {
-        appendable.append(playerName).append(":");
+    public void writeUserToLine(@NotNull PlayerProfile profile, @NotNull Appendable appendable) throws IOException {
+        appendable.append(profile.getPlayerName()).append(":");
         appendable.append(String.valueOf(profile.getSkillLevel(PrimarySkillType.MINING))).append(":");
         appendable.append(IGNORED).append(":");
         appendable.append(IGNORED).append(":");
@@ -473,7 +463,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
         appendable.append(IGNORED).append(":"); //mob health bar
         appendable.append(String.valueOf(profile.getSkillLevel(PrimarySkillType.ALCHEMY))).append(":");
         appendable.append(String.valueOf(profile.getSkillXpLevel(PrimarySkillType.ALCHEMY))).append(":");
-        appendable.append(uuid != null ? uuid.toString() : "NULL").append(":");
+        appendable.append(profile.getUniqueId() != null ? profile.getUniqueId().toString() : "NULL").append(":");
         appendable.append(String.valueOf(profile.getScoreboardTipsShown())).append(":");
         appendable.append(String.valueOf(profile.getUniqueData(UniqueDataType.CHIMAERA_WING_DATS))).append(":");
         appendable.append(String.valueOf(profile.getLastLogin())).append(":"); //overhaul last login
@@ -527,7 +517,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
                 }
 
                 try (FileWriter fileWriter = new FileWriter(usersFile)) {
-                    writeUserToLine(playerProfile, playerName, uuid, stringBuilder);
+                    writeUserToLine(playerProfile, stringBuilder);
                     fileWriter.write(stringBuilder.toString());
                 } catch (Exception e) {
                     e.printStackTrace();
@@ -541,32 +531,53 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
     }
 
     public @NotNull PlayerProfile loadPlayerProfile(@NotNull OfflinePlayer offlinePlayer) {
-        return loadPlayerByUUID(offlinePlayer.getUniqueId(), offlinePlayer.getName(), offlinePlayer.isOnline());
+        return processUserQuery(getUserQuery(offlinePlayer.getUniqueId(), offlinePlayer.getName()));
     }
 
     public @NotNull PlayerProfile loadPlayerProfile(@NotNull String playerName) {
-        return loadPlayerByName(playerName);
+        return processUserQuery(getUserQuery(null, playerName));
     }
 
-    public @NotNull PlayerProfile loadPlayerProfile(@NotNull UUID uuid, @Nullable String playerName) {
-        return loadPlayerByUUID(uuid, playerName, false);
+    public @NotNull PlayerProfile loadPlayerProfile(@NotNull UUID uuid) {
+        return processUserQuery(getUserQuery(uuid, null));
     }
 
-    public @NotNull PlayerProfile loadPlayerProfile(@NotNull UUID uuid) {
-        return loadPlayerByUUID(uuid, null, false);
+    private @NotNull UserQuery getUserQuery(@Nullable UUID uuid, @Nullable String playerName) throws NullPointerException {
+        boolean hasName = playerName != null && !playerName.equalsIgnoreCase("null");
+
+        if(hasName && uuid != null) {
+            return new UserQueryFull(playerName, uuid);
+        } else if (uuid != null) {
+            return new UserQueryUUIDImpl(uuid);
+        } else if(hasName) {
+            return new UserQueryNameImpl(playerName);
+        } else {
+            throw new NullPointerException("Both name and UUID cannot be null, at least one must be non-null!");
+        }
     }
 
     /**
-     * Find and load a player by UUID
+     * Find and load a player by UUID/Name
+     * If the name isn't null and doesn't match the name in the DB, the players name is then replaced/updated
      *
-     * @param uuid target uuid
-     * @param playerName target player name
-     * @param replaceName name to replace if the found name differs
+     * @param userQuery the query
      * @return a profile with the targets data or an unloaded profile if no data was found
-     * @deprecated only use this if you know what you are doing, replacing the name can cause havoc
      */
-    @Deprecated
-    public @NotNull PlayerProfile loadPlayerByUUID(@NotNull UUID uuid, @Nullable String playerName, boolean replaceName) {
+    private @NotNull PlayerProfile processUserQuery(@NotNull UserQuery userQuery) throws RuntimeException {
+        switch(userQuery.getType()) {
+            case UUID_AND_NAME:
+                return queryByUUIDAndName((UserQueryFull) userQuery);
+            case UUID:
+                return queryByUUID((UserQueryUUID) userQuery);
+            case NAME:
+                return queryByName((UserQueryNameImpl) userQuery);
+            default:
+                throw new RuntimeException("No case for this UserQueryType!");
+        }
+    }
+
+    private @NotNull PlayerProfile queryByName(@NotNull UserQueryName userQuery) {
+        String playerName = userQuery.getName();
         BufferedReader in = null;
 
         synchronized (fileWritingLock) {
@@ -575,45 +586,80 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
                 in = new BufferedReader(new FileReader(usersFilePath));
                 String line;
 
+
                 while ((line = in.readLine()) != null) {
+                    if(line.startsWith("#")) {
+                        continue;
+                    }
+
+
                     // Find if the line contains the player we want.
                     String[] rawSplitData = line.split(":");
 
+
                     /* Don't read corrupt data */
-                    if(rawSplitData.length < (UUID_INDEX + 1)) {
+                    if(rawSplitData.length < (USERNAME_INDEX + 1)) {
                         continue;
                     }
 
-                    /* Does this entry have a UUID? */
-                    if (rawSplitData[UUID_INDEX].equalsIgnoreCase("NULL")
-                            || rawSplitData[UUID_INDEX].isEmpty()
-                            || rawSplitData[UUID_INDEX].equalsIgnoreCase("")) {
-                        continue; //No UUID entry found for this data in the DB, go to next entry
-                    }
 
-                    // Compare provided UUID to DB
-                    if (!rawSplitData[UUID_INDEX].equalsIgnoreCase(uuid.toString())) {
-                        continue; //Doesn't match, go to the next entry
+                    //If we couldn't find anyone
+                    if(playerName.equalsIgnoreCase(rawSplitData[USERNAME_INDEX])) {
+                        return loadFromLine(rawSplitData);
+                    }
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            } finally {
+                // I have no idea why it's necessary to inline tryClose() here, but it removes
+                // a resource leak warning, and I'm trusting the compiler on this one.
+                if (in != null) {
+                    try {
+                        in.close();
+                    } catch (IOException e) {
+                        // Ignore
                     }
+                }
+            }
+        }
 
-                    /*
-                     * UUID Matched!
-                     * Making it this far means the current data line is considered a match
-                     */
 
+        //Return a new blank profile
+        return new PlayerProfile(playerName, new UUID(0, 0), startingLevel);
+    }
 
-                    /* Check for nickname changes and update since we are here anyways */
-                    if(playerName != null) {
-                        if(replaceName) {
-                            logger.info("A users name is being updated, this can happen from either a call to our API or they simply changed their name");
-                            if (!rawSplitData[USERNAME_INDEX].equalsIgnoreCase(playerName)) {
-                                //logger.info("Name updated for player: " + rawSplitData[USERNAME_INDEX] + " => " + playerName);
-                                rawSplitData[USERNAME_INDEX] = playerName;
-                            }
-                        }
+    private @NotNull PlayerProfile queryByUUID(@NotNull UserQueryUUID userQuery) {
+        BufferedReader in = null;
+        UUID uuid = userQuery.getUUID();
+
+        synchronized (fileWritingLock) {
+            try {
+                // Open the user file
+                in = new BufferedReader(new FileReader(usersFilePath));
+                String line;
+
+                while ((line = in.readLine()) != null) {
+                    if(line.startsWith("#")) {
+                        continue;
+                    }
+                    // Find if the line contains the player we want.
+                    String[] rawSplitData = line.split(":");
+
+                    /* Don't read corrupt data */
+                    if(rawSplitData.length < (UUID_INDEX + 1)) {
+                        continue;
                     }
 
-                    return loadFromLine(rawSplitData);
+                    try {
+                        UUID fromDataUUID = UUID.fromString(rawSplitData[UUID_INDEX]);
+                        if(fromDataUUID.equals(uuid)) {
+                            return loadFromLine(rawSplitData);
+                        }
+                    } catch (Exception e) {
+                        if(testing) {
+                            e.printStackTrace();
+                        }
+                    }
                 }
             } catch (Exception e) {
                 e.printStackTrace();
@@ -634,11 +680,13 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
          * No match was found in the file
          */
 
-        return grabUnloadedProfile(uuid, playerName); //Create an empty new profile and return
+        return grabUnloadedProfile(uuid, "Player-Not-Found="+uuid.toString());
     }
 
-    private @NotNull PlayerProfile loadPlayerByName(@NotNull String playerName) {
+    private @NotNull PlayerProfile queryByUUIDAndName(@NotNull UserQueryFull userQuery) {
         BufferedReader in = null;
+        String playerName = userQuery.getName();
+        UUID uuid = userQuery.getUUID();
 
         synchronized (fileWritingLock) {
             try {
@@ -650,18 +698,36 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
                     if(line.startsWith("#")) {
                         continue;
                     }
-
                     // Find if the line contains the player we want.
                     String[] rawSplitData = line.split(":");
 
                     /* Don't read corrupt data */
-                    if(rawSplitData.length < (USERNAME_INDEX + 1)) {
+                    if(rawSplitData.length < (UUID_INDEX + 1)) {
                         continue;
                     }
 
-                    //If we couldn't find anyone
-                    if(playerName.equalsIgnoreCase(rawSplitData[USERNAME_INDEX])) {
-                        return loadFromLine(rawSplitData);
+                    try {
+                        UUID fromDataUUID = UUID.fromString(rawSplitData[UUID_INDEX]);
+                        if(fromDataUUID.equals(uuid)) {
+                            //Matched UUID, now check if name matches
+                            String dbPlayerName = rawSplitData[USERNAME_INDEX];
+
+                            boolean matchingName = dbPlayerName.equalsIgnoreCase(playerName);
+
+                            if (!matchingName) {
+                                logger.info("When loading user: "+playerName +" with UUID of (" + uuid.toString()
+                                        +") we found a mismatched name, the name in the DB will be replaced (DB name: "+dbPlayerName+")");
+                                //logger.info("Name updated for player: " + rawSplitData[USERNAME_INDEX] + " => " + playerName);
+                                rawSplitData[USERNAME_INDEX] = playerName;
+                            }
+
+                            //TODO: Logic to replace name here
+                            return loadFromLine(rawSplitData);
+                        }
+                    } catch (Exception e) {
+                        if(testing) {
+                            e.printStackTrace();
+                        }
                     }
                 }
             } catch (Exception e) {
@@ -679,8 +745,11 @@ public final class FlatFileDatabaseManager implements DatabaseManager {
             }
         }
 
-        //Return a new blank profile
-        return new PlayerProfile(playerName, new UUID(0, 0), startingLevel);
+        /*
+         * No match was found in the file
+         */
+
+        return grabUnloadedProfile(uuid, playerName); //Create an empty new profile and return
     }
 
     private @NotNull PlayerProfile grabUnloadedProfile(@NotNull UUID uuid, @Nullable String playerName) {

+ 8 - 2
src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java

@@ -15,6 +15,7 @@ import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.skills.SkillTools;
 import org.apache.tomcat.jdbc.pool.DataSource;
 import org.apache.tomcat.jdbc.pool.PoolProperties;
+import org.bukkit.OfflinePlayer;
 import org.bukkit.entity.Player;
 import org.bukkit.scheduler.BukkitRunnable;
 import org.jetbrains.annotations.NotNull;
@@ -515,7 +516,7 @@ public final class SQLDatabaseManager implements DatabaseManager {
             if (id == -1) {
                 return new PlayerProfile(player.getName(), player.getUniqueId(), false, mcMMO.p.getAdvancedConfig().getStartingLevel());
             } else {
-                return loadPlayerProfile(player.getUniqueId(), player.getName());
+                return loadPlayerProfile(player);
             }
         } catch (SQLException e) {
             e.printStackTrace();
@@ -571,7 +572,12 @@ public final class SQLDatabaseManager implements DatabaseManager {
         }
     }
 
-    public @NotNull PlayerProfile loadPlayerProfile(@NotNull UUID uuid, @Nullable String playerName) {
+    @Override
+    public @NotNull PlayerProfile loadPlayerProfile(@NotNull OfflinePlayer offlinePlayer) {
+        return loadPlayerFromDB(offlinePlayer.getUniqueId(), offlinePlayer.getName());
+    }
+
+        public @NotNull PlayerProfile loadPlayerProfile(@NotNull UUID uuid, @Nullable String playerName) {
         return loadPlayerFromDB(uuid, playerName);
     }
 

+ 7 - 0
src/main/java/com/gmail/nossr50/database/UserQuery.java

@@ -0,0 +1,7 @@
+package com.gmail.nossr50.database;
+
+import org.jetbrains.annotations.NotNull;
+
+public interface UserQuery {
+    @NotNull UserQueryType getType();
+}

+ 31 - 0
src/main/java/com/gmail/nossr50/database/UserQueryFull.java

@@ -0,0 +1,31 @@
+package com.gmail.nossr50.database;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.UUID;
+
+public class UserQueryFull implements UserQueryUUID, UserQueryName {
+
+    private final @NotNull String name;
+    private final @NotNull UUID uuid;
+
+    public UserQueryFull(@NotNull String name, @NotNull UUID uuid) {
+        this.name = name;
+        this.uuid = uuid;
+    }
+
+    @Override
+    public @NotNull UserQueryType getType() {
+        return UserQueryType.UUID_AND_NAME;
+    }
+
+    @Override
+    public @NotNull String getName() {
+        return name;
+    }
+
+    @Override
+    public @NotNull UUID getUUID() {
+        return uuid;
+    }
+}

+ 7 - 0
src/main/java/com/gmail/nossr50/database/UserQueryName.java

@@ -0,0 +1,7 @@
+package com.gmail.nossr50.database;
+
+import org.jetbrains.annotations.NotNull;
+
+public interface UserQueryName extends UserQuery {
+    @NotNull String getName();
+}

+ 20 - 0
src/main/java/com/gmail/nossr50/database/UserQueryNameImpl.java

@@ -0,0 +1,20 @@
+package com.gmail.nossr50.database;
+
+import org.jetbrains.annotations.NotNull;
+
+public class UserQueryNameImpl implements UserQueryName {
+    private final @NotNull String name;
+
+    public UserQueryNameImpl(@NotNull String name) {
+        this.name = name;
+    }
+
+    @Override
+    public @NotNull UserQueryType getType() {
+        return UserQueryType.NAME;
+    }
+
+    public @NotNull String getName() {
+        return name;
+    }
+}

+ 7 - 0
src/main/java/com/gmail/nossr50/database/UserQueryType.java

@@ -0,0 +1,7 @@
+package com.gmail.nossr50.database;
+
+public enum UserQueryType {
+    UUID_AND_NAME,
+    UUID,
+    NAME
+}

+ 11 - 0
src/main/java/com/gmail/nossr50/database/UserQueryUUID.java

@@ -0,0 +1,11 @@
+package com.gmail.nossr50.database;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.UUID;
+
+public interface UserQueryUUID extends UserQuery {
+
+    @NotNull UUID getUUID();
+
+}

+ 23 - 0
src/main/java/com/gmail/nossr50/database/UserQueryUUIDImpl.java

@@ -0,0 +1,23 @@
+package com.gmail.nossr50.database;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.UUID;
+
+public class UserQueryUUIDImpl implements UserQueryUUID {
+    private final @NotNull UUID uuid;
+
+    public UserQueryUUIDImpl(@NotNull UUID uuid) {
+        this.uuid = uuid;
+    }
+
+    @Override
+    public @NotNull UserQueryType getType() {
+        return UserQueryType.UUID;
+    }
+
+    @Override
+    public @NotNull UUID getUUID() {
+        return uuid;
+    }
+}

+ 4 - 9
src/main/java/com/gmail/nossr50/datatypes/player/PlayerProfile.java

@@ -22,7 +22,7 @@ import java.util.concurrent.DelayQueue;
 
 public class PlayerProfile {
     private final String playerName;
-    private UUID uuid;
+    private @Nullable UUID uuid;
     private boolean loaded;
     private volatile boolean changed;
 
@@ -49,7 +49,7 @@ public class PlayerProfile {
     }
 
     //TODO: Add deprecated constructor w/o startinglevel
-    public PlayerProfile(String playerName, UUID uuid, int startingLevel) {
+    public PlayerProfile(String playerName, @Nullable UUID uuid, int startingLevel) {
         this.uuid = uuid;
         this.playerName = playerName;
 
@@ -100,10 +100,6 @@ public class PlayerProfile {
         new PlayerProfileSaveTask(this, false).runTaskAsynchronously(mcMMO.p);
     }
 
-    public void scheduleSyncSave() {
-        new PlayerProfileSaveTask(this, true).runTask(mcMMO.p);
-    }
-
     public void scheduleAsyncSaveDelay() {
         new PlayerProfileSaveTask(this, false).runTaskLaterAsynchronously(mcMMO.p, 20);
     }
@@ -126,8 +122,7 @@ public class PlayerProfile {
         if (changed) {
             mcMMO.p.getLogger().severe("PlayerProfile saving failed for player: " + playerName + " " + uuid);
 
-            if(saveAttempts > 0)
-            {
+            if(saveAttempts > 0) {
                 mcMMO.p.getLogger().severe("Attempted to save profile for player "+getPlayerName()
                         + " resulted in failure. "+saveAttempts+" have been made so far.");
             }
@@ -138,7 +133,7 @@ public class PlayerProfile {
 
                 //Back out of async saving if we detect a server shutdown, this is not always going to be caught
                 if(mcMMO.isServerShutdownExecuted() || useSync)
-                    scheduleSyncSave(); //Execute sync saves immediately
+                    new PlayerProfileSaveTask(this, true).runTask(mcMMO.p);
                 else
                     scheduleAsyncSave();
 

+ 1 - 1
src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileLoadingTask.java

@@ -41,7 +41,7 @@ public class PlayerProfileLoadingTask extends BukkitRunnable {
             return;
         }
 
-        PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(player.getUniqueId(), player.getName());
+        PlayerProfile profile = mcMMO.getDatabaseManager().loadPlayerProfile(player);
 
         if(!profile.isLoaded()) {
             mcMMO.p.getLogger().info("Creating new data for player: "+player.getName());

+ 254 - 24
src/test/java/com/gmail/nossr50/database/FlatFileDatabaseManagerTest.java

@@ -9,18 +9,25 @@ import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 import com.gmail.nossr50.datatypes.skills.SuperAbilityType;
 import com.gmail.nossr50.util.skills.SkillTools;
 import com.google.common.io.Files;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.Statistic;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.*;
 
 import java.io.*;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
+import java.util.logging.Filter;
+import java.util.logging.LogRecord;
 import java.util.logging.Logger;
 
 import static org.junit.jupiter.api.Assertions.*;
@@ -60,6 +67,11 @@ public class FlatFileDatabaseManagerTest {
     int expectedScoreboardTips = 1111;
     Long expectedLastLogin = 2020L;
 
+    @BeforeAll
+    static void initBeforeAll() {
+        logger.setFilter(new DebugFilter());
+    }
+
     @BeforeEach
     public void init() {
         assertNull(db);
@@ -175,7 +187,7 @@ public class FlatFileDatabaseManagerTest {
 
         //This makes sure our private method is working before the tests run afterwards
         ArrayList<String[]> dataFromFile = getSplitDataFromFile(dbFile);
-        System.out.println("File Path: "+ dbFile.getAbsolutePath());
+        logger.info("File Path: "+ dbFile.getAbsolutePath());
         assertArrayEquals(LINE_TWO_FROM_MISSING_DB.split(":"), dataFromFile.get(1));
         assertEquals(dataFromFile.get(1)[FlatFileDatabaseManager.UUID_INDEX], HEALTHY_DB_LINE_ONE_UUID_STR);
 
@@ -199,7 +211,7 @@ public class FlatFileDatabaseManagerTest {
 
         //This makes sure our private method is working before the tests run afterwards
         ArrayList<String[]> dataFromFile = getSplitDataFromFile(healthyDB);
-        System.out.println("File Path: "+healthyDB.getAbsolutePath());
+        logger.info("File Path: "+healthyDB.getAbsolutePath());
         assertArrayEquals(HEALTHY_DB_LINE_1.split(":"), dataFromFile.get(0));
         assertEquals(dataFromFile.get(0)[FlatFileDatabaseManager.UUID_INDEX], HEALTHY_DB_LINE_ONE_UUID_STR);
         UUID healthDBEntryOneUUID = UUID.fromString(HEALTHY_DB_LINE_ONE_UUID_STR);
@@ -270,7 +282,7 @@ public class FlatFileDatabaseManagerTest {
         assertEquals(playerName, playerProfile.getPlayerName());
         assertEquals(uuid, playerProfile.getUniqueId());
 
-        PlayerProfile retrievedFromDisk = db.loadPlayerProfile(uuid, playerName);
+        PlayerProfile retrievedFromDisk = db.loadPlayerProfile(uuid);
         assertTrue(retrievedFromDisk.isLoaded());
         assertEquals(playerName, retrievedFromDisk.getPlayerName());
         assertEquals(uuid, retrievedFromDisk.getUniqueId());
@@ -320,7 +332,7 @@ public class FlatFileDatabaseManagerTest {
 
         //This makes sure our private method is working before the tests run afterwards
         ArrayList<String[]> dataFromFile = getSplitDataFromFile(dbFile);
-        System.out.println("File Path: " + dbFile.getAbsolutePath());
+        logger.info("File Path: " + dbFile.getAbsolutePath());
         assertArrayEquals(HEALTHY_DB_LINE_1.split(":"), dataFromFile.get(0));
         assertEquals(dataFromFile.get(0)[FlatFileDatabaseManager.UUID_INDEX], HEALTHY_DB_LINE_ONE_UUID_STR);
 
@@ -335,16 +347,50 @@ public class FlatFileDatabaseManagerTest {
         String playerName = "nossr50";
         UUID uuid = UUID.fromString("588fe472-1c82-4c4e-9aa1-7eefccb277e3");
 
-        PlayerProfile profile1 = db.loadPlayerProfile(uuid, null);
-        PlayerProfile profile2 = db.loadPlayerProfile(uuid, playerName);
-        PlayerProfile profile3 = db.loadPlayerProfile(uuid, "incorrectName");
-        PlayerProfile profile4 = db.loadPlayerProfile(new UUID(0, 1), "shouldBeUnloaded");
-        assertFalse(profile4.isLoaded());
+        PlayerProfile profile1 = db.loadPlayerProfile(uuid);
+        testHealthyDataProfileValues(playerName, uuid, profile1);
+
+
+        assertFalse(db.loadPlayerProfile(new UUID(0, 1)).isLoaded()); //This profile should not exist and therefor will return unloaded
+    }
+
+    @Test
+    public void testLoadByUUIDAndName() {
+        File dbFile = prepareDatabaseTestResource(DB_HEALTHY);
+
+        /*
+         * We have established the files are in good order, so now for the actual testing
+         */
 
-        //Three possible ways to load the thing
+        //This makes sure our private method is working before the tests run afterwards
+        ArrayList<String[]> dataFromFile = getSplitDataFromFile(dbFile);
+        logger.info("File Path: " + dbFile.getAbsolutePath());
+        assertArrayEquals(HEALTHY_DB_LINE_1.split(":"), dataFromFile.get(0));
+        assertEquals(dataFromFile.get(0)[FlatFileDatabaseManager.UUID_INDEX], HEALTHY_DB_LINE_ONE_UUID_STR);
+
+        db = new FlatFileDatabaseManager(dbFile, logger, PURGE_TIME, 0, true);
+        List<FlatFileDataFlag> flagsFound = db.checkFileHealthAndStructure();
+        assertNull(flagsFound); //No flags should be found
+
+        /*
+         * Once the DB looks fine load the profile
+         */
+
+        String playerName = "nossr50";
+        UUID uuid = UUID.fromString("588fe472-1c82-4c4e-9aa1-7eefccb277e3");
+
+        TestOfflinePlayer player = new TestOfflinePlayer(playerName, uuid);
+        PlayerProfile profile1 = db.loadPlayerProfile(player);
         testHealthyDataProfileValues(playerName, uuid, profile1);
-        testHealthyDataProfileValues(playerName, uuid, profile2);
-        testHealthyDataProfileValues(playerName, uuid, profile3);
+
+        String updatedName = "updatedName";
+        TestOfflinePlayer updatedNamePlayer = new TestOfflinePlayer(updatedName, uuid);
+        PlayerProfile updatedNameProfile = db.loadPlayerProfile(updatedNamePlayer);
+        testHealthyDataProfileValues(updatedName, uuid, updatedNameProfile);
+
+        TestOfflinePlayer shouldNotExist = new TestOfflinePlayer("doesntexist", new UUID(0, 1));
+        PlayerProfile profile3 = db.loadPlayerProfile(shouldNotExist);
+        assertFalse(profile3.isLoaded());
     }
 
     private File prepareDatabaseTestResource(@NotNull String dbFileName) {
@@ -393,11 +439,11 @@ public class FlatFileDatabaseManagerTest {
             if(SkillTools.isChildSkill(primarySkillType))
                 continue;
 
-//            System.out.println("Checking expected values for: "+primarySkillType);
-//            System.out.println("Profile Level Value: "+profile.getSkillLevel(primarySkillType));
-//            System.out.println("Expected Lvl Value: "+getExpectedLevelHealthyDBEntryOne(primarySkillType));
-//            System.out.println("Profile Exp Value: "+profile.getSkillXpLevelRaw(primarySkillType));
-//            System.out.println("Expected Exp Value: "+getExpectedExperienceHealthyDBEntryOne(primarySkillType));
+//            logger.info("Checking expected values for: "+primarySkillType);
+//            logger.info("Profile Level Value: "+profile.getSkillLevel(primarySkillType));
+//            logger.info("Expected Lvl Value: "+getExpectedLevelHealthyDBEntryOne(primarySkillType));
+//            logger.info("Profile Exp Value: "+profile.getSkillXpLevelRaw(primarySkillType));
+//            logger.info("Expected Exp Value: "+getExpectedExperienceHealthyDBEntryOne(primarySkillType));
 
             assertEquals(getExpectedLevelHealthyDBEntryOne(primarySkillType), profile.getSkillLevel(primarySkillType));
             assertEquals(getExpectedExperienceHealthyDBEntryOne(primarySkillType), profile.getSkillXpLevelRaw(primarySkillType), 0);
@@ -640,7 +686,7 @@ public class FlatFileDatabaseManagerTest {
 
         //This makes sure our private method is working before the tests run afterwards
         ArrayList<String[]> dataFromFile = getSplitDataFromFile(copyOfFile);
-        System.out.println("File Path: "+copyOfFile.getAbsolutePath());
+        logger.info("File Path: "+copyOfFile.getAbsolutePath());
         assertArrayEquals(BAD_FILE_LINE_ONE.split(":"), dataFromFile.get(0));
         assertEquals(dataFromFile.get(22)[0], "nossr51");
         assertArrayEquals(BAD_DATA_FILE_LINE_TWENTY_THREE.split(":"), dataFromFile.get(22));
@@ -688,7 +734,7 @@ public class FlatFileDatabaseManagerTest {
             out.write(writer.toString());
         } catch (FileNotFoundException e) {
             e.printStackTrace();
-            System.out.println("File not found");
+            logger.info("File not found");
         } catch (IOException e) {
             e.printStackTrace();
         } finally {
@@ -703,12 +749,12 @@ public class FlatFileDatabaseManagerTest {
         }
 
         try {
-            System.out.println("Added the following lines to the FlatFileDatabase for the purposes of the test...");
+            logger.info("Added the following lines to the FlatFileDatabase for the purposes of the test...");
             // Open the file
             in = new BufferedReader(new FileReader(filePath));
             String line;
             while ((line = in.readLine()) != null) {
-                System.out.println(line);
+                logger.info(line);
             }
         } catch (IOException e) {
             e.printStackTrace();
@@ -731,4 +777,188 @@ public class FlatFileDatabaseManagerTest {
         assertNotNull(dataFlags);
         assertTrue(dataFlags.contains(flag));
     }
+
+    private class TestOfflinePlayer implements OfflinePlayer {
+
+        private final @NotNull String name;
+        private final @NotNull UUID uuid;
+
+        private TestOfflinePlayer(@NotNull String name, @NotNull UUID uuid) {
+            this.name = name;
+            this.uuid = uuid;
+        }
+
+        @Override
+        public boolean isOnline() {
+            return false;
+        }
+
+        @Nullable
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @NotNull
+        @Override
+        public UUID getUniqueId() {
+            return uuid;
+        }
+
+        @Override
+        public boolean isBanned() {
+            return false;
+        }
+
+        @Override
+        public boolean isWhitelisted() {
+            return false;
+        }
+
+        @Override
+        public void setWhitelisted(boolean value) {
+
+        }
+
+        @Nullable
+        @Override
+        public Player getPlayer() {
+            return null;
+        }
+
+        @Override
+        public long getFirstPlayed() {
+            return 0;
+        }
+
+        @Override
+        public long getLastPlayed() {
+            return 0;
+        }
+
+        @Override
+        public boolean hasPlayedBefore() {
+            return false;
+        }
+
+        @Nullable
+        @Override
+        public Location getBedSpawnLocation() {
+            return null;
+        }
+
+        @Override
+        public void incrementStatistic(@NotNull Statistic statistic) throws IllegalArgumentException {
+
+        }
+
+        @Override
+        public void decrementStatistic(@NotNull Statistic statistic) throws IllegalArgumentException {
+
+        }
+
+        @Override
+        public void incrementStatistic(@NotNull Statistic statistic, int amount) throws IllegalArgumentException {
+
+        }
+
+        @Override
+        public void decrementStatistic(@NotNull Statistic statistic, int amount) throws IllegalArgumentException {
+
+        }
+
+        @Override
+        public void setStatistic(@NotNull Statistic statistic, int newValue) throws IllegalArgumentException {
+
+        }
+
+        @Override
+        public int getStatistic(@NotNull Statistic statistic) throws IllegalArgumentException {
+            return 0;
+        }
+
+        @Override
+        public void incrementStatistic(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException {
+
+        }
+
+        @Override
+        public void decrementStatistic(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException {
+
+        }
+
+        @Override
+        public int getStatistic(@NotNull Statistic statistic, @NotNull Material material) throws IllegalArgumentException {
+            return 0;
+        }
+
+        @Override
+        public void incrementStatistic(@NotNull Statistic statistic, @NotNull Material material, int amount) throws IllegalArgumentException {
+
+        }
+
+        @Override
+        public void decrementStatistic(@NotNull Statistic statistic, @NotNull Material material, int amount) throws IllegalArgumentException {
+
+        }
+
+        @Override
+        public void setStatistic(@NotNull Statistic statistic, @NotNull Material material, int newValue) throws IllegalArgumentException {
+
+        }
+
+        @Override
+        public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException {
+
+        }
+
+        @Override
+        public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException {
+
+        }
+
+        @Override
+        public int getStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType) throws IllegalArgumentException {
+            return 0;
+        }
+
+        @Override
+        public void incrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount) throws IllegalArgumentException {
+
+        }
+
+        @Override
+        public void decrementStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int amount) {
+
+        }
+
+        @Override
+        public void setStatistic(@NotNull Statistic statistic, @NotNull EntityType entityType, int newValue) {
+
+        }
+
+        @NotNull
+        @Override
+        public Map<String, Object> serialize() {
+            return null;
+        }
+
+        @Override
+        public boolean isOp() {
+            return false;
+        }
+
+        @Override
+        public void setOp(boolean value) {
+
+        }
+    }
+
+    private static class DebugFilter implements Filter {
+
+        @Override
+        public boolean isLoggable(LogRecord record) {
+            return false;
+        }
+    }
 }