Skip to content

Commit 7794634

Browse files
JRoyMariell Hoversholmmdcfe
authored
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 <[email protected]> Co-authored-by: MD <[email protected]>
1 parent d091d69 commit 7794634

File tree

14 files changed

+478
-90
lines changed

14 files changed

+478
-90
lines changed

Essentials/src/main/java/com/earth2me/essentials/ISettings.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ public interface ISettings extends IConf {
238238

239239
long getTpaAcceptCancellation();
240240

241+
int getTpaMaxRequests();
242+
241243
long getTeleportInvulnerability();
242244

243245
boolean isTeleportInvulnerability();

Essentials/src/main/java/com/earth2me/essentials/IUser.java

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@
1212
import org.bukkit.Material;
1313
import org.bukkit.block.Block;
1414
import org.bukkit.entity.Player;
15+
import org.checkerframework.checker.nullness.qual.Nullable;
1516

1617
import java.math.BigDecimal;
18+
1719
import java.util.ArrayList;
1820
import java.util.Date;
1921
import java.util.List;
2022
import java.util.Map;
2123
import java.util.Set;
24+
import java.util.UUID;
2225
import java.util.regex.Pattern;
2326

2427
/**
@@ -61,8 +64,11 @@ public interface IUser {
6164
/**
6265
* Returns whether this user has an outstanding teleport request to deal with.
6366
*
67+
* @deprecated The teleport request system has been moved into a multi-user teleport request queue.
68+
* @see IUser#hasPendingTpaRequests(boolean, boolean)
6469
* @return whether there is a teleport request
6570
*/
71+
@Deprecated
6672
boolean hasOutstandingTeleportRequest();
6773

6874
/**
@@ -98,6 +104,11 @@ public interface IUser {
98104

99105
boolean canBuild();
100106

107+
/**
108+
* @deprecated The teleport request system has been moved into a multi-user teleport request queue.
109+
* @see IUser#getNextTpaRequest(boolean, boolean, boolean)
110+
*/
111+
@Deprecated
101112
long getTeleportRequestTime();
102113

103114
void enableInvulnerabilityAfterTeleport();
@@ -205,6 +216,8 @@ public interface IUser {
205216

206217
String getName();
207218

219+
UUID getUUID();
220+
208221
String getDisplayName();
209222

210223
String getFormattedNickname();
@@ -238,4 +251,74 @@ public interface IUser {
238251
void setToggleShout(boolean toggleShout);
239252

240253
boolean isToggleShout();
254+
255+
/**
256+
* Gets information about the most-recently-made, non-expired TPA request in the tpa queue of this {@link IUser}.
257+
* <p>
258+
* The TPA Queue is Last-In-First-Out queue which stores all the active pending teleport
259+
* requests of this {@link IUser}. Timeout calculations are also done during the
260+
* iteration process of this method, ensuring that teleport requests made past the timeout
261+
* period are removed from queue and therefore not returned here. The maximum size of this
262+
* queue is determined by {@link ISettings#getTpaMaxRequests()}.
263+
*
264+
* @param inform true if the underlying {@link IUser} should be informed if a request expires during iteration.
265+
* @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.
266+
* @param excludeHere true if /tphere requests should be ignored in fetching the next tpa request.
267+
* @return A {@link TpaRequest} corresponding to the next available request or null if no valid request is present.
268+
*/
269+
@Nullable TpaRequest getNextTpaRequest(boolean inform, boolean performExpirations, boolean excludeHere);
270+
271+
/**
272+
* Whether or not this {@link IUser} has any valid TPA requests in queue.
273+
*
274+
* @param inform true if the user should be informed if a request expires during iteration.
275+
* @param excludeHere true if /tpahere requests should be ignored in checking if a tpa request is available.
276+
* @return true if the user has an available pending request in queue.
277+
*/
278+
boolean hasPendingTpaRequests(boolean inform, boolean excludeHere);
279+
280+
class TpaRequest {
281+
private final String name;
282+
private final UUID requesterUuid;
283+
private boolean here;
284+
private Location location;
285+
private long time;
286+
287+
public TpaRequest(String name, UUID requesterUuid) {
288+
this.name = name;
289+
this.requesterUuid = requesterUuid;
290+
}
291+
292+
public String getName() {
293+
return name;
294+
}
295+
296+
public UUID getRequesterUuid() {
297+
return requesterUuid;
298+
}
299+
300+
public boolean isHere() {
301+
return here;
302+
}
303+
304+
public void setHere(boolean here) {
305+
this.here = here;
306+
}
307+
308+
public Location getLocation() {
309+
return location;
310+
}
311+
312+
public void setLocation(Location location) {
313+
this.location = location;
314+
}
315+
316+
public long getTime() {
317+
return time;
318+
}
319+
320+
public void setTime(long time) {
321+
this.time = time;
322+
}
323+
}
241324
}

Essentials/src/main/java/com/earth2me/essentials/Settings.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,11 @@ public long getTpaAcceptCancellation() {
12641264
return config.getLong("tpa-accept-cancellation", 120);
12651265
}
12661266

1267+
@Override
1268+
public int getTpaMaxRequests() {
1269+
return config.getInt("tpa-max-requests", 5);
1270+
}
1271+
12671272
private long _getTeleportInvulnerability() {
12681273
return config.getLong("teleport-invulnerability", 0) * 1000;
12691274
}

Essentials/src/main/java/com/earth2me/essentials/User.java

Lines changed: 107 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,21 @@
3131
import org.bukkit.metadata.FixedMetadataValue;
3232
import org.bukkit.potion.PotionEffect;
3333
import org.bukkit.potion.PotionEffectType;
34+
import org.checkerframework.checker.nullness.qual.Nullable;
3435

3536
import java.math.BigDecimal;
3637
import java.util.Calendar;
38+
import java.util.Collection;
3739
import java.util.GregorianCalendar;
40+
import java.util.Iterator;
41+
import java.util.LinkedHashMap;
3842
import java.util.List;
3943
import java.util.Locale;
4044
import java.util.Map;
4145
import java.util.UUID;
4246
import java.util.WeakHashMap;
4347
import java.util.concurrent.CompletableFuture;
48+
import java.util.concurrent.TimeUnit;
4449
import java.util.logging.Level;
4550
import java.util.logging.Logger;
4651

@@ -49,28 +54,37 @@
4954
public class User extends UserData implements Comparable<User>, IMessageRecipient, net.ess3.api.IUser {
5055
private static final Statistic PLAY_ONE_TICK = EnumUtil.getStatistic("PLAY_ONE_MINUTE", "PLAY_ONE_TICK");
5156
private static final Logger logger = Logger.getLogger("Essentials");
57+
58+
// User modules
5259
private final IMessageRecipient messageRecipient;
5360
private transient final AsyncTeleport teleport;
5461
private transient final Teleport legacyTeleport;
62+
63+
// User command confirmation strings
5564
private final Map<User, BigDecimal> confirmingPayments = new WeakHashMap<>();
56-
private transient UUID teleportRequester;
57-
private transient boolean teleportRequestHere;
58-
private transient Location teleportLocation;
65+
66+
// User teleport variables
67+
private final transient LinkedHashMap<String, TpaRequest> teleportRequestQueue = new LinkedHashMap<>();
68+
69+
// User properties
5970
private transient boolean vanished;
60-
private transient long teleportRequestTime;
61-
private transient long lastOnlineActivity;
62-
private transient long lastThrottledAction;
63-
private transient long lastActivity = System.currentTimeMillis();
6471
private boolean hidden = false;
6572
private boolean rightClickJump = false;
66-
private transient Location afkPosition = null;
6773
private boolean invSee = false;
6874
private boolean recipeSee = false;
6975
private boolean enderSee = false;
70-
private transient long teleportInvulnerabilityTimestamp = 0;
7176
private boolean ignoreMsg = false;
77+
78+
// User afk variables
7279
private String afkMessage;
7380
private long afkSince;
81+
private transient Location afkPosition = null;
82+
83+
// Misc
84+
private transient long lastOnlineActivity;
85+
private transient long lastThrottledAction;
86+
private transient long lastActivity = System.currentTimeMillis();
87+
private transient long teleportInvulnerabilityTimestamp = 0;
7488
private String confirmingClearCommand;
7589
private long lastNotifiedAboutMailsMs;
7690
private String lastHomeConfirmation;
@@ -91,9 +105,8 @@ public User(final Player base, final IEssentials ess) {
91105
this.messageRecipient = new SimpleMessageRecipient(ess, this);
92106
}
93107

94-
User update(final Player base) {
108+
void update(final Player base) {
95109
setBase(base);
96-
return this;
97110
}
98111

99112
@Override
@@ -328,44 +341,95 @@ public void setLogoutLocation() {
328341

329342
@Override
330343
public void requestTeleport(final User player, final boolean here) {
331-
teleportRequestTime = System.currentTimeMillis();
332-
teleportRequester = player == null ? null : player.getBase().getUniqueId();
333-
teleportRequestHere = here;
334-
if (player == null) {
335-
teleportLocation = null;
336-
} else {
337-
teleportLocation = here ? player.getLocation() : this.getLocation();
344+
final TpaRequest request = teleportRequestQueue.getOrDefault(player.getName(), new TpaRequest(player.getName(), player.getUUID()));
345+
request.setTime(System.currentTimeMillis());
346+
request.setHere(here);
347+
request.setLocation(here ? player.getLocation() : this.getLocation());
348+
349+
// Handle max queue size
350+
teleportRequestQueue.remove(request.getName());
351+
if (teleportRequestQueue.size() >= ess.getSettings().getTpaMaxRequests()) {
352+
String lastKey = null;
353+
for (Map.Entry<String, TpaRequest> entry : teleportRequestQueue.entrySet()) {
354+
lastKey = entry.getKey();
355+
}
356+
teleportRequestQueue.remove(lastKey);
338357
}
358+
359+
// Add request to queue
360+
teleportRequestQueue.put(request.getName(), request);
339361
}
340362

341363
@Override
364+
@Deprecated
342365
public boolean hasOutstandingTeleportRequest() {
343-
if (getTeleportRequest() != null) { // Player has outstanding teleport request.
344-
final long timeout = ess.getSettings().getTpaAcceptCancellation();
345-
if (timeout != 0) {
346-
if ((System.currentTimeMillis() - getTeleportRequestTime()) / 1000 <= timeout) { // Player has outstanding request
347-
return true;
348-
} else { // outstanding request expired.
349-
requestTeleport(null, false);
350-
return false;
351-
}
352-
} else { // outstanding request does not expire
353-
return true;
354-
}
355-
}
356-
return false;
366+
return getNextTpaRequest(false, false, false) != null;
367+
}
368+
369+
public Collection<String> getPendingTpaKeys() {
370+
return teleportRequestQueue.keySet();
357371
}
358372

359-
public UUID getTeleportRequest() {
360-
return teleportRequester;
373+
@Override
374+
public boolean hasPendingTpaRequests(boolean inform, boolean excludeHere) {
375+
return getNextTpaRequest(inform, false, excludeHere) != null;
376+
}
377+
378+
public boolean hasOutstandingTpaRequest(String playerUsername, boolean here) {
379+
final TpaRequest request = getOutstandingTpaRequest(playerUsername, false);
380+
return request != null && request.isHere() == here;
381+
}
382+
383+
public @Nullable TpaRequest getOutstandingTpaRequest(String playerUsername, boolean inform) {
384+
if (!teleportRequestQueue.containsKey(playerUsername)) {
385+
return null;
386+
}
387+
388+
final long timeout = ess.getSettings().getTpaAcceptCancellation();
389+
final TpaRequest request = teleportRequestQueue.get(playerUsername);
390+
if (timeout < 1 || System.currentTimeMillis() - request.getTime() <= timeout * 1000) {
391+
return request;
392+
}
393+
teleportRequestQueue.remove(playerUsername);
394+
if (inform) {
395+
sendMessage(tl("requestTimedOutFrom", ess.getUser(request.getRequesterUuid()).getDisplayName()));
396+
}
397+
return null;
361398
}
362399

363-
public boolean isTpRequestHere() {
364-
return teleportRequestHere;
400+
public TpaRequest removeTpaRequest(String playerUsername) {
401+
return teleportRequestQueue.remove(playerUsername);
365402
}
366403

367-
public Location getTpRequestLocation() {
368-
return teleportLocation;
404+
@Override
405+
public TpaRequest getNextTpaRequest(boolean inform, boolean performExpirations, boolean excludeHere) {
406+
if (teleportRequestQueue.isEmpty()) {
407+
return null;
408+
}
409+
410+
final long timeout = ess.getSettings().getTpaAcceptCancellation();
411+
final Iterator<Map.Entry<String, TpaRequest>> iterator = teleportRequestQueue.entrySet().iterator();
412+
TpaRequest nextRequest = null;
413+
while (iterator.hasNext()) {
414+
final TpaRequest request = iterator.next().getValue();
415+
if (timeout < 1 || (System.currentTimeMillis() - request.getTime()) <= TimeUnit.SECONDS.toMillis(timeout)) {
416+
if (excludeHere && request.isHere()) {
417+
continue;
418+
}
419+
420+
if (performExpirations) {
421+
return request;
422+
} else if (nextRequest == null) {
423+
nextRequest = request;
424+
}
425+
} else {
426+
if (inform) {
427+
sendMessage(tl("requestTimedOutFrom", ess.getUser(request.getRequesterUuid()).getDisplayName()));
428+
}
429+
iterator.remove();
430+
}
431+
}
432+
return nextRequest;
369433
}
370434

371435
public String getNick() {
@@ -824,8 +888,11 @@ public boolean canBuild() {
824888
return ess.getPermissionsHandler().canBuild(base, getGroup());
825889
}
826890

891+
@Override
892+
@Deprecated
827893
public long getTeleportRequestTime() {
828-
return teleportRequestTime;
894+
final TpaRequest request = getNextTpaRequest(false, false, false);
895+
return request == null ? 0L : request.getTime();
829896
}
830897

831898
public boolean isInvSee() {
@@ -993,7 +1060,7 @@ public String getName() {
9931060

9941061
@Override
9951062
public UUID getUUID() {
996-
return getBase().getUniqueId();
1063+
return this.getBase().getUniqueId();
9971064
}
9981065

9991066
@Override

Essentials/src/main/java/com/earth2me/essentials/commands/Commandtpa.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ public void run(final Server server, final User user, final String commandLabel,
3737
if (user.getWorld() != player.getWorld() && ess.getSettings().isWorldTeleportPermissions() && !user.isAuthorized("essentials.worlds." + player.getWorld().getName())) {
3838
throw new Exception(tl("noPerm", "essentials.worlds." + player.getWorld().getName()));
3939
}
40+
4041
// Don't let sender request teleport twice to the same player.
41-
if (user.getConfigUUID().equals(player.getTeleportRequest()) && player.hasOutstandingTeleportRequest() // Check timeout
42-
&& !player.isTpRequestHere()) { // Make sure the last teleport request was actually tpa and not tpahere
42+
if (player.hasOutstandingTpaRequest(user.getName(), false)) {
4343
throw new Exception(tl("requestSentAlready", player.getDisplayName()));
4444
}
45+
4546
if (player.isAutoTeleportEnabled() && !player.isIgnoredPlayer(user)) {
4647
final Trade charge = new Trade(this.getName(), ess);
4748
final AsyncTeleport teleport = user.getAsyncTeleport();
@@ -71,6 +72,7 @@ public void run(final Server server, final User user, final String commandLabel,
7172
player.sendMessage(tl("teleportRequestTimeoutInfo", ess.getSettings().getTpaAcceptCancellation()));
7273
}
7374
}
75+
7476
user.sendMessage(tl("requestSent", player.getDisplayName()));
7577
if (user.isAuthorized("essentials.tpacancel")) {
7678
user.sendMessage(tl("typeTpacancel"));

0 commit comments

Comments
 (0)