diff --git a/Essentials/pom.xml b/Essentials/pom.xml
index e18d0b618..d0401835d 100644
--- a/Essentials/pom.xml
+++ b/Essentials/pom.xml
@@ -63,7 +63,7 @@
io.papermc
paperlib
- 1.0.2
+ 1.0.3
compile
diff --git a/Essentials/src/com/earth2me/essentials/AsyncTeleport.java b/Essentials/src/com/earth2me/essentials/AsyncTeleport.java
new file mode 100644
index 000000000..26059dec8
--- /dev/null
+++ b/Essentials/src/com/earth2me/essentials/AsyncTeleport.java
@@ -0,0 +1,428 @@
+package com.earth2me.essentials;
+
+import com.earth2me.essentials.api.IAsyncTeleport;
+import com.earth2me.essentials.commands.WarpNotFoundException;
+import com.earth2me.essentials.utils.DateUtil;
+import com.earth2me.essentials.utils.LocationUtil;
+import io.papermc.lib.PaperLib;
+import net.ess3.api.IEssentials;
+import net.ess3.api.IUser;
+import net.ess3.api.InvalidWorldException;
+import net.ess3.api.events.UserTeleportEvent;
+import net.ess3.api.events.UserWarpEvent;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+import org.bukkit.event.player.PlayerRespawnEvent;
+import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
+
+import java.math.BigDecimal;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import static com.earth2me.essentials.I18n.tl;
+
+
+public class AsyncTeleport implements IAsyncTeleport {
+ private final IUser teleportOwner;
+ private final IEssentials ess;
+ private AsyncTimedTeleport timedTeleport;
+
+ private TeleportType tpType;
+
+ public AsyncTeleport(IUser user, IEssentials ess) {
+ this.teleportOwner = user;
+ this.ess = ess;
+ tpType = TeleportType.NORMAL;
+ }
+
+ public enum TeleportType {
+ TPA,
+ BACK,
+ NORMAL
+ }
+
+ public void cooldown(boolean check) throws Throwable {
+ CompletableFuture exceptionFuture = new CompletableFuture<>();
+ if (cooldown(check, exceptionFuture)) {
+ try {
+ exceptionFuture.get();
+ } catch (ExecutionException e) {
+ throw e.getCause();
+ }
+ }
+ }
+
+ public boolean cooldown(boolean check, CompletableFuture future) {
+ final Calendar time = new GregorianCalendar();
+ if (teleportOwner.getLastTeleportTimestamp() > 0) {
+ // Take the current time, and remove the delay from it.
+ final double cooldown = ess.getSettings().getTeleportCooldown();
+ final Calendar earliestTime = new GregorianCalendar();
+ earliestTime.add(Calendar.SECOND, -(int) cooldown);
+ earliestTime.add(Calendar.MILLISECOND, -(int) ((cooldown * 1000.0) % 1000.0));
+ // This value contains the most recent time a teleportPlayer could have been used that would allow another use.
+ final long earliestLong = earliestTime.getTimeInMillis();
+
+ // When was the last teleportPlayer used?
+ final long lastTime = teleportOwner.getLastTeleportTimestamp();
+
+ if (lastTime > time.getTimeInMillis()) {
+ // This is to make sure time didn't get messed up on last teleportPlayer use.
+ // If this happens, let's give the user the benifit of the doubt.
+ teleportOwner.setLastTeleportTimestamp(time.getTimeInMillis());
+ return false;
+ } else if (lastTime > earliestLong
+ && cooldownApplies()) {
+ time.setTimeInMillis(lastTime);
+ time.add(Calendar.SECOND, (int) cooldown);
+ time.add(Calendar.MILLISECOND, (int) ((cooldown * 1000.0) % 1000.0));
+ future.completeExceptionally(new Exception(tl("timeBeforeTeleport", DateUtil.formatDateDiff(time.getTimeInMillis()))));
+ return true;
+ }
+ }
+ // if justCheck is set, don't update lastTeleport; we're just checking
+ if (!check) {
+ teleportOwner.setLastTeleportTimestamp(time.getTimeInMillis());
+ }
+ return false;
+ }
+
+ private boolean cooldownApplies() {
+ boolean applies = true;
+ String globalBypassPerm = "essentials.teleport.cooldown.bypass";
+ switch (tpType) {
+ case NORMAL:
+ applies = !teleportOwner.isAuthorized(globalBypassPerm);
+ break;
+ case BACK:
+ applies = !(teleportOwner.isAuthorized(globalBypassPerm) &&
+ teleportOwner.isAuthorized("essentials.teleport.cooldown.bypass.back"));
+ break;
+ case TPA:
+ applies = !(teleportOwner.isAuthorized(globalBypassPerm) &&
+ teleportOwner.isAuthorized("essentials.teleport.cooldown.bypass.tpa"));
+ break;
+ }
+ return applies;
+ }
+
+ private void warnUser(final IUser user, final double delay) {
+ Calendar c = new GregorianCalendar();
+ c.add(Calendar.SECOND, (int) delay);
+ c.add(Calendar.MILLISECOND, (int) ((delay * 1000.0) % 1000.0));
+ user.sendMessage(tl("dontMoveMessage", DateUtil.formatDateDiff(c.getTimeInMillis())));
+ }
+
+
+ @Override
+ public void now(Location loc, boolean cooldown, TeleportCause cause, CompletableFuture future) {
+ if (cooldown && cooldown(false, future)) {
+ return;
+ }
+ final ITarget target = new LocationTarget(loc);
+ nowAsync(teleportOwner, target, cause, future);
+ }
+
+ @Override
+ public void now(Player entity, boolean cooldown, TeleportCause cause, CompletableFuture future) {
+ if (cooldown && cooldown(false, future)) {
+ future.complete(false);
+ return;
+ }
+ final ITarget target = new PlayerTarget(entity);
+ nowAsync(teleportOwner, target, cause, future);
+ future.thenAccept(success -> {
+ if (success) {
+ teleportOwner.sendMessage(tl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ()));
+ }
+ });
+ }
+
+ private void runOnMain(Runnable runnable) throws ExecutionException, InterruptedException {
+ if (Bukkit.isPrimaryThread()) {
+ runnable.run();
+ return;
+ }
+ CompletableFuture