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 taskLock = new CompletableFuture<>(); + Bukkit.getScheduler().runTask(ess, () -> { + runnable.run(); + taskLock.complete(new Object()); + }); + taskLock.get(); + } + + protected void nowAsync(IUser teleportee, ITarget target, TeleportCause cause, CompletableFuture future) { + cancel(false); + + UserTeleportEvent event = new UserTeleportEvent(teleportee, cause, target.getLocation()); + Bukkit.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + teleportee.setLastLocation(); + + if (!teleportee.getBase().isEmpty()) { + if (!ess.getSettings().isTeleportPassengerDismount()) { + future.completeExceptionally(new Exception(tl("passengerTeleportFail"))); + return; + } + + try { + runOnMain(() -> teleportee.getBase().eject()); //EntityDismountEvent requires a sync context. + } catch (ExecutionException | InterruptedException e) { + future.completeExceptionally(e); + return; + } + } + teleportee.setLastLocation(); + PaperLib.getChunkAtAsync(target.getLocation()).thenAccept(chunk -> { + Location loc = target.getLocation(); + if (LocationUtil.isBlockUnsafeForUser(teleportee, chunk.getWorld(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())) { + if (ess.getSettings().isTeleportSafetyEnabled()) { + if (ess.getSettings().isForceDisableTeleportSafety()) { + //The chunk we're teleporting to is 100% going to be loaded here, no need to teleport async. + teleportee.getBase().teleport(loc, cause); + } else { + try { + //There's a chance the safer location is outside the loaded chunk so still teleport async here. + PaperLib.teleportAsync(teleportee.getBase(), LocationUtil.getSafeDestination(ess, teleportee, loc), cause); + } catch (Exception e) { + future.completeExceptionally(e); + return; + } + } + } else { + future.completeExceptionally(new Exception(tl("unsafeTeleportDestination", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()))); + return; + } + } else { + if (ess.getSettings().isForceDisableTeleportSafety()) { + //The chunk we're teleporting to is 100% going to be loaded here, no need to teleport async. + teleportee.getBase().teleport(loc, cause); + } else { + if (ess.getSettings().isTeleportToCenterLocation()) { + loc = LocationUtil.getRoundedDestination(loc); + } + //There's a *small* chance the rounded destination produces a location outside the loaded chunk so still teleport async here. + PaperLib.teleportAsync(teleportee.getBase(), loc, cause); + } + } + future.complete(true); + }); + } + + @Override + public void teleport(Location loc, Trade chargeFor, TeleportCause cause, CompletableFuture future) { + teleport(teleportOwner, new LocationTarget(loc), chargeFor, cause, future); + } + + @Override + public void teleport(Player entity, Trade chargeFor, TeleportCause cause, CompletableFuture future) { + teleportOwner.sendMessage(tl("teleportToPlayer", entity.getDisplayName())); + teleport(teleportOwner, new PlayerTarget(entity), chargeFor, cause, future); + } + + @Override + public void teleportPlayer(IUser otherUser, Location loc, Trade chargeFor, TeleportCause cause, CompletableFuture future) { + teleport(otherUser, new LocationTarget(loc), chargeFor, cause, future); + } + + @Override + public void teleportPlayer(IUser otherUser, Player entity, Trade chargeFor, TeleportCause cause, CompletableFuture future) { + ITarget target = new PlayerTarget(entity); + teleport(otherUser, target, chargeFor, cause, future); + future.thenAccept(success -> { + if (success) { + otherUser.sendMessage(tl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ())); + teleportOwner.sendMessage(tl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ())); + } + }); + } + + private void teleport(IUser teleportee, ITarget target, Trade chargeFor, TeleportCause cause, CompletableFuture future) { + double delay = ess.getSettings().getTeleportDelay(); + + Trade cashCharge = chargeFor; + + if (chargeFor != null) { + chargeFor.isAffordableFor(teleportOwner, future); + if (future.isCompletedExceptionally()) { + return; + } + + //This code is to make sure that commandcosts are checked in the initial world, and not in the resulting world. + if (!chargeFor.getCommandCost(teleportOwner).equals(BigDecimal.ZERO)) { + //By converting a command cost to a regular cost, the command cost permission isn't checked when executing the charge after teleport. + cashCharge = new Trade(chargeFor.getCommandCost(teleportOwner), ess); + } + } + + if (cooldown(true, future)) { + return; + } + if (delay <= 0 || teleportOwner.isAuthorized("essentials.teleport.timer.bypass") || teleportee.isAuthorized("essentials.teleport.timer.bypass")) { + if (cooldown(false, future)) { + return; + } + nowAsync(teleportee, target, cause, future); + if (cashCharge != null) { + cashCharge.charge(teleportOwner, future); + if (future.isCompletedExceptionally()) { + return; + } + } + return; + } + + cancel(false); + warnUser(teleportee, delay); + initTimer((long) (delay * 1000.0), teleportee, target, cashCharge, cause, false); + } + + private void teleportOther(IUser teleporter, IUser teleportee, ITarget target, Trade chargeFor, TeleportCause cause, CompletableFuture future) { + double delay = ess.getSettings().getTeleportDelay(); + + Trade cashCharge = chargeFor; + + if (teleporter != null && chargeFor != null) { + chargeFor.isAffordableFor(teleporter, future); + if (future.isCompletedExceptionally()) { + return; + } + + //This code is to make sure that commandcosts are checked in the initial world, and not in the resulting world. + if (!chargeFor.getCommandCost(teleporter).equals(BigDecimal.ZERO)) { + //By converting a command cost to a regular cost, the command cost permission isn't checked when executing the charge after teleport. + cashCharge = new Trade(chargeFor.getCommandCost(teleporter), ess); + } + } + + if (cooldown(true, future)) { + return; + } + if (delay <= 0 || teleporter == null + || teleporter.isAuthorized("essentials.teleport.timer.bypass") + || teleportOwner.isAuthorized("essentials.teleport.timer.bypass") + || teleportee.isAuthorized("essentials.teleport.timer.bypass")) { + if (cooldown(false, future)) { + return; + } + + nowAsync(teleportee, target, cause, future); + if (teleporter != null && cashCharge != null) { + cashCharge.charge(teleporter, future); + if (future.isCompletedExceptionally()) { + return; + } + } + return; + } + + cancel(false); + warnUser(teleportee, delay); + initTimer((long) (delay * 1000.0), teleportee, target, cashCharge, cause, false); + } + + @Override + public void respawn(Trade chargeFor, TeleportCause cause, CompletableFuture future) { + double delay = ess.getSettings().getTeleportDelay(); + if (chargeFor != null) { + chargeFor.isAffordableFor(teleportOwner, future); + if (future.isCompletedExceptionally()) { + return; + } + } + if (cooldown(true, future)) { + return; + } + if (delay <= 0 || teleportOwner.isAuthorized("essentials.teleport.timer.bypass")) { + if (cooldown(false, future)) { + return; + } + respawnNow(teleportOwner, cause, future); + if (chargeFor != null) { + chargeFor.charge(teleportOwner, future); + } + return; + } + + cancel(false); + warnUser(teleportOwner, delay); + initTimer((long) (delay * 1000.0), teleportOwner, null, chargeFor, cause, true); + } + + void respawnNow(IUser teleportee, TeleportCause cause, CompletableFuture future) { + final Player player = teleportee.getBase(); + Location bed = player.getBedSpawnLocation(); + if (bed != null) { + nowAsync(teleportee, new LocationTarget(bed), cause, future); + } else { + if (ess.getSettings().isDebug()) { + ess.getLogger().info("Could not find bed spawn, forcing respawn event."); + } + final PlayerRespawnEvent pre = new PlayerRespawnEvent(player, player.getWorld().getSpawnLocation(), false); + ess.getServer().getPluginManager().callEvent(pre); + nowAsync(teleportee, new LocationTarget(pre.getRespawnLocation()), cause, future); + } + } + + @Override + public void warp(IUser otherUser, String warp, Trade chargeFor, TeleportCause cause, CompletableFuture future) { + UserWarpEvent event = new UserWarpEvent(otherUser, warp, chargeFor); + Bukkit.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + + warp = event.getWarp(); + Location loc; + try { + loc = ess.getWarps().getWarp(warp); + } catch (WarpNotFoundException | InvalidWorldException e) { + future.completeExceptionally(e); + return; + } + otherUser.sendMessage(tl("warpingTo", warp, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); + if (!otherUser.equals(teleportOwner)) { + teleportOwner.sendMessage(tl("warpingTo", warp, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); + } + teleport(otherUser, new LocationTarget(loc), chargeFor, cause, future); + } + + @Override + public void back(Trade chargeFor, CompletableFuture future) { + back(teleportOwner, chargeFor, future); + } + + @Override + public void back(IUser teleporter, Trade chargeFor, CompletableFuture future) { + tpType = TeleportType.BACK; + final Location loc = teleportOwner.getLastLocation(); + teleportOwner.sendMessage(tl("backUsageMsg", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); + teleportOther(teleporter, teleportOwner, new LocationTarget(loc), chargeFor, TeleportCause.COMMAND, future); + } + + @Override + public void back(CompletableFuture future) { + nowAsync(teleportOwner, new LocationTarget(teleportOwner.getLastLocation()), TeleportCause.COMMAND, future); + } + + public void setTpType(TeleportType tpType) { + this.tpType = tpType; + } + + //If we need to cancelTimer a pending teleportPlayer call this method + private void cancel(boolean notifyUser) { + if (timedTeleport != null) { + timedTeleport.cancelTimer(notifyUser); + timedTeleport = null; + } + } + + private void initTimer(long delay, IUser teleportUser, ITarget target, Trade chargeFor, TeleportCause cause, boolean respawn) { + timedTeleport = new AsyncTimedTeleport(teleportOwner, ess, this, delay, teleportUser, target, chargeFor, cause, respawn); + } +} diff --git a/Essentials/src/com/earth2me/essentials/AsyncTimedTeleport.java b/Essentials/src/com/earth2me/essentials/AsyncTimedTeleport.java new file mode 100644 index 000000000..86c91c057 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/AsyncTimedTeleport.java @@ -0,0 +1,151 @@ +package com.earth2me.essentials; + +import net.ess3.api.IEssentials; +import net.ess3.api.IUser; +import org.bukkit.Location; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import static com.earth2me.essentials.I18n.tl; + + +public class AsyncTimedTeleport implements Runnable { + private static final double MOVE_CONSTANT = 0.3; + private final IUser teleportOwner; + private final IEssentials ess; + private final AsyncTeleport teleport; + private final UUID timer_teleportee; + private int timer_task; + private final long timer_started; // time this task was initiated + private final long timer_delay; // how long to delay the teleportPlayer + private double timer_health; + // note that I initially stored a clone of the location for reference, but... + // when comparing locations, I got incorrect mismatches (rounding errors, looked like) + // so, the X/Y/Z values are stored instead and rounded off + private final long timer_initX; + private final long timer_initY; + private final long timer_initZ; + private final ITarget timer_teleportTarget; + private final boolean timer_respawn; + private final boolean timer_canMove; + private final Trade timer_chargeFor; + private final TeleportCause timer_cause; + + AsyncTimedTeleport(IUser user, IEssentials ess, AsyncTeleport teleport, long delay, IUser teleportUser, ITarget target, Trade chargeFor, TeleportCause cause, boolean respawn) { + this.teleportOwner = user; + this.ess = ess; + this.teleport = teleport; + this.timer_started = System.currentTimeMillis(); + this.timer_delay = delay; + this.timer_health = teleportUser.getBase().getHealth(); + this.timer_initX = Math.round(teleportUser.getBase().getLocation().getX() * MOVE_CONSTANT); + this.timer_initY = Math.round(teleportUser.getBase().getLocation().getY() * MOVE_CONSTANT); + this.timer_initZ = Math.round(teleportUser.getBase().getLocation().getZ() * MOVE_CONSTANT); + this.timer_teleportee = teleportUser.getBase().getUniqueId(); + this.timer_teleportTarget = target; + this.timer_chargeFor = chargeFor; + this.timer_cause = cause; + this.timer_respawn = respawn; + this.timer_canMove = user.isAuthorized("essentials.teleport.timer.move"); + + timer_task = ess.runTaskTimerAsynchronously(this, 20, 20).getTaskId(); + } + + @Override + public void run() { + + if (teleportOwner == null || !teleportOwner.getBase().isOnline() || teleportOwner.getBase().getLocation() == null) { + cancelTimer(false); + return; + } + + final IUser teleportUser = ess.getUser(this.timer_teleportee); + + if (teleportUser == null || !teleportUser.getBase().isOnline()) { + cancelTimer(false); + return; + } + + final Location currLocation = teleportUser.getBase().getLocation(); + if (currLocation == null) { + cancelTimer(false); + return; + } + + if (!timer_canMove && (Math.round(currLocation.getX() * MOVE_CONSTANT) != timer_initX || Math.round(currLocation.getY() * MOVE_CONSTANT) != timer_initY || Math.round(currLocation.getZ() * MOVE_CONSTANT) != timer_initZ || teleportUser.getBase().getHealth() < timer_health)) { + // user moved, cancelTimer teleportPlayer + cancelTimer(true); + return; + } + + class DelayedTeleportTask implements Runnable { + @Override + public void run() { + + timer_health = teleportUser.getBase().getHealth(); // in case user healed, then later gets injured + final long now = System.currentTimeMillis(); + if (now > timer_started + timer_delay) { + try { + teleport.cooldown(false); + } catch (Throwable ex) { + teleportOwner.sendMessage(tl("cooldownWithMessage", ex.getMessage())); + if (teleportOwner != teleportUser) { + teleportUser.sendMessage(tl("cooldownWithMessage", ex.getMessage())); + } + } + try { + cancelTimer(false); + teleportUser.sendMessage(tl("teleportationCommencing")); + + CompletableFuture future = new CompletableFuture<>(); + future.exceptionally(e -> { + ess.showError(teleportOwner.getSource(), e, "\\ teleport"); + return false; + }); + if (timer_chargeFor != null) { + timer_chargeFor.isAffordableFor(teleportOwner); + } + if (timer_respawn) { + teleport.respawnNow(teleportUser, timer_cause, future); + } else { + teleport.nowAsync(teleportUser, timer_teleportTarget, timer_cause, future); + } + future.thenAccept(success -> { + if (timer_chargeFor != null) { + try { + timer_chargeFor.charge(teleportOwner); + } catch (ChargeException ex) { + ess.showError(teleportOwner.getSource(), ex, "\\ teleport"); + } + } + }); + + } catch (Exception ex) { + ess.showError(teleportOwner.getSource(), ex, "\\ teleport"); + } + } + } + } + ess.scheduleSyncDelayedTask(new DelayedTeleportTask()); + } + + //If we need to cancelTimer a pending teleportPlayer call this method + void cancelTimer(boolean notifyUser) { + if (timer_task == -1) { + return; + } + try { + ess.getServer().getScheduler().cancelTask(timer_task); + if (notifyUser) { + teleportOwner.sendMessage(tl("pendingTeleportCancelled")); + if (timer_teleportee != null && !timer_teleportee.equals(teleportOwner.getBase().getUniqueId())) { + ess.getUser(timer_teleportee).sendMessage(tl("pendingTeleportCancelled")); + } + } + } finally { + timer_task = -1; + } + } +} diff --git a/Essentials/src/com/earth2me/essentials/IUser.java b/Essentials/src/com/earth2me/essentials/IUser.java index c931b8ac6..1d334e17a 100644 --- a/Essentials/src/com/earth2me/essentials/IUser.java +++ b/Essentials/src/com/earth2me/essentials/IUser.java @@ -1,5 +1,6 @@ package com.earth2me.essentials; +import com.earth2me.essentials.api.IAsyncTeleport; import com.earth2me.essentials.commands.IEssentialsCommand; import net.ess3.api.ITeleport; import net.ess3.api.MaxMoneyException; @@ -54,8 +55,14 @@ public interface IUser { */ boolean hasOutstandingTeleportRequest(); + /** + * @deprecated This API is not asynchronous. Use {@link com.earth2me.essentials.api.IAsyncTeleport IAsyncTeleport} with {@link IUser#getAsyncTeleport()} + */ + @Deprecated ITeleport getTeleport(); + IAsyncTeleport getAsyncTeleport(); + BigDecimal getMoney(); void setMoney(final BigDecimal value) throws MaxMoneyException; diff --git a/Essentials/src/com/earth2me/essentials/Jails.java b/Essentials/src/com/earth2me/essentials/Jails.java index 17864d3c5..b14d87f89 100644 --- a/Essentials/src/com/earth2me/essentials/Jails.java +++ b/Essentials/src/com/earth2me/essentials/Jails.java @@ -22,6 +22,7 @@ import org.bukkit.plugin.PluginManager; import java.io.File; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.logging.Level; import java.util.logging.Logger; @@ -116,7 +117,11 @@ public class Jails extends AsyncStorageObjectHolder future) throws Exception { + acquireReadLock(); + try { + if (user.getBase().isOnline()) { + Location loc = getJail(jail); + user.getAsyncTeleport().now(loc, false, TeleportCause.COMMAND, future); + future.thenAccept(success -> { + user.setJail(jail); + }); + return; + } + user.setJail(jail); + } finally { + unlock(); + } + } + @Override public void setJail(final String jailName, final Location loc) throws Exception { acquireWriteLock(); @@ -254,16 +277,22 @@ public class Jails extends AsyncStorageObjectHolder future = new CompletableFuture<>(); + future.exceptionally(ex -> { if (ess.getSettings().isDebug()) { LOGGER.log(Level.INFO, tl("returnPlayerToJailError", user.getName(), ex.getLocalizedMessage()), ex); } else { LOGGER.log(Level.INFO, tl("returnPlayerToJailError", user.getName(), ex.getLocalizedMessage())); } + return false; + }); + future.thenAccept(success -> user.sendMessage(tl("jailMessage"))); + + try { + sendToJail(user, user.getJail(), future); + } catch (Exception ex) { + future.completeExceptionally(ex); } - user.sendMessage(tl("jailMessage")); } } } diff --git a/Essentials/src/com/earth2me/essentials/Teleport.java b/Essentials/src/com/earth2me/essentials/Teleport.java index bab29fb1f..d458c140f 100644 --- a/Essentials/src/com/earth2me/essentials/Teleport.java +++ b/Essentials/src/com/earth2me/essentials/Teleport.java @@ -21,6 +21,10 @@ import java.util.GregorianCalendar; import static com.earth2me.essentials.I18n.tl; +/** + * @deprecated This API is not asynchronous. Use {@link com.earth2me.essentials.AsyncTeleport AsyncTeleport} + */ +@Deprecated public class Teleport implements ITeleport { private final IUser teleportOwner; private final IEssentials ess; @@ -28,6 +32,7 @@ public class Teleport implements ITeleport { private TeleportType tpType; + @Deprecated public Teleport(IUser user, IEssentials ess) { this.teleportOwner = user; this.ess = ess; @@ -40,6 +45,7 @@ public class Teleport implements ITeleport { NORMAL } + @Deprecated public void cooldown(boolean check) throws Exception { final Calendar time = new GregorianCalendar(); if (teleportOwner.getLastTeleportTimestamp() > 0) { @@ -73,6 +79,7 @@ public class Teleport implements ITeleport { } } + @Deprecated private boolean cooldownApplies() { boolean applies = true; String globalBypassPerm = "essentials.teleport.cooldown.bypass"; @@ -92,6 +99,7 @@ public class Teleport implements ITeleport { return applies; } + @Deprecated private void warnUser(final IUser user, final double delay) { Calendar c = new GregorianCalendar(); c.add(Calendar.SECOND, (int) delay); @@ -101,6 +109,7 @@ public class Teleport implements ITeleport { //The now function is used when you want to skip tp delay when teleporting someone to a location or player. @Override + @Deprecated public void now(Location loc, boolean cooldown, TeleportCause cause) throws Exception { if (cooldown) { cooldown(false); @@ -110,6 +119,7 @@ public class Teleport implements ITeleport { } @Override + @Deprecated public void now(Player entity, boolean cooldown, TeleportCause cause) throws Exception { if (cooldown) { cooldown(false); @@ -119,6 +129,7 @@ public class Teleport implements ITeleport { teleportOwner.sendMessage(tl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ())); } + @Deprecated protected void now(IUser teleportee, ITarget target, TeleportCause cause) throws Exception { cancel(false); Location loc = target.getLocation(); @@ -171,12 +182,14 @@ public class Teleport implements ITeleport { } @Override + @Deprecated public void teleport(Location loc, Trade chargeFor, TeleportCause cause) throws Exception { teleport(teleportOwner, new LocationTarget(loc), chargeFor, cause); } //This is used when teleporting to a player @Override + @Deprecated public void teleport(Player entity, Trade chargeFor, TeleportCause cause) throws Exception { ITarget target = new PlayerTarget(entity); teleportOwner.sendMessage(tl("teleportToPlayer", entity.getDisplayName())); @@ -185,12 +198,14 @@ public class Teleport implements ITeleport { //This is used when teleporting to stored location @Override + @Deprecated public void teleportPlayer(IUser teleportee, Location loc, Trade chargeFor, TeleportCause cause) throws Exception { teleport(teleportee, new LocationTarget(loc), chargeFor, cause); } //This is used on /tphere @Override + @Deprecated public void teleportPlayer(IUser teleportee, Player entity, Trade chargeFor, TeleportCause cause) throws Exception { ITarget target = new PlayerTarget(entity); teleport(teleportee, target, chargeFor, cause); @@ -198,6 +213,7 @@ public class Teleport implements ITeleport { teleportOwner.sendMessage(tl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ())); } + @Deprecated private void teleport(IUser teleportee, ITarget target, Trade chargeFor, TeleportCause cause) throws Exception { double delay = ess.getSettings().getTeleportDelay(); @@ -228,6 +244,7 @@ public class Teleport implements ITeleport { initTimer((long) (delay * 1000.0), teleportee, target, cashCharge, cause, false); } + @Deprecated private void teleportOther(IUser teleporter, IUser teleportee, ITarget target, Trade chargeFor, TeleportCause cause) throws Exception { double delay = ess.getSettings().getTeleportDelay(); @@ -263,6 +280,7 @@ public class Teleport implements ITeleport { //The respawn function is a wrapper used to handle tp fallback, on /jail and /home @Override + @Deprecated public void respawn(final Trade chargeFor, TeleportCause cause) throws Exception { double delay = ess.getSettings().getTeleportDelay(); if (chargeFor != null) { @@ -283,6 +301,7 @@ public class Teleport implements ITeleport { initTimer((long) (delay * 1000.0), teleportOwner, null, chargeFor, cause, true); } + @Deprecated void respawnNow(IUser teleportee, TeleportCause cause) throws Exception { final Player player = teleportee.getBase(); Location bed = player.getBedSpawnLocation(); @@ -300,6 +319,7 @@ public class Teleport implements ITeleport { //The warp function is a wrapper used to teleportPlayer a player to a /warp @Override + @Deprecated public void warp(IUser teleportee, String warp, Trade chargeFor, TeleportCause cause) throws Exception { UserWarpEvent event = new UserWarpEvent(teleportee, warp, chargeFor); Bukkit.getServer().getPluginManager().callEvent(event); @@ -318,12 +338,14 @@ public class Teleport implements ITeleport { //The back function is a wrapper used to teleportPlayer a player /back to their previous location. @Override + @Deprecated public void back(Trade chargeFor) throws Exception { back(teleportOwner, chargeFor); } //This function is a wrapper over the other back function for cases where another player performs back for them @Override + @Deprecated public void back(IUser teleporter, Trade chargeFor) throws Exception { tpType = TeleportType.BACK; final Location loc = teleportOwner.getLastLocation(); @@ -333,15 +355,18 @@ public class Teleport implements ITeleport { //This function is used to throw a user back after a jail sentence @Override + @Deprecated public void back() throws Exception { now(teleportOwner, new LocationTarget(teleportOwner.getLastLocation()), TeleportCause.COMMAND); } + @Deprecated public void setTpType(TeleportType tpType) { this.tpType = tpType; } //If we need to cancelTimer a pending teleportPlayer call this method + @Deprecated private void cancel(boolean notifyUser) { if (timedTeleport != null) { timedTeleport.cancelTimer(notifyUser); @@ -349,6 +374,7 @@ public class Teleport implements ITeleport { } } + @Deprecated private void initTimer(long delay, IUser teleportUser, ITarget target, Trade chargeFor, TeleportCause cause, boolean respawn) { timedTeleport = new TimedTeleport(teleportOwner, ess, this, delay, teleportUser, target, chargeFor, cause, respawn); } diff --git a/Essentials/src/com/earth2me/essentials/TimedTeleport.java b/Essentials/src/com/earth2me/essentials/TimedTeleport.java index c3e959f58..6b8a31ec4 100644 --- a/Essentials/src/com/earth2me/essentials/TimedTeleport.java +++ b/Essentials/src/com/earth2me/essentials/TimedTeleport.java @@ -98,19 +98,17 @@ public class TimedTeleport implements Runnable { cancelTimer(false); teleportUser.sendMessage(tl("teleportationCommencing")); - try { - if (timer_chargeFor != null) { - timer_chargeFor.isAffordableFor(teleportOwner); - } - if (timer_respawn) { - teleport.respawnNow(teleportUser, timer_cause); - } else { - teleport.now(teleportUser, timer_teleportTarget, timer_cause); - } - if (timer_chargeFor != null) { - timer_chargeFor.charge(teleportOwner); - } - } catch (Exception ignored) {} + if (timer_chargeFor != null) { + timer_chargeFor.isAffordableFor(teleportOwner); + } + if (timer_respawn) { + teleport.respawnNow(teleportUser, timer_cause); + } else { + teleport.now(teleportUser, timer_teleportTarget, timer_cause); + } + if (timer_chargeFor != null) { + timer_chargeFor.charge(teleportOwner); + } } catch (Exception ex) { ess.showError(teleportOwner.getSource(), ex, "\\ teleport"); diff --git a/Essentials/src/com/earth2me/essentials/Trade.java b/Essentials/src/com/earth2me/essentials/Trade.java index e003dccac..b364867a6 100644 --- a/Essentials/src/com/earth2me/essentials/Trade.java +++ b/Essentials/src/com/earth2me/essentials/Trade.java @@ -18,6 +18,8 @@ import java.text.DateFormat; import java.util.Date; import java.util.Locale; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; @@ -81,26 +83,43 @@ public class Trade { } public void isAffordableFor(final IUser user) throws ChargeException { + CompletableFuture future = new CompletableFuture<>(); + isAffordableFor(user, future); + if (future.isCompletedExceptionally()) { + try { + future.get(); + } catch (InterruptedException e) { //If this happens, we have bigger problems... + e.printStackTrace(); + } catch (ExecutionException e) { + throw (ChargeException) e.getCause(); + } + } + } + + public void isAffordableFor(final IUser user, CompletableFuture future) { if (ess.getSettings().isDebug()) { ess.getLogger().log(Level.INFO, "checking if " + user.getName() + " can afford charge."); } if (getMoney() != null && getMoney().signum() > 0 && !user.canAfford(getMoney())) { - throw new ChargeException(tl("notEnoughMoney", NumberUtil.displayCurrency(getMoney(), ess))); + future.completeExceptionally(new ChargeException(tl("notEnoughMoney", NumberUtil.displayCurrency(getMoney(), ess)))); + return; } if (getItemStack() != null && !user.getBase().getInventory().containsAtLeast(itemStack, itemStack.getAmount())) { - throw new ChargeException(tl("missingItems", getItemStack().getAmount(), ess.getItemDb().name(getItemStack()))); + future.completeExceptionally(new ChargeException(tl("missingItems", getItemStack().getAmount(), ess.getItemDb().name(getItemStack())))); + return; } BigDecimal money; if (command != null && !command.isEmpty() && (money = getCommandCost(user)).signum() > 0 && !user.canAfford(money)) { - throw new ChargeException(tl("notEnoughMoney", NumberUtil.displayCurrency(money, ess))); + future.completeExceptionally(new ChargeException(tl("notEnoughMoney", NumberUtil.displayCurrency(money, ess)))); + return; } if (exp != null && exp > 0 && SetExpFix.getTotalExperience(user.getBase()) < exp) { - throw new ChargeException(tl("notEnoughExperience")); + future.completeExceptionally(new ChargeException(tl("notEnoughExperience"))); } } @@ -175,6 +194,20 @@ public class Trade { } public void charge(final IUser user) throws ChargeException { + CompletableFuture future = new CompletableFuture<>(); + charge(user, future); + if (future.isCompletedExceptionally()) { + try { + future.get(); + } catch (InterruptedException e) { //If this happens, we have bigger problems... + e.printStackTrace(); + } catch (ExecutionException e) { + throw (ChargeException) e.getCause(); + } + } + } + + public void charge(final IUser user, CompletableFuture future) { if (ess.getSettings().isDebug()) { ess.getLogger().log(Level.INFO, "attempting to charge user " + user.getName()); } @@ -183,7 +216,8 @@ public class Trade { ess.getLogger().log(Level.INFO, "charging user " + user.getName() + " money " + getMoney().toPlainString()); } if (!user.canAfford(getMoney()) && getMoney().signum() > 0) { - throw new ChargeException(tl("notEnoughMoney", NumberUtil.displayCurrency(getMoney(), ess))); + future.completeExceptionally(new ChargeException(tl("notEnoughMoney", NumberUtil.displayCurrency(getMoney(), ess)))); + return; } user.takeMoney(getMoney()); } @@ -192,7 +226,8 @@ public class Trade { ess.getLogger().log(Level.INFO, "charging user " + user.getName() + " itemstack " + getItemStack().toString()); } if (!user.getBase().getInventory().containsAtLeast(getItemStack(), getItemStack().getAmount())) { - throw new ChargeException(tl("missingItems", getItemStack().getAmount(), getItemStack().getType().toString().toLowerCase(Locale.ENGLISH).replace("_", " "))); + future.completeExceptionally(new ChargeException(tl("missingItems", getItemStack().getAmount(), getItemStack().getType().toString().toLowerCase(Locale.ENGLISH).replace("_", " ")))); + return; } user.getBase().getInventory().removeItem(getItemStack()); user.getBase().updateInventory(); @@ -200,7 +235,8 @@ public class Trade { if (command != null) { final BigDecimal cost = getCommandCost(user); if (!user.canAfford(cost) && cost.signum() > 0) { - throw new ChargeException(tl("notEnoughMoney", NumberUtil.displayCurrency(cost, ess))); + future.completeExceptionally(new ChargeException(tl("notEnoughMoney", NumberUtil.displayCurrency(cost, ess)))); + return; } user.takeMoney(cost); } @@ -210,7 +246,8 @@ public class Trade { } final int experience = SetExpFix.getTotalExperience(user.getBase()); if (experience < getExperience() && getExperience() > 0) { - throw new ChargeException(tl("notEnoughExperience")); + future.completeExceptionally(new ChargeException(tl("notEnoughExperience"))); + return; } SetExpFix.setTotalExperience(user.getBase(), experience - getExperience()); } diff --git a/Essentials/src/com/earth2me/essentials/User.java b/Essentials/src/com/earth2me/essentials/User.java index 1d34e5519..a8660c4ff 100644 --- a/Essentials/src/com/earth2me/essentials/User.java +++ b/Essentials/src/com/earth2me/essentials/User.java @@ -16,7 +16,6 @@ import net.ess3.api.events.JailStatusChangeEvent; import net.ess3.api.events.MuteStatusChangeEvent; import net.ess3.api.events.UserBalanceUpdateEvent; import net.ess3.nms.refl.ReflUtil; - import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Material; @@ -29,6 +28,7 @@ import org.bukkit.potion.PotionEffectType; import java.math.BigDecimal; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.logging.Level; import java.util.logging.Logger; @@ -42,7 +42,8 @@ public class User extends UserData implements Comparable, IMessageRecipien private transient boolean teleportRequestHere; private transient Location teleportLocation; private transient boolean vanished; - private transient final Teleport teleport; + private transient final AsyncTeleport teleport; + private transient final Teleport legacyTeleport; private transient long teleportRequestTime; private transient long lastOnlineActivity; private transient long lastThrottledAction; @@ -63,7 +64,8 @@ public class User extends UserData implements Comparable, IMessageRecipien public User(final Player base, final IEssentials ess) { super(base, ess); - teleport = new Teleport(this, ess); + teleport = new AsyncTeleport(this, ess); + legacyTeleport = new Teleport(this, ess); if (isAfk()) { afkPosition = this.getLocation(); } @@ -401,10 +403,19 @@ public class User extends UserData implements Comparable, IMessageRecipien } @Override - public Teleport getTeleport() { + public AsyncTeleport getAsyncTeleport() { return teleport; } + /** + * @deprecated This API is not asynchronous. Use {@link User#getAsyncTeleport()} + */ + @Override + @Deprecated + public Teleport getTeleport() { + return legacyTeleport; + } + public long getLastOnlineActivity() { return lastOnlineActivity; } @@ -567,14 +578,12 @@ public class User extends UserData implements Comparable, IMessageRecipien sendMessage(tl("haveBeenReleased")); setJail(null); if (ess.getSettings().isTeleportBackWhenFreedFromJail()) { - try { - getTeleport().back(); - } catch (Exception ex) { - try { - getTeleport().respawn(null, TeleportCause.PLUGIN); - } catch (Exception ignored) { - } - } + CompletableFuture future = new CompletableFuture<>(); + getAsyncTeleport().back(future); + future.exceptionally(e -> { + getAsyncTeleport().respawn(null, TeleportCause.PLUGIN, new CompletableFuture<>()); + return false; + }); } return true; } diff --git a/Essentials/src/com/earth2me/essentials/api/IAsyncTeleport.java b/Essentials/src/com/earth2me/essentials/api/IAsyncTeleport.java new file mode 100644 index 000000000..7847986a3 --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/api/IAsyncTeleport.java @@ -0,0 +1,123 @@ +package com.earth2me.essentials.api; + +import com.earth2me.essentials.Trade; +import net.ess3.api.IUser; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerTeleportEvent; + +import java.util.concurrent.CompletableFuture; + + +public interface IAsyncTeleport { + + /** + * Used to skip teleportPlayer delay when teleporting someone to a location or player. + * + * @param loc - Where should the player end up + * @param cooldown - If cooldown should be enforced + * @param cause - The reported teleportPlayer cause + * @param future - Future which is completed with the success status of the execution + */ + void now(Location loc, boolean cooldown, PlayerTeleportEvent.TeleportCause cause, CompletableFuture future); + + /** + * Used to skip teleportPlayer delay when teleporting someone to a location or player. + * + * @param entity - Where should the player end up + * @param cooldown - If cooldown should be enforced + * @param cause - The reported teleportPlayer cause + * @param future - Future which is completed with the success status of the execution + */ + void now(Player entity, boolean cooldown, PlayerTeleportEvent.TeleportCause cause, CompletableFuture future); + + /** + * Teleport a player to a specific location + * + * @param loc - Where should the player end up + * @param chargeFor - What the user will be charged if teleportPlayer is successful + * @param cause - The reported teleportPlayer cause. + * @param future - Future which is completed with the success status of the execution + */ + void teleport(Location loc, Trade chargeFor, PlayerTeleportEvent.TeleportCause cause, CompletableFuture future); + + /** + * Teleport a player to a specific player + * + * @param entity - Where should the player end up + * @param chargeFor - What the user will be charged if teleportPlayer is successful + * @param cause - The reported teleportPlayer cause + * @param future - Future which is completed with the success status of the execution + */ + void teleport(Player entity, Trade chargeFor, PlayerTeleportEvent.TeleportCause cause, CompletableFuture future); + + /** + * Teleport a player to a specific location + * + * @param otherUser - Which user will be teleported + * @param loc - Where should the player end up + * @param chargeFor - What the user will be charged if teleportPlayer is successful + * @param cause - The reported teleportPlayer cause + * @param future - Future which is completed with the success status of the execution + */ + void teleportPlayer(IUser otherUser, Location loc, Trade chargeFor, PlayerTeleportEvent.TeleportCause cause, CompletableFuture future); + + /** + * Teleport a player to a specific player + * + * @param otherUser - Which user will be teleported + * @param entity - Where should the player end up + * @param chargeFor - What the user will be charged if teleportPlayer is successful + * @param cause - The reported teleportPlayer cause + * @param future - Future which is completed with the success status of the execution + */ + void teleportPlayer(IUser otherUser, Player entity, Trade chargeFor, PlayerTeleportEvent.TeleportCause cause, CompletableFuture future); + + /** + * Teleport wrapper used to handle tp fallback on /jail and /home + * + * @param chargeFor - What the user will be charged if teleportPlayer is successful + * @param cause - The reported teleportPlayer cause + * @param future - Future which is completed with the success status of the execution + */ + void respawn(final Trade chargeFor, PlayerTeleportEvent.TeleportCause cause, CompletableFuture future); + + /** + * Teleport wrapper used to handle /warp teleports + * + * @param otherUser - Which user will be teleported + * @param warp - The name of the warp the user will be teleported too. + * @param chargeFor - What the user will be charged if teleportPlayer is successful + * @param cause - The reported teleportPlayer cause + * @param future - Future which is completed with the success status of the execution + */ + void warp(IUser otherUser, String warp, Trade chargeFor, PlayerTeleportEvent.TeleportCause cause, CompletableFuture future); + + /** + * Teleport wrapper used to handle /back teleports + * + * @param chargeFor - What the user will be charged if teleportPlayer is successful + * @param future - Future which is completed with the success status of the execution + */ + void back(Trade chargeFor, CompletableFuture future); + + /** + * Teleport wrapper used to handle /back teleports that + * are executed by a different player with this + * instance of teleport as a target. + * + * @param teleporter - The user performing the /back command. + * This value may be {@code null} to indicate console. + * @param chargeFor - What the {@code teleporter} will be charged if teleportPlayer is successful + * @param future - Future which is completed with the success status of the execution + */ + void back(IUser teleporter, Trade chargeFor, CompletableFuture future); + + /** + * Teleport wrapper used to handle throwing user home after a jail sentence + * + * @param future - Future which is completed with the success status of the execution + */ + void back(CompletableFuture future); + +} diff --git a/Essentials/src/com/earth2me/essentials/api/IJails.java b/Essentials/src/com/earth2me/essentials/api/IJails.java index b065c9951..b776faa19 100644 --- a/Essentials/src/com/earth2me/essentials/api/IJails.java +++ b/Essentials/src/com/earth2me/essentials/api/IJails.java @@ -4,6 +4,7 @@ import net.ess3.api.IUser; import org.bukkit.Location; import java.util.Collection; +import java.util.concurrent.CompletableFuture; public interface IJails extends IReload { @@ -46,13 +47,27 @@ public interface IJails extends IReload { /** * Attempts to send the given user to the given jail * + * @deprecated Use {@link IJails#sendToJail(IUser, String, CompletableFuture)} + * * @param user the user to send to jail * @param jail the jail to send the user to * * @throws Exception if the user is offline or jail does not exist */ + @Deprecated void sendToJail(IUser user, String jail) throws Exception; + /** + * Attempts to send the given user to the given jail + * + * @param user the user to send to jail + * @param jail the jail to send the user to + * @param future Future which is completed with the success status of the execution + * + * @throws Exception if the user is offline or jail does not exist + */ + void sendToJail(IUser user, String jail, CompletableFuture future) throws Exception; + /** * Set a new jail with the given name and location * diff --git a/Essentials/src/com/earth2me/essentials/api/ITeleport.java b/Essentials/src/com/earth2me/essentials/api/ITeleport.java index 1ebad87e5..5993b2a8a 100644 --- a/Essentials/src/com/earth2me/essentials/api/ITeleport.java +++ b/Essentials/src/com/earth2me/essentials/api/ITeleport.java @@ -7,6 +7,9 @@ import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerTeleportEvent; +/** + * @deprecated This API is not asynchronous. Use {@link com.earth2me.essentials.api.IAsyncTeleport IAsyncTeleport} + */ public interface ITeleport { /** * Used to skip teleportPlayer delay when teleporting someone to a location or player. @@ -17,6 +20,7 @@ public interface ITeleport { * * @throws Exception */ + @Deprecated void now(Location loc, boolean cooldown, PlayerTeleportEvent.TeleportCause cause) throws Exception; /** @@ -28,6 +32,7 @@ public interface ITeleport { * * @throws Exception */ + @Deprecated void now(Player entity, boolean cooldown, PlayerTeleportEvent.TeleportCause cause) throws Exception; @Deprecated @@ -42,6 +47,7 @@ public interface ITeleport { * * @throws Exception */ + @Deprecated void teleport(Location loc, Trade chargeFor, PlayerTeleportEvent.TeleportCause cause) throws Exception; /** @@ -53,6 +59,7 @@ public interface ITeleport { * * @throws Exception */ + @Deprecated void teleport(Player entity, Trade chargeFor, PlayerTeleportEvent.TeleportCause cause) throws Exception; /** @@ -65,6 +72,7 @@ public interface ITeleport { * * @throws Exception */ + @Deprecated void teleportPlayer(IUser otherUser, Location loc, Trade chargeFor, PlayerTeleportEvent.TeleportCause cause) throws Exception; /** @@ -77,6 +85,7 @@ public interface ITeleport { * * @throws Exception */ + @Deprecated void teleportPlayer(IUser otherUser, Player entity, Trade chargeFor, PlayerTeleportEvent.TeleportCause cause) throws Exception; /** @@ -87,6 +96,7 @@ public interface ITeleport { * * @throws Exception */ + @Deprecated void respawn(final Trade chargeFor, PlayerTeleportEvent.TeleportCause cause) throws Exception; /** @@ -99,6 +109,7 @@ public interface ITeleport { * * @throws Exception */ + @Deprecated void warp(IUser otherUser, String warp, Trade chargeFor, PlayerTeleportEvent.TeleportCause cause) throws Exception; /** @@ -108,6 +119,7 @@ public interface ITeleport { * * @throws Exception */ + @Deprecated void back(Trade chargeFor) throws Exception; /** @@ -117,10 +129,11 @@ public interface ITeleport { * * @param teleporter - The user performing the /back command. * This value may be {@code null} to indicate console. - * @param chargeFor - What the {@code teleporter} will be charged if teleportPlayer is successful + * @param chargeFor - What the {@code teleporter} will be charged if teleportPlayer is successful * * @throws Exception */ + @Deprecated void back(IUser teleporter, Trade chargeFor) throws Exception; /** @@ -128,6 +141,7 @@ public interface ITeleport { * * @throws Exception */ + @Deprecated void back() throws Exception; } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandback.java b/Essentials/src/com/earth2me/essentials/commands/Commandback.java index e8171359b..7116619ee 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandback.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandback.java @@ -23,11 +23,11 @@ public class Commandback extends EssentialsCommand { protected void run(Server server, User user, String commandLabel, String[] args) throws Exception { CommandSource sender = user.getSource(); if (args.length > 0 && user.isAuthorized("essentials.back.others")) { - this.parseCommand(server, sender, args, true); + this.parseCommand(server, sender, args, true, commandLabel); return; } - teleportBack(sender, user); + teleportBack(sender, user, commandLabel); } @Override @@ -36,10 +36,10 @@ public class Commandback extends EssentialsCommand { throw new NotEnoughArgumentsException(); } - this.parseCommand(server, sender, args, true); + this.parseCommand(server, sender, args, true, commandLabel); } - private void parseCommand(Server server, CommandSource sender, String[] args, boolean allowOthers) throws Exception { + private void parseCommand(Server server, CommandSource sender, String[] args, boolean allowOthers, String commandLabel) throws Exception { Collection players = new ArrayList<>(); if (allowOthers && args.length > 0 && args[0].trim().length() > 2) { @@ -52,11 +52,11 @@ public class Commandback extends EssentialsCommand { for (Player player : players) { sender.sendMessage(tl("backOther", player.getName())); - teleportBack(sender, ess.getUser(player)); + teleportBack(sender, ess.getUser(player), commandLabel); } } - private void teleportBack(CommandSource sender, User user) throws Exception { + private void teleportBack(CommandSource sender, User user, String commandLabel) throws Exception { if (user.getLastLocation() == null) { throw new Exception(tl("noLocationFound")); } @@ -77,15 +77,15 @@ public class Commandback extends EssentialsCommand { } if (requester == null) { - user.getTeleport().back(null, null); + user.getAsyncTeleport().back(null, null, getNewExceptionFuture(sender, commandLabel)); } else if (!requester.equals(user)) { Trade charge = new Trade(this.getName(), this.ess); charge.isAffordableFor(requester); - user.getTeleport().back(requester, charge); + user.getAsyncTeleport().back(requester, charge, getNewExceptionFuture(sender, commandLabel)); } else { Trade charge = new Trade(this.getName(), this.ess); charge.isAffordableFor(user); - user.getTeleport().back(charge); + user.getAsyncTeleport().back(charge, getNewExceptionFuture(sender, commandLabel)); } throw new NoChargeException(); } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandhome.java b/Essentials/src/com/earth2me/essentials/commands/Commandhome.java index 38e9baa1b..32e6fa185 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandhome.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandhome.java @@ -3,6 +3,7 @@ package com.earth2me.essentials.commands; import com.earth2me.essentials.Trade; import com.earth2me.essentials.User; import com.earth2me.essentials.utils.StringUtil; +import io.papermc.lib.PaperLib; import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; @@ -10,6 +11,7 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.concurrent.CompletableFuture; import static com.earth2me.essentials.I18n.tl; @@ -39,41 +41,56 @@ public class Commandhome extends EssentialsCommand { } try { if ("bed".equalsIgnoreCase(homeName) && user.isAuthorized("essentials.home.bed")) { - final Location bed = player.getBase().getBedSpawnLocation(); - if (bed != null) { - user.getTeleport().teleport(bed, charge, TeleportCause.COMMAND); - throw new NoChargeException(); - } else { - throw new Exception(tl("bedMissing")); + if (!player.getBase().isOnline()) { + throw new Exception(tl("bedOffline")); } - } - goHome(user, player, homeName.toLowerCase(Locale.ENGLISH), charge); - } catch (NotEnoughArgumentsException e) { - Location bed = player.getBase().getBedSpawnLocation(); - final List homes = player.getHomes(); - if (homes.isEmpty() && player.equals(user)) { - if (ess.getSettings().isSpawnIfNoHome()) { - user.getTeleport().respawn(charge, TeleportCause.COMMAND); - } else { - throw new Exception(tl("noHomeSetPlayer")); - } - } else if (homes.isEmpty()) { - throw new Exception(tl("noHomeSetPlayer")); - } else if (homes.size() == 1 && player.equals(user)) { - goHome(user, player, homes.get(0), charge); - } else { - final int count = homes.size(); - if (user.isAuthorized("essentials.home.bed")) { - if (bed != null) { - homes.add(tl("bed")); + PaperLib.getBedSpawnLocationAsync(player.getBase(), true).thenAccept(location -> { + CompletableFuture future = getNewExceptionFuture(user.getSource(), commandLabel); + if (location != null) { + future.thenAccept(success -> { + if (success) { + user.sendMessage(tl("teleportHome", "bed")); + } + }); + user.getAsyncTeleport().teleport(location, charge, TeleportCause.COMMAND, future); } else { - homes.add(tl("bedNull")); + showError(user.getBase(), new Exception(tl("bedMissing")), commandLabel); } - } - user.sendMessage(tl("homes", StringUtil.joinList(homes), count, getHomeLimit(player))); + }); + return; } + goHome(user, player, homeName.toLowerCase(Locale.ENGLISH), charge, getNewExceptionFuture(user.getSource(), commandLabel)); + } catch (NotEnoughArgumentsException e) { + final User finalPlayer = player; + PaperLib.getBedSpawnLocationAsync(player.getBase(), true).thenAccept(bed -> { + final List homes = finalPlayer.getHomes(); + if (homes.isEmpty() && finalPlayer.equals(user)) { + if (ess.getSettings().isSpawnIfNoHome()) { + user.getAsyncTeleport().respawn(charge, TeleportCause.COMMAND, getNewExceptionFuture(user.getSource(), commandLabel)); + } else { + showError(user.getBase(), new Exception(tl("noHomeSetPlayer")), commandLabel); + } + } else if (homes.isEmpty()) { + showError(user.getBase(), new Exception(tl("noHomeSetPlayer")), commandLabel); + } else if (homes.size() == 1 && finalPlayer.equals(user)) { + try { + goHome(user, finalPlayer, homes.get(0), charge, getNewExceptionFuture(user.getSource(), commandLabel)); + } catch (Exception exception) { + showError(user.getBase(), exception, commandLabel); + } + } else { + final int count = homes.size(); + if (user.isAuthorized("essentials.home.bed")) { + if (bed != null) { + homes.add(tl("bed")); + } else { + homes.add(tl("bedNull")); + } + } + user.sendMessage(tl("homes", StringUtil.joinList(homes), count, getHomeLimit(finalPlayer))); + } + }); } - throw new NoChargeException(); } private String getHomeLimit(final User player) { @@ -86,7 +103,7 @@ public class Commandhome extends EssentialsCommand { return Integer.toString(ess.getSettings().getHomeLimit(player)); } - private void goHome(final User user, final User player, final String home, final Trade charge) throws Exception { + private void goHome(final User user, final User player, final String home, final Trade charge, CompletableFuture future) throws Exception { if (home.length() < 1) { throw new NotEnoughArgumentsException(); } @@ -97,8 +114,12 @@ public class Commandhome extends EssentialsCommand { if (user.getWorld() != loc.getWorld() && ess.getSettings().isWorldHomePermissions() && !user.isAuthorized("essentials.worlds." + loc.getWorld().getName())) { throw new Exception(tl("noPerm", "essentials.worlds." + loc.getWorld().getName())); } - user.getTeleport().teleport(loc, charge, TeleportCause.COMMAND); - user.sendMessage(tl("teleportHome", home)); + user.getAsyncTeleport().teleport(loc, charge, TeleportCause.COMMAND, future); + future.thenAccept(success -> { + if (success) { + user.sendMessage(tl("teleportHome", home)); + } + }); } @Override diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandjump.java b/Essentials/src/com/earth2me/essentials/commands/Commandjump.java index 046fd61a8..82d71b025 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandjump.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandjump.java @@ -46,8 +46,7 @@ public class Commandjump extends EssentialsCommand { final Trade charge = new Trade(this.getName(), ess); charge.isAffordableFor(user); - user.getTeleport().teleport(loc, charge, TeleportCause.COMMAND); - throw new NoChargeException(); + user.getAsyncTeleport().teleport(loc, charge, TeleportCause.COMMAND, getNewExceptionFuture(user.getSource(), commandLabel)); } @Override diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandtogglejail.java b/Essentials/src/com/earth2me/essentials/commands/Commandtogglejail.java index 080f66592..85af0c1a3 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandtogglejail.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandtogglejail.java @@ -9,6 +9,7 @@ import org.bukkit.Server; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; import static com.earth2me.essentials.I18n.tl; @@ -43,23 +44,33 @@ public class Commandtogglejail extends EssentialsCommand { ess.getServer().getPluginManager().callEvent(event); if (!event.isCancelled()) { + long preTimeDiff = 0; + if (args.length > 2) { + final String time = getFinalArg(args, 2); + preTimeDiff = DateUtil.parseDateDiff(time, true); + + } + final long timeDiff = preTimeDiff; + CompletableFuture future = getNewExceptionFuture(sender, commandLabel); + future.thenAccept(success -> { + if (success) { + player.setJailed(true); + player.sendMessage(tl("userJailed")); + player.setJail(null); + player.setJail(args[1]); + if (args.length > 2) { + player.setJailTimeout(timeDiff); + } + sender.sendMessage((timeDiff > 0 ? tl("playerJailedFor", player.getName(), DateUtil.formatDateDiff(timeDiff)) : tl("playerJailed", player.getName()))); + } + }); if (player.getBase().isOnline()) { - ess.getJails().sendToJail(player, args[1]); + ess.getJails().sendToJail(player, args[1], future); } else { // Check if jail exists ess.getJails().getJail(args[1]); + future.complete(true); } - player.setJailed(true); - player.sendMessage(tl("userJailed")); - player.setJail(null); - player.setJail(args[1]); - long timeDiff = 0; - if (args.length > 2) { - final String time = getFinalArg(args, 2); - timeDiff = DateUtil.parseDateDiff(time, true); - player.setJailTimeout(timeDiff); - } - sender.sendMessage((timeDiff > 0 ? tl("playerJailedFor", player.getName(), DateUtil.formatDateDiff(timeDiff)) : tl("playerJailed", player.getName()))); } return; } @@ -91,7 +102,14 @@ public class Commandtogglejail extends EssentialsCommand { player.sendMessage(tl("jailReleasedPlayerNotify")); player.setJail(null); if (player.getBase().isOnline()) { - player.getTeleport().back(); + CompletableFuture future = getNewExceptionFuture(sender, commandLabel); + player.getAsyncTeleport().back(future); + future.thenAccept(success -> { + if (success) { + sender.sendMessage(tl("jailReleased", player.getName())); + } + }); + return; } sender.sendMessage(tl("jailReleased", player.getName())); } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandtop.java b/Essentials/src/com/earth2me/essentials/commands/Commandtop.java index 6b60d2de6..47a17e029 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandtop.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandtop.java @@ -7,6 +7,8 @@ import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import java.util.concurrent.CompletableFuture; + import static com.earth2me.essentials.I18n.tl; @@ -22,8 +24,12 @@ public class Commandtop extends EssentialsCommand { final float pitch = user.getLocation().getPitch(); final float yaw = user.getLocation().getYaw(); final Location loc = LocationUtil.getSafeDestination(new Location(user.getWorld(), topX, user.getWorld().getMaxHeight(), topZ, yaw, pitch)); - user.getTeleport().teleport(loc, new Trade(this.getName(), ess), TeleportCause.COMMAND); - user.sendMessage(tl("teleportTop", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); - + CompletableFuture future = new CompletableFuture<>(); + future.thenAccept(success -> { + if (success) { + user.sendMessage(tl("teleportTop", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); + } + }); + user.getAsyncTeleport().teleport(loc, new Trade(this.getName(), ess), TeleportCause.COMMAND, getNewExceptionFuture(user.getSource(), commandLabel)); } } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandtp.java b/Essentials/src/com/earth2me/essentials/commands/Commandtp.java index f6272d662..3903ec8e8 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandtp.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandtp.java @@ -10,6 +10,7 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; import static com.earth2me.essentials.I18n.tl; @@ -21,6 +22,7 @@ public class Commandtp extends EssentialsCommand { @Override public void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception { + CompletableFuture future = getNewExceptionFuture(user.getSource(), commandLabel); switch (args.length) { case 0: throw new NotEnoughArgumentsException(); @@ -41,8 +43,8 @@ public class Commandtp extends EssentialsCommand { } final Trade charge = new Trade(this.getName(), ess); charge.isAffordableFor(user); - user.getTeleport().teleport(player.getBase(), charge, TeleportCause.COMMAND); - throw new NoChargeException(); + user.getAsyncTeleport().teleport(player.getBase(), charge, TeleportCause.COMMAND, future); + break; case 3: if (!user.isAuthorized("essentials.tp.position")) { throw new Exception(tl("noPerm", "essentials.tp.position")); @@ -54,8 +56,12 @@ public class Commandtp extends EssentialsCommand { throw new NotEnoughArgumentsException(tl("teleportInvalidLocation")); } final Location locpos = new Location(user.getWorld(), x2, y2, z2, user.getLocation().getYaw(), user.getLocation().getPitch()); - user.getTeleport().now(locpos, false, TeleportCause.COMMAND); - user.sendMessage(tl("teleporting", locpos.getWorld().getName(), locpos.getBlockX(), locpos.getBlockY(), locpos.getBlockZ())); + user.getAsyncTeleport().now(locpos, false, TeleportCause.COMMAND, future); + future.thenAccept(success -> { + if (success) { + user.sendMessage(tl("teleporting", locpos.getWorld().getName(), locpos.getBlockX(), locpos.getBlockY(), locpos.getBlockZ())); + } + }); break; case 4: if (!user.isAuthorized("essentials.tp.others")) { @@ -76,8 +82,12 @@ public class Commandtp extends EssentialsCommand { throw new Exception(tl("teleportDisabled", target2.getDisplayName())); } user.sendMessage(tl("teleporting", locposother.getWorld().getName(), locposother.getBlockX(), locposother.getBlockY(), locposother.getBlockZ())); - target2.getTeleport().now(locposother, false, TeleportCause.COMMAND); - target2.sendMessage(tl("teleporting", locposother.getWorld().getName(), locposother.getBlockX(), locposother.getBlockY(), locposother.getBlockZ())); + target2.getAsyncTeleport().now(locposother, false, TeleportCause.COMMAND, future); + future.thenAccept(success -> { + if (success) { + target2.sendMessage(tl("teleporting", locposother.getWorld().getName(), locposother.getBlockX(), locposother.getBlockY(), locposother.getBlockZ())); + } + }); break; case 2: default: @@ -96,7 +106,7 @@ public class Commandtp extends EssentialsCommand { throw new Exception(tl("noPerm", "essentials.worlds." + toPlayer.getWorld().getName())); } target.sendMessage(tl("teleportAtoB", user.getDisplayName(), toPlayer.getDisplayName())); - target.getTeleport().now(toPlayer.getBase(), false, TeleportCause.COMMAND); + target.getAsyncTeleport().now(toPlayer.getBase(), false, TeleportCause.COMMAND, future); break; } } @@ -111,7 +121,7 @@ public class Commandtp extends EssentialsCommand { if (args.length == 2) { final User toPlayer = getPlayer(server, args, 1, true, false); target.sendMessage(tl("teleportAtoB", Console.NAME, toPlayer.getDisplayName())); - target.getTeleport().now(toPlayer.getBase(), false, TeleportCause.COMMAND); + target.getAsyncTeleport().now(toPlayer.getBase(), false, TeleportCause.COMMAND, getNewExceptionFuture(sender, commandLabel)); } else if (args.length > 3) { final double x = args[1].startsWith("~") ? target.getLocation().getX() + (args[1].length() > 1 ? Double.parseDouble(args[1].substring(1)) : 0) : Double.parseDouble(args[1]); final double y = args[2].startsWith("~") ? target.getLocation().getY() + (args[2].length() > 1 ? Double.parseDouble(args[2].substring(1)) : 0) : Double.parseDouble(args[2]); @@ -121,8 +131,13 @@ public class Commandtp extends EssentialsCommand { } final Location loc = new Location(target.getWorld(), x, y, z, target.getLocation().getYaw(), target.getLocation().getPitch()); sender.sendMessage(tl("teleporting", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); - target.getTeleport().now(loc, false, TeleportCause.COMMAND); - target.sendMessage(tl("teleporting", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); + CompletableFuture future = getNewExceptionFuture(sender, commandLabel); + target.getAsyncTeleport().now(loc, false, TeleportCause.COMMAND, future); + future.thenAccept(success -> { + if (success) { + target.sendMessage(tl("teleporting", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); + } + }); } else { throw new NotEnoughArgumentsException(); } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandtpa.java b/Essentials/src/com/earth2me/essentials/commands/Commandtpa.java index 5d68bb596..7cfb84633 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandtpa.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandtpa.java @@ -1,6 +1,6 @@ package com.earth2me.essentials.commands; -import com.earth2me.essentials.Teleport; +import com.earth2me.essentials.AsyncTeleport; import com.earth2me.essentials.Trade; import com.earth2me.essentials.User; import net.ess3.api.events.TPARequestEvent; @@ -9,6 +9,7 @@ import org.bukkit.event.player.PlayerTeleportEvent; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; import static com.earth2me.essentials.I18n.tl; @@ -44,11 +45,16 @@ public class Commandtpa extends EssentialsCommand { } if (player.isAutoTeleportEnabled() && !player.isIgnoredPlayer(user)) { final Trade charge = new Trade(this.getName(), ess); - Teleport teleport = user.getTeleport(); - teleport.setTpType(Teleport.TeleportType.TPA); - teleport.teleport(player.getBase(), charge, PlayerTeleportEvent.TeleportCause.COMMAND); - player.sendMessage(tl("requestAcceptedAuto", user.getDisplayName())); - user.sendMessage(tl("requestAcceptedFromAuto", player.getDisplayName())); + AsyncTeleport teleport = (AsyncTeleport) user.getAsyncTeleport(); + teleport.setTpType(AsyncTeleport.TeleportType.TPA); + CompletableFuture future = getNewExceptionFuture(user.getSource(), commandLabel); + teleport.teleport(player.getBase(), charge, PlayerTeleportEvent.TeleportCause.COMMAND, future); + future.thenAccept(success -> { + if (success) { + player.sendMessage(tl("requestAcceptedAuto", user.getDisplayName())); + user.sendMessage(tl("requestAcceptedFromAuto", player.getDisplayName())); + } + }); return; } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandtpaccept.java b/Essentials/src/com/earth2me/essentials/commands/Commandtpaccept.java index ab7a61270..d1d7dc684 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandtpaccept.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandtpaccept.java @@ -1,12 +1,14 @@ package com.earth2me.essentials.commands; -import com.earth2me.essentials.Teleport; +import com.earth2me.essentials.AsyncTeleport; import com.earth2me.essentials.Trade; import com.earth2me.essentials.User; import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import java.util.concurrent.CompletableFuture; + import static com.earth2me.essentials.I18n.tl; @@ -49,24 +51,31 @@ public class Commandtpaccept extends EssentialsCommand { user.sendMessage(tl("requestAccepted")); requester.sendMessage(tl("requestAcceptedFrom", user.getDisplayName())); - try { - if (user.isTpRequestHere()) { - final Location loc = user.getTpRequestLocation(); - Teleport teleport = requester.getTeleport(); - teleport.setTpType(Teleport.TeleportType.TPA); - teleport.teleportPlayer(user, user.getTpRequestLocation(), charge, TeleportCause.COMMAND); - requester.sendMessage(tl("teleporting", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); - } else { - Teleport teleport = requester.getTeleport(); - teleport.setTpType(Teleport.TeleportType.TPA); - teleport.teleport(user.getBase(), charge, TeleportCause.COMMAND); - } - } catch (Exception ex) { + CompletableFuture future = getNewExceptionFuture(requester.getSource(), commandLabel); + future.exceptionally(e -> { user.sendMessage(tl("pendingTeleportCancelled")); - ess.showError(requester.getSource(), ex, commandLabel); + return false; + }); + future.thenAccept(success -> { + if (success) { + user.requestTeleport(null, false); + } + }); + if (user.isTpRequestHere()) { + final Location loc = user.getTpRequestLocation(); + AsyncTeleport teleport = (AsyncTeleport) requester.getAsyncTeleport(); + teleport.setTpType(AsyncTeleport.TeleportType.TPA); + future.thenAccept(success -> { + if (success) { + requester.sendMessage(tl("teleporting", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); + } + }); + teleport.teleportPlayer(user, user.getTpRequestLocation(), charge, TeleportCause.COMMAND, future); + } else { + AsyncTeleport teleport = (AsyncTeleport) requester.getAsyncTeleport(); + teleport.setTpType(AsyncTeleport.TeleportType.TPA); + teleport.teleport(user.getBase(), charge, TeleportCause.COMMAND, future); } - user.requestTeleport(null, false); - throw new NoChargeException(); } } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandtpall.java b/Essentials/src/com/earth2me/essentials/commands/Commandtpall.java index ad5bba4e6..ae33ebabe 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandtpall.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandtpall.java @@ -21,17 +21,17 @@ public class Commandtpall 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()) { - teleportAllPlayers(server, sender, ess.getUser(sender.getPlayer())); + teleportAllPlayers(server, sender, ess.getUser(sender.getPlayer()), commandLabel); return; } throw new NotEnoughArgumentsException(); } final User target = getPlayer(server, sender, args, 0); - teleportAllPlayers(server, sender, target); + teleportAllPlayers(server, sender, target, commandLabel); } - private void teleportAllPlayers(Server server, CommandSource sender, User target) { + private void teleportAllPlayers(Server server, CommandSource sender, User target, String label) { sender.sendMessage(tl("teleportAll")); final Location loc = target.getLocation(); for (User player : ess.getOnlineUsers()) { @@ -41,11 +41,7 @@ public class Commandtpall extends EssentialsCommand { if (sender.getSender().equals(target.getBase()) && target.getWorld() != player.getWorld() && ess.getSettings().isWorldTeleportPermissions() && !target.isAuthorized("essentials.worlds." + target.getWorld().getName())) { continue; } - try { - player.getTeleport().now(loc, false, TeleportCause.COMMAND); - } catch (Exception ex) { - ess.showError(sender, ex, getName()); - } + player.getAsyncTeleport().now(loc, false, TeleportCause.COMMAND, getNewExceptionFuture(sender, label)); } } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandtphere.java b/Essentials/src/com/earth2me/essentials/commands/Commandtphere.java index c8177c246..138729589 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandtphere.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandtphere.java @@ -25,8 +25,7 @@ public class Commandtphere 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())); } - user.getTeleport().teleportPlayer(player, user.getBase(), new Trade(this.getName(), ess), TeleportCause.COMMAND); - throw new NoChargeException(); + user.getAsyncTeleport().teleportPlayer(player, user.getBase(), new Trade(this.getName(), ess), TeleportCause.COMMAND, getNewExceptionFuture(user.getSource(), commandLabel)); } @Override diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandtpo.java b/Essentials/src/com/earth2me/essentials/commands/Commandtpo.java index 0ce0132ca..e2f234662 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandtpo.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandtpo.java @@ -6,6 +6,7 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; import static com.earth2me.essentials.I18n.tl; @@ -26,7 +27,7 @@ public class Commandtpo 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())); } - user.getTeleport().now(player.getBase(), false, TeleportCause.COMMAND); + user.getAsyncTeleport().now(player.getBase(), false, TeleportCause.COMMAND, getNewExceptionFuture(user.getSource(), commandLabel)); break; default: @@ -40,8 +41,13 @@ public class Commandtpo extends EssentialsCommand { throw new Exception(tl("noPerm", "essentials.worlds." + toPlayer.getWorld().getName())); } - target.getTeleport().now(toPlayer.getBase(), false, TeleportCause.COMMAND); - target.sendMessage(tl("teleportAtoB", user.getDisplayName(), toPlayer.getDisplayName())); + CompletableFuture future = getNewExceptionFuture(user.getSource(), commandLabel); + target.getAsyncTeleport().now(toPlayer.getBase(), false, TeleportCause.COMMAND, future); + future.thenAccept(success -> { + if (success) { + target.sendMessage(tl("teleportAtoB", user.getDisplayName(), toPlayer.getDisplayName())); + } + }); break; } } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandtpoffline.java b/Essentials/src/com/earth2me/essentials/commands/Commandtpoffline.java index ad998aa39..46ea3b493 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandtpoffline.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandtpoffline.java @@ -28,7 +28,7 @@ public class Commandtpoffline extends EssentialsCommand { } user.sendMessage(tl("teleporting", logout.getWorld().getName(), logout.getBlockX(), logout.getBlockY(), logout.getBlockZ())); - user.getTeleport().now(logout, false, PlayerTeleportEvent.TeleportCause.COMMAND); + user.getAsyncTeleport().now(logout, false, PlayerTeleportEvent.TeleportCause.COMMAND, getNewExceptionFuture(user.getSource(), label)); } } } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandtpohere.java b/Essentials/src/com/earth2me/essentials/commands/Commandtpohere.java index 39016cb10..03ec4b8c6 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandtpohere.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandtpohere.java @@ -28,8 +28,7 @@ public class Commandtpohere extends EssentialsCommand { throw new Exception(tl("noPerm", "essentials.worlds." + user.getWorld().getName())); } - // Verify permission - player.getTeleport().now(user.getBase(), false, TeleportCause.COMMAND); + player.getAsyncTeleport().now(user.getBase(), false, TeleportCause.COMMAND, getNewExceptionFuture(user.getSource(), commandLabel)); } @Override diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandtppos.java b/Essentials/src/com/earth2me/essentials/commands/Commandtppos.java index 763e5cec8..ff6de7d15 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandtppos.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandtppos.java @@ -55,8 +55,7 @@ public class Commandtppos extends EssentialsCommand { final Trade charge = new Trade(this.getName(), ess); charge.isAffordableFor(user); user.sendMessage(tl("teleporting", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); - user.getTeleport().teleport(loc, charge, TeleportCause.COMMAND); - throw new NoChargeException(); + user.getAsyncTeleport().teleport(loc, charge, TeleportCause.COMMAND, getNewExceptionFuture(user.getSource(), commandLabel)); } @Override @@ -85,8 +84,7 @@ public class Commandtppos extends EssentialsCommand { } sender.sendMessage(tl("teleporting", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); user.sendMessage(tl("teleporting", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); - user.getTeleport().teleport(loc, null, TeleportCause.COMMAND); - + user.getAsyncTeleport().teleport(loc, null, TeleportCause.COMMAND, getNewExceptionFuture(user.getSource(), commandLabel)); } @Override diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandwarp.java b/Essentials/src/com/earth2me/essentials/commands/Commandwarp.java index 07abc89b6..45967c90d 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandwarp.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandwarp.java @@ -41,11 +41,10 @@ public class Commandwarp extends EssentialsCommand { User otherUser = null; if (args.length == 2 && (user.isAuthorized("essentials.warp.otherplayers") || user.isAuthorized("essentials.warp.others"))) { otherUser = getPlayer(server, user, args, 1); - warpUser(user, otherUser, args[0]); - throw new NoChargeException(); + warpUser(user, otherUser, args[0], commandLabel); + return; } - warpUser(user, user, args[0]); - throw new NoChargeException(); + warpUser(user, user, args[0], commandLabel); } } @@ -56,9 +55,7 @@ public class Commandwarp extends EssentialsCommand { throw new NoChargeException(); } User otherUser = getPlayer(server, args, 1, true, false); - otherUser.getTeleport().warp(otherUser, args[0], null, TeleportCause.COMMAND); - throw new NoChargeException(); - + otherUser.getAsyncTeleport().warp(otherUser, args[0], null, TeleportCause.COMMAND, getNewExceptionFuture(sender, commandLabel)); } //TODO: Use one of the new text classes, like /help ? @@ -91,7 +88,7 @@ public class Commandwarp extends EssentialsCommand { } } - private void warpUser(final User owner, final User user, final String name) throws Exception { + private void warpUser(final User owner, final User user, final String name, final String commandLabel) throws Exception { final Trade chargeWarp = new Trade("warp-" + name.toLowerCase(Locale.ENGLISH).replace('_', '-'), ess); final Trade chargeCmd = new Trade(this.getName(), ess); final BigDecimal fullCharge = chargeWarp.getCommandCost(user).add(chargeCmd.getCommandCost(user)); @@ -100,7 +97,7 @@ public class Commandwarp extends EssentialsCommand { if (ess.getSettings().getPerWarpPermission() && !owner.isAuthorized("essentials.warps." + name)) { throw new Exception(tl("warpUsePermission")); } - owner.getTeleport().warp(user, name, charge, TeleportCause.COMMAND); + owner.getAsyncTeleport().warp(user, name, charge, TeleportCause.COMMAND, getNewExceptionFuture(user.getSource(), commandLabel)); } private List getAvailableWarpsFor(final IUser user) { diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandworld.java b/Essentials/src/com/earth2me/essentials/commands/Commandworld.java index 07a8da408..486562ece 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandworld.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandworld.java @@ -66,8 +66,7 @@ public class Commandworld extends EssentialsCommand { final Trade charge = new Trade(this.getName(), ess); charge.isAffordableFor(user); - user.getTeleport().teleport(target, charge, TeleportCause.COMMAND); - throw new NoChargeException(); + user.getAsyncTeleport().teleport(target, charge, TeleportCause.COMMAND, getNewExceptionFuture(user.getSource(), commandLabel)); } @Override diff --git a/Essentials/src/com/earth2me/essentials/commands/EssentialsCommand.java b/Essentials/src/com/earth2me/essentials/commands/EssentialsCommand.java index b3600ea10..4a1cd17a5 100644 --- a/Essentials/src/com/earth2me/essentials/commands/EssentialsCommand.java +++ b/Essentials/src/com/earth2me/essentials/commands/EssentialsCommand.java @@ -1,5 +1,6 @@ package com.earth2me.essentials.commands; +import org.bukkit.command.CommandSender; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginDescriptionFile; import com.earth2me.essentials.CommandSource; @@ -24,6 +25,8 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; import java.util.logging.Logger; import static com.earth2me.essentials.I18n.tl; @@ -346,6 +349,24 @@ public abstract class EssentialsCommand implements IEssentialsCommand { return command.tabComplete(sender.getSender(), label, effectiveArgs); } + @Override + public void showError(CommandSender sender, Throwable throwable, String commandLabel) { + sender.sendMessage(tl("errorWithMessage", throwable.getMessage())); + if (ess.getSettings().isDebug()) { + logger.log(Level.INFO, tl("errorCallingCommand", commandLabel), throwable); + throwable.printStackTrace(); + } + } + + public CompletableFuture getNewExceptionFuture(CommandSource sender, String commandLabel) { + CompletableFuture future = new CompletableFuture<>(); + future.exceptionally(e -> { + showError(sender.getSender(), e, commandLabel); + return false; + }); + return future; + } + /** * Common time durations (in seconds), for use in tab completion. */ diff --git a/Essentials/src/com/earth2me/essentials/commands/IEssentialsCommand.java b/Essentials/src/com/earth2me/essentials/commands/IEssentialsCommand.java index 07cb08abe..86e08877a 100644 --- a/Essentials/src/com/earth2me/essentials/commands/IEssentialsCommand.java +++ b/Essentials/src/com/earth2me/essentials/commands/IEssentialsCommand.java @@ -6,6 +6,8 @@ import com.earth2me.essentials.User; import net.ess3.api.IEssentials; import org.bukkit.Server; import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + import java.util.List; @@ -23,4 +25,6 @@ public interface IEssentialsCommand { void setEssentials(IEssentials ess); void setEssentialsModule(IEssentialsModule module); + + void showError(CommandSender sender, Throwable throwable, String commandLabel); } diff --git a/Essentials/src/com/earth2me/essentials/signs/SignWarp.java b/Essentials/src/com/earth2me/essentials/signs/SignWarp.java index 2bdc5afdb..add12a47c 100644 --- a/Essentials/src/com/earth2me/essentials/signs/SignWarp.java +++ b/Essentials/src/com/earth2me/essentials/signs/SignWarp.java @@ -6,6 +6,8 @@ import com.earth2me.essentials.User; import net.ess3.api.IEssentials; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import java.util.concurrent.CompletableFuture; + import static com.earth2me.essentials.I18n.tl; @@ -52,12 +54,17 @@ public class SignWarp extends EssentialsSign { } final Trade charge = getTrade(sign, 3, ess); - try { - player.getTeleport().warp(player, warpName, charge, TeleportCause.PLUGIN); - Trade.log("Sign", "Warp", "Interact", username, null, username, charge, sign.getBlock().getLocation(), ess); - } catch (Exception ex) { - throw new SignException(ex.getMessage(), ex); - } + CompletableFuture future = new CompletableFuture<>(); + player.getAsyncTeleport().warp(player, warpName, charge, TeleportCause.PLUGIN, future); + future.thenAccept(success -> { + if (success) { + Trade.log("Sign", "Warp", "Interact", username, null, username, charge, sign.getBlock().getLocation(), ess); + } + }); + future.exceptionally(e -> { + ess.showError(player.getSource(), e, "\\ sign: " + signName); + return false; + }); return true; } } diff --git a/Essentials/src/com/earth2me/essentials/utils/LocationUtil.java b/Essentials/src/com/earth2me/essentials/utils/LocationUtil.java index 2683e1e7d..5a81b2c69 100644 --- a/Essentials/src/com/earth2me/essentials/utils/LocationUtil.java +++ b/Essentials/src/com/earth2me/essentials/utils/LocationUtil.java @@ -153,7 +153,7 @@ public class LocationUtil { } public static Location getSafeDestination(final IEssentials ess, final IUser user, final Location loc) throws Exception { - if (user.getBase().isOnline() && loc.getWorld().equals(user.getBase().getWorld()) && (user.getBase().getGameMode() == GameMode.CREATIVE || user.isGodModeEnabled()) && user.getBase().getAllowFlight()) { + if (user.getBase().isOnline() && (user.getBase().getGameMode() == GameMode.CREATIVE || user.getBase().getGameMode() == GameMode.SPECTATOR || user.isGodModeEnabled()) || user.getBase().getAllowFlight()) { if (shouldFly(loc)) { user.getBase().setFlying(true); } diff --git a/Essentials/src/messages.properties b/Essentials/src/messages.properties index 0cf9b9b3d..ab51488b4 100644 --- a/Essentials/src/messages.properties +++ b/Essentials/src/messages.properties @@ -56,6 +56,7 @@ banipCommandUsage=/
bed=\u00a7obed\u00a7r bedMissing=\u00a74Your bed is either unset, missing or blocked. bedNull=\u00a7mbed\u00a7r +bedOffline=\u00a74Cannot teleport to the beds of offline users. bedSet=\u00a76Bed spawn set\! beezookaCommandDescription=Throw an exploding bee at your opponent. beezookaCommandUsage=/ diff --git a/Essentials/src/net/ess3/api/ITeleport.java b/Essentials/src/net/ess3/api/ITeleport.java index 65c680761..c09852170 100644 --- a/Essentials/src/net/ess3/api/ITeleport.java +++ b/Essentials/src/net/ess3/api/ITeleport.java @@ -1,6 +1,9 @@ package net.ess3.api; +/** + * @deprecated This API is not asynchronous. Use {@link com.earth2me.essentials.api.IAsyncTeleport IAsyncTeleport} + */ public interface ITeleport extends com.earth2me.essentials.api.ITeleport { } diff --git a/Essentials/src/net/ess3/api/events/UserTeleportEvent.java b/Essentials/src/net/ess3/api/events/UserTeleportEvent.java index 8410aace2..cd9ae33de 100644 --- a/Essentials/src/net/ess3/api/events/UserTeleportEvent.java +++ b/Essentials/src/net/ess3/api/events/UserTeleportEvent.java @@ -1,11 +1,12 @@ package net.ess3.api.events; import net.ess3.api.IUser; -import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; /** * Called when the player teleports */ @@ -18,6 +19,7 @@ public class UserTeleportEvent extends Event implements Cancellable { private boolean cancelled = false; public UserTeleportEvent(IUser user, TeleportCause cause, Location target) { + super(!Bukkit.isPrimaryThread()); this.user = user; this.cause = cause; this.target = target; diff --git a/EssentialsSpawn/src/com/earth2me/essentials/spawn/Commandspawn.java b/EssentialsSpawn/src/com/earth2me/essentials/spawn/Commandspawn.java index c903f404f..f37059822 100644 --- a/EssentialsSpawn/src/com/earth2me/essentials/spawn/Commandspawn.java +++ b/EssentialsSpawn/src/com/earth2me/essentials/spawn/Commandspawn.java @@ -5,12 +5,13 @@ import com.earth2me.essentials.Console; import com.earth2me.essentials.Trade; import com.earth2me.essentials.User; import com.earth2me.essentials.commands.EssentialsCommand; -import com.earth2me.essentials.commands.NoChargeException; import com.earth2me.essentials.commands.NotEnoughArgumentsException; import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import java.util.concurrent.CompletableFuture; + import static com.earth2me.essentials.I18n.tl; @@ -25,14 +26,18 @@ public class Commandspawn extends EssentialsCommand { charge.isAffordableFor(user); if (args.length > 0 && user.isAuthorized("essentials.spawn.others")) { final User otherUser = getPlayer(server, user, args, 0); - respawn(user.getSource(), user, otherUser, charge); - if (!otherUser.equals(user)) { - otherUser.sendMessage(tl("teleportAtoB", user.getDisplayName(), "spawn")); - } + CompletableFuture future = new CompletableFuture<>(); + future.thenAccept(success -> { + if (success) { + if (!otherUser.equals(user)) { + otherUser.sendMessage(tl("teleportAtoB", user.getDisplayName(), "spawn")); + } + } + }); + respawn(user.getSource(), user, otherUser, charge, commandLabel, future); } else { - respawn(user.getSource(), user, user, charge); + respawn(user.getSource(), user, user, charge, commandLabel, new CompletableFuture<>()); } - throw new NoChargeException(); } @Override @@ -41,19 +46,27 @@ public class Commandspawn extends EssentialsCommand { throw new NotEnoughArgumentsException(); } final User user = getPlayer(server, args, 0, true, false); - respawn(sender, null, user, null); - user.sendMessage(tl("teleportAtoB", Console.NAME, "spawn")); - + CompletableFuture future = new CompletableFuture<>(); + respawn(sender, null, user, null, commandLabel, future); + future.thenAccept(success -> { + if (success) { + user.sendMessage(tl("teleportAtoB", Console.NAME, "spawn")); + } + }); } - private void respawn(final CommandSource sender, final User teleportOwner, final User teleportee, final Trade charge) throws Exception { + private void respawn(final CommandSource sender, final User teleportOwner, final User teleportee, final Trade charge, String commandLabel, CompletableFuture future) throws Exception { final SpawnStorage spawns = (SpawnStorage) this.module; final Location spawn = spawns.getSpawn(teleportee.getGroup()); sender.sendMessage(tl("teleporting", spawn.getWorld().getName(), spawn.getBlockX(), spawn.getBlockY(), spawn.getBlockZ())); + future.exceptionally(e -> { + showError(sender.getSender(), e, commandLabel); + return false; + }); if (teleportOwner == null) { - teleportee.getTeleport().now(spawn, false, TeleportCause.COMMAND); + teleportee.getAsyncTeleport().now(spawn, false, TeleportCause.COMMAND, future); } else { - teleportOwner.getTeleport().teleportPlayer(teleportee, spawn, charge, TeleportCause.COMMAND); + teleportOwner.getAsyncTeleport().teleportPlayer(teleportee, spawn, charge, TeleportCause.COMMAND, future); } } } diff --git a/EssentialsSpawn/src/com/earth2me/essentials/spawn/EssentialsSpawnPlayerListener.java b/EssentialsSpawn/src/com/earth2me/essentials/spawn/EssentialsSpawnPlayerListener.java index 05db07ed8..306875ab6 100644 --- a/EssentialsSpawn/src/com/earth2me/essentials/spawn/EssentialsSpawnPlayerListener.java +++ b/EssentialsSpawn/src/com/earth2me/essentials/spawn/EssentialsSpawnPlayerListener.java @@ -17,6 +17,7 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import java.util.List; import java.util.Locale; +import java.util.concurrent.CompletableFuture; import java.util.logging.Level; import java.util.logging.Logger; @@ -135,13 +136,14 @@ class EssentialsSpawnPlayerListener implements Listener { return; } - try { - final Location spawn = spawns.getSpawn(ess.getSettings().getNewbieSpawn()); - if (spawn != null) { - user.getTeleport().now(spawn, false, TeleportCause.PLUGIN); - } - } catch (Exception ex) { - logger.log(Level.WARNING, tl("teleportNewPlayerError"), ex); + final Location spawn = spawns.getSpawn(ess.getSettings().getNewbieSpawn()); + if (spawn != null) { + CompletableFuture future = new CompletableFuture<>(); + future.exceptionally(e -> { + logger.log(Level.WARNING, tl("teleportNewPlayerError"), e); + return false; + }); + user.getAsyncTeleport().now(spawn, false, TeleportCause.PLUGIN, future); } } }