From 285641159476a03a70980318be1c2e8b242517bf Mon Sep 17 00:00:00 2001 From: Brettflan Date: Tue, 13 Mar 2012 05:54:48 -0500 Subject: [PATCH] Remake of radius claim method. It now starts in the current chunk and spirals outward, in a repeating task designed to keep from overloading the server. The old method tried to put together a list of chunks, and then tried to claim them immediately starting from one corner of the overall area. New setting "radiusClaimFailureLimit" (default 9). If claims are unsuccessful that many times in a row during a radius claim, the task will cancel out. There is no longer a limit to the specified radius since the process should no longer cause major server stress, and due to the process canceling out after several failures as just described. Added some new methods to FLocation to quickly convert between block/chunk/region positions, and rewrote the FLocation hashCode() method to make it faster. --- src/com/massivecraft/factions/Conf.java | 5 +- src/com/massivecraft/factions/FLocation.java | 48 +++- src/com/massivecraft/factions/FPlayer.java | 2 - .../massivecraft/factions/cmd/CmdClaim.java | 56 +++-- .../factions/util/SpiralTask.java | 213 ++++++++++++++++++ 5 files changed, 288 insertions(+), 36 deletions(-) create mode 100644 src/com/massivecraft/factions/util/SpiralTask.java diff --git a/src/com/massivecraft/factions/Conf.java b/src/com/massivecraft/factions/Conf.java index fdf6e7ee..a1b4a728 100644 --- a/src/com/massivecraft/factions/Conf.java +++ b/src/com/massivecraft/factions/Conf.java @@ -129,7 +129,10 @@ public class Conf public static boolean claimsCanBeUnconnectedIfOwnedByOtherFaction = true; public static int claimsRequireMinFactionMembers = 1; public static int claimedLandsMax = 0; - + + // if someone is doing a radius claim and the process fails to claim land this many times in a row, it will exit + public static int radiusClaimFailureLimit = 9; + //public static double considerFactionsReallyOfflineAfterXMinutes = 0.0; public static int actionDeniedPainAmount = 2; diff --git a/src/com/massivecraft/factions/FLocation.java b/src/com/massivecraft/factions/FLocation.java index 856d0e03..7e419c36 100644 --- a/src/com/massivecraft/factions/FLocation.java +++ b/src/com/massivecraft/factions/FLocation.java @@ -40,9 +40,7 @@ public class FLocation public FLocation(Location location) { -// this(location.getWorld().getName(), (int) Math.floor(location.getX() / cellSize) , (int) Math.floor(location.getZ() / cellSize)); - // handy dandy rapid bitshifting instead of division - this(location.getWorld().getName(), location.getBlockX() >> 4, location.getBlockZ() >> 4); + this( location.getWorld().getName(), blockToChunk(location.getBlockX()), blockToChunk(location.getBlockZ()) ); } public FLocation(Player player) @@ -109,6 +107,41 @@ public class FLocation return "["+this.getWorldName()+","+this.getCoordString()+"]"; } + //----------------------------------------------// + // Block/Chunk/Region Value Transformation + //----------------------------------------------// + + // bit-shifting is used because it's much faster than standard division and multiplication + public static int blockToChunk(int blockVal) + { // 1 chunk is 16x16 blocks + return blockVal >> 4; // ">> 4" == "/ 16" + } + + public static int blockToRegion(int blockVal) + { // 1 region is 512x512 blocks + return blockVal >> 9; // ">> 9" == "/ 512" + } + + public static int chunkToRegion(int chunkVal) + { // 1 region is 32x32 chunks + return chunkVal >> 5; // ">> 5" == "/ 32" + } + + public static int chunkToBlock(int chunkVal) + { + return chunkVal << 4; // "<< 4" == "* 16" + } + + public static int regionToBlock(int regionVal) + { + return regionVal << 9; // "<< 9" == "* 512" + } + + public static int regionToChunk(int regionVal) + { + return regionVal << 5; // "<< 5" == "* 32" + } + //----------------------------------------------// // Misc Geometry //----------------------------------------------// @@ -175,12 +208,9 @@ public class FLocation @Override public int hashCode() { - int hash = 3; - hash = 19 * hash + (this.worldName != null ? this.worldName.hashCode() : 0); - hash = 19 * hash + this.x; - hash = 19 * hash + this.z; - return hash; - }; + // should be fast, with good range and few hash collisions: (x * 512) + z + worldName.hashCode + return (this.x << 9) + this.z + (this.worldName != null ? this.worldName.hashCode() : 0); + } @Override public boolean equals(Object obj) diff --git a/src/com/massivecraft/factions/FPlayer.java b/src/com/massivecraft/factions/FPlayer.java index e4fe7ccb..4c0a9074 100644 --- a/src/com/massivecraft/factions/FPlayer.java +++ b/src/com/massivecraft/factions/FPlayer.java @@ -648,12 +648,10 @@ public class FPlayer extends PlayerEntity implements EconomyParticipator { Faction faction = this.getFaction(); if ( ! Econ.modifyMoney(faction, -cost, "to claim this land", "for claiming this land")) return false; - } else { if ( ! Econ.modifyMoney(this, -cost, "to claim this land", "for claiming this land")) return false; - } } diff --git a/src/com/massivecraft/factions/cmd/CmdClaim.java b/src/com/massivecraft/factions/cmd/CmdClaim.java index 90118d8b..dbc0b525 100644 --- a/src/com/massivecraft/factions/cmd/CmdClaim.java +++ b/src/com/massivecraft/factions/cmd/CmdClaim.java @@ -1,12 +1,11 @@ package com.massivecraft.factions.cmd; -import java.util.Set; - -import org.bukkit.Location; - +import com.massivecraft.factions.Conf; import com.massivecraft.factions.FLocation; import com.massivecraft.factions.Faction; import com.massivecraft.factions.struct.Permission; +import com.massivecraft.factions.util.SpiralTask; + public class CmdClaim extends FCommand { @@ -33,34 +32,43 @@ public class CmdClaim extends FCommand public void perform() { // Read and validate input - Faction forFaction = this.argAsFaction(0, myFaction); + final Faction forFaction = this.argAsFaction(0, myFaction); + int radius = this.argAsInt(1, 1); - // just to cut the unauthorized off immediately instead of going on to do radius calculations - if (! fme.canClaimForFactionAtLocation(forFaction, me.getLocation(), false)) + if (radius < 1) { - msg("You do not currently have permission to claim land for the faction "+forFaction.describeTo(fme) +"."); + msg("If you specify a radius, it must be at least 1."); return; } - double radius = this.argAsDouble(1, 1d); - radius -= 0.5; - if (radius <= 0) + if (radius < 2) { - msg("That radius is to small."); - return; + // single chunk + fme.attemptClaim(forFaction, me.getLocation(), true); } - else if (radius > 100) // huge radius can crash server + else { - msg("That radius is overly large. Remember that the radius is in chunks (16x16 blocks), not individual blocks."); - return; - } - - // Get the FLocations - Set flocs = new FLocation(me).getCircle(radius); - for (FLocation floc : flocs) - { - fme.attemptClaim(forFaction, new Location(floc.getWorld(), floc.getX() << 4, 1, floc.getZ() << 4), true); + // radius claim + new SpiralTask(new FLocation(me), radius) + { + private int failCount = 0; + private final int limit = Conf.radiusClaimFailureLimit - 1; + + @Override + public boolean work() + { + boolean success = fme.attemptClaim(forFaction, this.currentLocation(), true); + if (success) + failCount = 0; + else if ( ! success && failCount++ >= limit) + { + this.stop(); + return false; + } + + return true; + } + }; } } - } diff --git a/src/com/massivecraft/factions/util/SpiralTask.java b/src/com/massivecraft/factions/util/SpiralTask.java new file mode 100644 index 00000000..98d5927a --- /dev/null +++ b/src/com/massivecraft/factions/util/SpiralTask.java @@ -0,0 +1,213 @@ +package com.massivecraft.factions.util; + +import java.util.logging.Level; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; + +import com.massivecraft.factions.FLocation; +import com.massivecraft.factions.P; + + +/* + * reference diagram, task should move in this pattern out from chunk 0 in the center. + * 8 [>][>][>][>][>] etc. + * [^][6][>][>][>][>][>][6] + * [^][^][4][>][>][>][4][v] + * [^][^][^][2][>][2][v][v] + * [^][^][^][^][0][v][v][v] + * [^][^][^][1][1][v][v][v] + * [^][^][3][<][<][3][v][v] + * [^][5][<][<][<][<][5][v] + * [7][<][<][<][<][<][<][7] + */ + +public abstract class SpiralTask implements Runnable +{ + // general task-related reference data + private transient World world = null; + private transient boolean readyToGo = false; + private transient int taskID = -1; + private transient int limit = 0; + + // values for the spiral pattern routine + private transient int x = 0; + private transient int z = 0; + private transient boolean isZLeg = false; + private transient boolean isNeg = false; + private transient int length = -1; + private transient int current = 0; + + @SuppressWarnings("LeakingThisInConstructor") + public SpiralTask(FLocation fLocation, int radius) + { + // limit is determined based on spiral leg length for given radius; see insideRadius() + this.limit = (radius - 1) * 2; + + this.world = Bukkit.getWorld(fLocation.getWorldName()); + if (this.world == null) + { + P.p.log(Level.WARNING, "[SpiralTask] A valid world must be specified!"); + this.stop(); + return; + } + + this.x = (int)fLocation.getX(); + this.z = (int)fLocation.getZ(); + + this.readyToGo = true; + + // get this party started + this.setTaskID(Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(P.p, this, 2, 2)); + } + +/* + * This is where the necessary work is done; you'll need to override this method with whatever you want + * done at each chunk in the spiral pattern. + * Return false if the entire task needs to be aborted, otherwise return true to continue. + */ + public abstract boolean work(); + +/* + * Returns an FLocation pointing at the current chunk X and Z values. + */ + public final FLocation currentFLocation() + { + return new FLocation(world.getName(), x, z); + } +/* + * Returns a Location pointing at the current chunk X and Z values. + * note that the Location is at the corner of the chunk, not the center. + */ + public final Location currentLocation() + { + return new Location(world, FLocation.chunkToBlock(x), 65.0, FLocation.chunkToBlock(z)); + } +/* + * Returns current chunk X and Z values. + */ + public final int getX() + { + return x; + } + public final int getZ() + { + return z; + } + + + +/* + * Below are the guts of the class, which you normally wouldn't need to mess with. + */ + + public final void setTaskID(int ID) + { + if (ID == -1) + this.stop(); + taskID = ID; + } + + public final void run() + { + if (!this.valid() || !readyToGo) return; + + // this is set so it only does one iteration at a time, no matter how frequently the timer fires + readyToGo = false; + + // make sure we're still inside the specified radius + if ( ! this.insideRadius()) return; + + // track this to keep one iteration from dragging on too long and possibly choking the system + long loopStartTime = now(); + + // keep going until the task has been running for 20ms or more, then stop to take a breather + while (now() < loopStartTime + 20) + { + // run the primary task on the current X/Z coordinates + if ( ! this.work()) + { + this.finish(); + return; + } + + // move on to next chunk in spiral + if ( ! this.moveToNext()) + return; + } + + // ready for the next iteration to run + readyToGo = true; + } + + // step through chunks in spiral pattern from center; returns false if we're done, otherwise returns true + public final boolean moveToNext() + { + if ( ! this.valid()) return false; + + // make sure we don't need to turn down the next leg of the spiral + if (current < length) + { + current++; + + // if we're outside the radius, we're done + if ( ! this.insideRadius()) return false; + } + else + { // one leg/side of the spiral down... + current = 0; + isZLeg ^= true; + // every second leg (between X and Z legs, negative or positive), length increases + if (isZLeg) + { + isNeg ^= true; + length++; + } + } + + // move one chunk further in the appropriate direction + if (isZLeg) + z += (isNeg) ? -1 : 1; + else + x += (isNeg) ? -1 : 1; + + return true; + } + + public final boolean insideRadius() + { + boolean inside = current < limit; + if (!inside) + this.finish(); + return inside; + } + + // for successful completion + public void finish() + { +// P.p.log("SpiralTask successfully completed!"); + this.stop(); + } + + // we're done, whether finished or cancelled + public final void stop() + { + if (!this.valid()) return; + + readyToGo = false; + Bukkit.getServer().getScheduler().cancelTask(taskID); + taskID = -1; + } + + // is this task still valid/workable? + public final boolean valid() + { + return taskID != -1; + } + + private static long now() + { + return System.currentTimeMillis(); + } +}