Added generic cooldown methods to CompositeCommand

This API enables any command to have a cool down and to have that cool
down called by another command. For example, the Kick command sets the
cool down on the Invite command. It is possible for commands to set cool
downs on themselves too.
Currently, cool downs are not stored persistently in the database so
they disappear if the server is reloaded. This should be okay for now.
This commit is contained in:
tastybento 2018-08-04 18:45:13 -07:00
parent 2cc6d36997
commit 783caf985f
11 changed files with 84 additions and 115 deletions

View File

@ -96,8 +96,16 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
*/
private String topLabel = "";
/**
* Confirmation tracker
*/
private static Map<User, Confirmer> toBeConfirmed = new HashMap<>();
/**
* Cool down tracker
*/
private Map<UUID, Map<UUID, Long>> 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;
}
}

View File

@ -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

View File

@ -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));
}
}
}
}

View File

@ -261,4 +261,5 @@ public interface WorldSettings {
* @return max number of deaths for this world
*/
int getDeathsMax();
}

View File

@ -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<String, Integer> deaths = new HashMap<>();
@Expose
private Map<Location, Long> 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<Location, Long> getKickedList() {
return kickedList;
}
/**
* @param kickedList the kickedList to set
*/
public void setKickedList(Map<Location, Long> 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;

View File

@ -673,4 +673,5 @@ public class IslandWorldManager {
public int getDeathsMax(World world) {
return worldSettings.get(Util.getWorld(world)).getDeathsMax();
}
}

View File

@ -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

View File

@ -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));
}
}

View File

@ -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<CompositeCommand> 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);
}
}

View File

@ -121,7 +121,6 @@ public class MySQLDatabaseHandlerTest {
players.setHomeLocation(location, 2);
Map<Location, Long> map = new HashMap<>();
map.put(location, 324L);
players.setKickedList(map);
players.setLocale("sdfsd");
players.setPlayerName("name");
players.setPlayerUUID(UUID.randomUUID());

View File

@ -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);
}
}