From f11a56cc6b8227c8f4aac0d58470f40eeeccd02b Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 24 Feb 2024 14:47:22 -0800 Subject: [PATCH] Better enable disbanding of teams via admin command --- .../commands/admin/team/AdminTeamCommand.java | 2 +- .../admin/team/AdminTeamDisbandCommand.java | 81 ++++++++-- .../bentobox/managers/IslandsManager.java | 2 + .../team/AdminTeamDisbandCommandTest.java | 148 +++++++++++++----- 4 files changed, 177 insertions(+), 56 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamCommand.java index b66989b8b..f882be359 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamCommand.java @@ -32,7 +32,7 @@ public class AdminTeamCommand extends CompositeCommand new AdminTeamAddCommand(this); new AdminTeamDisbandCommand(this); - new AdminTeamFixCommand(this); + new AdminTeamKickCommand(this); new AdminTeamSetownerCommand(this); } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamDisbandCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamDisbandCommand.java index bb5ac14af..d47de209d 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamDisbandCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamDisbandCommand.java @@ -1,7 +1,14 @@ package world.bentobox.bentobox.api.commands.admin.team; +import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.UUID; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.Nullable; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.events.island.IslandEvent; @@ -14,6 +21,13 @@ import world.bentobox.bentobox.util.Util; public class AdminTeamDisbandCommand extends CompositeCommand { + private Island island; + private @Nullable UUID targetUUID; + + /** + * Disbands a team + * @param parent parent command + */ public AdminTeamDisbandCommand(CompositeCommand parent) { super(parent, "disband"); } @@ -26,32 +40,55 @@ public class AdminTeamDisbandCommand extends CompositeCommand { } @Override - public boolean execute(User user, String label, List args) { + public boolean canExecute(User user, String label, List args) { // If args are not right, show help - if (args.size() != 1) { + if (args.isEmpty() || args.size() > 2) { showHelp(this, user); return false; } // Get target - UUID targetUUID = Util.getUUID(args.get(0)); + targetUUID = Util.getUUID(args.get(0)); if (targetUUID == null) { user.sendMessage("general.errors.unknown-player", TextVariables.NAME, args.get(0)); return false; } - if (!getIslands().hasIsland(getWorld(), targetUUID)) { - user.sendMessage("general.errors.no-island"); - return false; - } if (!getIslands().inTeam(getWorld(), targetUUID)) { - user.sendMessage("general.errors.not-in-team"); + user.sendMessage("general.errors.player-is-not-owner", TextVariables.NAME, args.get(0)); return false; } - Island island = getIslands().getPrimaryIsland(getWorld(), targetUUID); - if (!targetUUID.equals(island.getOwner())) { - user.sendMessage("commands.admin.team.disband.use-disband-owner", "[owner]", - getPlayers().getName(island.getOwner())); + // Find the island the player is an owner of + Map islands = getIslandsXYZ(targetUUID); + if (islands.isEmpty()) { + user.sendMessage("general.errors.player-has-no-island"); return false; } + + if (islands.size() > 1) { + if (args.size() != 2 || !islands.containsKey(args.get(1))) { + user.sendMessage("commands.admin.team.disband.more-than-one-island", TextVariables.NAME, + getPlayers().getName(island.getOwner())); + islands.keySet().forEach(coords -> user.sendMessage("commands.admin.team.disband.more-than-one-island", + TextVariables.XYZ, coords)); + return false; + } + // Get the named island + island = islands.get(args.get(1)); + } else { + // Get the only island + island = islands.values().iterator().next(); + } + return true; + } + + private Map getIslandsXYZ(UUID target) { + return getIslands().getOwnedIslands(getWorld(), target).stream().filter(is -> is.getMemberSet().size() > 1) // Filter for teams + .collect(Collectors.toMap(island -> Util.xyz(island.getCenter().toVector()), island -> island)); + } + + @Override + public boolean execute(User user, String label, List args) { + Objects.requireNonNull(island); + Objects.requireNonNull(targetUUID); // Disband team island.getMemberSet().forEach(m -> { User mUser = User.getInstance(m); @@ -68,4 +105,24 @@ public class AdminTeamDisbandCommand extends CompositeCommand { user.sendMessage("commands.admin.team.disband.success", TextVariables.NAME, args.get(0)); return true; } + + @Override + public Optional> tabComplete(User user, String alias, List args) { + String lastArg = !args.isEmpty() ? args.get(args.size() - 1) : ""; + if (args.isEmpty()) { + // Don't show every player on the server. Require at least the first letter + return Optional.empty(); + } else if (args.size() == 3) { + List options = new ArrayList<>(Util.getOnlinePlayerList(user)); + return Optional.of(Util.tabLimit(options, lastArg)); + } else if (args.size() > 3) { + // Find out which user + UUID uuid = getPlayers().getUUID(args.get(1)); + if (uuid != null) { + return Optional.of(Util.tabLimit(new ArrayList<>(getIslandsXYZ(uuid).keySet()), lastArg)); + } + } + return Optional.empty(); + } + } diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index 2b659dc6e..347aa3e9c 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -1919,7 +1919,9 @@ public class IslandsManager { * @param user - admin calling * @param world - game world to check * @return CompletableFuture boolean - true when done + * @deprecated Not compatible with multi-islands. Will be removed. */ + @Deprecated public CompletableFuture checkTeams(User user, World world) { CompletableFuture r = new CompletableFuture<>(); user.sendMessage("commands.admin.team.fix.scanning"); diff --git a/src/test/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamDisbandCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamDisbandCommandTest.java index dd3f59f0b..38307ca88 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamDisbandCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/admin/team/AdminTeamDisbandCommandTest.java @@ -3,6 +3,7 @@ package world.bentobox.bentobox.api.commands.admin.team; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.framework; import static org.mockito.Mockito.mock; @@ -16,19 +17,23 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.UUID; import org.bukkit.Bukkit; -import org.bukkit.World; +import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.util.Vector; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.stubbing.Answer; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; @@ -55,7 +60,7 @@ import world.bentobox.bentobox.util.Util; * */ @RunWith(PowerMockRunner.class) -@PrepareForTest({ Bukkit.class, BentoBox.class, User.class }) +@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, Util.class }) public class AdminTeamDisbandCommandTest { @Mock @@ -76,6 +81,9 @@ public class AdminTeamDisbandCommandTest { private UUID notUUID; @Mock private @Nullable Island island; + @Mock + private @NonNull Location location; + private AdminTeamDisbandCommand itl; /** */ @@ -121,8 +129,10 @@ public class AdminTeamDisbandCommandTest { when(im.hasIsland(any(), any(UUID.class))).thenReturn(true); when(im.hasIsland(any(), any(User.class))).thenReturn(true); when(island.getOwner()).thenReturn(uuid); - when(im.getIsland(any(World.class), any(UUID.class))).thenReturn(island); - when(im.getPrimaryIsland(any(), any())).thenReturn(island); + when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid, notUUID)); + when(island.getCenter()).thenReturn(location); + when(location.toVector()).thenReturn(new Vector(1, 2, 3)); + when(im.getOwnedIslands(any(), eq(uuid))).thenReturn(Set.of(island)); when(plugin.getIslands()).thenReturn(im); // Has team @@ -152,6 +162,15 @@ public class AdminTeamDisbandCommandTest { // Plugin Manager when(Bukkit.getPluginManager()).thenReturn(pim); + + // Online players + PowerMockito.mockStatic(Util.class, Mockito.RETURNS_MOCKS); + when(Util.getOnlinePlayerList(user)).thenReturn(List.of("tastybento", "BONNe")); + when(Util.translateColorCodes(anyString())) + .thenAnswer((Answer) invocation -> invocation.getArgument(0, String.class)); + + // DUT + itl = new AdminTeamDisbandCommand(ac); } @After @@ -161,56 +180,59 @@ public class AdminTeamDisbandCommandTest { } /** - * Test method for {@link AdminTeamDisbandCommand#execute(User, String, List)}. + * Test method for {@link AdminTeamDisbandCommand#canExecute(User, String, List)}. */ @Test public void testExecuteNoTarget() { - AdminTeamDisbandCommand itl = new AdminTeamDisbandCommand(ac); - assertFalse(itl.execute(user, itl.getLabel(), new ArrayList<>())); + assertFalse(itl.canExecute(user, itl.getLabel(), new ArrayList<>())); } /** - * Test method for {@link AdminTeamDisbandCommand#execute(User, String, List)}. + * Test method for {@link AdminTeamDisbandCommand#canExecute(User, String, List)}. */ @Test public void testExecuteUnknownPlayer() { - AdminTeamDisbandCommand itl = new AdminTeamDisbandCommand(ac); String[] name = { "tastybento" }; when(pm.getUUID(any())).thenReturn(null); - assertFalse(itl.execute(user, itl.getLabel(), Arrays.asList(name))); + assertFalse(itl.canExecute(user, itl.getLabel(), Arrays.asList(name))); verify(user).sendMessage("general.errors.unknown-player", "[name]", name[0]); } /** - * Test method for {@link AdminTeamDisbandCommand#execute(User, String, List)}. + * Test method for {@link AdminTeamDisbandCommand#canExecute(User, String, List)}. */ @Test public void testExecutePlayerNotInTeam() { - AdminTeamDisbandCommand itl = new AdminTeamDisbandCommand(ac); - String[] name = { "tastybento" }; - when(pm.getUUID(any())).thenReturn(notUUID); - // when(im.getMembers(any(), any())).thenReturn(new HashSet<>()); - assertFalse(itl.execute(user, itl.getLabel(), Arrays.asList(name))); - verify(user).sendMessage(eq("general.errors.not-in-team")); + when(Util.getUUID("tastybento")).thenReturn(notUUID); + assertFalse(itl.canExecute(user, itl.getLabel(), List.of("tastybento"))); + verify(user).sendMessage("general.errors.player-is-not-owner", "[name]", "tastybento"); } /** - * Test method for {@link AdminTeamDisbandCommand#execute(User, String, List)}. + * Test method for {@link AdminTeamDisbandCommand#canExecute(User, String, List)}. */ @Test - public void testExecuteDisbandNotOwner() { + public void testExecuteDisbandNoIsland() { when(im.inTeam(any(), any())).thenReturn(true); - Island is = mock(Island.class); - when(im.getIsland(any(), any(UUID.class))).thenReturn(is); - String[] name = {"tastybento"}; - when(pm.getUUID(any())).thenReturn(notUUID); + when(Util.getUUID("tastybento")).thenReturn(uuid); + when(im.getOwnedIslands(any(), eq(uuid))).thenReturn(Set.of()); + assertFalse(itl.canExecute(user, itl.getLabel(), Arrays.asList("tastybento"))); + verify(user).sendMessage("general.errors.player-has-no-island"); + } - //when(im.getOwner(any(), eq(notUUID))).thenReturn(uuid); - when(pm.getName(any())).thenReturn("owner"); + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.team.AdminTeamDisbandCommand#canExecute(User, String, List)}. + */ + @Test + public void testCanExecuteSuccess() { + when(im.inTeam(any(), any())).thenReturn(true); + when(Util.getUUID("tastybento")).thenReturn(uuid); + when(pm.getName(uuid)).thenReturn("tastybento"); + // Members + when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid, notUUID)); - AdminTeamDisbandCommand itl = new AdminTeamDisbandCommand(ac); - assertFalse(itl.execute(user, itl.getLabel(), Arrays.asList(name))); - verify(user).sendMessage("commands.admin.team.disband.use-disband-owner", "[owner]", "owner"); + + assertTrue(itl.canExecute(user, itl.getLabel(), List.of("tastybento"))); } /** @@ -218,24 +240,64 @@ public class AdminTeamDisbandCommandTest { */ @Test public void testExecuteSuccess() { - when(im.inTeam(any(), any())).thenReturn(true); - when(im.getIsland(any(), any(UUID.class))).thenReturn(island); - String[] name = {"tastybento"}; - when(pm.getUUID(any())).thenReturn(notUUID); - when(pm.getName(any())).thenReturn(name[0]); - // Owner - when(island.getOwner()).thenReturn(notUUID); - // Members - when(island.getMemberSet()).thenReturn(ImmutableSet.of(uuid, notUUID)); - - AdminTeamDisbandCommand itl = new AdminTeamDisbandCommand(ac); - assertTrue(itl.execute(user, itl.getLabel(), Arrays.asList(name))); - verify(im, never()).removePlayer(island, notUUID); - verify(im).removePlayer(island, uuid); - verify(user).sendMessage("commands.admin.team.disband.success", TextVariables.NAME, name[0]); + this.testCanExecuteSuccess(); + assertTrue(itl.execute(user, itl.getLabel(), List.of("tastybento"))); + verify(im, never()).removePlayer(island, uuid); + verify(im).removePlayer(island, notUUID); + verify(user).sendMessage("commands.admin.team.disband.success", TextVariables.NAME, "tastybento"); verify(p).sendMessage("commands.admin.team.disband.disbanded"); verify(p2).sendMessage("commands.admin.team.disband.disbanded"); // 2 + 1 verify(pim, times(3)).callEvent(any()); } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.team.AdminTeamDisbandCommand#tabComplete(User, String, List)} + */ + @Test + public void testTabCompleteNoArgs() { + AdminTeamDisbandCommand itl = new AdminTeamDisbandCommand(ac); + Optional> list = itl.tabComplete(user, "", List.of("")); + assertTrue(list.isEmpty()); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.team.AdminTeamDisbandCommand#tabComplete(User, String, List)} + */ + @Test + public void testTabCompleteOneArg() { + when(Util.getUUID("tastybento")).thenReturn(uuid); + when(pm.getName(uuid)).thenReturn("tastybento"); + + AdminTeamDisbandCommand itl = new AdminTeamDisbandCommand(ac); + Optional> list = itl.tabComplete(user, "", List.of("tasty")); + assertTrue(list.isEmpty()); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.team.AdminTeamDisbandCommand#tabComplete(User, String, List)} + */ + @Test + public void testTabCompleteTwoArgs() { + when(Util.getUUID("tastybento")).thenReturn(uuid); + when(pm.getName(uuid)).thenReturn("tastybento"); + + AdminTeamDisbandCommand itl = new AdminTeamDisbandCommand(ac); + Optional> list = itl.tabComplete(user, "", List.of("tastybento", "1")); + assertTrue(list.isEmpty()); + } + + /** + * Test method for {@link world.bentobox.bentobox.api.commands.admin.team.AdminTeamDisbandCommand#tabComplete(User, String, List)} + */ + @Test + public void testTabCompleteThreeArgs() { + when(Util.getUUID("tastybento")).thenReturn(uuid); + when(pm.getName(uuid)).thenReturn("tastybento"); + + AdminTeamDisbandCommand itl = new AdminTeamDisbandCommand(ac); + Optional> list = itl.tabComplete(user, "", List.of("tastybento", "1,2,3", "ddd")); + assertFalse(list.isEmpty()); + } + }