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.