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() { + } +}