diff --git a/src/main/java/world/bentobox/challenges/ChallengesManager.java b/src/main/java/world/bentobox/challenges/ChallengesManager.java index 721800c..4f889c3 100644 --- a/src/main/java/world/bentobox/challenges/ChallengesManager.java +++ b/src/main/java/world/bentobox/challenges/ChallengesManager.java @@ -23,6 +23,7 @@ import world.bentobox.challenges.events.ChallengeResetAllEvent; import world.bentobox.challenges.events.ChallengeResetEvent; import world.bentobox.challenges.events.LevelCompletedEvent; import world.bentobox.challenges.utils.LevelStatus; +import world.bentobox.challenges.utils.Utils; /** @@ -148,6 +149,7 @@ public class ChallengesManager this.challengeDatabase.loadObjects().forEach(this::loadChallenge); this.levelDatabase.loadObjects().forEach(this::loadLevel); + // It is not necessary to load all players in memory. // this.playersDatabase.loadObjects().forEach(this::loadPlayerData); } @@ -494,6 +496,180 @@ public class ChallengesManager } + // --------------------------------------------------------------------- + // Section: Wipe data + // --------------------------------------------------------------------- + + + /** + * This method migrated all challenges addon data from worldName to addonID formant. + */ + public void migrateDatabase(User user, World world) + { + world = Util.getWorld(world); + + if (user.isPlayer()) + { + user.sendMessage("challenges.messages.admin.migrate-start"); + } + else + { + this.addon.log("Starting migration to new data format."); + } + + boolean challenges = this.migrateChallenges(world); + boolean levels = this.migrateLevels(world); + + if (challenges || levels) + { + this.migratePlayers(world); + + if (user.isPlayer()) + { + user.sendMessage("challenges.messages.admin.migrate-end"); + } + else + { + this.addon.log("Migration to new data format completed."); + } + } + else + { + if (user.isPlayer()) + { + user.sendMessage("challenges.messages.admin.migrate-not"); + } + else + { + this.addon.log("All data is valid. Migration is not necessary."); + } + } + } + + + /** + * This method collects all data from levels database and migrates them. + */ + private boolean migrateLevels(World world) + { + String addonName = Utils.getGameMode(world); + + if (addonName == null || addonName.equalsIgnoreCase(world.getName())) + { + return false; + } + + boolean updated = false; + List levelList = this.levelDatabase.loadObjects(); + for (ChallengeLevel level : levelList) + { + if (level.getUniqueId().regionMatches(true, 0, world.getName() + "_", 0, world.getName().length() + 1)) + { + this.levelDatabase.deleteID(level.getUniqueId()); + this.levelCacheData.remove(level.getUniqueId()); + + level.setUniqueId( + addonName + level.getUniqueId().substring(world.getName().length())); + + Set challengesID = new HashSet<>(level.getChallenges()); + level.getChallenges().clear(); + + challengesID.forEach(challenge -> + level.getChallenges().add(addonName + challenge.substring(world.getName().length()))); + + this.levelDatabase.saveObject(level); + this.levelCacheData.put(level.getUniqueId(), level); + + updated = true; + } + } + + return updated; + } + + + /** + * This method collects all data from challenges database and migrates them. + */ + private boolean migrateChallenges(World world) + { + String addonName = Utils.getGameMode(world); + + if (addonName == null || addonName.equalsIgnoreCase(world.getName())) + { + return false; + } + + boolean updated = false; + + List challengeList = this.challengeDatabase.loadObjects(); + + for (Challenge challenge : challengeList) + { + if (challenge.getUniqueId().regionMatches(true, 0, world.getName() + "_", 0, world.getName().length() + 1)) + { + this.challengeDatabase.deleteID(challenge.getUniqueId()); + this.challengeCacheData.remove(challenge.getUniqueId()); + + challenge.setUniqueId(addonName + challenge.getUniqueId().substring(world.getName().length())); + updated = true; + + this.challengeDatabase.saveObject(challenge); + this.challengeCacheData.put(challenge.getUniqueId(), challenge); + } + } + + return updated; + } + + + /** + * This method collects all data from players database and migrates them. + */ + private void migratePlayers(World world) + { + String addonName = Utils.getGameMode(world); + + if (addonName == null || addonName.equalsIgnoreCase(world.getName())) + { + return; + } + + List playerDataList = this.playersDatabase.loadObjects(); + + playerDataList.forEach(playerData -> { + Set levelsDone = new TreeSet<>(playerData.getLevelsDone()); + levelsDone.forEach(level -> { + if (level.regionMatches(true, 0, world.getName() + "_", 0, world.getName().length() + 1)) + { + playerData.getLevelsDone().remove(level); + playerData.getLevelsDone().add(addonName + level.substring(world.getName().length())); + } + }); + + Map challengeStatus = new TreeMap<>(playerData.getChallengeStatus()); + challengeStatus.forEach((challenge, count) -> { + if (challenge.regionMatches(true, 0, world.getName() + "_", 0, world.getName().length() + 1)) + { + playerData.getChallengeStatus().remove(challenge); + playerData.getChallengeStatus().put(addonName + challenge.substring(world.getName().length()), count); + } + }); + + Map challengeTimestamp = new TreeMap<>(playerData.getChallengesTimestamp()); + challengeTimestamp.forEach((challenge, timestamp) -> { + if (challenge.regionMatches(true, 0, world.getName() + "_", 0, world.getName().length() + 1)) + { + playerData.getChallengesTimestamp().remove(challenge); + playerData.getChallengesTimestamp().put(addonName + challenge.substring(world.getName().length()), timestamp); + } + }); + + this.playersDatabase.saveObject(playerData); + }); + } + + // --------------------------------------------------------------------- // Section: Saving methods // --------------------------------------------------------------------- diff --git a/src/main/java/world/bentobox/challenges/commands/admin/Challenges.java b/src/main/java/world/bentobox/challenges/commands/admin/Challenges.java index 52ac0ff..aa86351 100644 --- a/src/main/java/world/bentobox/challenges/commands/admin/Challenges.java +++ b/src/main/java/world/bentobox/challenges/commands/admin/Challenges.java @@ -43,6 +43,10 @@ public class Challenges extends CompositeCommand // Reset challenge command new ResetCommand(this.getAddon(), this); + + new ShowChallenges(this.getAddon(), this); + + new MigrateCommand(this.getAddon(), this); } diff --git a/src/main/java/world/bentobox/challenges/commands/admin/MigrateCommand.java b/src/main/java/world/bentobox/challenges/commands/admin/MigrateCommand.java new file mode 100644 index 0000000..cfcadfd --- /dev/null +++ b/src/main/java/world/bentobox/challenges/commands/admin/MigrateCommand.java @@ -0,0 +1,37 @@ +package world.bentobox.challenges.commands.admin; + + +import java.util.List; + +import world.bentobox.bentobox.api.addons.Addon; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.challenges.ChallengesAddon; + + +public class MigrateCommand extends CompositeCommand { + + /** + * Migrates challenges + * @param addon + * @param cmd + */ + public MigrateCommand(Addon addon, CompositeCommand cmd) { + super(addon, cmd, "migrate"); + } + + @Override + public boolean execute(User user, String label, List args) { + ((ChallengesAddon)getAddon()).getChallengesManager().migrateDatabase(user, getWorld()); + + return true; + } + + + @Override + public void setup() { + this.setPermission("challenges.admin"); + this.setParametersHelp("challenges.commands.admin.migrate.parameters"); + this.setDescription("challenges.commands.admin.migrate.description"); + } +} diff --git a/src/main/java/world/bentobox/challenges/commands/admin/ShowChallenges.java b/src/main/java/world/bentobox/challenges/commands/admin/ShowChallenges.java index 9ea60f7..6226fc5 100644 --- a/src/main/java/world/bentobox/challenges/commands/admin/ShowChallenges.java +++ b/src/main/java/world/bentobox/challenges/commands/admin/ShowChallenges.java @@ -1,6 +1,7 @@ package world.bentobox.challenges.commands.admin; import java.util.List; +import java.util.logging.Level; import world.bentobox.challenges.ChallengesAddon; import world.bentobox.bentobox.api.addons.Addon; @@ -28,8 +29,19 @@ public class ShowChallenges extends CompositeCommand { @Override public boolean execute(User user, String label, List args) { - ((ChallengesAddon)getAddon()).getChallengesManager().getAllChallengesNames(this.getWorld()).forEach(user::sendRawMessage); + if (user.isPlayer()) + { + ((ChallengesAddon) getAddon()).getChallengesManager(). + getAllChallengesNames(this.getWorld()).forEach(user::sendRawMessage); + } + else + { + ((ChallengesAddon) getAddon()).getChallengesManager(). + getAllChallengesNames(this.getWorld()).forEach(c -> this.getAddon().log(c)); + } + return true; + } } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index dc5659f..de70610 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -36,6 +36,9 @@ challenges: reset: description: 'This command allows to reset challenge for player without GUI. If "challenge_id" is set to "all", then it will reset all challenges.' parameters: ' ' + migrate: + description: 'This method allows to migrate challenges data that refers to current game mode world to new 0.8.0 storage format.' + parameters: '' user: main: description: 'This method opens Challenges GUI.' @@ -341,6 +344,10 @@ challenges: reset: '&2You reset challenge [name] for [player]!' reset-all: '&2All [player] challenges were reset!' not-completed: '&2This challenge is not completed yet!' + + migrate-start: '&2Start migrating challenges addon data.' + migrate-end: '&2Challenges addon data is updated to new format.' + migrate-not: '&2All data is valid.' you-completed-challenge: '&2You completed the [value] &r&2challenge!' you-repeated-challenge: '&2You repeated the [value] &r&2challenge!' you-repeated-challenge-multiple: '&2You repeated the [value] &r&2challenge [count] times!'