From e7fe787b20b6510b385ed50a41774cc3a948df24 Mon Sep 17 00:00:00 2001
From: sk89q
Date: Mon, 31 Mar 2014 16:55:58 -0700
Subject: [PATCH] Broke up EditSession into many Extents.
---
.../java/com/sk89q/worldedit/EditSession.java | 1074 +++++++----------
.../java/com/sk89q/worldedit/LocalWorld.java | 7 +
.../worldedit/extent/BlockBagExtent.java | 120 ++
.../worldedit/extent/BlockChangeLimiter.java | 87 ++
.../worldedit/extent/BlockQuirkExtent.java | 64 +
.../worldedit/extent/ChangeSetExtent.java | 56 +
.../worldedit/extent/ChunkLoadingExtent.java | 66 +
.../worldedit/extent/DataValidatorExtent.java | 63 +
.../com/sk89q/worldedit/extent/Extent.java | 11 +
.../worldedit/extent/ExtentDelegate.java | 96 ++
.../worldedit/extent/FastModeExtent.java | 108 ++
...Buffer.java => ForgetfulExtentBuffer.java} | 33 +-
.../sk89q/worldedit/extent/MaskingExtent.java | 73 ++
.../extent/reorder/SimpleBlockReorder.java | 212 ++++
.../visitor/BlockMapEntryVisitor.java | 58 +
15 files changed, 1463 insertions(+), 665 deletions(-)
create mode 100644 src/main/java/com/sk89q/worldedit/extent/BlockBagExtent.java
create mode 100644 src/main/java/com/sk89q/worldedit/extent/BlockChangeLimiter.java
create mode 100644 src/main/java/com/sk89q/worldedit/extent/BlockQuirkExtent.java
create mode 100644 src/main/java/com/sk89q/worldedit/extent/ChangeSetExtent.java
create mode 100644 src/main/java/com/sk89q/worldedit/extent/ChunkLoadingExtent.java
create mode 100644 src/main/java/com/sk89q/worldedit/extent/DataValidatorExtent.java
create mode 100644 src/main/java/com/sk89q/worldedit/extent/ExtentDelegate.java
create mode 100644 src/main/java/com/sk89q/worldedit/extent/FastModeExtent.java
rename src/main/java/com/sk89q/worldedit/extent/{ExtentBuffer.java => ForgetfulExtentBuffer.java} (86%)
create mode 100644 src/main/java/com/sk89q/worldedit/extent/MaskingExtent.java
create mode 100644 src/main/java/com/sk89q/worldedit/extent/reorder/SimpleBlockReorder.java
create mode 100644 src/main/java/com/sk89q/worldedit/function/visitor/BlockMapEntryVisitor.java
diff --git a/src/main/java/com/sk89q/worldedit/EditSession.java b/src/main/java/com/sk89q/worldedit/EditSession.java
index af6e61334..5562eedea 100644
--- a/src/main/java/com/sk89q/worldedit/EditSession.java
+++ b/src/main/java/com/sk89q/worldedit/EditSession.java
@@ -1,7 +1,7 @@
-// $Id$
/*
- * WorldEditLibrary
- * Copyright (C) 2010 sk89q and contributors
+ * 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 General Public License as published by
@@ -16,19 +16,18 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
+
package com.sk89q.worldedit;
import com.sk89q.worldedit.bags.BlockBag;
-import com.sk89q.worldedit.bags.BlockBagException;
-import com.sk89q.worldedit.bags.UnplaceableBlockException;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.blocks.BlockID;
import com.sk89q.worldedit.blocks.BlockType;
import com.sk89q.worldedit.expression.Expression;
import com.sk89q.worldedit.expression.ExpressionException;
import com.sk89q.worldedit.expression.runtime.RValue;
-import com.sk89q.worldedit.extent.Extent;
-import com.sk89q.worldedit.extent.ExtentBuffer;
+import com.sk89q.worldedit.extent.*;
+import com.sk89q.worldedit.extent.reorder.SimpleBlockReorder;
import com.sk89q.worldedit.function.GroundFunction;
import com.sk89q.worldedit.function.RegionMaskingFilter;
import com.sk89q.worldedit.function.block.BlockCount;
@@ -36,18 +35,15 @@ 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.*;
-import com.sk89q.worldedit.function.operation.ChangeSetExecutor;
-import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
-import com.sk89q.worldedit.function.operation.OperationHelper;
-import com.sk89q.worldedit.function.operation.OperationQueue;
+import com.sk89q.worldedit.function.operation.*;
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.*;
-import com.sk89q.worldedit.history.changeset.BlockOptimizedHistory;
-import com.sk89q.worldedit.history.changeset.ChangeSet;
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.masks.Mask;
import com.sk89q.worldedit.math.interpolation.Interpolation;
import com.sk89q.worldedit.math.interpolation.KochanekBartelsInterpolation;
@@ -63,6 +59,7 @@ import com.sk89q.worldedit.shape.RegionShape;
import com.sk89q.worldedit.shape.WorldEditExpressionEnvironment;
import com.sk89q.worldedit.util.TreeGenerator;
+import javax.annotation.Nullable;
import java.util.*;
import static com.google.common.base.Preconditions.checkArgument;
@@ -70,208 +67,92 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static com.sk89q.worldedit.regions.Regions.*;
/**
- * This class can wrap all block editing operations into one "edit session" that
- * stores the state of the blocks before modification. This allows for easy undo
- * or redo. In addition to that, this class can use a "queue mode" that will
- * know how to handle some special types of items such as signs and torches. For
- * example, torches must be placed only after there is already a block below it,
- * otherwise the torch will be placed as an item.
- *
- * @author sk89q
+ * 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 static Random prng = new Random();
+ /**
+ * Used by {@link #setBlock(Vector, BaseBlock, boolean, Level)} to
+ * determine which {@link Extent}s should be bypassed.
+ */
+ public enum Level {
+ NORMAL,
+ NO_HISTORY_REORDER,
+ NO_HISTORY
+ }
- protected LocalWorld world;
+ @SuppressWarnings("ProtectedField")
+ protected final LocalWorld world;
private final ChangeSet changeSet = new BlockOptimizedHistory();
- /**
- * Blocks that should be placed before last.
- */
- private DoubleArrayList queueAfter =
- new DoubleArrayList(false);
+ private final FastModeExtent fastModeExtent;
+ private final BlockBagExtent blockBagExtent;
+ private final SimpleBlockReorder reorderExtent;
+ private final MaskingExtent maskingExtent;
+ private final BlockChangeLimiter changeLimiter;
- /**
- * Blocks that should be placed last.
- */
- private DoubleArrayList queueLast =
- new DoubleArrayList(false);
+ private final Extent bypassReorderHistory;
+ private final Extent bypassHistory;
+ private final Extent bypassNone;
- /**
- * Blocks that should be placed after all other blocks.
- */
- private DoubleArrayList queueFinal =
- new DoubleArrayList(false);
-
- /**
- * The maximum number of blocks to change at a time. If this number is
- * exceeded, a MaxChangedBlocksException exception will be raised. -1
- * indicates no limit.
- */
- private int maxBlocks = -1;
-
- /**
- * Indicates whether some types of blocks should be queued for best
- * reproduction.
- */
- private boolean queued = false;
-
- /**
- * Use the fast mode, which may leave chunks not flagged "dirty".
- */
- private boolean fastMode = false;
-
- /**
- * Block bag to use for getting blocks.
- */
- private BlockBag blockBag;
-
- /**
- * List of missing blocks;
- */
- private Map missingBlocks = new HashMap();
-
- /**
- * Mask to cover operations.
- */
- private Mask mask;
+ @SuppressWarnings("deprecation")
+ private Mask oldMask;
/**
* Construct the object with a maximum number of blocks.
*
- * @param world
- * @param maxBlocks
+ * @param world the world
+ * @param maxBlocks the maximum number of blocks that can be changed, or -1 to use no limit
*/
public EditSession(LocalWorld world, int maxBlocks) {
- if (maxBlocks < -1) {
- throw new IllegalArgumentException("Max blocks must be >= -1");
- }
-
- this.maxBlocks = maxBlocks;
- this.world = world;
+ this(world, maxBlocks, null);
}
/**
* Construct the object with a maximum number of blocks and a block bag.
*
- * @param world
- * @param maxBlocks
- * @param blockBag
- * @blockBag
+ * @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
*/
- public EditSession(LocalWorld world, int maxBlocks, BlockBag blockBag) {
- if (maxBlocks < -1) {
- throw new IllegalArgumentException("Max blocks must be >= -1");
- }
+ public EditSession(LocalWorld world, int maxBlocks, @Nullable BlockBag blockBag) {
+ checkNotNull(world);
+ checkArgument(maxBlocks >= -1, "maxBlocks >= -1 required");
- this.maxBlocks = maxBlocks;
- this.blockBag = blockBag;
this.world = world;
+
+ // This extents are ALWAYS used
+ fastModeExtent = new FastModeExtent(world, false);
+ ChunkLoadingExtent chunkLoadingExtent = new ChunkLoadingExtent(fastModeExtent, world);
+ BlockQuirkExtent quirkExtent = new BlockQuirkExtent(chunkLoadingExtent, world);
+ DataValidatorExtent validator = new DataValidatorExtent(quirkExtent, world);
+ blockBagExtent = new BlockBagExtent(validator, world, blockBag);
+
+ // This extent can be skipped by calling rawSetBlock()
+ reorderExtent = new SimpleBlockReorder(blockBagExtent, false);
+
+ // These extents can be skipped by calling smartSetBlock()
+ ChangeSetExtent changeSetExtent = new ChangeSetExtent(reorderExtent, changeSet);
+ maskingExtent = new MaskingExtent(changeSetExtent, Masks.alwaysTrue());
+ changeLimiter = new BlockChangeLimiter(maskingExtent, maxBlocks);
+
+ this.bypassReorderHistory = blockBagExtent;
+ this.bypassHistory = reorderExtent;
+ this.bypassNone = changeLimiter;
}
/**
- * Sets a block without changing history.
+ * Get the world.
*
- * @param pt
- * @param block
- * @return Whether the block changed
+ * @return the world
*/
- public boolean rawSetBlock(Vector pt, BaseBlock block) {
- final int y = pt.getBlockY();
- final int type = block.getType();
- if (y < 0 || y > world.getMaxY()) {
- return false;
- }
-
- world.checkLoadedChunk(pt);
-
- // No invalid blocks
- if (!world.isValidBlockType(type)) {
- return false;
- }
-
- final int existing = world.getBlockType(pt);
-
- // Clear the container block so that it doesn't drop items
- if (BlockType.isContainerBlock(existing)) {
- world.clearContainerBlockContents(pt);
- // Ice turns until water so this has to be done first
- } else if (existing == BlockID.ICE) {
- world.setBlockType(pt, BlockID.AIR);
- }
-
- if (blockBag != null) {
- if (type > 0) {
- try {
- blockBag.fetchPlacedBlock(type, 0);
- } catch (UnplaceableBlockException e) {
- return false;
- } catch (BlockBagException e) {
- if (!missingBlocks.containsKey(type)) {
- missingBlocks.put(type, 1);
- } else {
- missingBlocks.put(type, missingBlocks.get(type) + 1);
- }
- return false;
- }
- }
-
- if (existing > 0) {
- try {
- blockBag.storeDroppedBlock(existing, world.getBlockData(pt));
- } catch (BlockBagException e) {
- }
- }
- }
-
- boolean result;
-
- if (type == 0) {
- if (fastMode) {
- result = world.setBlockTypeFast(pt, 0);
- } else {
- result = world.setBlockType(pt, 0);
- }
- } else {
- result = world.setBlock(pt, block, !fastMode);
- }
-
- return result;
- }
-
- @Override
- public boolean setBlock(Vector location, BaseBlock block, boolean notifyAdjacent) throws MaxChangedBlocksException {
- return setBlock(location, block);
- }
-
- /**
- * Sets the block at position x, y, z with a block type. If queue mode is
- * enabled, blocks may not be actually set in world until flushQueue() is
- * called.
- *
- * @param pt
- * @param block
- * @return Whether the block changed -- not entirely dependable
- * @throws MaxChangedBlocksException
- */
- public boolean setBlock(Vector pt, BaseBlock block)
- throws MaxChangedBlocksException {
- BlockVector blockPt = pt.toBlockVector();
-
- if (mask != null) {
- if (!mask.matches(this, blockPt)) {
- return false;
- }
- }
-
- if (maxBlocks != -1 && changeSet.size() > maxBlocks) {
- throw new MaxChangedBlocksException(maxBlocks);
- }
-
- changeSet.add(new BlockChange(blockPt, getBlock(pt), block));
-
- return smartSetBlock(pt, block);
+ public LocalWorld getWorld() {
+ return world;
}
/**
@@ -283,6 +164,340 @@ public class EditSession implements Extent {
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 changeLimiter.getLimit();
+ }
+
+ /**
+ * Set the maximum number of blocks that can be changed.
+ *
+ * @param limit the limit (>= 0) or -1 for no limit
+ */
+ public void setBlockChangeLimit(int limit) {
+ changeLimiter.setLimit(limit);
+ }
+
+ /**
+ * Returns queue status.
+ *
+ * @return whether the queue is enabled
+ */
+ public boolean isQueueEnabled() {
+ return reorderExtent.isEnabled();
+ }
+
+ /**
+ * Queue certain types of block for better reproduction of those blocks.
+ */
+ public void enableQueue() {
+ reorderExtent.setEnabled(true);
+ }
+
+ /**
+ * Disable the queue. This will flush the queue.
+ */
+ public void disableQueue() {
+ if (isQueueEnabled()) {
+ flushQueue();
+ }
+ reorderExtent.setEnabled(true);
+ }
+
+ /**
+ * Get the mask.
+ *
+ * @return mask, may be null
+ */
+ @SuppressWarnings("deprecation")
+ public Mask getMask() {
+ return oldMask;
+ }
+
+ /**
+ * Set a mask.
+ *
+ * @param mask mask or null
+ */
+ @SuppressWarnings("deprecation")
+ public void setMask(Mask mask) {
+ this.oldMask = mask;
+ if (mask == null) {
+ maskingExtent.setMask(Masks.alwaysTrue());
+ } else {
+ maskingExtent.setMask(Masks.wrap(this, mask));
+ }
+ }
+
+ /**
+ * Set whether fast mode is enabled.
+ *
+ * Fast mode may skip lighting checks or adjacent block notification.
+ *
+ * @param enabled true to enable
+ */
+ public void setFastMode(boolean enabled) {
+ fastModeExtent.setEnabled(enabled);
+ }
+
+ /**
+ * Return fast mode status.
+ *
+ * Fast mode may skip lighting checks or adjacent block notification.
+ *
+ * @return true if enabled
+ */
+ public boolean hasFastMode() {
+ return fastModeExtent.isEnabled();
+ }
+
+ /**
+ * Get the {@link BlockBag} is used.
+ *
+ * @return a block bag or null
+ */
+ public BlockBag getBlockBag() {
+ return blockBagExtent.getBlockBag();
+ }
+
+ /**
+ * Set a {@link BlockBag} to use.
+ *
+ * @param blockBag the block bag to set, or null to use none
+ */
+ public void setBlockBag(BlockBag blockBag) {
+ blockBagExtent.setBlockBag(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 blockBagExtent.popMissing();
+ }
+
+ /**
+ * 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 changeSet.size();
+ }
+
+ @Override
+ public BaseBlock getBlock(Vector position) {
+ return world.getBlock(position);
+ }
+
+ @Override
+ public int getBlockType(Vector position) {
+ return world.getBlockType(position);
+ }
+
+ @Override
+ public int getBlockData(Vector position) {
+ 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(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(int x, int z, int minY, 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(int x, int z, int minY, int maxY, boolean naturalOnly) {
+ for (int y = maxY; y >= minY; --y) {
+ Vector pt = new Vector(x, y, z);
+ int id = getBlockType(pt);
+ int data = getBlockData(pt);
+ if (naturalOnly ? BlockType.isNaturalTerrainBlock(id, data) : !BlockType.canPassThrough(id, data)) {
+ return y;
+ }
+ }
+
+ return minY;
+ }
+
+ /**
+ * Set a block, bypassing both history and block re-ordering.
+ *
+ * @param position the position to set the block at
+ * @param block the block
+ * @param notifyAdjacent true to notify adjacent
+ * @param level the level
+ * @return whether the block changed
+ */
+ public boolean setBlock(Vector position, BaseBlock block, boolean notifyAdjacent, Level level) throws WorldEditException {
+ switch (level) {
+ case NORMAL:
+ return bypassNone.setBlock(position, block, notifyAdjacent);
+ case NO_HISTORY_REORDER:
+ return bypassHistory.setBlock(position, block, notifyAdjacent);
+ case NO_HISTORY:
+ return bypassReorderHistory.setBlock(position, block, notifyAdjacent);
+ }
+
+ throw new RuntimeException("New enum entry added that is unhandled here");
+ }
+
+ @Override
+ public boolean setBlock(Vector location, BaseBlock block, boolean notifyAdjacent) throws WorldEditException {
+ return setBlock(location, block, notifyAdjacent, Level.NORMAL);
+ }
+
+ /**
+ * 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
+ * @deprecated Use {@link #setBlock(Vector, BaseBlock, boolean, Level)}
+ */
+ @Deprecated
+ public boolean rawSetBlock(Vector position, BaseBlock block) {
+ try {
+ return setBlock(position, block, true, Level.NO_HISTORY_REORDER);
+ } catch (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
+ * @deprecated Use {@link #setBlock(Vector, BaseBlock, boolean, Level)}
+ */
+ @Deprecated
+ public boolean smartSetBlock(Vector position, BaseBlock block) {
+ try {
+ return setBlock(position, block, true, Level.NO_HISTORY);
+ } catch (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 block the block
+ * @return Whether the block changed -- not entirely dependable
+ * @throws MaxChangedBlocksException thrown if too many blocks are changed
+ */
+ public boolean setBlock(Vector position, BaseBlock block) throws MaxChangedBlocksException {
+ try {
+ return setBlock(position, block, true, Level.NORMAL);
+ } catch (MaxChangedBlocksException e) {
+ throw e;
+ } catch (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(Vector position, 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(Set vset, Pattern pattern) throws MaxChangedBlocksException {
+ int affected = 0;
+ for (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
+ */
+ @SuppressWarnings("deprecation")
+ public boolean setChanceBlockIfAir(Vector position, BaseBlock block, double probability)
+ throws MaxChangedBlocksException {
+ return Math.random() <= probability && 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(Vector position, BaseBlock block) throws MaxChangedBlocksException {
+ return getBlock(position).isAir() && setBlock(position, block);
+ }
+
/**
* Insert a contrived block change into the history.
*
@@ -296,136 +511,10 @@ public class EditSession implements Extent {
changeSet.add(new BlockChange(position.toBlockVector(), existing, block));
}
- /**
- * Set a block with a pattern.
- *
- * @param pt
- * @param pat
- * @return Whether the block changed -- not entirely dependable
- * @throws MaxChangedBlocksException
- */
- public boolean setBlock(Vector pt, Pattern pat)
- throws MaxChangedBlocksException {
- return setBlock(pt, pat.next(pt));
- }
-
- /**
- * Set a block only if there's no block already there.
- *
- * @param pt
- * @param block
- * @return if block was changed
- * @throws MaxChangedBlocksException
- */
- public boolean setBlockIfAir(Vector pt, BaseBlock block)
- throws MaxChangedBlocksException {
- if (!getBlock(pt).isAir()) {
- return false;
- } else {
- return setBlock(pt, block);
- }
- }
-
- /**
- * Actually set the block. Will use queue.
- *
- * @param pt
- * @param block
- * @return
- */
- public boolean smartSetBlock(Vector pt, BaseBlock block) {
- if (queued) {
- if (BlockType.shouldPlaceLast(block.getType())) {
- // Place torches, etc. last
- queueLast.put(pt.toBlockVector(), block);
- return !(getBlockType(pt) == block.getType() && getBlockData(pt) == block.getData());
- } else if (BlockType.shouldPlaceFinal(block.getType())) {
- // Place signs, reed, etc even later
- queueFinal.put(pt.toBlockVector(), block);
- return !(getBlockType(pt) == block.getType() && getBlockData(pt) == block.getData());
- } else if (BlockType.shouldPlaceLast(getBlockType(pt))) {
- // Destroy torches, etc. first
- rawSetBlock(pt, new BaseBlock(BlockID.AIR));
- } else {
- queueAfter.put(pt.toBlockVector(), block);
- return !(getBlockType(pt) == block.getType() && getBlockData(pt) == block.getData());
- }
- }
-
- return rawSetBlock(pt, block);
- }
-
- /**
- * Gets the block type at a position x, y, z.
- *
- * @param pt
- * @return Block type
- */
- public BaseBlock getBlock(Vector pt) {
- // In the case of the queue, the block may have not actually been
- // changed yet
- if (queued) {
- /*
- * BlockVector blockPt = pt.toBlockVector();
- *
- * if (current.containsKey(blockPt)) { return current.get(blockPt);
- * }
- */
- }
-
- return rawGetBlock(pt);
- }
-
- /**
- * Gets the block type at a position x, y, z.
- *
- * @param pt
- * @return Block type
- */
- public int getBlockType(Vector pt) {
- // In the case of the queue, the block may have not actually been
- // changed yet
- if (queued) {
- /*
- * BlockVector blockPt = pt.toBlockVector();
- *
- * if (current.containsKey(blockPt)) { return current.get(blockPt);
- * }
- */
- }
-
- return world.getBlockType(pt);
- }
-
- public int getBlockData(Vector pt) {
- // In the case of the queue, the block may have not actually been
- // changed yet
- if (queued) {
- /*
- * BlockVector blockPt = pt.toBlockVector();
- *
- * if (current.containsKey(blockPt)) { return current.get(blockPt);
- * }
- */
- }
-
- return world.getBlockData(pt);
- }
-
- /**
- * Gets the block type at a position x, y, z.
- *
- * @param pt
- * @return BaseBlock
- */
- public BaseBlock rawGetBlock(Vector pt) {
- return world.getBlock(pt);
- }
-
/**
* Restores all blocks to their initial state.
*
- * @param sess
+ * @param sess a new {@link EditSession} to perform the undo in
*/
public void undo(EditSession sess) {
UndoContext context = new UndoContext();
@@ -437,7 +526,7 @@ public class EditSession implements Extent {
/**
* Sets to new state.
*
- * @param sess
+ * @param sess a new {@link EditSession} to perform the redo in
*/
public void redo(EditSession sess) {
UndoContext context = new UndoContext();
@@ -449,93 +538,22 @@ public class EditSession implements Extent {
/**
* Get the number of changed blocks.
*
- * @return
+ * @return the number of changes
*/
public int size() {
- return changeSet.size();
+ return getBlockChangeCount();
}
/**
- * Get the maximum number of blocks that can be changed. -1 will be returned
- * if disabled.
- *
- * @return block change limit
+ * Finish off the queue.
*/
- public int getBlockChangeLimit() {
- return maxBlocks;
+ public void flushQueue() {
+ OperationHelper.completeBlindly(commit());
}
- /**
- * Set the maximum number of blocks that can be changed.
- *
- * @param maxBlocks -1 to disable
- */
- public void setBlockChangeLimit(int maxBlocks) {
- if (maxBlocks < -1) {
- throw new IllegalArgumentException("Max blocks must be >= -1");
- }
- this.maxBlocks = maxBlocks;
- }
-
- /**
- * Returns queue status.
- *
- * @return whether the queue is enabled
- */
- public boolean isQueueEnabled() {
- return queued;
- }
-
- /**
- * Queue certain types of block for better reproduction of those blocks.
- */
- public void enableQueue() {
- queued = true;
- }
-
- /**
- * Disable the queue. This will flush the queue.
- */
- public void disableQueue() {
- if (queued) {
- flushQueue();
- }
- queued = false;
- }
-
- /**
- * Set fast mode.
- *
- * @param fastMode
- */
- public void setFastMode(boolean fastMode) {
- this.fastMode = fastMode;
- }
-
- /**
- * Return fast mode status.
- *
- * @return
- */
- public boolean hasFastMode() {
- return fastMode;
- }
-
- /**
- * Set a block by chance.
- *
- * @param pos
- * @param block
- * @param c 0-1 chance
- * @return whether a block was changed
- * @throws MaxChangedBlocksException
- */
- public boolean setChanceBlockIfAir(Vector pos, BaseBlock block, double c)
- throws MaxChangedBlocksException {
- if (Math.random() <= c) {
- return setBlockIfAir(pos, block);
- }
- return false;
+ @Override
+ public @Nullable Operation commit() {
+ return bypassNone.commit();
}
public int countBlock(Region region, Set searchIDs) {
@@ -562,225 +580,6 @@ public class EditSession implements Extent {
return count.getCount();
}
- /**
- * Returns the highest solid 'terrain' block which can occur naturally.
- *
- * @param x
- * @param z
- * @param minY minimal height
- * @param maxY maximal height
- * @return height of highest block found or 'minY'
- */
- public int getHighestTerrainBlock(int x, int z, int minY, int maxY) {
- return getHighestTerrainBlock(x, z, minY, maxY, false);
- }
-
- /**
- * Returns the highest solid 'terrain' block which can occur naturally.
- *
- * @param x
- * @param z
- * @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(int x, int z, int minY, int maxY, boolean naturalOnly) {
- for (int y = maxY; y >= minY; --y) {
- Vector pt = new Vector(x, y, z);
- int id = getBlockType(pt);
- int data = getBlockData(pt);
- if (naturalOnly ? BlockType.isNaturalTerrainBlock(id, data) : !BlockType.canPassThrough(id, data)) {
- return y;
- }
- }
- return minY;
- }
-
- /**
- * Gets the list of missing blocks and clears the list for the next
- * operation.
- *
- * @return
- */
- public Map popMissingBlocks() {
- Map missingBlocks = this.missingBlocks;
- this.missingBlocks = new HashMap();
- return missingBlocks;
- }
-
- /**
- * @return the blockBag
- */
- public BlockBag getBlockBag() {
- return blockBag;
- }
-
- /**
- * @param blockBag the blockBag to set
- */
- public void setBlockBag(BlockBag blockBag) {
- this.blockBag = blockBag;
- }
-
- /**
- * Get the world.
- *
- * @return
- */
- public LocalWorld getWorld() {
- return world;
- }
-
- /**
- * Get the number of blocks changed, including repeated block changes.
- *
- * @return
- */
- public int getBlockChangeCount() {
- return changeSet.size();
- }
-
- /**
- * Get the mask.
- *
- * @return mask, may be null
- */
- public Mask getMask() {
- return mask;
- }
-
- /**
- * Set a mask.
- *
- * @param mask mask or null
- */
- public void setMask(Mask mask) {
- this.mask = mask;
- }
-
- /**
- * Finish off the queue.
- */
- public void flushQueue() {
- if (!queued) {
- return;
- }
-
- final Set dirtyChunks = new HashSet();
-
- for (Map.Entry entry : queueAfter) {
- BlockVector pt = entry.getKey();
- rawSetBlock(pt, entry.getValue());
-
- // TODO: use ChunkStore.toChunk(pt) after optimizing it.
- if (fastMode) {
- dirtyChunks.add(new BlockVector2D(pt.getBlockX() >> 4, pt.getBlockZ() >> 4));
- }
- }
-
- // We don't want to place these blocks if other blocks were missing
- // because it might cause the items to drop
- if (blockBag == null || missingBlocks.size() == 0) {
- for (Map.Entry entry : queueLast) {
- BlockVector pt = entry.getKey();
- rawSetBlock(pt, entry.getValue());
-
- // TODO: use ChunkStore.toChunk(pt) after optimizing it.
- if (fastMode) {
- dirtyChunks.add(new BlockVector2D(pt.getBlockX() >> 4, pt.getBlockZ() >> 4));
- }
- }
-
- final Set blocks = new HashSet();
- final Map blockTypes = new HashMap();
- for (Map.Entry entry : queueFinal) {
- final BlockVector pt = entry.getKey();
- blocks.add(pt);
- blockTypes.put(pt, entry.getValue());
- }
-
- while (!blocks.isEmpty()) {
- BlockVector current = blocks.iterator().next();
- if (!blocks.contains(current)) {
- continue;
- }
-
- final Deque walked = new LinkedList();
-
- while (true) {
- walked.addFirst(current);
-
- assert(blockTypes.containsKey(current));
-
- final BaseBlock baseBlock = blockTypes.get(current);
-
- final int type = baseBlock.getType();
- final int data = baseBlock.getData();
-
- switch (type) {
- case BlockID.WOODEN_DOOR:
- case BlockID.IRON_DOOR:
- if ((data & 0x8) == 0) {
- // Deal with lower door halves being attached to the floor AND the upper half
- BlockVector upperBlock = current.add(0, 1, 0).toBlockVector();
- if (blocks.contains(upperBlock) && !walked.contains(upperBlock)) {
- walked.addFirst(upperBlock);
- }
- }
- break;
-
- case BlockID.MINECART_TRACKS:
- case BlockID.POWERED_RAIL:
- case BlockID.DETECTOR_RAIL:
- case BlockID.ACTIVATOR_RAIL:
- // Here, rails are hardcoded to be attached to the block below them.
- // They're also attached to the block they're ascending towards via BlockType.getAttachment.
- BlockVector lowerBlock = current.add(0, -1, 0).toBlockVector();
- if (blocks.contains(lowerBlock) && !walked.contains(lowerBlock)) {
- walked.addFirst(lowerBlock);
- }
- break;
- }
-
- final PlayerDirection attachment = BlockType.getAttachment(type, data);
- if (attachment == null) {
- // Block is not attached to anything => we can place it
- break;
- }
-
- current = current.add(attachment.vector()).toBlockVector();
-
- if (!blocks.contains(current)) {
- // We ran outside the remaing set => assume we can place blocks on this
- break;
- }
-
- if (walked.contains(current)) {
- // Cycle detected => This will most likely go wrong, but there's nothing we can do about it.
- break;
- }
- }
-
- for (BlockVector pt : walked) {
- rawSetBlock(pt, blockTypes.get(pt));
- blocks.remove(pt);
-
- // TODO: use ChunkStore.toChunk(pt) after optimizing it.
- if (fastMode) {
- dirtyChunks.add(new BlockVector2D(pt.getBlockX() >> 4, pt.getBlockZ() >> 4));
- }
- }
- }
- }
-
- if (!dirtyChunks.isEmpty()) world.fixAfterFastMode(dirtyChunks);
-
- queueAfter.clear();
- queueLast.clear();
- queueFinal.clear();
- }
-
/**
* Fills an area recursively in the X/Z directions.
*
@@ -1252,7 +1051,7 @@ public class EditSession implements Extent {
BlockReplace remove = new BlockReplace(this, pattern);
// Copy to a buffer so we don't destroy our original before we can copy all the blocks from it
- ExtentBuffer buffer = new ExtentBuffer(this, new RegionMask(region));
+ ForgetfulExtentBuffer buffer = new ForgetfulExtentBuffer(this, new RegionMask(region));
ForwardExtentCopy copy = new ForwardExtentCopy(this, region, buffer, to);
copy.setTransform(new AffineTransform().translate(dir.multiply(distance)));
copy.setSourceFunction(remove); // Remove
@@ -1562,14 +1361,6 @@ public class EditSession implements Extent {
return affected;
}
- private static final double lengthSq(double x, double y, double z) {
- return (x * x) + (y * y) + (z * z);
- }
-
- private static final double lengthSq(double x, double z) {
- return (x * x) + (z * z);
- }
-
/**
* Makes a pyramid.
*
@@ -2082,15 +1873,6 @@ public class EditSession implements Extent {
return affected;
}
- private static final Vector[] recurseDirections = {
- PlayerDirection.NORTH.vector(),
- PlayerDirection.EAST.vector(),
- PlayerDirection.SOUTH.vector(),
- PlayerDirection.WEST.vector(),
- PlayerDirection.UP.vector(),
- PlayerDirection.DOWN.vector(),
- };
-
/**
* Hollows out the region (Semi-well-defined for non-cuboid selections).
*
@@ -2331,16 +2113,6 @@ public class EditSession implements Extent {
return returnset;
}
- private int setBlocks(Set vset, Pattern pattern)
- throws MaxChangedBlocksException {
-
- int affected = 0;
- for (Vector v : vset) {
- affected += setBlock(v, pattern) ? 1 : 0;
- }
- return affected;
- }
-
private void recurseHollow(Region region, BlockVector origin, Set outside) {
final LinkedList queue = new LinkedList();
queue.addLast(origin);
@@ -2399,4 +2171,22 @@ public class EditSession implements Extent {
return shape.generate(this, biomeType, hollow);
}
+
+ private static final Vector[] recurseDirections = {
+ PlayerDirection.NORTH.vector(),
+ PlayerDirection.EAST.vector(),
+ PlayerDirection.SOUTH.vector(),
+ PlayerDirection.WEST.vector(),
+ PlayerDirection.UP.vector(),
+ PlayerDirection.DOWN.vector(),
+ };
+
+ private static final double lengthSq(double x, double y, double z) {
+ return (x * x) + (y * y) + (z * z);
+ }
+
+ private static final double lengthSq(double x, double z) {
+ return (x * x) + (z * z);
+ }
+
}
diff --git a/src/main/java/com/sk89q/worldedit/LocalWorld.java b/src/main/java/com/sk89q/worldedit/LocalWorld.java
index b2cf7638d..e5ed7edfb 100644
--- a/src/main/java/com/sk89q/worldedit/LocalWorld.java
+++ b/src/main/java/com/sk89q/worldedit/LocalWorld.java
@@ -25,9 +25,11 @@ import com.sk89q.worldedit.foundation.Block;
import com.sk89q.worldedit.foundation.World;
import com.sk89q.worldedit.function.mask.BlockMask;
import com.sk89q.worldedit.function.mask.Mask;
+import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.TreeGenerator;
+import javax.annotation.Nullable;
import java.util.PriorityQueue;
import java.util.Random;
@@ -618,4 +620,9 @@ public abstract class LocalWorld implements World, Extent {
new BaseBlock(BlockID.WATER, -1));
}
+ @Override
+ public @Nullable Operation commit() {
+ return null;
+ }
+
}
diff --git a/src/main/java/com/sk89q/worldedit/extent/BlockBagExtent.java b/src/main/java/com/sk89q/worldedit/extent/BlockBagExtent.java
new file mode 100644
index 000000000..734e6883c
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/extent/BlockBagExtent.java
@@ -0,0 +1,120 @@
+/*
+ * 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 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.extent;
+
+import com.sk89q.worldedit.LocalWorld;
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.WorldEditException;
+import com.sk89q.worldedit.bags.BlockBag;
+import com.sk89q.worldedit.bags.BlockBagException;
+import com.sk89q.worldedit.bags.UnplaceableBlockException;
+import com.sk89q.worldedit.blocks.BaseBlock;
+
+import javax.annotation.Nullable;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Applies a {@link BlockBag} to operations.
+ */
+public class BlockBagExtent extends ExtentDelegate {
+
+ private final LocalWorld world;
+ private Map missingBlocks = new HashMap();
+ private BlockBag blockBag;
+
+ /**
+ * Create a new instance.
+ *
+ * @param extent the extent
+ * @param world the world
+ * @param blockBag the block bag
+ */
+ public BlockBagExtent(Extent extent, LocalWorld world, @Nullable BlockBag blockBag) {
+ super(extent);
+ checkNotNull(world);
+ this.world = world;
+ this.blockBag = blockBag;
+ }
+
+ /**
+ * Get the block bag.
+ *
+ * @return a block bag, which may be null if none is used
+ */
+ public @Nullable BlockBag getBlockBag() {
+ return blockBag;
+ }
+
+ /**
+ * Set the block bag.
+ *
+ * @param blockBag a block bag, which may be null if none is used
+ */
+ public void setBlockBag(@Nullable 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 popMissing() {
+ Map missingBlocks = this.missingBlocks;
+ this.missingBlocks = new HashMap();
+ return missingBlocks;
+ }
+
+ @Override
+ public boolean setBlock(Vector location, BaseBlock block, boolean notifyAdjacent) throws WorldEditException {
+ if (blockBag != null) {
+ final int type = block.getType();
+ final int existing = world.getBlockType(location);
+
+ if (type > 0) {
+ try {
+ blockBag.fetchPlacedBlock(type, 0);
+ } catch (UnplaceableBlockException e) {
+ return false;
+ } catch (BlockBagException e) {
+ if (!missingBlocks.containsKey(type)) {
+ missingBlocks.put(type, 1);
+ } else {
+ missingBlocks.put(type, missingBlocks.get(type) + 1);
+ }
+ return false;
+ }
+ }
+
+ if (existing > 0) {
+ try {
+ blockBag.storeDroppedBlock(existing, world.getBlockData(location));
+ } catch (BlockBagException ignored) {
+ }
+ }
+ }
+
+ return super.setBlock(location, block, notifyAdjacent);
+ }
+}
diff --git a/src/main/java/com/sk89q/worldedit/extent/BlockChangeLimiter.java b/src/main/java/com/sk89q/worldedit/extent/BlockChangeLimiter.java
new file mode 100644
index 000000000..618ec535b
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/extent/BlockChangeLimiter.java
@@ -0,0 +1,87 @@
+/*
+ * 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 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.extent;
+
+import com.sk89q.worldedit.MaxChangedBlocksException;
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.WorldEditException;
+import com.sk89q.worldedit.blocks.BaseBlock;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Limits the number of blocks that can be changed before a
+ * {@link MaxChangedBlocksException} is thrown.
+ */
+public class BlockChangeLimiter extends ExtentDelegate {
+
+ private int limit;
+ private int count = 0;
+
+ /**
+ * Create a new instance.
+ *
+ * @param extent the extent
+ * @param limit the limit (>= 0) or -1 for no limit
+ */
+ public BlockChangeLimiter(Extent extent, int limit) {
+ super(extent);
+ setLimit(limit);
+ }
+
+ /**
+ * Get the limit.
+ *
+ * @return the limit (>= 0) or -1 for no limit
+ */
+ public int getLimit() {
+ return limit;
+ }
+
+ /**
+ * Set the limit.
+ *
+ * @param limit the limit (>= 0) or -1 for no limit
+ */
+ public void setLimit(int limit) {
+ checkArgument(limit >= -1, "limit >= -1 required");
+ this.limit = limit;
+ }
+
+ /**
+ * Get the number of blocks that have been counted so far.
+ *
+ * @return the number of blocks
+ */
+ public int getCount() {
+ return count;
+ }
+
+ @Override
+ public boolean setBlock(Vector location, BaseBlock block, boolean notifyAdjacent) throws WorldEditException {
+ if (limit >= 0) {
+ if (count >= limit) {
+ throw new MaxChangedBlocksException(limit);
+ }
+ count++;
+ }
+ return super.setBlock(location, block, notifyAdjacent);
+ }
+}
diff --git a/src/main/java/com/sk89q/worldedit/extent/BlockQuirkExtent.java b/src/main/java/com/sk89q/worldedit/extent/BlockQuirkExtent.java
new file mode 100644
index 000000000..961e976f6
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/extent/BlockQuirkExtent.java
@@ -0,0 +1,64 @@
+/*
+ * 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 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.extent;
+
+import com.sk89q.worldedit.LocalWorld;
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.WorldEditException;
+import com.sk89q.worldedit.blocks.BaseBlock;
+import com.sk89q.worldedit.blocks.BlockID;
+import com.sk89q.worldedit.blocks.BlockType;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Handles various quirks when setting blocks, such as ice turning
+ * into water or containers dropping their contents.
+ */
+public class BlockQuirkExtent extends ExtentDelegate {
+
+ private final LocalWorld world;
+
+ /**
+ * Create a new instance.
+ *
+ * @param extent the extent
+ * @param world the world
+ */
+ public BlockQuirkExtent(Extent extent, LocalWorld world) {
+ super(extent);
+ checkNotNull(world);
+ this.world = world;
+ }
+
+ @Override
+ public boolean setBlock(Vector location, BaseBlock block, boolean notifyAdjacent) throws WorldEditException {
+ final int existing = world.getBlockType(location);
+
+ if (BlockType.isContainerBlock(existing)) {
+ world.clearContainerBlockContents(location); // Clear the container block so that it doesn't drop items
+ } else if (existing == BlockID.ICE) {
+ world.setBlockType(location, BlockID.AIR); // Ice turns until water so this has to be done first
+ }
+
+ return super.setBlock(location, block, notifyAdjacent);
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/extent/ChangeSetExtent.java b/src/main/java/com/sk89q/worldedit/extent/ChangeSetExtent.java
new file mode 100644
index 000000000..5f5a9534c
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/extent/ChangeSetExtent.java
@@ -0,0 +1,56 @@
+/*
+ * 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 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.extent;
+
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.WorldEditException;
+import com.sk89q.worldedit.blocks.BaseBlock;
+import com.sk89q.worldedit.history.change.BlockChange;
+import com.sk89q.worldedit.history.changeset.ChangeSet;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Stores changes to a {@link ChangeSet}.
+ */
+public class ChangeSetExtent extends ExtentDelegate {
+
+ private final ChangeSet changeSet;
+
+ /**
+ * Create a new instance.
+ *
+ * @param extent the extent
+ * @param changeSet the change set
+ */
+ public ChangeSetExtent(Extent extent, ChangeSet changeSet) {
+ super(extent);
+ checkNotNull(changeSet);
+ this.changeSet = changeSet;
+ }
+
+ @Override
+ public boolean setBlock(Vector location, BaseBlock block, boolean notifyAdjacent) throws WorldEditException {
+ BaseBlock previous = getBlock(location);
+ changeSet.add(new BlockChange(location.toBlockVector(), previous, block));
+ return super.setBlock(location, block, notifyAdjacent);
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/extent/ChunkLoadingExtent.java b/src/main/java/com/sk89q/worldedit/extent/ChunkLoadingExtent.java
new file mode 100644
index 000000000..b1b41e44c
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/extent/ChunkLoadingExtent.java
@@ -0,0 +1,66 @@
+/*
+ * 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 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.extent;
+
+import com.sk89q.worldedit.LocalWorld;
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.WorldEditException;
+import com.sk89q.worldedit.blocks.BaseBlock;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Automatically loads chunks when blocks are accessed.
+ */
+public class ChunkLoadingExtent extends ExtentDelegate {
+
+ private final LocalWorld world;
+ private boolean enabled;
+
+ /**
+ * Create a new instance.
+ *
+ * @param extent the extent
+ * @param world the world
+ * @param enabled true to enable
+ */
+ public ChunkLoadingExtent(Extent extent, LocalWorld world, boolean enabled) {
+ super(extent);
+ checkNotNull(world);
+ this.enabled = enabled;
+ this.world = world;
+ }
+
+ /**
+ * Create a new instance with chunk loading enabled.
+ *
+ * @param extent the extent
+ * @param world the world
+ */
+ public ChunkLoadingExtent(Extent extent, LocalWorld world) {
+ this(extent, world, true);
+ }
+
+ @Override
+ public boolean setBlock(Vector location, BaseBlock block, boolean notifyAdjacent) throws WorldEditException {
+ world.checkLoadedChunk(location);
+ return super.setBlock(location, block, notifyAdjacent);
+ }
+}
diff --git a/src/main/java/com/sk89q/worldedit/extent/DataValidatorExtent.java b/src/main/java/com/sk89q/worldedit/extent/DataValidatorExtent.java
new file mode 100644
index 000000000..8a4ba1217
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/extent/DataValidatorExtent.java
@@ -0,0 +1,63 @@
+/*
+ * 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 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.extent;
+
+import com.sk89q.worldedit.LocalWorld;
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.WorldEditException;
+import com.sk89q.worldedit.blocks.BaseBlock;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Validates set data to prevent creating invalid blocks and such.
+ */
+public class DataValidatorExtent extends ExtentDelegate {
+
+ private final LocalWorld world;
+
+ /**
+ * Create a new instance.
+ *
+ * @param extent the extent
+ * @param world the world
+ */
+ public DataValidatorExtent(Extent extent, LocalWorld world) {
+ super(extent);
+ checkNotNull(world);
+ this.world = world;
+ }
+
+ @Override
+ public boolean setBlock(Vector location, BaseBlock block, boolean notifyAdjacent) throws WorldEditException {
+ final int y = location.getBlockY();
+ final int type = block.getType();
+ if (y < 0 || y > world.getMaxY()) {
+ return false;
+ }
+
+ // No invalid blocks
+ if (!world.isValidBlockType(type)) {
+ return false;
+ }
+
+ return super.setBlock(location, block, notifyAdjacent);
+ }
+}
diff --git a/src/main/java/com/sk89q/worldedit/extent/Extent.java b/src/main/java/com/sk89q/worldedit/extent/Extent.java
index 6047f1e41..066a62ada 100644
--- a/src/main/java/com/sk89q/worldedit/extent/Extent.java
+++ b/src/main/java/com/sk89q/worldedit/extent/Extent.java
@@ -22,6 +22,9 @@ package com.sk89q.worldedit.extent;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock;
+import com.sk89q.worldedit.function.operation.Operation;
+
+import javax.annotation.Nullable;
/**
* A world, portion of a world, clipboard, or other object that can have blocks set or
@@ -76,4 +79,12 @@ public interface Extent {
*/
boolean setBlock(Vector location, BaseBlock block, boolean notifyAdjacent) throws WorldEditException;
+ /**
+ * Return an {@link Operation} that should be called to tie up loose ends
+ * (such as to commit changes in a buffer).
+ *
+ * @return an operation or null if there is none to execute
+ */
+ @Nullable Operation commit();
+
}
diff --git a/src/main/java/com/sk89q/worldedit/extent/ExtentDelegate.java b/src/main/java/com/sk89q/worldedit/extent/ExtentDelegate.java
new file mode 100644
index 000000000..8a23b1209
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/extent/ExtentDelegate.java
@@ -0,0 +1,96 @@
+/*
+ * 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 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.extent;
+
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.WorldEditException;
+import com.sk89q.worldedit.blocks.BaseBlock;
+import com.sk89q.worldedit.function.operation.Operation;
+import com.sk89q.worldedit.function.operation.OperationQueue;
+
+import javax.annotation.Nullable;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A base class for {@link Extent}s that merely passes extents onto another.
+ */
+public class ExtentDelegate implements Extent {
+
+ private final Extent extent;
+
+ /**
+ * Create a new instance.
+ *
+ * @param extent the extent
+ */
+ public ExtentDelegate(Extent extent) {
+ checkNotNull(extent);
+ this.extent = extent;
+ }
+
+ /**
+ * Get the extent.
+ *
+ * @return the extent
+ */
+ public Extent getExtent() {
+ return extent;
+ }
+
+ @Override
+ public BaseBlock getBlock(Vector location) {
+ return extent.getBlock(location);
+ }
+
+ @Override
+ public int getBlockType(Vector location) {
+ return extent.getBlockType(location);
+ }
+
+ @Override
+ public int getBlockData(Vector location) {
+ return extent.getBlockData(location);
+ }
+
+ @Override
+ public boolean setBlock(Vector location, BaseBlock block, boolean notifyAdjacent) throws WorldEditException {
+ return extent.setBlock(location, block, notifyAdjacent);
+ }
+
+ protected Operation commitBefore() {
+ return null;
+ }
+
+ @Override
+ public final @Nullable Operation commit() {
+ Operation ours = commitBefore();
+ Operation other = extent.commit();
+ if (ours != null && other != null) {
+ return new OperationQueue(ours, other);
+ } else if (ours != null) {
+ return ours;
+ } else if (other != null) {
+ return other;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/com/sk89q/worldedit/extent/FastModeExtent.java b/src/main/java/com/sk89q/worldedit/extent/FastModeExtent.java
new file mode 100644
index 000000000..1a2e7cada
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/extent/FastModeExtent.java
@@ -0,0 +1,108 @@
+/*
+ * 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 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.extent;
+
+import com.sk89q.worldedit.BlockVector2D;
+import com.sk89q.worldedit.LocalWorld;
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.WorldEditException;
+import com.sk89q.worldedit.blocks.BaseBlock;
+import com.sk89q.worldedit.function.operation.Operation;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implements "fast mode" which may skip physics, lighting, etc.
+ */
+public class FastModeExtent extends ExtentDelegate {
+
+ private final LocalWorld world;
+ private final Set dirtyChunks = new HashSet();
+ private boolean enabled = true;
+
+ /**
+ * Create a new instance with fast mode enabled.
+ *
+ * @param world the world
+ */
+ public FastModeExtent(LocalWorld world) {
+ this(world, true);
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param world the world
+ * @param enabled true to enable fast mode
+ */
+ public FastModeExtent(LocalWorld world, boolean enabled) {
+ super(world);
+ checkNotNull(world);
+ this.world = world;
+ this.enabled = enabled;
+ }
+
+ /**
+ * Return whether fast mode is enabled.
+ *
+ * @return true if fast mode is enabled
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * Set fast mode enable status.
+ *
+ * @param enabled true to enable fast mode
+ */
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ @Override
+ public boolean setBlock(Vector location, BaseBlock block, boolean notifyAdjacent) throws WorldEditException {
+ dirtyChunks.add(new BlockVector2D(location.getBlockX() >> 4, location.getBlockZ() >> 4));
+ return super.setBlock(location, block, notifyAdjacent || enabled);
+ }
+
+ @Override
+ protected Operation commitBefore() {
+ if (dirtyChunks.size() > 0) {
+ return new Operation() {
+ @Override
+ public Operation resume() throws WorldEditException {
+ world.fixAfterFastMode(dirtyChunks);
+ return null;
+ }
+
+ @Override
+ public void cancel() {
+ }
+ };
+ } else {
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/extent/ExtentBuffer.java b/src/main/java/com/sk89q/worldedit/extent/ForgetfulExtentBuffer.java
similarity index 86%
rename from src/main/java/com/sk89q/worldedit/extent/ExtentBuffer.java
rename to src/main/java/com/sk89q/worldedit/extent/ForgetfulExtentBuffer.java
index e13594169..fe1322c2e 100644
--- a/src/main/java/com/sk89q/worldedit/extent/ExtentBuffer.java
+++ b/src/main/java/com/sk89q/worldedit/extent/ForgetfulExtentBuffer.java
@@ -38,14 +38,16 @@ import java.util.Map;
import static com.google.common.base.Preconditions.checkNotNull;
/**
- * Buffers changes to an {@link Extent} and allows later retrieval of buffered
- * changes for actual setting.
+ * Buffers changes to an {@link Extent} and allows later retrieval for
+ * actual application of the changes.
+ *
+ * This buffer will not attempt to return results from the buffer when
+ * accessor methods (such as {@link #getBlock(Vector)}) are called.
*/
-public class ExtentBuffer implements Extent, Pattern {
+public class ForgetfulExtentBuffer extends ExtentDelegate implements Pattern {
private static final BaseBlock AIR = new BaseBlock(BlockID.AIR);
- private final Extent delegate;
private final Map buffer = new LinkedHashMap();
private final Mask mask;
private Vector min = null;
@@ -56,7 +58,7 @@ public class ExtentBuffer implements Extent, Pattern {
*
* @param delegate the delegate extent for {@link Extent#getBlock(Vector)}, etc. calls
*/
- public ExtentBuffer(Extent delegate) {
+ public ForgetfulExtentBuffer(Extent delegate) {
this(delegate, Masks.alwaysTrue());
}
@@ -67,28 +69,13 @@ public class ExtentBuffer implements Extent, Pattern {
* @param delegate the delegate extent for {@link Extent#getBlock(Vector)}, etc. calls
* @param mask the mask
*/
- public ExtentBuffer(Extent delegate, Mask mask) {
+ public ForgetfulExtentBuffer(Extent delegate, Mask mask) {
+ super(delegate);
checkNotNull(delegate);
checkNotNull(mask);
- this.delegate = delegate;
this.mask = mask;
}
- @Override
- public BaseBlock getBlock(Vector location) {
- return delegate.getBlock(location);
- }
-
- @Override
- public int getBlockType(Vector location) {
- return delegate.getBlockType(location);
- }
-
- @Override
- public int getBlockData(Vector location) {
- return delegate.getBlockData(location);
- }
-
@Override
public boolean setBlock(Vector location, BaseBlock block, boolean notifyAdjacent) throws WorldEditException {
// Update minimum
@@ -110,7 +97,7 @@ public class ExtentBuffer implements Extent, Pattern {
buffer.put(blockVector, block);
return true;
} else {
- return delegate.setBlock(location, block, notifyAdjacent);
+ return getExtent().setBlock(location, block, notifyAdjacent);
}
}
diff --git a/src/main/java/com/sk89q/worldedit/extent/MaskingExtent.java b/src/main/java/com/sk89q/worldedit/extent/MaskingExtent.java
new file mode 100644
index 000000000..1571258f2
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/extent/MaskingExtent.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 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.extent;
+
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.WorldEditException;
+import com.sk89q.worldedit.blocks.BaseBlock;
+import com.sk89q.worldedit.function.mask.Mask;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class MaskingExtent extends ExtentDelegate {
+
+ private Mask mask;
+
+ /**
+ * Create a new instance.
+ *
+ * @param extent the extent
+ * @param mask the mask
+ */
+ public MaskingExtent(Extent extent, Mask mask) {
+ super(extent);
+ checkNotNull(mask);
+ this.mask = mask;
+ }
+
+ /**
+ * Get the mask.
+ *
+ * @return the mask
+ */
+ public Mask getMask() {
+ return mask;
+ }
+
+ /**
+ * Set a mask.
+ *
+ * @param mask a mask
+ */
+ public void setMask(Mask mask) {
+ checkNotNull(mask);
+ this.mask = mask;
+ }
+
+ @Override
+ public boolean setBlock(Vector location, BaseBlock block, boolean notifyAdjacent) throws WorldEditException {
+ if (mask.test(location)) {
+ return super.setBlock(location, block, notifyAdjacent);
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/extent/reorder/SimpleBlockReorder.java b/src/main/java/com/sk89q/worldedit/extent/reorder/SimpleBlockReorder.java
new file mode 100644
index 000000000..e3d7fd266
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/extent/reorder/SimpleBlockReorder.java
@@ -0,0 +1,212 @@
+/*
+ * 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 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.extent.reorder;
+
+import com.google.common.collect.Iterators;
+import com.sk89q.worldedit.*;
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.blocks.BaseBlock;
+import com.sk89q.worldedit.blocks.BlockID;
+import com.sk89q.worldedit.blocks.BlockType;
+import com.sk89q.worldedit.extent.ExtentDelegate;
+import com.sk89q.worldedit.extent.Extent;
+import com.sk89q.worldedit.function.operation.Operation;
+import com.sk89q.worldedit.function.operation.OperationQueue;
+import com.sk89q.worldedit.function.visitor.BlockMapEntryVisitor;
+
+import java.util.*;
+
+/**
+ * Re-orders blocks into several stages.
+ */
+public class SimpleBlockReorder extends ExtentDelegate {
+
+ private DoubleArrayList stage1 = new DoubleArrayList(false);
+ private DoubleArrayList stage2 = new DoubleArrayList(false);
+ private DoubleArrayList stage3 = new DoubleArrayList(false);
+ private boolean enabled;
+
+ /**
+ * Create a new instance.
+ *
+ * @param extent the extent
+ * @param enabled true to enable
+ */
+ public SimpleBlockReorder(Extent extent, boolean enabled) {
+ super(extent);
+ this.enabled = enabled;
+ }
+
+ /**
+ * Create a new instance when the re-ordering is enabled.
+ *
+ * @param extent the extent
+ */
+ public SimpleBlockReorder(Extent extent) {
+ this(extent, true);
+ }
+
+ /**
+ * Return whether re-ordering is enabled.
+ *
+ * @return true if re-ordering is enabled
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * Set whether re-ordering is enabled.
+ *
+ * @param enabled true if re-ordering is enabled
+ */
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ @Override
+ public boolean setBlock(Vector location, BaseBlock block, boolean notifyAdjacent) throws WorldEditException {
+ if (!enabled) {
+ return super.setBlock(location, block, notifyAdjacent);
+ }
+
+ if (BlockType.shouldPlaceLast(block.getType())) {
+ // Place torches, etc. last
+ stage2.put(location.toBlockVector(), block);
+ return !(getBlockType(location) == block.getType() && getBlockData(location) == block.getData());
+ } else if (BlockType.shouldPlaceFinal(block.getType())) {
+ // Place signs, reed, etc even later
+ stage3.put(location.toBlockVector(), block);
+ return !(getBlockType(location) == block.getType() && getBlockData(location) == block.getData());
+ } else if (BlockType.shouldPlaceLast(getBlockType(location))) {
+ // Destroy torches, etc. first
+ super.setBlock(location, new BaseBlock(BlockID.AIR), notifyAdjacent);
+ return super.setBlock(location, block, notifyAdjacent);
+ } else {
+ stage1.put(location.toBlockVector(), block);
+ return !(getBlockType(location) == block.getType() && getBlockData(location) == block.getData());
+ }
+ }
+
+ @Override
+ public Operation commitBefore() {
+ return new OperationQueue(
+ new BlockMapEntryVisitor(
+ getExtent(),
+ Iterators.concat(stage1.iterator(), stage2.iterator())),
+ new Stage3Committer());
+ }
+
+ private class Stage3Committer implements Operation {
+
+ @Override
+ public Operation resume() throws WorldEditException {
+ Extent extent = getExtent();
+
+ final Set blocks = new HashSet();
+ final Map blockTypes = new HashMap();
+ for (Map.Entry entry : stage3) {
+ final BlockVector pt = entry.getKey();
+ blocks.add(pt);
+ blockTypes.put(pt, entry.getValue());
+ }
+
+ while (!blocks.isEmpty()) {
+ BlockVector current = blocks.iterator().next();
+ if (!blocks.contains(current)) {
+ continue;
+ }
+
+ final Deque walked = new LinkedList();
+
+ while (true) {
+ walked.addFirst(current);
+
+ assert (blockTypes.containsKey(current));
+
+ final BaseBlock baseBlock = blockTypes.get(current);
+
+ final int type = baseBlock.getType();
+ final int data = baseBlock.getData();
+
+ switch (type) {
+ case BlockID.WOODEN_DOOR:
+ case BlockID.IRON_DOOR:
+ if ((data & 0x8) == 0) {
+ // Deal with lower door halves being attached to the floor AND the upper half
+ BlockVector upperBlock = current.add(0, 1, 0).toBlockVector();
+ if (blocks.contains(upperBlock) && !walked.contains(upperBlock)) {
+ walked.addFirst(upperBlock);
+ }
+ }
+ break;
+
+ case BlockID.MINECART_TRACKS:
+ case BlockID.POWERED_RAIL:
+ case BlockID.DETECTOR_RAIL:
+ case BlockID.ACTIVATOR_RAIL:
+ // Here, rails are hardcoded to be attached to the block below them.
+ // They're also attached to the block they're ascending towards via BlockType.getAttachment.
+ BlockVector lowerBlock = current.add(0, -1, 0).toBlockVector();
+ if (blocks.contains(lowerBlock) && !walked.contains(lowerBlock)) {
+ walked.addFirst(lowerBlock);
+ }
+ break;
+ }
+
+ final PlayerDirection attachment = BlockType.getAttachment(type, data);
+ if (attachment == null) {
+ // Block is not attached to anything => we can place it
+ break;
+ }
+
+ current = current.add(attachment.vector()).toBlockVector();
+
+ if (!blocks.contains(current)) {
+ // We ran outside the remaing set => assume we can place blocks on this
+ break;
+ }
+
+ if (walked.contains(current)) {
+ // Cycle detected => This will most likely go wrong, but there's nothing we can do about it.
+ break;
+ }
+ }
+
+ for (BlockVector pt : walked) {
+ extent.setBlock(pt, blockTypes.get(pt), true);
+ blocks.remove(pt);
+ }
+ }
+
+ stage1.clear();
+ stage2.clear();
+ stage3.clear();
+
+ return null;
+ }
+
+ @Override
+ public void cancel() {
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/function/visitor/BlockMapEntryVisitor.java b/src/main/java/com/sk89q/worldedit/function/visitor/BlockMapEntryVisitor.java
new file mode 100644
index 000000000..08324b118
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/function/visitor/BlockMapEntryVisitor.java
@@ -0,0 +1,58 @@
+/*
+ * 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 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.function.visitor;
+
+import com.sk89q.worldedit.BlockVector;
+import com.sk89q.worldedit.WorldEditException;
+import com.sk89q.worldedit.blocks.BaseBlock;
+import com.sk89q.worldedit.extent.Extent;
+import com.sk89q.worldedit.function.operation.Operation;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class BlockMapEntryVisitor implements Operation {
+
+ private final Extent extent;
+ private final Iterator> iterator;
+
+ public BlockMapEntryVisitor(Extent extent, Iterator> iterator) {
+ checkNotNull(extent);
+ checkNotNull(iterator);
+ this.extent = extent;
+ this.iterator = iterator;
+ }
+
+ @Override
+ public Operation resume() throws WorldEditException {
+ while (iterator.hasNext()) {
+ Map.Entry entry = iterator.next();
+ extent.setBlock(entry.getKey(), entry.getValue(), true);
+ }
+
+ return null;
+ }
+
+ @Override
+ public void cancel() {
+ }
+}