diff --git a/.gitignore b/.gitignore index 96374c4e..c4474de4 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,7 @@ $RECYCLE.BIN/ Network Trash Folder Temporary Items .apdisk +*.classpath +.project +*.prefs +*.class diff --git a/config.yml b/config.yml new file mode 100644 index 00000000..e69de29b diff --git a/plugin.yml b/plugin.yml new file mode 100644 index 00000000..775d1510 --- /dev/null +++ b/plugin.yml @@ -0,0 +1,32 @@ +name: FastAsyncWorldEdit +main: com.boydti.fawe.bukkit.FaweBukkit +version: 1.1.4 +description: Fast Async WorldEdit plugin +authors: [Empire92] +loadbefore: [WorldEdit] +load: STARTUP +database: false +#softdepend: [WorldGuard, PlotSquared, MCore, Factions, GriefPrevention, Residence, Towny, PlotMe, PreciousStones] +commands: + wea: + description: (FAWE) Bypass WorldEdit processing and area restrictions + aliases: [weanywhere,worldeditanywhere,/wea,/weanywhere,/worldeditanywhere] + usage: "Vault is required for the toggle. Optionally, you can set the permission fawe.bypass" + fixlighting: + description: (FAWE) Fix the lighting in your current chunk + aliases: [/fixlighting] + stream: + description: (FAWE) Stream a schematic into the world + aliases: [/stream] + wrg: + description: (FAWE) Select your current WorldEdit Region. + aliases: [/wrg,wer,/wer,worldeditregion,/worldeditregion,/region] +permissions: + fawe.bypass: + default: false + fawe.admin: + default: false + fawe.stream: + default: false + fawe.fixlighting: + default: false \ No newline at end of file diff --git a/src/com/boydti/fawe/Fawe.java b/src/com/boydti/fawe/Fawe.java new file mode 100644 index 00000000..0314d9a2 --- /dev/null +++ b/src/com/boydti/fawe/Fawe.java @@ -0,0 +1,226 @@ +package com.boydti.fawe; + +import java.io.File; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryUsage; +import java.util.List; + +import javax.management.InstanceAlreadyExistsException; +import javax.management.Notification; +import javax.management.NotificationEmitter; +import javax.management.NotificationListener; + +import com.boydti.fawe.command.FixLighting; +import com.boydti.fawe.command.Stream; +import com.boydti.fawe.command.Wea; +import com.boydti.fawe.command.WorldEditRegion; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.util.Lag; +import com.boydti.fawe.util.MemUtil; +import com.boydti.fawe.util.SetBlockQueue; +import com.boydti.fawe.util.TaskManager; +import com.boydti.fawe.util.WEManager; +import com.boydti.fawe.util.WESubscriber; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.SchematicCommands; +import com.sk89q.worldedit.command.ScriptingCommands; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.function.visitor.BreadthFirstSearch; +import com.sk89q.worldedit.function.visitor.DownwardVisitor; +import com.sk89q.worldedit.function.visitor.EntityVisitor; +import com.sk89q.worldedit.function.visitor.FlatRegionVisitor; +import com.sk89q.worldedit.function.visitor.LayerVisitor; +import com.sk89q.worldedit.function.visitor.NonRisingVisitor; +import com.sk89q.worldedit.function.visitor.RecursiveVisitor; +import com.sk89q.worldedit.function.visitor.RegionVisitor; + +public class Fawe { + /** + * The FAWE instance; + */ + private static Fawe INSTANCE; + + /** + * Get the implementation specific class + * @return + */ + @SuppressWarnings("unchecked") + public static T imp() { + return INSTANCE != null ? (T) INSTANCE.IMP : null; + } + + /** + * Get the implementation independent class + * @return + */ + public static Fawe get() { + return INSTANCE; + } + + /** + * Setup Fawe + * @param implementation + * @throws InstanceAlreadyExistsException + */ + public static void set(final IFawe implementation) throws InstanceAlreadyExistsException, IllegalArgumentException { + if (INSTANCE != null) { + throw new InstanceAlreadyExistsException("FAWE has already been initialized with: " + INSTANCE.IMP); + } + if (implementation == null) { + throw new IllegalArgumentException("Implementation may not be null."); + } + INSTANCE = new Fawe(implementation); + } + + /** + * Write something to the console + * @param s + */ + public static void debug(final String s) { + if (INSTANCE != null) { + INSTANCE.IMP.debug(s); + } else { + System.out.print(s); + } + } + + /** + * The platform specific implementation + */ + private final IFawe IMP; + private final Thread thread; + + private Fawe(final IFawe implementation) { + IMP = implementation; + + this.thread = Thread.currentThread(); + + /* + * Implementation dependent stuff + */ + setupConfigs(); + setupCommands(); + + TaskManager.IMP = IMP.getTaskManager(); + SetBlockQueue.IMP.queue = IMP.getQueue(); + + // Delayed setup + TaskManager.IMP.later(new Runnable() { + @Override + public void run() { + // worldedit + WEManager.IMP.managers.addAll(IMP.getMaskManagers()); + worldedit = WorldEdit.getInstance(); + // Events + setupEvents(); + IMP.setupVault(); + } + }, 0); + + /* + * Instance independent stuff + */ + setupInjector(); + setupMemoryListener(); + + // Lag + final Lag lag = new Lag(); + TaskManager.IMP.repeat(lag, 100); + } + + private void setupEvents() { + WorldEdit.getInstance().getEventBus().register(new WESubscriber()); + if (Settings.COMMAND_PROCESSOR) { + IMP.setupWEListener(); + } + } + + private void setupCommands() { + IMP.setupCommand("wea", new Wea()); + IMP.setupCommand("fixlighting", new FixLighting()); + IMP.setupCommand("stream", new Stream()); + IMP.setupCommand("wrg", new WorldEditRegion()); + } + + private void setupConfigs() { + // Setting up config.yml + Settings.setup(new File(IMP.getDirectory(), "config.yml")); + // Setting up message.yml + BBC.load(new File(IMP.getDirectory(), "message.yml")); + } + + private WorldEdit worldedit; + + public WorldEdit getWorldEdit() { + return worldedit; + } + + private void setupInjector() { + EditSession.inject(); + Operations.inject(); + SchematicCommands.inject(); + ScriptingCommands.inject(); + BreadthFirstSearch.inject(); + DownwardVisitor.inject(); + EntityVisitor.inject(); + FlatRegionVisitor.inject(); + LayerVisitor.inject(); + NonRisingVisitor.inject(); + RecursiveVisitor.inject(); + RegionVisitor.inject(); + } + + private void setupMemoryListener() { + final MemoryMXBean memBean = ManagementFactory.getMemoryMXBean(); + final NotificationEmitter ne = (NotificationEmitter) memBean; + + ne.addNotificationListener(new NotificationListener() { + @Override + public void handleNotification(final Notification notification, final Object handback) { + MemUtil.memoryLimitedTask(); + } + }, null, null); + + final List memPools = ManagementFactory.getMemoryPoolMXBeans(); + for (final MemoryPoolMXBean mp : memPools) { + if (mp.isUsageThresholdSupported()) { + final MemoryUsage mu = mp.getUsage(); + final long max = mu.getMax(); + if (max < 0) { + continue; + } + final long alert = (max * Settings.MEM_FREE) / 100; + mp.setUsageThreshold(alert); + + } + } + } + + public Thread getMainThread() { + return thread; + } + + /* + * TODO FIXME + * - Speed up chunk changes i.e. more than 1 chunk a tick + * - Async packet sending + * - Block logging + * - Investigate instance.clearSessions() / move to after manual session clearing + * - Redo WEManager delay / command queue + * - Support older versions of bukkit + * - Optimize lighting updates / chunk sending + * + * TESTING: + * - Undo / Redo + * - lighting + * - streaming + * - That history is kept after relog + * - Brush delay + * - Chunk loading for edits outside an area + * + */ +} diff --git a/src/com/boydti/fawe/FaweAPI.java b/src/com/boydti/fawe/FaweAPI.java new file mode 100644 index 00000000..d8c387de --- /dev/null +++ b/src/com/boydti/fawe/FaweAPI.java @@ -0,0 +1,331 @@ +package com.boydti.fawe; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.Material; + +import com.boydti.fawe.object.ChunkLoc; +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.FaweLocation; +import com.boydti.fawe.util.SetBlockQueue; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.jnbt.ByteArrayTag; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.IntTag; +import com.sk89q.jnbt.NBTInputStream; +import com.sk89q.jnbt.NamedTag; +import com.sk89q.jnbt.ShortTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.world.biome.BaseBiome; + +/** + * The FaweAPI class offers a few useful functions.
+ * - This class is not intended to replace the WorldEdit API
+ *
+ * FaweAPI.[some method] + */ +public class FaweAPI { + + /** + * Set a block at a location asynchronously + * @param loc + * @param m + */ + public static void setBlockAsync(final Location loc, final Material m) { + SetBlockQueue.IMP.setBlock(loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), (short) m.getId()); + } + + /** + * Set a block at a location asynchronously + * @param world + * @param x + * @param y + * @param z + * @param id + * @param data + */ + public static void setBlockAsync(final String world, final int x, final int y, final int z, final short id, final byte data) { + SetBlockQueue.IMP.setBlock(world, x, y, z, id, data); + } + + /** + * Set a biome at a location asynchronously + * @param world + * @param x + * @param z + * @param id + * @param data + */ + public static void setBiomeAsync(final String world, final int x, final int z, BaseBiome biome) { + SetBlockQueue.IMP.setBiome(world, x, z, biome); + } + + /** + * Set a biome at a location asynchronously + * @param loc + * @param biome + */ + public static void setBiomeAsync(Location loc, BaseBiome biome) { + SetBlockQueue.IMP.setBiome(loc.getWorld().getName(), loc.getBlockX(), loc.getBlockZ(), biome); + } + + /** + * This will return a FaweChunk object that can be modified.
+ * - The FaweChunk object can be reused if you want identical changes across chunks
+ * - This is additive modification.
+ * - First use {@link FaweChunk#fill(int, byte)} (e.g. with air) for absolute modification
+ * When ready, use {@link #setChunk(FaweChunk, ChunkLoc)} + * @return + */ + public static FaweChunk createChunk() { + return SetBlockQueue.IMP.queue.getChunk(new ChunkLoc(null, 0, 0)); + } + + /** + * @see #createChunk() + * @param data + * @param location + */ + public static void setChunkAsync(FaweChunk data, ChunkLoc location) { + data.setChunkLoc(location); + data.addToQueue(); + } + + /** + * @see #createChunk() + * @param data + * @param chunk + */ + public static void setChunkAsync(FaweChunk data, Chunk chunk) { + ChunkLoc loc = new ChunkLoc(chunk.getWorld().getName(), chunk.getX(), chunk.getZ()); + data.setChunkLoc(loc); + data.addToQueue(); + } + + /** + * Fix the lighting at a chunk location.
+ * - The fixAll parameter determines if extensive relighting should occur (slow) + * @param loc + */ + public static void fixLighting(ChunkLoc loc, boolean fixAll) { + SetBlockQueue.IMP.queue.fixLighting(SetBlockQueue.IMP.queue.getChunk(loc), fixAll); + } + + /** + * Fix the lighting at a chunk.
+ * - The fixAll parameter determines if extensive relighting should occur (slow) + * @param chunk + */ + public static void fixLighting(Chunk chunk, boolean fixAll) { + ChunkLoc loc = new ChunkLoc(chunk.getWorld().getName(), chunk.getX(), chunk.getZ()); + SetBlockQueue.IMP.queue.fixLighting(SetBlockQueue.IMP.queue.getChunk(loc), fixAll); + } + + /** + * If a schematic is too large to be pasted normally
+ * - Skips any block history + * - Ignores some block data + * @param file + * @param loc + * @return + */ + public static void streamSchematicAsync(final File file, final Location loc) { + FaweLocation fl = new FaweLocation(loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); + streamSchematicAsync(file, fl); + } + + /** + * If a schematic is too large to be pasted normally
+ * - Skips any block history + * - Ignores some block data + * @param file + * @param loc + * @return + */ + public static void streamSchematicAsync(final File file, final FaweLocation loc) { + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + try { + FileInputStream is = new FileInputStream(file); + streamSchematic(is, loc); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } + + /** + * If a schematic is too large to be pasted normally
+ * - Skips any block history + * - Ignores some block data + * @param url + * @param loc + */ + public static void streamSchematicAsync(final URL url, final FaweLocation loc) { + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + try { + ReadableByteChannel rbc = Channels.newChannel(url.openStream()); + final InputStream is = Channels.newInputStream(rbc); + streamSchematic(is, loc); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } + + /** + * If a schematic is too large to be pasted normally
+ * - Skips any block history + * - Ignores some block data + * @param is + * @param loc + * @throws IOException + */ + public static void streamSchematic(InputStream is, FaweLocation loc) throws IOException { + NBTInputStream stream = new NBTInputStream(new GZIPInputStream(is)); + NamedTag name = stream.readNamedTag(); + stream.close(); + + CompoundTag tag = (CompoundTag) name.getTag(); + Map tagMap = tag.getValue(); + + short width = ShortTag.class.cast(tagMap.get("Width")).getValue(); + short length = ShortTag.class.cast(tagMap.get("Length")).getValue(); + short height = ShortTag.class.cast(tagMap.get("Height")).getValue(); + byte[] ids = ByteArrayTag.class.cast(tagMap.get("Blocks")).getValue(); + byte[] datas = ByteArrayTag.class.cast(tagMap.get("Data")).getValue(); + + String world = loc.world; + + int x_offset = loc.x + IntTag.class.cast(tagMap.get("WEOffsetX")).getValue(); + int y_offset = loc.y + IntTag.class.cast(tagMap.get("WEOffsetY")).getValue(); + int z_offset = loc.z + IntTag.class.cast(tagMap.get("WEOffsetZ")).getValue(); + + name = null; + tagMap = null; + tag = null; + + for (int y = 0; y < height; y++) { + final int yy = y_offset + y; + if (yy > 255) { + continue; + } + final int i1 = y * width * length; + for (int z = 0; z < length; z++) { + final int i2 = (z * width) + i1; + int zz = z_offset + z; + for (int x = 0; x < width; x++) { + final int i = i2 + x; + int xx = x_offset + x; + short id = (short) (ids[i] & 0xFF); + switch (id) { + case 0: + case 2: + case 4: + case 13: + case 14: + case 15: + case 20: + case 21: + case 22: + case 30: + case 32: + case 37: + case 39: + case 40: + case 41: + case 42: + case 45: + case 46: + case 47: + case 48: + case 49: + case 50: + case 51: + case 55: + case 56: + case 57: + case 58: + case 60: + case 7: + case 8: + case 9: + case 10: + case 11: + case 73: + case 74: + case 75: + case 76: + case 78: + case 79: + case 80: + case 81: + case 82: + case 83: + case 85: + case 87: + case 88: + case 101: + case 102: + case 103: + case 110: + case 112: + case 113: + case 121: + case 122: + case 129: + case 133: + case 165: + case 166: + case 169: + case 170: + case 172: + case 173: + case 174: + case 181: + case 182: + case 188: + case 189: + case 190: + case 191: + case 192: + setBlockAsync(world, xx, yy, zz, id, (byte) 0); + break; + default: { + setBlockAsync(world, xx, yy, zz, id, datas[i]); + break; + } + } + } + } + } + + ids = null; + datas = null; + System.gc(); + System.gc(); + } + + /** + * Set a task to run when the async queue is empty + * @param whenDone + */ + public static void addTask(final Runnable whenDone) { + SetBlockQueue.IMP.addTask(whenDone); + } +} diff --git a/src/com/boydti/fawe/FaweCache.java b/src/com/boydti/fawe/FaweCache.java new file mode 100644 index 00000000..98eb3ea7 --- /dev/null +++ b/src/com/boydti/fawe/FaweCache.java @@ -0,0 +1,39 @@ +package com.boydti.fawe; + +import com.boydti.fawe.object.PseudoRandom; + +public class FaweCache { + public final static short[][][] CACHE_I = new short[256][16][16]; + public final static short[][][] CACHE_J = new short[256][16][16]; + + public final static byte[][] CACHE_X = new byte[16][4096]; + public final static short[][] CACHE_Y = new short[16][4096]; + public final static byte[][] CACHE_Z = new byte[16][4096]; + + public final static short[] CACHE_ID = new short[65535]; + public final static byte[] CACHE_DATA = new byte[65535]; + + public final static PseudoRandom RANDOM = new PseudoRandom(); + + static { + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + for (int y = 0; y < 256; y++) { + final short i = (short) (y >> 4); + final short j = (short) (((y & 0xF) << 8) | (z << 4) | x); + CACHE_I[y][x][z] = i; + CACHE_J[y][x][z] = j; + CACHE_X[i][j] = (byte) x; + CACHE_Y[i][j] = (short) y; + CACHE_Z[i][j] = (byte) z; + } + } + } + for (int i = 0; i < 65535; i++) { + final int j = i >> 4; + final int k = i & 0xF; + CACHE_ID[i] = (short) j; + CACHE_DATA[i] = (byte) k; + } + } +} diff --git a/src/com/boydti/fawe/IFawe.java b/src/com/boydti/fawe/IFawe.java new file mode 100644 index 00000000..7a6f2d90 --- /dev/null +++ b/src/com/boydti/fawe/IFawe.java @@ -0,0 +1,36 @@ +package com.boydti.fawe; + +import java.io.File; +import java.util.Collection; + +import com.boydti.fawe.object.EditSessionWrapper; +import com.boydti.fawe.object.FaweCommand; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.regions.FaweMaskManager; +import com.boydti.fawe.util.FaweQueue; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.worldedit.EditSession; + +public interface IFawe { + public void debug(final String s); + + public File getDirectory(); + + public void setupCommand(final String label, final FaweCommand cmd); + + public FawePlayer wrap(final Object obj); + + public void setupWEListener(); + + public void setupVault(); + + public TaskManager getTaskManager(); + + public int[] getVersion(); + + public FaweQueue getQueue(); + + public EditSessionWrapper getEditSessionWrapper(final EditSession session); + + public Collection getMaskManagers(); +} diff --git a/src/com/boydti/fawe/bukkit/BukkitCommand.java b/src/com/boydti/fawe/bukkit/BukkitCommand.java new file mode 100644 index 00000000..c69fb959 --- /dev/null +++ b/src/com/boydti/fawe/bukkit/BukkitCommand.java @@ -0,0 +1,24 @@ +package com.boydti.fawe.bukkit; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.object.FaweCommand; + +public class BukkitCommand implements CommandExecutor { + + private final FaweCommand cmd; + + public BukkitCommand(final FaweCommand cmd) { + this.cmd = cmd; + } + + @Override + public boolean onCommand(final CommandSender sender, final Command cmd, final String label, final String[] args) { + this.cmd.execute(Fawe.imp().wrap(sender), args); + return true; + } + +} diff --git a/src/com/boydti/fawe/bukkit/BukkitPlayer.java b/src/com/boydti/fawe/bukkit/BukkitPlayer.java new file mode 100644 index 00000000..df45fb48 --- /dev/null +++ b/src/com/boydti/fawe/bukkit/BukkitPlayer.java @@ -0,0 +1,65 @@ +package com.boydti.fawe.bukkit; + +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.object.FaweLocation; +import com.boydti.fawe.object.FawePlayer; + +public class BukkitPlayer extends FawePlayer { + + public BukkitPlayer(final Player parent) { + super(parent); + } + + @Override + public String getName() { + return parent.getName(); + } + + @Override + public UUID getUUID() { + return parent.getUniqueId(); + } + + @Override + public boolean hasPermission(final String perm) { + return parent.hasPermission(perm); + } + + @Override + public void setPermission(final String perm, final boolean flag) { + if (flag) { + Fawe. imp().getVault().permission.playerAdd(parent, perm); + } else { + Fawe. imp().getVault().permission.playerRemove(parent, perm); + } + } + + @Override + public void sendMessage(final String message) { + parent.sendMessage(ChatColor.translateAlternateColorCodes('&', message)); + } + + @Override + public void executeCommand(final String cmd) { + Bukkit.getServer().dispatchCommand(parent, cmd); + } + + @Override + public FaweLocation getLocation() { + Location loc = parent.getLocation(); + return new FaweLocation(loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); + } + + @Override + public com.sk89q.worldedit.entity.Player getPlayer() { + return Fawe. imp().getWorldEditPlugin().wrapPlayer(parent); + } + +} diff --git a/src/com/boydti/fawe/bukkit/BukkitTaskMan.java b/src/com/boydti/fawe/bukkit/BukkitTaskMan.java new file mode 100644 index 00000000..ca2e064b --- /dev/null +++ b/src/com/boydti/fawe/bukkit/BukkitTaskMan.java @@ -0,0 +1,67 @@ +package com.boydti.fawe.bukkit; + +import java.util.HashMap; + +import org.apache.commons.lang.mutable.MutableInt; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +import com.boydti.fawe.util.TaskManager; + +public class BukkitTaskMan extends TaskManager { + + private final Plugin plugin; + + public BukkitTaskMan(Plugin plugin) { + this.plugin = plugin; + } + + @Override + public int repeat(final Runnable r, final int interval) { + return plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, r, interval, interval); + } + + @Override + public int repeatAsync(final Runnable r, final int interval) { + return plugin.getServer().getScheduler().scheduleAsyncRepeatingTask(plugin, r, interval, interval); + } + + public MutableInt index = new MutableInt(0); + public HashMap tasks = new HashMap<>(); + + @Override + public void async(final Runnable r) { + if (r == null) { + return; + } + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, r).getTaskId(); + } + + @Override + public void task(final Runnable r) { + if (r == null) { + return; + } + plugin.getServer().getScheduler().runTask(plugin, r).getTaskId(); + } + + @Override + public void later(final Runnable r, final int delay) { + if (r == null) { + return; + } + plugin.getServer().getScheduler().runTaskLater(plugin, r, delay).getTaskId(); + } + + @Override + public void laterAsync(final Runnable r, final int delay) { + plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, r, delay); + } + + @Override + public void cancel(final int task) { + if (task != -1) { + Bukkit.getScheduler().cancelTask(task); + } + } +} diff --git a/src/com/boydti/fawe/bukkit/FaweBukkit.java b/src/com/boydti/fawe/bukkit/FaweBukkit.java new file mode 100644 index 00000000..d5e972a3 --- /dev/null +++ b/src/com/boydti/fawe/bukkit/FaweBukkit.java @@ -0,0 +1,201 @@ +package com.boydti.fawe.bukkit; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.IFawe; +import com.boydti.fawe.bukkit.regions.FactionsFeature; +import com.boydti.fawe.bukkit.regions.FactionsUUIDFeature; +import com.boydti.fawe.bukkit.regions.GriefPreventionFeature; +import com.boydti.fawe.bukkit.regions.PlotMeFeature; +import com.boydti.fawe.bukkit.regions.PlotSquaredFeature; +import com.boydti.fawe.bukkit.regions.PreciousStonesFeature; +import com.boydti.fawe.bukkit.regions.ResidenceFeature; +import com.boydti.fawe.bukkit.regions.TownyFeature; +import com.boydti.fawe.bukkit.regions.Worldguard; +import com.boydti.fawe.bukkit.v1_8.BukkitEditSessionWrapper_1_8; +import com.boydti.fawe.bukkit.v1_8.BukkitQueue_1_8; +import com.boydti.fawe.object.EditSessionWrapper; +import com.boydti.fawe.object.FaweCommand; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.regions.FaweMaskManager; +import com.boydti.fawe.util.FaweQueue; +import com.boydti.fawe.util.StringMan; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; + +public class FaweBukkit extends JavaPlugin implements IFawe { + + private VaultUtil vault; + private WorldEditPlugin worldedit; + + public VaultUtil getVault() { + return vault; + } + + public WorldEditPlugin getWorldEditPlugin() { + if (worldedit == null) { + worldedit = (WorldEditPlugin) Bukkit.getPluginManager().getPlugin("WorldEdit"); + } + return worldedit; + } + + @Override + public void onEnable() { + try { + Fawe.set(this); + } catch (Exception e) { + e.printStackTrace(); + getServer().shutdown(); + } + } + + @Override + public void debug(final String s) { + getLogger().info(s); + } + + @Override + public File getDirectory() { + return getDataFolder(); + } + + @Override + public void setupCommand(final String label, final FaweCommand cmd) { + getCommand(label).setExecutor(new BukkitCommand(cmd)); + } + + @Override + public FawePlayer wrap(final Object obj) { + if (obj.getClass() == String.class) { + return new BukkitPlayer(Bukkit.getPlayer((String) obj)); + } else if (obj instanceof Player) { + return new BukkitPlayer((Player) obj); + } else { + return null; + } + } + + @Override + public void setupWEListener() { + getServer().getPluginManager().registerEvents(new WEListener(), this); + } + + @Override + public void setupVault() { + try { + vault = new VaultUtil(); + } catch (final Throwable e) { + e.printStackTrace(); + } + } + + @Override + public TaskManager getTaskManager() { + return new BukkitTaskMan(this); + } + + @Override + public int[] getVersion() { + try { + final int[] version = new int[3]; + final String[] split = Bukkit.getBukkitVersion().split("-")[0].split("\\."); + version[0] = Integer.parseInt(split[0]); + version[1] = Integer.parseInt(split[1]); + if (split.length == 3) { + version[2] = Integer.parseInt(split[2]); + } + return version; + } catch (final Exception e) { + e.printStackTrace(); + debug(StringMan.getString(Bukkit.getBukkitVersion())); + debug(StringMan.getString(Bukkit.getBukkitVersion().split("-")[0].split("\\."))); + return new int[] { Integer.MAX_VALUE, 0, 0 }; + } + } + + @Override + public FaweQueue getQueue() { + return new BukkitQueue_1_8(); + } + + @Override + public EditSessionWrapper getEditSessionWrapper(final EditSession session) { + return new BukkitEditSessionWrapper_1_8(session); + } + + @Override + public Collection getMaskManagers() { + final Plugin worldguardPlugin = Bukkit.getServer().getPluginManager().getPlugin("WorldGuard"); + final ArrayList managers = new ArrayList<>(); + if ((worldguardPlugin != null) && worldguardPlugin.isEnabled()) { + managers.add(new Worldguard(worldguardPlugin, this)); + Fawe.debug("Plugin 'WorldGuard' found. Using it now."); + } else { + Fawe.debug("Plugin 'WorldGuard' not found. Worldguard features disabled."); + } + final Plugin plotmePlugin = Bukkit.getServer().getPluginManager().getPlugin("PlotMe"); + if ((plotmePlugin != null) && plotmePlugin.isEnabled()) { + managers.add(new PlotMeFeature(plotmePlugin, this)); + Fawe.debug("Plugin 'PlotMe' found. Using it now."); + } else { + Fawe.debug("Plugin 'PlotMe' not found. PlotMe features disabled."); + } + final Plugin townyPlugin = Bukkit.getServer().getPluginManager().getPlugin("Towny"); + if ((townyPlugin != null) && townyPlugin.isEnabled()) { + managers.add(new TownyFeature(townyPlugin, this)); + Fawe.debug("Plugin 'Towny' found. Using it now."); + } else { + Fawe.debug("Plugin 'Towny' not found. Towny features disabled."); + } + final Plugin factionsPlugin = Bukkit.getServer().getPluginManager().getPlugin("Factions"); + if ((factionsPlugin != null) && factionsPlugin.isEnabled()) { + try { + managers.add(new FactionsFeature(factionsPlugin, this)); + Fawe.debug("Plugin 'Factions' found. Using it now."); + } catch (final Throwable e) { + managers.add(new FactionsUUIDFeature(factionsPlugin, this)); + Fawe.debug("Plugin 'FactionsUUID' found. Using it now."); + } + } else { + Fawe.debug("Plugin 'Factions' not found. Factions features disabled."); + } + final Plugin residencePlugin = Bukkit.getServer().getPluginManager().getPlugin("Residence"); + if ((residencePlugin != null) && residencePlugin.isEnabled()) { + managers.add(new ResidenceFeature(residencePlugin, this)); + Fawe.debug("Plugin 'Residence' found. Using it now."); + } else { + Fawe.debug("Plugin 'Residence' not found. Factions features disabled."); + } + final Plugin griefpreventionPlugin = Bukkit.getServer().getPluginManager().getPlugin("GriefPrevention"); + if ((griefpreventionPlugin != null) && griefpreventionPlugin.isEnabled()) { + managers.add(new GriefPreventionFeature(griefpreventionPlugin, this)); + Fawe.debug("Plugin 'GriefPrevention' found. Using it now."); + } else { + Fawe.debug("Plugin 'GriefPrevention' not found. GriefPrevention features disabled."); + } + final Plugin plotsquaredPlugin = Bukkit.getServer().getPluginManager().getPlugin("PlotSquared"); + if ((plotsquaredPlugin != null) && plotsquaredPlugin.isEnabled()) { + managers.add(new PlotSquaredFeature(plotsquaredPlugin, this)); + Fawe.debug("Plugin 'PlotSquared' found. Using it now."); + } else { + Fawe.debug("Plugin 'PlotSquared' not found. PlotSquared features disabled."); + } + final Plugin preciousstonesPlugin = Bukkit.getServer().getPluginManager().getPlugin("PreciousStones"); + if ((preciousstonesPlugin != null) && preciousstonesPlugin.isEnabled()) { + managers.add(new PreciousStonesFeature(preciousstonesPlugin, this)); + Fawe.debug("Plugin 'PreciousStones' found. Using it now."); + } else { + Fawe.debug("Plugin 'PreciousStones' not found. PreciousStones features disabled."); + } + return managers; + } +} diff --git a/src/com/boydti/fawe/bukkit/VaultUtil.java b/src/com/boydti/fawe/bukkit/VaultUtil.java new file mode 100644 index 00000000..0f6e62b3 --- /dev/null +++ b/src/com/boydti/fawe/bukkit/VaultUtil.java @@ -0,0 +1,19 @@ +package com.boydti.fawe.bukkit; + +import net.milkbowl.vault.permission.Permission; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.RegisteredServiceProvider; + +public class VaultUtil { + public final Permission permission; + + public VaultUtil() { + final RegisteredServiceProvider permissionProvider = Bukkit.getServer().getServicesManager().getRegistration(net.milkbowl.vault.permission.Permission.class); + if (permissionProvider != null) { + permission = permissionProvider.getProvider(); + } else { + permission = null; + } + } +} diff --git a/src/com/boydti/fawe/bukkit/WEListener.java b/src/com/boydti/fawe/bukkit/WEListener.java new file mode 100644 index 00000000..c849ad16 --- /dev/null +++ b/src/com/boydti/fawe/bukkit/WEListener.java @@ -0,0 +1,311 @@ +package com.boydti.fawe.bukkit; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.RegionWrapper; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.Perm; +import com.boydti.fawe.util.WEManager; +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.IncompleteRegionException; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.LocalWorld; +import com.sk89q.worldedit.bukkit.BukkitUtil; +import com.sk89q.worldedit.regions.Region; + +@Deprecated +public class WEListener implements Listener { + + public final HashSet rad1 = new HashSet<>(Arrays.asList("forestgen", "pumpkins", "drain", "fixwater", "fixlava", "replacenear", "snow", "thaw", "ex", "butcher", "size")); + public final HashSet rad2 = new HashSet<>(Arrays.asList("fill", "fillr", "removenear", "remove")); + public final HashSet rad2_1 = new HashSet<>(Arrays.asList("hcyl", "cyl")); + public final HashSet rad2_2 = new HashSet<>(Arrays.asList("sphere", "pyramid")); + public final HashSet rad2_3 = new HashSet<>(Arrays.asList("brush smooth")); + public final HashSet rad3_1 = new HashSet<>(Arrays.asList("brush gravity")); + public final HashSet rad3_2 = new HashSet<>(Arrays.asList("brush sphere", "brush cylinder")); + + public final HashSet region = new HashSet<>(Arrays.asList("move", "set", "replace", "overlay", "walls", "outline", "deform", "hollow", "smooth", "naturalize", "paste", "count", "distr", + "copy", "cut", "green", "setbiome")); + public final HashSet regionExtend = new HashSet<>(Arrays.asList("stack")); + public final HashSet unregioned = new HashSet<>(Arrays.asList("paste", "redo", "undo", "rotate", "flip", "generate", "schematic", "schem")); + public final HashSet unsafe1 = new HashSet<>(Arrays.asList("cs", ".s", "restore", "snapshot", "delchunks", "listchunks")); + public final HashSet restricted = new HashSet<>(Arrays.asList("up")); + public final HashSet other = new HashSet<>(Arrays.asList("undo", "redo", "schematic", "schem", "count")); + + public boolean checkCommand(final List list, final String cmd) { + for (final String identifier : list) { + if (("/" + identifier).equals(cmd) || ("//" + identifier).equals(cmd) || ("/worldedit:/" + identifier).equals(cmd) || ("/worldedit:" + identifier).equals(cmd)) { + return true; + } + } + return false; + } + + public String reduceCmd(final String cmd, final boolean single) { + if (cmd.startsWith("/worldedit:/")) { + return cmd.substring(12); + } + if (cmd.startsWith("/worldedit:")) { + return cmd.substring(11); + } + if (cmd.startsWith("//")) { + return cmd.substring(2); + } + if (single && cmd.startsWith("/")) { + return cmd.substring(1); + } + return cmd; + } + + public int getInt(final String s) { + try { + int max = 0; + final String[] split = s.split(","); + for (final String rad : split) { + final int val = Integer.parseInt(rad); + if (val > max) { + max = val; + } + } + return max; + } catch (final NumberFormatException e) { + return 0; + } + } + + public boolean checkVolume(final FawePlayer player, final long volume, final long max, final Cancellable e) { + if (volume > max) { + MainUtil.sendMessage(FawePlayer.wrap(player.getName()), BBC.WORLDEDIT_VOLUME.s().replaceAll("%current%", volume + "").replaceAll("%max%", max + "")); + e.setCancelled(true); + } + if (Perm.hasPermission(player, "fawe.admin") && !Perm.hasPermission(player, "fawe.bypass")) { + BBC.WORLDEDIT_BYPASS.send(player); + } + return true; + } + + public boolean checkSelection(final FawePlayer player, final int modifier, final long max, final Cancellable e) { + LocalSession session = Fawe.get().getWorldEdit().getSession(player.getName()); + LocalWorld w = BukkitUtil.getLocalWorld(player.parent.getWorld()); + Region selection = null; + try { + selection = session.getSelection(w); + } catch (IncompleteRegionException e2) {} + if (selection == null) { + return true; + } + final BlockVector pos1 = selection.getMinimumPoint().toBlockVector(); + final BlockVector pos2 = selection.getMaximumPoint().toBlockVector(); + final HashSet mask = WEManager.IMP.getMask(player); + final RegionWrapper region = new RegionWrapper(pos1.getBlockX(), pos2.getBlockX(), pos1.getBlockZ(), pos2.getBlockZ()); + if (Settings.REQUIRE_SELECTION) { + String arg = null; + if (!WEManager.IMP.regionContains(region, mask)) { + arg = "pos1 + pos2"; + } else if (!WEManager.IMP.maskContains(mask, pos1.getBlockX(), pos1.getBlockZ())) { + arg = "pos1"; + } else if (!WEManager.IMP.maskContains(mask, pos2.getBlockX(), pos2.getBlockZ())) { + arg = "pos2"; + } + if (arg != null) { + BBC.REQUIRE_SELECTION_IN_MASK.send(player, arg); + e.setCancelled(true); + if (Perm.hasPermission(player, "fawe.admin") && !Perm.hasPermission(player, "fawe.bypass")) { + BBC.WORLDEDIT_BYPASS.send(player); + } + return true; + } + if (!WEManager.IMP.regionContains(region, mask)) { + BBC.REQUIRE_SELECTION_IN_MASK.send(player, "pos1 + pos2"); + e.setCancelled(true); + if (Perm.hasPermission(player, "fawe.admin") && !Perm.hasPermission(player, "fawe.bypass")) { + BBC.WORLDEDIT_BYPASS.send(player); + } + return true; + } + } + final long volume = Math.abs((pos1.getBlockX() - pos2.getBlockX()) * (pos1.getBlockY() - pos2.getBlockY()) * (pos1.getBlockZ() - pos2.getBlockZ())) * modifier; + return checkVolume(player, volume, max, e); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public boolean onPlayerCommand(final PlayerCommandPreprocessEvent e) { + final FawePlayer player = FawePlayer.wrap(e.getPlayer()); + final String message = e.getMessage(); + final String cmd = message.toLowerCase(); + final boolean single = true; + final String[] split = cmd.split(" "); + + final long maxVolume = Settings.WE_MAX_VOLUME; + final long maxIterations = Settings.WE_MAX_ITERATIONS; + // if (player.hasPermission("fawe.bypass")) { + // return true; + // } + if (split.length >= 2) { + final String reduced = reduceCmd(split[0], single); + final String reduced2 = reduceCmd(split[0] + " " + split[1], single); + if (rad1.contains(reduced)) { + if (WEManager.IMP.delay(player, message)) { + e.setCancelled(true); + return true; + } + final long volume = getInt(split[1]) * 256; + return checkVolume(player, volume, maxVolume, e); + } + if (rad2.contains(reduced)) { + if (WEManager.IMP.delay(player, message)) { + e.setCancelled(true); + return true; + } + if (split.length >= 3) { + final long volume = getInt(split[2]) * 256; + return checkVolume(player, volume, maxVolume, e); + } + return true; + } + if (rad2_1.contains(reduced)) { + if (WEManager.IMP.delay(player, message)) { + e.setCancelled(true); + return true; + } + if (split.length >= 4) { + final long volume = getInt(split[2]) * getInt(split[3]); + return checkVolume(player, volume, maxVolume, e); + } + return true; + } + if (rad2_2.contains(reduced)) { + if (WEManager.IMP.delay(player, message)) { + e.setCancelled(true); + return true; + } + if (split.length >= 3) { + final long radius = getInt(split[2]); + final long volume = radius * radius; + return checkVolume(player, volume, maxVolume, e); + } + return true; + } + if (rad2_3.contains(reduced2)) { + if (WEManager.IMP.delay(player, message)) { + e.setCancelled(true); + return true; + } + if (split.length >= 3) { + if (split.length == 4) { + final int iterations = getInt(split[3]); + if (iterations > maxIterations) { + MainUtil.sendMessage(player, BBC.WORLDEDIT_ITERATIONS.s().replaceAll("%current%", iterations + "").replaceAll("%max%", maxIterations + "")); + e.setCancelled(true); + if (Perm.hasPermission(player, "fawe.admin") && !Perm.hasPermission(player, "fawe.bypass")) { + BBC.WORLDEDIT_BYPASS.send(player); + } + return true; + } + } + final long radius = getInt(split[2]); + final long volume = radius * radius; + return checkVolume(player, volume, maxVolume, e); + } + return true; + } + if (rad3_1.contains(reduced2)) { + if (WEManager.IMP.delay(player, message)) { + e.setCancelled(true); + return true; + } + if (split.length >= 3) { + int i = 2; + if (split[i].equalsIgnoreCase("-h")) { + i = 3; + } + final long radius = getInt(split[i]); + final long volume = radius * radius; + return checkVolume(player, volume, maxVolume, e); + } + return true; + } + if (rad3_2.contains(reduced2)) { + if (WEManager.IMP.delay(player, message)) { + e.setCancelled(true); + return true; + } + if (split.length >= 4) { + int i = 3; + if (split[i].equalsIgnoreCase("-h")) { + i = 4; + } + final long radius = getInt(split[i]); + final long volume = radius * radius; + return checkVolume(player, volume, maxVolume, e); + } + return true; + } + if (regionExtend.contains(reduced)) { + if (WEManager.IMP.delay(player, message)) { + e.setCancelled(true); + return true; + } + return checkSelection(player, getInt(split[1]), maxVolume, e); + } + } + final String reduced = reduceCmd(split[0], single); + if (Settings.WE_BLACKLIST.contains(reduced)) { + BBC.WORLDEDIT_UNSAFE.send(player); + e.setCancelled(true); + if (Perm.hasPermission(player, "fawe.admin") && !Perm.hasPermission(player, "fawe.bypass")) { + BBC.WORLDEDIT_BYPASS.send(player); + } + } + if (restricted.contains(reduced)) { + final HashSet mask = WEManager.IMP.getMask(player); + Location loc = player.parent.getLocation(); + for (final RegionWrapper region : mask) { + if (region.isIn(loc.getBlockX(), loc.getBlockZ())) { + if (WEManager.IMP.delay(player, message)) { + e.setCancelled(true); + return true; + } + return true; + } + } + e.setCancelled(true); + BBC.REQUIRE_SELECTION_IN_MASK.send(player); + return true; + } + if (region.contains(reduced)) { + if (WEManager.IMP.delay(player, message)) { + e.setCancelled(true); + return true; + } + return checkSelection(player, 1, maxVolume, e); + } + if (unregioned.contains(reduced)) { + if (WEManager.IMP.delay(player, message)) { + e.setCancelled(true); + return true; + } + } + if (other.contains(reduced)) { + if (WEManager.IMP.delay(player, message)) { + e.setCancelled(true); + return true; + } + } + return true; + } +} diff --git a/src/com/boydti/fawe/bukkit/regions/BukkitMaskManager.java b/src/com/boydti/fawe/bukkit/regions/BukkitMaskManager.java new file mode 100644 index 00000000..308c985b --- /dev/null +++ b/src/com/boydti/fawe/bukkit/regions/BukkitMaskManager.java @@ -0,0 +1,12 @@ +package com.boydti.fawe.bukkit.regions; + +import org.bukkit.entity.Player; + +import com.boydti.fawe.regions.FaweMaskManager; + +public abstract class BukkitMaskManager extends FaweMaskManager { + + public BukkitMaskManager(final String plugin) { + super(plugin); + } +} diff --git a/src/com/boydti/fawe/bukkit/regions/FactionsFeature.java b/src/com/boydti/fawe/bukkit/regions/FactionsFeature.java new file mode 100644 index 00000000..a84b3144 --- /dev/null +++ b/src/com/boydti/fawe/bukkit/regions/FactionsFeature.java @@ -0,0 +1,48 @@ +package com.boydti.fawe.bukkit.regions; + +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; +import com.massivecraft.factions.entity.BoardColl; +import com.massivecraft.factions.entity.Faction; +import com.massivecraft.massivecore.ps.PS; + +public class FactionsFeature extends BukkitMaskManager implements Listener { + FaweBukkit plugin; + Plugin factions; + + public FactionsFeature(final Plugin factionsPlugin, final FaweBukkit p3) { + super(factionsPlugin.getName()); + factions = factionsPlugin; + plugin = p3; + BoardColl.get(); + } + + @Override + public FaweMask getMask(final FawePlayer fp) { + final Player player = fp.parent; + final Location loc = player.getLocation(); + final Faction fac = BoardColl.get().getFactionAt(PS.valueOf(loc)); + if (fac != null) { + if (fac.getOnlinePlayers().contains(player)) { + if (fac.getComparisonName().equals("wilderness") == false) { + final Chunk chunk = loc.getChunk(); + final Location pos1 = new Location(loc.getWorld(), chunk.getX() * 16, 0, chunk.getZ() * 16); + final Location pos2 = new Location(loc.getWorld(), (chunk.getX() * 16) + 15, 156, (chunk.getZ() * 16) + 15); + return new FaweMask(pos1, pos2) { + @Override + public String getName() { + return "CHUNK:" + loc.getChunk().getX() + "," + loc.getChunk().getZ(); + } + }; + } + } + } + return null; + } +} diff --git a/src/com/boydti/fawe/bukkit/regions/FactionsUUIDFeature.java b/src/com/boydti/fawe/bukkit/regions/FactionsUUIDFeature.java new file mode 100644 index 00000000..3d411bdc --- /dev/null +++ b/src/com/boydti/fawe/bukkit/regions/FactionsUUIDFeature.java @@ -0,0 +1,103 @@ +package com.boydti.fawe.bukkit.regions; + +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.RegionWrapper; +import com.boydti.fawe.util.Perm; +import com.massivecraft.factions.Board; +import com.massivecraft.factions.FLocation; +import com.massivecraft.factions.Faction; + +public class FactionsUUIDFeature extends BukkitMaskManager implements Listener { + private final Board instance; + + public FactionsUUIDFeature(final Plugin factionsPlugin, final FaweBukkit p3) { + super(factionsPlugin.getName()); + instance = Board.getInstance(); + } + + @Override + public FaweMask getMask(final FawePlayer fp) { + final Player player = fp.parent; + final Chunk chunk = player.getLocation().getChunk(); + final boolean perm = Perm.hasPermission(FawePlayer.wrap(player), "fawe.factions.wilderness"); + final RegionWrapper locs = new RegionWrapper(chunk.getX(), chunk.getX(), chunk.getZ(), chunk.getZ()); + final World world = player.getWorld(); + + int count = 32; + + if (isAdded(locs, world, player, perm)) { + boolean hasPerm = true; + + RegionWrapper chunkSelection; + while (hasPerm && (count > 0)) { + count--; + + hasPerm = false; + + chunkSelection = new RegionWrapper(locs.maxX + 1, locs.maxX + 1, locs.minZ, locs.maxZ); + + if (isAdded(chunkSelection, world, player, perm)) { + locs.maxX += 1; + hasPerm = true; + } + + chunkSelection = new RegionWrapper(locs.minX - 1, locs.minX - 1, locs.minZ, locs.maxZ); + + if (isAdded(chunkSelection, world, player, perm)) { + locs.minX -= 1; + hasPerm = true; + } + + chunkSelection = new RegionWrapper(locs.minX, locs.maxX, locs.maxZ + 1, locs.maxZ + 1); + + if (isAdded(chunkSelection, world, player, perm)) { + locs.maxZ += 1; + hasPerm = true; + } + + chunkSelection = new RegionWrapper(locs.minX, locs.maxX, locs.minZ - 1, locs.minZ - 1); + + if (isAdded(chunkSelection, world, player, perm)) { + locs.minZ -= 1; + hasPerm = true; + } + } + + final Location pos1 = new Location(world, locs.minX << 4, 1, locs.minZ << 4); + final Location pos2 = new Location(world, 15 + (locs.maxX << 4), 256, 15 + (locs.maxZ << 4)); + return new FaweMask(pos1, pos2) { + @Override + public String getName() { + return "CHUNK:" + pos1.getChunk().getX() + "," + pos1.getChunk().getZ(); + } + }; + } + return null; + } + + public boolean isAdded(final RegionWrapper locs, final World world, final Player player, final boolean perm) { + for (int x = locs.minX; x <= locs.maxX; x++) { + for (int z = locs.minZ; z <= locs.maxZ; z++) { + final Faction fac = instance.getFactionAt(new FLocation(world.getName(), x, z)); + if (fac == null) { + return false; + } + if (!fac.getOnlinePlayers().contains(player)) { + return false; + } + if (fac.isWilderness() && !perm) { + return false; + } + } + } + return true; + } +} diff --git a/src/com/boydti/fawe/bukkit/regions/FaweMask.java b/src/com/boydti/fawe/bukkit/regions/FaweMask.java new file mode 100644 index 00000000..3bfeb2fd --- /dev/null +++ b/src/com/boydti/fawe/bukkit/regions/FaweMask.java @@ -0,0 +1,97 @@ +package com.boydti.fawe.bukkit.regions; + +import java.util.Arrays; +import java.util.HashSet; + +import org.bukkit.Location; + +import com.boydti.fawe.object.RegionWrapper; + +public class FaweMask { + private String description = null; + private Location position1; + private Location position2; + + public FaweMask(final Location pos1, final Location pos2, final String id) { + if ((pos1 == null) || (pos2 == null)) { + throw new IllegalArgumentException("Locations cannot be null!"); + } + if (pos1.getWorld().equals(pos2.getWorld()) == false) { + throw new IllegalArgumentException("Locations must be in the same world!"); + } + description = id; + position1 = new Location(pos1.getWorld(), Math.min(pos1.getBlockX(), pos2.getBlockX()), 0, Math.min(pos1.getBlockZ(), pos2.getBlockZ())); + position2 = new Location(pos1.getWorld(), Math.max(pos1.getBlockX(), pos2.getBlockX()), 256, Math.max(pos1.getBlockZ(), pos2.getBlockZ())); + } + + public FaweMask(final Location pos1, final Location pos2) { + if ((pos1 == null) || (pos2 == null)) { + throw new IllegalArgumentException("Locations cannot be null!"); + } + if (pos1.getWorld().equals(pos2.getWorld()) == false) { + throw new IllegalArgumentException("Locations must be in the same world!"); + } + position1 = new Location(pos1.getWorld(), Math.min(pos1.getBlockX(), pos2.getBlockX()), 0, Math.min(pos1.getBlockZ(), pos2.getBlockZ())); + position2 = new Location(pos1.getWorld(), Math.max(pos1.getBlockX(), pos2.getBlockX()), 256, Math.max(pos1.getBlockZ(), pos2.getBlockZ())); + } + + public HashSet getRegions() { + final Location lower = getLowerBound(); + final Location upper = getUpperBound(); + return new HashSet<>(Arrays.asList(new RegionWrapper(lower.getBlockX(), upper.getBlockX(), lower.getBlockZ(), upper.getBlockZ()))); + } + + public String getName() { + return description; + } + + public Location getLowerBound() { + return position1; + } + + public Location getUpperBound() { + return position2; + } + + public void setBounds(final Location pos1, final Location pos2) { + if ((pos1 == null) || (pos2 == null)) { + throw new IllegalArgumentException("Locations cannot be null!"); + } + if (pos1.getWorld().equals(pos2.getWorld()) == false) { + throw new IllegalArgumentException("Locations must be in the same world!"); + } + position1 = new Location(pos1.getWorld(), Math.min(pos1.getBlockX(), pos2.getBlockX()), 0, Math.min(pos1.getBlockZ(), pos2.getBlockZ())); + position2 = new Location(pos1.getWorld(), Math.max(pos1.getBlockX(), pos2.getBlockX()), 256, Math.max(pos1.getBlockZ(), pos2.getBlockZ())); + } + + public Location[] getBounds() { + final Location[] locations = { position1, position2 }; + return locations; + } + + public boolean contains(final Location loc) { + if (position1.getWorld().equals(loc.getWorld())) { + if (loc.getBlockX() < position1.getBlockX()) { + return false; + } + if (loc.getBlockX() > position2.getBlockX()) { + return false; + } + if (loc.getBlockZ() < position1.getBlockZ()) { + return false; + } + if (loc.getBlockZ() > position2.getBlockZ()) { + return false; + } + if (loc.getBlockY() < position1.getBlockY()) { + return false; + } + if (loc.getBlockY() > position2.getBlockY()) { + return false; + } + } else { + return false; + } + return true; + } +} diff --git a/src/com/boydti/fawe/bukkit/regions/GriefPreventionFeature.java b/src/com/boydti/fawe/bukkit/regions/GriefPreventionFeature.java new file mode 100644 index 00000000..6ce531d9 --- /dev/null +++ b/src/com/boydti/fawe/bukkit/regions/GriefPreventionFeature.java @@ -0,0 +1,46 @@ +package com.boydti.fawe.bukkit.regions; + +import me.ryanhamshire.GriefPrevention.Claim; +import me.ryanhamshire.GriefPrevention.GriefPrevention; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; + +public class GriefPreventionFeature extends BukkitMaskManager implements Listener { + FaweBukkit plugin; + Plugin griefprevention; + + public GriefPreventionFeature(final Plugin griefpreventionPlugin, final FaweBukkit p3) { + super(griefpreventionPlugin.getName()); + griefprevention = griefpreventionPlugin; + plugin = p3; + } + + @Override + public FaweMask getMask(final FawePlayer fp) { + final Player player = fp.parent; + final Location location = player.getLocation(); + final Claim claim = GriefPrevention.instance.dataStore.getClaimAt(location, true, null); + if (claim != null) { + final String uuid = player.getUniqueId().toString(); + if (claim.getOwnerName().equalsIgnoreCase(player.getName()) || claim.getOwnerName().equals(uuid)) { + claim.getGreaterBoundaryCorner().getBlockX(); + final Location pos1 = new Location(location.getWorld(), claim.getLesserBoundaryCorner().getBlockX(), 0, claim.getLesserBoundaryCorner().getBlockZ()); + final Location pos2 = new Location(location.getWorld(), claim.getGreaterBoundaryCorner().getBlockX(), 256, claim.getGreaterBoundaryCorner().getBlockZ()); + return new FaweMask(pos1, pos2) { + @Override + public String getName() { + return "CLAIM:" + claim.toString(); + } + }; + } + } + return null; + + } +} diff --git a/src/com/boydti/fawe/bukkit/regions/PlotMeFeature.java b/src/com/boydti/fawe/bukkit/regions/PlotMeFeature.java new file mode 100644 index 00000000..72cd81e3 --- /dev/null +++ b/src/com/boydti/fawe/bukkit/regions/PlotMeFeature.java @@ -0,0 +1,50 @@ +package com.boydti.fawe.bukkit.regions; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; +import com.worldcretornica.plotme_core.Plot; +import com.worldcretornica.plotme_core.PlotMe_Core; +import com.worldcretornica.plotme_core.bukkit.PlotMe_CorePlugin; +import com.worldcretornica.plotme_core.bukkit.api.BukkitPlayer; +import com.worldcretornica.plotme_core.bukkit.api.BukkitWorld; + +public class PlotMeFeature extends BukkitMaskManager implements Listener { + FaweBukkit plugin; + PlotMe_Core plotme; + + public PlotMeFeature(final Plugin plotmePlugin, final FaweBukkit p3) { + super(plotmePlugin.getName()); + plotme = ((PlotMe_CorePlugin) plotmePlugin).getAPI(); + plugin = p3; + + } + + @Override + public FaweMask getMask(final FawePlayer fp) { + final Player player = fp.parent; + final Location location = player.getLocation(); + final Plot plot = plotme.getPlotMeCoreManager().getPlotById(new BukkitPlayer(player)); + if (plot == null) { + return null; + } + final boolean isallowed = plot.isAllowed(player.getUniqueId()); + if (isallowed) { + final Location pos1 = new Location(location.getWorld(), plotme.getGenManager(player.getWorld().getName()).bottomX(plot.getId(), new BukkitWorld(player.getWorld())), 0, plotme + .getGenManager(player.getWorld().getName()).bottomZ(plot.getId(), new BukkitWorld(player.getWorld()))); + final Location pos2 = new Location(location.getWorld(), plotme.getGenManager(player.getWorld().getName()).topX(plot.getId(), new BukkitWorld(player.getWorld())), 256, plotme + .getGenManager(player.getWorld().getName()).topZ(plot.getId(), new BukkitWorld(player.getWorld()))); + return new FaweMask(pos1, pos2) { + @Override + public String getName() { + return plot.getId(); + } + }; + } + return null; + } +} diff --git a/src/com/boydti/fawe/bukkit/regions/PlotSquaredFeature.java b/src/com/boydti/fawe/bukkit/regions/PlotSquaredFeature.java new file mode 100644 index 00000000..d6fe60be --- /dev/null +++ b/src/com/boydti/fawe/bukkit/regions/PlotSquaredFeature.java @@ -0,0 +1,86 @@ +package com.boydti.fawe.bukkit.regions; + +import java.util.HashSet; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.RegionWrapper; +import com.intellectualcrafters.plot.PS; +import com.intellectualcrafters.plot.object.Plot; +import com.intellectualcrafters.plot.object.PlotId; +import com.intellectualcrafters.plot.object.PlotPlayer; +import com.intellectualcrafters.plot.util.MainUtil; +import com.plotsquared.bukkit.BukkitMain; + +public class PlotSquaredFeature extends BukkitMaskManager implements Listener { + FaweBukkit plugin; + + public PlotSquaredFeature(final Plugin plotPlugin, final FaweBukkit p3) { + super(plotPlugin.getName()); + plugin = p3; + BukkitMain.worldEdit = null; + PS.get().worldedit = null; + } + + @Override + public FaweMask getMask(final FawePlayer fp) { + final PlotPlayer pp = PlotPlayer.wrap(fp.parent); + Plot plot = pp.getCurrentPlot(); + if (plot == null) { + final com.intellectualcrafters.plot.object.Location loc = pp.getLocation(); + final String world = loc.getWorld(); + int min = Integer.MAX_VALUE; + for (final Plot p : pp.getPlots()) { + if (p.world.equals(world)) { + final double d = p.getHome().getEuclideanDistanceSquared(loc); + if (d < min) { + min = (int) d; + plot = p; + } + } + } + } + if (plot != null) { + final PlotId id = plot.id; + boolean hasPerm = false; + if (plot.owner != null) { + if (plot.owner.equals(pp.getUUID())) { + hasPerm = true; + } else if (plot.isAdded(pp.getUUID()) && pp.hasPermission("fawe.plotsquared.member")) { + hasPerm = true; + } + if (hasPerm) { + final World world = fp.parent.getWorld(); + final com.intellectualcrafters.plot.object.RegionWrapper region = MainUtil.getLargestRegion(plot); + final HashSet regions = MainUtil.getRegions(plot); + + final Location pos1 = new Location(world, region.minX, 0, region.minZ); + final Location pos2 = new Location(world, region.maxX, 256, region.maxZ); + + final HashSet faweRegions = new HashSet(); + for (final com.intellectualcrafters.plot.object.RegionWrapper current : regions) { + faweRegions.add(new RegionWrapper(current.minX, current.maxX, current.minZ, current.maxZ)); + } + return new FaweMask(pos1, pos2) { + @Override + public String getName() { + return "PLOT^2:" + id; + } + + @Override + public HashSet getRegions() { + return faweRegions; + } + }; + } + } + } + return null; + } +} diff --git a/src/com/boydti/fawe/bukkit/regions/PreciousStonesFeature.java b/src/com/boydti/fawe/bukkit/regions/PreciousStonesFeature.java new file mode 100644 index 00000000..61714a01 --- /dev/null +++ b/src/com/boydti/fawe/bukkit/regions/PreciousStonesFeature.java @@ -0,0 +1,47 @@ +package com.boydti.fawe.bukkit.regions; + +import java.util.List; + +import net.sacredlabyrinth.Phaed.PreciousStones.FieldFlag; +import net.sacredlabyrinth.Phaed.PreciousStones.PreciousStones; +import net.sacredlabyrinth.Phaed.PreciousStones.vectors.Field; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; + +public class PreciousStonesFeature extends BukkitMaskManager implements Listener { + FaweBukkit plugin; + Plugin preciousstones; + + public PreciousStonesFeature(final Plugin preciousstonesPlugin, final FaweBukkit p3) { + super(preciousstonesPlugin.getName()); + preciousstones = preciousstonesPlugin; + plugin = p3; + + } + + @Override + public FaweMask getMask(final FawePlayer fp) { + final Player player = fp.parent; + final Location location = player.getLocation(); + final List fields = PreciousStones.API().getFieldsProtectingArea(FieldFlag.PLOT, location); + for (final Field myfield : fields) { + if (myfield.getOwner().equalsIgnoreCase(player.getName()) || (myfield.getAllowed().contains(player.getName()))) { + final Location pos1 = new Location(location.getWorld(), myfield.getCorners().get(0).getBlockX(), myfield.getCorners().get(0).getBlockY(), myfield.getCorners().get(0).getBlockZ()); + final Location pos2 = new Location(location.getWorld(), myfield.getCorners().get(1).getBlockX(), myfield.getCorners().get(1).getBlockY(), myfield.getCorners().get(1).getBlockZ()); + return new FaweMask(pos1, pos2) { + @Override + public String getName() { + return "FIELD:" + myfield.toString(); + } + }; + } + } + return null; + } +} diff --git a/src/com/boydti/fawe/bukkit/regions/ResidenceFeature.java b/src/com/boydti/fawe/bukkit/regions/ResidenceFeature.java new file mode 100644 index 00000000..e455cb41 --- /dev/null +++ b/src/com/boydti/fawe/bukkit/regions/ResidenceFeature.java @@ -0,0 +1,45 @@ +package com.boydti.fawe.bukkit.regions; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +import com.bekvon.bukkit.residence.Residence; +import com.bekvon.bukkit.residence.protection.ClaimedResidence; +import com.bekvon.bukkit.residence.protection.CuboidArea; +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; + +public class ResidenceFeature extends BukkitMaskManager implements Listener { + FaweBukkit plugin; + Plugin residence; + + public ResidenceFeature(final Plugin residencePlugin, final FaweBukkit p3) { + super(residencePlugin.getName()); + residence = residencePlugin; + plugin = p3; + + } + + @Override + public FaweMask getMask(final FawePlayer fp) { + final Player player = fp.parent; + final Location location = player.getLocation(); + final ClaimedResidence residence = Residence.getResidenceManager().getByLoc(location); + if (residence != null) { + if (residence.getPlayersInResidence().contains(player)) { + final CuboidArea area = residence.getAreaArray()[0]; + final Location pos1 = area.getHighLoc(); + final Location pos2 = area.getLowLoc(); + return new FaweMask(pos1, pos2) { + @Override + public String getName() { + return "RESIDENCE: " + residence.getName(); + } + }; + } + } + return null; + } +} diff --git a/src/com/boydti/fawe/bukkit/regions/TownyFeature.java b/src/com/boydti/fawe/bukkit/regions/TownyFeature.java new file mode 100644 index 00000000..bfc8206e --- /dev/null +++ b/src/com/boydti/fawe/bukkit/regions/TownyFeature.java @@ -0,0 +1,72 @@ +package com.boydti.fawe.bukkit.regions; + +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; +import com.palmergames.bukkit.towny.Towny; +import com.palmergames.bukkit.towny.object.PlayerCache; +import com.palmergames.bukkit.towny.object.TownBlock; +import com.palmergames.bukkit.towny.object.TownyUniverse; +import com.palmergames.bukkit.towny.object.WorldCoord; + +public class TownyFeature extends BukkitMaskManager implements Listener { + FaweBukkit plugin; + Plugin towny; + + public TownyFeature(final Plugin townyPlugin, final FaweBukkit p3) { + super(townyPlugin.getName()); + towny = townyPlugin; + plugin = p3; + } + + @Override + public FaweMask getMask(final FawePlayer fp) { + final Player player = fp.parent; + final Location location = player.getLocation(); + try { + final PlayerCache cache = ((Towny) towny).getCache(player); + final WorldCoord mycoord = cache.getLastTownBlock(); + if (mycoord == null) { + return null; + } else { + final TownBlock myplot = mycoord.getTownBlock(); + if (myplot == null) { + return null; + } else { + boolean isMember = false; + try { + if (myplot.getResident().getName().equals(player.getName())) { + isMember = true; + } + } catch (final Exception e) { + + } + if (!isMember) { + if (player.hasPermission("fawe.towny.*")) { + isMember = true; + } else if (myplot.getTown().isMayor(TownyUniverse.getDataSource().getResident(player.getName()))) { + isMember = true; + } + } + if (isMember) { + final Chunk chunk = location.getChunk(); + final Location pos1 = new Location(location.getWorld(), chunk.getX() * 16, 0, chunk.getZ() * 16); + final Location pos2 = new Location(location.getWorld(), (chunk.getX() * 16) + 15, 156, (chunk.getZ() * 16) + 15); + return new FaweMask(pos1, pos2) { + @Override + public String getName() { + return "PLOT:" + location.getChunk().getX() + "," + location.getChunk().getZ(); + } + }; + } + } + } + } catch (final Exception e) {} + return null; + } +} diff --git a/src/com/boydti/fawe/bukkit/regions/Worldguard.java b/src/com/boydti/fawe/bukkit/regions/Worldguard.java new file mode 100644 index 00000000..e5ef2d3e --- /dev/null +++ b/src/com/boydti/fawe/bukkit/regions/Worldguard.java @@ -0,0 +1,93 @@ +package com.boydti.fawe.bukkit.regions; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.object.FawePlayer; +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +public class Worldguard extends BukkitMaskManager implements Listener { + WorldGuardPlugin worldguard; + FaweBukkit plugin; + + private WorldGuardPlugin getWorldGuard() { + final Plugin plugin = Bukkit.getPluginManager().getPlugin("WorldGuard"); + + // WorldGuard may not be loaded + if ((plugin == null) || !(plugin instanceof WorldGuardPlugin)) { + return null; // Maybe you want throw an exception instead + } + + return (WorldGuardPlugin) plugin; + } + + public Worldguard(final Plugin p2, final FaweBukkit p3) { + super(p2.getName()); + worldguard = getWorldGuard(); + plugin = p3; + + } + + public ProtectedRegion isowner(final Player player, final Location location) { + final com.sk89q.worldguard.LocalPlayer localplayer = worldguard.wrapPlayer(player); + final RegionManager manager = worldguard.getRegionManager(player.getWorld()); + final ApplicableRegionSet regions = manager.getApplicableRegions(player.getLocation()); + for (final ProtectedRegion region : regions) { + if (region.isOwner(localplayer)) { + return region; + } else if (region.getId().toLowerCase().equals(player.getName().toLowerCase())) { + return region; + } else if (region.getId().toLowerCase().contains(player.getName().toLowerCase() + "//")) { + return region; + } else if (region.isOwner("*")) { + return region; + } + } + return null; + } + + public ProtectedRegion getregion(final Player player, final BlockVector location) { + final com.sk89q.worldguard.LocalPlayer localplayer = worldguard.wrapPlayer(player); + final ApplicableRegionSet regions = worldguard.getRegionManager(player.getWorld()).getApplicableRegions(location); + for (final ProtectedRegion region : regions) { + if (region.isOwner(localplayer)) { + return region; + } else if (region.getId().toLowerCase().equals(player.getName().toLowerCase())) { + return region; + } else if (region.getId().toLowerCase().contains(player.getName().toLowerCase() + "//")) { + return region; + } else if (region.isOwner("*")) { + return region; + } + } + return null; + } + + @Override + public FaweMask getMask(final FawePlayer fp) { + final Player player = fp.parent; + final Location location = player.getLocation(); + final ProtectedRegion myregion = isowner(player, location); + if (myregion != null) { + final Location pos1 = new Location(location.getWorld(), myregion.getMinimumPoint().getBlockX(), myregion.getMinimumPoint().getBlockY(), myregion.getMinimumPoint().getBlockZ()); + final Location pos2 = new Location(location.getWorld(), myregion.getMaximumPoint().getBlockX(), myregion.getMaximumPoint().getBlockY(), myregion.getMaximumPoint().getBlockZ()); + return new FaweMask(pos1, pos2) { + @Override + public String getName() { + return myregion.getId(); + } + }; + } else { + return null; + } + + } +} diff --git a/src/com/boydti/fawe/bukkit/v0/BukkitEditSessionWrapper_0.java b/src/com/boydti/fawe/bukkit/v0/BukkitEditSessionWrapper_0.java new file mode 100644 index 00000000..61cfeae4 --- /dev/null +++ b/src/com/boydti/fawe/bukkit/v0/BukkitEditSessionWrapper_0.java @@ -0,0 +1,31 @@ +package com.boydti.fawe.bukkit.v0; + +import com.boydti.fawe.logging.BlocksHubHook; +import com.boydti.fawe.object.EditSessionWrapper; +import com.boydti.fawe.object.FawePlayer; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.history.changeset.ChangeSet; + +public class BukkitEditSessionWrapper_0 extends EditSessionWrapper { + + private BlocksHubHook hook; + + public BukkitEditSessionWrapper_0(EditSession session) { + super(session); + try { + this.hook = new BlocksHubHook(); + } catch (Throwable e) { + + } + } + + @Override + public Extent getHistoryExtent(Extent parent, ChangeSet set, FawePlayer player) { + if (hook != null) { + return hook.getLoggingExtent(parent, set, player); + } + return super.getHistoryExtent(parent, set, player); + } + +} diff --git a/src/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java b/src/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java new file mode 100644 index 00000000..46651bf3 --- /dev/null +++ b/src/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java @@ -0,0 +1,183 @@ +package com.boydti.fawe.bukkit.v0; + +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.plugin.Plugin; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.ChunkLoc; +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.util.FaweQueue; +import com.boydti.fawe.util.SetBlockQueue; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.worldedit.world.biome.BaseBiome; + +public abstract class BukkitQueue_0 extends FaweQueue implements Listener { + + private final HashMap> toLight = new HashMap<>(); + + public BukkitQueue_0() { + TaskManager.IMP.task(new Runnable() { + @Override + public void run() { + Bukkit.getPluginManager().registerEvents(BukkitQueue_0.this, (Plugin) Fawe.imp()); + } + }); + } + + @EventHandler + public void onMove(PlayerMoveEvent event) { + Location loc = event.getTo(); + if (!loc.getChunk().equals(event.getFrom().getChunk())) { + Chunk chunk = loc.getChunk(); + ChunkLoc cl = new ChunkLoc(chunk.getWorld().getName(), chunk.getX(), chunk.getZ()); + } + } + + @EventHandler + public void onChunkLoad(ChunkLoadEvent event) { + if (toLight.size() == 0) { + return; + } + Chunk chunk = event.getChunk(); + ChunkLoc loc = new ChunkLoc(chunk.getWorld().getName(), chunk.getX(), chunk.getZ()); + for (int x = -1; x <= 1; x++) { + for (int z = -1; z <= 1; z++) { + ChunkLoc a = new ChunkLoc(loc.world, loc.x + x, loc.z + z); + if (toLight.containsKey(a)) { + if (fixLighting(toLight.get(a), Settings.FIX_ALL_LIGHTING)) { + toLight.remove(a); + return; + } + } + } + } + } + + private final ConcurrentHashMap> blocks = new ConcurrentHashMap<>(); + + @Override + public boolean setBlock(final String world, int x, final int y, int z, final short id, final byte data) { + if ((y > 255) || (y < 0)) { + return false; + } + final ChunkLoc wrap = new ChunkLoc(world, x >> 4, z >> 4); + x = x & 15; + z = z & 15; + FaweChunk result = blocks.get(wrap); + if (result == null) { + result = getChunk(wrap); + result.setBlock(x, y, z, id, data); + final FaweChunk previous = blocks.put(wrap, result); + if (previous == null) { + return true; + } + blocks.put(wrap, previous); + result = previous; + } + result.setBlock(x, y, z, id, data); + return true; + } + + @Override + public boolean setBiome(String world, int x, int z, BaseBiome biome) { + final ChunkLoc wrap = new ChunkLoc(world, x >> 4, z >> 4); + x = x & 15; + z = z & 15; + FaweChunk result = blocks.get(wrap); + if (result == null) { + result = getChunk(wrap); + final FaweChunk previous = blocks.put(wrap, result); + if (previous != null) { + blocks.put(wrap, previous); + result = previous; + } + } + result.setBiome(x, z, biome); + return true; + } + + @Override + public FaweChunk next() { + try { + if (blocks.size() == 0) { + return null; + } + final Iterator>> iter = blocks.entrySet().iterator(); + final FaweChunk toReturn = iter.next().getValue(); + if (SetBlockQueue.IMP.isWaiting()) { + return null; + } + iter.remove(); + execute(toReturn); + return toReturn; + } catch (final Throwable e) { + e.printStackTrace(); + return null; + } + } + + private final ArrayDeque> toUpdate = new ArrayDeque<>(); + + public boolean execute(final FaweChunk fc) { + if (fc == null) { + return false; + } + // Load chunk + final Chunk chunk = fc.getChunk(); + chunk.load(true); + // Set blocks / entities / biome + if (!setComponents(fc)) { + return false; + } + toUpdate.add(fc); + // Fix lighting + SetBlockQueue.IMP.addTask(new Runnable() { + + @Override + public void run() { + if (toUpdate.size() == 0) { + return; + } + for (FaweChunk fc : sendChunk(toUpdate)) { + toLight.put(fc.getChunkLoc(), fc); + } + toUpdate.clear(); + } + }); + return true; + } + + @Override + public void clear() { + blocks.clear(); + } + + @Override + public void setChunk(FaweChunk chunk) { + blocks.put(chunk.getChunkLoc(), (FaweChunk) chunk); + } + + public abstract Collection> sendChunk(final Collection> fcs); + + public abstract boolean setComponents(final FaweChunk fc); + + @Override + public abstract FaweChunk getChunk(final ChunkLoc wrap); + + @Override + public abstract boolean fixLighting(FaweChunk fc, boolean fixAll); +} diff --git a/src/com/boydti/fawe/bukkit/v1_8/BukkitChunk_1_8.java b/src/com/boydti/fawe/bukkit/v1_8/BukkitChunk_1_8.java new file mode 100644 index 00000000..1579d04a --- /dev/null +++ b/src/com/boydti/fawe/bukkit/v1_8/BukkitChunk_1_8.java @@ -0,0 +1,233 @@ +package com.boydti.fawe.bukkit.v1_8; + +import java.util.Arrays; + +import org.bukkit.Bukkit; +import org.bukkit.Chunk; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.object.ChunkLoc; +import com.boydti.fawe.object.FaweChunk; +import com.sk89q.worldedit.world.biome.BaseBiome; + +public class BukkitChunk_1_8 extends FaweChunk { + + private char[][] ids; + + private final short[] count; + private final short[] air; + private final short[] relight; + private int[][] biomes; + + public Chunk chunk; + + /** + * A FaweSections object represents a chunk and the blocks that you wish to change in it. + */ + protected BukkitChunk_1_8(final ChunkLoc chunk) { + super(chunk); + ids = new char[16][]; + count = new short[16]; + air = new short[16]; + relight = new short[16]; + } + + @Override + public Chunk getChunk() { + if (chunk == null) { + final ChunkLoc cl = getChunkLoc(); + chunk = Bukkit.getWorld(cl.world).getChunkAt(cl.x, cl.z); + } + return chunk; + } + + @Override + public void setChunkLoc(final ChunkLoc loc) { + super.setChunkLoc(loc); + chunk = null; + } + + /** + * Get the number of block changes in a specified section + * @param i + * @return + */ + public int getCount(final int i) { + return count[i]; + } + + public int getAir(final int i) { + return air[i]; + } + + public void setCount(int i, short value) { + count[i] = value; + } + + /** + * Get the number of block changes in a specified section + * @param i + * @return + */ + public int getRelight(final int i) { + return relight[i]; + } + + public int getTotalCount() { + int total = 0; + for (int i = 0; i < 16; i++) { + total += count[i]; + } + return total; + } + + public int getTotalRelight() { + if (getTotalCount() == 0 && biomes == null) { + Arrays.fill(count, (short) 1); + Arrays.fill(relight, Short.MAX_VALUE); + return Short.MAX_VALUE; + } + int total = 0; + for (int i = 0; i < 16; i++) { + total += relight[i]; + } + return total; + } + + /** + * Get the raw data for a section + * @param i + * @return + */ + public char[] getIdArray(final int i) { + return ids[i]; + } + + public void clear() { + ids = null; + biomes = null; + } + public int[][] getBiomeArray() { + return biomes; + } + + @Override + public void setBlock(final int x, final int y, final int z, final int id, byte data) { + final int i = FaweCache.CACHE_I[y][x][z]; + final int j = FaweCache.CACHE_J[y][x][z]; + char[] vs = ids[i]; + if (vs == null) { + vs = ids[i] = new char[4096]; + count[i]++; + } else if (vs[j] == 0) { + count[i]++; + } + switch (id) { + case 0: + air[i]++; + vs[j] = (char) 1; + return; + case 10: + case 11: + case 39: + case 40: + case 50: + case 51: + case 74: + case 76: + case 89: + case 122: + case 124: + case 138: + case 169: + relight[i]++; + case 2: + case 4: + case 13: + case 14: + case 15: + case 20: + case 21: + case 22: + case 30: + case 32: + case 37: + case 41: + case 42: + case 45: + case 46: + case 47: + case 48: + case 49: + case 55: + case 56: + case 57: + case 58: + case 60: + case 7: + case 8: + case 9: + case 73: + case 75: + case 78: + case 79: + case 80: + case 81: + case 82: + case 83: + case 85: + case 87: + case 88: + case 101: + case 102: + case 103: + case 110: + case 112: + case 113: + case 121: + case 129: + case 133: + case 165: + case 166: + case 170: + case 172: + case 173: + case 174: + case 181: + case 182: + case 188: + case 189: + case 190: + case 191: + case 192: + vs[j] = (char) (id << 4); + return; + case 130: + case 62: + relight[i]++; + case 54: + case 146: + case 61: + case 65: + case 68: + if (data < 2) { + data = 2; + } + default: + vs[j] = (char) ((id << 4) + data); + return; + } + } + + @Override + public void setBiome(int x, int z, BaseBiome biome) { + if (biomes == null) { + biomes = new int[16][]; + } + int[] index = biomes[x]; + if (index == null) { + index = biomes[x] = new int[16]; + } + index[z] = biome.getId(); + } +} diff --git a/src/com/boydti/fawe/bukkit/v1_8/BukkitEditSessionWrapper_1_8.java b/src/com/boydti/fawe/bukkit/v1_8/BukkitEditSessionWrapper_1_8.java new file mode 100644 index 00000000..a36323c1 --- /dev/null +++ b/src/com/boydti/fawe/bukkit/v1_8/BukkitEditSessionWrapper_1_8.java @@ -0,0 +1,167 @@ +package com.boydti.fawe.bukkit.v1_8; + +import static com.boydti.fawe.util.ReflectionUtils.getRefClass; + +import org.bukkit.Bukkit; + +import com.boydti.fawe.bukkit.v0.BukkitEditSessionWrapper_0; +import com.boydti.fawe.util.ReflectionUtils.RefClass; +import com.boydti.fawe.util.ReflectionUtils.RefField; +import com.boydti.fawe.util.ReflectionUtils.RefMethod; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.blocks.BlockType; + +public class BukkitEditSessionWrapper_1_8 extends BukkitEditSessionWrapper_0 { + + private final RefClass classCraftWorld = getRefClass("{cb}.CraftWorld"); + private final RefClass classChunk = getRefClass("{nms}.Chunk"); + private final RefClass classWorld = getRefClass("{nms}.World"); + + private RefMethod worldGetHandle; + private RefMethod methodGetChunkAt; + private RefField heightMap; + private Object nmsWorld; + + private int lastXMin; + private int lastZMin; + private Object lastChunk; + + public BukkitEditSessionWrapper_1_8(final EditSession session) { + super(session); + try { + worldGetHandle = classCraftWorld.getMethod("getHandle"); + methodGetChunkAt = classWorld.getMethod("getChunkAt", int.class, int.class); + heightMap = classChunk.getField("heightMap"); + nmsWorld = worldGetHandle.of(Bukkit.getWorld(session.getWorld().getName())).call(); + } catch (final Exception e) { + e.printStackTrace(); + } + } + + @Override + public int getHighestTerrainBlock(final int x, final int z, final int minY, final int maxY, final boolean naturalOnly) { + final int bx = x >> 4; + final int bz = z >> 4; + int[] heights; + if ((lastChunk == null) || (bx != lastXMin) || (bz != lastZMin)) { + lastXMin = bx; + lastZMin = bz; + lastChunk = methodGetChunkAt.of(nmsWorld).call(bx, bz); + } + if (lastChunk != null) { + heights = (int[]) heightMap.of(lastChunk).get(); + final int lx = x & 15; + final int lz = z & 15; + final int height = heights[((lz << 4) | lx)]; + if ((height <= maxY) && (height >= minY)) { + final Vector pt = new Vector(x, height, z); + final int id = session.getBlockType(pt); + if (naturalOnly ? BlockType.isNaturalTerrainBlock(id, 0) : !BlockType.canPassThrough(id, 0)) { + return height; + } + } + } + for (int y = maxY; y >= minY; --y) { + final Vector pt = new Vector(x, y, z); + final int id = session.getBlockType(pt); + int data; + switch (id) { + case 0: { + continue; + } + case 2: + case 4: + case 13: + case 14: + case 15: + case 20: + case 21: + case 22: + case 25: + case 30: + case 32: + case 37: + case 39: + case 40: + case 41: + case 42: + case 45: + case 46: + case 47: + case 48: + case 49: + case 50: + case 51: + case 52: + case 54: + case 55: + case 56: + case 57: + case 58: + case 60: + case 61: + case 62: + case 7: + case 8: + case 9: + case 10: + case 11: + case 73: + case 74: + case 75: + case 76: + case 78: + case 79: + case 80: + case 81: + case 82: + case 83: + case 84: + case 85: + case 87: + case 88: + case 101: + case 102: + case 103: + case 110: + case 112: + case 113: + case 117: + case 121: + case 122: + case 123: + case 124: + case 129: + case 133: + case 138: + case 137: + case 140: + case 165: + case 166: + case 169: + case 170: + case 172: + case 173: + case 174: + case 176: + case 177: + case 181: + case 182: + case 188: + case 189: + case 190: + case 191: + case 192: + return y; + default: + data = 0; + } + if (naturalOnly ? BlockType.isNaturalTerrainBlock(id, data) : !BlockType.canPassThrough(id, data)) { + return y; + } + } + return minY; + } + +} diff --git a/src/com/boydti/fawe/bukkit/v1_8/BukkitQueue_1_8.java b/src/com/boydti/fawe/bukkit/v1_8/BukkitQueue_1_8.java new file mode 100644 index 00000000..0387969c --- /dev/null +++ b/src/com/boydti/fawe/bukkit/v1_8/BukkitQueue_1_8.java @@ -0,0 +1,693 @@ +package com.boydti.fawe.bukkit.v1_8; + +import static com.boydti.fawe.util.ReflectionUtils.getRefClass; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.World.Environment; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.generator.BlockPopulator; +import org.bukkit.generator.ChunkGenerator; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.bukkit.v0.BukkitQueue_0; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.ChunkLoc; +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.IntegerPair; +import com.boydti.fawe.util.MemUtil; +import com.boydti.fawe.util.ReflectionUtils.RefClass; +import com.boydti.fawe.util.ReflectionUtils.RefConstructor; +import com.boydti.fawe.util.ReflectionUtils.RefField; +import com.boydti.fawe.util.ReflectionUtils.RefMethod; +import com.boydti.fawe.util.ReflectionUtils.RefMethod.RefExecutor; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.LocalWorld; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.bukkit.BukkitUtil; +import com.sk89q.worldedit.world.biome.BaseBiome; + +public class BukkitQueue_1_8 extends BukkitQueue_0 { + + private final RefClass classEntityPlayer = getRefClass("{nms}.EntityPlayer"); + private final RefClass classMapChunk = getRefClass("{nms}.PacketPlayOutMapChunk"); + private final RefClass classPacket = getRefClass("{nms}.Packet"); + private final RefClass classConnection = getRefClass("{nms}.PlayerConnection"); + private final RefClass classChunk = getRefClass("{nms}.Chunk"); + private final RefClass classCraftPlayer = getRefClass("{cb}.entity.CraftPlayer"); + private final RefClass classCraftChunk = getRefClass("{cb}.CraftChunk"); + private final RefClass classWorld = getRefClass("{nms}.World"); + private final RefClass classCraftWorld = getRefClass("{cb}.CraftWorld"); + private final RefField mustSave = classChunk.getField("mustSave"); + private final RefClass classBlockPosition = getRefClass("{nms}.BlockPosition"); + private final RefClass classChunkSection = getRefClass("{nms}.ChunkSection"); + + private RefMethod methodGetHandlePlayer; + private RefMethod methodGetHandleChunk; + private RefConstructor MapChunk; + private RefField connection; + private RefMethod send; + private RefMethod methodInitLighting; + private RefConstructor classBlockPositionConstructor; + private RefConstructor classChunkSectionConstructor; + private RefMethod methodX; + private RefMethod methodAreNeighborsLoaded; + private RefMethod methodD; + private RefField fieldSections; + private RefField fieldWorld; + private RefMethod methodGetIdArray; + + private final HashMap worldMap = new HashMap<>(); + + public BukkitQueue_1_8() { + try { + methodGetHandlePlayer = classCraftPlayer.getMethod("getHandle"); + methodGetHandleChunk = classCraftChunk.getMethod("getHandle"); + methodInitLighting = classChunk.getMethod("initLighting"); + MapChunk = classMapChunk.getConstructor(classChunk.getRealClass(), boolean.class, int.class); + connection = classEntityPlayer.getField("playerConnection"); + send = classConnection.getMethod("sendPacket", classPacket.getRealClass()); + classBlockPositionConstructor = classBlockPosition.getConstructor(int.class, int.class, int.class); + methodX = classWorld.getMethod("x", classBlockPosition.getRealClass()); + methodD = classChunk.getMethod("d", int.class, int.class, int.class); + fieldSections = classChunk.getField("sections"); + fieldWorld = classChunk.getField("world"); + methodGetIdArray = classChunkSection.getMethod("getIdArray"); + methodAreNeighborsLoaded = classChunk.getMethod("areNeighborsLoaded", int.class); + classChunkSectionConstructor = classChunkSection.getConstructor(int.class, boolean.class, char[].class); + } catch (final NoSuchMethodException e) { + e.printStackTrace(); + } + } + + public FaweGenerator_1_8 getFaweGenerator(final World world) { + final ChunkGenerator gen = world.getGenerator(); + if ((gen != null) && (gen instanceof FaweGenerator_1_8)) { + return (FaweGenerator_1_8) gen; + } + FaweGenerator_1_8 faweGen = worldMap.get(world.getName()); + if (faweGen != null) { + return faweGen; + } + faweGen = new FaweGenerator_1_8(this, world); + worldMap.put(world.getName(), faweGen); + return faweGen; + } + + @Override + public Collection> sendChunk(final Collection> fcs) { + final HashMap, Object> packets = new HashMap<>(); + final HashMap>> map = new HashMap<>(); + + for (final FaweChunk fc : fcs) { + String world = fc.getChunkLoc().world; + ArrayList> list = map.get(world); + if (list == null) { + list = new ArrayList<>(); + map.put(world, list); + } + list.add(fc); + } + final int view = Bukkit.getServer().getViewDistance(); + for (final Player player : Bukkit.getOnlinePlayers()) { + final String world = player.getWorld().getName(); + final ArrayList> list = map.get(world); + if (list == null) { + continue; + } + final Location loc = player.getLocation(); + final int cx = loc.getBlockX() >> 4; + final int cz = loc.getBlockZ() >> 4; + final Object entity = methodGetHandlePlayer.of(player).call(); + + for (final FaweChunk fc : list) { + final int dx = Math.abs(cx - fc.getChunkLoc().x); + final int dz = Math.abs(cz - fc.getChunkLoc().z); + if ((dx > view) || (dz > view)) { + continue; + } + RefExecutor con = send.of(connection.of(entity).get()); + Object packet = packets.get(fc); + if (packet == null) { + final Object c = methodGetHandleChunk.of(fc.getChunk()).call(); + packet = MapChunk.create(c, true, 65535); + packets.put(fc, packet); + con.call(packet); + } else { + con.call(packet); + } + } + } + final HashSet> chunks = new HashSet>(); + for (FaweChunk fc : fcs) { + Chunk chunk = fc.getChunk(); + chunk.unload(true, false); + chunk.load(); + ChunkLoc loc = fc.getChunkLoc(); + chunk.getWorld().refreshChunk(loc.x, loc.z); + if (!fixLighting(fc, Settings.FIX_ALL_LIGHTING)) { + chunks.add(fc); + } + } + return chunks; + } + + @Override + public boolean fixLighting(final FaweChunk fc, boolean fixAll) { + try { + BukkitChunk_1_8 bc = (BukkitChunk_1_8) fc; + final Chunk chunk = bc.getChunk(); + if (!chunk.isLoaded()) { + chunk.load(false); + } + + // Initialize lighting + final Object c = methodGetHandleChunk.of(chunk).call(); + + if (!(boolean) methodAreNeighborsLoaded.of(c).call(1)) { + return false; + } + + methodInitLighting.of(c).call(); + + if ((bc.getTotalRelight() == 0 && !fixAll)) { + return true; + } + + final Object[] sections = (Object[]) fieldSections.of(c).get(); + final Object w = fieldWorld.of(c).get(); + + final int X = chunk.getX() << 4; + final int Z = chunk.getZ() << 4; + + RefExecutor relight = methodX.of(w); + for (int j = 0; j < sections.length; j++) { + final Object section = sections[j]; + if (section == null) { + continue; + } + if ((bc.getRelight(j) == 0 && !fixAll) || bc.getCount(j) == 0 || (bc.getCount(j) >= 4096 && bc.getAir(j) == 0)) { + continue; + } + final char[] array = getIdArray(section); + int l = FaweCache.RANDOM.random(2); + for (int k = 0; k < array.length; k++) { + final int i = array[k]; + if (i < 16) { + continue; + } + final short id = FaweCache.CACHE_ID[i]; + switch (id) { // Lighting + default: + if (!fixAll) { + continue; + } + if ((k & 1) == l) { + l = 1 - l; + continue; + } + case 10: + case 11: + case 39: + case 40: + case 50: + case 51: + case 62: + case 74: + case 76: + case 89: + case 122: + case 124: + case 130: + case 138: + case 169: + final int x = FaweCache.CACHE_X[j][k]; + final int y = FaweCache.CACHE_Y[j][k]; + final int z = FaweCache.CACHE_Z[j][k]; + if (isSurrounded(sections, x, y, z)) { + continue; + } + final Object pos = classBlockPositionConstructor.create(X + x, y, Z + z); + relight.call(pos); + } + } + } + return true; + } catch (final Throwable e) { + e.printStackTrace(); + } + return false; + } + + public boolean isSurrounded(Object[] sections, int x, int y, int z) { + return isSolid(getId(sections, x, y + 1, z)) + && isSolid(getId(sections, x + 1, y - 1, z)) + && isSolid(getId(sections, x - 1, y, z)) + && isSolid(getId(sections, x, y, z + 1)) + && isSolid(getId(sections, x, y, z - 1)); + } + + public boolean isSolid(int i) { + if (i == 0) { + return false; + } + return Material.getMaterial(i).isOccluding(); + } + + public int getId(Object[] sections, int x, int y, int z) { + if (x < 0 || x > 15 || z < 0 || z > 15) { + return 1; + } + if (y < 0 || y > 255) { + return 1; + } + int i = FaweCache.CACHE_I[y][x][z]; + Object section = sections[i]; + if (section == null) { + return 0; + } + char[] array = getIdArray(section); + int j = FaweCache.CACHE_J[y][x][z]; + return array[j] >> 4; + } + + @Override + public boolean setComponents(final FaweChunk fc) { + try { + final BukkitChunk_1_8 fs = ((BukkitChunk_1_8) fc); + final Chunk chunk = fs.getChunk(); + final World world = chunk.getWorld(); + + final boolean flag = world.getEnvironment() == Environment.NORMAL; + + // Sections + final Method getHandele = chunk.getClass().getDeclaredMethod("getHandle"); + final Object c = getHandele.invoke(chunk); + final Class clazz = c.getClass(); + final Field sf = clazz.getDeclaredField("sections"); + sf.setAccessible(true); + final Field tf = clazz.getDeclaredField("tileEntities"); + final Field ef = clazz.getDeclaredField("entitySlices"); + + final Object[] sections = (Object[]) sf.get(c); + final HashMap tiles = (HashMap) tf.get(c); + final List[] entities = (List[]) ef.get(c); + + Method xm = null; + Method ym = null; + Method zm = null; + + // Trim tiles + final Set> entryset = (Set>) (Set) tiles.entrySet(); + final Iterator> iter = entryset.iterator(); + while (iter.hasNext()) { + final Entry tile = iter.next(); + final Object pos = tile.getKey(); + if (xm == null) { + final Class clazz2 = pos.getClass().getSuperclass(); + xm = clazz2.getDeclaredMethod("getX"); + ym = clazz2.getDeclaredMethod("getY"); + zm = clazz2.getDeclaredMethod("getZ"); + } + final int lx = (int) xm.invoke(pos) & 15; + final int ly = (int) ym.invoke(pos); + final int lz = (int) zm.invoke(pos) & 15; + final int j = FaweCache.CACHE_I[ly][lx][lz]; + final int k = FaweCache.CACHE_J[ly][lx][lz]; + final char[] array = fs.getIdArray(j); + if (array == null) { + continue; + } + if (array[k] != 0) { + iter.remove(); + } + } + + // Trim entities + for (int i = 0; i < 16; i++) { + if ((entities[i] != null) && (fs.getCount(i) >= 4096)) { + entities[i].clear(); + } + } + + // Efficiently merge sections + for (int j = 0; j < sections.length; j++) { + if (fs.getCount(j) == 0) { + continue; + } + final char[] newArray = fs.getIdArray(j); + if (newArray == null) { + continue; + } + Object section = sections[j]; + if ((section == null) || (fs.getCount(j) >= 4096)) { + section = sections[j] = newChunkSection(j << 4, flag, newArray); + continue; + } + final char[] currentArray = getIdArray(section); + boolean fill = true; + for (int k = 0; k < newArray.length; k++) { + final char n = newArray[k]; + if (n == 0) { + fill = false; + continue; + } + switch (n) { + case 0: + fill = false; + continue; + case 1: + fill = false; + currentArray[k] = 0; + continue; + default: + currentArray[k] = n; + continue; + } + } + if (fill) { + fs.setCount(j, Short.MAX_VALUE); + } + } + + // Biomes + int[][] biomes = fs.getBiomeArray(); + if (biomes != null) { + LocalWorld lw = BukkitUtil.getLocalWorld(world); + int X = fs.getChunkLoc().x << 4; + int Z = fs.getChunkLoc().z << 4; + BaseBiome bb = new BaseBiome(0); + int last = 0; + for (int x = 0; x < 16; x++) { + int[] array = biomes[x]; + if (array == null) { + continue; + } + for (int z = 0; z < 16; z++) { + int biome = array[z]; + if (biome == 0) { + continue; + } + if (last != biome) { + last = biome; + bb.setId(biome); + } + lw.setBiome(new Vector2D(X + x, Z + z), bb); + } + } + } + + // Clear + fs.clear(); + return true; + } catch (final Exception e) { + e.printStackTrace(); + } + return false; + } + + @Override + public void clear() { + super.clear(); + ArrayDeque toUnload = new ArrayDeque<>(); + final int distance = Bukkit.getViewDistance() + 2; + HashMap> players = new HashMap<>(); + for (final Player player : Bukkit.getOnlinePlayers()) { + // Clear history + final LocalSession session = Fawe.get().getWorldEdit().getSession(player.getName()); + session.clearHistory(); + session.setClipboard(null); + final Location loc = player.getLocation(); + final World worldObj = loc.getWorld(); + final String world = worldObj.getName(); + HashMap map = players.get(world); + if (map == null) { + map = new HashMap<>(); + players.put(world, map); + } + final IntegerPair origin = new IntegerPair(loc.getBlockX() >> 4, loc.getBlockZ() >> 4); + Integer val = map.get(origin); + int check; + if (val != null) { + if (val == distance) { + continue; + } + check = distance - val; + } else { + check = distance; + map.put(origin, distance); + } + for (int x = -distance; x <= distance; x++) { + if ((x >= check) || (-x >= check)) { + continue; + } + for (int z = -distance; z <= distance; z++) { + if ((z >= check) || (-z >= check)) { + continue; + } + final int weight = distance - Math.max(Math.abs(x), Math.abs(z)); + final IntegerPair chunk = new IntegerPair(x + origin.x, z + origin.z); + val = map.get(chunk); + if ((val == null) || (val < weight)) { + map.put(chunk, weight); + } + } + } + } + for (final World world : Bukkit.getWorlds()) { + final String name = world.getName(); + final HashMap map = players.get(name); + if ((map == null) || (map.size() == 0)) { + final boolean save = world.isAutoSave(); + world.setAutoSave(false); + for (final Chunk chunk : world.getLoadedChunks()) { + unloadChunk(name, chunk); + } + world.setAutoSave(save); + continue; + } + final Chunk[] chunks = world.getLoadedChunks(); + for (final Chunk chunk : chunks) { + final int x = chunk.getX(); + final int z = chunk.getZ(); + if (!map.containsKey(new IntegerPair(x, z))) { + toUnload.add(chunk); + } else if (chunk.getEntities().length > 4096) { + for (final Entity ent : chunk.getEntities()) { + ent.remove(); + } + } + } + } + // GC again + System.gc(); + System.gc(); + // If still critical memory + int free = MemUtil.calculateMemory(); + if (free <= 1) { + for (final Chunk chunk : toUnload) { + unloadChunk(chunk.getWorld().getName(), chunk); + } + } else if (free == Integer.MAX_VALUE) { + for (final Chunk chunk : toUnload) { + chunk.unload(true, false); + } + } else { + return; + } + toUnload = null; + players = null; + System.gc(); + System.gc(); + free = MemUtil.calculateMemory(); + if (free > 1) { + return; + } + Collection online = Bukkit.getOnlinePlayers(); + if (online.size() > 0) { + online.iterator().next().kickPlayer("java.lang.OutOfMemoryError"); + } + online = null; + System.gc(); + System.gc(); + free = MemUtil.calculateMemory(); + if ((free > 1) || (Bukkit.getOnlinePlayers().size() > 0)) { + return; + } + for (final World world : Bukkit.getWorlds()) { + final String name = world.getName(); + for (final Chunk chunk : world.getLoadedChunks()) { + unloadChunk(name, chunk); + } + } + System.gc(); + System.gc(); + } + + public Object newChunkSection(final int i, final boolean flag, final char[] ids) { + return classChunkSectionConstructor.create(i, flag, ids); + } + + public char[] getIdArray(final Object obj) { + return (char[]) methodGetIdArray.of(obj).call(); + } + + @Override + public FaweChunk getChunk(final ChunkLoc wrap) { + return new BukkitChunk_1_8(wrap); + } + + public boolean unloadChunk(final String world, final Chunk chunk) { + final Object c = methodGetHandleChunk.of(chunk).call(); + mustSave.of(c).set(false); + if (chunk.isLoaded()) { + chunk.unload(false, false); + } + return true; + } + + public ChunkGenerator setGenerator(final World world, final ChunkGenerator newGen) { + try { + final ChunkGenerator gen = world.getGenerator(); + final Class clazz = world.getClass(); + final Field generator = clazz.getDeclaredField("generator"); + generator.setAccessible(true); + generator.set(world, newGen); + + final Field wf = clazz.getDeclaredField("world"); + wf.setAccessible(true); + final Object w = wf.get(world); + final Class clazz2 = w.getClass().getSuperclass(); + final Field generator2 = clazz2.getDeclaredField("generator"); + generator2.set(w, newGen); + + return gen; + } catch (final Exception e) { + e.printStackTrace(); + } + return null; + } + + public List setPopulator(final World world, final List newPop) { + try { + final List pop = world.getPopulators(); + final Field populators = world.getClass().getDeclaredField("populators"); + populators.setAccessible(true); + populators.set(world, newPop); + return pop; + } catch (final Exception e) { + e.printStackTrace(); + } + return null; + } + + public void setEntitiesAndTiles(final Chunk chunk, final List[] entities, final Map tiles) { + try { + final Class clazz = chunk.getClass(); + final Method handle = clazz.getMethod("getHandle"); + final Object c = handle.invoke(chunk); + final Class clazz2 = c.getClass(); + + if (tiles.size() > 0) { + final Field tef = clazz2.getDeclaredField("tileEntities"); + final Map te = (Map) tef.get(c); + final Method put = te.getClass().getMethod("putAll", Map.class); + put.invoke(te, tiles); + } + + final Field esf = clazz2.getDeclaredField("entitySlices"); + esf.setAccessible(true); + esf.set(c, entities); + } catch (final Exception e) { + e.printStackTrace(); + } + } + + public Object getProvider(final World world) { + try { + // Provider 1 + final Class clazz = world.getClass(); + final Field wf = clazz.getDeclaredField("world"); + wf.setAccessible(true); + final Object w = wf.get(world); + final Field provider = w.getClass().getSuperclass().getDeclaredField("chunkProvider"); + provider.setAccessible(true); + // ChunkProviderServer + final Class clazz2 = w.getClass(); + final Field wpsf = clazz2.getDeclaredField("chunkProviderServer"); + // Store old provider server + final Object worldProviderServer = wpsf.get(w); + // Store the old provider + final Field cp = worldProviderServer.getClass().getDeclaredField("chunkProvider"); + return cp.get(worldProviderServer); + } catch (final Exception e) { + e.printStackTrace(); + } + return null; + } + + public Object setProvider(final World world, Object newProvider) { + try { + // Provider 1 + final Class clazz = world.getClass(); + final Field wf = clazz.getDeclaredField("world"); + wf.setAccessible(true); + final Object w = wf.get(world); + // ChunkProviderServer + final Class clazz2 = w.getClass(); + final Field wpsf = clazz2.getDeclaredField("chunkProviderServer"); + // Store old provider server + final Object worldProviderServer = wpsf.get(w); + // Store the old provider + final Field cp = worldProviderServer.getClass().getDeclaredField("chunkProvider"); + final Object oldProvider = cp.get(worldProviderServer); + // Provider 2 + final Class clazz3 = worldProviderServer.getClass(); + final Field provider2 = clazz3.getDeclaredField("chunkProvider"); + // If the provider needs to be calculated + if (newProvider == null) { + Method k; + try { + k = clazz2.getDeclaredMethod("k"); + } catch (final Throwable e) { + try { + k = clazz2.getDeclaredMethod("j"); + } catch (final Throwable e2) { + e2.printStackTrace(); + return null; + } + } + k.setAccessible(true); + final Object tempProviderServer = k.invoke(w); + newProvider = cp.get(tempProviderServer); + // Restore old provider + wpsf.set(w, worldProviderServer); + } + // Set provider for provider server + provider2.set(worldProviderServer, newProvider); + // Return the previous provider + return oldProvider; + } catch (final Exception e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/src/com/boydti/fawe/bukkit/v1_8/FaweGenerator_1_8.java b/src/com/boydti/fawe/bukkit/v1_8/FaweGenerator_1_8.java new file mode 100644 index 00000000..abf724bd --- /dev/null +++ b/src/com/boydti/fawe/bukkit/v1_8/FaweGenerator_1_8.java @@ -0,0 +1,459 @@ +package com.boydti.fawe.bukkit.v1_8; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Random; +import java.util.Set; + +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Biome; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.ChunkPopulateEvent; +import org.bukkit.generator.BlockPopulator; +import org.bukkit.generator.ChunkGenerator; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.bukkit.FaweBukkit; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.ChunkLoc; + +public class FaweGenerator_1_8 extends ChunkGenerator implements Listener { + private boolean events; + + private final ChunkGenerator parent; + private final List pops; + private final Object provider; + + private short[][] ids; + private byte[][] data; + private Map tiles; + private List[] entities; + private Biome[][] biomes; + + private final World world; + + private final BukkitQueue_1_8 queue; + + private void registerEvents() { + if (events) { + return; + } + Bukkit.getPluginManager().registerEvents(this, Fawe. imp()); + } + + @EventHandler + private void onPopulate(final ChunkPopulateEvent event) { + final World world = event.getWorld(); + final ChunkGenerator gen = world.getGenerator(); + if (gen instanceof FaweGenerator_1_8) { + final FaweGenerator_1_8 fawe = (FaweGenerator_1_8) gen; + if (fawe.data == null) { + return; + } + fawe.populate(event.getChunk()); + decouple((FaweGenerator_1_8) gen, world); + } + } + + public void setBlock(final short[][] result, final int x, final int y, final int z, final short blkid) { + if (result[FaweCache.CACHE_I[y][x][z]] == null) { + result[FaweCache.CACHE_I[y][x][z]] = new short[4096]; + } + result[FaweCache.CACHE_I[y][x][z]][FaweCache.CACHE_J[y][x][z]] = blkid; + } + + public void setBlock(final short[][] result, final int x, final int y, final int z, final short[] blkid) { + if (blkid.length == 1) { + setBlock(result, x, y, z, blkid[0]); + } + final short id = blkid[FaweCache.RANDOM.random(blkid.length)]; + if (result[FaweCache.CACHE_I[y][x][z]] == null) { + result[FaweCache.CACHE_I[y][x][z]] = new short[4096]; + } + result[FaweCache.CACHE_I[y][x][z]][FaweCache.CACHE_J[y][x][z]] = id; + } + + public void setBlocks(final short[][] ids, final byte[][] data, final int x, final int z) { + this.ids = ids; + this.data = data == null ? new byte[16][] : data; + if (parent == null) { + inject(this, world); + } + world.regenerateChunk(x, z); + } + + /** + * Regenerate chunk with the provided id / data / block count
+ * - You can provide null for datas / count but it will be marginally slower + * @param ids + * @param datas + * @param count + * @param chunk + */ + @Deprecated + public void regenerateBlocks(final short[][] ids, byte[][] datas, short[] count, final Chunk chunk) { + if (datas == null) { + datas = new byte[16][]; + } + if (count == null) { + count = new short[16]; + } + final int x = chunk.getX(); + final int z = chunk.getZ(); + + boolean skip = true; + for (int i = 0; i < 16; i++) { + if (count[i] < 4096) { + skip = false; + break; + } + } + + if (!skip) { + try { + chunk.load(true); + biomes = new Biome[16][16]; + final int X = x << 4; + final int Z = z << 4; + for (int xx = 0; xx < 16; xx++) { + final int xxx = X + x; + for (int zz = 0; zz < 16; zz++) { + final int zzz = Z + zz; + biomes[xx][zz] = world.getBiome(xxx, zzz); + } + } + final Method getHandele = chunk.getClass().getDeclaredMethod("getHandle"); + final Object c = getHandele.invoke(chunk); + final Class clazz = c.getClass(); + final Field sf = clazz.getDeclaredField("sections"); + sf.setAccessible(true); + final Field tf = clazz.getDeclaredField("tileEntities"); + final Field ef = clazz.getDeclaredField("entitySlices"); + + final Object[] sections = (Object[]) sf.get(c); + final HashMap tiles = (HashMap) tf.get(c); + final List[] entities = (List[]) ef.get(c); + + Method xm = null; + Method ym = null; + Method zm = null; + + // Copy entities / blockstates + final Set> entryset = (Set>) (Set) tiles.entrySet(); + final Iterator> iter = entryset.iterator(); + while (iter.hasNext()) { + final Entry tile = iter.next(); + final Object loc = tile.getKey(); + if (xm == null) { + final Class clazz2 = loc.getClass().getSuperclass(); + xm = clazz2.getDeclaredMethod("getX"); + ym = clazz2.getDeclaredMethod("getY"); + zm = clazz2.getDeclaredMethod("getZ"); + } + final int lx = (int) xm.invoke(loc) & 15; + final int ly = (int) ym.invoke(loc); + final int lz = (int) zm.invoke(loc) & 15; + final int j = FaweCache.CACHE_I[ly][lx][lz]; + final int k = FaweCache.CACHE_J[ly][lx][lz]; + if (ids[j] == null) { + continue; + } + if (ids[j][k] != 0) { + iter.remove(); + } + } + + this.tiles = tiles; + // Trim entities + for (int i = 0; i < 16; i++) { + if ((entities[i] != null) && (count[i] >= 4096)) { + entities[i].clear(); + } + } + this.entities = entities; + + // Efficiently merge sections + Method getIdArray = null; + for (int j = 0; j < sections.length; j++) { + if (count[j] >= 4096) { + continue; + } + final Object section = sections[j]; + if (section == null) { + continue; + } + if (getIdArray == null) { + final Class clazz2 = section.getClass(); + getIdArray = clazz2.getDeclaredMethod("getIdArray"); + } + final char[] array = (char[]) getIdArray.invoke(section); + for (int k = 0; k < array.length; k++) { + final int i = array[k]; + if (i < 16) { + continue; + } + short[] va = ids[j]; + if (va == null) { + va = new short[4096]; + ids[j] = va; + } + final short v = va[k]; + if (v != 0) { + continue; + } + final short id = FaweCache.CACHE_ID[i]; + va[k] = id; + switch (id) { + case 0: + case 2: + case 4: + case 13: + case 14: + case 15: + case 20: + case 21: + case 22: + case 30: + case 32: + case 37: + case 39: + case 40: + case 41: + case 42: + case 45: + case 46: + case 47: + case 48: + case 49: + case 50: + case 51: + case 55: + case 56: + case 57: + case 58: + case 60: + case 7: + case 8: + case 9: + case 10: + case 11: + case 73: + case 74: + case 75: + case 76: + case 78: + case 79: + case 80: + case 81: + case 82: + case 83: + case 85: + case 87: + case 88: + case 101: + case 102: + case 103: + case 110: + case 112: + case 113: + case 121: + case 122: + case 129: + case 133: + case 165: + case 166: + case 169: + case 170: + case 172: + case 173: + case 174: + case 181: + case 182: + case 188: + case 189: + case 190: + case 191: + case 192: + continue; + case 53: + case 67: + case 108: + case 109: + case 114: + case 128: + case 134: + case 135: + case 136: + case 156: + case 163: + case 164: + case 180: + byte db = FaweCache.CACHE_DATA[i]; + if (db == 0) { + db = -1; + } + if (datas[j] == null) { + datas[j] = new byte[4096]; + } + datas[j][k] = db; + continue; + } + final byte db = FaweCache.CACHE_DATA[i]; + if (db == 0) { + continue; + } + if (datas[j] == null) { + datas[j] = new byte[4096]; + } + datas[j][k] = db; + } + } + + } catch (final Throwable e) { + e.printStackTrace(); + return; + } + } + // Execute + this.ids = ids; + data = datas; + if (parent == null) { + inject(this, world); + } + world.regenerateChunk(x, z); + } + + public void inject(final FaweGenerator_1_8 gen, final World world) { + queue.setGenerator(world, gen); + queue.setPopulator(world, new ArrayList()); + queue.setProvider(world, null); + } + + public void decouple(final FaweGenerator_1_8 gen, final World world) { + gen.data = null; + gen.ids = null; + gen.tiles = null; + gen.entities = null; + gen.biomes = null; + if (gen.parent == null) { + queue.setGenerator(world, gen.parent); + queue.setPopulator(world, gen.pops); + if (gen.provider != null) { + queue.setProvider(world, gen.provider); + } + } + } + + public FaweGenerator_1_8(final BukkitQueue_1_8 queue, final World world) { + this.queue = queue; + this.world = world; + parent = world.getGenerator(); + pops = world.getPopulators(); + if (parent == null) { + provider = queue.getProvider(world); + } else { + provider = null; + } + registerEvents(); + } + + @Override + public short[][] generateExtBlockSections(final World world, final Random random, final int x, final int z, final BiomeGrid biomes) { + short[][] result; + if (ids != null) { + result = ids; + if ((biomes != null) && (this.biomes != null)) { + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + biomes.setBiome(i, j, this.biomes[i][j]); + } + } + } + } else if (parent != null) { + result = parent.generateExtBlockSections(world, random, x, z, biomes); + } else { + result = null; + } + return result; + } + + public void populate(final Chunk chunk) { + for (int i = 0; i < data.length; i++) { + final byte[] section = data[i]; + if (section == null) { + continue; + } + for (int j = 0; j < section.length; j++) { + final byte v = section[j]; + if (v == 0) { + continue; + } + final int x = FaweCache.CACHE_X[i][j]; + final int y = FaweCache.CACHE_Y[i][j]; + final int z = FaweCache.CACHE_Z[i][j]; + chunk.getBlock(x, y, z).setData(v != -1 ? v : 0, false); + } + } + if ((tiles != null) || (entities != null)) { + queue.setEntitiesAndTiles(chunk, entities, tiles); + } + final BukkitChunk_1_8 fc = new BukkitChunk_1_8(new ChunkLoc(chunk.getWorld().getName(), chunk.getX(), chunk.getZ())); + fc.chunk = chunk; + queue.fixLighting(fc, Settings.FIX_ALL_LIGHTING); + } + + @Override + public byte[] generate(final World world, final Random random, final int x, final int z) { + if (ids == null) { + try { + parent.generate(world, random, x, z); + } catch (final Throwable e) { + return null; + } + } + return null; + } + + @Override + public byte[][] generateBlockSections(final World world, final Random random, final int x, final int z, final BiomeGrid biomes) { + if ((ids == null) && (parent != null)) { + return parent.generateBlockSections(world, random, x, z, biomes); + } + return null; + } + + @Override + public boolean canSpawn(final World world, final int x, final int z) { + if (parent != null) { + return parent.canSpawn(world, x, z); + } + return true; + } + + @Override + public List getDefaultPopulators(final World world) { + if ((ids == null) && (parent != null)) { + return parent.getDefaultPopulators(world); + } + return null; + } + + @Override + public Location getFixedSpawnLocation(final World world, final Random random) { + if ((ids == null) && (parent != null)) { + return parent.getFixedSpawnLocation(world, random); + } + return null; + } +} diff --git a/src/com/boydti/fawe/command/FixLighting.java b/src/com/boydti/fawe/command/FixLighting.java new file mode 100644 index 00000000..dd0961e9 --- /dev/null +++ b/src/com/boydti/fawe/command/FixLighting.java @@ -0,0 +1,54 @@ +package com.boydti.fawe.command; + +import com.boydti.fawe.FaweAPI; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.object.ChunkLoc; +import com.boydti.fawe.object.FaweCommand; +import com.boydti.fawe.object.FaweLocation; +import com.boydti.fawe.object.FawePlayer; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.regions.Region; + +public class FixLighting extends FaweCommand { + + public FixLighting() { + super("fawe.fixlighting"); + } + + @Override + public boolean execute(final FawePlayer player, final String... args) { + if (player == null) { + return false; + } + FaweLocation loc = player.getLocation(); + Region selection = player.getSelection(); + if (selection == null) { + FaweAPI.fixLighting(new ChunkLoc(loc.world, loc.x >> 4, loc.z >> 4), Settings.FIX_ALL_LIGHTING); + BBC.FIX_LIGHTING_CHUNK.send(player); + return true; + } + int cx = loc.x >> 4; + int cz = loc.z >> 4; + Vector bot = selection.getMinimumPoint(); + Vector top = selection.getMaximumPoint(); + + int minX = Math.max(cx - 8, bot.getBlockX() >> 4); + int minZ = Math.max(cz - 8, bot.getBlockZ() >> 4); + + int maxX = Math.min(cx + 8, top.getBlockX() >> 4); + int maxZ = Math.min(cz + 8, top.getBlockZ() >> 4); + + int count = 0; + for (int x = minX; x <= maxX; x++) { + for (int z = minZ; z <= maxZ; z++) { + ChunkLoc cl = new ChunkLoc(loc.world, x, z); + FaweAPI.fixLighting(new ChunkLoc(loc.world, x >> 4, z >> 4), Settings.FIX_ALL_LIGHTING); + count++; + } + } + BBC.FIX_LIGHTING_SELECTION.send(player, count); + + return true; + } +} diff --git a/src/com/boydti/fawe/command/Stream.java b/src/com/boydti/fawe/command/Stream.java new file mode 100644 index 00000000..ba71cd90 --- /dev/null +++ b/src/com/boydti/fawe/command/Stream.java @@ -0,0 +1,38 @@ +package com.boydti.fawe.command; + +import java.io.File; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweAPI; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FaweCommand; +import com.boydti.fawe.object.FawePlayer; + +public class Stream extends FaweCommand { + + public Stream() { + super("fawe.stream"); + } + + @Override + public boolean execute(final FawePlayer player, final String... args) { + if (player == null) { + return false; + } + if (args.length != 1) { + BBC.COMMAND_SYNTAX.send(player, "/stream "); + return false; + } + if (!args[0].endsWith(".schematic")) { + args[0] += ".schematic"; + } + File file = Fawe.get().getWorldEdit().getWorkingDirectoryFile(Fawe.get().getWorldEdit().getConfiguration().saveDir + File.separator + args[0]); + if (!file.exists()) { + BBC.SCHEMATIC_NOT_FOUND.send(player, args); + return false; + } + FaweAPI.streamSchematicAsync(file, player.getLocation()); + BBC.SCHEMATIC_PASTING.send(player); + return true; + } +} diff --git a/src/com/boydti/fawe/command/Wea.java b/src/com/boydti/fawe/command/Wea.java new file mode 100644 index 00000000..fe0ecc27 --- /dev/null +++ b/src/com/boydti/fawe/command/Wea.java @@ -0,0 +1,35 @@ +package com.boydti.fawe.command; + +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FaweCommand; +import com.boydti.fawe.object.FawePlayer; + +public class Wea extends FaweCommand { + + public Wea() { + super("fawe.admin"); + } + + @Override + public boolean execute(final FawePlayer player, final String... args) { + if (player == null) { + return false; + } + if (toggle(player)) { + BBC.WORLDEDIT_BYPASSED.send(player); + } else { + BBC.WORLDEDIT_RESTRICTED.send(player); + } + return true; + } + + private boolean toggle(FawePlayer player) { + if (player.hasPermission("fawe.bypass")) { + player.setPermission("fawe.bypass", false); + return false; + } else { + player.setPermission("fawe.bypass", true); + return true; + } + } +} diff --git a/src/com/boydti/fawe/command/WorldEditRegion.java b/src/com/boydti/fawe/command/WorldEditRegion.java new file mode 100644 index 00000000..d09d27da --- /dev/null +++ b/src/com/boydti/fawe/command/WorldEditRegion.java @@ -0,0 +1,38 @@ +package com.boydti.fawe.command; + +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FaweCommand; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.RegionWrapper; + +public class WorldEditRegion extends FaweCommand { + + public WorldEditRegion() { + super("fawe.worldeditregion"); + } + + @Override + public boolean execute(final FawePlayer player, final String... args) { + if (player == null) { + return false; + } + RegionWrapper region = player.getLargestRegion(); + if (region == null) { + BBC.NO_REGION.send(player); + return false; + } + player.setSelection(region); + BBC.SET_REGION.send(player); + return true; + } + + private boolean toggle(FawePlayer player) { + if (player.hasPermission("fawe.bypass")) { + player.setPermission("fawe.bypass", false); + return false; + } else { + player.setPermission("fawe.bypass", true); + return true; + } + } +} diff --git a/src/com/boydti/fawe/config/BBC.java b/src/com/boydti/fawe/config/BBC.java new file mode 100644 index 00000000..2a4bc496 --- /dev/null +++ b/src/com/boydti/fawe/config/BBC.java @@ -0,0 +1,210 @@ +package com.boydti.fawe.config; + +import java.io.File; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +import org.bukkit.ChatColor; +import org.bukkit.configuration.file.YamlConfiguration; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.util.StringMan; + +public enum BBC { + + /* + * Things to note about this class: + * Can use multiple arguments %s, %s1, %s2, %s3 etc + */ + COMMAND_SYNTAX("&cUsage: &7%s0", "Error"), + SCHEMATIC_NOT_FOUND("&cSchematic not found: &7%s0", "Error"), + SCHEMATIC_PASTING("&7The schematic is pasting. This cannot be undone.", "Info"), + FIX_LIGHTING_CHUNK("&7Lighting has been fixed in your current chunk. Relog to see the affect.", "Info"), + FIX_LIGHTING_SELECTION("&7Lighting has been fixed in %s0 chunks. Relog to see the affect.", "Info"), + NO_REGION("&cYou have no current WorldEdit region", "Error"), + SET_REGION("&7Selection set to your current WorldEdit region", "Info"), + WORLDEDIT_DELAYED("&7Please wait while we process your WorldEdit action...", "Info"), + WORLDEDIT_RUN("&7Apologies for the delay. Now executing: %s", "Info"), + WORLDEDIT_COMPLETE("&7WorldEdit action completed.", "Info"), + REQUIRE_SELECTION_IN_MASK("&7%s of your selection is not within your mask. You can only make edits within allowed regions.", "Info"), + WORLDEDIT_VOLUME("&7You cannot select a volume of %current%. The maximum volume you can modify is %max%.", "Info"), + WORLDEDIT_ITERATIONS("&7You cannot iterate %current% times. The maximum number of iterations allowed is %max%.", "Info"), + WORLDEDIT_UNSAFE("&7Access to that command has been blocked", "Info"), + WORLDEDIT_DANGEROUS_WORLDEDIT("&cFAWE processed unsafe WorldEdit at %s0 by %s1", "Info"), + WORLDEDIT_BYPASS("&7&oTo bypass your restrictions use &c/wea", "Info"), + WORLDEDIT_EXTEND("&cYour WorldEdit may have extended outside your allowed region.", "Error"), + WORLDEDIT_BYPASSED("&7Currently bypassing WorldEdit restriction.", "Info"), + WORLDEDIT_UNMASKED("&6Your WorldEdit is now unrestricted.", "Info"), + WORLDEDIT_RESTRICTED("&6Your WorldEdit is now restricted.", "Info"), + WORLDEDIT_OOM("&cYour WorldEdit action was cancelled due to low memory.", "Info"), + WORLDEDIT_OOM_ADMIN("&cPossible options:\n&8 - &7//fast\n&8 - &7Do smaller edits\n&8 - &7Allocate more memory\n&8 - &7Disable this safeguard", "Info"), + NOT_PLAYER("&cYou must be a player to perform this action!", "Error"), + OOM( + "&8[&cCritical&8] &cDetected low memory i.e. < 1%. FAWE will take the following actions:\n&8 - &7Terminate WE block placement\n&8 - &7Clear WE history\n&8 - &7Unload non essential chunks\n&8 - &7Kill entities\n&8 - &7Garbage collect\n&cIgnore this if trying to crash server.\n&7Note: Low memory is likely (but not necessarily) caused by WE", + "Error"); + + private static final HashMap replacements = new HashMap<>(); + /** + * Translated + */ + private String s; + /** + * Default + */ + private String d; + /** + * What locale category should this translation fall under + */ + private String cat; + /** + * Should the string be prefixed? + */ + private boolean prefix; + + /** + * Constructor for custom strings. + */ + BBC() { + /* + * use setCustomString(); + */ + } + + /** + * Constructor + * + * @param d default + * @param prefix use prefix + */ + BBC(final String d, final boolean prefix, final String cat) { + this.d = d; + if (s == null) { + s = d; + } + this.prefix = prefix; + this.cat = cat.toLowerCase(); + } + + /** + * Constructor + * + * @param d default + */ + BBC(final String d, final String cat) { + this(d, true, cat.toLowerCase()); + } + + public String format(final Object... args) { + String m = s; + for (int i = args.length - 1; i >= 0; i--) { + if (args[i] == null) { + continue; + } + m = m.replaceAll("%s" + i, args[i].toString()); + } + if (args.length > 0) { + m = m.replaceAll("%s", args[0].toString()); + } + return m; + } + + public static void load(final File file) { + try { + if (!file.exists()) { + file.getParentFile().mkdirs(); + file.createNewFile(); + } + final YamlConfiguration yml = YamlConfiguration.loadConfiguration(file); + final Set keys = yml.getKeys(true); + final EnumSet all = EnumSet.allOf(BBC.class); + final HashSet allNames = new HashSet<>(); + final HashSet allCats = new HashSet<>(); + final HashSet toRemove = new HashSet<>(); + for (final BBC c : all) { + allNames.add(c.name()); + allCats.add(c.cat.toLowerCase()); + } + final HashSet captions = new HashSet<>(); + boolean changed = false; + for (final String key : keys) { + if (!yml.isString(key)) { + if (!allCats.contains(key)) { + toRemove.add(key); + } + continue; + } + final String[] split = key.split("\\."); + final String node = split[split.length - 1].toUpperCase(); + final BBC caption = allNames.contains(node) ? valueOf(node) : null; + if (caption != null) { + final String value = yml.getString(key); + if (!split[0].equalsIgnoreCase(caption.cat)) { + changed = true; + yml.set(key, null); + yml.set(caption.cat + "." + caption.name().toLowerCase(), value); + } + captions.add(caption); + caption.s = value; + } else { + toRemove.add(key); + } + } + for (final String remove : toRemove) { + changed = true; + yml.set(remove, null); + } + replacements.clear(); + for (final char letter : "1234567890abcdefklmnor".toCharArray()) { + replacements.put("&" + letter, "\u00a7" + letter); + } + replacements.put("\\\\n", "\n"); + replacements.put("\\n", "\n"); + replacements.put("&-", "\n"); + for (final BBC caption : all) { + if (!captions.contains(caption)) { + changed = true; + yml.set(caption.cat + "." + caption.name().toLowerCase(), caption.d); + } + caption.s = StringMan.replaceFromMap(caption.s, replacements); + } + if (changed) { + yml.save(file); + } + } catch (final Exception e) { + e.printStackTrace(); + } + } + + public String s() { + return s; + } + + public boolean usePrefix() { + return prefix; + } + + /** + * @return translated and color decoded + * + * @see org.bukkit.ChatColor#translateAlternateColorCodes(char, String) + */ + public String translated() { + return ChatColor.translateAlternateColorCodes('&', s()); + } + + public String getCat() { + return cat; + } + + public void send(final FawePlayer player, final Object... args) { + if (player == null) { + Fawe.debug(format(args)); + } else { + player.sendMessage(format(args)); + } + } + +} diff --git a/src/com/boydti/fawe/config/Settings.java b/src/com/boydti/fawe/config/Settings.java new file mode 100644 index 00000000..51ed8c2f --- /dev/null +++ b/src/com/boydti/fawe/config/Settings.java @@ -0,0 +1,69 @@ +package com.boydti.fawe.config; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.bukkit.configuration.file.YamlConfiguration; + +public class Settings { + + public static int MAX_BLOCKSTATES = 1337; + public static int MAX_ENTITIES = 1337; + public static long WE_MAX_ITERATIONS = 1000; + public static long WE_MAX_VOLUME = 50000000; + public static boolean REQUIRE_SELECTION = false; + public static boolean FIX_ALL_LIGHTING = true; + public static boolean COMMAND_PROCESSOR = false; + public static List WE_BLACKLIST = Arrays.asList("cs", ".s", "restore", "snapshot", "delchunks", "listchunks"); + public static long MEM_FREE = 95; + + public static void setup(final File file) { + if (!file.exists()) { + file.getParentFile().mkdirs(); + try { + file.createNewFile(); + } catch (final IOException e) { + e.printStackTrace(); + } + } + final YamlConfiguration config = YamlConfiguration.loadConfiguration(file); + + final Map options = new HashMap<>(); + options.put("max-blockstates", MAX_BLOCKSTATES); + options.put("max-entities", MAX_ENTITIES); + options.put("max-iterations", WE_MAX_ITERATIONS); + options.put("max-volume", WE_MAX_VOLUME); + options.put("require-selection-in-mask", REQUIRE_SELECTION); + options.put("command-blacklist", WE_BLACKLIST); + options.put("command-processor", COMMAND_PROCESSOR); + options.put("max-memory-percent", MEM_FREE); + options.put("fix-all-lighting", FIX_ALL_LIGHTING); + + for (final Entry node : options.entrySet()) { + if (!config.contains(node.getKey())) { + config.set(node.getKey(), node.getValue()); + } + } + FIX_ALL_LIGHTING = config.getBoolean("fix-all-lighting"); + COMMAND_PROCESSOR = config.getBoolean("command-processor"); + MAX_BLOCKSTATES = config.getInt("max-blockstates"); + MAX_ENTITIES = config.getInt("max-entities"); + WE_MAX_ITERATIONS = config.getInt("max-iterations"); + WE_MAX_VOLUME = config.getInt("max-volume"); + MEM_FREE = config.getInt("max-memory-percent"); + REQUIRE_SELECTION = config.getBoolean("require-selection-in-mask"); + WE_BLACKLIST = config.getStringList("command-blacklist"); + + + try { + config.save(file); + } catch (final IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/com/boydti/fawe/logging/BlocksHubHook.java b/src/com/boydti/fawe/logging/BlocksHubHook.java new file mode 100644 index 00000000..e775e2a6 --- /dev/null +++ b/src/com/boydti/fawe/logging/BlocksHubHook.java @@ -0,0 +1,24 @@ +package com.boydti.fawe.logging; + +import org.PrimeSoft.blocksHub.BlocksHub; +import org.PrimeSoft.blocksHub.IBlocksHubApi; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import com.boydti.fawe.object.FawePlayer; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.history.changeset.ChangeSet; + +public class BlocksHubHook { + private final BlocksHub hub; + private final IBlocksHubApi api; + + public BlocksHubHook() { + this.hub = (BlocksHub) Bukkit.getServer().getPluginManager().getPlugin("BlocksHub"); + this.api = hub.getApi(); + } + + public Extent getLoggingExtent(Extent parent, ChangeSet set, FawePlayer player) { + return new LoggingExtent(parent, set, (FawePlayer) player, api); + } +} diff --git a/src/com/boydti/fawe/logging/LoggingExtent.java b/src/com/boydti/fawe/logging/LoggingExtent.java new file mode 100644 index 00000000..a1df320e --- /dev/null +++ b/src/com/boydti/fawe/logging/LoggingExtent.java @@ -0,0 +1,247 @@ +package com.boydti.fawe.logging; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nullable; + +import org.PrimeSoft.blocksHub.IBlocksHubApi; +import org.bukkit.World; +import org.bukkit.entity.Player; + +import com.boydti.fawe.object.FawePlayer; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.history.change.BlockChange; +import com.sk89q.worldedit.history.change.EntityCreate; +import com.sk89q.worldedit.history.change.EntityRemove; +import com.sk89q.worldedit.history.changeset.ChangeSet; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.Location; + +/** + * Stores changes to a {@link ChangeSet}. + * Logs changes to BlocksHub + */ +public class LoggingExtent extends AbstractDelegateExtent { + + private final ChangeSet changeSet; + private final IBlocksHubApi api; + private final String playerName; + private final World world; + private final org.bukkit.Location loc; + + /** + * Create a new instance. + * + * @param extent the extent + * @param changeSet the change set + * @param api + * @param player + * @param thread + */ + public LoggingExtent(final Extent extent, final ChangeSet changeSet, FawePlayer player, IBlocksHubApi api) { + super(extent); + checkNotNull(changeSet); + this.changeSet = changeSet; + this.api = api; + this.playerName = player.getName(); + this.world = player.parent.getWorld(); + this.loc = new org.bukkit.Location(world, 0, 0, 0); + } + + @Override + public synchronized boolean setBlock(final Vector location, final BaseBlock block) throws WorldEditException { + if (super.setBlock(location, block)) { + BaseBlock previous; + try { + previous = getBlock(location); + } catch (final Exception e) { + previous = getBlock(location); + } + final int id_p = previous.getId(); + final int id_b = block.getId(); + switch (id_p) { + case 0: + case 2: + case 4: + case 13: + case 14: + case 15: + case 20: + case 21: + case 22: + case 25: + case 30: + case 32: + case 37: + case 39: + case 40: + case 41: + case 42: + case 45: + case 46: + case 47: + case 48: + case 49: + case 50: + case 51: + case 52: + case 54: + case 55: + case 56: + case 57: + case 58: + case 60: + case 61: + case 62: + case 7: + case 8: + case 9: + case 10: + case 11: + case 73: + case 74: + case 75: + case 76: + case 78: + case 79: + case 80: + case 81: + case 82: + case 83: + case 84: + case 85: + case 87: + case 88: + case 101: + case 102: + case 103: + case 110: + case 112: + case 113: + case 117: + case 121: + case 122: + case 123: + case 124: + case 129: + case 133: + case 138: + case 137: + case 140: + case 165: + case 166: + case 169: + case 170: + case 172: + case 173: + case 174: + case 176: + case 177: + case 181: + case 182: + case 188: + case 189: + case 190: + case 191: + case 192: + if (id_p == id_b) { + return false; + } + loc.setX(location.getX()); + loc.setY(location.getY()); + loc.setZ(location.getZ()); + api.logBlock(playerName, world, loc, id_p, (byte) 0, id_b, (byte) 0); + default: + int data_p = previous.getData(); + int data_b = block.getData(); + if (id_p == id_b && data_b == data_p) { + return false; + } + loc.setX(location.getX()); + loc.setY(location.getY()); + loc.setZ(location.getZ()); + api.logBlock(playerName, world, loc, id_p, (byte) data_p, id_b, (byte) data_b); + } + changeSet.add(new BlockChange(location.toBlockVector(), previous, block)); + return true; + } + return false; + } + + @Nullable + @Override + public Entity createEntity(final Location location, final BaseEntity state) { + final Entity entity = super.createEntity(location, state); + if (state != null) { + changeSet.add(new EntityCreate(location, state, entity)); + } + return entity; + } + + @Override + public List getEntities() { + return wrapEntities(super.getEntities()); + } + + @Override + public List getEntities(final Region region) { + return wrapEntities(super.getEntities(region)); + } + + private List wrapEntities(final List entities) { + final List newList = new ArrayList(entities.size()); + for (final Entity entity : entities) { + newList.add(new TrackedEntity(entity)); + } + return newList; + } + + private class TrackedEntity implements Entity { + private final Entity entity; + + private TrackedEntity(final Entity entity) { + this.entity = entity; + } + + @Override + public BaseEntity getState() { + return entity.getState(); + } + + @Override + public Location getLocation() { + return entity.getLocation(); + } + + @Override + public Extent getExtent() { + return entity.getExtent(); + } + + @Override + public boolean remove() { + final Location location = entity.getLocation(); + final BaseEntity state = entity.getState(); + final boolean success = entity.remove(); + if ((state != null) && success) { + changeSet.add(new EntityRemove(location, state)); + } + return success; + } + + @Nullable + @Override + public T getFacet(final Class cls) { + return entity.getFacet(cls); + } + } +} diff --git a/src/com/boydti/fawe/object/ChunkLoc.java b/src/com/boydti/fawe/object/ChunkLoc.java new file mode 100644 index 00000000..6b814467 --- /dev/null +++ b/src/com/boydti/fawe/object/ChunkLoc.java @@ -0,0 +1,56 @@ +package com.boydti.fawe.object; + +public class ChunkLoc { + public final int x; + public final int z; + public final String world; + + public ChunkLoc(final String world, final int x, final int z) { + this.world = world; + this.x = x; + this.z = z; + } + + @Override + public int hashCode() { + int result; + if (x >= 0) { + if (z >= 0) { + result = (x * x) + (3 * x) + (2 * x * z) + z + (z * z); + } else { + final int y1 = -z; + result = (x * x) + (3 * x) + (2 * x * y1) + y1 + (y1 * y1) + 1; + } + } else { + final int x1 = -x; + if (z >= 0) { + result = -((x1 * x1) + (3 * x1) + (2 * x1 * z) + z + (z * z)); + } else { + final int y1 = -z; + result = -((x1 * x1) + (3 * x1) + (2 * x1 * y1) + y1 + (y1 * y1) + 1); + } + } + result = (result * 31) + world.hashCode(); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ChunkLoc other = (ChunkLoc) obj; + return ((x == other.x) && (z == other.z) && (world.equals(other.world))); + } + + @Override + public String toString() { + return world + ":" + x + "," + z; + } +} diff --git a/src/com/boydti/fawe/object/EditSessionWrapper.java b/src/com/boydti/fawe/object/EditSessionWrapper.java new file mode 100644 index 00000000..689059ff --- /dev/null +++ b/src/com/boydti/fawe/object/EditSessionWrapper.java @@ -0,0 +1,32 @@ +package com.boydti.fawe.object; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.blocks.BlockType; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.history.changeset.ChangeSet; + +public class EditSessionWrapper { + + public final EditSession session; + + public EditSessionWrapper(final EditSession session) { + this.session = session; + } + + public int getHighestTerrainBlock(final int x, final int z, final int minY, final int maxY, final boolean naturalOnly) { + for (int y = maxY; y >= minY; --y) { + final Vector pt = new Vector(x, y, z); + final int id = session.getBlockType(pt); + final int data = session.getBlockData(pt); + if (naturalOnly ? BlockType.isNaturalTerrainBlock(id, data) : !BlockType.canPassThrough(id, data)) { + return y; + } + } + return minY; + } + + public Extent getHistoryExtent(Extent parent, ChangeSet set, FawePlayer player) { + return new HistoryExtent(parent, set); + } +} diff --git a/src/com/boydti/fawe/object/FastWorldEditExtent.java b/src/com/boydti/fawe/object/FastWorldEditExtent.java new file mode 100644 index 00000000..9fb46920 --- /dev/null +++ b/src/com/boydti/fawe/object/FastWorldEditExtent.java @@ -0,0 +1,186 @@ +package com.boydti.fawe.object; + +import java.util.List; + +import com.boydti.fawe.util.SetBlockQueue; +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.biome.BaseBiome; + +public class FastWorldEditExtent extends AbstractDelegateExtent { + + private final String world; + private final Thread thread; + + public FastWorldEditExtent(World world, Thread thread) { + super(world); + this.thread = thread; + this.world = world.getName(); + } + + @Override + public Entity createEntity(Location location, BaseEntity entity) { + synchronized (thread) { + return super.createEntity(location, entity); + } + } + + @Override + public BaseBiome getBiome(Vector2D position) { + synchronized (thread) { + return super.getBiome(position); + } + } + + private BaseBlock lastBlock; + private BlockVector lastVector; + + @Override + public BaseBlock getLazyBlock(Vector position) { + if (lastBlock != null && lastVector.equals(position.toBlockVector())) { + return lastBlock; + } + synchronized (thread) { + lastVector = position.toBlockVector(); + return lastBlock = super.getLazyBlock(position); + } + } + + @Override + public List getEntities() { + synchronized (thread) { + return super.getEntities(); + } + } + + @Override + public List getEntities(Region region) { + synchronized (thread) { + return super.getEntities(region); + } + } + + @Override + public BaseBlock getBlock(Vector position) { + synchronized (thread) { + return super.getLazyBlock(position); + } + } + + @Override + public boolean setBiome(Vector2D position, BaseBiome biome) { + SetBlockQueue.IMP.setBiome(world, position.getBlockX(), position.getBlockZ(), biome); + return true; + } + + @Override + public boolean setBlock(Vector location, BaseBlock block) throws WorldEditException { + short id = (short) block.getId(); + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + switch (id) { + case 0: + case 2: + case 4: + case 13: + case 14: + case 15: + case 20: + case 21: + case 22: + case 25: + case 30: + case 32: + case 37: + case 39: + case 40: + case 41: + case 42: + case 45: + case 46: + case 47: + case 48: + case 49: + case 50: + case 51: + case 52: + case 54: + case 55: + case 56: + case 57: + case 58: + case 60: + case 61: + case 62: + case 7: + case 8: + case 9: + case 10: + case 11: + case 73: + case 74: + case 75: + case 76: + case 78: + case 79: + case 80: + case 81: + case 82: + case 83: + case 84: + case 85: + case 87: + case 88: + case 101: + case 102: + case 103: + case 110: + case 112: + case 113: + case 117: + case 121: + case 122: + case 123: + case 124: + case 129: + case 133: + case 138: + case 137: + case 140: + case 165: + case 166: + case 169: + case 170: + case 172: + case 173: + case 174: + case 176: + case 177: + case 181: + case 182: + case 188: + case 189: + case 190: + case 191: + case 192: { + SetBlockQueue.IMP.setBlock(world, x, y, z, id); + return true; + } + default: { + SetBlockQueue.IMP.setBlock(world, x, y, z, id, (byte) block.getData()); + return true; + } + } + } + +} diff --git a/src/com/boydti/fawe/object/FaweChangeSet.java b/src/com/boydti/fawe/object/FaweChangeSet.java new file mode 100644 index 00000000..200a2464 --- /dev/null +++ b/src/com/boydti/fawe/object/FaweChangeSet.java @@ -0,0 +1,44 @@ +package com.boydti.fawe.object; + +import java.util.ArrayDeque; +import java.util.Iterator; + +import com.sk89q.worldedit.history.change.BlockChange; +import com.sk89q.worldedit.history.change.Change; +import com.sk89q.worldedit.history.changeset.ChangeSet; + +public class FaweChangeSet implements ChangeSet { + + private final ArrayDeque changes = new ArrayDeque<>(); + + @Override + public void add(final Change change) { + if (change.getClass() == BlockChange.class) { + final BlockChange bc = (BlockChange) change; + bc.getCurrent(); + // BaseBlock previous = bc.getPrevious(); + // BlockVector pos = bc.getPosition(); + // int x = pos.getBlockX(); + // int y = pos.getBlockY(); + // int z = pos.getBlockZ(); + changes.add(bc); + } else { + changes.add(change); + } + } + + @Override + public Iterator backwardIterator() { + return changes.descendingIterator(); + } + + @Override + public Iterator forwardIterator() { + return changes.iterator(); + } + + @Override + public int size() { + return changes.size(); + } +} diff --git a/src/com/boydti/fawe/object/FaweChunk.java b/src/com/boydti/fawe/object/FaweChunk.java new file mode 100644 index 00000000..a8397722 --- /dev/null +++ b/src/com/boydti/fawe/object/FaweChunk.java @@ -0,0 +1,65 @@ +package com.boydti.fawe.object; + +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.util.SetBlockQueue; +import com.sk89q.worldedit.world.biome.BaseBiome; + +public abstract class FaweChunk { + + private ChunkLoc chunk; + + /** + * A FaweSections object represents a chunk and the blocks that you wish to change in it. + */ + public FaweChunk(final ChunkLoc chunk) { + this.chunk = chunk; + } + + public void setChunkLoc(final ChunkLoc loc) { + this.chunk = loc; + } + + public ChunkLoc getChunkLoc() { + return this.chunk; + } + + public void addToQueue() { + if (chunk == null) { + throw new IllegalArgumentException("Chunk location cannot be null!"); + } + SetBlockQueue.IMP.queue.setChunk(this); + } + + public void fixLighting() { + SetBlockQueue.IMP.queue.fixLighting(this, Settings.FIX_ALL_LIGHTING); + } + + public void fill(int id, byte data) { + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 256; y++) { + for (int z = 0; z < 16; z++) { + setBlock(x, y, z, id, data); + } + } + } + } + + public abstract T getChunk(); + + public abstract void setBlock(final int x, final int y, final int z, final int id, final byte data); + + public abstract void setBiome(int x, int z, BaseBiome biome); + + @Override + public int hashCode() { + return chunk.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof FaweChunk)) { + return false; + } + return chunk.equals(((FaweChunk) obj).chunk); + } +} diff --git a/src/com/boydti/fawe/object/FaweCommand.java b/src/com/boydti/fawe/object/FaweCommand.java new file mode 100644 index 00000000..22dcee39 --- /dev/null +++ b/src/com/boydti/fawe/object/FaweCommand.java @@ -0,0 +1,15 @@ +package com.boydti.fawe.object; + +public abstract class FaweCommand { + public final String perm; + + public FaweCommand(final String perm) { + this.perm = perm; + } + + public String getPerm() { + return perm; + } + + public abstract boolean execute(final FawePlayer player, final String... args); +} diff --git a/src/com/boydti/fawe/object/FaweLocation.java b/src/com/boydti/fawe/object/FaweLocation.java new file mode 100644 index 00000000..a3e5c795 --- /dev/null +++ b/src/com/boydti/fawe/object/FaweLocation.java @@ -0,0 +1,45 @@ +package com.boydti.fawe.object; + +import com.boydti.fawe.util.SetBlockQueue; + +/** + */ +public class FaweLocation { + + + public final int x; + public final int y; + public final int z; + public final String world; + + public FaweLocation(String world, int x, int y, int z) { + this.world = world; + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final FaweLocation other = (FaweLocation) obj; + return ((x == other.x) && (y == other.y) && (z == other.z) && (world.equals(other.world))); + } + + @Override + public int hashCode() { + return x << 8 + z << 4 + y; + } + + public void setBlockAsync(short id, byte data) { + SetBlockQueue.IMP.setBlock(world, x, y, z, id, data); + } +} diff --git a/src/com/boydti/fawe/object/FawePlayer.java b/src/com/boydti/fawe/object/FawePlayer.java new file mode 100644 index 00000000..9142bab2 --- /dev/null +++ b/src/com/boydti/fawe/object/FawePlayer.java @@ -0,0 +1,87 @@ +package com.boydti.fawe.object; + +import java.util.HashSet; +import java.util.UUID; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.util.WEManager; +import com.sk89q.worldedit.IncompleteRegionException; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.regions.RegionSelector; +import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; + +public abstract class FawePlayer { + + public final T parent; + private LocalSession session; + + public static FawePlayer wrap(final Object obj) { + return Fawe.imp().wrap(obj); + } + + public FawePlayer(final T parent) { + this.parent = parent; + } + + public abstract String getName(); + + public abstract UUID getUUID(); + + public abstract boolean hasPermission(final String perm); + + public abstract void setPermission(final String perm, final boolean flag); + + public abstract void sendMessage(final String message); + + public abstract void executeCommand(final String substring); + + public abstract FaweLocation getLocation(); + + public abstract Player getPlayer(); + + public Region getSelection() { + try { + return getSession().getSelection(getPlayer().getWorld()); + } catch (IncompleteRegionException e) { + return null; + } + } + + public LocalSession getSession() { + return session != null ? session : Fawe.get().getWorldEdit().getSession(getPlayer()); + } + + public HashSet getCurrentRegions() { + return WEManager.IMP.getMask(this); + } + + public void setSelection(RegionWrapper region) { + Player player = getPlayer(); + RegionSelector selector = new CuboidRegionSelector(player.getWorld(), region.getBottomVector(), region.getTopVector()); + getSession().setRegionSelector(player.getWorld(), selector); + } + + public RegionWrapper getLargestRegion() { + int area = 0; + RegionWrapper max = null; + for (RegionWrapper region : getCurrentRegions()) { + int tmp = (region.maxX - region.minX) * (region.maxZ - region.minZ); + if (tmp > area) { + area = tmp; + max = region; + } + } + return max; + } + + @Override + public String toString() { + return getName(); + } + + public boolean hasWorldEditBypass() { + return hasPermission("fawe.bypass"); + } +} diff --git a/src/com/boydti/fawe/object/HistoryExtent.java b/src/com/boydti/fawe/object/HistoryExtent.java new file mode 100644 index 00000000..30d360d1 --- /dev/null +++ b/src/com/boydti/fawe/object/HistoryExtent.java @@ -0,0 +1,221 @@ +package com.boydti.fawe.object; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nullable; + +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.history.change.BlockChange; +import com.sk89q.worldedit.history.change.EntityCreate; +import com.sk89q.worldedit.history.change.EntityRemove; +import com.sk89q.worldedit.history.changeset.ChangeSet; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.Location; + +/** + * Stores changes to a {@link ChangeSet}. + */ +public class HistoryExtent extends AbstractDelegateExtent { + + private final ChangeSet changeSet; + + /** + * Create a new instance. + * + * @param extent the extent + * @param changeSet the change set + * @param thread + */ + public HistoryExtent(final Extent extent, final ChangeSet changeSet) { + super(extent); + checkNotNull(changeSet); + this.changeSet = changeSet; + } + + @Override + public synchronized boolean setBlock(final Vector location, final BaseBlock block) throws WorldEditException { + if (super.setBlock(location, block)) { + BaseBlock previous; + try { + previous = getBlock(location); + } catch (final Exception e) { + previous = getBlock(location); + } + final int id_p = previous.getId(); + final int id_b = block.getId(); + if (id_p == id_b) { + switch (id_p) { + case 0: + case 2: + case 4: + case 13: + case 14: + case 15: + case 20: + case 21: + case 22: + case 25: + case 30: + case 32: + case 37: + case 39: + case 40: + case 41: + case 42: + case 45: + case 46: + case 47: + case 48: + case 49: + case 50: + case 51: + case 52: + case 54: + case 55: + case 56: + case 57: + case 58: + case 60: + case 61: + case 62: + case 7: + case 8: + case 9: + case 10: + case 11: + case 73: + case 74: + case 75: + case 76: + case 78: + case 79: + case 80: + case 81: + case 82: + case 83: + case 84: + case 85: + case 87: + case 88: + case 101: + case 102: + case 103: + case 110: + case 112: + case 113: + case 117: + case 121: + case 122: + case 123: + case 124: + case 129: + case 133: + case 138: + case 137: + case 140: + case 165: + case 166: + case 169: + case 170: + case 172: + case 173: + case 174: + case 176: + case 177: + case 181: + case 182: + case 188: + case 189: + case 190: + case 191: + case 192: + return false; + default: + if (block.getData() == previous.getData()) { + return false; + } + } + } + changeSet.add(new BlockChange(location.toBlockVector(), previous, block)); + return true; + } + return false; + } + + @Nullable + @Override + public Entity createEntity(final Location location, final BaseEntity state) { + final Entity entity = super.createEntity(location, state); + if (state != null) { + changeSet.add(new EntityCreate(location, state, entity)); + } + return entity; + } + + @Override + public List getEntities() { + return wrapEntities(super.getEntities()); + } + + @Override + public List getEntities(final Region region) { + return wrapEntities(super.getEntities(region)); + } + + private List wrapEntities(final List entities) { + final List newList = new ArrayList(entities.size()); + for (final Entity entity : entities) { + newList.add(new TrackedEntity(entity)); + } + return newList; + } + + private class TrackedEntity implements Entity { + private final Entity entity; + + private TrackedEntity(final Entity entity) { + this.entity = entity; + } + + @Override + public BaseEntity getState() { + return entity.getState(); + } + + @Override + public Location getLocation() { + return entity.getLocation(); + } + + @Override + public Extent getExtent() { + return entity.getExtent(); + } + + @Override + public boolean remove() { + final Location location = entity.getLocation(); + final BaseEntity state = entity.getState(); + final boolean success = entity.remove(); + if ((state != null) && success) { + changeSet.add(new EntityRemove(location, state)); + } + return success; + } + + @Nullable + @Override + public T getFacet(final Class cls) { + return entity.getFacet(cls); + } + } +} diff --git a/src/com/boydti/fawe/object/IntegerPair.java b/src/com/boydti/fawe/object/IntegerPair.java new file mode 100644 index 00000000..0ef75507 --- /dev/null +++ b/src/com/boydti/fawe/object/IntegerPair.java @@ -0,0 +1,50 @@ +package com.boydti.fawe.object; + +public class IntegerPair { + public int x; + public int z; + + public IntegerPair(final int x, final int z) { + this.x = x; + this.z = z; + } + + int hash; + + @Override + public int hashCode() { + if (hash == 0) { + long val = 0; + if (x >= 0) { + if (z >= 0) { + val = (x * x) + (3 * x) + (2 * x * z) + z + (z * z); + } else { + final int z1 = -z; + val = (x * x) + (3 * x) + (2 * x * z1) + z1 + (z1 * z1) + 1; + } + } else { + final int x1 = -x; + if (z >= 0) { + val = -((x1 * x1) + (3 * x1) + (2 * x1 * z) + z + (z * z)); + } else { + final int z1 = -z; + val = -((x1 * x1) + (3 * x1) + (2 * x1 * z1) + z1 + (z1 * z1) + 1); + } + } + hash = (int) (val % Integer.MAX_VALUE); + } + return hash; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if ((obj == null) || (hashCode() != obj.hashCode()) || (getClass() != obj.getClass())) { + return false; + } + final IntegerPair other = (IntegerPair) obj; + return ((x == other.x) && (z == other.z)); + } +} diff --git a/src/com/boydti/fawe/object/NullExtent.java b/src/com/boydti/fawe/object/NullExtent.java new file mode 100644 index 00000000..2b97474d --- /dev/null +++ b/src/com/boydti/fawe/object/NullExtent.java @@ -0,0 +1,79 @@ +package com.boydti.fawe.object; + +import java.util.ArrayList; +import java.util.List; + +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.biome.BaseBiome; + +public class NullExtent implements Extent { + + private final BaseBiome nullBiome = new BaseBiome(0); + + private final BaseBlock nullBlock = new BaseBlock(0); + + @Override + public BaseBiome getBiome(final Vector2D arg0) { + return nullBiome; + } + + @Override + public BaseBlock getBlock(final Vector arg0) { + return nullBlock; + } + + @Override + public BaseBlock getLazyBlock(final Vector arg0) { + return nullBlock; + } + + @Override + public Operation commit() { + return null; + } + + @Override + public boolean setBiome(final Vector2D arg0, final BaseBiome arg1) { + return false; + } + + @Override + public boolean setBlock(final Vector arg0, final BaseBlock arg1) throws WorldEditException { + return false; + } + + @Override + public Entity createEntity(final Location arg0, final BaseEntity arg1) { + return null; + } + + @Override + public List getEntities() { + return new ArrayList<>(); + } + + @Override + public List getEntities(final Region arg0) { + return new ArrayList<>(); + } + + @Override + public Vector getMaximumPoint() { + return new Vector(0, 0, 0); + } + + @Override + public Vector getMinimumPoint() { + return new Vector(0, 0, 0); + } + +} diff --git a/src/com/boydti/fawe/object/ProcessedWEExtent.java b/src/com/boydti/fawe/object/ProcessedWEExtent.java new file mode 100644 index 00000000..157d231a --- /dev/null +++ b/src/com/boydti/fawe/object/ProcessedWEExtent.java @@ -0,0 +1,297 @@ +package com.boydti.fawe.object; + +import java.util.HashSet; +import java.util.List; + +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.SetBlockQueue; +import com.boydti.fawe.util.WEManager; +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.biome.BaseBiome; + +public class ProcessedWEExtent extends AbstractDelegateExtent { + private Extent parent; + + private boolean BSblocked = false; + private boolean Eblocked = false; + private int BScount = 0; + private int Ecount = 0; + private int count = 0; + + private int max; + private final FawePlayer user; + private final String world; + private final HashSet mask; + private final Thread thread; + + public ProcessedWEExtent(World world, Thread thread, FawePlayer player, HashSet mask, int max) { + super(world); + this.user = player; + this.world = world.getName(); + this.max = max != -1 ? max : Integer.MAX_VALUE; + this.mask = mask; + this.thread = thread; + } + + public void setMax(int max) { + this.max = max != -1 ? max : Integer.MAX_VALUE; + } + + public void setParent(Extent parent) { + this.parent = parent; + } + + @Override + public Entity createEntity(Location location, BaseEntity entity) { + if (Eblocked) { + return null; + } + Ecount++; + if (Ecount > Settings.MAX_ENTITIES) { + Eblocked = true; + MainUtil.sendAdmin(BBC.WORLDEDIT_DANGEROUS_WORLDEDIT.format(world + ": " + location.getBlockX() + "," + location.getBlockY() + "," + location.getBlockZ(), user)); + } + if (WEManager.IMP.maskContains(mask, location.getBlockX(), location.getBlockZ())) { + synchronized (thread) { + return super.createEntity(location, entity); + } + } + return null; + } + + @Override + public BaseBiome getBiome(Vector2D position) { + synchronized (thread) { + return super.getBiome(position); + } + } + + private BaseBlock lastBlock; + private BlockVector lastVector; + + @Override + public BaseBlock getLazyBlock(Vector position) { + if (lastBlock != null && lastVector.equals(position.toBlockVector())) { + return lastBlock; + } + synchronized (thread) { + lastVector = position.toBlockVector(); + return lastBlock = super.getLazyBlock(position); + } + } + + @Override + public List getEntities() { + synchronized (thread) { + return super.getEntities(); + } + } + + @Override + public List getEntities(Region region) { + synchronized (thread) { + return super.getEntities(region); + } + } + + @Override + public BaseBlock getBlock(Vector position) { + synchronized (thread) { + return super.getLazyBlock(position); + } + } + + @Override + public boolean setBlock(final Vector location, final BaseBlock block) throws WorldEditException { + final short id = (short) block.getType(); + switch (id) { + case 54: + case 130: + case 142: + case 27: + case 137: + case 52: + case 154: + case 84: + case 25: + case 144: + case 138: + case 176: + case 177: + case 63: + case 119: + case 68: + case 323: + case 117: + case 116: + case 28: + case 66: + case 157: + case 61: + case 62: + case 140: + case 146: + case 149: + case 150: + case 158: + case 23: + case 123: + case 124: + case 29: + case 33: + case 151: + case 178: { + if (BSblocked) { + return false; + } + BScount++; + if (BScount > Settings.MAX_BLOCKSTATES) { + BSblocked = true; + MainUtil.sendAdmin(BBC.WORLDEDIT_DANGEROUS_WORLDEDIT.format(world + ": " + location.getBlockX() + "," + location.getBlockY() + "," + location.getBlockZ(), user)); + } + final int x = location.getBlockX(); + final int z = location.getBlockZ(); + if (WEManager.IMP.maskContains(mask, x, z)) { + if (count++ > max) { + if (parent != null) { + WEManager.IMP.cancelEdit(parent); + parent = null; + } + return false; + } + SetBlockQueue.IMP.setBlock(world, x, location.getBlockY(), z, id, (byte) block.getData()); + } + break; + } + default: { + final int x = location.getBlockX(); + final int y = location.getBlockY(); + final int z = location.getBlockZ(); + if (WEManager.IMP.maskContains(mask, location.getBlockX(), location.getBlockZ())) { + if (count++ > max) { + WEManager.IMP.cancelEdit(parent); + parent = null; + return false; + } + switch (id) { + case 0: + case 2: + case 4: + case 13: + case 14: + case 15: + case 20: + case 21: + case 22: + case 25: + case 30: + case 32: + case 37: + case 39: + case 40: + case 41: + case 42: + case 45: + case 46: + case 47: + case 48: + case 49: + case 50: + case 51: + case 52: + case 54: + case 55: + case 56: + case 57: + case 58: + case 60: + case 61: + case 62: + case 7: + case 8: + case 9: + case 10: + case 11: + case 73: + case 74: + case 75: + case 76: + case 78: + case 79: + case 80: + case 81: + case 82: + case 83: + case 84: + case 85: + case 87: + case 88: + case 101: + case 102: + case 103: + case 110: + case 112: + case 113: + case 117: + case 121: + case 122: + case 123: + case 124: + case 129: + case 133: + case 138: + case 137: + case 140: + case 165: + case 166: + case 169: + case 170: + case 172: + case 173: + case 174: + case 176: + case 177: + case 181: + case 182: + case 188: + case 189: + case 190: + case 191: + case 192: { + SetBlockQueue.IMP.setBlock(world, x, y, z, id); + break; + } + default: { + SetBlockQueue.IMP.setBlock(world, x, y, z, id, (byte) block.getData()); + break; + } + } + return true; + } + } + + } + return false; + } + + @Override + public boolean setBiome(final Vector2D position, final BaseBiome biome) { + if (WEManager.IMP.maskContains(mask, position.getBlockX(), position.getBlockZ())) { + SetBlockQueue.IMP.setBiome(world, position.getBlockX(), position.getBlockZ(), biome); + } + return false; + } +} diff --git a/src/com/boydti/fawe/object/PseudoRandom.java b/src/com/boydti/fawe/object/PseudoRandom.java new file mode 100644 index 00000000..f941e87a --- /dev/null +++ b/src/com/boydti/fawe/object/PseudoRandom.java @@ -0,0 +1,34 @@ +package com.boydti.fawe.object; + +public class PseudoRandom { + private long state; + + public PseudoRandom() { + state = System.nanoTime(); + } + + public PseudoRandom(final long state) { + this.state = state; + } + + public long nextLong() { + final long a = state; + state = xorShift64(a); + return a; + } + + public long xorShift64(long a) { + a ^= (a << 21); + a ^= (a >>> 35); + a ^= (a << 4); + return a; + } + + public int random(final int n) { + if (n == 1) { + return 0; + } + final long r = ((nextLong() >>> 32) * n) >> 32; + return (int) r; + } +} diff --git a/src/com/boydti/fawe/object/RegionWrapper.java b/src/com/boydti/fawe/object/RegionWrapper.java new file mode 100644 index 00000000..f23bc96b --- /dev/null +++ b/src/com/boydti/fawe/object/RegionWrapper.java @@ -0,0 +1,41 @@ +package com.boydti.fawe.object; + +import com.sk89q.worldedit.Vector; + +public class RegionWrapper { + public int minX; + public int maxX; + public int minZ; + public int maxZ; + + public RegionWrapper(final int minX, final int maxX, final int minZ, final int maxZ) { + this.maxX = maxX; + this.minX = minX; + this.maxZ = maxZ; + this.minZ = minZ; + } + + public RegionWrapper(Vector pos1, Vector pos2) { + this.minX = Math.min(pos1.getBlockX(), pos2.getBlockX()); + this.minZ = Math.min(pos1.getBlockZ(), pos2.getBlockZ()); + this.maxX = Math.max(pos1.getBlockX(), pos2.getBlockX()); + this.maxZ = Math.max(pos1.getBlockZ(), pos2.getBlockZ()); + } + + public boolean isIn(final int x, final int z) { + return ((x >= minX) && (x <= maxX) && (z >= minZ) && (z <= maxZ)); + } + + @Override + public String toString() { + return minX + "," + minZ + "->" + maxX + "," + maxZ; + } + + public Vector getBottomVector() { + return new Vector(minX, 1, minZ); + } + + public Vector getTopVector() { + return new Vector(maxX, 255, maxZ); + } +} diff --git a/src/com/boydti/fawe/object/SendChunk.java b/src/com/boydti/fawe/object/SendChunk.java new file mode 100644 index 00000000..13a3efa2 --- /dev/null +++ b/src/com/boydti/fawe/object/SendChunk.java @@ -0,0 +1,10 @@ +package com.boydti.fawe.object; + +import java.util.Collection; + +public abstract class SendChunk { + + public abstract void fixLighting(final Collection locs); + + public abstract void update(final Collection locs); +} diff --git a/src/com/boydti/fawe/regions/FaweMaskManager.java b/src/com/boydti/fawe/regions/FaweMaskManager.java new file mode 100644 index 00000000..a91c44cc --- /dev/null +++ b/src/com/boydti/fawe/regions/FaweMaskManager.java @@ -0,0 +1,23 @@ +package com.boydti.fawe.regions; + +import com.boydti.fawe.bukkit.regions.FaweMask; +import com.boydti.fawe.object.FawePlayer; + +public abstract class FaweMaskManager { + private final String key; + + public FaweMaskManager(String plugin) { + this.key = plugin.toLowerCase(); + } + + public String getKey() { + return this.key; + } + + @Override + public String toString() { + return this.key; + } + + public abstract FaweMask getMask(final FawePlayer player); +} diff --git a/src/com/boydti/fawe/util/ExtentWrapper.java b/src/com/boydti/fawe/util/ExtentWrapper.java new file mode 100644 index 00000000..0ad347d2 --- /dev/null +++ b/src/com/boydti/fawe/util/ExtentWrapper.java @@ -0,0 +1,11 @@ +package com.boydti.fawe.util; + +import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.extent.Extent; + +public class ExtentWrapper extends AbstractDelegateExtent { + + public ExtentWrapper(final Extent extent) { + super(extent); + } +} diff --git a/src/com/boydti/fawe/util/FaweQueue.java b/src/com/boydti/fawe/util/FaweQueue.java new file mode 100644 index 00000000..d7f27833 --- /dev/null +++ b/src/com/boydti/fawe/util/FaweQueue.java @@ -0,0 +1,40 @@ +package com.boydti.fawe.util; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.ChunkLoc; +import com.boydti.fawe.object.FaweChunk; +import com.sk89q.worldedit.world.biome.BaseBiome; + +public abstract class FaweQueue { + public abstract boolean setBlock(final String world, final int x, final int y, final int z, final short id, final byte data); + + public abstract boolean setBiome(final String world, final int x, final int z, final BaseBiome biome); + + public abstract FaweChunk getChunk(ChunkLoc wrap); + + public abstract void setChunk(FaweChunk chunk); + + public abstract boolean fixLighting(FaweChunk chunk, boolean fixAll); + + /** + * Gets the FaweChunk and sets the requested blocks + * @return + */ + public abstract FaweChunk next(); + + public void saveMemory() { + MainUtil.sendAdmin(BBC.OOM.s()); + // Set memory limited + MemUtil.memoryLimitedTask(); + // Clear block placement + SetBlockQueue.IMP.queue.clear(); + Fawe.get().getWorldEdit().clearSessions(); + // GC + System.gc(); + System.gc(); + // Unload chunks + } + + protected abstract void clear(); +} diff --git a/src/com/boydti/fawe/util/Lag.java b/src/com/boydti/fawe/util/Lag.java new file mode 100644 index 00000000..55463221 --- /dev/null +++ b/src/com/boydti/fawe/util/Lag.java @@ -0,0 +1,81 @@ +package com.boydti.fawe.util; + +/** + * TPS and Lag Checker. + */ +public class Lag implements Runnable { + /** + * Ticks + */ + public static final long[] T = new long[600]; + /** + * Tick count + */ + public static int TC = 0; + /** + * something :_: + */ + @SuppressWarnings("unused") + public static long LT = 0L; + + /** + * Get the server TPS + * + * @return server tick per second + */ + public static double getTPS() { + return Math.round(getTPS(100)) > 20.0D ? 20.0D : Math.round(getTPS(100)); + } + + /** + * Return the tick per second (measured in $ticks) + * + * @param ticks Ticks + * + * @return ticks per second + */ + public static double getTPS(final int ticks) { + if (TC < ticks) { + return 20.0D; + } + final int t = (TC - 1 - ticks) % T.length; + final long e = System.currentTimeMillis() - T[t]; + return ticks / (e / 1000.0D); + } + + /** + * Get number of ticks since + * + * @param tI Ticks < + * + * @return number of ticks since $tI + */ + public static long getElapsed(final int tI) { + final long t = T[tI % T.length]; + return System.currentTimeMillis() - t; + } + + /** + * Get lag percentage + * + * @return lag percentage + */ + public static double getPercentage() { + return Math.round((1.0D - (Lag.getTPS() / 20.0D)) * 100.0D); + } + + /** + * Get TPS percentage (of 20) + * + * @return TPS percentage + */ + public static double getFullPercentage() { + return getTPS() * 5.0D; + } + + @Override + public void run() { + T[TC % T.length] = System.currentTimeMillis(); + TC++; + } +} diff --git a/src/com/boydti/fawe/util/MainUtil.java b/src/com/boydti/fawe/util/MainUtil.java new file mode 100644 index 00000000..ae681db3 --- /dev/null +++ b/src/com/boydti/fawe/util/MainUtil.java @@ -0,0 +1,32 @@ +package com.boydti.fawe.util; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.object.FawePlayer; + +public class MainUtil { + /* + * Generic non plugin related utils + * e.g. sending messages + */ + public static void sendMessage(final FawePlayer player, String message) { + message = ChatColor.translateAlternateColorCodes('&', message); + if (player == null) { + Fawe.debug(message); + } else { + player.sendMessage(message); + } + } + + public static void sendAdmin(final String s) { + for (final Player player : Bukkit.getOnlinePlayers()) { + if (player.hasPermission("fawe.admin")) { + player.sendMessage(s); + } + } + Fawe.debug(s); + } +} diff --git a/src/com/boydti/fawe/util/MemUtil.java b/src/com/boydti/fawe/util/MemUtil.java new file mode 100644 index 00000000..96ea37dc --- /dev/null +++ b/src/com/boydti/fawe/util/MemUtil.java @@ -0,0 +1,41 @@ +package com.boydti.fawe.util; + +import java.util.concurrent.atomic.AtomicBoolean; + +import com.boydti.fawe.config.Settings; + +public class MemUtil { + + private static AtomicBoolean memory = new AtomicBoolean(false); + + public static boolean isMemoryFree() { + return !memory.get(); + } + + public static boolean isMemoryLimited() { + return memory.get(); + } + + public static int calculateMemory() { + final long heapSize = Runtime.getRuntime().totalMemory(); + final long heapMaxSize = Runtime.getRuntime().maxMemory(); + if (heapSize < heapMaxSize) { + return Integer.MAX_VALUE; + } + final long heapFreeSize = Runtime.getRuntime().freeMemory(); + final int size = (int) ((heapFreeSize * 100) / heapMaxSize); + if (size > (100 - Settings.MEM_FREE)) { + memoryPlentifulTask(); + return Integer.MAX_VALUE; + } + return size; + } + + public static void memoryLimitedTask() { + memory.set(true); + } + + public static void memoryPlentifulTask() { + memory.set(false); + } +} diff --git a/src/com/boydti/fawe/util/Perm.java b/src/com/boydti/fawe/util/Perm.java new file mode 100644 index 00000000..a6fa9e64 --- /dev/null +++ b/src/com/boydti/fawe/util/Perm.java @@ -0,0 +1,44 @@ +package com.boydti.fawe.util; + +import com.boydti.fawe.object.FawePlayer; + +public enum Perm { + /* + * Permission related functions + */ + ADMIN("fawe.admin", "admin"); + + public String s; + public String cat; + + Perm(final String perm, final String cat) { + s = perm; + this.cat = cat; + } + + public boolean has(final FawePlayer player) { + return hasPermission(player, this); + } + + public boolean hasPermission(final FawePlayer player, final Perm perm) { + return hasPermission(player, perm.s); + } + + public static boolean hasPermission(final FawePlayer player, final String perm) { + if ((player == null) || player.hasPermission(ADMIN.s)) { + return true; + } + if (player.hasPermission(perm)) { + return true; + } + final String[] nodes = perm.split("\\."); + final StringBuilder n = new StringBuilder(); + for (int i = 0; i < (nodes.length - 1); i++) { + n.append(nodes[i] + (".")); + if (player.hasPermission(n + "*")) { + return true; + } + } + return false; + } +} diff --git a/src/com/boydti/fawe/util/ReflectionUtils.java b/src/com/boydti/fawe/util/ReflectionUtils.java new file mode 100644 index 00000000..146d8abd --- /dev/null +++ b/src/com/boydti/fawe/util/ReflectionUtils.java @@ -0,0 +1,706 @@ +package com.boydti.fawe.util; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.bukkit.Bukkit; +import org.bukkit.Server; + +/** + * @author DPOH-VAR + * @version 1.0 + */ +@SuppressWarnings({ "UnusedDeclaration", "rawtypes" }) +public class ReflectionUtils { + /** + * prefix of bukkit classes + */ + private static String preClassB = "org.bukkit.craftbukkit"; + /** + * prefix of minecraft classes + */ + private static String preClassM = "net.minecraft.server"; + /** + * boolean value, TRUE if server uses forge or MCPC+ + */ + private static boolean forge = false; + /** check server version and class names */ + static { + if (Bukkit.getServer() != null) { + if (Bukkit.getVersion().contains("MCPC") || Bukkit.getVersion().contains("Forge")) { + forge = true; + } + final Server server = Bukkit.getServer(); + final Class bukkitServerClass = server.getClass(); + String[] pas = bukkitServerClass.getName().split("\\."); + if (pas.length == 5) { + final String verB = pas[3]; + preClassB += "." + verB; + } + try { + final Method getHandle = bukkitServerClass.getDeclaredMethod("getHandle"); + final Object handle = getHandle.invoke(server); + final Class handleServerClass = handle.getClass(); + pas = handleServerClass.getName().split("\\."); + if (pas.length == 5) { + final String verM = pas[3]; + preClassM += "." + verM; + } + } catch (final Exception ignored) {} + } + } + + public static Class getNmsClass(final String name) { + final String className = "net.minecraft.server." + getVersion() + "." + name; + return getClass(className); + } + + public static Class getCbClass(final String name) { + final String className = "org.bukkit.craftbukkit." + getVersion() + "." + name; + return getClass(className); + } + + public static Class getUtilClass(final String name) { + try { + return Class.forName(name); //Try before 1.8 first + } catch (final ClassNotFoundException ex) { + try { + return Class.forName("net.minecraft.util." + name); //Not 1.8 + } catch (final ClassNotFoundException ex2) { + return null; + } + } + } + + public static String getVersion() { + final String packageName = Bukkit.getServer().getClass().getPackage().getName(); + return packageName.substring(packageName.lastIndexOf('.') + 1); + } + + public static Object getHandle(final Object wrapper) { + final Method getHandle = makeMethod(wrapper.getClass(), "getHandle"); + return callMethod(getHandle, wrapper); + } + + //Utils + public static Method makeMethod(final Class clazz, final String methodName, final Class... paramaters) { + try { + return clazz.getDeclaredMethod(methodName, paramaters); + } catch (final NoSuchMethodException ex) { + return null; + } catch (final Exception ex) { + throw new RuntimeException(ex); + } + } + + @SuppressWarnings("unchecked") + public static T callMethod(final Method method, final Object instance, final Object... paramaters) { + if (method == null) { + throw new RuntimeException("No such method"); + } + method.setAccessible(true); + try { + return (T) method.invoke(instance, paramaters); + } catch (final InvocationTargetException ex) { + throw new RuntimeException(ex.getCause()); + } catch (final Exception ex) { + throw new RuntimeException(ex); + } + } + + @SuppressWarnings("unchecked") + public static Constructor makeConstructor(final Class clazz, final Class... paramaterTypes) { + try { + return (Constructor) clazz.getConstructor(paramaterTypes); + } catch (final NoSuchMethodException ex) { + return null; + } catch (final Exception ex) { + throw new RuntimeException(ex); + } + } + + public static T callConstructor(final Constructor constructor, final Object... paramaters) { + if (constructor == null) { + throw new RuntimeException("No such constructor"); + } + constructor.setAccessible(true); + try { + return constructor.newInstance(paramaters); + } catch (final InvocationTargetException ex) { + throw new RuntimeException(ex.getCause()); + } catch (final Exception ex) { + throw new RuntimeException(ex); + } + } + + public static Field makeField(final Class clazz, final String name) { + try { + return clazz.getDeclaredField(name); + } catch (final NoSuchFieldException ex) { + return null; + } catch (final Exception ex) { + throw new RuntimeException(ex); + } + } + + @SuppressWarnings("unchecked") + public static T getField(final Field field, final Object instance) { + if (field == null) { + throw new RuntimeException("No such field"); + } + field.setAccessible(true); + try { + return (T) field.get(instance); + } catch (final Exception ex) { + throw new RuntimeException(ex); + } + } + + public static void setField(final Field field, final Object instance, final Object value) { + if (field == null) { + throw new RuntimeException("No such field"); + } + field.setAccessible(true); + try { + field.set(instance, value); + } catch (final Exception ex) { + throw new RuntimeException(ex); + } + } + + public static Class getClass(final String name) { + try { + return Class.forName(name); + } catch (final ClassNotFoundException ex) { + return null; + } + } + + public static Class getClass(final String name, final Class superClass) { + try { + return Class.forName(name).asSubclass(superClass); + } catch (ClassCastException | ClassNotFoundException ex) { + return null; + } + } + + /** + * @return true if server has forge classes + */ + public static boolean isForge() { + return forge; + } + + /** + * Get class for name. Replace {nms} to net.minecraft.server.V*. Replace {cb} to org.bukkit.craftbukkit.V*. Replace + * {nm} to net.minecraft + * + * @param classes possible class paths + * + * @return RefClass object + * + * @throws RuntimeException if no class found + */ + public static RefClass getRefClass(final String... classes) throws RuntimeException { + for (String className : classes) { + try { + className = className.replace("{cb}", preClassB).replace("{nms}", preClassM).replace("{nm}", "net.minecraft"); + return getRefClass(Class.forName(className)); + } catch (final ClassNotFoundException ignored) {} + } + throw new RuntimeException("no class found"); + } + + /** + * get RefClass object by real class + * + * @param clazz class + * + * @return RefClass based on passed class + */ + public static RefClass getRefClass(final Class clazz) { + return new RefClass(clazz); + } + + /** + * RefClass - utility to simplify work with reflections. + */ + public static class RefClass { + private final Class clazz; + + private RefClass(final Class clazz) { + this.clazz = clazz; + } + + /** + * get passed class + * + * @return class + */ + public Class getRealClass() { + return clazz; + } + + /** + * see {@link Class#isInstance(Object)} + * + * @param object the object to check + * + * @return true if object is an instance of this class + */ + public boolean isInstance(final Object object) { + return clazz.isInstance(object); + } + + /** + * get existing method by name and types + * + * @param name name + * @param types method parameters. can be Class or RefClass + * + * @return RefMethod object + * + * @throws RuntimeException if method not found + */ + public RefMethod getMethod(final String name, final Object... types) throws NoSuchMethodException { + try { + final Class[] classes = new Class[types.length]; + int i = 0; + for (final Object e : types) { + if (e instanceof Class) { + classes[i++] = (Class) e; + } else if (e instanceof RefClass) { + classes[i++] = ((RefClass) e).getRealClass(); + } else { + classes[i++] = e.getClass(); + } + } + try { + return new RefMethod(clazz.getMethod(name, classes)); + } catch (final NoSuchMethodException ignored) { + return new RefMethod(clazz.getDeclaredMethod(name, classes)); + } + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + /** + * get existing constructor by types + * + * @param types parameters. can be Class or RefClass + * + * @return RefMethod object + * + * @throws RuntimeException if constructor not found + */ + public RefConstructor getConstructor(final Object... types) { + try { + final Class[] classes = new Class[types.length]; + int i = 0; + for (final Object e : types) { + if (e instanceof Class) { + classes[i++] = (Class) e; + } else if (e instanceof RefClass) { + classes[i++] = ((RefClass) e).getRealClass(); + } else { + classes[i++] = e.getClass(); + } + } + try { + return new RefConstructor(clazz.getConstructor(classes)); + } catch (final NoSuchMethodException ignored) { + return new RefConstructor(clazz.getDeclaredConstructor(classes)); + } + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + /** + * find method by type parameters + * + * @param types parameters. can be Class or RefClass + * + * @return RefMethod object + * + * @throws RuntimeException if method not found + */ + public RefMethod findMethod(final Object... types) { + final Class[] classes = new Class[types.length]; + int t = 0; + for (final Object e : types) { + if (e instanceof Class) { + classes[t++] = (Class) e; + } else if (e instanceof RefClass) { + classes[t++] = ((RefClass) e).getRealClass(); + } else { + classes[t++] = e.getClass(); + } + } + final List methods = new ArrayList<>(); + Collections.addAll(methods, clazz.getMethods()); + Collections.addAll(methods, clazz.getDeclaredMethods()); + findMethod: for (final Method m : methods) { + final Class[] methodTypes = m.getParameterTypes(); + if (methodTypes.length != classes.length) { + continue; + } + for (final Class aClass : classes) { + if (!Arrays.equals(classes, methodTypes)) { + continue findMethod; + } + return new RefMethod(m); + } + } + throw new RuntimeException("no such method"); + } + + /** + * find method by name + * + * @param names possible names of method + * + * @return RefMethod object + * + * @throws RuntimeException if method not found + */ + public RefMethod findMethodByName(final String... names) { + final List methods = new ArrayList<>(); + Collections.addAll(methods, clazz.getMethods()); + Collections.addAll(methods, clazz.getDeclaredMethods()); + for (final Method m : methods) { + for (final String name : names) { + if (m.getName().equals(name)) { + return new RefMethod(m); + } + } + } + throw new RuntimeException("no such method"); + } + + /** + * find method by return value + * + * @param type type of returned value + * + * @return RefMethod + * + * @throws RuntimeException if method not found + */ + public RefMethod findMethodByReturnType(final RefClass type) { + return findMethodByReturnType(type.clazz); + } + + /** + * find method by return value + * + * @param type type of returned value + * + * @return RefMethod + * + * @throws RuntimeException if method not found + */ + public RefMethod findMethodByReturnType(Class type) { + if (type == null) { + type = void.class; + } + final List methods = new ArrayList<>(); + Collections.addAll(methods, clazz.getMethods()); + Collections.addAll(methods, clazz.getDeclaredMethods()); + for (final Method m : methods) { + if (type.equals(m.getReturnType())) { + return new RefMethod(m); + } + } + throw new RuntimeException("no such method"); + } + + /** + * find constructor by number of arguments + * + * @param number number of arguments + * + * @return RefConstructor + * + * @throws RuntimeException if constructor not found + */ + public RefConstructor findConstructor(final int number) { + final List constructors = new ArrayList<>(); + Collections.addAll(constructors, clazz.getConstructors()); + Collections.addAll(constructors, clazz.getDeclaredConstructors()); + for (final Constructor m : constructors) { + if (m.getParameterTypes().length == number) { + return new RefConstructor(m); + } + } + throw new RuntimeException("no such constructor"); + } + + /** + * get field by name + * + * @param name field name + * + * @return RefField + * + * @throws RuntimeException if field not found + */ + public RefField getField(final String name) { + try { + try { + return new RefField(clazz.getField(name)); + } catch (final NoSuchFieldException ignored) { + return new RefField(clazz.getDeclaredField(name)); + } + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + /** + * find field by type + * + * @param type field type + * + * @return RefField + * + * @throws RuntimeException if field not found + */ + public RefField findField(final RefClass type) { + return findField(type.clazz); + } + + /** + * find field by type + * + * @param type field type + * + * @return RefField + * + * @throws RuntimeException if field not found + */ + public RefField findField(Class type) { + if (type == null) { + type = void.class; + } + final List fields = new ArrayList<>(); + Collections.addAll(fields, clazz.getFields()); + Collections.addAll(fields, clazz.getDeclaredFields()); + for (final Field f : fields) { + if (type.equals(f.getType())) { + return new RefField(f); + } + } + throw new RuntimeException("no such field"); + } + } + + /** + * Method wrapper + */ + public static class RefMethod { + private final Method method; + + private RefMethod(final Method method) { + this.method = method; + method.setAccessible(true); + } + + /** + * @return passed method + */ + public Method getRealMethod() { + return method; + } + + /** + * @return owner class of method + */ + public RefClass getRefClass() { + return new RefClass(method.getDeclaringClass()); + } + + /** + * @return class of method return type + */ + public RefClass getReturnRefClass() { + return new RefClass(method.getReturnType()); + } + + /** + * apply method to object + * + * @param e object to which the method is applied + * + * @return RefExecutor with method call(...) + */ + public RefExecutor of(final Object e) { + return new RefExecutor(e); + } + + /** + * call static method + * + * @param params sent parameters + * + * @return return value + */ + public Object call(final Object... params) { + try { + return method.invoke(null, params); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + public class RefExecutor { + final Object e; + + public RefExecutor(final Object e) { + this.e = e; + } + + /** + * apply method for selected object + * + * @param params sent parameters + * + * @return return value + * + * @throws RuntimeException if something went wrong + */ + public Object call(final Object... params) { + try { + return method.invoke(e, params); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + } + } + + /** + * Constructor wrapper + */ + public static class RefConstructor { + private final Constructor constructor; + + private RefConstructor(final Constructor constructor) { + this.constructor = constructor; + constructor.setAccessible(true); + } + + /** + * @return passed constructor + */ + public Constructor getRealConstructor() { + return constructor; + } + + /** + * @return owner class of method + */ + public RefClass getRefClass() { + return new RefClass(constructor.getDeclaringClass()); + } + + /** + * create new instance with constructor + * + * @param params parameters for constructor + * + * @return new object + * + * @throws RuntimeException if something went wrong + */ + public Object create(final Object... params) { + try { + return constructor.newInstance(params); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + } + + public static class RefField { + private final Field field; + + private RefField(final Field field) { + this.field = field; + field.setAccessible(true); + } + + /** + * @return passed field + */ + public Field getRealField() { + return field; + } + + /** + * @return owner class of field + */ + public RefClass getRefClass() { + return new RefClass(field.getDeclaringClass()); + } + + /** + * @return type of field + */ + public RefClass getFieldRefClass() { + return new RefClass(field.getType()); + } + + /** + * apply fiend for object + * + * @param e applied object + * + * @return RefExecutor with getter and setter + */ + public RefExecutor of(final Object e) { + return new RefExecutor(e); + } + + public class RefExecutor { + final Object e; + + public RefExecutor(final Object e) { + this.e = e; + } + + /** + * set field value for applied object + * + * @param param value + */ + public void set(final Object param) { + try { + field.set(e, param); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + /** + * get field value for applied object + * + * @return value of field + */ + public Object get() { + try { + return field.get(e); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + } + } +} diff --git a/src/com/boydti/fawe/util/SafeExtentWrapper.java b/src/com/boydti/fawe/util/SafeExtentWrapper.java new file mode 100644 index 00000000..6bfdc92b --- /dev/null +++ b/src/com/boydti/fawe/util/SafeExtentWrapper.java @@ -0,0 +1,36 @@ +package com.boydti.fawe.util; + +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FawePlayer; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.extent.Extent; + +public class SafeExtentWrapper extends AbstractDelegateExtent { + private final FawePlayer player; + + public SafeExtentWrapper(final FawePlayer player, final Extent extent) { + super(extent); + this.player = player; + } + + @Override + public boolean setBlock(final Vector location, final BaseBlock block) throws WorldEditException { + if (super.setBlock(location, block)) { + if (MemUtil.isMemoryLimited()) { + if (player != null) { + BBC.WORLDEDIT_OOM.send(player); + if (Perm.hasPermission(player, "worldedit.fast")) { + BBC.WORLDEDIT_OOM_ADMIN.send(player); + } + } + WEManager.IMP.cancelEdit(this); + return false; + } + return true; + } + return false; + } +} diff --git a/src/com/boydti/fawe/util/SetBlockQueue.java b/src/com/boydti/fawe/util/SetBlockQueue.java new file mode 100644 index 00000000..c553abf3 --- /dev/null +++ b/src/com/boydti/fawe/util/SetBlockQueue.java @@ -0,0 +1,143 @@ +package com.boydti.fawe.util; + +import java.util.ArrayDeque; +import java.util.concurrent.atomic.AtomicInteger; + +import com.boydti.fawe.object.FaweChunk; +import com.sk89q.worldedit.world.biome.BaseBiome; + +public class SetBlockQueue { + + public static final SetBlockQueue IMP = new SetBlockQueue(); + + public FaweQueue queue; + + private final AtomicInteger time_waiting = new AtomicInteger(2); + private final AtomicInteger time_current = new AtomicInteger(0); + private final ArrayDeque runnables = new ArrayDeque<>(); + private long last; + private long last2; + + public SetBlockQueue() { + TaskManager.IMP.repeat(new Runnable() { + @Override + public void run() { + if (!MemUtil.isMemoryFree()) { + final int mem = MemUtil.calculateMemory(); + if (mem != Integer.MAX_VALUE) { + if (mem <= 1) { + queue.saveMemory(); + return; + } + if (forceChunkSet()) { + System.gc(); + } else { + time_current.incrementAndGet(); + tasks(); + } + return; + } + } + long free = 50 + Math.min(50 + last - (last = System.currentTimeMillis()), last2 - System.currentTimeMillis()); + time_current.incrementAndGet(); + do { + if (isWaiting()) { + return; + } + final FaweChunk current = queue.next(); + if (current == null) { + time_waiting.set(Math.max(time_waiting.get(), time_current.get() - 2)); + tasks(); + return; + } + } while ((last2 = System.currentTimeMillis()) - last < free); + time_waiting.set(time_current.get() - 1); + } + }, 1); + } + + public boolean forceChunkSet() { + final FaweChunk set = queue.next(); + return set != null; + } + + public boolean isWaiting() { + return time_waiting.get() >= time_current.get(); + } + + public boolean isDone() { + return (time_waiting.get() + 1) < time_current.get(); + } + + public void setWaiting() { + time_waiting.set(time_current.get() + 1); + } + + public boolean addTask(final Runnable whenDone) { + if (isDone()) { + // Run + tasks(); + if (whenDone != null) { + whenDone.run(); + } + return true; + } + if (whenDone != null) { + runnables.add(whenDone); + } + return false; + } + + public boolean tasks() { + if (runnables.size() == 0) { + return false; + } + final ArrayDeque tmp = runnables.clone(); + runnables.clear(); + for (final Runnable runnable : tmp) { + runnable.run(); + } + return true; + } + + /** + * @param world + * @param x + * @param y + * @param z + * @param id + * @param data + * @return + */ + public boolean setBlock(final String world, final int x, final int y, final int z, final short id, final byte data) { + SetBlockQueue.IMP.setWaiting(); + return queue.setBlock(world, x, y, z, id, data); + } + + /** + * @param world + * @param x + * @param y + * @param z + * @param id + * @return + */ + public boolean setBlock(final String world, final int x, final int y, final int z, final short id) { + SetBlockQueue.IMP.setWaiting(); + return queue.setBlock(world, x, y, z, id, (byte) 0); + } + + /** + * @param world + * @param x + * @param y + * @param z + * @param id + * @param data + * @return + */ + public boolean setBiome(final String world, final int x, final int z, BaseBiome biome) { + SetBlockQueue.IMP.setWaiting(); + return queue.setBiome(world, x, z, biome); + } +} diff --git a/src/com/boydti/fawe/util/StringMan.java b/src/com/boydti/fawe/util/StringMan.java new file mode 100644 index 00000000..ab6c1990 --- /dev/null +++ b/src/com/boydti/fawe/util/StringMan.java @@ -0,0 +1,269 @@ +package com.boydti.fawe.util; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class StringMan { + public static String replaceFromMap(final String string, final Map replacements) { + final StringBuilder sb = new StringBuilder(string); + int size = string.length(); + for (final Entry entry : replacements.entrySet()) { + if (size == 0) { + break; + } + final String key = entry.getKey(); + final String value = entry.getValue(); + int start = sb.indexOf(key, 0); + while (start > -1) { + final int end = start + key.length(); + final int nextSearchStart = start + value.length(); + sb.replace(start, end, value); + size -= end - start; + start = sb.indexOf(key, nextSearchStart); + } + } + return sb.toString(); + } + + public static int intersection(Set options, String[] toCheck) { + int count = 0; + for (String check : toCheck) { + if (options.contains(check)) { + count++; + } + } + return count; + } + + public static String getString(final Object obj) { + if (obj == null) { + return "null"; + } + if (obj.getClass() == String.class) { + return (String) obj; + } + if (obj.getClass().isArray()) { + String result = ""; + String prefix = ""; + + for (int i = 0; i < Array.getLength(obj); i++) { + result += prefix + getString(Array.get(obj, i)); + prefix = ","; + } + return "( " + result + " )"; + } else if (obj instanceof Collection) { + String result = ""; + String prefix = ""; + for (final Object element : (Collection) obj) { + result += prefix + getString(element); + prefix = ","; + } + return "[ " + result + " ]"; + } else { + return obj.toString(); + } + } + + public static String replaceFirst(final char c, final String s) { + if (s == null) { + return ""; + } + if (s.isEmpty()) { + return s; + } + char[] chars = s.toCharArray(); + final char[] newChars = new char[chars.length]; + int used = 0; + boolean found = false; + for (final char cc : chars) { + if (!found && (c == cc)) { + found = true; + } else { + newChars[used++] = cc; + } + } + if (found) { + chars = new char[newChars.length - 1]; + System.arraycopy(newChars, 0, chars, 0, chars.length); + return String.valueOf(chars); + } + return s; + } + + public static String replaceAll(final String string, final Object... pairs) { + final StringBuilder sb = new StringBuilder(string); + for (int i = 0; i < pairs.length; i += 2) { + final String key = pairs[i] + ""; + final String value = pairs[i + 1] + ""; + int start = sb.indexOf(key, 0); + while (start > -1) { + final int end = start + key.length(); + final int nextSearchStart = start + value.length(); + sb.replace(start, end, value); + start = sb.indexOf(key, nextSearchStart); + } + } + return sb.toString(); + } + + public static boolean isAlphanumeric(final String str) { + for (int i = 0; i < str.length(); i++) { + final char c = str.charAt(i); + if ((c < 0x30) || ((c >= 0x3a) && (c <= 0x40)) || ((c > 0x5a) && (c <= 0x60)) || (c > 0x7a)) { + return false; + } + } + return true; + } + + public static boolean isAlphanumericUnd(final String str) { + for (int i = 0; i < str.length(); i++) { + final char c = str.charAt(i); + if ((c < 0x30) || ((c >= 0x3a) && (c <= 0x40)) || ((c > 0x5a) && (c <= 0x60)) || (c > 0x7a) || (c == '_')) { + return false; + } + } + return true; + } + + public static boolean isAlpha(final String str) { + for (int i = 0; i < str.length(); i++) { + final char c = str.charAt(i); + if ((c <= 0x40) || ((c > 0x5a) && (c <= 0x60)) || (c > 0x7a)) { + return false; + } + } + return true; + } + + public static String join(final Collection collection, final String delimiter) { + return join(collection.toArray(), delimiter); + } + + public static String joinOrdered(final Collection collection, final String delimiter) { + final Object[] array = collection.toArray(); + Arrays.sort(array, new Comparator() { + @Override + public int compare(final Object a, final Object b) { + return a.hashCode() - b.hashCode(); + } + + }); + return join(array, delimiter); + } + + public static String join(final Collection collection, final char delimiter) { + return join(collection.toArray(), delimiter + ""); + } + + public static boolean isAsciiPrintable(final char c) { + return (c >= ' ') && (c < ''); + } + + public static boolean isAsciiPrintable(final String s) { + for (final char c : s.toCharArray()) { + if (!isAsciiPrintable(c)) { + return false; + } + } + return true; + } + + public static int getLevenshteinDistance(String s, String t) { + int n = s.length(); + int m = t.length(); + if (n == 0) { + return m; + } else if (m == 0) { + return n; + } + if (n > m) { + final String tmp = s; + s = t; + t = tmp; + n = m; + m = t.length(); + } + int p[] = new int[n + 1]; + int d[] = new int[n + 1]; + int _d[]; + int i; + int j; + char t_j; + int cost; + for (i = 0; i <= n; i++) { + p[i] = i; + } + for (j = 1; j <= m; j++) { + t_j = t.charAt(j - 1); + d[0] = j; + + for (i = 1; i <= n; i++) { + cost = s.charAt(i - 1) == t_j ? 0 : 1; + d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost); + } + _d = p; + p = d; + d = _d; + } + return p[n]; + } + + public static String join(final Object[] array, final String delimiter) { + final StringBuilder result = new StringBuilder(); + for (int i = 0, j = array.length; i < j; i++) { + if (i > 0) { + result.append(delimiter); + } + result.append(array[i]); + } + return result.toString(); + } + + public static String join(final int[] array, final String delimiter) { + final Integer[] wrapped = new Integer[array.length]; + for (int i = 0; i < array.length; i++) { + wrapped[i] = array[i]; + } + return join(wrapped, delimiter); + } + + public static boolean isEqualToAny(final String a, final String... args) { + for (final String arg : args) { + if (StringMan.isEqual(a, arg)) { + return true; + } + } + return false; + } + + public static boolean isEqualIgnoreCaseToAny(final String a, final String... args) { + for (final String arg : args) { + if (StringMan.isEqualIgnoreCase(a, arg)) { + return true; + } + } + return false; + } + + public static boolean isEqual(final String a, final String b) { + return ((a == b) || ((a != null) && (b != null) && (a.length() == b.length()) && (a.hashCode() == b.hashCode()) && a.equals(b))); + } + + public static boolean isEqualIgnoreCase(final String a, final String b) { + return ((a == b) || ((a != null) && (b != null) && (a.length() == b.length()) && a.equalsIgnoreCase(b))); + } + + public static String repeat(final String s, final int n) { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < n; i++) { + sb.append(s); + } + return sb.toString(); + } +} diff --git a/src/com/boydti/fawe/util/TaskManager.java b/src/com/boydti/fawe/util/TaskManager.java new file mode 100644 index 00000000..dd05c28f --- /dev/null +++ b/src/com/boydti/fawe/util/TaskManager.java @@ -0,0 +1,20 @@ +package com.boydti.fawe.util; + +public abstract class TaskManager { + + public static TaskManager IMP; + + public abstract int repeat(final Runnable r, final int interval); + + public abstract int repeatAsync(final Runnable r, final int interval); + + public abstract void async(final Runnable r); + + public abstract void task(final Runnable r); + + public abstract void later(final Runnable r, final int delay); + + public abstract void laterAsync(final Runnable r, final int delay); + + public abstract void cancel(final int task); +} diff --git a/src/com/boydti/fawe/util/WEManager.java b/src/com/boydti/fawe/util/WEManager.java new file mode 100644 index 00000000..7826ca9a --- /dev/null +++ b/src/com/boydti/fawe/util/WEManager.java @@ -0,0 +1,130 @@ +package com.boydti.fawe.util; + +import java.lang.reflect.Field; +import java.util.ArrayDeque; +import java.util.HashSet; + +import com.boydti.fawe.bukkit.regions.FaweMask; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.NullExtent; +import com.boydti.fawe.object.RegionWrapper; +import com.boydti.fawe.regions.FaweMaskManager; +import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.extent.Extent; + +public class WEManager { + + public final static WEManager IMP = new WEManager(); + + public final ArrayDeque managers = new ArrayDeque<>(); + + public void cancelEdit(Extent parent) { + try { + final Field field = AbstractDelegateExtent.class.getDeclaredField("extent"); + field.setAccessible(true); + field.set(parent, new NullExtent()); + } catch (final Exception e) { + e.printStackTrace(); + } + parent = null; + } + + public boolean maskContains(final HashSet mask, final int x, final int z) { + for (final RegionWrapper region : mask) { + if ((x >= region.minX) && (x <= region.maxX) && (z >= region.minZ) && (z <= region.maxZ)) { + return true; + } + } + return false; + } + + public HashSet getMask(final FawePlayer player) { + final HashSet regions = new HashSet<>(); + if (player.hasPermission("fawe.bypass")) { + regions.add(new RegionWrapper(Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE)); + return regions; + } + for (final FaweMaskManager manager : managers) { + if (player.hasPermission("fawe." + manager.getKey())) { + final FaweMask mask = manager.getMask(player); + if (mask != null) { + regions.addAll(mask.getRegions()); + } + } + } + return regions; + } + + public boolean intersects(final RegionWrapper region1, final RegionWrapper region2) { + return (region1.minX <= region2.maxX) && (region1.maxX >= region2.minX) && (region1.minZ <= region2.maxZ) && (region1.maxZ >= region2.minZ); + } + + public boolean regionContains(final RegionWrapper selection, final HashSet mask) { + for (final RegionWrapper region : mask) { + if (intersects(region, selection)) { + return true; + } + } + return false; + } + + public boolean delay(final FawePlayer player, final String command) { + final long start = System.currentTimeMillis(); + return delay(player, new Runnable() { + @Override + public void run() { + try { + if ((System.currentTimeMillis() - start) > 1000) { + BBC.WORLDEDIT_RUN.send(FawePlayer.wrap(player)); + } + TaskManager.IMP.task(new Runnable() { + @Override + public void run() { + final long start = System.currentTimeMillis(); + player.executeCommand(command.substring(1)); + TaskManager.IMP.later(new Runnable() { + @Override + public void run() { + SetBlockQueue.IMP.addTask(new Runnable() { + @Override + public void run() { + if ((System.currentTimeMillis() - start) > 1000) { + BBC.WORLDEDIT_COMPLETE.send(FawePlayer.wrap(player)); + } + } + }); + } + }, 2); + } + }); + } catch (final Exception e) { + e.printStackTrace(); + } + } + }, false, false); + } + + public boolean delay(final FawePlayer player, final Runnable whenDone, final boolean delayed, final boolean onlyDelayedExecution) { + final boolean free = SetBlockQueue.IMP.addTask(null); + if (free) { + if (delayed) { + if (whenDone != null) { + whenDone.run(); + } + } else { + if ((whenDone != null) && !onlyDelayedExecution) { + whenDone.run(); + } else { + return false; + } + } + } else { + if (!delayed && (player != null)) { + BBC.WORLDEDIT_DELAYED.send(player); + } + SetBlockQueue.IMP.addTask(whenDone); + } + return true; + } +} diff --git a/src/com/boydti/fawe/util/WESubscriber.java b/src/com/boydti/fawe/util/WESubscriber.java new file mode 100644 index 00000000..d1f67ef1 --- /dev/null +++ b/src/com/boydti/fawe/util/WESubscriber.java @@ -0,0 +1,12 @@ +package com.boydti.fawe.util; + +import com.sk89q.worldedit.event.extent.EditSessionEvent; +import com.sk89q.worldedit.util.eventbus.EventHandler.Priority; +import com.sk89q.worldedit.util.eventbus.Subscribe; + +public class WESubscriber { + + @Subscribe(priority = Priority.VERY_EARLY) + public void onEditSession(final EditSessionEvent event) { + } +} diff --git a/src/com/sk89q/worldedit/EditSession.java b/src/com/sk89q/worldedit/EditSession.java new file mode 100644 index 00000000..0fc7ff87 --- /dev/null +++ b/src/com/sk89q/worldedit/EditSession.java @@ -0,0 +1,2559 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.sk89q.worldedit.regions.Regions.asFlatRegion; +import static com.sk89q.worldedit.regions.Regions.maximumBlockY; +import static com.sk89q.worldedit.regions.Regions.minimumBlockY; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.annotation.Nullable; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.EditSessionWrapper; +import com.boydti.fawe.object.FastWorldEditExtent; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.NullExtent; +import com.boydti.fawe.object.ProcessedWEExtent; +import com.boydti.fawe.object.RegionWrapper; +import com.boydti.fawe.util.ExtentWrapper; +import com.boydti.fawe.util.MemUtil; +import com.boydti.fawe.util.Perm; +import com.boydti.fawe.util.SafeExtentWrapper; +import com.boydti.fawe.util.TaskManager; +import com.boydti.fawe.util.WEManager; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.blocks.BlockID; +import com.sk89q.worldedit.blocks.BlockType; +import com.sk89q.worldedit.command.tool.BrushTool; +import com.sk89q.worldedit.command.tool.Tool; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.event.extent.EditSessionEvent; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extent.ChangeSetExtent; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.extent.MaskingExtent; +import com.sk89q.worldedit.extent.buffer.ForgetfulExtentBuffer; +import com.sk89q.worldedit.extent.inventory.BlockBag; +import com.sk89q.worldedit.extent.reorder.MultiStageReorder; +import com.sk89q.worldedit.extent.world.SurvivalModeExtent; +import com.sk89q.worldedit.function.GroundFunction; +import com.sk89q.worldedit.function.RegionMaskingFilter; +import com.sk89q.worldedit.function.block.BlockReplace; +import com.sk89q.worldedit.function.block.Naturalizer; +import com.sk89q.worldedit.function.generator.GardenPatchGenerator; +import com.sk89q.worldedit.function.mask.BlockMask; +import com.sk89q.worldedit.function.mask.BoundedHeightMask; +import com.sk89q.worldedit.function.mask.ExistingBlockMask; +import com.sk89q.worldedit.function.mask.FuzzyBlockMask; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.mask.MaskIntersection; +import com.sk89q.worldedit.function.mask.MaskUnion; +import com.sk89q.worldedit.function.mask.Masks; +import com.sk89q.worldedit.function.mask.NoiseFilter2D; +import com.sk89q.worldedit.function.mask.RegionMask; +import com.sk89q.worldedit.function.operation.ChangeSetExecutor; +import com.sk89q.worldedit.function.operation.ForwardExtentCopy; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.function.operation.OperationQueue; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.function.pattern.BlockPattern; +import com.sk89q.worldedit.function.pattern.Patterns; +import com.sk89q.worldedit.function.util.RegionOffset; +import com.sk89q.worldedit.function.visitor.DownwardVisitor; +import com.sk89q.worldedit.function.visitor.LayerVisitor; +import com.sk89q.worldedit.function.visitor.NonRisingVisitor; +import com.sk89q.worldedit.function.visitor.RecursiveVisitor; +import com.sk89q.worldedit.function.visitor.RegionVisitor; +import com.sk89q.worldedit.history.UndoContext; +import com.sk89q.worldedit.history.change.BlockChange; +import com.sk89q.worldedit.history.changeset.BlockOptimizedHistory; +import com.sk89q.worldedit.history.changeset.ChangeSet; +import com.sk89q.worldedit.internal.expression.Expression; +import com.sk89q.worldedit.internal.expression.ExpressionException; +import com.sk89q.worldedit.internal.expression.runtime.RValue; +import com.sk89q.worldedit.math.interpolation.Interpolation; +import com.sk89q.worldedit.math.interpolation.KochanekBartelsInterpolation; +import com.sk89q.worldedit.math.interpolation.Node; +import com.sk89q.worldedit.math.noise.RandomNoise; +import com.sk89q.worldedit.math.transform.AffineTransform; +import com.sk89q.worldedit.patterns.Pattern; +import com.sk89q.worldedit.patterns.SingleBlockPattern; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.EllipsoidRegion; +import com.sk89q.worldedit.regions.FlatRegion; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.regions.Regions; +import com.sk89q.worldedit.regions.shape.ArbitraryBiomeShape; +import com.sk89q.worldedit.regions.shape.ArbitraryShape; +import com.sk89q.worldedit.regions.shape.RegionShape; +import com.sk89q.worldedit.regions.shape.WorldEditExpressionEnvironment; +import com.sk89q.worldedit.util.Countable; +import com.sk89q.worldedit.util.TreeGenerator; +import com.sk89q.worldedit.util.collection.DoubleArrayList; +import com.sk89q.worldedit.util.eventbus.EventBus; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.biome.BaseBiome; + +/** + * An {@link Extent} that handles history, {@link BlockBag}s, change limits, + * block re-ordering, and much more. Most operations in WorldEdit use this class. + * + *

Most of the actual functionality is implemented with a number of other + * {@link Extent}s that are chained together. For example, history is logged + * using the {@link ChangeSetExtent}.

+ */ +public class EditSession implements Extent { + + private final Logger log = Logger.getLogger(EditSession.class.getCanonicalName()); + + /** + * Used by {@link #setBlock(Vector, BaseBlock, Stage)} to + * determine which {@link Extent}s should be bypassed. + */ + public enum Stage { + BEFORE_HISTORY, BEFORE_REORDER, BEFORE_CHANGE + } + + protected final World world; + private final ChangeSet changeSet = new BlockOptimizedHistory(); + private final EditSessionWrapper wrapper; + private MultiStageReorder reorderExtent; + private @Nullable Extent changeSetExtent; + private MaskingExtent maskingExtent; + private @Nullable ProcessedWEExtent processed; + private final Extent bypassReorderHistory; + private final Extent bypassHistory; + private final Extent bypassNone; + private boolean fastmode; + private Mask oldMask; + + /** + * Create a new instance. + * + * @param world a world + * @param maxBlocks the maximum number of blocks that can be changed, or -1 to use no limit + * @deprecated use {@link WorldEdit#getEditSessionFactory()} to create {@link EditSession}s + */ + @Deprecated + public EditSession(final LocalWorld world, final int maxBlocks) { + this(world, maxBlocks, null); + } + + /** + * Create a new instance. + * + * @param world a world + * @param maxBlocks the maximum number of blocks that can be changed, or -1 to use no limit + * @param blockBag the block bag to set, or null to use none + * @deprecated use {@link WorldEdit#getEditSessionFactory()} to create {@link EditSession}s + */ + @Deprecated + public EditSession(final LocalWorld world, final int maxBlocks, @Nullable final BlockBag blockBag) { + this(WorldEdit.getInstance().getEventBus(), world, maxBlocks, blockBag, new EditSessionEvent(world, null, maxBlocks, null)); + } + + + private final Thread thread; + + private int changes = 0; + private int maxBlocks; + private BlockBag blockBag; + + /** + * Construct the object with a maximum number of blocks and a block bag. + * + * @param eventBus the event bus + * @param world the world + * @param maxBlocks the maximum number of blocks that can be changed, or -1 to use no limit + * @param blockBag an optional {@link BlockBag} to use, otherwise null + * @param event the event to call with the extent + */ + public EditSession(final EventBus eventBus, final World world, final int maxBlocks, @Nullable final BlockBag blockBag, final EditSessionEvent event) { + checkNotNull(eventBus); + checkArgument(maxBlocks >= -1, "maxBlocks >= -1 required"); + checkNotNull(event); + + this.blockBag = blockBag; + this.maxBlocks = maxBlocks; + this.thread = Fawe.get().getMainThread(); + this.world = world; + wrapper = Fawe.imp().getEditSessionWrapper(this); + + // Invalid; return null extent + if (world == null) { + Extent extent = new NullExtent(); + bypassReorderHistory = extent; + bypassHistory = extent; + bypassNone = extent; + return; + } + Actor actor = event.getActor(); + + // Not a player; bypass history + if (actor == null || !actor.isPlayer()) { + Extent extent = new FastWorldEditExtent(world, thread); + // Everything bypasses + extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_CHANGE); + extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_REORDER); + extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_HISTORY); + bypassReorderHistory = extent; + bypassHistory = extent; + bypassNone = extent; + return; + } + + Extent extent; + String name = actor.getName(); + FawePlayer fp = FawePlayer.wrap(name); + LocalSession session = fp.getSession(); + fastmode = session.hasFastMode(); + if (fp.hasWorldEditBypass()) { + // Bypass skips processing and area restrictions + extent = new FastWorldEditExtent(world, thread); + if (hasFastMode()) { + // Fastmode skips history and memory checks + extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_CHANGE); + extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_REORDER); + extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_HISTORY); + bypassReorderHistory = extent; + bypassHistory = extent; + bypassNone = extent; + return; + } + } else { + if (MemUtil.isMemoryLimited()) { + BBC.WORLDEDIT_OOM.send(fp); + if (Perm.hasPermission(fp, "worldedit.fast")) { + BBC.WORLDEDIT_OOM_ADMIN.send(fp); + } + // Memory limit reached; return null extent + extent = new NullExtent(); + bypassReorderHistory = extent; + bypassHistory = extent; + bypassNone = extent; + return; + } + HashSet mask = WEManager.IMP.getMask(fp); + if (mask.size() == 0) { + if (Perm.hasPermission(fp, "fawe.admin")) { + BBC.WORLDEDIT_BYPASS.send(fp); + } else { + BBC.WORLDEDIT_EXTEND.send(fp); + } + // No allowed area; return null extent + extent = new NullExtent(); + bypassReorderHistory = extent; + bypassHistory = extent; + bypassNone = extent; + return; + } + // Process the WorldEdit action + extent = processed = new ProcessedWEExtent(world, thread, fp, mask, maxBlocks); + if (hasFastMode()) { + // Fastmode skips history, masking, and memory checks + extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_CHANGE); + extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_REORDER); + extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_HISTORY); + extent = new ExtentWrapper(extent); + // Set the parent. This allows efficient cancelling + processed.setParent(extent); + bypassReorderHistory = extent; + bypassHistory = extent; + bypassNone = extent; + return; + } + // Perform memory checks after reorder + extent = new SafeExtentWrapper(fp, extent); + processed.setParent(extent); + } + // Include history, masking and memory checking. + Extent wrapped; + extent = wrapped = wrapExtent(extent, eventBus, event, Stage.BEFORE_CHANGE); + extent = reorderExtent = new MultiStageReorder(extent, false); + extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_REORDER); + extent = changeSetExtent = wrapper.getHistoryExtent(extent, changeSet, fp); + final Player skp = (Player) actor; + final int item = skp.getItemInHand(); + boolean hasMask = session.getMask() != null; + if ((item != 0) && (!hasMask)) { + try { + final Tool tool = session.getTool(item); + if ((tool != null) && (tool instanceof BrushTool)) { + hasMask = ((BrushTool) tool).getMask() != null; + } + } catch (final Exception e) {} + } + if (hasMask) { + extent = maskingExtent = new MaskingExtent(extent, Masks.alwaysTrue()); + } + + extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_HISTORY); + extent = new SafeExtentWrapper(fp, extent); + this.bypassReorderHistory = wrapped; + this.bypassHistory = reorderExtent; + this.bypassNone = extent; + return; + } + + private Extent wrapExtent(final Extent extent, final EventBus eventBus, EditSessionEvent event, final Stage stage) { + event = event.clone(stage); + event.setExtent(extent); + eventBus.post(event); + Extent toReturn = event.getExtent(); + if (toReturn != extent) { + Fawe.debug("&cPotentially inefficient WorldEdit extent: " + toReturn.getClass().getCanonicalName()); + Fawe.debug("&8 - &7For area restrictions, it is recommended to use the FaweAPI"); + Fawe.debug("&8 - &7Ignore this if not an area restriction"); + } + return toReturn; + } + + /** + * Get the world. + * + * @return the world + */ + public World getWorld() { + return world; + } + + /** + * Get the underlying {@link ChangeSet}. + * + * @return the change set + */ + public ChangeSet getChangeSet() { + return changeSet; + } + + /** + * Get the maximum number of blocks that can be changed. -1 will be returned + * if it the limit disabled. + * + * @return the limit (>= 0) or -1 for no limit + */ + public int getBlockChangeLimit() { + return maxBlocks; + } + + /** + * Set the maximum number of blocks that can be changed. + * + * @param limit the limit (>= 0) or -1 for no limit + */ + public void setBlockChangeLimit(final int limit) { + if (processed != null) { + maxBlocks = limit; + processed.setMax(limit); + } + } + + /** + * Returns queue status. + * + * @return whether the queue is enabled + */ + public boolean isQueueEnabled() { + return reorderExtent != null && reorderExtent.isEnabled(); + } + + /** + * Queue certain types of block for better reproduction of those blocks. + */ + public void enableQueue() { + if (reorderExtent != null) { + reorderExtent.setEnabled(true); + } + } + + /** + * Disable the queue. This will flush the queue. + */ + public void disableQueue() { + if (isQueueEnabled()) { + flushQueue(); + } + if (reorderExtent != null) { + reorderExtent.setEnabled(true); + } + } + + /** + * Get the mask. + * + * @return mask, may be null + */ + public Mask getMask() { + return oldMask; + } + + /** + * Set a mask. + * + * @param mask mask or null + */ + public void setMask(final Mask mask) { + if (maskingExtent == null) { + return; + } + oldMask = mask; + if (mask == null) { + maskingExtent.setMask(Masks.alwaysTrue()); + } else { + maskingExtent.setMask(mask); + } + } + + /** + * Set the mask. + * + * @param mask the mask + * @deprecated Use {@link #setMask(Mask)} + */ + @Deprecated + public void setMask(final com.sk89q.worldedit.masks.Mask mask) { + if (mask == null) { + setMask((Mask) null); + } else { + setMask(Masks.wrap(mask)); + } + } + + /** + * Get the {@link SurvivalModeExtent}. + * + * @return the survival simulation extent + */ + public SurvivalModeExtent getSurvivalExtent() { + return null; + } + + /** + * Set whether fast mode is enabled. + * + *

Fast mode may skip lighting checks or adjacent block + * notification.

+ * + * @param enabled true to enable + */ + public void setFastMode(final boolean enabled) { + fastmode = enabled; + } + + /** + * Return fast mode status. + * + *

Fast mode may skip lighting checks or adjacent block + * notification.

+ * + * @return true if enabled + */ + public boolean hasFastMode() { + return fastmode; + } + + /** + * Get the {@link BlockBag} is used. + * + * @return a block bag or null + */ + public BlockBag getBlockBag() { + return blockBag; + } + + /** + * Set a {@link BlockBag} to use. + * + * @param blockBag the block bag to set, or null to use none + */ + public void setBlockBag(final BlockBag blockBag) { + this.blockBag = blockBag; + } + + /** + * Gets the list of missing blocks and clears the list for the next + * operation. + * + * @return a map of missing blocks + */ + public Map popMissingBlocks() { + return new HashMap<>(); + } + + /** + * Get the number of blocks changed, including repeated block changes. + * + *

This number may not be accurate.

+ * + * @return the number of block changes + */ + public int getBlockChangeCount() { + return changes; + } + + @Override + public BaseBiome getBiome(final Vector2D position) { + synchronized (thread) { + return bypassNone.getBiome(position); + } + } + + @Override + public boolean setBiome(final Vector2D position, final BaseBiome biome) { + changes = -1; + return bypassNone.setBiome(position, biome); + } + + @Override + public BaseBlock getLazyBlock(final Vector position) { + synchronized (thread) { + return world.getBlock(position); + } + } + + @Override + public synchronized BaseBlock getBlock(final Vector position) { + synchronized (thread) { + return world.getBlock(position); + } + } + + /** + * Get a block type at the given position. + * + * @param position the position + * @return the block type + * @deprecated Use {@link #getLazyBlock(Vector)} or {@link #getBlock(Vector)} + */ + @Deprecated + public synchronized int getBlockType(final Vector position) { + synchronized (thread) { + return world.getBlockType(position); + } + } + + /** + * Get a block data at the given position. + * + * @param position the position + * @return the block data + * @deprecated Use {@link #getLazyBlock(Vector)} or {@link #getBlock(Vector)} + */ + @Deprecated + public synchronized int getBlockData(final Vector position) { + synchronized (thread) { + return world.getBlockData(position); + } + } + + /** + * Gets the block type at a position. + * + * @param position the position + * @return a block + * @deprecated Use {@link #getBlock(Vector)} + */ + @Deprecated + public BaseBlock rawGetBlock(final Vector position) { + return getBlock(position); + } + + /** + * Returns the highest solid 'terrain' block which can occur naturally. + * + * @param x the X coordinate + * @param z the Z cooridnate + * @param minY minimal height + * @param maxY maximal height + * @return height of highest block found or 'minY' + */ + public int getHighestTerrainBlock(final int x, final int z, final int minY, final int maxY) { + return getHighestTerrainBlock(x, z, minY, maxY, false); + } + + /** + * Returns the highest solid 'terrain' block which can occur naturally. + * + * @param x the X coordinate + * @param z the Z coordinate + * @param minY minimal height + * @param maxY maximal height + * @param naturalOnly look at natural blocks or all blocks + * @return height of highest block found or 'minY' + */ + public int getHighestTerrainBlock(final int x, final int z, final int minY, final int maxY, final boolean naturalOnly) { + return wrapper.getHighestTerrainBlock(x, z, minY, maxY, naturalOnly); + } + + /** + * Set a block, bypassing both history and block re-ordering. + * + * @param position the position to set the block at + * @param block the block + * @param stage the level + * @return whether the block changed + * @throws WorldEditException thrown on a set error + */ + public boolean setBlock(final Vector position, final BaseBlock block, final Stage stage) throws WorldEditException { + changes = -1; + switch (stage) { + case BEFORE_HISTORY: + return bypassNone.setBlock(position, block); + case BEFORE_CHANGE: + return bypassHistory.setBlock(position, block); + case BEFORE_REORDER: + return bypassReorderHistory.setBlock(position, block); + } + + throw new RuntimeException("New enum entry added that is unhandled here"); + } + + /** + * Set a block, bypassing both history and block re-ordering. + * + * @param position the position to set the block at + * @param block the block + * @return whether the block changed + */ + public boolean rawSetBlock(final Vector position, final BaseBlock block) { + try { + return setBlock(position, block, Stage.BEFORE_CHANGE); + } catch (final WorldEditException e) { + throw new RuntimeException("Unexpected exception", e); + } + } + + /** + * Set a block, bypassing history but still utilizing block re-ordering. + * + * @param position the position to set the block at + * @param block the block + * @return whether the block changed + */ + public boolean smartSetBlock(final Vector position, final BaseBlock block) { + try { + return setBlock(position, block, Stage.BEFORE_REORDER); + } catch (final WorldEditException e) { + throw new RuntimeException("Unexpected exception", e); + } + } + + @Override + public boolean setBlock(final Vector position, final BaseBlock block) throws MaxChangedBlocksException { + try { + return setBlock(position, block, Stage.BEFORE_HISTORY); + } catch (final MaxChangedBlocksException e) { + throw e; + } catch (final WorldEditException e) { + throw new RuntimeException("Unexpected exception", e); + } + } + + /** + * Sets the block at a position, subject to both history and block re-ordering. + * + * @param position the position + * @param pattern a pattern to use + * @return Whether the block changed -- not entirely dependable + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public boolean setBlock(final Vector position, final Pattern pattern) throws MaxChangedBlocksException { + return setBlock(position, pattern.next(position)); + } + + /** + * Set blocks that are in a set of positions and return the number of times + * that the block set calls returned true. + * + * @param vset a set of positions + * @param pattern the pattern + * @return the number of changed blocks + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + private int setBlocks(final Set vset, final Pattern pattern) throws MaxChangedBlocksException { + int affected = 0; + for (final Vector v : vset) { + affected += setBlock(v, pattern) ? 1 : 0; + } + return affected; + } + + /** + * Set a block (only if a previous block was not there) if {@link Math#random()} + * returns a number less than the given probability. + * + * @param position the position + * @param block the block + * @param probability a probability between 0 and 1, inclusive + * @return whether a block was changed + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public boolean setChanceBlockIfAir(final Vector position, final BaseBlock block, final double probability) throws MaxChangedBlocksException { + return (FaweCache.RANDOM.random(65536) <= (probability * 65536)) && setBlockIfAir(position, block); + } + + /** + * Set a block only if there's no block already there. + * + * @param position the position + * @param block the block to set + * @return if block was changed + * @throws MaxChangedBlocksException thrown if too many blocks are changed + * @deprecated Use your own method + */ + @Deprecated + public boolean setBlockIfAir(final Vector position, final BaseBlock block) throws MaxChangedBlocksException { + return getBlock(position).isAir() && setBlock(position, block); + } + + @Override + @Nullable + public Entity createEntity(final com.sk89q.worldedit.util.Location location, final BaseEntity entity) { + return bypassNone.createEntity(location, entity); + } + + /** + * Insert a contrived block change into the history. + * + * @param position the position + * @param existing the previous block at that position + * @param block the new block + * @deprecated Get the change set with {@link #getChangeSet()} and add the change with that + */ + @Deprecated + public void rememberChange(final Vector position, final BaseBlock existing, final BaseBlock block) { + changeSet.add(new BlockChange(position.toBlockVector(), existing, block)); + } + + /** + * Restores all blocks to their initial state. + * + * @param editSession a new {@link EditSession} to perform the undo in + */ + public void undo(final EditSession editSession) { + final UndoContext context = new UndoContext(); + context.setExtent(editSession.bypassHistory); + Operations.completeSmart(ChangeSetExecutor.createUndo(changeSet, context), new Runnable() { + @Override + public void run() { + editSession.flushQueue(); + } + }, true); + editSession.changes = 0; + changes = 0; + } + + /** + * Sets to new state. + * + * @param editSession a new {@link EditSession} to perform the redo in + */ + public void redo(final EditSession editSession) { + final UndoContext context = new UndoContext(); + context.setExtent(editSession.bypassHistory); + Operations.completeSmart(ChangeSetExecutor.createRedo(changeSet, context), new Runnable() { + @Override + public void run() { + editSession.flushQueue(); + } + }, true); + editSession.changes = 0; + changes = 0; + } + + /** + * Get the number of changed blocks. + * + * @return the number of changes + */ + public int size() { + return getBlockChangeCount(); + } + + @Override + public Vector getMinimumPoint() { + return getWorld().getMinimumPoint(); + } + + @Override + public Vector getMaximumPoint() { + return getWorld().getMaximumPoint(); + } + + @Override + public List getEntities(final Region region) { + return bypassNone.getEntities(region); + } + + @Override + public List getEntities() { + return bypassNone.getEntities(); + } + + /** + * Finish off the queue. + */ + public void flushQueue() { + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + Operations.completeBlindly(commit()); + } + }); + } + + @Override + public @Nullable Operation commit() { + return bypassNone.commit(); + } + + /** + * Count the number of blocks of a given list of types in a region. + * + * @param region the region + * @param searchIDs a list of IDs to search + * @return the number of found blocks + */ + public int countBlock(final Region region, final Set searchIDs) { + final boolean[] ids = new boolean[256]; + for (final int id : searchIDs) { + if ((id < 256) && (id > 0)) { + ids[id] = true; + } + } + return countBlock(region, ids); + } + + public int countBlock(final Region region, final boolean[] ids) { + int i = 0; + for (final Vector pt : region) { + final int id = getBlockType(pt); + if (ids[id]) { + i++; + } + } + return i; + } + + /** + * Count the number of blocks of a list of types in a region. + * + * @param region the region + * @param searchBlocks the list of blocks to search + * @return the number of blocks that matched the pattern + */ + public int countBlocks(final Region region, final Set searchBlocks) { + final boolean[] ids = new boolean[256]; + for (final BaseBlock block : searchBlocks) { + final int id = block.getId(); + if ((id < 256) && (id > 0)) { + ids[id] = true; + } + } + return countBlock(region, ids); + } + + /** + * Fills an area recursively in the X/Z directions. + * + * @param origin the location to start from + * @param block the block to fill with + * @param radius the radius of the spherical area to fill + * @param depth the maximum depth, starting from the origin + * @param recursive whether a breadth-first search should be performed + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int fillXZ(final Vector origin, final BaseBlock block, final double radius, final int depth, final boolean recursive) throws MaxChangedBlocksException { + return fillXZ(origin, new SingleBlockPattern(block), radius, depth, recursive); + } + + /** + * Fills an area recursively in the X/Z directions. + * + * @param origin the origin to start the fill from + * @param pattern the pattern to fill with + * @param radius the radius of the spherical area to fill, with 0 as the smallest radius + * @param depth the maximum depth, starting from the origin, with 1 as the smallest depth + * @param recursive whether a breadth-first search should be performed + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int fillXZ(final Vector origin, final Pattern pattern, final double radius, final int depth, final boolean recursive) throws MaxChangedBlocksException { + checkNotNull(origin); + checkNotNull(pattern); + checkArgument(radius >= 0, "radius >= 0"); + checkArgument(depth >= 1, "depth >= 1"); + + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + final MaskIntersection mask = new MaskIntersection(new RegionMask(new EllipsoidRegion(null, origin, new Vector(radius, radius, radius))), new BoundedHeightMask(Math.max( + (origin.getBlockY() - depth) + 1, 0), Math.min(getWorld().getMaxY(), origin.getBlockY())), Masks.negate(new ExistingBlockMask(EditSession.this))); + + // Want to replace blocks + final BlockReplace replace = new BlockReplace(EditSession.this, Patterns.wrap(pattern)); + + // Pick how we're going to visit blocks + RecursiveVisitor visitor; + if (recursive) { + visitor = new RecursiveVisitor(mask, replace); + } else { + visitor = new DownwardVisitor(mask, replace, origin.getBlockY()); + } + + // Start at the origin + visitor.visit(origin); + + // Execute + Operations.completeSmart(visitor, new Runnable() { + @Override + public void run() { + EditSession.this.flushQueue(); + } + }, true); + } + }); + return changes = -1; + } + + /** + * Remove a cuboid above the given position with a given apothem and a given height. + * + * @param position base position + * @param apothem an apothem of the cuboid (on the XZ plane), where the minimum is 1 + * @param height the height of the cuboid, where the minimum is 1 + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int removeAbove(final Vector position, final int apothem, final int height) throws MaxChangedBlocksException { + checkNotNull(position); + checkArgument(apothem >= 1, "apothem >= 1"); + checkArgument(height >= 1, "height >= 1"); + + final Region region = new CuboidRegion(getWorld(), // Causes clamping of Y range + position.add(-apothem + 1, 0, -apothem + 1), position.add(apothem - 1, height - 1, apothem - 1)); + final Pattern pattern = new SingleBlockPattern(new BaseBlock(BlockID.AIR)); + return setBlocks(region, pattern); + } + + /** + * Remove a cuboid below the given position with a given apothem and a given height. + * + * @param position base position + * @param apothem an apothem of the cuboid (on the XZ plane), where the minimum is 1 + * @param height the height of the cuboid, where the minimum is 1 + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int removeBelow(final Vector position, final int apothem, final int height) throws MaxChangedBlocksException { + checkNotNull(position); + checkArgument(apothem >= 1, "apothem >= 1"); + checkArgument(height >= 1, "height >= 1"); + + final Region region = new CuboidRegion(getWorld(), // Causes clamping of Y range + position.add(-apothem + 1, 0, -apothem + 1), position.add(apothem - 1, -height + 1, apothem - 1)); + final Pattern pattern = new SingleBlockPattern(new BaseBlock(BlockID.AIR)); + return setBlocks(region, pattern); + } + + /** + * Remove blocks of a certain type nearby a given position. + * + * @param position center position of cuboid + * @param blockType the block type to match + * @param apothem an apothem of the cuboid, where the minimum is 1 + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int removeNear(final Vector position, final int blockType, final int apothem) throws MaxChangedBlocksException { + checkNotNull(position); + checkArgument(apothem >= 1, "apothem >= 1"); + + final Mask mask = new FuzzyBlockMask(this, new BaseBlock(blockType, -1)); + final Vector adjustment = new Vector(1, 1, 1).multiply(apothem - 1); + final Region region = new CuboidRegion(getWorld(), // Causes clamping of Y range + position.add(adjustment.multiply(-1)), position.add(adjustment)); + final Pattern pattern = new SingleBlockPattern(new BaseBlock(BlockID.AIR)); + return replaceBlocks(region, mask, pattern); + } + + /** + * Sets all the blocks inside a region to a given block type. + * + * @param region the region + * @param block the block + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int setBlocks(final Region region, final BaseBlock block) throws MaxChangedBlocksException { + return setBlocks(region, new SingleBlockPattern(block)); + } + + /** + * Sets all the blocks inside a region to a given pattern. + * + * @param region the region + * @param pattern the pattern that provides the replacement block + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int setBlocks(final Region region, final Pattern pattern) throws MaxChangedBlocksException { + checkNotNull(region); + checkNotNull(pattern); + + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + final BlockReplace replace = new BlockReplace(EditSession.this, Patterns.wrap(pattern)); + final RegionVisitor visitor = new RegionVisitor(region, replace); + Operations.completeSmart(visitor, new Runnable() { + @Override + public void run() { + EditSession.this.flushQueue(); + } + }, true); + } + }); + return changes = -1; + } + + /** + * Replaces all the blocks matching a given filter, within a given region, to a block + * returned by a given pattern. + * + * @param region the region to replace the blocks within + * @param filter a list of block types to match, or null to use {@link com.sk89q.worldedit.masks.ExistingBlockMask} + * @param replacement the replacement block + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int replaceBlocks(final Region region, final Set filter, final BaseBlock replacement) throws MaxChangedBlocksException { + return replaceBlocks(region, filter, new SingleBlockPattern(replacement)); + } + + /** + * Replaces all the blocks matching a given filter, within a given region, to a block + * returned by a given pattern. + * + * @param region the region to replace the blocks within + * @param filter a list of block types to match, or null to use {@link com.sk89q.worldedit.masks.ExistingBlockMask} + * @param pattern the pattern that provides the new blocks + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int replaceBlocks(final Region region, final Set filter, final Pattern pattern) throws MaxChangedBlocksException { + final Mask mask = filter == null ? new ExistingBlockMask(this) : new FuzzyBlockMask(this, filter); + return replaceBlocks(region, mask, pattern); + } + + /** + * Replaces all the blocks matching a given mask, within a given region, to a block + * returned by a given pattern. + * + * @param region the region to replace the blocks within + * @param mask the mask that blocks must match + * @param pattern the pattern that provides the new blocks + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int replaceBlocks(final Region region, final Mask mask, final Pattern pattern) throws MaxChangedBlocksException { + checkNotNull(region); + checkNotNull(mask); + checkNotNull(pattern); + + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + final BlockReplace replace = new BlockReplace(EditSession.this, Patterns.wrap(pattern)); + final RegionMaskingFilter filter = new RegionMaskingFilter(mask, replace); + final RegionVisitor visitor = new RegionVisitor(region, filter); + Operations.completeSmart(visitor, new Runnable() { + @Override + public void run() { + EditSession.this.flushQueue(); + } + }, true); + } + }); + return changes = -1; + } + + /** + * Sets the blocks at the center of the given region to the given pattern. + * If the center sits between two blocks on a certain axis, then two blocks + * will be placed to mark the center. + * + * @param region the region to find the center of + * @param pattern the replacement pattern + * @return the number of blocks placed + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int center(final Region region, final Pattern pattern) throws MaxChangedBlocksException { + checkNotNull(region); + checkNotNull(pattern); + + final Vector center = region.getCenter(); + final Region centerRegion = new CuboidRegion(getWorld(), // Causes clamping of Y range + new Vector((int) center.getX(), (int) center.getY(), (int) center.getZ()), center.toBlockVector()); + return setBlocks(centerRegion, pattern); + } + + /** + * Make the faces of the given region as if it was a {@link CuboidRegion}. + * + * @param region the region + * @param block the block to place + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int makeCuboidFaces(final Region region, final BaseBlock block) throws MaxChangedBlocksException { + return makeCuboidFaces(region, new SingleBlockPattern(block)); + } + + /** + * Make the faces of the given region as if it was a {@link CuboidRegion}. + * + * @param region the region + * @param pattern the pattern to place + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int makeCuboidFaces(final Region region, final Pattern pattern) throws MaxChangedBlocksException { + checkNotNull(region); + checkNotNull(pattern); + + final CuboidRegion cuboid = CuboidRegion.makeCuboid(region); + final Region faces = cuboid.getFaces(); + return setBlocks(faces, pattern); + } + + /** + * Make the faces of the given region. The method by which the faces are found + * may be inefficient, because there may not be an efficient implementation supported + * for that specific shape. + * + * @param region the region + * @param pattern the pattern to place + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int makeFaces(final Region region, final Pattern pattern) throws MaxChangedBlocksException { + checkNotNull(region); + checkNotNull(pattern); + + if (region instanceof CuboidRegion) { + return makeCuboidFaces(region, pattern); + } else { + return new RegionShape(region).generate(this, pattern, true); + } + } + + /** + * Make the walls (all faces but those parallel to the X-Z plane) of the given region + * as if it was a {@link CuboidRegion}. + * + * @param region the region + * @param block the block to place + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int makeCuboidWalls(final Region region, final BaseBlock block) throws MaxChangedBlocksException { + return makeCuboidWalls(region, new SingleBlockPattern(block)); + } + + /** + * Make the walls (all faces but those parallel to the X-Z plane) of the given region + * as if it was a {@link CuboidRegion}. + * + * @param region the region + * @param pattern the pattern to place + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int makeCuboidWalls(final Region region, final Pattern pattern) throws MaxChangedBlocksException { + checkNotNull(region); + checkNotNull(pattern); + + final CuboidRegion cuboid = CuboidRegion.makeCuboid(region); + final Region faces = cuboid.getWalls(); + return setBlocks(faces, pattern); + } + + /** + * Make the walls of the given region. The method by which the walls are found + * may be inefficient, because there may not be an efficient implementation supported + * for that specific shape. + * + * @param region the region + * @param pattern the pattern to place + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int makeWalls(final Region region, final Pattern pattern) throws MaxChangedBlocksException { + checkNotNull(region); + checkNotNull(pattern); + + if (region instanceof CuboidRegion) { + return makeCuboidWalls(region, pattern); + } else { + final int minY = region.getMinimumPoint().getBlockY(); + final int maxY = region.getMaximumPoint().getBlockY(); + final ArbitraryShape shape = new RegionShape(region) { + @Override + protected BaseBlock getMaterial(final int x, final int y, final int z, final BaseBlock defaultMaterial) { + if ((y > maxY) || (y < minY)) { + // Put holes into the floor and ceiling by telling ArbitraryShape that the shape goes on outside the region + return defaultMaterial; + } + + return super.getMaterial(x, y, z, defaultMaterial); + } + }; + return shape.generate(this, pattern, true); + } + } + + /** + * Places a layer of blocks on top of ground blocks in the given region + * (as if it were a cuboid). + * + * @param region the region + * @param block the placed block + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int overlayCuboidBlocks(final Region region, final BaseBlock block) throws MaxChangedBlocksException { + checkNotNull(block); + + return overlayCuboidBlocks(region, new SingleBlockPattern(block)); + } + + /** + * Places a layer of blocks on top of ground blocks in the given region + * (as if it were a cuboid). + * + * @param region the region + * @param pattern the placed block pattern + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + @SuppressWarnings("deprecation") + public int overlayCuboidBlocks(final Region region, final Pattern pattern) throws MaxChangedBlocksException { + checkNotNull(region); + checkNotNull(pattern); + + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + final BlockReplace replace = new BlockReplace(EditSession.this, Patterns.wrap(pattern)); + final RegionOffset offset = new RegionOffset(new Vector(0, 1, 0), replace); + final GroundFunction ground = new GroundFunction(new ExistingBlockMask(EditSession.this), offset); + final LayerVisitor visitor = new LayerVisitor(asFlatRegion(region), minimumBlockY(region), maximumBlockY(region), ground); + Operations.completeSmart(visitor, new Runnable() { + @Override + public void run() { + EditSession.this.flushQueue(); + } + }, true); + } + }); + return changes = -1; + } + + /** + * Turns the first 3 layers into dirt/grass and the bottom layers + * into rock, like a natural Minecraft mountain. + * + * @param region the region to affect + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int naturalizeCuboidBlocks(final Region region) throws MaxChangedBlocksException { + checkNotNull(region); + + TaskManager.IMP.async(new Runnable() { + + @Override + public void run() { + final Naturalizer naturalizer = new Naturalizer(EditSession.this); + final FlatRegion flatRegion = Regions.asFlatRegion(region); + final LayerVisitor visitor = new LayerVisitor(flatRegion, minimumBlockY(region), maximumBlockY(region), naturalizer); + Operations.completeSmart(visitor, new Runnable() { + @Override + public void run() { + EditSession.this.flushQueue(); + } + }, true); + } + }); + return changes = -1; + } + + /** + * Stack a cuboid region. + * + * @param region the region to stack + * @param dir the direction to stack + * @param count the number of times to stack + * @param copyAir true to also copy air blocks + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int stackCuboidRegion(final Region region, final Vector dir, final int count, final boolean copyAir) throws MaxChangedBlocksException { + checkNotNull(region); + checkNotNull(dir); + checkArgument(count >= 1, "count >= 1 required"); + + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + final Vector size = region.getMaximumPoint().subtract(region.getMinimumPoint()).add(1, 1, 1); + final Vector to = region.getMinimumPoint(); + final ForwardExtentCopy copy = new ForwardExtentCopy(EditSession.this, region, EditSession.this, to); + copy.setRepetitions(count); + copy.setTransform(new AffineTransform().translate(dir.multiply(size))); + if (!copyAir) { + copy.setSourceMask(new ExistingBlockMask(EditSession.this)); + } + Operations.completeSmart(copy, new Runnable() { + @Override + public void run() { + EditSession.this.flushQueue(); + } + }, true); + } + }); + return changes = -1; + } + + /** + * Move the blocks in a region a certain direction. + * + * @param region the region to move + * @param dir the direction + * @param distance the distance to move + * @param copyAir true to copy air blocks + * @param replacement the replacement block to fill in after moving, or null to use air + * @return number of blocks moved + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int moveRegion(final Region region, final Vector dir, final int distance, final boolean copyAir, final BaseBlock replacement) throws MaxChangedBlocksException { + checkNotNull(region); + checkNotNull(dir); + checkArgument(distance >= 1, "distance >= 1 required"); + + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + final Vector to = region.getMinimumPoint(); + + // Remove the original blocks + final com.sk89q.worldedit.function.pattern.Pattern pattern = replacement != null ? new BlockPattern(replacement) : new BlockPattern(new BaseBlock(BlockID.AIR)); + final BlockReplace remove = new BlockReplace(EditSession.this, pattern); + + // Copy to a buffer so we don't destroy our original before we can copy all the blocks from it + final ForgetfulExtentBuffer buffer = new ForgetfulExtentBuffer(EditSession.this, new RegionMask(region)); + final ForwardExtentCopy copy = new ForwardExtentCopy(EditSession.this, region, buffer, to); + copy.setTransform(new AffineTransform().translate(dir.multiply(distance))); + copy.setSourceFunction(remove); // Remove + copy.setRemovingEntities(true); + if (!copyAir) { + copy.setSourceMask(new ExistingBlockMask(EditSession.this)); + } + + // Then we need to copy the buffer to the world + final BlockReplace replace = new BlockReplace(EditSession.this, buffer); + final RegionVisitor visitor = new RegionVisitor(buffer.asRegion(), replace); + + final OperationQueue operation = new OperationQueue(copy, visitor); + Operations.completeSmart(operation, new Runnable() { + @Override + public void run() { + EditSession.this.flushQueue(); + } + }, true); + } + }); + return changes = -1; + } + + /** + * Move the blocks in a region a certain direction. + * + * @param region the region to move + * @param dir the direction + * @param distance the distance to move + * @param copyAir true to copy air blocks + * @param replacement the replacement block to fill in after moving, or null to use air + * @return number of blocks moved + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int moveCuboidRegion(final Region region, final Vector dir, final int distance, final boolean copyAir, final BaseBlock replacement) throws MaxChangedBlocksException { + return moveRegion(region, dir, distance, copyAir, replacement); + } + + /** + * Drain nearby pools of water or lava. + * + * @param origin the origin to drain from, which will search a 3x3 area + * @param radius the radius of the removal, where a value should be 0 or greater + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int drainArea(final Vector origin, final double radius) throws MaxChangedBlocksException { + checkNotNull(origin); + checkArgument(radius >= 0, "radius >= 0 required"); + + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + final MaskIntersection mask = new MaskIntersection(new BoundedHeightMask(0, getWorld().getMaxY()), + new RegionMask(new EllipsoidRegion(null, origin, new Vector(radius, radius, radius))), getWorld().createLiquidMask()); + + final BlockReplace replace = new BlockReplace(EditSession.this, new BlockPattern(new BaseBlock(BlockID.AIR))); + final RecursiveVisitor visitor = new RecursiveVisitor(mask, replace); + + // Around the origin in a 3x3 block + for (final BlockVector position : CuboidRegion.fromCenter(origin, 1)) { + if (mask.test(position)) { + visitor.visit(position); + } + } + + Operations.completeSmart(visitor, new Runnable() { + @Override + public void run() { + EditSession.this.flushQueue(); + } + }, true); + } + }); + return changes = -1; + } + + /** + * Fix liquids so that they turn into stationary blocks and extend outward. + * + * @param origin the original position + * @param radius the radius to fix + * @param moving the block ID of the moving liquid + * @param stationary the block ID of the stationary liquid + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int fixLiquid(final Vector origin, final double radius, final int moving, final int stationary) throws MaxChangedBlocksException { + checkNotNull(origin); + checkArgument(radius >= 0, "radius >= 0 required"); + + TaskManager.IMP.async(new Runnable() { + + @Override + public void run() { + // Our origins can only be liquids + final BlockMask liquidMask = new BlockMask(EditSession.this, new BaseBlock(moving, -1), new BaseBlock(stationary, -1)); + + // But we will also visit air blocks + final MaskIntersection blockMask = new MaskUnion(liquidMask, new BlockMask(EditSession.this, new BaseBlock(BlockID.AIR))); + + // There are boundaries that the routine needs to stay in + final MaskIntersection mask = new MaskIntersection(new BoundedHeightMask(0, Math.min(origin.getBlockY(), getWorld().getMaxY())), new RegionMask(new EllipsoidRegion(null, origin, + new Vector(radius, radius, radius))), blockMask); + + final BlockReplace replace = new BlockReplace(EditSession.this, new BlockPattern(new BaseBlock(stationary))); + final NonRisingVisitor visitor = new NonRisingVisitor(mask, replace); + + // Around the origin in a 3x3 block + for (final BlockVector position : CuboidRegion.fromCenter(origin, 1)) { + if (liquidMask.test(position)) { + visitor.visit(position); + } + } + + Operations.completeSmart(visitor, new Runnable() { + @Override + public void run() { + EditSession.this.flushQueue(); + } + }, true); + } + }); + return changes = -1; + } + + /** + * Makes a cylinder. + * + * @param pos Center of the cylinder + * @param block The block pattern to use + * @param radius The cylinder's radius + * @param height The cylinder's up/down extent. If negative, extend downward. + * @param filled If false, only a shell will be generated. + * @return number of blocks changed + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int makeCylinder(final Vector pos, final Pattern block, final double radius, final int height, final boolean filled) throws MaxChangedBlocksException { + return makeCylinder(pos, block, radius, radius, height, filled); + } + + /** + * Makes a cylinder. + * + * @param pos Center of the cylinder + * @param block The block pattern to use + * @param radiusX The cylinder's largest north/south extent + * @param radiusZ The cylinder's largest east/west extent + * @param height The cylinder's up/down extent. If negative, extend downward. + * @param filled If false, only a shell will be generated. + * @return number of blocks changed + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int makeCylinder(Vector pos, final Pattern block, double radiusX, double radiusZ, int height, final boolean filled) throws MaxChangedBlocksException { + int affected = 0; + + radiusX += 0.5; + radiusZ += 0.5; + + if (height == 0) { + return changes = -1; + } else if (height < 0) { + height = -height; + pos = pos.subtract(0, height, 0); + } + + if (pos.getBlockY() < 0) { + pos = pos.setY(0); + } else if (((pos.getBlockY() + height) - 1) > world.getMaxY()) { + height = (world.getMaxY() - pos.getBlockY()) + 1; + } + + final double invRadiusX = 1 / radiusX; + final double invRadiusZ = 1 / radiusZ; + + final int ceilRadiusX = (int) Math.ceil(radiusX); + final int ceilRadiusZ = (int) Math.ceil(radiusZ); + + double nextXn = 0; + forX: for (int x = 0; x <= ceilRadiusX; ++x) { + final double xn = nextXn; + nextXn = (x + 1) * invRadiusX; + double nextZn = 0; + forZ: for (int z = 0; z <= ceilRadiusZ; ++z) { + final double zn = nextZn; + nextZn = (z + 1) * invRadiusZ; + + final double distanceSq = lengthSq(xn, zn); + if (distanceSq > 1) { + if (z == 0) { + break forX; + } + break forZ; + } + + if (!filled) { + if ((lengthSq(nextXn, zn) <= 1) && (lengthSq(xn, nextZn) <= 1)) { + continue; + } + } + + for (int y = 0; y < height; ++y) { + if (setBlock(pos.add(x, y, z), block)) { + ++affected; + } + if (setBlock(pos.add(-x, y, z), block)) { + ++affected; + } + if (setBlock(pos.add(x, y, -z), block)) { + ++affected; + } + if (setBlock(pos.add(-x, y, -z), block)) { + ++affected; + } + } + } + } + + return affected; + } + + /** + * Makes a sphere. + * + * @param pos Center of the sphere or ellipsoid + * @param block The block pattern to use + * @param radius The sphere's radius + * @param filled If false, only a shell will be generated. + * @return number of blocks changed + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int makeSphere(final Vector pos, final Pattern block, final double radius, final boolean filled) throws MaxChangedBlocksException { + return makeSphere(pos, block, radius, radius, radius, filled); + } + + /** + * Makes a sphere or ellipsoid. + * + * @param pos Center of the sphere or ellipsoid + * @param block The block pattern to use + * @param radiusX The sphere/ellipsoid's largest north/south extent + * @param radiusY The sphere/ellipsoid's largest up/down extent + * @param radiusZ The sphere/ellipsoid's largest east/west extent + * @param filled If false, only a shell will be generated. + * @return number of blocks changed + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int makeSphere(final Vector pos, final Pattern block, double radiusX, double radiusY, double radiusZ, final boolean filled) throws MaxChangedBlocksException { + int affected = 0; + + radiusX += 0.5; + radiusY += 0.5; + radiusZ += 0.5; + + final double invRadiusX = 1 / radiusX; + final double invRadiusY = 1 / radiusY; + final double invRadiusZ = 1 / radiusZ; + + final int ceilRadiusX = (int) Math.ceil(radiusX); + final int ceilRadiusY = (int) Math.ceil(radiusY); + final int ceilRadiusZ = (int) Math.ceil(radiusZ); + + double nextXn = 0; + forX: for (int x = 0; x <= ceilRadiusX; ++x) { + final double xn = nextXn; + nextXn = (x + 1) * invRadiusX; + double nextYn = 0; + forY: for (int y = 0; y <= ceilRadiusY; ++y) { + final double yn = nextYn; + nextYn = (y + 1) * invRadiusY; + double nextZn = 0; + forZ: for (int z = 0; z <= ceilRadiusZ; ++z) { + final double zn = nextZn; + nextZn = (z + 1) * invRadiusZ; + + final double distanceSq = lengthSq(xn, yn, zn); + if (distanceSq > 1) { + if (z == 0) { + if (y == 0) { + break forX; + } + break forY; + } + break forZ; + } + + if (!filled) { + if ((lengthSq(nextXn, yn, zn) <= 1) && (lengthSq(xn, nextYn, zn) <= 1) && (lengthSq(xn, yn, nextZn) <= 1)) { + continue; + } + } + + if (setBlock(pos.add(x, y, z), block)) { + ++affected; + } + if (setBlock(pos.add(-x, y, z), block)) { + ++affected; + } + if (setBlock(pos.add(x, -y, z), block)) { + ++affected; + } + if (setBlock(pos.add(x, y, -z), block)) { + ++affected; + } + if (setBlock(pos.add(-x, -y, z), block)) { + ++affected; + } + if (setBlock(pos.add(x, -y, -z), block)) { + ++affected; + } + if (setBlock(pos.add(-x, y, -z), block)) { + ++affected; + } + if (setBlock(pos.add(-x, -y, -z), block)) { + ++affected; + } + } + } + } + + return affected; + } + + /** + * Makes a pyramid. + * + * @param position a position + * @param block a block + * @param size size of pyramid + * @param filled true if filled + * @return number of blocks changed + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int makePyramid(final Vector position, final Pattern block, int size, final boolean filled) throws MaxChangedBlocksException { + int affected = 0; + + final int height = size; + + for (int y = 0; y <= height; ++y) { + size--; + for (int x = 0; x <= size; ++x) { + for (int z = 0; z <= size; ++z) { + + if ((filled && (z <= size) && (x <= size)) || (z == size) || (x == size)) { + + if (setBlock(position.add(x, y, z), block)) { + ++affected; + } + if (setBlock(position.add(-x, y, z), block)) { + ++affected; + } + if (setBlock(position.add(x, y, -z), block)) { + ++affected; + } + if (setBlock(position.add(-x, y, -z), block)) { + ++affected; + } + } + } + } + } + + return affected; + } + + /** + * Thaw blocks in a radius. + * + * @param position the position + * @param radius the radius + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int thaw(final Vector position, final double radius) throws MaxChangedBlocksException { + int affected = 0; + final double radiusSq = radius * radius; + + final int ox = position.getBlockX(); + final int oy = position.getBlockY(); + final int oz = position.getBlockZ(); + + final BaseBlock air = new BaseBlock(0); + final BaseBlock water = new BaseBlock(BlockID.STATIONARY_WATER); + + final int ceilRadius = (int) Math.ceil(radius); + for (int x = ox - ceilRadius; x <= (ox + ceilRadius); ++x) { + for (int z = oz - ceilRadius; z <= (oz + ceilRadius); ++z) { + if ((new Vector(x, oy, z)).distanceSq(position) > radiusSq) { + continue; + } + + for (int y = world.getMaxY(); y >= 1; --y) { + final Vector pt = new Vector(x, y, z); + final int id = getBlockType(pt); + + switch (id) { + case BlockID.ICE: + if (setBlock(pt, water)) { + ++affected; + } + break; + + case BlockID.SNOW: + if (setBlock(pt, air)) { + ++affected; + } + break; + + case BlockID.AIR: + continue; + + default: + break; + } + + break; + } + } + } + + return affected; + } + + /** + * Make snow in a radius. + * + * @param position a position + * @param radius a radius + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int simulateSnow(final Vector position, final double radius) throws MaxChangedBlocksException { + int affected = 0; + final double radiusSq = radius * radius; + + final int ox = position.getBlockX(); + final int oy = position.getBlockY(); + final int oz = position.getBlockZ(); + + final BaseBlock ice = new BaseBlock(BlockID.ICE); + final BaseBlock snow = new BaseBlock(BlockID.SNOW); + + final int ceilRadius = (int) Math.ceil(radius); + for (int x = ox - ceilRadius; x <= (ox + ceilRadius); ++x) { + for (int z = oz - ceilRadius; z <= (oz + ceilRadius); ++z) { + if ((new Vector(x, oy, z)).distanceSq(position) > radiusSq) { + continue; + } + + for (int y = world.getMaxY(); y >= 1; --y) { + final Vector pt = new Vector(x, y, z); + final int id = getBlockType(pt); + + if (id == BlockID.AIR) { + continue; + } + + // Ice! + if ((id == BlockID.WATER) || (id == BlockID.STATIONARY_WATER)) { + if (setBlock(pt, ice)) { + ++affected; + } + break; + } + + // Snow should not cover these blocks + if (BlockType.isTranslucent(id)) { + break; + } + + // Too high? + if (y == world.getMaxY()) { + break; + } + + // add snow cover + if (setBlock(pt.add(0, 1, 0), snow)) { + ++affected; + } + break; + } + } + } + + return affected; + } + + /** + * Make dirt green. + * + * @param position a position + * @param radius a radius + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + * @deprecated Use {@link #green(Vector, double, boolean)}. + */ + @Deprecated + public int green(final Vector position, final double radius) throws MaxChangedBlocksException { + return green(position, radius, true); + } + + /** + * Make dirt green. + * + * @param position a position + * @param radius a radius + * @param onlyNormalDirt only affect normal dirt (data value 0) + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int green(final Vector position, final double radius, final boolean onlyNormalDirt) throws MaxChangedBlocksException { + int affected = 0; + final double radiusSq = radius * radius; + + final int ox = position.getBlockX(); + final int oy = position.getBlockY(); + final int oz = position.getBlockZ(); + + final BaseBlock grass = new BaseBlock(BlockID.GRASS); + + final int ceilRadius = (int) Math.ceil(radius); + for (int x = ox - ceilRadius; x <= (ox + ceilRadius); ++x) { + for (int z = oz - ceilRadius; z <= (oz + ceilRadius); ++z) { + if ((new Vector(x, oy, z)).distanceSq(position) > radiusSq) { + continue; + } + + loop: for (int y = world.getMaxY(); y >= 1; --y) { + final Vector pt = new Vector(x, y, z); + final int id = getBlockType(pt); + final int data = getBlockData(pt); + + switch (id) { + case BlockID.DIRT: + if (onlyNormalDirt && (data != 0)) { + break loop; + } + + if (setBlock(pt, grass)) { + ++affected; + } + break loop; + + case BlockID.WATER: + case BlockID.STATIONARY_WATER: + case BlockID.LAVA: + case BlockID.STATIONARY_LAVA: + // break on liquids... + break loop; + + default: + // ...and all non-passable blocks + if (!BlockType.canPassThrough(id, data)) { + break loop; + } + } + } + } + } + + return affected; + } + + /** + * Makes pumpkin patches randomly in an area around the given position. + * + * @param position the base position + * @param apothem the apothem of the (square) area + * @return number of patches created + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int makePumpkinPatches(final Vector position, final int apothem) throws MaxChangedBlocksException { + + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + // We want to generate pumpkins + final GardenPatchGenerator generator = new GardenPatchGenerator(EditSession.this); + generator.setPlant(GardenPatchGenerator.getPumpkinPattern()); + + // In a region of the given radius + final FlatRegion region = new CuboidRegion(getWorld(), // Causes clamping of Y range + position.add(-apothem, -5, -apothem), position.add(apothem, 10, apothem)); + final double density = 0.02; + + final GroundFunction ground = new GroundFunction(new ExistingBlockMask(EditSession.this), generator); + final LayerVisitor visitor = new LayerVisitor(region, minimumBlockY(region), maximumBlockY(region), ground); + visitor.setMask(new NoiseFilter2D(new RandomNoise(), density)); + Operations.completeSmart(visitor, new Runnable() { + @Override + public void run() { + EditSession.this.flushQueue(); + } + }, true); + } + }); + return changes = -1; + } + + /** + * Makes a forest. + * + * @param basePosition a position + * @param size a size + * @param density between 0 and 1, inclusive + * @param treeGenerator the tree genreator + * @return number of trees created + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int makeForest(final Vector basePosition, final int size, final double density, final TreeGenerator treeGenerator) throws MaxChangedBlocksException { + for (int x = basePosition.getBlockX() - size; x <= (basePosition.getBlockX() + size); ++x) { + for (int z = basePosition.getBlockZ() - size; z <= (basePosition.getBlockZ() + size); ++z) { + // Don't want to be in the ground + if (!getBlock(new Vector(x, basePosition.getBlockY(), z)).isAir()) { + continue; + } + // The gods don't want a tree here + if (FaweCache.RANDOM.random(65536) >= (density * 65536)) { + continue; + } // def 0.05 + + for (int y = basePosition.getBlockY(); y >= (basePosition.getBlockY() - 10); --y) { + // Check if we hit the ground + final int t = getBlock(new Vector(x, y, z)).getType(); + if ((t == BlockID.GRASS) || (t == BlockID.DIRT)) { + treeGenerator.generate(this, new Vector(x, y + 1, z)); + break; + } else if (t == BlockID.SNOW) { + setBlock(new Vector(x, y, z), new BaseBlock(BlockID.AIR)); + } else if (t != BlockID.AIR) { // Trees won't grow on this! + break; + } + } + } + } + return changes = -1; + } + + /** + * Get the block distribution inside a region. + * + * @param region a region + * @return the results + */ + public List> getBlockDistribution(final Region region) { + final List> distribution = new ArrayList>(); + final Map> map = new HashMap>(); + + if (region instanceof CuboidRegion) { + // Doing this for speed + final Vector min = region.getMinimumPoint(); + final Vector max = region.getMaximumPoint(); + + final int minX = min.getBlockX(); + final int minY = min.getBlockY(); + final int minZ = min.getBlockZ(); + final int maxX = max.getBlockX(); + final int maxY = max.getBlockY(); + final int maxZ = max.getBlockZ(); + + for (int x = minX; x <= maxX; ++x) { + for (int y = minY; y <= maxY; ++y) { + for (int z = minZ; z <= maxZ; ++z) { + final Vector pt = new Vector(x, y, z); + + final int id = getBlockType(pt); + + if (map.containsKey(id)) { + map.get(id).increment(); + } else { + final Countable c = new Countable(id, 1); + map.put(id, c); + distribution.add(c); + } + } + } + } + } else { + for (final Vector pt : region) { + final int id = getBlockType(pt); + + if (map.containsKey(id)) { + map.get(id).increment(); + } else { + final Countable c = new Countable(id, 1); + map.put(id, c); + } + } + } + + Collections.sort(distribution); + // Collections.reverse(distribution); + + return distribution; + } + + /** + * Get the block distribution (with data values) inside a region. + * + * @param region a region + * @return the results + */ + public List> getBlockDistributionWithData(final Region region) { + final List> distribution = new ArrayList>(); + final Map> map = new HashMap>(); + + if (region instanceof CuboidRegion) { + // Doing this for speed + final Vector min = region.getMinimumPoint(); + final Vector max = region.getMaximumPoint(); + + final int minX = min.getBlockX(); + final int minY = min.getBlockY(); + final int minZ = min.getBlockZ(); + final int maxX = max.getBlockX(); + final int maxY = max.getBlockY(); + final int maxZ = max.getBlockZ(); + + for (int x = minX; x <= maxX; ++x) { + for (int y = minY; y <= maxY; ++y) { + for (int z = minZ; z <= maxZ; ++z) { + final Vector pt = new Vector(x, y, z); + + final BaseBlock blk = new BaseBlock(getBlockType(pt), getBlockData(pt)); + + if (map.containsKey(blk)) { + map.get(blk).increment(); + } else { + final Countable c = new Countable(blk, 1); + map.put(blk, c); + distribution.add(c); + } + } + } + } + } else { + for (final Vector pt : region) { + final BaseBlock blk = new BaseBlock(getBlockType(pt), getBlockData(pt)); + + if (map.containsKey(blk)) { + map.get(blk).increment(); + } else { + final Countable c = new Countable(blk, 1); + map.put(blk, c); + } + } + } + + Collections.sort(distribution); + // Collections.reverse(distribution); + + return distribution; + } + + public int makeShape(final Region region, final Vector zero, final Vector unit, final Pattern pattern, final String expressionString, final boolean hollow) throws ExpressionException, + MaxChangedBlocksException { + final Expression expression = Expression.compile(expressionString, "x", "y", "z", "type", "data"); + expression.optimize(); + + final RValue typeVariable = expression.getVariable("type", false); + final RValue dataVariable = expression.getVariable("data", false); + + final WorldEditExpressionEnvironment environment = new WorldEditExpressionEnvironment(this, unit, zero); + expression.setEnvironment(environment); + + final ArbitraryShape shape = new ArbitraryShape(region) { + @Override + protected BaseBlock getMaterial(final int x, final int y, final int z, final BaseBlock defaultMaterial) { + final Vector current = new Vector(x, y, z); + environment.setCurrentBlock(current); + final Vector scaled = current.subtract(zero).divide(unit); + + try { + if (expression.evaluate(scaled.getX(), scaled.getY(), scaled.getZ(), defaultMaterial.getType(), defaultMaterial.getData()) <= 0) { + return null; + } + + return new BaseBlock((int) typeVariable.getValue(), (int) dataVariable.getValue()); + } catch (final Exception e) { + log.log(Level.WARNING, "Failed to create shape", e); + return null; + } + } + }; + + return shape.generate(this, pattern, hollow); + } + + public int deformRegion(final Region region, final Vector zero, final Vector unit, final String expressionString) throws ExpressionException, MaxChangedBlocksException { + final Expression expression = Expression.compile(expressionString, "x", "y", "z"); + expression.optimize(); + + final RValue x = expression.getVariable("x", false); + final RValue y = expression.getVariable("y", false); + final RValue z = expression.getVariable("z", false); + + final WorldEditExpressionEnvironment environment = new WorldEditExpressionEnvironment(this, unit, zero); + expression.setEnvironment(environment); + + final DoubleArrayList queue = new DoubleArrayList(false); + + for (final BlockVector position : region) { + // offset, scale + final Vector scaled = position.subtract(zero).divide(unit); + + // transform + expression.evaluate(scaled.getX(), scaled.getY(), scaled.getZ()); + + final BlockVector sourcePosition = environment.toWorld(x.getValue(), y.getValue(), z.getValue()); + + // read block from world + final BaseBlock material = new BaseBlock(world.getBlockType(sourcePosition), world.getBlockData(sourcePosition)); + + // queue operation + queue.put(position, material); + } + + int affected = 0; + for (final Map.Entry entry : queue) { + final BlockVector position = entry.getKey(); + final BaseBlock material = entry.getValue(); + + // set at new position + if (setBlock(position, material)) { + ++affected; + } + } + + return affected; + } + + /** + * Hollows out the region (Semi-well-defined for non-cuboid selections). + * + * @param region the region to hollow out. + * @param thickness the thickness of the shell to leave (manhattan distance) + * @param pattern The block pattern to use + * + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int hollowOutRegion(final Region region, final int thickness, final Pattern pattern) throws MaxChangedBlocksException { + int affected = 0; + + final Set outside = new HashSet(); + + final Vector min = region.getMinimumPoint(); + final Vector max = region.getMaximumPoint(); + + final int minX = min.getBlockX(); + final int minY = min.getBlockY(); + final int minZ = min.getBlockZ(); + final int maxX = max.getBlockX(); + final int maxY = max.getBlockY(); + final int maxZ = max.getBlockZ(); + + for (int x = minX; x <= maxX; ++x) { + for (int y = minY; y <= maxY; ++y) { + recurseHollow(region, new BlockVector(x, y, minZ), outside); + recurseHollow(region, new BlockVector(x, y, maxZ), outside); + } + } + + for (int y = minY; y <= maxY; ++y) { + for (int z = minZ; z <= maxZ; ++z) { + recurseHollow(region, new BlockVector(minX, y, z), outside); + recurseHollow(region, new BlockVector(maxX, y, z), outside); + } + } + + for (int z = minZ; z <= maxZ; ++z) { + for (int x = minX; x <= maxX; ++x) { + recurseHollow(region, new BlockVector(x, minY, z), outside); + recurseHollow(region, new BlockVector(x, maxY, z), outside); + } + } + + for (int i = 1; i < thickness; ++i) { + final Set newOutside = new HashSet(); + outer: for (final BlockVector position : region) { + for (final Vector recurseDirection : recurseDirections) { + final BlockVector neighbor = position.add(recurseDirection).toBlockVector(); + + if (outside.contains(neighbor)) { + newOutside.add(position); + continue outer; + } + } + } + + outside.addAll(newOutside); + } + + outer: for (final BlockVector position : region) { + for (final Vector recurseDirection : recurseDirections) { + final BlockVector neighbor = position.add(recurseDirection).toBlockVector(); + + if (outside.contains(neighbor)) { + continue outer; + } + } + + if (setBlock(position, pattern.next(position))) { + ++affected; + } + } + + return affected; + } + + /** + * Draws a line (out of blocks) between two vectors. + * + * @param pattern The block pattern used to draw the line. + * @param pos1 One of the points that define the line. + * @param pos2 The other point that defines the line. + * @param radius The radius (thickness) of the line. + * @param filled If false, only a shell will be generated. + * + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int drawLine(final Pattern pattern, final Vector pos1, final Vector pos2, final double radius, final boolean filled) throws MaxChangedBlocksException { + + Set vset = new HashSet(); + boolean notdrawn = true; + + final int x1 = pos1.getBlockX(), y1 = pos1.getBlockY(), z1 = pos1.getBlockZ(); + final int x2 = pos2.getBlockX(), y2 = pos2.getBlockY(), z2 = pos2.getBlockZ(); + int tipx = x1, tipy = y1, tipz = z1; + final int dx = Math.abs(x2 - x1), dy = Math.abs(y2 - y1), dz = Math.abs(z2 - z1); + + if ((dx + dy + dz) == 0) { + vset.add(new Vector(tipx, tipy, tipz)); + notdrawn = false; + } + + if ((Math.max(Math.max(dx, dy), dz) == dx) && notdrawn) { + for (int domstep = 0; domstep <= dx; domstep++) { + tipx = x1 + (domstep * ((x2 - x1) > 0 ? 1 : -1)); + tipy = (int) Math.round(y1 + (((domstep * ((double) dy)) / (dx)) * ((y2 - y1) > 0 ? 1 : -1))); + tipz = (int) Math.round(z1 + (((domstep * ((double) dz)) / (dx)) * ((z2 - z1) > 0 ? 1 : -1))); + + vset.add(new Vector(tipx, tipy, tipz)); + } + notdrawn = false; + } + + if ((Math.max(Math.max(dx, dy), dz) == dy) && notdrawn) { + for (int domstep = 0; domstep <= dy; domstep++) { + tipy = y1 + (domstep * ((y2 - y1) > 0 ? 1 : -1)); + tipx = (int) Math.round(x1 + (((domstep * ((double) dx)) / (dy)) * ((x2 - x1) > 0 ? 1 : -1))); + tipz = (int) Math.round(z1 + (((domstep * ((double) dz)) / (dy)) * ((z2 - z1) > 0 ? 1 : -1))); + + vset.add(new Vector(tipx, tipy, tipz)); + } + notdrawn = false; + } + + if ((Math.max(Math.max(dx, dy), dz) == dz) && notdrawn) { + for (int domstep = 0; domstep <= dz; domstep++) { + tipz = z1 + (domstep * ((z2 - z1) > 0 ? 1 : -1)); + tipy = (int) Math.round(y1 + (((domstep * ((double) dy)) / (dz)) * ((y2 - y1) > 0 ? 1 : -1))); + tipx = (int) Math.round(x1 + (((domstep * ((double) dx)) / (dz)) * ((x2 - x1) > 0 ? 1 : -1))); + + vset.add(new Vector(tipx, tipy, tipz)); + } + notdrawn = false; + } + + vset = getBallooned(vset, radius); + if (!filled) { + vset = getHollowed(vset); + } + return setBlocks(vset, pattern); + } + + /** + * Draws a spline (out of blocks) between specified vectors. + * + * @param pattern The block pattern used to draw the spline. + * @param nodevectors The list of vectors to draw through. + * @param tension The tension of every node. + * @param bias The bias of every node. + * @param continuity The continuity of every node. + * @param quality The quality of the spline. Must be greater than 0. + * @param radius The radius (thickness) of the spline. + * @param filled If false, only a shell will be generated. + * + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int drawSpline(final Pattern pattern, final List nodevectors, final double tension, final double bias, final double continuity, final double quality, final double radius, + final boolean filled) throws MaxChangedBlocksException { + + Set vset = new HashSet(); + final List nodes = new ArrayList(nodevectors.size()); + + final Interpolation interpol = new KochanekBartelsInterpolation(); + + for (final Vector nodevector : nodevectors) { + final Node n = new Node(nodevector); + n.setTension(tension); + n.setBias(bias); + n.setContinuity(continuity); + nodes.add(n); + } + + interpol.setNodes(nodes); + final double splinelength = interpol.arcLength(0, 1); + for (double loop = 0; loop <= 1; loop += 1D / splinelength / quality) { + final Vector tipv = interpol.getPosition(loop); + final int tipx = (int) Math.round(tipv.getX()); + final int tipy = (int) Math.round(tipv.getY()); + final int tipz = (int) Math.round(tipv.getZ()); + + vset.add(new Vector(tipx, tipy, tipz)); + } + + vset = getBallooned(vset, radius); + if (!filled) { + vset = getHollowed(vset); + } + return setBlocks(vset, pattern); + } + + private double hypot(final double... pars) { + double sum = 0; + for (final double d : pars) { + sum += Math.pow(d, 2); + } + return Math.sqrt(sum); + } + + private Set getBallooned(final Set vset, final double radius) { + final Set returnset = new HashSet(); + final int ceilrad = (int) Math.ceil(radius); + + for (final Vector v : vset) { + final int tipx = v.getBlockX(), tipy = v.getBlockY(), tipz = v.getBlockZ(); + + for (int loopx = tipx - ceilrad; loopx <= (tipx + ceilrad); loopx++) { + for (int loopy = tipy - ceilrad; loopy <= (tipy + ceilrad); loopy++) { + for (int loopz = tipz - ceilrad; loopz <= (tipz + ceilrad); loopz++) { + if (hypot(loopx - tipx, loopy - tipy, loopz - tipz) <= radius) { + returnset.add(new Vector(loopx, loopy, loopz)); + } + } + } + } + } + return returnset; + } + + private Set getHollowed(final Set vset) { + final Set returnset = new HashSet(); + for (final Vector v : vset) { + final double x = v.getX(), y = v.getY(), z = v.getZ(); + if (!(vset.contains(new Vector(x + 1, y, z)) + && vset.contains(new Vector(x - 1, y, z)) + && vset.contains(new Vector(x, y + 1, z)) + && vset.contains(new Vector(x, y - 1, z)) + && vset.contains(new Vector(x, y, z + 1)) && vset.contains(new Vector(x, y, z - 1)))) { + returnset.add(v); + } + } + return returnset; + } + + private void recurseHollow(final Region region, final BlockVector origin, final Set outside) { + final LinkedList queue = new LinkedList(); + queue.addLast(origin); + + while (!queue.isEmpty()) { + final BlockVector current = queue.removeFirst(); + if (!BlockType.canPassThrough(getBlockType(current), getBlockData(current))) { + continue; + } + + if (!outside.add(current)) { + continue; + } + + if (!region.contains(current)) { + continue; + } + + for (final Vector recurseDirection : recurseDirections) { + queue.addLast(current.add(recurseDirection).toBlockVector()); + } + } // while + } + + public int makeBiomeShape(final Region region, final Vector zero, final Vector unit, final BaseBiome biomeType, final String expressionString, final boolean hollow) throws ExpressionException, + MaxChangedBlocksException { + final Vector2D zero2D = zero.toVector2D(); + final Vector2D unit2D = unit.toVector2D(); + + final Expression expression = Expression.compile(expressionString, "x", "z"); + expression.optimize(); + + final EditSession editSession = this; + final WorldEditExpressionEnvironment environment = new WorldEditExpressionEnvironment(editSession, unit, zero); + expression.setEnvironment(environment); + + final ArbitraryBiomeShape shape = new ArbitraryBiomeShape(region) { + @Override + protected BaseBiome getBiome(final int x, final int z, final BaseBiome defaultBiomeType) { + final Vector2D current = new Vector2D(x, z); + environment.setCurrentBlock(current.toVector(0)); + final Vector2D scaled = current.subtract(zero2D).divide(unit2D); + + try { + if (expression.evaluate(scaled.getX(), scaled.getZ()) <= 0) { + return null; + } + + return defaultBiomeType; + } catch (final Exception e) { + log.log(Level.WARNING, "Failed to create shape", e); + return null; + } + } + }; + + return shape.generate(this, biomeType, hollow); + } + + private final Vector[] recurseDirections = { + PlayerDirection.NORTH.vector(), + PlayerDirection.EAST.vector(), + PlayerDirection.SOUTH.vector(), + PlayerDirection.WEST.vector(), + PlayerDirection.UP.vector(), + PlayerDirection.DOWN.vector(), }; + + private double lengthSq(final double x, final double y, final double z) { + return (x * x) + (y * y) + (z * z); + } + + private double lengthSq(final double x, final double z) { + return (x * x) + (z * z); + } + + public static Class inject() { + return EditSession.class; + } +} diff --git a/src/com/sk89q/worldedit/command/DispatcherWrapper.java b/src/com/sk89q/worldedit/command/DispatcherWrapper.java new file mode 100644 index 00000000..18064a81 --- /dev/null +++ b/src/com/sk89q/worldedit/command/DispatcherWrapper.java @@ -0,0 +1,115 @@ +package com.sk89q.worldedit.command; + +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import com.boydti.fawe.util.TaskManager; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandLocals; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.platform.CommandManager; +import com.sk89q.worldedit.extension.platform.PlatformManager; +import com.sk89q.worldedit.util.command.CommandCallable; +import com.sk89q.worldedit.util.command.CommandMapping; +import com.sk89q.worldedit.util.command.Description; +import com.sk89q.worldedit.util.command.Dispatcher; + +/** + * + * @author SBPrime + */ +public class DispatcherWrapper implements Dispatcher { + private final Dispatcher parent; + + public final Dispatcher getParent() { + return parent; + } + + public DispatcherWrapper(Dispatcher parent) { + this.parent = parent; + } + + @Override + public void registerCommand(CommandCallable callable, String... alias) { + parent.registerCommand(callable, alias); + } + + @Override + public Set getCommands() { + return parent.getCommands(); + } + + @Override + public Collection getPrimaryAliases() { + return parent.getPrimaryAliases(); + } + + @Override + public Collection getAliases() { + return parent.getAliases(); + } + + @Override + public CommandMapping get(String alias) { + return parent.get(alias); + } + + @Override + public boolean contains(String alias) { + return parent.contains(alias); + } + + @Override + public boolean call(final String arguments, final CommandLocals locals, final String[] parentCommands) throws CommandException { + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + try { + parent.call(arguments, locals, parentCommands); + } catch (CommandException e) { + e.printStackTrace(); + } + } + }); + return true; + } + + @Override + public Description getDescription() { + return parent.getDescription(); + } + + @Override + public boolean testPermission(CommandLocals locals) { + return parent.testPermission(locals); + } + + @Override + public List getSuggestions(String arguments, CommandLocals locals) throws CommandException { + return parent.getSuggestions(arguments, locals); + } + + public static void inject() { + // Delayed injection + TaskManager.IMP.task(new Runnable() { + @Override + public void run() { + try { + PlatformManager platform = WorldEdit.getInstance().getPlatformManager(); + CommandManager command = platform.getCommandManager(); + Class clazz = command.getClass(); + Field field = clazz.getDeclaredField("dispatcher"); + field.setAccessible(true); + Dispatcher parent = (Dispatcher) field.get(command); + DispatcherWrapper dispatcher = new DispatcherWrapper(parent); + field.set(command, dispatcher); + } catch (Throwable e) { + e.printStackTrace(); + } + } + }); + } + +} \ No newline at end of file diff --git a/src/com/sk89q/worldedit/command/FlattenedClipboardTransform.java b/src/com/sk89q/worldedit/command/FlattenedClipboardTransform.java new file mode 100644 index 00000000..79895cd4 --- /dev/null +++ b/src/com/sk89q/worldedit/command/FlattenedClipboardTransform.java @@ -0,0 +1,139 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.command; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.transform.BlockTransformExtent; +import com.sk89q.worldedit.function.operation.ForwardExtentCopy; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.math.transform.AffineTransform; +import com.sk89q.worldedit.math.transform.CombinedTransform; +import com.sk89q.worldedit.math.transform.Transform; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.world.registry.WorldData; + +/** + * Helper class to 'bake' a transform into a clipboard. + * + *

This class needs a better name and may need to be made more generic.

+ * + * @see Clipboard + * @see Transform + */ +public class FlattenedClipboardTransform { + + private final Clipboard original; + private final Transform transform; + private final WorldData worldData; + + /** + * Create a new instance. + * + * @param original the original clipboard + * @param transform the transform + * @param worldData the world data instance + */ + private FlattenedClipboardTransform(final Clipboard original, final Transform transform, final WorldData worldData) { + checkNotNull(original); + checkNotNull(transform); + checkNotNull(worldData); + this.original = original; + this.transform = transform; + this.worldData = worldData; + } + + /** + * Get the transformed region. + * + * @return the transformed region + */ + public Region getTransformedRegion() { + final Region region = original.getRegion(); + final Vector minimum = region.getMinimumPoint(); + final Vector maximum = region.getMaximumPoint(); + + final Transform transformAround = new CombinedTransform(new AffineTransform().translate(original.getOrigin().multiply(-1)), transform, new AffineTransform().translate(original.getOrigin())); + + final Vector[] corners = new Vector[] { + minimum, + maximum, + minimum.setX(maximum.getX()), + minimum.setY(maximum.getY()), + minimum.setZ(maximum.getZ()), + maximum.setX(minimum.getX()), + maximum.setY(minimum.getY()), + maximum.setZ(minimum.getZ()) }; + + for (int i = 0; i < corners.length; i++) { + corners[i] = transformAround.apply(corners[i]); + } + + Vector newMinimum = corners[0]; + Vector newMaximum = corners[0]; + + for (int i = 1; i < corners.length; i++) { + newMinimum = Vector.getMinimum(newMinimum, corners[i]); + newMaximum = Vector.getMaximum(newMaximum, corners[i]); + } + + // After transformation, the points may not really sit on a block, + // so we should expand the region for edge cases + newMinimum = newMinimum.setX(Math.floor(newMinimum.getX())); + newMinimum = newMinimum.setY(Math.floor(newMinimum.getY())); + newMinimum = newMinimum.setZ(Math.floor(newMinimum.getZ())); + + newMaximum = newMaximum.setX(Math.ceil(newMaximum.getX())); + newMaximum = newMaximum.setY(Math.ceil(newMaximum.getY())); + newMaximum = newMaximum.setZ(Math.ceil(newMaximum.getZ())); + + return new CuboidRegion(newMinimum, newMaximum); + } + + /** + * Create an operation to copy from the original clipboard to the given extent. + * + * @param target the target + * @return the operation + */ + public Operation copyTo(final Extent target) { + final BlockTransformExtent extent = new BlockTransformExtent(original, transform, worldData.getBlockRegistry()); + final ForwardExtentCopy copy = new ForwardExtentCopy(extent, original.getRegion(), original.getOrigin(), target, original.getOrigin()); + copy.setTransform(transform); + return copy; + } + + /** + * Create a new instance to bake the transform with. + * + * @param original the original clipboard + * @param transform the transform + * @param worldData the world data instance + * @return a builder + */ + public static FlattenedClipboardTransform transform(final Clipboard original, final Transform transform, final WorldData worldData) { + return new FlattenedClipboardTransform(original, transform, worldData); + } + +} diff --git a/src/com/sk89q/worldedit/command/SchematicCommands.java b/src/com/sk89q/worldedit/command/SchematicCommands.java new file mode 100644 index 00000000..353ec5e5 --- /dev/null +++ b/src/com/sk89q/worldedit/command/SchematicCommands.java @@ -0,0 +1,320 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.command; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Comparator; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.boydti.fawe.util.SetBlockQueue; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandPermissions; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.LocalConfiguration; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.math.transform.Transform; +import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.util.command.parametric.Optional; +import com.sk89q.worldedit.util.io.Closer; +import com.sk89q.worldedit.util.io.file.FilenameException; +import com.sk89q.worldedit.util.io.file.FilenameResolutionException; +import com.sk89q.worldedit.world.registry.WorldData; + +/** + * Commands that work with schematic files. + */ +public class SchematicCommands { + + private static final Logger log = Logger.getLogger(SchematicCommands.class.getCanonicalName()); + private final WorldEdit worldEdit; + + /** + * Create a new instance. + * + * @param worldEdit reference to WorldEdit + */ + public SchematicCommands(final WorldEdit worldEdit) { + checkNotNull(worldEdit); + this.worldEdit = worldEdit; + } + + @Command(aliases = { "load" }, usage = "[] ", desc = "Load a schematic into your clipboard") + @Deprecated + @CommandPermissions({ "worldedit.clipboard.load", "worldedit.schematic.load" }) + public void load(final Player player, final LocalSession session, @Optional("schematic") final String formatName, final String filename) throws FilenameException { + final LocalConfiguration config = worldEdit.getConfiguration(); + + final File dir = worldEdit.getWorkingDirectoryFile(config.saveDir); + final File f = worldEdit.getSafeOpenFile(player, dir, filename, "schematic", "schematic"); + + if (!f.exists()) { + player.printError("Schematic " + filename + " does not exist!"); + return; + } + + final ClipboardFormat format = ClipboardFormat.findByAlias(formatName); + if (format == null) { + player.printError("Unknown schematic format: " + formatName); + return; + } + + SetBlockQueue.IMP.addTask(new Runnable() { + @Override + public void run() { + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + final Closer closer = Closer.create(); + try { + final String filePath = f.getCanonicalPath(); + final String dirPath = dir.getCanonicalPath(); + + if (!filePath.substring(0, dirPath.length()).equals(dirPath)) { + player.printError("Clipboard file could not read or it does not exist."); + } else { + final FileInputStream fis = closer.register(new FileInputStream(f)); + final BufferedInputStream bis = closer.register(new BufferedInputStream(fis)); + final ClipboardReader reader = format.getReader(bis); + + final WorldData worldData = player.getWorld().getWorldData(); + final Clipboard clipboard = reader.read(player.getWorld().getWorldData()); + session.setClipboard(new ClipboardHolder(clipboard, worldData)); + + log.info(player.getName() + " loaded " + filePath); + player.print(filename + " loaded. Paste it with //paste"); + } + } catch (final IOException e) { + player.printError("Schematic could not read or it does not exist: " + e.getMessage()); + log.log(Level.WARNING, "Failed to load a saved clipboard", e); + } finally { + try { + closer.close(); + } catch (final IOException ignored) {} + } + } + }); + } + }); + } + + @Command(aliases = { "save" }, usage = "[] ", desc = "Save a schematic into your clipboard") + @Deprecated + @CommandPermissions({ "worldedit.clipboard.save", "worldedit.schematic.save" }) + public void save(final Player player, final LocalSession session, @Optional("schematic") final String formatName, final String filename) throws CommandException, WorldEditException { + final LocalConfiguration config = worldEdit.getConfiguration(); + + final File dir = worldEdit.getWorkingDirectoryFile(config.saveDir); + final File f = worldEdit.getSafeSaveFile(player, dir, filename, "schematic", "schematic"); + + final ClipboardFormat format = ClipboardFormat.findByAlias(formatName); + if (format == null) { + player.printError("Unknown schematic format: " + formatName); + return; + } + + final ClipboardHolder holder = session.getClipboard(); + final Clipboard clipboard = holder.getClipboard(); + final Transform transform = holder.getTransform(); + final Clipboard target; + + // If we have a transform, bake it into the copy + if (!transform.isIdentity()) { + final FlattenedClipboardTransform result = FlattenedClipboardTransform.transform(clipboard, transform, holder.getWorldData()); + target = new BlockArrayClipboard(result.getTransformedRegion()); + target.setOrigin(clipboard.getOrigin()); + Operations.completeLegacy(result.copyTo(target)); + } else { + target = clipboard; + } + + SetBlockQueue.IMP.addTask(new Runnable() { + @Override + public void run() { + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + final Closer closer = Closer.create(); + try { + // Create parent directories + final File parent = f.getParentFile(); + if ((parent != null) && !parent.exists()) { + if (!parent.mkdirs()) { + log.info("Could not create folder for schematics!"); + return; + } + } + + final FileOutputStream fos = closer.register(new FileOutputStream(f)); + final BufferedOutputStream bos = closer.register(new BufferedOutputStream(fos)); + final ClipboardWriter writer = closer.register(format.getWriter(bos)); + writer.write(target, holder.getWorldData()); + log.info(player.getName() + " saved " + f.getCanonicalPath()); + player.print(filename + " saved."); + } catch (final IOException e) { + player.printError("Schematic could not written: " + e.getMessage()); + log.log(Level.WARNING, "Failed to write a saved clipboard", e); + } finally { + try { + closer.close(); + } catch (final IOException ignored) {} + } + } + }); + } + }); + } + + @Command(aliases = { "delete", "d" }, usage = "", desc = "Delete a saved schematic", help = "Delete a schematic from the schematic list", min = 1, max = 1) + @CommandPermissions("worldedit.schematic.delete") + public void delete(final Player player, final LocalSession session, final EditSession editSession, final CommandContext args) throws WorldEditException { + final LocalConfiguration config = worldEdit.getConfiguration(); + final String filename = args.getString(0); + + final File dir = worldEdit.getWorkingDirectoryFile(config.saveDir); + final File f = worldEdit.getSafeSaveFile(player, dir, filename, "schematic", "schematic"); + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + if (!f.exists()) { + player.printError("Schematic " + filename + " does not exist!"); + return; + } + + if (!f.delete()) { + player.printError("Deletion of " + filename + " failed! Maybe it is read-only."); + return; + } + + player.print(filename + " has been deleted."); + } + }); + } + + @Command(aliases = { "formats", "listformats", "f" }, desc = "List available formats", max = 0) + @CommandPermissions("worldedit.schematic.formats") + public void formats(final Actor actor) throws WorldEditException { + actor.print("Available clipboard formats (Name: Lookup names)"); + StringBuilder builder; + boolean first = true; + for (final ClipboardFormat format : ClipboardFormat.values()) { + builder = new StringBuilder(); + builder.append(format.name()).append(": "); + for (final String lookupName : format.getAliases()) { + if (!first) { + builder.append(", "); + } + builder.append(lookupName); + first = false; + } + first = true; + actor.print(builder.toString()); + } + } + + @Command(aliases = { "list", "all", "ls" }, desc = "List saved schematics", max = 0, flags = "dn", help = "List all schematics in the schematics directory\n" + + " -d sorts by date, oldest first\n" + + " -n sorts by date, newest first\n") + @CommandPermissions("worldedit.schematic.list") + public void list(final Actor actor, final CommandContext args) throws WorldEditException { + final File dir = worldEdit.getWorkingDirectoryFile(worldEdit.getConfiguration().saveDir); + final File[] files = dir.listFiles(new FileFilter() { + @Override + public boolean accept(final File file) { + // sort out directories from the schematic list + // if WE supports sub-directories in the future, + // this will have to be changed + return file.isFile(); + } + }); + if (files == null) { + throw new FilenameResolutionException(dir.getPath(), "Schematics directory invalid or not found."); + } + + final int sortType = args.hasFlag('d') ? -1 : args.hasFlag('n') ? 1 : 0; + // cleanup file list + Arrays.sort(files, new Comparator() { + @Override + public int compare(final File f1, final File f2) { + // this should no longer happen, as directory-ness is checked before + // however, if a directory slips through, this will break the contract + // of comparator transitivity + if (!f1.isFile() || !f2.isFile()) { + return -1; + } + // http://stackoverflow.com/questions/203030/best-way-to-list-files-in-java-sorted-by-date-modified + int result = sortType == 0 ? f1.getName().compareToIgnoreCase(f2.getName()) : // use name by default + Long.valueOf(f1.lastModified()).compareTo(f2.lastModified()); // use date if there is a flag + if (sortType == 1) { + result = -result; // flip date for newest first instead of oldest first + } + return result; + } + }); + + actor.print("Available schematics (Filename (Format)):"); + actor.print(listFiles("", files)); + } + + private String listFiles(final String prefix, final File[] files) { + final StringBuilder build = new StringBuilder(); + for (final File file : files) { + if (file.isDirectory()) { + build.append(listFiles(prefix + file.getName() + "/", file.listFiles())); + continue; + } + + if (!file.isFile()) { + continue; + } + + build.append("\n\u00a79"); + final ClipboardFormat format = ClipboardFormat.findByFile(file); + build.append(prefix).append(file.getName()).append(": ").append(format == null ? "Unknown" : format.name()); + } + return build.toString(); + } + + public static Class inject() { + return SchematicCommands.class; + } +} diff --git a/src/com/sk89q/worldedit/command/ScriptingCommands.java b/src/com/sk89q/worldedit/command/ScriptingCommands.java new file mode 100644 index 00000000..4260d925 --- /dev/null +++ b/src/com/sk89q/worldedit/command/ScriptingCommands.java @@ -0,0 +1,130 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.command; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.sk89q.minecraft.util.commands.Logging.LogMode.ALL; + +import java.io.File; + +import com.boydti.fawe.util.SetBlockQueue; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandPermissions; +import com.sk89q.minecraft.util.commands.Logging; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.Player; + +/** + * Commands related to scripting. + */ +public class ScriptingCommands { + + private final WorldEdit worldEdit; + + /** + * Create a new instance. + * + * @param worldEdit reference to WorldEdit + */ + public ScriptingCommands(final WorldEdit worldEdit) { + checkNotNull(worldEdit); + this.worldEdit = worldEdit; + } + + @Command(aliases = { "cs" }, usage = " [args...]", desc = "Execute a CraftScript", min = 1, max = -1) + @CommandPermissions("worldedit.scripting.execute") + @Logging(ALL) + public void execute(final Player player, final LocalSession session, final EditSession editSession, final CommandContext args) throws WorldEditException { + final String[] scriptArgs = args.getSlice(1); + final String name = args.getString(0); + + if (!player.hasPermission("worldedit.scripting.execute." + name)) { + player.printError("You don't have permission to use that script."); + return; + } + + session.setLastScript(name); + + final File dir = worldEdit.getWorkingDirectoryFile(worldEdit.getConfiguration().scriptsDir); + final File f = worldEdit.getSafeOpenFile(player, dir, name, "js", "js"); + SetBlockQueue.IMP.addTask(new Runnable() { + @Override + public void run() { + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + try { + worldEdit.runScript(player, f, scriptArgs); + } catch (final WorldEditException ex) { + player.printError("Error while executing CraftScript."); + } + } + }); + } + }); + } + + @Command(aliases = { ".s" }, usage = "[args...]", desc = "Execute last CraftScript", min = 0, max = -1) + @CommandPermissions("worldedit.scripting.execute") + @Logging(ALL) + public void executeLast(final Player player, final LocalSession session, final EditSession editSession, final CommandContext args) throws WorldEditException { + final String lastScript = session.getLastScript(); + + if (!player.hasPermission("worldedit.scripting.execute." + lastScript)) { + player.printError("You don't have permission to use that script."); + return; + } + + if (lastScript == null) { + player.printError("Use /cs with a script name first."); + return; + } + + final String[] scriptArgs = args.getSlice(0); + + final File dir = worldEdit.getWorkingDirectoryFile(worldEdit.getConfiguration().scriptsDir); + final File f = worldEdit.getSafeOpenFile(player, dir, lastScript, "js", "js"); + + SetBlockQueue.IMP.addTask(new Runnable() { + @Override + public void run() { + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + try { + worldEdit.runScript(player, f, scriptArgs); + } catch (final WorldEditException ex) { + player.printError("Error while executing CraftScript."); + } + } + }); + } + }); + } + + public static Class inject() { + return ScriptingCommands.class; + } +} diff --git a/src/com/sk89q/worldedit/function/operation/Operations.java b/src/com/sk89q/worldedit/function/operation/Operations.java new file mode 100644 index 00000000..c74409bc --- /dev/null +++ b/src/com/sk89q/worldedit/function/operation/Operations.java @@ -0,0 +1,112 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.operation; + +import com.boydti.fawe.util.SetBlockQueue; +import com.boydti.fawe.util.TaskManager; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.WorldEditException; + +/** + * Operation helper methods. + */ +public final class Operations { + + private Operations() {} + + /** + * Complete a given operation synchronously until it completes. + * + * @param op operation to execute + * @throws WorldEditException WorldEdit exception + */ + public static void complete(Operation operation) throws WorldEditException { + while (operation != null) { + operation = operation.resume(new RunContext()); + } + } + + /** + * Complete a given operation synchronously until it completes. Catch all + * errors that is not {@link MaxChangedBlocksException} for legacy reasons. + * + * @param op operation to execute + * @throws MaxChangedBlocksException thrown when too many blocks have been changed + */ + public static void completeLegacy(Operation operation) throws MaxChangedBlocksException { + try { + while (operation != null) { + operation = operation.resume(new RunContext()); + } + } catch (final WorldEditException e) { + e.printStackTrace(); + } + } + + /** + * Complete a given operation synchronously until it completes. Re-throw all + * {@link com.sk89q.worldedit.WorldEditException} exceptions as + * {@link java.lang.RuntimeException}s. + * + * @param op operation to execute + */ + public static void completeBlindly(Operation operation) { + try { + while (operation != null) { + operation = operation.resume(new RunContext()); + } + } catch (final Exception e) { + e.printStackTrace(); + } + } + + public static void completeSmart(final Operation op, final Runnable whenDone, final boolean threadsafe) { + if (!threadsafe) { + completeBlindly(op); + if (whenDone != null) { + whenDone.run(); + } + return; + } + SetBlockQueue.IMP.addTask(new Runnable() { + @Override + public void run() { + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + Operation operation = op; + while (operation != null) { + try { + operation = operation.resume(new RunContext()); + } catch (final Exception e) { + e.printStackTrace(); + } + } + TaskManager.IMP.task(whenDone); + } + }); + }; + }); + } + + public static Class inject() { + return Operations.class; + } +} diff --git a/src/com/sk89q/worldedit/function/visitor/BreadthFirstSearch.java b/src/com/sk89q/worldedit/function/visitor/BreadthFirstSearch.java new file mode 100644 index 00000000..740573ad --- /dev/null +++ b/src/com/sk89q/worldedit/function/visitor/BreadthFirstSearch.java @@ -0,0 +1,187 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.visitor; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.function.RegionFunction; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.function.operation.RunContext; + +/** + * Performs a breadth-first search starting from points added with + * {@link #visit(com.sk89q.worldedit.Vector)}. The search continues + * to a certain adjacent point provided that the method + * {@link #isVisitable(com.sk89q.worldedit.Vector, com.sk89q.worldedit.Vector)} + * returns true for that point. + * + *

As an abstract implementation, this class can be used to implement + * functionality that starts at certain points and extends outward from + * those points.

+ */ +public abstract class BreadthFirstSearch implements Operation { + + private final RegionFunction function; + private final Queue queue = new ArrayDeque(); + private final Set visited = new HashSet(); + private final List directions = new ArrayList(); + private int affected = 0; + + /** + * Create a new instance. + * + * @param function the function to apply to visited blocks + */ + protected BreadthFirstSearch(final RegionFunction function) { + checkNotNull(function); + this.function = function; + addAxes(); + } + + /** + * Get the list of directions will be visited. + * + *

Directions are {@link com.sk89q.worldedit.Vector}s that determine + * what adjacent points area available. Vectors should not be + * unit vectors. An example of a valid direction is + * {@code new Vector(1, 0, 1)}.

+ * + *

The list of directions can be cleared.

+ * + * @return the list of directions + */ + protected Collection getDirections() { + return directions; + } + + /** + * Add the directions along the axes as directions to visit. + */ + protected void addAxes() { + directions.add(new Vector(0, -1, 0)); + directions.add(new Vector(0, 1, 0)); + directions.add(new Vector(-1, 0, 0)); + directions.add(new Vector(1, 0, 0)); + directions.add(new Vector(0, 0, -1)); + directions.add(new Vector(0, 0, 1)); + } + + /** + * Add the diagonal directions as directions to visit. + */ + protected void addDiagonal() { + directions.add(new Vector(1, 0, 1)); + directions.add(new Vector(-1, 0, -1)); + directions.add(new Vector(1, 0, -1)); + directions.add(new Vector(-1, 0, 1)); + } + + /** + * Add the given location to the list of locations to visit, provided + * that it has not been visited. The position passed to this method + * will still be visited even if it fails + * {@link #isVisitable(com.sk89q.worldedit.Vector, com.sk89q.worldedit.Vector)}. + * + *

This method should be used before the search begins, because if + * the position does fail the test, and the search has already + * visited it (because it is connected to another root point), + * the search will mark the position as "visited" and a call to this + * method will do nothing.

+ * + * @param position the position + */ + public void visit(final Vector position) { + final BlockVector blockVector = position.toBlockVector(); + if (!visited.contains(blockVector)) { + queue.add(blockVector); + visited.add(blockVector); + } + } + + /** + * Try to visit the given 'to' location. + * + * @param from the origin block + * @param to the block under question + */ + private void visit(final Vector from, final Vector to) { + final BlockVector blockVector = to.toBlockVector(); + if (!visited.contains(blockVector)) { + visited.add(blockVector); + if (isVisitable(from, to)) { + queue.add(blockVector); + } + } + } + + /** + * Return whether the given 'to' block should be visited, starting from the + * 'from' block. + * + * @param from the origin block + * @param to the block under question + * @return true if the 'to' block should be visited + */ + protected abstract boolean isVisitable(final Vector from, final Vector to); + + /** + * Get the number of affected objects. + * + * @return the number of affected + */ + public int getAffected() { + return affected; + } + + @Override + public Operation resume(final RunContext run) throws WorldEditException { + Vector position; + while ((position = queue.poll()) != null) { + if (function.apply(position)) { + affected++; + } + + for (final Vector dir : directions) { + visit(position, position.add(dir)); + } + } + return null; + } + + @Override + public void cancel() {} + + public static Class inject() { + return Operations.class; + } + +} diff --git a/src/com/sk89q/worldedit/function/visitor/DownwardVisitor.java b/src/com/sk89q/worldedit/function/visitor/DownwardVisitor.java new file mode 100644 index 00000000..123da36c --- /dev/null +++ b/src/com/sk89q/worldedit/function/visitor/DownwardVisitor.java @@ -0,0 +1,73 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.visitor; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Collection; + +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.function.RegionFunction; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.operation.Operations; + +/** + * Visits adjacent points on the same X-Z plane as long as the points + * pass the given mask, and then executes the provided region + * function on the entire column. + * + *

This is used by {@code //fill}.

+ */ +public class DownwardVisitor extends RecursiveVisitor { + + private final int baseY; + + /** + * Create a new visitor. + * + * @param mask the mask + * @param function the function + * @param baseY the base Y + */ + public DownwardVisitor(final Mask mask, final RegionFunction function, final int baseY) { + super(mask, function); + checkNotNull(mask); + + this.baseY = baseY; + + final Collection directions = getDirections(); + directions.clear(); + directions.add(new Vector(1, 0, 0)); + directions.add(new Vector(-1, 0, 0)); + directions.add(new Vector(0, 0, 1)); + directions.add(new Vector(0, 0, -1)); + directions.add(new Vector(0, -1, 0)); + } + + @Override + protected boolean isVisitable(final Vector from, final Vector to) { + final int fromY = from.getBlockY(); + return ((fromY == baseY) || (to.subtract(from).getBlockY() < 0)) && super.isVisitable(from, to); + } + + public static Class inject() { + return Operations.class; + } +} diff --git a/src/com/sk89q/worldedit/function/visitor/EntityVisitor.java b/src/com/sk89q/worldedit/function/visitor/EntityVisitor.java new file mode 100644 index 00000000..48db0ad0 --- /dev/null +++ b/src/com/sk89q/worldedit/function/visitor/EntityVisitor.java @@ -0,0 +1,80 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.visitor; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Iterator; + +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.function.EntityFunction; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.function.operation.RunContext; + +/** + * Visits entities as provided by an {@code Iterator}. + */ +public class EntityVisitor implements Operation { + + private final EntityFunction function; + private final int affected = 0; + private final Iterator iterator; + + /** + * Create a new instance. + * + * @param iterator the iterator + * @param function the function + */ + public EntityVisitor(final Iterator iterator, final EntityFunction function) { + checkNotNull(iterator); + checkNotNull(function); + + this.function = function; + this.iterator = iterator; + } + + /** + * Get the number of affected objects. + * + * @return the number of affected + */ + public int getAffected() { + return affected; + } + + @Override + public Operation resume(final RunContext run) throws WorldEditException { + while (iterator.hasNext()) { + function.apply(iterator.next()); + } + return null; + } + + @Override + public void cancel() {} + + public static Class inject() { + return Operations.class; + } + +} diff --git a/src/com/sk89q/worldedit/function/visitor/FlatRegionVisitor.java b/src/com/sk89q/worldedit/function/visitor/FlatRegionVisitor.java new file mode 100644 index 00000000..7686d5a5 --- /dev/null +++ b/src/com/sk89q/worldedit/function/visitor/FlatRegionVisitor.java @@ -0,0 +1,78 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.visitor; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.function.FlatRegionFunction; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.function.operation.RunContext; +import com.sk89q.worldedit.regions.FlatRegion; + +/** + * Applies region functions to columns in a {@link FlatRegion}. + */ +public class FlatRegionVisitor implements Operation { + + private final FlatRegionFunction function; + private final int affected = 0; + private final Iterable iterator; + + /** + * Create a new visitor. + * + * @param flatRegion a flat region + * @param function a function to apply to columns + */ + public FlatRegionVisitor(final FlatRegion flatRegion, final FlatRegionFunction function) { + checkNotNull(flatRegion); + checkNotNull(function); + this.function = function; + iterator = flatRegion.asFlatRegion(); + } + + /** + * Get the number of affected objects. + * + * @return the number of affected + */ + public int getAffected() { + return affected; + } + + @Override + public Operation resume(final RunContext run) throws WorldEditException { + for (final Vector2D pt : iterator) { + function.apply(pt); + } + return null; + } + + @Override + public void cancel() {} + + public static Class inject() { + return Operations.class; + } + +} diff --git a/src/com/sk89q/worldedit/function/visitor/LayerVisitor.java b/src/com/sk89q/worldedit/function/visitor/LayerVisitor.java new file mode 100644 index 00000000..51a2ca71 --- /dev/null +++ b/src/com/sk89q/worldedit/function/visitor/LayerVisitor.java @@ -0,0 +1,130 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.visitor; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.function.LayerFunction; +import com.sk89q.worldedit.function.mask.Mask2D; +import com.sk89q.worldedit.function.mask.Masks; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.function.operation.RunContext; +import com.sk89q.worldedit.regions.FlatRegion; + +/** + * Visits the layers within a region. + * + *

This class works by iterating over all the columns in a {@link FlatRegion}, + * finding the first ground block in each column (searching from a given + * maximum Y down to a minimum Y), and then applies a {@link LayerFunction} to + * each layer.

+ */ +public class LayerVisitor implements Operation { + + private final LayerFunction function; + private Mask2D mask = Masks.alwaysTrue2D(); + private final int minY; + private final int maxY; + private final Iterable iterator; + + /** + * Create a new visitor. + * + * @param flatRegion the flat region to visit + * @param minY the minimum Y to stop the search at + * @param maxY the maximum Y to begin the search at + * @param function the layer function to apply t blocks + */ + public LayerVisitor(final FlatRegion flatRegion, final int minY, final int maxY, final LayerFunction function) { + checkNotNull(flatRegion); + checkArgument(minY <= maxY, "minY <= maxY required"); + checkNotNull(function); + this.minY = minY; + this.maxY = maxY; + this.function = function; + iterator = flatRegion.asFlatRegion(); + } + + /** + * Get the mask that determines which columns within the flat region + * will be visited. + * + * @return a 2D mask + */ + public Mask2D getMask() { + return mask; + } + + /** + * Set the mask that determines which columns within the flat region + * will be visited. + * + * @param mask a 2D mask + */ + public void setMask(final Mask2D mask) { + checkNotNull(mask); + this.mask = mask; + } + + @Override + public Operation resume(final RunContext run) throws WorldEditException { + for (final Vector2D column : iterator) { + if (!mask.test(column)) { + continue; + } + + // Abort if we are underground + if (function.isGround(column.toVector(maxY + 1))) { + return null; + } + + boolean found = false; + int groundY = 0; + for (int y = maxY; y >= minY; --y) { + final Vector test = column.toVector(y); + if (!found) { + if (function.isGround(test)) { + found = true; + groundY = y; + } + } + + if (found) { + if (!function.apply(test, groundY - y)) { + break; + } + } + } + } + return null; + } + + @Override + public void cancel() {} + + public static Class inject() { + return Operations.class; + } +} diff --git a/src/com/sk89q/worldedit/function/visitor/NonRisingVisitor.java b/src/com/sk89q/worldedit/function/visitor/NonRisingVisitor.java new file mode 100644 index 00000000..c102beb0 --- /dev/null +++ b/src/com/sk89q/worldedit/function/visitor/NonRisingVisitor.java @@ -0,0 +1,55 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.visitor; + +import java.util.Collection; + +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.function.RegionFunction; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.operation.Operations; + +/** + * A {@link RecursiveVisitor} that goes orthogonally to the side and down, but never up. + */ +public class NonRisingVisitor extends RecursiveVisitor { + + /** + * Create a new recursive visitor. + * + * @param mask the mask + * @param function the function + */ + public NonRisingVisitor(final Mask mask, final RegionFunction function) { + super(mask, function); + final Collection directions = getDirections(); + directions.clear(); + directions.add(new Vector(1, 0, 0)); + directions.add(new Vector(-1, 0, 0)); + directions.add(new Vector(0, 0, 1)); + directions.add(new Vector(0, 0, -1)); + directions.add(new Vector(0, -1, 0)); + } + + public static Class inject() { + return Operations.class; + } + +} diff --git a/src/com/sk89q/worldedit/function/visitor/RecursiveVisitor.java b/src/com/sk89q/worldedit/function/visitor/RecursiveVisitor.java new file mode 100644 index 00000000..964eb030 --- /dev/null +++ b/src/com/sk89q/worldedit/function/visitor/RecursiveVisitor.java @@ -0,0 +1,57 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.visitor; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.function.RegionFunction; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.operation.Operations; + +/** + * An implementation of an {@link BreadthFirstSearch} that uses a mask to + * determine where a block should be visited. + */ +public class RecursiveVisitor extends BreadthFirstSearch { + + private final Mask mask; + + /** + * Create a new recursive visitor. + * + * @param mask the mask + * @param function the function + */ + public RecursiveVisitor(final Mask mask, final RegionFunction function) { + super(function); + checkNotNull(mask); + this.mask = mask; + } + + @Override + protected boolean isVisitable(final Vector from, final Vector to) { + return mask.test(to); + } + + public static Class inject() { + return Operations.class; + } +} diff --git a/src/com/sk89q/worldedit/function/visitor/RegionVisitor.java b/src/com/sk89q/worldedit/function/visitor/RegionVisitor.java new file mode 100644 index 00000000..a0f04068 --- /dev/null +++ b/src/com/sk89q/worldedit/function/visitor/RegionVisitor.java @@ -0,0 +1,72 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.visitor; + +import java.util.Iterator; + +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.function.RegionFunction; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.function.operation.RunContext; +import com.sk89q.worldedit.regions.Region; + +/** + * Utility class to apply region functions to {@link com.sk89q.worldedit.regions.Region}. + */ +public class RegionVisitor implements Operation { + + private final RegionFunction function; + private final int affected = 0; + + private final Iterator iterator; + + public RegionVisitor(final Region region, final RegionFunction function) { + this.function = function; + iterator = region.iterator(); + + } + + /** + * Get the number of affected objects. + * + * @return the number of affected + */ + public int getAffected() { + return affected; + } + + @Override + public Operation resume(final RunContext run) throws WorldEditException { + while (iterator.hasNext()) { + function.apply(iterator.next()); + } + return null; + } + + @Override + public void cancel() {} + + public static Class inject() { + return Operations.class; + } + +}