diff --git a/Changelog.txt b/Changelog.txt index 093b9d52a..c895f3955 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -161,6 +161,11 @@ Version 2.2.0 Added API method to grab the level cap of a skill by its PrimarySkillType ENUM definition Added API method to check if a skill was being level capped Added 'UndefinedSkillBehaviour' for trying to use a method that has no behaviour defined for the provided skill +Version 2.1.69 + Fixed a few places where mcMMO would not save player data immediately which may cause players to lose a few minutes of progress + + A big thanks to Sleepyflea for helping test this patch and report this bug. + Version 2.1.68 Updated Japanese locale (thanks Snake) Fixed a bug where consuming food in the off hand did not trigger the Diet abilities diff --git a/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java b/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java index 0ea25819a..9174e56fb 100644 --- a/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java +++ b/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java @@ -1073,7 +1073,7 @@ public final class ExperienceAPI { PlayerProfile profile = getOfflineProfile(playerUniqueId); profile.addXp(skill, XP); - profile.save(); + profile.save(true); } @Deprecated diff --git a/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java b/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java index a2f54eb34..d768930a0 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java +++ b/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java @@ -90,9 +90,10 @@ public class McMMOPlayer { private Location teleportCommence; private boolean isUsingUnarmed; private HashMap personalXPModifiers; + private String playerName; public McMMOPlayer(Player player, PlayerProfile profile) { - String playerName = player.getName(); + this.playerName = player.getName(); UUID uuid = player.getUniqueId(); this.player = player; @@ -159,6 +160,10 @@ public class McMMOPlayer { return personalXPModifiers.get(primarySkillType); } + public String getPlayerName() { + return playerName; + } + /*public void hideXpBar(PrimarySkillType primarySkillType) { experienceBarManager.hideExperienceBar(primarySkillType); @@ -1005,7 +1010,7 @@ public class McMMOPlayer { BleedTimerTask.bleedOut(thisPlayer); if (syncSave) { - getProfile().save(); + getProfile().save(true); } else { getProfile().scheduleAsyncSave(); } 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 2790dcdfa..e1b29851c 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/player/PlayerProfile.java +++ b/src/main/java/com/gmail/nossr50/datatypes/player/PlayerProfile.java @@ -85,14 +85,23 @@ public class PlayerProfile { } public void scheduleAsyncSave() { - new PlayerProfileSaveTask(this).runTaskAsynchronously(mcMMO.p); + new PlayerProfileSaveTask(this, false).runTaskAsynchronously(mcMMO.p); + } + + public void scheduleSyncSave() { + new PlayerProfileSaveTask(this, true).runTask(mcMMO.p); } public void scheduleAsyncSaveDelay() { - new PlayerProfileSaveTask(this).runTaskLaterAsynchronously(mcMMO.p, 20); + new PlayerProfileSaveTask(this, false).runTaskLaterAsynchronously(mcMMO.p, 20); } - public void save() { + @Deprecated + public void scheduleSyncSaveDelay() { + new PlayerProfileSaveTask(this, true).runTaskLater(mcMMO.p, 20); + } + + public void save(boolean useSync) { if (!changed || !loaded) { saveAttempts = 0; return; @@ -112,7 +121,12 @@ public class PlayerProfile { if (saveAttempts < 10) { saveAttempts++; - scheduleAsyncSaveDelay(); + + if(useSync) + scheduleSyncSave(); //Execute sync saves immediately + else + scheduleAsyncSaveDelay(); + return; } else { mcMMO.p.getLogger().severe("mcMMO has failed to save the profile for " @@ -121,9 +135,9 @@ public class PlayerProfile { " Check your console for errors and inspect your DB for issues."); } + } else { + saveAttempts = 0; } - - saveAttempts = 0; } public String getPlayerName() { @@ -135,7 +149,7 @@ public class PlayerProfile { } public void setUniqueId(UUID uuid) { - changed = true; + markProfileDirty(); this.uuid = uuid; } @@ -153,17 +167,24 @@ public class PlayerProfile { } public void setMobHealthbarType(MobHealthbarType mobHealthbarType) { - changed = true; + markProfileDirty(); this.mobHealthbarType = mobHealthbarType; } + /** + * Marks the profile as "dirty" which flags a profile to be saved in the next save operation + */ + public void markProfileDirty() { + changed = true; + } + public int getScoreboardTipsShown() { return scoreboardTipsShown; } public void setScoreboardTipsShown(int scoreboardTipsShown) { - changed = true; + markProfileDirty(); this.scoreboardTipsShown = scoreboardTipsShown; } @@ -181,12 +202,12 @@ public class PlayerProfile { } protected void setChimaeraWingDATS(int DATS) { - changed = true; + markProfileDirty(); uniquePlayerData.put(UniqueDataType.CHIMAERA_WING_DATS, DATS); } public void setUniqueData(UniqueDataType uniqueDataType, int newData) { - changed = true; + markProfileDirty(); uniquePlayerData.put(uniqueDataType, newData); } @@ -211,7 +232,7 @@ public class PlayerProfile { * @param DATS the DATS of the ability */ protected void setAbilityDATS(SuperAbilityType ability, long DATS) { - changed = true; + markProfileDirty(); abilityDATS.put(ability, (int) (DATS * .001D)); } @@ -220,7 +241,7 @@ public class PlayerProfile { * Reset all ability cooldowns. */ protected void resetCooldowns() { - changed = true; + markProfileDirty(); for (SuperAbilityType ability : abilityDATS.keySet()) { abilityDATS.put(ability, 0); @@ -248,7 +269,7 @@ public class PlayerProfile { return; } - changed = true; + markProfileDirty(); skillsXp.put(skill, xpLevel); } @@ -256,7 +277,7 @@ public class PlayerProfile { protected double levelUp(PrimarySkillType skill) { double xpRemoved = getXpToLevel(skill); - changed = true; + markProfileDirty(); skills.put(skill, skills.get(skill) + 1); skillsXp.put(skill, skillsXp.get(skill) - xpRemoved); @@ -275,7 +296,7 @@ public class PlayerProfile { return; } - changed = true; + markProfileDirty(); skillsXp.put(skill, skillsXp.get(skill) - xp); } @@ -285,7 +306,7 @@ public class PlayerProfile { return; } - changed = true; + markProfileDirty(); skillsXp.put(skill, skillsXp.get(skill) - xp); } @@ -301,7 +322,7 @@ public class PlayerProfile { return; } - changed = true; + markProfileDirty(); //Don't allow levels to be negative if (level < 0) @@ -328,7 +349,7 @@ public class PlayerProfile { * @param xp Number of experience to add */ public void addXp(PrimarySkillType skill, double xp) { - changed = true; + markProfileDirty(); if (skill.isChildSkill()) { Set parentSkills = FamilyTree.getParents(skill); diff --git a/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java b/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java index cab65ef0c..050a18ecc 100644 --- a/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java @@ -492,7 +492,9 @@ public class PlayerListener implements Listener { } McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player); - mcMMOPlayer.logout(false); + //There's an issue with using Async saves on player quit + //Basically there are conditions in which an async task does not execute fast enough to save the data if the server shutdown shortly after this task was scheduled + mcMMOPlayer.logout(true); } /** diff --git a/src/main/java/com/gmail/nossr50/mcMMO.java b/src/main/java/com/gmail/nossr50/mcMMO.java index 3e178ca06..7c5684f54 100644 --- a/src/main/java/com/gmail/nossr50/mcMMO.java +++ b/src/main/java/com/gmail/nossr50/mcMMO.java @@ -196,7 +196,6 @@ public class mcMMO extends JavaPlugin { @Override public void onDisable() { try { -// Alchemy.finishAllBrews(); // Finish all partially complete AlchemyBrewTasks to prevent vanilla brewing continuation on restart UserManager.saveAll(); // Make sure to save player information if the server shuts down UserManager.clearAll(); PartyManager.saveParties(); // Save our parties diff --git a/src/main/java/com/gmail/nossr50/runnables/SaveTimerTask.java b/src/main/java/com/gmail/nossr50/runnables/SaveTimerTask.java index 240c52178..2e679b824 100644 --- a/src/main/java/com/gmail/nossr50/runnables/SaveTimerTask.java +++ b/src/main/java/com/gmail/nossr50/runnables/SaveTimerTask.java @@ -14,7 +14,7 @@ public class SaveTimerTask extends BukkitRunnable { int count = 1; for (McMMOPlayer mcMMOPlayer : UserManager.getPlayers()) { - new PlayerProfileSaveTask(mcMMOPlayer.getProfile()).runTaskLaterAsynchronously(mcMMO.p, count); + new PlayerProfileSaveTask(mcMMOPlayer.getProfile(), false).runTaskLaterAsynchronously(mcMMO.p, count); count++; } diff --git a/src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileSaveTask.java b/src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileSaveTask.java index 414896983..593d785ca 100644 --- a/src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileSaveTask.java +++ b/src/main/java/com/gmail/nossr50/runnables/player/PlayerProfileSaveTask.java @@ -5,13 +5,15 @@ import org.bukkit.scheduler.BukkitRunnable; public class PlayerProfileSaveTask extends BukkitRunnable { private PlayerProfile playerProfile; + private boolean isSync; - public PlayerProfileSaveTask(PlayerProfile playerProfile) { + public PlayerProfileSaveTask(PlayerProfile playerProfile, boolean isSync) { this.playerProfile = playerProfile; + this.isSync = isSync; } @Override public void run() { - playerProfile.save(); + playerProfile.save(isSync); } } diff --git a/src/main/java/com/gmail/nossr50/util/player/UserManager.java b/src/main/java/com/gmail/nossr50/util/player/UserManager.java index 071fe6739..1378d9473 100644 --- a/src/main/java/com/gmail/nossr50/util/player/UserManager.java +++ b/src/main/java/com/gmail/nossr50/util/player/UserManager.java @@ -11,11 +11,14 @@ import org.bukkit.metadata.FixedMetadataValue; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; public final class UserManager { private UserManager() { } + private static HashSet playerDataSet; //Used to track players for sync saves on shutdown + /** * Track a new user. @@ -24,6 +27,16 @@ public final class UserManager { */ public static void track(McMMOPlayer mcMMOPlayer) { mcMMOPlayer.getPlayer().setMetadata(MetadataConstants.PLAYER_DATA_METAKEY, new FixedMetadataValue(mcMMO.p, mcMMOPlayer)); + + if(playerDataSet == null) + playerDataSet = new HashSet<>(); + + playerDataSet.add(mcMMOPlayer); //for sync saves on shutdown + } + + public static void cleanupPlayer(McMMOPlayer mcMMOPlayer) { + if(playerDataSet != null && playerDataSet.contains(mcMMOPlayer)) + playerDataSet.remove(mcMMOPlayer); } /** @@ -32,7 +45,11 @@ public final class UserManager { * @param player The Player object */ public static void remove(Player player) { + McMMOPlayer mcMMOPlayer = getPlayer(player); player.removeMetadata(MetadataConstants.PLAYER_DATA_METAKEY, mcMMO.p); + + if(playerDataSet != null && playerDataSet.contains(mcMMOPlayer)) + playerDataSet.remove(mcMMOPlayer); //Clear sync save tracking } /** @@ -42,22 +59,32 @@ public final class UserManager { for (Player player : mcMMO.p.getServer().getOnlinePlayers()) { remove(player); } + + if(playerDataSet != null) + playerDataSet.clear(); //Clear sync save tracking } /** * Save all users ON THIS THREAD. */ public static void saveAll() { - ImmutableList onlinePlayers = ImmutableList.copyOf(mcMMO.p.getServer().getOnlinePlayers()); - mcMMO.p.debug("Saving mcMMOPlayers... (" + onlinePlayers.size() + ")"); + ImmutableList trackedSyncData = ImmutableList.copyOf(playerDataSet); - for (Player player : onlinePlayers) { - try { - getPlayer(player).getProfile().save(); - } catch (Exception e) { - mcMMO.p.getLogger().warning("Could not save mcMMO player data for player: " + player.getName()); + mcMMO.p.getLogger().info("Saving mcMMOPlayers... (" + trackedSyncData.size() + ")"); + + for (McMMOPlayer playerData : trackedSyncData) { + try + { + mcMMO.p.getLogger().info("Saving data for player: "+playerData.getPlayerName()); + playerData.getProfile().save(true); + } + catch (Exception e) + { + mcMMO.p.getLogger().warning("Could not save mcMMO player data for player: " + playerData.getPlayerName()); } } + + mcMMO.p.getLogger().info("Finished save operation for "+trackedSyncData.size()+" players!"); } public static Collection getPlayers() {