Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
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.NonNull;
import org.eclipse.jdt.annotation.Nullable;
Expand All @@ -22,6 +27,7 @@
public class AdminTeamKickCommand extends CompositeCommand {

private @Nullable UUID targetUUID;
private @Nullable Island island;

public AdminTeamKickCommand(CompositeCommand parent) {
super(parent, "kick");
Expand All @@ -37,7 +43,7 @@ public void setup() {
@Override
public boolean canExecute(User user, String label, List<String> args) {
// If args are not right, show help
if (args.size() != 1) {
if (args.isEmpty() || args.size() > 2) {
showHelp(this, user);
return false;
}
Expand All @@ -53,34 +59,74 @@ public boolean canExecute(User user, String label, List<String> args) {
return false;
}

return true;
}

@Override
public boolean execute(User user, String label, @NonNull List<String> args) {
List<Island> islands = getIslands().getIslands(getWorld(), targetUUID);
Map<String, Island> islands = getMemberIslandsXYZ(targetUUID);
if (islands.isEmpty()) {
user.sendMessage("commands.admin.team.kick.not-in-team");
return false;
}
islands.forEach(island -> {
if (!user.getUniqueId().equals(island.getOwner())) {
assert targetUUID != null;
User target = User.getInstance(targetUUID);
target.sendMessage("commands.admin.team.kick.admin-kicked");

getIslands().removePlayer(island, targetUUID);
user.sendMessage("commands.admin.team.kick.success", TextVariables.NAME, target.getName(), "[owner]",
getPlayers().getName(island.getOwner()));
// Fire event so add-ons know
TeamEvent.builder().island(island).reason(TeamEvent.Reason.KICK).involvedPlayer(targetUUID).admin(true)
.build();
IslandEvent.builder().island(island).involvedPlayer(targetUUID).admin(true)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(island.getRank(target), RanksManager.VISITOR_RANK).build();
if (args.size() == 1) {
if (islands.size() == 1) {
island = islands.values().iterator().next();
} else {
// Multiple islands – require the player to specify which one
user.sendMessage("commands.admin.unregister.errors.player-has-more-than-one-island");
islands.keySet().forEach(coords ->
user.sendMessage("commands.admin.unregister.errors.specify-island-location",
TextVariables.XYZ, coords));
return false;
}
} else {
// args.size() == 2: xyz was supplied
if (!islands.containsKey(args.get(1))) {
user.sendMessage("commands.admin.unregister.errors.unknown-island-location");
return false;
}
});
user.sendMessage("commands.admin.team.kick.success-all");
island = islands.get(args.get(1));
}
return true;
}

/**
* Returns a map of x,y,z → island for all team islands in this world that the
* target player is a member of.
*/
private Map<String, Island> getMemberIslandsXYZ(UUID target) {
return getIslands().getIslands(getWorld(), target).stream()
.filter(Island::hasTeam)
.collect(Collectors.toMap(i -> Util.xyz(i.getCenter().toVector()), i -> i));
}

@Override
public boolean execute(User user, String label, @NonNull List<String> args) {
Objects.requireNonNull(island);
Objects.requireNonNull(targetUUID);
User target = User.getInstance(targetUUID);
target.sendMessage("commands.admin.team.kick.admin-kicked");
getIslands().removePlayer(island, targetUUID);
user.sendMessage("commands.admin.team.kick.success", TextVariables.NAME, target.getName(), "[owner]",
getPlayers().getName(island.getOwner()));
// Fire events so add-ons know
TeamEvent.builder().island(island).reason(TeamEvent.Reason.KICK).involvedPlayer(targetUUID).admin(true).build();
IslandEvent.builder().island(island).involvedPlayer(targetUUID).admin(true)
.reason(IslandEvent.Reason.RANK_CHANGE)
.rankChange(island.getRank(target), RanksManager.VISITOR_RANK).build();
return true;
}

@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
String lastArg = !args.isEmpty() ? args.getLast() : "";
if (args.isEmpty()) {
// Don't show every player on the server. Require at least the first letter
return Optional.empty();
} else if (args.size() == 2) {
// Completing the xyz arg: show the islands the target is a member of
UUID targetId = getPlayers().getUUID(args.getFirst());
if (targetId != null) {
return Optional.of(Util.tabLimit(new ArrayList<>(getMemberIslandsXYZ(targetId).keySet()), lastArg));
}
}
return Optional.empty();
}
}
3 changes: 1 addition & 2 deletions src/main/resources/locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,12 @@ commands:
fixed: '<green>Fixed</green>'
done: '<green>Scan</green>'
kick:
parameters: <team player>
parameters: <team player> [x,y,z]
description: kick a player from a team
cannot-kick-owner: '<red>You cannot kick the owner. Kick members first.</red>'
not-in-team: '<red>This player is not in a team.</red>'
admin-kicked: '<red>The admin kicked you from the team.</red>'
success: '<aqua>[name] </aqua><green>has been kicked from </green><aqua>[owner]</aqua><green>''s [prefix_island].</green>'
success-all: '<aqua>Player removed from all teams in this world</aqua>'
setowner:
parameters: <player>
description: transfers [prefix_island] ownership to the player
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
Expand All @@ -17,6 +16,7 @@
import java.util.Optional;
import java.util.UUID;

import org.bukkit.util.Vector;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -48,6 +48,8 @@ class AdminTeamKickCommandTest extends CommonTestSetup {
@Mock
private Island island2;

private static final String XYZ = "0,0,0";

@Override
@BeforeEach
public void setUp() throws Exception {
Expand All @@ -58,8 +60,9 @@ public void setUp() throws Exception {
CommandsManager cm = mock(CommandsManager.class);
when(plugin.getCommandsManager()).thenReturn(cm);

// Player
// Admin player (user)
when(user.isOp()).thenReturn(false);
when(user.isPlayer()).thenReturn(true);
uuid = UUID.randomUUID();
notUUID = UUID.randomUUID();
while (notUUID.equals(uuid)) {
Expand All @@ -74,19 +77,20 @@ public void setUp() throws Exception {
when(ac.getSubCommandAliases()).thenReturn(new HashMap<>());
when(ac.getWorld()).thenReturn(world);

// Island
when(island.getOwner()).thenReturn(uuid);
// island2 is owned by notUUID (the target) and has a team
when(island2.getOwner()).thenReturn(notUUID);
when(island2.hasTeam()).thenReturn(true);
when(island2.getCenter()).thenReturn(location);
when(location.toVector()).thenReturn(new Vector(0, 0, 0));

// Player has island to begin with
when(im.hasIsland(any(), any(UUID.class))).thenReturn(true);
when(im.hasIsland(any(), any(User.class))).thenReturn(true);
when(im.getIslands(world, uuid)).thenReturn(List.of(island, island2));

// Has team
when(im.inTeam(any(), eq(uuid))).thenReturn(true);
// Target (notUUID) is in a team and is a member of island2
when(im.inTeam(any(), eq(notUUID))).thenReturn(true);
// By default, target is on island2 only
when(im.getIslands(world, notUUID)).thenReturn(List.of(island2));

when(plugin.getPlayers()).thenReturn(pm);
when(pm.getUUID(any())).thenReturn(notUUID);
when(pm.getName(any())).thenReturn("target");

// Locales
LocalesManager lm = mock(LocalesManager.class);
Expand All @@ -95,7 +99,6 @@ public void setUp() throws Exception {

// Addon
when(iwm.getAddon(any())).thenReturn(Optional.empty());

}

@Override
Expand All @@ -114,6 +117,16 @@ void testCanExecuteNoTarget() {
// Show help
}

/**
* Test method for {@link AdminTeamKickCommand#canExecute(User, String, List)}.
*/
@Test
void testCanExecuteTooManyArgs() {
AdminTeamKickCommand itl = new AdminTeamKickCommand(ac);
assertFalse(itl.canExecute(user, itl.getLabel(), List.of("a", "b", "c")));
// Show help
}

/**
* Test method for {@link AdminTeamKickCommand#canExecute(User, String, List)}.
*/
Expand All @@ -132,28 +145,118 @@ void testCanExecuteUnknownPlayer() {
void testCanExecutePlayerNotInTeam() {
AdminTeamKickCommand itl = new AdminTeamKickCommand(ac);
when(pm.getUUID(any())).thenReturn(notUUID);
assertFalse(itl.canExecute(user, itl.getLabel(), Collections.singletonList("tastybento")));
when(im.inTeam(any(), eq(notUUID))).thenReturn(false);
assertFalse(itl.canExecute(user, itl.getLabel(), Collections.singletonList("target")));
verify(user).sendMessage("commands.admin.team.kick.not-in-team");
}

/**
* Test method for {@link world.bentobox.bentobox.api.commands.admin.team.AdminTeamKickCommand#execute(User, String, List)}.
* Test that a player in a team on a single island can be kicked with 1 arg.
*/
@Test
void testCanExecuteSuccess() {
AdminTeamKickCommand itl = new AdminTeamKickCommand(ac);
assertTrue(itl.canExecute(user, itl.getLabel(), Collections.singletonList("target")));
}

/**
* Test that a player on multiple islands requires an xyz arg.
*/
@Test
void testCanExecuteMultipleIslandsRequiresXyz() {
// Set up island (admin-owned) with a different center location so it gets a unique xyz key
when(island.getOwner()).thenReturn(uuid);
when(island.hasTeam()).thenReturn(true);
org.bukkit.Location loc2 = mock(org.bukkit.Location.class);
when(loc2.toVector()).thenReturn(new Vector(100, 64, 100));
when(island.getCenter()).thenReturn(loc2);
when(im.getIslands(world, notUUID)).thenReturn(List.of(island, island2));
AdminTeamKickCommand itl = new AdminTeamKickCommand(ac);
assertFalse(itl.canExecute(user, itl.getLabel(), Collections.singletonList("target")));
verify(user).sendMessage("commands.admin.unregister.errors.player-has-more-than-one-island");
}

/**
* Test that an unknown xyz arg gives an error.
*/
@Test
void testCanExecuteUnknownIslandLocation() {
AdminTeamKickCommand itl = new AdminTeamKickCommand(ac);
assertFalse(itl.canExecute(user, itl.getLabel(), List.of("target", "9,9,9")));
verify(user).sendMessage("commands.admin.unregister.errors.unknown-island-location");
}

/**
* Test that a valid xyz arg selects the correct island.
*/
@Test
void testExecute() {
when(im.inTeam(any(), any())).thenReturn(true);
String name = "tastybento";
when(pm.getUUID(any())).thenReturn(uuid);
when(pm.getName(any())).thenReturn(name);
void testCanExecuteWithValidXyz() {
AdminTeamKickCommand itl = new AdminTeamKickCommand(ac);
assertTrue(itl.canExecute(user, itl.getLabel(), List.of("target", XYZ)));
}

/**
* Test method for {@link AdminTeamKickCommand#execute(User, String, List)}.
* Target on one island; kicked with 1 arg.
*/
@Test
void testExecuteSingleIsland() {
String name = "target";
AdminTeamKickCommand itl = new AdminTeamKickCommand(ac);
assertTrue(itl.canExecute(user, itl.getLabel(), Collections.singletonList(name)));
assertTrue(itl.execute(user, itl.getLabel(), Collections.singletonList(name)));
verify(im, never()).removePlayer(island, uuid);
verify(im).removePlayer(island2, uuid);
verify(user).sendMessage(eq("commands.admin.team.kick.success"), eq(TextVariables.NAME), any(), eq("[owner]"), eq(name));
// Offline so event will be called 3 times
verify(im).removePlayer(island2, notUUID);
verify(user).sendMessage(eq("commands.admin.team.kick.success"), eq(TextVariables.NAME), any(), eq("[owner]"),
any());
// 3 events: TeamEvent + IslandEvent (IslandEvent.build fires 2 callEvent calls)
verify(pim, times(3)).callEvent(any());
}

/**
* Test method for {@link AdminTeamKickCommand#execute(User, String, List)}.
* Target on multiple islands; kicked with explicit xyz.
*/
@Test
void testExecuteWithXyz() {
AdminTeamKickCommand itl = new AdminTeamKickCommand(ac);
assertTrue(itl.canExecute(user, itl.getLabel(), List.of("target", XYZ)));
assertTrue(itl.execute(user, itl.getLabel(), List.of("target", XYZ)));
verify(im).removePlayer(island2, notUUID);
verify(user).sendMessage(eq("commands.admin.team.kick.success"), eq(TextVariables.NAME), any(), eq("[owner]"),
any());
verify(pim, times(3)).callEvent(any());
}

/**
* Test tab complete with no args returns empty.
*/
@Test
void testTabCompleteNoArgs() {
AdminTeamKickCommand itl = new AdminTeamKickCommand(ac);
Optional<List<String>> result = itl.tabComplete(user, "", List.of(""));
assertTrue(result.isEmpty());
}

/**
* Test tab complete for second arg returns xyz of the target's team islands.
*/
@Test
void testTabCompleteSecondArg() {
when(pm.getUUID("target")).thenReturn(notUUID);
AdminTeamKickCommand itl = new AdminTeamKickCommand(ac);
Optional<List<String>> result = itl.tabComplete(user, "", List.of("target", ""));
assertTrue(result.isPresent());
assertTrue(result.get().contains(XYZ));
}

/**
* Test tab complete for second arg returns empty when player is unknown.
*/
@Test
void testTabCompleteSecondArgUnknownPlayer() {
when(pm.getUUID("unknown")).thenReturn(null);
AdminTeamKickCommand itl = new AdminTeamKickCommand(ac);
Optional<List<String>> result = itl.tabComplete(user, "", List.of("unknown", ""));
assertTrue(result.isEmpty());
}
}
Loading