diff --git a/Changelog.txt b/Changelog.txt index 4ec46bbba..3361322de 100644 --- a/Changelog.txt +++ b/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 diff --git a/src/main/java/com/gmail/nossr50/api/DatabaseAPI.java b/src/main/java/com/gmail/nossr50/api/DatabaseAPI.java index 8f2879549..55f682377 100644 --- a/src/main/java/com/gmail/nossr50/api/DatabaseAPI.java +++ b/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 player UUID + * @param uuid 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 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 playerName target player + * @return true if the player exists in the DB, false if they do not + */ + public boolean doesPlayerExistInDB(@NotNull String playerName) { + PlayerProfile playerProfile = mcMMO.getDatabaseManager().loadPlayerProfile(playerName); return playerProfile.isLoaded(); } diff --git a/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java b/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java index a7055bbb5..678f1346b 100644 --- a/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java +++ b/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. + *
+ * 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. *
@@ -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. + *
+ * 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. *
@@ -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. + *
+ * 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. *
@@ -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. + *
+ * 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. *
@@ -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(); diff --git a/src/main/java/com/gmail/nossr50/commands/database/ConvertDatabaseCommand.java b/src/main/java/com/gmail/nossr50/commands/database/ConvertDatabaseCommand.java index 1667e9111..8642bc922 100644 --- a/src/main/java/com/gmail/nossr50/commands/database/ConvertDatabaseCommand.java +++ b/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); diff --git a/src/main/java/com/gmail/nossr50/commands/experience/ExperienceCommand.java b/src/main/java/com/gmail/nossr50/commands/experience/ExperienceCommand.java index 4e84f8bff..5f310828e 100644 --- a/src/main/java/com/gmail/nossr50/commands/experience/ExperienceCommand.java +++ b/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)) { diff --git a/src/main/java/com/gmail/nossr50/commands/experience/SkillresetCommand.java b/src/main/java/com/gmail/nossr50/commands/experience/SkillresetCommand.java index 4b558d422..d4353224e 100644 --- a/src/main/java/com/gmail/nossr50/commands/experience/SkillresetCommand.java +++ b/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)) { diff --git a/src/main/java/com/gmail/nossr50/database/DatabaseManager.java b/src/main/java/com/gmail/nossr50/database/DatabaseManager.java index 3f9ac7a77..c377bbbfe 100644 --- a/src/main/java/com/gmail/nossr50/database/DatabaseManager.java +++ b/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); diff --git a/src/main/java/com/gmail/nossr50/database/FlatFileDatabaseManager.java b/src/main/java/com/gmail/nossr50/database/FlatFileDatabaseManager.java index dd85cccf5..f301777b1 100644 --- a/src/main/java/com/gmail/nossr50/database/FlatFileDatabaseManager.java +++ b/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,103 +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); - } - - public @NotNull PlayerProfile loadPlayerProfile(@NotNull UUID uuid, @Nullable String playerName) { - return loadPlayerByUUID(uuid, playerName, false); + return processUserQuery(getUserQuery(null, playerName)); } public @NotNull PlayerProfile loadPlayerProfile(@NotNull UUID uuid) { - return loadPlayerByUUID(uuid, null, false); + return processUserQuery(getUserQuery(uuid, null)); + } + + 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) { - BufferedReader in = null; - - synchronized (fileWritingLock) { - try { - // Open the user file - in = new BufferedReader(new FileReader(usersFilePath)); - String line; - - while ((line = in.readLine()) != null) { - // 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; - } - - /* 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 - } - - /* - * UUID Matched! - * Making it this far means the current data line is considered a match - */ - - - /* 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; - } - } - } - - 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 - } - } - } + 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!"); } - - /* - * No match was found in the file - */ - - return grabUnloadedProfile(uuid, playerName); //Create an empty new profile and return } - private @NotNull PlayerProfile loadPlayerByName(@NotNull String playerName) { + private @NotNull PlayerProfile queryByName(@NotNull UserQueryName userQuery) { + String playerName = userQuery.getName(); BufferedReader in = null; synchronized (fileWritingLock) { @@ -646,19 +586,23 @@ 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 < (USERNAME_INDEX + 1)) { continue; } + //If we couldn't find anyone if(playerName.equalsIgnoreCase(rawSplitData[USERNAME_INDEX])) { return loadFromLine(rawSplitData); @@ -679,10 +623,135 @@ public final class FlatFileDatabaseManager implements DatabaseManager { } } + //Return a new blank profile return new PlayerProfile(playerName, new UUID(0, 0), startingLevel); } + 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; + } + + 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(); + } 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 + } + } + } + } + + /* + * No match was found in the file + */ + + return grabUnloadedProfile(uuid, "Player-Not-Found="+uuid.toString()); + } + + private @NotNull PlayerProfile queryByUUIDAndName(@NotNull UserQueryFull userQuery) { + BufferedReader in = null; + String playerName = userQuery.getName(); + 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; + } + + 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) { + 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 + } + } + } + } + + /* + * 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) { if(playerName == null) { playerName = ""; //No name for you boy! diff --git a/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java b/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java index a937efb0b..566a2e4f5 100644 --- a/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java +++ b/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); } diff --git a/src/main/java/com/gmail/nossr50/database/UserQuery.java b/src/main/java/com/gmail/nossr50/database/UserQuery.java new file mode 100644 index 000000000..4c6b5e730 --- /dev/null +++ b/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(); +} diff --git a/src/main/java/com/gmail/nossr50/database/UserQueryFull.java b/src/main/java/com/gmail/nossr50/database/UserQueryFull.java new file mode 100644 index 000000000..47a3e965a --- /dev/null +++ b/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; + } +} diff --git a/src/main/java/com/gmail/nossr50/database/UserQueryName.java b/src/main/java/com/gmail/nossr50/database/UserQueryName.java new file mode 100644 index 000000000..60604101c --- /dev/null +++ b/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(); +} diff --git a/src/main/java/com/gmail/nossr50/database/UserQueryNameImpl.java b/src/main/java/com/gmail/nossr50/database/UserQueryNameImpl.java new file mode 100644 index 000000000..fe7abf910 --- /dev/null +++ b/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; + } +} diff --git a/src/main/java/com/gmail/nossr50/database/UserQueryType.java b/src/main/java/com/gmail/nossr50/database/UserQueryType.java new file mode 100644 index 000000000..588dd76b0 --- /dev/null +++ b/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 +} diff --git a/src/main/java/com/gmail/nossr50/database/UserQueryUUID.java b/src/main/java/com/gmail/nossr50/database/UserQueryUUID.java new file mode 100644 index 000000000..192997f90 --- /dev/null +++ b/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(); + +} diff --git a/src/main/java/com/gmail/nossr50/database/UserQueryUUIDImpl.java b/src/main/java/com/gmail/nossr50/database/UserQueryUUIDImpl.java new file mode 100644 index 000000000..49ad038f5 --- /dev/null +++ b/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; + } +} diff --git a/src/main/java/com/gmail/nossr50/datatypes/player/PlayerProfile.java b/src/main/java/com/gmail/nossr50/datatypes/player/PlayerProfile.java index 81d2d8417..ff9ebbb70 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/player/PlayerProfile.java +++ b/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(); diff --git a/src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileLoadingTask.java b/src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileLoadingTask.java index 95c0ddc71..5a82b43da 100644 --- a/src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileLoadingTask.java +++ b/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()); diff --git a/src/test/java/com/gmail/nossr50/database/FlatFileDatabaseManagerTest.java b/src/test/java/com/gmail/nossr50/database/FlatFileDatabaseManagerTest.java index e00d3f3ae..e6001626a 100644 --- a/src/test/java/com/gmail/nossr50/database/FlatFileDatabaseManagerTest.java +++ b/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 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 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 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()); - - //Three possible ways to load the thing + PlayerProfile profile1 = db.loadPlayerProfile(uuid); testHealthyDataProfileValues(playerName, uuid, profile1); - testHealthyDataProfileValues(playerName, uuid, profile2); - testHealthyDataProfileValues(playerName, uuid, profile3); + + + 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 + */ + + //This makes sure our private method is working before the tests run afterwards + ArrayList 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 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); + + 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 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 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; + } + } } \ No newline at end of file