diff --git a/Essentials/src/main/java/com/earth2me/essentials/ISettings.java b/Essentials/src/main/java/com/earth2me/essentials/ISettings.java index c4f409114..405fe5dc0 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/ISettings.java +++ b/Essentials/src/main/java/com/earth2me/essentials/ISettings.java @@ -238,6 +238,8 @@ public interface ISettings extends IConf { long getTpaAcceptCancellation(); + int getTpaMaxRequests(); + long getTeleportInvulnerability(); boolean isTeleportInvulnerability(); diff --git a/Essentials/src/main/java/com/earth2me/essentials/IUser.java b/Essentials/src/main/java/com/earth2me/essentials/IUser.java index c78e18407..797df13e4 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/IUser.java +++ b/Essentials/src/main/java/com/earth2me/essentials/IUser.java @@ -12,13 +12,16 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Player; +import org.checkerframework.checker.nullness.qual.Nullable; import java.math.BigDecimal; + import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.regex.Pattern; /** @@ -61,8 +64,11 @@ public interface IUser { /** * Returns whether this user has an outstanding teleport request to deal with. * + * @deprecated The teleport request system has been moved into a multi-user teleport request queue. + * @see IUser#hasPendingTpaRequests(boolean, boolean) * @return whether there is a teleport request */ + @Deprecated boolean hasOutstandingTeleportRequest(); /** @@ -98,6 +104,11 @@ public interface IUser { boolean canBuild(); + /** + * @deprecated The teleport request system has been moved into a multi-user teleport request queue. + * @see IUser#getNextTpaRequest(boolean, boolean, boolean) + */ + @Deprecated long getTeleportRequestTime(); void enableInvulnerabilityAfterTeleport(); @@ -205,6 +216,8 @@ public interface IUser { String getName(); + UUID getUUID(); + String getDisplayName(); String getFormattedNickname(); @@ -238,4 +251,74 @@ public interface IUser { void setToggleShout(boolean toggleShout); boolean isToggleShout(); + + /** + * Gets information about the most-recently-made, non-expired TPA request in the tpa queue of this {@link IUser}. + *

+ * The TPA Queue is Last-In-First-Out queue which stores all the active pending teleport + * requests of this {@link IUser}. Timeout calculations are also done during the + * iteration process of this method, ensuring that teleport requests made past the timeout + * period are removed from queue and therefore not returned here. The maximum size of this + * queue is determined by {@link ISettings#getTpaMaxRequests()}. + * + * @param inform true if the underlying {@link IUser} should be informed if a request expires during iteration. + * @param performExpirations true if this method should not spend time validating time for all items in the queue and just return the first item in the queue. + * @param excludeHere true if /tphere requests should be ignored in fetching the next tpa request. + * @return A {@link TpaRequest} corresponding to the next available request or null if no valid request is present. + */ + @Nullable TpaRequest getNextTpaRequest(boolean inform, boolean performExpirations, boolean excludeHere); + + /** + * Whether or not this {@link IUser} has any valid TPA requests in queue. + * + * @param inform true if the user should be informed if a request expires during iteration. + * @param excludeHere true if /tpahere requests should be ignored in checking if a tpa request is available. + * @return true if the user has an available pending request in queue. + */ + boolean hasPendingTpaRequests(boolean inform, boolean excludeHere); + + class TpaRequest { + private final String name; + private final UUID requesterUuid; + private boolean here; + private Location location; + private long time; + + public TpaRequest(String name, UUID requesterUuid) { + this.name = name; + this.requesterUuid = requesterUuid; + } + + public String getName() { + return name; + } + + public UUID getRequesterUuid() { + return requesterUuid; + } + + public boolean isHere() { + return here; + } + + public void setHere(boolean here) { + this.here = here; + } + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + + public long getTime() { + return time; + } + + public void setTime(long time) { + this.time = time; + } + } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/Settings.java b/Essentials/src/main/java/com/earth2me/essentials/Settings.java index 20ad33b62..47eecc985 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Settings.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Settings.java @@ -1264,6 +1264,11 @@ public class Settings implements net.ess3.api.ISettings { return config.getLong("tpa-accept-cancellation", 120); } + @Override + public int getTpaMaxRequests() { + return config.getInt("tpa-max-requests", 5); + } + private long _getTeleportInvulnerability() { return config.getLong("teleport-invulnerability", 0) * 1000; } diff --git a/Essentials/src/main/java/com/earth2me/essentials/User.java b/Essentials/src/main/java/com/earth2me/essentials/User.java index 4d161c5c0..039747ad1 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/User.java +++ b/Essentials/src/main/java/com/earth2me/essentials/User.java @@ -31,16 +31,21 @@ import org.bukkit.inventory.PlayerInventory; import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; +import org.checkerframework.checker.nullness.qual.Nullable; import java.math.BigDecimal; import java.util.Calendar; +import java.util.Collection; import java.util.GregorianCalendar; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.UUID; import java.util.WeakHashMap; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -49,28 +54,37 @@ import static com.earth2me.essentials.I18n.tl; public class User extends UserData implements Comparable, IMessageRecipient, net.ess3.api.IUser { private static final Statistic PLAY_ONE_TICK = EnumUtil.getStatistic("PLAY_ONE_MINUTE", "PLAY_ONE_TICK"); private static final Logger logger = Logger.getLogger("Essentials"); + + // User modules private final IMessageRecipient messageRecipient; private transient final AsyncTeleport teleport; private transient final Teleport legacyTeleport; + + // User command confirmation strings private final Map confirmingPayments = new WeakHashMap<>(); - private transient UUID teleportRequester; - private transient boolean teleportRequestHere; - private transient Location teleportLocation; + + // User teleport variables + private final transient LinkedHashMap teleportRequestQueue = new LinkedHashMap<>(); + + // User properties private transient boolean vanished; - private transient long teleportRequestTime; - private transient long lastOnlineActivity; - private transient long lastThrottledAction; - private transient long lastActivity = System.currentTimeMillis(); private boolean hidden = false; private boolean rightClickJump = false; - private transient Location afkPosition = null; private boolean invSee = false; private boolean recipeSee = false; private boolean enderSee = false; - private transient long teleportInvulnerabilityTimestamp = 0; private boolean ignoreMsg = false; + + // User afk variables private String afkMessage; private long afkSince; + private transient Location afkPosition = null; + + // Misc + private transient long lastOnlineActivity; + private transient long lastThrottledAction; + private transient long lastActivity = System.currentTimeMillis(); + private transient long teleportInvulnerabilityTimestamp = 0; private String confirmingClearCommand; private long lastNotifiedAboutMailsMs; private String lastHomeConfirmation; @@ -91,9 +105,8 @@ public class User extends UserData implements Comparable, IMessageRecipien this.messageRecipient = new SimpleMessageRecipient(ess, this); } - User update(final Player base) { + void update(final Player base) { setBase(base); - return this; } @Override @@ -328,44 +341,95 @@ public class User extends UserData implements Comparable, IMessageRecipien @Override public void requestTeleport(final User player, final boolean here) { - teleportRequestTime = System.currentTimeMillis(); - teleportRequester = player == null ? null : player.getBase().getUniqueId(); - teleportRequestHere = here; - if (player == null) { - teleportLocation = null; - } else { - teleportLocation = here ? player.getLocation() : this.getLocation(); + final TpaRequest request = teleportRequestQueue.getOrDefault(player.getName(), new TpaRequest(player.getName(), player.getUUID())); + request.setTime(System.currentTimeMillis()); + request.setHere(here); + request.setLocation(here ? player.getLocation() : this.getLocation()); + + // Handle max queue size + teleportRequestQueue.remove(request.getName()); + if (teleportRequestQueue.size() >= ess.getSettings().getTpaMaxRequests()) { + String lastKey = null; + for (Map.Entry entry : teleportRequestQueue.entrySet()) { + lastKey = entry.getKey(); + } + teleportRequestQueue.remove(lastKey); } + + // Add request to queue + teleportRequestQueue.put(request.getName(), request); } @Override + @Deprecated public boolean hasOutstandingTeleportRequest() { - if (getTeleportRequest() != null) { // Player has outstanding teleport request. - final long timeout = ess.getSettings().getTpaAcceptCancellation(); - if (timeout != 0) { - if ((System.currentTimeMillis() - getTeleportRequestTime()) / 1000 <= timeout) { // Player has outstanding request - return true; - } else { // outstanding request expired. - requestTeleport(null, false); - return false; + return getNextTpaRequest(false, false, false) != null; + } + + public Collection getPendingTpaKeys() { + return teleportRequestQueue.keySet(); + } + + @Override + public boolean hasPendingTpaRequests(boolean inform, boolean excludeHere) { + return getNextTpaRequest(inform, false, excludeHere) != null; + } + + public boolean hasOutstandingTpaRequest(String playerUsername, boolean here) { + final TpaRequest request = getOutstandingTpaRequest(playerUsername, false); + return request != null && request.isHere() == here; + } + + public @Nullable TpaRequest getOutstandingTpaRequest(String playerUsername, boolean inform) { + if (!teleportRequestQueue.containsKey(playerUsername)) { + return null; + } + + final long timeout = ess.getSettings().getTpaAcceptCancellation(); + final TpaRequest request = teleportRequestQueue.get(playerUsername); + if (timeout < 1 || System.currentTimeMillis() - request.getTime() <= timeout * 1000) { + return request; + } + teleportRequestQueue.remove(playerUsername); + if (inform) { + sendMessage(tl("requestTimedOutFrom", ess.getUser(request.getRequesterUuid()).getDisplayName())); + } + return null; + } + + public TpaRequest removeTpaRequest(String playerUsername) { + return teleportRequestQueue.remove(playerUsername); + } + + @Override + public TpaRequest getNextTpaRequest(boolean inform, boolean performExpirations, boolean excludeHere) { + if (teleportRequestQueue.isEmpty()) { + return null; + } + + final long timeout = ess.getSettings().getTpaAcceptCancellation(); + final Iterator> iterator = teleportRequestQueue.entrySet().iterator(); + TpaRequest nextRequest = null; + while (iterator.hasNext()) { + final TpaRequest request = iterator.next().getValue(); + if (timeout < 1 || (System.currentTimeMillis() - request.getTime()) <= TimeUnit.SECONDS.toMillis(timeout)) { + if (excludeHere && request.isHere()) { + continue; } - } else { // outstanding request does not expire - return true; + + if (performExpirations) { + return request; + } else if (nextRequest == null) { + nextRequest = request; + } + } else { + if (inform) { + sendMessage(tl("requestTimedOutFrom", ess.getUser(request.getRequesterUuid()).getDisplayName())); + } + iterator.remove(); } } - return false; - } - - public UUID getTeleportRequest() { - return teleportRequester; - } - - public boolean isTpRequestHere() { - return teleportRequestHere; - } - - public Location getTpRequestLocation() { - return teleportLocation; + return nextRequest; } public String getNick() { @@ -824,8 +888,11 @@ public class User extends UserData implements Comparable, IMessageRecipien return ess.getPermissionsHandler().canBuild(base, getGroup()); } + @Override + @Deprecated public long getTeleportRequestTime() { - return teleportRequestTime; + final TpaRequest request = getNextTpaRequest(false, false, false); + return request == null ? 0L : request.getTime(); } public boolean isInvSee() { @@ -993,7 +1060,7 @@ public class User extends UserData implements Comparable, IMessageRecipien @Override public UUID getUUID() { - return getBase().getUniqueId(); + return this.getBase().getUniqueId(); } @Override diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpa.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpa.java index 6233349e6..877e0b1f6 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpa.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpa.java @@ -37,11 +37,12 @@ public class Commandtpa extends EssentialsCommand { if (user.getWorld() != player.getWorld() && ess.getSettings().isWorldTeleportPermissions() && !user.isAuthorized("essentials.worlds." + player.getWorld().getName())) { throw new Exception(tl("noPerm", "essentials.worlds." + player.getWorld().getName())); } + // Don't let sender request teleport twice to the same player. - if (user.getConfigUUID().equals(player.getTeleportRequest()) && player.hasOutstandingTeleportRequest() // Check timeout - && !player.isTpRequestHere()) { // Make sure the last teleport request was actually tpa and not tpahere + if (player.hasOutstandingTpaRequest(user.getName(), false)) { throw new Exception(tl("requestSentAlready", player.getDisplayName())); } + if (player.isAutoTeleportEnabled() && !player.isIgnoredPlayer(user)) { final Trade charge = new Trade(this.getName(), ess); final AsyncTeleport teleport = user.getAsyncTeleport(); @@ -71,6 +72,7 @@ public class Commandtpa extends EssentialsCommand { player.sendMessage(tl("teleportRequestTimeoutInfo", ess.getSettings().getTpaAcceptCancellation())); } } + user.sendMessage(tl("requestSent", player.getDisplayName())); if (user.isAuthorized("essentials.tpacancel")) { user.sendMessage(tl("typeTpacancel")); diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpaall.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpaall.java index ca3b72896..5b0ec1b8a 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpaall.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpaall.java @@ -19,17 +19,17 @@ public class Commandtpaall extends EssentialsCommand { public void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception { if (args.length < 1) { if (sender.isPlayer()) { - teleportAAllPlayers(server, sender, ess.getUser(sender.getPlayer())); + tpaAll(sender, ess.getUser(sender.getPlayer())); return; } throw new NotEnoughArgumentsException(); } final User target = getPlayer(server, sender, args, 0); - teleportAAllPlayers(server, sender, target); + tpaAll(sender, target); } - private void teleportAAllPlayers(final Server server, final CommandSource sender, final User target) { + private void tpaAll(final CommandSource sender, final User target) { sender.sendMessage(tl("teleportAAll")); for (final User player : ess.getOnlineUsers()) { if (target == player) { @@ -41,6 +41,7 @@ public class Commandtpaall extends EssentialsCommand { if (sender.getSender().equals(target.getBase()) && target.getWorld() != player.getWorld() && ess.getSettings().isWorldTeleportPermissions() && !target.isAuthorized("essentials.worlds." + target.getWorld().getName())) { continue; } + try { final TPARequestEvent tpaEvent = new TPARequestEvent(sender, player, true); ess.getServer().getPluginManager().callEvent(tpaEvent); diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpacancel.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpacancel.java index 2531b2799..c73e3b7b6 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpacancel.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpacancel.java @@ -1,7 +1,6 @@ package com.earth2me.essentials.commands; import com.earth2me.essentials.User; -import net.ess3.api.IEssentials; import org.bukkit.Server; import static com.earth2me.essentials.I18n.tl; @@ -15,20 +14,12 @@ public class Commandtpacancel extends EssentialsCommand { /** * Cancel {@link User}'s tp request if its {@code requester} is equal to the given {@code requester}. * - * @param ess ess instance * @param user user holding tp request * @param requester tp requester * @return whether tp was cancelled */ - public static boolean cancelTeleportRequest(final IEssentials ess, final User user, final User requester) throws Exception { - if (user.getTeleportRequest() != null) { - final User userRequester = ess.getUser(user.getTeleportRequest()); - if (requester.equals(userRequester)) { - user.requestTeleport(null, false); - return true; - } - } - return false; + public static boolean cancelTeleportRequest(final User user, final User requester) { + return user.removeTpaRequest(requester.getName()) != null; } @Override @@ -37,7 +28,7 @@ public class Commandtpacancel extends EssentialsCommand { int cancellations = 0; for (final User onlineUser : ess.getOnlineUsers()) { if (onlineUser == user) continue; - if (cancelTeleportRequest(ess, onlineUser, user)) { + if (cancelTeleportRequest(onlineUser, user)) { cancellations++; } } @@ -48,7 +39,7 @@ public class Commandtpacancel extends EssentialsCommand { } } else { final User targetPlayer = getPlayer(server, user, args, 0); - if (cancelTeleportRequest(ess, targetPlayer, user)) { + if (cancelTeleportRequest(targetPlayer, user)) { user.sendMessage(tl("teleportRequestSpecificCancelled", targetPlayer.getName())); } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpaccept.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpaccept.java index 77c5ecfc5..cabdeb8ca 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpaccept.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpaccept.java @@ -1,12 +1,18 @@ package com.earth2me.essentials.commands; import com.earth2me.essentials.AsyncTeleport; +import com.earth2me.essentials.IUser; import com.earth2me.essentials.Trade; import com.earth2me.essentials.User; +import net.essentialsx.api.v2.events.TeleportRequestResponseEvent; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.concurrent.CompletableFuture; import static com.earth2me.essentials.I18n.tl; @@ -18,36 +24,87 @@ public class Commandtpaccept extends EssentialsCommand { @Override public void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception { - final User requester; - try { - requester = ess.getUser(user.getTeleportRequest()); - } catch (final Exception ex) { + final boolean acceptAll; + if (args.length > 0) { + acceptAll = args[0].equals("*") || args[0].equalsIgnoreCase("all"); + } else { + acceptAll = false; + } + + if (!user.hasPendingTpaRequests(true, acceptAll)) { throw new Exception(tl("noPendingRequest")); } + if (args.length > 0) { + if (acceptAll) { + acceptAllRequests(user, commandLabel); + throw new NoChargeException(); + } + user.sendMessage(tl("requestAccepted")); + handleTeleport(user, user.getOutstandingTpaRequest(getPlayer(server, user, args, 0).getName(), true), commandLabel); + } else { + user.sendMessage(tl("requestAccepted")); + handleTeleport(user, user.getNextTpaRequest(true, false, false), commandLabel); + } + throw new NoChargeException(); + } + + private void acceptAllRequests(final User user, final String commandLabel) throws Exception { + IUser.TpaRequest request; + int count = 0; + while ((request = user.getNextTpaRequest(true, true, true)) != null) { + try { + handleTeleport(user, request, commandLabel); + count++; + } catch (Exception e) { + ess.showError(user.getSource(), e, commandLabel); + } finally { + user.removeTpaRequest(request.getName()); + } + } + user.sendMessage(tl("requestAcceptedAll", count)); + } + + @Override + protected List getTabCompleteOptions(Server server, User user, String commandLabel, String[] args) { + if (args.length == 1) { + final List options = new ArrayList<>(user.getPendingTpaKeys()); + options.add("*"); + return options; + } else { + return Collections.emptyList(); + } + } + + private void handleTeleport(final User user, final IUser.TpaRequest request, String commandLabel) throws Exception { + if (request == null) { + throw new Exception(tl("noPendingRequest")); + } + final User requester = ess.getUser(request.getRequesterUuid()); + if (!requester.getBase().isOnline()) { + user.removeTpaRequest(request.getName()); throw new Exception(tl("noPendingRequest")); } - if (user.isTpRequestHere() && ((!requester.isAuthorized("essentials.tpahere") && !requester.isAuthorized("essentials.tpaall")) || (user.getWorld() != requester.getWorld() && ess.getSettings().isWorldTeleportPermissions() && !user.isAuthorized("essentials.worlds." + user.getWorld().getName())))) { + if (request.isHere() && ((!requester.isAuthorized("essentials.tpahere") && !requester.isAuthorized("essentials.tpaall")) || (user.getWorld() != requester.getWorld() && ess.getSettings().isWorldTeleportPermissions() && !user.isAuthorized("essentials.worlds." + user.getWorld().getName())))) { throw new Exception(tl("noPendingRequest")); } - if (!user.isTpRequestHere() && (!requester.isAuthorized("essentials.tpa") || (user.getWorld() != requester.getWorld() && ess.getSettings().isWorldTeleportPermissions() && !user.isAuthorized("essentials.worlds." + requester.getWorld().getName())))) { + if (!request.isHere() && (!requester.isAuthorized("essentials.tpa") || (user.getWorld() != requester.getWorld() && ess.getSettings().isWorldTeleportPermissions() && !user.isAuthorized("essentials.worlds." + requester.getWorld().getName())))) { throw new Exception(tl("noPendingRequest")); } - if (args.length > 0 && !requester.getName().contains(args[0])) { - throw new Exception(tl("noPendingRequest")); - } - - if (!user.hasOutstandingTeleportRequest()) { - user.requestTeleport(null, false); - throw new Exception(tl("requestTimedOut")); + final TeleportRequestResponseEvent event = new TeleportRequestResponseEvent(user, requester, request, true); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + if (ess.getSettings().isDebug()) { + logger.info("TPA accept cancelled by API for " + user.getName() + " (requested by " + requester.getName() + ")"); + } + return; } final Trade charge = new Trade(this.getName(), ess); - user.sendMessage(tl("requestAccepted")); requester.sendMessage(tl("requestAcceptedFrom", user.getDisplayName())); final CompletableFuture future = getNewExceptionFuture(requester.getSource(), commandLabel); @@ -55,8 +112,8 @@ public class Commandtpaccept extends EssentialsCommand { user.sendMessage(tl("pendingTeleportCancelled")); return false; }); - if (user.isTpRequestHere()) { - final Location loc = user.getTpRequestLocation(); + if (request.isHere()) { + final Location loc = request.getLocation(); final AsyncTeleport teleport = requester.getAsyncTeleport(); teleport.setTpType(AsyncTeleport.TeleportType.TPA); future.thenAccept(success -> { @@ -64,14 +121,12 @@ public class Commandtpaccept extends EssentialsCommand { requester.sendMessage(tl("teleporting", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); } }); - teleport.teleportPlayer(user, user.getTpRequestLocation(), charge, TeleportCause.COMMAND, future); + teleport.teleportPlayer(user, loc, charge, TeleportCause.COMMAND, future); } else { final AsyncTeleport teleport = requester.getAsyncTeleport(); teleport.setTpType(AsyncTeleport.TeleportType.TPA); teleport.teleport(user.getBase(), charge, TeleportCause.COMMAND, future); } - user.requestTeleport(null, false); - throw new NoChargeException(); + user.removeTpaRequest(request.getName()); } - } diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpahere.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpahere.java index 5a1a3e474..7d1c95cce 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpahere.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpahere.java @@ -33,11 +33,12 @@ public class Commandtpahere extends EssentialsCommand { if (user.getWorld() != player.getWorld() && ess.getSettings().isWorldTeleportPermissions() && !user.isAuthorized("essentials.worlds." + user.getWorld().getName())) { throw new Exception(tl("noPerm", "essentials.worlds." + user.getWorld().getName())); } + // Don't let sender request teleport twice to the same player. - if (user.getConfigUUID().equals(player.getTeleportRequest()) && player.hasOutstandingTeleportRequest() // Check timeout - && player.isTpRequestHere()) { // Make sure the last teleport request was actually tpahere and not tpa + if (player.hasOutstandingTpaRequest(user.getName(), true)) { throw new Exception(tl("requestSentAlready", player.getDisplayName())); } + if (!player.isIgnoredPlayer(user)) { final TPARequestEvent tpaEvent = new TPARequestEvent(user.getSource(), player, true); ess.getServer().getPluginManager().callEvent(tpaEvent); diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpdeny.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpdeny.java index d9c23a791..b5c10de51 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpdeny.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpdeny.java @@ -1,8 +1,15 @@ package com.earth2me.essentials.commands; +import com.earth2me.essentials.IUser; import com.earth2me.essentials.User; +import net.essentialsx.api.v2.events.TeleportRequestResponseEvent; +import org.bukkit.Bukkit; import org.bukkit.Server; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import static com.earth2me.essentials.I18n.tl; public class Commandtpdeny extends EssentialsCommand { @@ -12,16 +19,84 @@ public class Commandtpdeny extends EssentialsCommand { @Override public void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception { - if (user.getTeleportRequest() == null) { + final boolean denyAll; + if (args.length > 0) { + denyAll = args[0].equals("*") || args[0].equalsIgnoreCase("all"); + } else { + denyAll = false; + } + + if (!user.hasPendingTpaRequests(false, false)) { throw new Exception(tl("noPendingRequest")); } - final User player = ess.getUser(user.getTeleportRequest()); - if (player == null) { + + final IUser.TpaRequest denyRequest; + if (args.length > 0) { + if (denyAll) { + denyAllRequests(user); + return; + } + denyRequest = user.getOutstandingTpaRequest(getPlayer(server, user, args, 0).getName(), false); + } else { + denyRequest = user.getNextTpaRequest(false, true, false); + } + + if (denyRequest == null) { throw new Exception(tl("noPendingRequest")); } + final User player = ess.getUser(denyRequest.getRequesterUuid()); + if (player == null || !player.getBase().isOnline()) { + throw new Exception(tl("noPendingRequest")); + } + + if (sendEvent(user, player, denyRequest)) { + return; + } + user.sendMessage(tl("requestDenied")); player.sendMessage(tl("requestDeniedFrom", user.getDisplayName())); - user.requestTeleport(null, false); + user.removeTpaRequest(denyRequest.getName()); + } + + private void denyAllRequests(User user) { + IUser.TpaRequest request; + int count = 0; + while ((request = user.getNextTpaRequest(false, true, false)) != null) { + final User player = ess.getUser(request.getRequesterUuid()); + + if (sendEvent(user, player, request)) { + continue; + } + + if (player != null && player.getBase().isOnline()) { + player.sendMessage(tl("requestDeniedFrom", user.getDisplayName())); + } + + user.removeTpaRequest(request.getName()); + count++; + } + user.sendMessage(tl("requestDeniedAll", count)); + } + + private boolean sendEvent(User user, User player, IUser.TpaRequest request) { + final TeleportRequestResponseEvent event = new TeleportRequestResponseEvent(user, player, request, false); + Bukkit.getPluginManager().callEvent(event); + final boolean cancelled = event.isCancelled(); + if (cancelled && ess.getSettings().isDebug()) { + logger.info("TPA deny cancelled by API for " + user.getName() + " (requested by " + player.getName() + ")"); + } + return event.isCancelled(); + } + + @Override + protected List getTabCompleteOptions(Server server, User user, String commandLabel, String[] args) { + if (args.length == 1) { + final List options = new ArrayList<>(user.getPendingTpaKeys()); + options.add("*"); + return options; + } else { + return Collections.emptyList(); + } } } diff --git a/Essentials/src/main/java/net/essentialsx/api/v2/events/TeleportRequestResponseEvent.java b/Essentials/src/main/java/net/essentialsx/api/v2/events/TeleportRequestResponseEvent.java new file mode 100644 index 000000000..66bd2cd81 --- /dev/null +++ b/Essentials/src/main/java/net/essentialsx/api/v2/events/TeleportRequestResponseEvent.java @@ -0,0 +1,90 @@ +package net.essentialsx.api.v2.events; + +import com.earth2me.essentials.IUser; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * Called when a player accepts or denies a teleport. + */ +public class TeleportRequestResponseEvent extends Event implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private final net.ess3.api.IUser requestee; + private final net.ess3.api.IUser requester; + private final IUser.TpaRequest tpaRequest; + private final boolean accept; + private boolean canceled = false; + + public TeleportRequestResponseEvent(net.ess3.api.IUser requestee, net.ess3.api.IUser requester, IUser.TpaRequest tpaRequest, boolean accept) { + this.requestee = requestee; + this.requester = requester; + this.tpaRequest = tpaRequest; + this.accept = accept; + } + + /** + * Gets the user who is accepting/denying this teleport request. + * @return the user accepting/denying the request. + */ + public net.ess3.api.IUser getRequestee() { + return requestee; + } + + /** + * Gets the user who submitted this teleport request. + * @return the user who sent the request. + */ + public net.ess3.api.IUser getRequester() { + return requester; + } + + /** + * Gets information about this teleport request. + * @return the {@link com.earth2me.essentials.IUser.TpaRequest} object of this event. + */ + public IUser.TpaRequest getTpaRequest() { + return tpaRequest; + } + + /** + * Whether or not the request has been accepted. + * @return true if accepted, false if denied. + */ + public boolean isAccept() { + return accept; + } + + /** + * Whether or not the request has been denied. + * @return true if denied, false if accepted. + */ + public boolean isDeny() { + return !isAccept(); + } + + @Override + public boolean isCancelled() { + return canceled; + } + + /** + * Sets whether or not to cancel this teleport request. + * Note that cancelling this event will not show a message to users about the cancellation. + * @param cancel whether or not to cancel this teleport request. + */ + @Override + public void setCancelled(boolean cancel) { + this.canceled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/Essentials/src/main/resources/config.yml b/Essentials/src/main/resources/config.yml index 58c925e20..bd96fcaf4 100644 --- a/Essentials/src/main/resources/config.yml +++ b/Essentials/src/main/resources/config.yml @@ -698,6 +698,11 @@ jail-online-time: false # Set to 0 for no timeout. tpa-accept-cancellation: 120 +# The maximum number of simultaneous tpa requests that can be pending for any player. +# Once past this threshold, old requests will instantly time out. +# Defaults to 5. +tpa-max-requests: 5 + # Allow players to set hats by clicking on their helmet slot. allow-direct-hat: true diff --git a/Essentials/src/main/resources/messages.properties b/Essentials/src/main/resources/messages.properties index 33275ba59..526dc22db 100644 --- a/Essentials/src/main/resources/messages.properties +++ b/Essentials/src/main/resources/messages.properties @@ -1023,14 +1023,17 @@ replyLastRecipientDisabledFor=\u00a76Replying to last message recipient \u00a7cd replyLastRecipientEnabled=\u00a76Replying to last message recipient \u00a7cenabled\u00a76. replyLastRecipientEnabledFor=\u00a76Replying to last message recipient \u00a7cenabled \u00a76for \u00a7c{0}\u00a76. requestAccepted=\u00a76Teleport request accepted. +requestAcceptedAll=\u00a76Accepted \u00a7c{0} \u00a76pending teleport request(s). requestAcceptedAuto=\u00a76Automatically accepted a teleport request from {0}. requestAcceptedFrom=\u00a7c{0} \u00a76accepted your teleport request. requestAcceptedFromAuto=\u00a7c{0} \u00a76accepted your teleport request automatically. requestDenied=\u00a76Teleport request denied. +requestDeniedAll=\u00a76Denied \u00a7c{0} \u00a76pending teleport request(s). requestDeniedFrom=\u00a7c{0} \u00a76denied your teleport request. requestSent=\u00a76Request sent to\u00a7c {0}\u00a76. requestSentAlready=\u00a74You have already sent {0}\u00a74 a teleport request. requestTimedOut=\u00a74Teleport request has timed out. +requestTimedOutFrom=\u00a74Teleport request from \u00a7c{0} \u00a74has timed out. resetBal=\u00a76Balance has been reset to \u00a7c{0} \u00a76for all online players. resetBalAll=\u00a76Balance has been reset to \u00a7c{0} \u00a76for all players. rest=\u00a76You feel well rested. @@ -1290,10 +1293,14 @@ tpacancelCommandUsage1=/ tpacancelCommandUsage1Description=Cancels all your outstanding teleport requests tpacancelCommandUsage2=/ tpacancelCommandUsage2Description=Cancels all your outstanding teleport request with the specified player -tpacceptCommandDescription=Accepts a teleport request. +tpacceptCommandDescription=Accepts teleport requests. tpacceptCommandUsage=/ [otherplayer] tpacceptCommandUsage1=/ -tpacceptCommandUsage1Description=Accepts an incoming teleport request +tpacceptCommandUsage1Description=Accepts the most recent teleport request +tpacceptCommandUsage2=/ +tpacceptCommandUsage2Description=Accepts a teleport request from the specified player +tpacceptCommandUsage3=/ * +tpacceptCommandUsage3Description=Accepts all teleport requests tpahereCommandDescription=Request that the specified player teleport to you. tpahereCommandUsage=/ tpahereCommandUsage1=/ @@ -1306,10 +1313,14 @@ tpautoCommandDescription=Automatically accept teleportation requests. tpautoCommandUsage=/ [player] tpautoCommandUsage1=/ [player] tpautoCommandUsage1Description=Toggles if tpa requests are auto accepted for yourself or another player if specified -tpdenyCommandDescription=Reject a teleport request. +tpdenyCommandDescription=Rejects teleport requests. tpdenyCommandUsage=/ tpdenyCommandUsage1=/ -tpdenyCommandUsage1Description=Rejects an incoming teleport request +tpdenyCommandUsage1Description=Rejects the most recent teleport request +tpdenyCommandUsage2=/ +tpdenyCommandUsage2Description=Rejects a teleport request from the specified player +tpdenyCommandUsage3=/ * +tpdenyCommandUsage3Description=Rejects all teleport requests tphereCommandDescription=Teleport a player to you. tphereCommandUsage=/ tphereCommandUsage1=/ diff --git a/Essentials/src/main/resources/plugin.yml b/Essentials/src/main/resources/plugin.yml index 45f2607fc..8e60aa7b8 100644 --- a/Essentials/src/main/resources/plugin.yml +++ b/Essentials/src/main/resources/plugin.yml @@ -509,8 +509,8 @@ commands: usage: / aliases: [etpaall] tpaccept: - description: Accepts a teleport request. - usage: / [otherplayer] + description: Accepts teleport requests. + usage: / [player|*] aliases: [etpaccept,tpyes,etpyes] tpahere: description: Request that the specified player teleport to you. @@ -529,8 +529,8 @@ commands: usage: / [player] aliases: [etpacancel] tpdeny: - description: Reject a teleport request. - usage: / + description: Rejects teleport requests. + usage: / [player|*] aliases: [etpdeny,tpno,etpno] tphere: description: Teleport a player to you.