package com.massivecraft.factions.util; import com.massivecraft.factions.FLocation; import com.massivecraft.factions.FactionsPlugin; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; import java.util.logging.Level; /* * 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) { FactionsPlugin.getInstance().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(FactionsPlugin.getInstance(), this, 2, 2)); } private static long now() { return System.currentTimeMillis(); } /* * 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; } /* * Below are the guts of the class, which you normally wouldn't need to mess with. */ public final int getZ() { return z; } 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() { 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; } }