Maturing the border exemption; instead of a blanket exemption this starts a check task to see if the player's world has caught up with their portaled location.

This commit is contained in:
Daniel Boston 2016-06-15 22:27:48 -04:00
parent 4b6ac2a3d8
commit 116718e1a2
4 changed files with 114 additions and 29 deletions

View File

@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.wimbli.WorldBorder</groupId> <groupId>com.wimbli.WorldBorder</groupId>
<artifactId>WorldBorder</artifactId> <artifactId>WorldBorder</artifactId>
<version>1.8.7</version> <version>1.8.8</version>
<name>WorldBorder</name> <name>WorldBorder</name>
<url>https://github.com/Brettflan/WorldBorder</url> <url>https://github.com/Brettflan/WorldBorder</url>
<issueManagement> <issueManagement>
@ -60,7 +60,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version> <version>3.5.1</version>
<configuration> <configuration>
<source>1.7</source> <source>1.7</source>
<target>1.7</target> <target>1.7</target>

View File

@ -2,8 +2,9 @@ package com.wimbli.WorldBorder;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashSet; import java.util.LinkedHashMap;
import java.util.Set; import java.util.Map;
import java.util.UUID;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -36,31 +37,95 @@ 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. /** track players who are being handled (moved back inside the border) already; needed
private static Set<String> handlingPlayers = Collections.synchronizedSet(new LinkedHashSet<String>()); * 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 Map<String, BukkitRunnable> handlingPlayers = Collections.synchronizedMap(new LinkedHashMap<String, BukkitRunnable>());
/** /**
* In 1.9, there is a significant delay between teleportation event and when the player's location is actually updated. * 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 * 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. * incorrectly test the player's prior-world location against the new-world location during that amorphous
* in-between period.
* *
* This function allows a temporary delay against the check to let Minecraft "catch up" the player's _real_ location. * This function allows a configurable recheck to let Minecraft "catch up" the player's <i>real</i> location.
*
* In the meantime, the player is exempted from border crossing checks (and from spurious additional teleport event
* checks).
*
* Note that additional portal teleports (e.g. if the player immediately portals back) will reset
* this check gracefully.
*
* @param player The player who is being exempted.
* @param world The world the player is supposedly now in.
* @param maxDelay The <i>maximum</i> ticks to spend exempting this player
* @param recheckDelay The ticks to wait inbetween rechecks.
*/ */
public static void timedPlayerExemption(final Player player, long delay) { public static void timedPlayerExemption(final Player player, final String world, final long maxDelay, final long recheckDelay) {
handlingPlayers.add(player.getName().toLowerCase());
new BukkitRunnable() { // Check for existing watch; cancel if one exists.
private final String playerName = player.getName().toLowerCase(); BukkitRunnable alreadyWatching = handlingPlayers.get(player.getName().toLowerCase());
@Override if (alreadyWatching != null) {
public void run() { try {
handlingPlayers.remove(playerName); alreadyWatching.cancel();
if (Config.Debug()) } catch (IllegalStateException e){}
Config.log("Exemption for " + playerName + " expired"); }
}
}.runTaskLater(WorldBorder.plugin, delay); alreadyWatching = new BukkitRunnable() {
private final String playerName = player.getName().toLowerCase();
private final UUID playerUUID = player.getUniqueId();
private long currentDelay = recheckDelay;
@Override
public void run() {
// Are we done checking?
if (currentDelay > maxDelay) {
this.cancel();
handlingPlayers.remove(playerName);
if (Config.Debug())
Config.log("Done watching " + playerName + ". They are in the hands of fate, now.");
return;
}
currentDelay += recheckDelay;
// Is this player still online?
Player player = Bukkit.getPlayer(playerUUID);
if (player == null) { // assume offline
this.cancel();
handlingPlayers.remove(playerName);
if (Config.Debug())
Config.log("Looks like " + playerName + " logged off. Suspending watch.");
return;
}
// Are we still stuck between worlds?
Location loc = player.getLocation();
World worldObj = loc.getWorld();
if (world.equals(worldObj.getName())) {
// No, we made it!
this.cancel();
handlingPlayers.remove(playerName);
if (Config.Debug())
Config.log("Minecraft caught up with " + playerName + ". Ending watch.");
return;
}
if (Config.Debug()) {
Config.log("Based on teleport " + playerName + " is in " + world +
" but Minecraft still thinks they are in " + worldObj.getName() +
". Checking again in " + recheckDelay);
}
}
};
// Store the exemption and start the recheck.
handlingPlayers.put(player.getName().toLowerCase(), alreadyWatching);
alreadyWatching.runTaskTimer(WorldBorder.plugin, recheckDelay, recheckDelay);
if (Config.Debug()) if (Config.Debug())
Config.log("Exempting " + player.getName().toLowerCase() + " for " + delay + " ticks."); Config.log("Rechecking " + player.getName() + "'s world every " +
recheckDelay + " ticks for " + maxDelay + " 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 // 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
@ -80,11 +145,11 @@ public class BorderCheckTask implements Runnable
return null; return null;
// if player is in bypass list (from bypass command), allow them beyond border; also ignore players currently being handled already // if player is in bypass list (from bypass command), allow them beyond border; also ignore players currently being handled already
if (Config.isPlayerBypassing(player.getUniqueId()) || handlingPlayers.contains(player.getName().toLowerCase())) if (Config.isPlayerBypassing(player.getUniqueId()) || handlingPlayers.containsKey(player.getName().toLowerCase()))
return null; return null;
// tag this player as being handled so we can't get stuck in a loop due to Bukkit currently sometimes repeatedly providing incorrect location through teleport event // tag this player as being handled so we can't get stuck in a loop due to Bukkit currently sometimes repeatedly providing incorrect location through teleport event
handlingPlayers.add(player.getName().toLowerCase()); handlingPlayers.put(player.getName().toLowerCase(), null);
Location newLoc = newLocation(player, loc, border, notify); Location newLoc = newLocation(player, loc, border, notify);
boolean handlingVehicle = false; boolean handlingVehicle = false;

View File

@ -56,6 +56,8 @@ public class Config
private static int fillMemoryTolerance = 500; private static int fillMemoryTolerance = 500;
private static boolean preventBlockPlace = false; private static boolean preventBlockPlace = false;
private static boolean preventMobSpawn = false; private static boolean preventMobSpawn = false;
private static long maxExemptionTicks = 21l;
private static long portalRecheckTicks = 4l; // These together give 5 recheck chances over the course of a second.
// for monitoring plugin efficiency // for monitoring plugin efficiency
// public static long timeUsed = 0; // public static long timeUsed = 0;
@ -539,6 +541,21 @@ public class Config
return false; return false;
} }
public static long getMaxExemptionTicks() {
return maxExemptionTicks;
}
public static void setMaxExemptionTicks(long maxExemptionTicks) {
Config.maxExemptionTicks = maxExemptionTicks;
}
public static long getPortalRecheckTicks() {
return portalRecheckTicks;
}
public static void setPortalRecheckTicks(long portalRecheckTicks) {
Config.portalRecheckTicks = portalRecheckTicks;
}
public static String replaceAmpColors (String message) public static String replaceAmpColors (String message)
@ -600,6 +617,8 @@ public class Config
fillMemoryTolerance = cfg.getInt("fill-memory-tolerance", 500); fillMemoryTolerance = cfg.getInt("fill-memory-tolerance", 500);
preventBlockPlace = cfg.getBoolean("prevent-block-place"); preventBlockPlace = cfg.getBoolean("prevent-block-place");
preventMobSpawn = cfg.getBoolean("prevent-mob-spawn"); preventMobSpawn = cfg.getBoolean("prevent-mob-spawn");
maxExemptionTicks = cfg.getLong("max-exemption-ticks", 21l);
portalRecheckTicks = cfg.getLong("portal-recheck-ticks", 4l);
StartBorderTimer(); StartBorderTimer();
@ -708,6 +727,8 @@ public class Config
cfg.set("fill-memory-tolerance", fillMemoryTolerance); cfg.set("fill-memory-tolerance", fillMemoryTolerance);
cfg.set("prevent-block-place", preventBlockPlace); cfg.set("prevent-block-place", preventBlockPlace);
cfg.set("prevent-mob-spawn", preventMobSpawn); cfg.set("prevent-mob-spawn", preventMobSpawn);
cfg.set("max-exemption-ticks", maxExemptionTicks);
cfg.set("portal-recheck-ticks", portalRecheckTicks);
cfg.set("worlds", null); cfg.set("worlds", null);
for(Entry<String, BorderData> stringBorderDataEntry : borders.entrySet()) for(Entry<String, BorderData> stringBorderDataEntry : borders.entrySet())
@ -746,4 +767,5 @@ public class Config
if (logIt) if (logIt)
logConfig("Configuration saved."); logConfig("Configuration saved.");
} }
} }

View File

@ -9,7 +9,6 @@ import org.bukkit.event.player.PlayerPortalEvent;
import org.bukkit.event.world.ChunkLoadEvent; import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.Location; import org.bukkit.Location;
public class WBListener implements Listener public class WBListener implements Listener
{ {
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
@ -19,11 +18,9 @@ public class WBListener implements Listener
if (Config.KnockBack() == 0.0) if (Config.KnockBack() == 0.0)
return; return;
if (Config.Debug()) if (event instanceof PlayerPortalEvent) { // Avoid overlapping management.
Config.log("General Teleport cause: " + event.getCause().toString()); if (Config.Debug())
Config.log("Skipping teleport management event - covered by onPlayerPortal");
if (PlayerTeleportEvent.TeleportCause.NETHER_PORTAL == event.getCause()) {
Config.log("Skipping teleport management event - covered by onPlayerPortal");
return; return;
} }
@ -55,7 +52,8 @@ public class WBListener implements Listener
event.setTo(newLoc); event.setTo(newLoc);
} }
BorderCheckTask.timedPlayerExemption(event.getPlayer(), 100l); BorderCheckTask.timedPlayerExemption(event.getPlayer(), event.getTo().getWorld().getName(),
Config.getMaxExemptionTicks(), Config.getPortalRecheckTicks());
} }
@EventHandler(priority = EventPriority.MONITOR) @EventHandler(priority = EventPriority.MONITOR)