diff --git a/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java index 84267fe7a..f58ab764a 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java @@ -96,8 +96,16 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi */ private String topLabel = ""; + /** + * Confirmation tracker + */ private static Map toBeConfirmed = new HashMap<>(); + /** + * Cool down tracker + */ + private Map> cooldowns = new HashMap<>(); + /** * Top level command * @param addon - addon creating the command @@ -658,4 +666,36 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi } } + + /** + * Set a cool down - can be set by other commands on this one + * @param uniqueId - the caller + * @param targetUUID - the target (if any) + * @param timeInSeconds - time in seconds to cool down + */ + public void setCooldown(UUID uniqueId, UUID targetUUID, int timeInSeconds) { + cooldowns.putIfAbsent(uniqueId, new HashMap<>()); + cooldowns.get(uniqueId).put(targetUUID, System.currentTimeMillis() + timeInSeconds * 1000); + } + + /** + * Check if cool down has expired or not + * @param user - the caller of the command + * @param targetUUID - the target (if any) + * @return true if cool down does not exist, false if it is still active + */ + protected boolean checkCooldown(User user, UUID targetUUID) { + if (!cooldowns.containsKey(user.getUniqueId()) || user.isOp() || user.hasPermission(getPermissionPrefix() + ".mod.bypasscooldowns")) { + return true; + } + cooldowns.putIfAbsent(user.getUniqueId(), new HashMap<>()); + if (cooldowns.get(user.getUniqueId()).getOrDefault(targetUUID, 0L) - System.currentTimeMillis() <= 0) { + // Cool down is done + cooldowns.get(user.getUniqueId()).remove(targetUUID); + return true; + } + int timeToGo = (int) ((cooldowns.get(user.getUniqueId()).getOrDefault(targetUUID, 0L) - System.currentTimeMillis()) / 1000); + user.sendMessage("general.errors.you-must-wait", TextVariables.NUMBER, String.valueOf(timeToGo)); + return false; + } } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java index f7ad2a3d6..cf059f4ac 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommand.java @@ -74,11 +74,8 @@ public class IslandTeamInviteCommand extends CompositeCommand { user.sendMessage("commands.island.team.invite.errors.cannot-invite-self"); return false; } - // Check if this player can be invited to this island, or - // whether they are still on cooldown - long time = getPlayers().getInviteCoolDownTime(invitedPlayerUUID, getIslands().getIslandLocation(getWorld(), playerUUID)); - if (time > 0 && !user.isOp()) { - user.sendMessage("commands.island.team.invite.errors.cooldown", TextVariables.NUMBER, String.valueOf(time)); + // Check cool down + if (getSettings().getInviteWait() > 0 && !checkCooldown(user, invitedPlayerUUID)) { return false; } // Player cannot invite someone already on a team diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java index 687733e00..cbb462087 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommand.java @@ -53,7 +53,7 @@ public class IslandTeamKickCommand extends CompositeCommand { kick(user, targetUUID); return true; } else { - this.askConfirmation(user, () -> kick(user, targetUUID)); + askConfirmation(user, () -> kick(user, targetUUID)); return false; } } @@ -72,7 +72,15 @@ public class IslandTeamKickCommand extends CompositeCommand { // TODO: needs Vault } user.sendMessage("general.success"); + + // Add cooldown for this player and target + if (getSettings().getInviteWait() > 0) { + // Get the invite class from the parent + if (getParent() != null) { + getParent().getSubCommand("invite").ifPresent(c -> c.setCooldown(user.getUniqueId(), targetUUID, getSettings().getInviteWait() * 60)); + } + + } } - } \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/api/configuration/WorldSettings.java b/src/main/java/world/bentobox/bentobox/api/configuration/WorldSettings.java index de23f3faf..dd1a36a3e 100644 --- a/src/main/java/world/bentobox/bentobox/api/configuration/WorldSettings.java +++ b/src/main/java/world/bentobox/bentobox/api/configuration/WorldSettings.java @@ -261,4 +261,5 @@ public interface WorldSettings { * @return max number of deaths for this world */ int getDeathsMax(); + } diff --git a/src/main/java/world/bentobox/bentobox/database/objects/Players.java b/src/main/java/world/bentobox/bentobox/database/objects/Players.java index 8c69e68e5..d8cd8f59e 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/Players.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/Players.java @@ -1,7 +1,5 @@ package world.bentobox.bentobox.database.objects; -import java.util.Calendar; -import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -35,8 +33,6 @@ public class Players implements DataObject { private String locale = ""; @Expose private Map deaths = new HashMap<>(); - @Expose - private Map kickedList = new HashMap<>(); /** * This is required for database storage @@ -53,7 +49,6 @@ public class Players implements DataObject { this.uniqueId = uniqueId.toString(); homeLocations = new HashMap<>(); locale = ""; - kickedList = new HashMap<>(); // Try to get player's name this.playerName = Bukkit.getOfflinePlayer(uniqueId).getName(); if (this.playerName == null) { @@ -100,20 +95,6 @@ public class Players implements DataObject { return homeLocations; } - /** - * @return the kickedList - */ - public Map getKickedList() { - return kickedList; - } - - /** - * @param kickedList the kickedList to set - */ - public void setKickedList(Map kickedList) { - this.kickedList = kickedList; - } - /** * @param homeLocations the homeLocations to set */ @@ -248,47 +229,6 @@ public class Players implements DataObject { } } - /** - * Can invite or still waiting for cool down to end - * - * @param location - the location - * to check - * @return number of mins/hours left until cool down ends - */ - public long getInviteCoolDownTime(Location location) { - // Check the hashmap - if (location != null && kickedList.containsKey(location)) { - // The location is in the list - // Check the date/time - Date kickedDate = new Date(kickedList.get(location)); - Calendar coolDownTime = Calendar.getInstance(); - coolDownTime.setTime(kickedDate); - coolDownTime.add(Calendar.MINUTE, getPlugin().getSettings().getInviteWait()); - // Add the invite cooldown period - Calendar timeNow = Calendar.getInstance(); - if (coolDownTime.before(timeNow)) { - // The time has expired - kickedList.remove(location); - return 0; - } else { - // Still not there yet - // Time in minutes - return (long) Math.ceil((coolDownTime.getTimeInMillis() - timeNow.getTimeInMillis()) / (1000 * 60D)); - } - } - return 0; - } - - /** - * Starts the invite cooldown timer for location. Location should be the center of an island. - * @param location - the location - */ - public void startInviteCoolDownTimer(Location location) { - if (location != null) { - kickedList.put(location, System.currentTimeMillis()); - } - } - @Override public String getUniqueId() { return uniqueId; diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandWorldManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandWorldManager.java index e3689a4f9..ec1afccb9 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandWorldManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandWorldManager.java @@ -673,4 +673,5 @@ public class IslandWorldManager { public int getDeathsMax(World world) { return worldSettings.get(Util.getWorld(world)).getDeathsMax(); } + } diff --git a/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java b/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java index f5d18149c..a1864b371 100644 --- a/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java @@ -315,32 +315,6 @@ public class PlayersManager { playerCache.get(playerUUID).setResets(world, resets); } - /** - * Returns how long the player must wait before they can be invited to an - * island with the location - * - * @param playerUUID - the player's UUID - * @param location - the location - * @return time to wait in minutes/hours - */ - public long getInviteCoolDownTime(UUID playerUUID, Location location) { - addPlayer(playerUUID); - return playerCache.get(playerUUID).getInviteCoolDownTime(location); - } - - /** - * Starts the timer for the player for this location before which they can - * be invited - * Called when they are kicked from an island or leave. - * - * @param playerUUID - the player's UUID - * @param location - the location - */ - public void startInviteCoolDownTimer(UUID playerUUID, Location location) { - addPlayer(playerUUID); - playerCache.get(playerUUID).startInviteCoolDownTimer(location); - } - /** * Returns the locale for this player. If missing, will return nothing * @param playerUUID - the player's UUID diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java index 815e36a2b..7d26f20d6 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamInviteCommandTest.java @@ -27,7 +27,6 @@ import org.powermock.reflect.Whitebox; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.Settings; import world.bentobox.bentobox.api.commands.CompositeCommand; -import world.bentobox.bentobox.api.commands.island.team.IslandTeamInviteCommand; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.managers.CommandsManager; import world.bentobox.bentobox.managers.IslandWorldManager; @@ -49,6 +48,7 @@ public class IslandTeamInviteCommandTest { private IslandsManager im; private PlayersManager pm; private UUID notUUID; + private Settings s; /** * @throws java.lang.Exception @@ -64,7 +64,7 @@ public class IslandTeamInviteCommandTest { when(plugin.getCommandsManager()).thenReturn(cm); // Settings - Settings s = mock(Settings.class); + s = mock(Settings.class); when(s.getResetWait()).thenReturn(0L); when(plugin.getSettings()).thenReturn(s); @@ -211,4 +211,16 @@ public class IslandTeamInviteCommandTest { Mockito.verify(user).sendMessage(Mockito.eq("commands.island.team.invite.errors.already-on-team")); } + /** + * Test method for {@link IslandTeamInviteCommand#execute(world.bentobox.bentobox.api.user.User, java.util.List)}. + */ + @Test + public void testExecuteCoolDownActive() { + // 10 minutes = 600 seconds + when(s.getInviteWait()).thenReturn(10); + IslandTeamInviteCommand itl = new IslandTeamInviteCommand(ic); + String[] name = {"tastybento"}; + itl.execute(user, itl.getLabel(), Arrays.asList(name)); + } + } diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommandTest.java index d39bf7a2b..6a36b274c 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/island/team/IslandTeamKickCommandTest.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -32,7 +33,6 @@ import org.powermock.reflect.Whitebox; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.Settings; import world.bentobox.bentobox.api.commands.CompositeCommand; -import world.bentobox.bentobox.api.commands.island.team.IslandTeamKickCommand; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.managers.CommandsManager; import world.bentobox.bentobox.managers.IslandWorldManager; @@ -57,6 +57,7 @@ public class IslandTeamKickCommandTest { private UUID notUUID; private IslandWorldManager iwm; private Player player; + private CompositeCommand subCommand; /** * @throws java.lang.Exception @@ -95,6 +96,9 @@ public class IslandTeamKickCommandTest { // Parent command has no aliases ic = mock(CompositeCommand.class); when(ic.getSubCommandAliases()).thenReturn(new HashMap<>()); + subCommand = mock(CompositeCommand.class); + Optional optionalCommand = Optional.of(subCommand); + when(ic.getSubCommand(Mockito.anyString())).thenReturn(optionalCommand); // Player has island to begin with im = mock(IslandsManager.class); @@ -266,4 +270,15 @@ public class IslandTeamKickCommandTest { Mockito.verify(enderChest).clear(); Mockito.verify(inv).clear(); } + + /** + * Test method for {@link IslandTeamKickCommand#execute(world.bentobox.bentobox.api.user.User, java.util.List)}. + */ + @Test + public void testCooldown() { + // 10 minutes = 600 seconds + when(s.getInviteWait()).thenReturn(10); + testExecuteNoConfirmation(); + Mockito.verify(subCommand).setCooldown(uuid, notUUID, 600); + } } diff --git a/src/test/java/world/bentobox/bentobox/database/mysql/MySQLDatabaseHandlerTest.java b/src/test/java/world/bentobox/bentobox/database/mysql/MySQLDatabaseHandlerTest.java index 85edee29f..0450305bf 100644 --- a/src/test/java/world/bentobox/bentobox/database/mysql/MySQLDatabaseHandlerTest.java +++ b/src/test/java/world/bentobox/bentobox/database/mysql/MySQLDatabaseHandlerTest.java @@ -121,7 +121,6 @@ public class MySQLDatabaseHandlerTest { players.setHomeLocation(location, 2); Map map = new HashMap<>(); map.put(location, 324L); - players.setKickedList(map); players.setLocale("sdfsd"); players.setPlayerName("name"); players.setPlayerUUID(UUID.randomUUID()); diff --git a/src/test/java/world/bentobox/bentobox/database/objects/PlayersTest.java b/src/test/java/world/bentobox/bentobox/database/objects/PlayersTest.java index e19d007d4..64da750e1 100644 --- a/src/test/java/world/bentobox/bentobox/database/objects/PlayersTest.java +++ b/src/test/java/world/bentobox/bentobox/database/objects/PlayersTest.java @@ -24,7 +24,6 @@ import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.reflect.Whitebox; import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.Settings; import world.bentobox.bentobox.managers.IslandWorldManager; @RunWith(PowerMockRunner.class) @@ -100,21 +99,4 @@ public class PlayersTest { assertTrue(p.getDeaths(world) == 0); } - @Test - public void testInviteCoolDownTime() throws InterruptedException { - Settings settings = mock(Settings.class); - when(settings.getInviteWait()).thenReturn(1); - when(plugin.getSettings()).thenReturn(settings); - Players p = new Players(plugin, UUID.randomUUID()); - // Check a null location - assertTrue(p.getInviteCoolDownTime(null) == 0); - // Real location - Location l = mock(Location.class); - // Should be no cooldown - assertTrue(p.getInviteCoolDownTime(l) == 0); - // Start the timer - p.startInviteCoolDownTimer(l); - // More than 0 cooldown - assertTrue(p.getInviteCoolDownTime(l) > 0); - } }