Add support for multiple queued TPA requests (#3801)

This PR adds support for players to receive multiple teleport requests, which are queued and can be managed independently of one another.

All commands should retain their current behavior but have some new additions;
* `/tpaccept`: now allows you to specify a player or `*` to accept a specific player's or all players' teleport request(s) respectively.
  - Using a wildcard will only accept all tpahere requests, as players can't teleport to multiple places simultaneously.
* `/tpdeny`: now allows you to specify a player or `*` to deny a specific player's or all players' teleport request(s) respectively.

This PR also adds a new setting for the maximum amount of pending TPA requests a user can have at once.
```yml
# The maximum amount of simultaneous tpa requests that can be pending for any given user.
# Once at this threshold, any new tpa requests will bump the oldest tpa requests out of queue.
# Defaults to 5.
tpa-max-amount: 5
```

Closes #3769
Closes #1550

Co-authored-by: Mariell Hoversholm <proximyst@proximy.st>
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com>
This commit is contained in:
Josh Roy 2021-12-04 09:40:06 -05:00 committed by GitHub
parent d091d6902c
commit 7794634d37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 483 additions and 95 deletions

View File

@ -238,6 +238,8 @@ public interface ISettings extends IConf {
long getTpaAcceptCancellation();
int getTpaMaxRequests();
long getTeleportInvulnerability();
boolean isTeleportInvulnerability();

View File

@ -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}.
* <p>
* 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;
}
}
}

View File

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

View File

@ -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<User>, 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<User, BigDecimal> confirmingPayments = new WeakHashMap<>();
private transient UUID teleportRequester;
private transient boolean teleportRequestHere;
private transient Location teleportLocation;
// User teleport variables
private final transient LinkedHashMap<String, TpaRequest> 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<User>, 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<User>, 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<String, TpaRequest> 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<String> 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<Map.Entry<String, TpaRequest>> 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<User>, 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<User>, IMessageRecipien
@Override
public UUID getUUID() {
return getBase().getUniqueId();
return this.getBase().getUniqueId();
}
@Override

View File

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

View File

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

View File

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

View File

@ -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<String> getTabCompleteOptions(Server server, User user, String commandLabel, String[] args) {
if (args.length == 1) {
final List<String> 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<Boolean> 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());
}
}

View File

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

View File

@ -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<String> getTabCompleteOptions(Server server, User user, String commandLabel, String[] args) {
if (args.length == 1) {
final List<String> options = new ArrayList<>(user.getPendingTpaKeys());
options.add("*");
return options;
} else {
return Collections.emptyList();
}
}
}

View File

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

View File

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

View File

@ -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=/<command>
tpacancelCommandUsage1Description=Cancels all your outstanding teleport requests
tpacancelCommandUsage2=/<command> <player>
tpacancelCommandUsage2Description=Cancels all your outstanding teleport request with the specified player
tpacceptCommandDescription=Accepts a teleport request.
tpacceptCommandDescription=Accepts teleport requests.
tpacceptCommandUsage=/<command> [otherplayer]
tpacceptCommandUsage1=/<command>
tpacceptCommandUsage1Description=Accepts an incoming teleport request
tpacceptCommandUsage1Description=Accepts the most recent teleport request
tpacceptCommandUsage2=/<command> <player>
tpacceptCommandUsage2Description=Accepts a teleport request from the specified player
tpacceptCommandUsage3=/<command> *
tpacceptCommandUsage3Description=Accepts all teleport requests
tpahereCommandDescription=Request that the specified player teleport to you.
tpahereCommandUsage=/<command> <player>
tpahereCommandUsage1=/<command> <player>
@ -1306,10 +1313,14 @@ tpautoCommandDescription=Automatically accept teleportation requests.
tpautoCommandUsage=/<command> [player]
tpautoCommandUsage1=/<command> [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=/<command>
tpdenyCommandUsage1=/<command>
tpdenyCommandUsage1Description=Rejects an incoming teleport request
tpdenyCommandUsage1Description=Rejects the most recent teleport request
tpdenyCommandUsage2=/<command> <player>
tpdenyCommandUsage2Description=Rejects a teleport request from the specified player
tpdenyCommandUsage3=/<command> *
tpdenyCommandUsage3Description=Rejects all teleport requests
tphereCommandDescription=Teleport a player to you.
tphereCommandUsage=/<command> <player>
tphereCommandUsage1=/<command> <player>

View File

@ -509,8 +509,8 @@ commands:
usage: /<command> <player>
aliases: [etpaall]
tpaccept:
description: Accepts a teleport request.
usage: /<command> [otherplayer]
description: Accepts teleport requests.
usage: /<command> [player|*]
aliases: [etpaccept,tpyes,etpyes]
tpahere:
description: Request that the specified player teleport to you.
@ -529,8 +529,8 @@ commands:
usage: /<command> [player]
aliases: [etpacancel]
tpdeny:
description: Reject a teleport request.
usage: /<command>
description: Rejects teleport requests.
usage: /<command> [player|*]
aliases: [etpdeny,tpno,etpno]
tphere:
description: Teleport a player to you.