diff --git a/src/main/java/com/wimbli/WorldBorder/BorderCheckTask.java b/src/main/java/com/wimbli/WorldBorder/BorderCheckTask.java index ae50e40..552bd16 100644 --- a/src/main/java/com/wimbli/WorldBorder/BorderCheckTask.java +++ b/src/main/java/com/wimbli/WorldBorder/BorderCheckTask.java @@ -17,7 +17,7 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.Location; import org.bukkit.util.Vector; import org.bukkit.World; - +import org.bukkit.scheduler.BukkitRunnable; public class BorderCheckTask implements Runnable { @@ -39,6 +39,30 @@ public class BorderCheckTask implements Runnable // track players who are being handled (moved back inside the border) already; needed since Bukkit is sometimes sending teleport events with the old (now incorrect) location still indicated, which can lead to a loop when we then teleport them thinking they're outside the border, triggering event again, etc. private static Set handlingPlayers = Collections.synchronizedSet(new LinkedHashSet()); + /** + * In 1.9, there is a significant delay between teleportation event and when the player's location is actually updated. + * However, the player world is updated immediately. This disconnection causes the regular checkPlayer to + * incorrectly test the player's prior-world location against the new-world location during that inbetween period. + * + * This function allows a temporary delay against the check to let Minecraft "catch up" the player's _real_ location. + */ + public static void timedPlayerExemption(final Player player, long delay) { + handlingPlayers.add(player.getName().toLowerCase()); + + new BukkitRunnable() { + private final String playerName = player.getName().toLowerCase(); + @Override + public void run() { + handlingPlayers.remove(playerName); + if (Config.Debug()) + Config.log("Exemption for " + playerName + " expired"); + } + }.runTaskLater(WorldBorder.plugin, delay); + + if (Config.Debug()) + Config.log("Exempting " + player.getName().toLowerCase() + " for " + delay + " ticks."); + } + // set targetLoc only if not current player location; set returnLocationOnly to true to have new Location returned if they need to be moved to one, instead of directly handling it public static Location checkPlayer(Player player, Location targetLoc, boolean returnLocationOnly, boolean notify) { diff --git a/src/main/java/com/wimbli/WorldBorder/WBListener.java b/src/main/java/com/wimbli/WorldBorder/WBListener.java index dba5cfe..8bfa58c 100644 --- a/src/main/java/com/wimbli/WorldBorder/WBListener.java +++ b/src/main/java/com/wimbli/WorldBorder/WBListener.java @@ -20,7 +20,12 @@ public class WBListener implements Listener return; if (Config.Debug()) - Config.log("Teleport cause: " + event.getCause().toString()); + Config.log("General Teleport cause: " + event.getCause().toString()); + + if (PlayerTeleportEvent.TeleportCause.NETHER_PORTAL == event.getCause()) { + Config.log("Skipping teleport management event - covered by onPlayerPortal"); + return; + } Location newLoc = BorderCheckTask.checkPlayer(event.getPlayer(), event.getTo(), true, true); if (newLoc != null) @@ -38,13 +43,19 @@ public class WBListener implements Listener @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onPlayerPortal(PlayerPortalEvent event) { + if (Config.Debug()) + Config.log("Player Portal Teleport cause: " + event.getCause().toString()); + // if knockback is set to 0, or portal redirection is disabled, simply return if (Config.KnockBack() == 0.0 || !Config.portalRedirection()) return; Location newLoc = BorderCheckTask.checkPlayer(event.getPlayer(), event.getTo(), true, false); - if (newLoc != null) + if (newLoc != null) { event.setTo(newLoc); + } + + BorderCheckTask.timedPlayerExemption(event.getPlayer(), 100l); } @EventHandler(priority = EventPriority.MONITOR)