diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java index 1f4142b8b..ab12543ed 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/DefaultAdminCommand.java @@ -11,6 +11,7 @@ import world.bentobox.bentobox.api.commands.admin.range.AdminRangeCommand; import world.bentobox.bentobox.api.commands.admin.resets.AdminResetsCommand; import world.bentobox.bentobox.api.commands.admin.team.AdminTeamAddCommand; import world.bentobox.bentobox.api.commands.admin.team.AdminTeamDisbandCommand; +import world.bentobox.bentobox.api.commands.admin.team.AdminTeamFixCommand; import world.bentobox.bentobox.api.commands.admin.team.AdminTeamKickCommand; import world.bentobox.bentobox.api.commands.admin.team.AdminTeamSetownerCommand; import world.bentobox.bentobox.api.localization.TextVariables; @@ -59,6 +60,7 @@ public abstract class DefaultAdminCommand extends CompositeCommand { new AdminTeamKickCommand(this); new AdminTeamDisbandCommand(this); new AdminTeamSetownerCommand(this); + new AdminTeamFixCommand(this); // Schems new AdminBlueprintCommand(this); // Register/unregister islands diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamFixCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamFixCommand.java new file mode 100644 index 000000000..601a4cb8f --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamFixCommand.java @@ -0,0 +1,35 @@ +package world.bentobox.bentobox.api.commands.admin.team; + +import java.util.List; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.user.User; + +public class AdminTeamFixCommand extends CompositeCommand { + + + public AdminTeamFixCommand(CompositeCommand parent) { + super(parent, "fix"); + } + + @Override + public void setup() { + setPermission("mod.team"); + setDescription("commands.admin.team.fix.description"); + } + + @Override + public boolean canExecute(User user, String label, List args) { + // If args are not right, show help + if (!args.isEmpty()) { + showHelp(this, user); + return false; + } + return true; + } + @Override + public boolean execute(User user, String label, List args) { + getIslands().checkTeams(user, getWorld()); + return true; + } +} diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index 10393682c..c52c4d9fa 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -1511,4 +1511,50 @@ public class IslandsManager { .anyMatch(n -> ChatColor.stripColor(n).equals(ChatColor.stripColor(name))); } + public CompletableFuture checkTeams(User user, World world) { + CompletableFuture r = new CompletableFuture<>(); + user.sendMessage("commands.admin.team.fix.scanning"); + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + Map owners = new HashMap<>(); + Map freq = new HashMap<>(); + handler.loadObjects() + .stream().filter(i -> i.getOwner() != null) + .filter(i -> i.getWorld().equals(world)) + .forEach(i -> { + int count = freq.containsKey(i.getOwner()) ? freq.get(i.getOwner()) : 0; + freq.put(i.getOwner(), count + 1); + if (owners.containsKey(i.getOwner())) { + user.sendMessage("commands.admin.team.fix.duplicate-owner" , TextVariables.NAME, plugin.getPlayers().getName(i.getOwner())); + } else { + owners.put(i.getOwner(), i); + } + }); + freq.entrySet().stream().filter(en -> en.getValue() > 1).forEach(en -> { + user.sendMessage("commands.admin.team.fix.player-has", TextVariables.NAME, plugin.getPlayers().getName(en.getKey()), TextVariables.NUMBER, String.valueOf(en.getValue())); + }); + owners.entrySet().stream().forEach(en -> { + en.getValue().getMemberSet().stream() + // Filter out owners + .filter(u-> owners.containsKey(u) && !owners.get(u).equals(en.getValue())) + .forEach(u -> { + user.sendMessage("commands.admin.team.fix.member-and-owner", TextVariables.NAME, plugin.getPlayers().getName(u)); + user.sendMessage("commands.admin.team.fix.member-of", "[xyz]", Util.xyz(en.getValue().getCenter().toVector())); + user.sendMessage("commands.admin.team.fix.owner-of", "[xyz]", Util.xyz(owners.get(u).getCenter().toVector())); + // Remove membership of this island + Island i = islandCache.getIslandById(en.getValue().getUniqueId()); + i.removeMember(u); + // Correct island cache + islandCache.setOwner(islandCache.getIslandById(owners.get(u).getUniqueId()), u); + // Save to database + handler.saveObjectAsync(i).thenRun(() -> user.sendMessage("commands.admin.team.fix.fixed")); + + }); + }); + user.sendMessage("commands.admin.team.fix.done"); + r.complete(true); + }); + + + return r; + } } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index d38b03ed9..42e0d658a 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -113,6 +113,16 @@ commands: use-disband-owner: "&c Not owner! Use disband [owner]." disbanded: "&c Admin disbanded your team!" success: "&b [name]&a 's team has been disbanded." + fix: + description: "scans and fixes cross island membership in database" + scanning: "Scanning database..." + duplicate-owner: "&c Player owns more than one island in database: [name]" + player-has: "&c Player [name] has [number] islands" + member-and-owner: "&c Cross-membership found: Player [name] is an island owner and member" + owner-of: "&c Owner of island at [xyz]" + member-of: "&c Member of island at [xyz]" + fixed: "&a Fixed" + done: "&a Scan completed" kick: parameters: "" description: "kick a player from a team"