Plex-FAWE/src/main/java/com/sk89q/worldedit/EditSession.java
sk89q 09f3364d9f Moved the block data check to LocalWorld, adding preliminary support for custom blocks.
All unknown blocks are now assumed to carry data, and so this will also future proof WorldEdit (for the immediate future). However, be aware that full custom block support requires carrying TileEntity data (and perhaps other data) and that is not yet supported.
2012-02-05 17:47:09 -08:00

2887 lines
88 KiB
Java

// $Id$
/*
* WorldEditLibrary
* Copyright (C) 2010 sk89q <http://www.sk89q.com> 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Map;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.Stack;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import com.sk89q.worldedit.regions.*;
import com.sk89q.worldedit.util.TreeGenerator;
import com.sk89q.worldedit.bags.*;
import com.sk89q.worldedit.blocks.*;
import com.sk89q.worldedit.expression.Expression;
import com.sk89q.worldedit.expression.ExpressionException;
import com.sk89q.worldedit.expression.runtime.RValue;
import com.sk89q.worldedit.masks.Mask;
import com.sk89q.worldedit.patterns.*;
/**
* 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
*/
public class EditSession {
/**
* Random number generator.
*/
private static Random prng = new Random();
/**
* World.
*/
protected LocalWorld world;
/**
* Stores the original blocks before modification.
*/
private DoubleArrayList<BlockVector, BaseBlock> original =
new DoubleArrayList<BlockVector, BaseBlock>(true);
/**
* Stores the current blocks.
*/
private DoubleArrayList<BlockVector, BaseBlock> current =
new DoubleArrayList<BlockVector, BaseBlock>(false);
/**
* Blocks that should be placed before last.
*/
private DoubleArrayList<BlockVector, BaseBlock> queueAfter =
new DoubleArrayList<BlockVector, BaseBlock>(false);
/**
* Blocks that should be placed last.
*/
private DoubleArrayList<BlockVector, BaseBlock> queueLast =
new DoubleArrayList<BlockVector, BaseBlock>(false);
/**
* Blocks that should be placed after all other blocks.
*/
private DoubleArrayList<BlockVector, BaseBlock> queueFinal =
new DoubleArrayList<BlockVector, BaseBlock>(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 Set<Integer> missingBlocks = new HashSet<Integer>();
/**
* Mask to cover operations.
*/
private Mask mask;
/**
* Construct the object with a maximum number of blocks.
*
* @param world
* @param maxBlocks
*/
public EditSession(LocalWorld world, int maxBlocks) {
if (maxBlocks < -1) {
throw new IllegalArgumentException("Max blocks must be >= -1");
}
this.maxBlocks = maxBlocks;
this.world = world;
}
/**
* Construct the object with a maximum number of blocks and a block bag.
*
* @param world
* @param maxBlocks
* @param blockBag
* @blockBag
*/
public EditSession(LocalWorld world, int maxBlocks, BlockBag blockBag) {
if (maxBlocks < -1) {
throw new IllegalArgumentException("Max blocks must be >= -1");
}
this.maxBlocks = maxBlocks;
this.blockBag = blockBag;
this.world = world;
}
/**
* Sets a block without changing history.
*
* @param pt
* @param block
* @return Whether the block changed
*/
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;
}
if (mask != null) {
if (!mask.matches(this, pt)) {
return false;
}
}
final int existing = world.getBlockType(pt);
// Clear the container block so that it doesn't drop items
if (BlockType.isContainerBlock(existing) && blockBag == null) {
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) {
missingBlocks.add(type);
return false;
}
}
if (existing > 0) {
try {
blockBag.storeDroppedBlock(existing, world.getBlockData(pt));
} catch (BlockBagException e) {
}
}
}
final boolean result;
if (world.usesBlockData(type)) {
if (fastMode) {
result = world.setTypeIdAndDataFast(pt, type, block.getData() > -1 ? block.getData() : 0);
} else {
result = world.setTypeIdAndData(pt, type, block.getData() > -1 ? block.getData() : 0);
}
} else {
if (fastMode) {
result = world.setBlockTypeFast(pt, type);
} else {
result = world.setBlockType(pt, type);
}
}
//System.out.println(pt + "" +result);
if (type != 0) {
if (block instanceof ContainerBlock) {
if (blockBag == null) {
world.copyToWorld(pt, block);
}
} else if (block instanceof TileEntityBlock) {
world.copyToWorld(pt, block);
}
}
return result;
}
/**
* 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 (!original.containsKey(blockPt)) {
original.put(blockPt, getBlock(pt));
if (maxBlocks != -1 && original.size() > maxBlocks) {
throw new MaxChangedBlocksException(maxBlocks);
}
// }
current.put(pt.toBlockVector(), block);
return smartSetBlock(pt, block);
}
/**
* Insert a contrived block change into the history.
*
* @param pt
* @param existing
* @param block
*/
public void rememberChange(Vector pt, BaseBlock existing, BaseBlock block) {
BlockVector blockPt = pt.toBlockVector();
original.put(blockPt, existing);
current.put(pt.toBlockVector(), 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) {
world.checkLoadedChunk(pt);
int type = world.getBlockType(pt);
int data = world.getBlockData(pt);
switch (type) {
case BlockID.WALL_SIGN:
case BlockID.SIGN_POST: {
SignBlock block = new SignBlock(type, data);
world.copyFromWorld(pt, block);
return block;
}
case BlockID.CHEST: {
ChestBlock block = new ChestBlock(data);
world.copyFromWorld(pt, block);
return block;
}
case BlockID.FURNACE:
case BlockID.BURNING_FURNACE: {
FurnaceBlock block = new FurnaceBlock(type, data);
world.copyFromWorld(pt, block);
return block;
}
case BlockID.DISPENSER: {
DispenserBlock block = new DispenserBlock(data);
world.copyFromWorld(pt, block);
return block;
}
case BlockID.MOB_SPAWNER: {
MobSpawnerBlock block = new MobSpawnerBlock(data);
world.copyFromWorld(pt, block);
return block;
}
case BlockID.NOTE_BLOCK: {
NoteBlock block = new NoteBlock(data);
world.copyFromWorld(pt, block);
return block;
}
default:
return new BaseBlock(type, data);
}
}
/**
* Restores all blocks to their initial state.
*
* @param sess
*/
public void undo(EditSession sess) {
for (Map.Entry<BlockVector, BaseBlock> entry : original) {
BlockVector pt = (BlockVector) entry.getKey();
sess.smartSetBlock(pt, (BaseBlock) entry.getValue());
}
sess.flushQueue();
}
/**
* Sets to new state.
*
* @param sess
*/
public void redo(EditSession sess) {
for (Map.Entry<BlockVector, BaseBlock> entry : current) {
BlockVector pt = (BlockVector) entry.getKey();
sess.smartSetBlock(pt, (BaseBlock) entry.getValue());
}
sess.flushQueue();
}
/**
* Get the number of changed blocks.
*
* @return
*/
public int size() {
return original.size();
}
/**
* Get the maximum number of blocks that can be changed. -1 will be returned
* if disabled.
*
* @return block change limit
*/
public int getBlockChangeLimit() {
return maxBlocks;
}
/**
* 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;
}
/**
* Count the number of blocks of a list of types in a region.
*
* @param region
* @param searchIDs
* @return
*/
public int countBlocks(Region region, Set<Integer> searchIDs) {
int count = 0;
if (region instanceof CuboidRegion) {
// Doing this for speed
Vector min = region.getMinimumPoint();
Vector max = region.getMaximumPoint();
int minX = min.getBlockX();
int minY = min.getBlockY();
int minZ = min.getBlockZ();
int maxX = max.getBlockX();
int maxY = max.getBlockY();
int maxZ = max.getBlockZ();
for (int x = minX; x <= maxX; ++x) {
for (int y = minY; y <= maxY; ++y) {
for (int z = minZ; z <= maxZ; ++z) {
Vector pt = new Vector(x, y, z);
if (searchIDs.contains(getBlockType(pt))) {
++count;
}
}
}
}
} else {
for (Vector pt : region) {
if (searchIDs.contains(getBlockType(pt))) {
++count;
}
}
}
return count;
}
/**
* 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) {
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);
if (naturalOnly ? BlockType.isNaturalTerrainBlock(id) : !BlockType.canPassThrough(id)) {
return y;
}
}
return minY;
}
/**
* Gets the list of missing blocks and clears the list for the next
* operation.
*
* @return
*/
public Set<Integer> popMissingBlocks() {
Set<Integer> missingBlocks = this.missingBlocks;
this.missingBlocks = new HashSet<Integer>();
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 original.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<BlockVector2D> dirtyChunks = new HashSet<BlockVector2D>();
for (Map.Entry<BlockVector, BaseBlock> entry : queueAfter) {
BlockVector pt = (BlockVector) entry.getKey();
rawSetBlock(pt, (BaseBlock) 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<BlockVector, BaseBlock> entry : queueLast) {
BlockVector pt = (BlockVector) entry.getKey();
rawSetBlock(pt, (BaseBlock) entry.getValue());
// TODO: use ChunkStore.toChunk(pt) after optimizing it.
if (fastMode) {
dirtyChunks.add(new BlockVector2D(pt.getBlockX() >> 4, pt.getBlockZ() >> 4));
}
}
final Set<BlockVector> blocks = new HashSet<BlockVector>();
final Map<BlockVector, BaseBlock> blockTypes = new HashMap<BlockVector, BaseBlock>();
for (Map.Entry<BlockVector, BaseBlock> 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<BlockVector> walked = new LinkedList<BlockVector>();
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);
}
}
}
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.
*
* @param origin
* @param block
* @param radius
* @param depth
* @param recursive
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int fillXZ(Vector origin, BaseBlock block, double radius, int depth,
boolean recursive) throws MaxChangedBlocksException {
int affected = 0;
int originX = origin.getBlockX();
int originY = origin.getBlockY();
int originZ = origin.getBlockZ();
HashSet<BlockVector> visited = new HashSet<BlockVector>();
Stack<BlockVector> queue = new Stack<BlockVector>();
queue.push(new BlockVector(originX, originY, originZ));
while (!queue.empty()) {
BlockVector pt = queue.pop();
int cx = pt.getBlockX();
int cy = pt.getBlockY();
int cz = pt.getBlockZ();
if (cy < 0 || cy > originY || visited.contains(pt)) {
continue;
}
visited.add(pt);
if (recursive) {
if (origin.distance(pt) > radius) {
continue;
}
if (getBlock(pt).isAir()) {
if (setBlock(pt, block)) {
++affected;
}
} else {
continue;
}
queue.push(new BlockVector(cx, cy - 1, cz));
queue.push(new BlockVector(cx, cy + 1, cz));
} else {
double dist = Math.sqrt(Math.pow(originX - cx, 2)
+ Math.pow(originZ - cz, 2));
int minY = originY - depth + 1;
if (dist > radius) {
continue;
}
if (getBlock(pt).isAir()) {
affected += fillY(cx, originY, cz, block, minY);
} else {
continue;
}
}
queue.push(new BlockVector(cx + 1, cy, cz));
queue.push(new BlockVector(cx - 1, cy, cz));
queue.push(new BlockVector(cx, cy, cz + 1));
queue.push(new BlockVector(cx, cy, cz - 1));
}
return affected;
}
/**
* Recursively fills a block and below until it hits another block.
*
* @param x
* @param cy
* @param z
* @param block
* @param minY
* @throws MaxChangedBlocksException
* @return
*/
private int fillY(int x, int cy, int z, BaseBlock block, int minY)
throws MaxChangedBlocksException {
int affected = 0;
for (int y = cy; y >= minY; --y) {
Vector pt = new Vector(x, y, z);
if (getBlock(pt).isAir()) {
setBlock(pt, block);
++affected;
} else {
break;
}
}
return affected;
}
/**
* Fills an area recursively in the X/Z directions.
*
* @param origin
* @param pattern
* @param radius
* @param depth
* @param recursive
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int fillXZ(Vector origin, Pattern pattern, double radius, int depth,
boolean recursive) throws MaxChangedBlocksException {
int affected = 0;
int originX = origin.getBlockX();
int originY = origin.getBlockY();
int originZ = origin.getBlockZ();
HashSet<BlockVector> visited = new HashSet<BlockVector>();
Stack<BlockVector> queue = new Stack<BlockVector>();
queue.push(new BlockVector(originX, originY, originZ));
while (!queue.empty()) {
BlockVector pt = queue.pop();
int cx = pt.getBlockX();
int cy = pt.getBlockY();
int cz = pt.getBlockZ();
if (cy < 0 || cy > originY || visited.contains(pt)) {
continue;
}
visited.add(pt);
if (recursive) {
if (origin.distance(pt) > radius) {
continue;
}
if (getBlock(pt).isAir()) {
if (setBlock(pt, pattern.next(pt))) {
++affected;
}
} else {
continue;
}
queue.push(new BlockVector(cx, cy - 1, cz));
queue.push(new BlockVector(cx, cy + 1, cz));
} else {
double dist = Math.sqrt(Math.pow(originX - cx, 2)
+ Math.pow(originZ - cz, 2));
int minY = originY - depth + 1;
if (dist > radius) {
continue;
}
if (getBlock(pt).isAir()) {
affected += fillY(cx, originY, cz, pattern, minY);
} else {
continue;
}
}
queue.push(new BlockVector(cx + 1, cy, cz));
queue.push(new BlockVector(cx - 1, cy, cz));
queue.push(new BlockVector(cx, cy, cz + 1));
queue.push(new BlockVector(cx, cy, cz - 1));
}
return affected;
}
/**
* Recursively fills a block and below until it hits another block.
*
* @param x
* @param cy
* @param z
* @param pattern
* @param minY
* @throws MaxChangedBlocksException
* @return
*/
private int fillY(int x, int cy, int z, Pattern pattern, int minY)
throws MaxChangedBlocksException {
int affected = 0;
for (int y = cy; y >= minY; --y) {
Vector pt = new Vector(x, y, z);
if (getBlock(pt).isAir()) {
setBlock(pt, pattern.next(pt));
++affected;
} else {
break;
}
}
return affected;
}
/**
* Remove blocks above.
*
* @param pos
* @param size
* @param height
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int removeAbove(Vector pos, int size, int height)
throws MaxChangedBlocksException {
int maxY = Math.min(world.getMaxY(), pos.getBlockY() + height - 1);
--size;
int affected = 0;
int oX = pos.getBlockX();
int oY = pos.getBlockY();
int oZ = pos.getBlockZ();
for (int x = oX - size; x <= oX + size; ++x) {
for (int z = oZ - size; z <= oZ + size; ++z) {
for (int y = oY; y <= maxY; ++y) {
Vector pt = new Vector(x, y, z);
if (getBlockType(pt) != BlockID.AIR) {
setBlock(pt, new BaseBlock(BlockID.AIR));
++affected;
}
}
}
}
return affected;
}
/**
* Remove blocks below.
*
* @param pos
* @param size
* @param height
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int removeBelow(Vector pos, int size, int height)
throws MaxChangedBlocksException {
int minY = Math.max(0, pos.getBlockY() - height);
--size;
int affected = 0;
int oX = pos.getBlockX();
int oY = pos.getBlockY();
int oZ = pos.getBlockZ();
for (int x = oX - size; x <= oX + size; ++x) {
for (int z = oZ - size; z <= oZ + size; ++z) {
for (int y = oY; y >= minY; --y) {
Vector pt = new Vector(x, y, z);
if (getBlockType(pt) != BlockID.AIR) {
setBlock(pt, new BaseBlock(BlockID.AIR));
++affected;
}
}
}
}
return affected;
}
/**
* Remove nearby blocks of a type.
*
* @param pos
* @param blockType
* @param size
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int removeNear(Vector pos, int blockType, int size)
throws MaxChangedBlocksException {
int affected = 0;
BaseBlock air = new BaseBlock(BlockID.AIR);
int minX = pos.getBlockX() - size;
int maxX = pos.getBlockX() + size;
int minY = Math.max(0, pos.getBlockY() - size);
int maxY = Math.min(world.getMaxY(), pos.getBlockY() + size);
int minZ = pos.getBlockZ() - size;
int maxZ = pos.getBlockZ() + size;
for (int x = minX; x <= maxX; ++x) {
for (int y = minY; y <= maxY; ++y) {
for (int z = minZ; z <= maxZ; ++z) {
Vector p = new Vector(x, y, z);
if (getBlockType(p) == blockType) {
if (setBlock(p, air)) {
++affected;
}
}
}
}
}
return affected;
}
/**
* Sets all the blocks inside a region to a certain block type.
*
* @param region
* @param block
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int setBlocks(Region region, BaseBlock block)
throws MaxChangedBlocksException {
int affected = 0;
if (region instanceof CuboidRegion) {
// Doing this for speed
Vector min = region.getMinimumPoint();
Vector max = region.getMaximumPoint();
int minX = min.getBlockX();
int minY = min.getBlockY();
int minZ = min.getBlockZ();
int maxX = max.getBlockX();
int maxY = max.getBlockY();
int maxZ = max.getBlockZ();
for (int x = minX; x <= maxX; ++x) {
for (int y = minY; y <= maxY; ++y) {
for (int z = minZ; z <= maxZ; ++z) {
Vector pt = new Vector(x, y, z);
if (setBlock(pt, block)) {
++affected;
}
}
}
}
} else {
for (Vector pt : region) {
if (setBlock(pt, block)) {
++affected;
}
}
}
return affected;
}
/**
* Sets all the blocks inside a region to a certain block type.
*
* @param region
* @param pattern
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int setBlocks(Region region, Pattern pattern)
throws MaxChangedBlocksException {
int affected = 0;
if (region instanceof CuboidRegion) {
// Doing this for speed
Vector min = region.getMinimumPoint();
Vector max = region.getMaximumPoint();
int minX = min.getBlockX();
int minY = min.getBlockY();
int minZ = min.getBlockZ();
int maxX = max.getBlockX();
int maxY = max.getBlockY();
int maxZ = max.getBlockZ();
for (int x = minX; x <= maxX; ++x) {
for (int y = minY; y <= maxY; ++y) {
for (int z = minZ; z <= maxZ; ++z) {
Vector pt = new Vector(x, y, z);
if (setBlock(pt, pattern.next(pt))) {
++affected;
}
}
}
}
} else {
for (Vector pt : region) {
if (setBlock(pt, pattern.next(pt))) {
++affected;
}
}
}
return affected;
}
/**
* Replaces all the blocks of a type inside a region to another block type.
*
* @param region
* @param fromBlockTypes -1 for non-air
* @param toBlock
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int replaceBlocks(Region region, Set<BaseBlock> fromBlockTypes, BaseBlock toBlock) throws MaxChangedBlocksException {
Set<BaseBlock> definiteBlockTypes = new HashSet<BaseBlock>();
Set<Integer> fuzzyBlockTypes = new HashSet<Integer>();
if (fromBlockTypes != null) {
for (BaseBlock block : fromBlockTypes) {
if (block.getData() == -1) {
fuzzyBlockTypes.add(block.getType());
} else {
definiteBlockTypes.add(block);
}
}
}
int affected = 0;
if (region instanceof CuboidRegion) {
// Doing this for speed
Vector min = region.getMinimumPoint();
Vector max = region.getMaximumPoint();
int minX = min.getBlockX();
int minY = min.getBlockY();
int minZ = min.getBlockZ();
int maxX = max.getBlockX();
int maxY = max.getBlockY();
int maxZ = max.getBlockZ();
for (int x = minX; x <= maxX; ++x) {
for (int y = minY; y <= maxY; ++y) {
for (int z = minZ; z <= maxZ; ++z) {
Vector pt = new Vector(x, y, z);
BaseBlock curBlockType = getBlock(pt);
if (fromBlockTypes == null) {
//replace <to-block>
if (curBlockType.isAir()) {
continue;
}
} else {
//replace <from-block> <to-block>
if (!definiteBlockTypes.contains(curBlockType) && !fuzzyBlockTypes.contains(curBlockType.getType())) {
continue;
}
}
if (setBlock(pt, toBlock)) {
++affected;
}
}
}
}
} else {
for (Vector pt : region) {
BaseBlock curBlockType = getBlock(pt);
if (fromBlockTypes == null) {
//replace <to-block>
if (curBlockType.isAir()) {
continue;
}
} else {
//replace <from-block> <to-block>
if (!definiteBlockTypes.contains(curBlockType) && !fuzzyBlockTypes.contains(curBlockType.getType())) {
continue;
}
}
if (setBlock(pt, toBlock)) {
++affected;
}
}
}
return affected;
}
/**
* Replaces all the blocks of a type inside a region to another block type.
*
* @param region
* @param fromBlockTypes -1 for non-air
* @param pattern
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int replaceBlocks(Region region, Set<BaseBlock> fromBlockTypes, Pattern pattern) throws MaxChangedBlocksException {
Set<BaseBlock> definiteBlockTypes = new HashSet<BaseBlock>();
Set<Integer> fuzzyBlockTypes = new HashSet<Integer>();
for (BaseBlock block : fromBlockTypes) {
if (block.getData() == -1) {
fuzzyBlockTypes.add(block.getType());
} else {
definiteBlockTypes.add(block);
}
}
int affected = 0;
if (region instanceof CuboidRegion) {
// Doing this for speed
Vector min = region.getMinimumPoint();
Vector max = region.getMaximumPoint();
int minX = min.getBlockX();
int minY = min.getBlockY();
int minZ = min.getBlockZ();
int maxX = max.getBlockX();
int maxY = max.getBlockY();
int maxZ = max.getBlockZ();
for (int x = minX; x <= maxX; ++x) {
for (int y = minY; y <= maxY; ++y) {
for (int z = minZ; z <= maxZ; ++z) {
Vector pt = new Vector(x, y, z);
BaseBlock curBlockType = getBlock(pt);
if (definiteBlockTypes == null) {
//replace <to-block>
if (curBlockType.isAir()) {
continue;
}
} else {
//replace <from-block> <to-block>
if (!definiteBlockTypes.contains(curBlockType) && fuzzyBlockTypes.contains(curBlockType.getType())) {
continue;
}
}
if (setBlock(pt, pattern.next(pt))) {
++affected;
}
}
}
}
} else {
for (Vector pt : region) {
BaseBlock curBlockType = getBlock(pt);
if (definiteBlockTypes == null) {
//replace <to-block>
if (curBlockType.isAir()) {
continue;
}
} else {
//replace <from-block> <to-block>
if (!definiteBlockTypes.contains(curBlockType) && fuzzyBlockTypes.contains(curBlockType.getType())) {
continue;
}
}
if (setBlock(pt, pattern.next(pt))) {
++affected;
}
}
}
return affected;
}
/**
* Make faces of the region (as if it was a cuboid if it's not).
*
* @param region
* @param block
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int makeCuboidFaces(Region region, BaseBlock block)
throws MaxChangedBlocksException {
int affected = 0;
Vector min = region.getMinimumPoint();
Vector max = region.getMaximumPoint();
int minX = min.getBlockX();
int minY = min.getBlockY();
int minZ = min.getBlockZ();
int maxX = max.getBlockX();
int maxY = max.getBlockY();
int maxZ = max.getBlockZ();
for (int x = minX; x <= maxX; ++x) {
for (int y = minY; y <= maxY; ++y) {
if (setBlock(new Vector(x, y, minZ), block)) {
++affected;
}
if (setBlock(new Vector(x, y, maxZ), block)) {
++affected;
}
++affected;
}
}
for (int y = minY; y <= maxY; ++y) {
for (int z = minZ; z <= maxZ; ++z) {
if (setBlock(new Vector(minX, y, z), block)) {
++affected;
}
if (setBlock(new Vector(maxX, y, z), block)) {
++affected;
}
}
}
for (int z = minZ; z <= maxZ; ++z) {
for (int x = minX; x <= maxX; ++x) {
if (setBlock(new Vector(x, minY, z), block)) {
++affected;
}
if (setBlock(new Vector(x, maxY, z), block)) {
++affected;
}
}
}
return affected;
}
/**
* Make faces of the region (as if it was a cuboid if it's not).
*
* @param region
* @param pattern
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int makeCuboidFaces(Region region, Pattern pattern)
throws MaxChangedBlocksException {
int affected = 0;
Vector min = region.getMinimumPoint();
Vector max = region.getMaximumPoint();
int minX = min.getBlockX();
int minY = min.getBlockY();
int minZ = min.getBlockZ();
int maxX = max.getBlockX();
int maxY = max.getBlockY();
int maxZ = max.getBlockZ();
for (int x = minX; x <= maxX; ++x) {
for (int y = minY; y <= maxY; ++y) {
Vector minV = new Vector(x, y, minZ);
if (setBlock(min, pattern.next(minV))) {
++affected;
}
Vector maxV = new Vector(x, y, maxZ);
if (setBlock(maxV, pattern.next(maxV))) {
++affected;
}
++affected;
}
}
for (int y = minY; y <= maxY; ++y) {
for (int z = minZ; z <= maxZ; ++z) {
Vector minV = new Vector(minX, y, z);
if (setBlock(minV, pattern.next(minV))) {
++affected;
}
Vector maxV = new Vector(maxX, y, z);
if (setBlock(maxV, pattern.next(maxV))) {
++affected;
}
}
}
for (int z = minZ; z <= maxZ; ++z) {
for (int x = minX; x <= maxX; ++x) {
Vector minV = new Vector(x, minY, z);
if (setBlock(minV, pattern.next(minV))) {
++affected;
}
Vector maxV = new Vector(x, maxY, z);
if (setBlock(maxV, pattern.next(maxV))) {
++affected;
}
}
}
return affected;
}
/**
* Make walls of the region (as if it was a cuboid if it's not).
*
* @param region
* @param block
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int makeCuboidWalls(Region region, BaseBlock block)
throws MaxChangedBlocksException {
int affected = 0;
Vector min = region.getMinimumPoint();
Vector max = region.getMaximumPoint();
int minX = min.getBlockX();
int minY = min.getBlockY();
int minZ = min.getBlockZ();
int maxX = max.getBlockX();
int maxY = max.getBlockY();
int maxZ = max.getBlockZ();
for (int x = minX; x <= maxX; ++x) {
for (int y = minY; y <= maxY; ++y) {
if (setBlock(new Vector(x, y, minZ), block)) {
++affected;
}
if (setBlock(new Vector(x, y, maxZ), block)) {
++affected;
}
++affected;
}
}
for (int y = minY; y <= maxY; ++y) {
for (int z = minZ; z <= maxZ; ++z) {
if (setBlock(new Vector(minX, y, z), block)) {
++affected;
}
if (setBlock(new Vector(maxX, y, z), block)) {
++affected;
}
}
}
return affected;
}
/**
* Make walls of the region (as if it was a cuboid if it's not).
*
* @param region
* @param block
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int makeCuboidWalls(Region region, Pattern pattern)
throws MaxChangedBlocksException {
int affected = 0;
Vector min = region.getMinimumPoint();
Vector max = region.getMaximumPoint();
int minX = min.getBlockX();
int minY = min.getBlockY();
int minZ = min.getBlockZ();
int maxX = max.getBlockX();
int maxY = max.getBlockY();
int maxZ = max.getBlockZ();
for (int x = minX; x <= maxX; ++x) {
for (int y = minY; y <= maxY; ++y) {
Vector minV = new Vector(x, y, minZ);
if (setBlock(minV, pattern.next(minV))) {
++affected;
}
Vector maxV = new Vector(x, y, maxZ);
if (setBlock(maxV, pattern.next(maxV))) {
++affected;
}
++affected;
}
}
for (int y = minY; y <= maxY; ++y) {
for (int z = minZ; z <= maxZ; ++z) {
Vector minV = new Vector(minX, y, z);
if (setBlock(minV, pattern.next(minV))) {
++affected;
}
Vector maxV = new Vector(maxX, y, z);
if (setBlock(maxV, pattern.next(maxV))) {
++affected;
}
}
}
return affected;
}
/**
* Overlays a layer of blocks over a cuboid area.
*
* @param region
* @param block
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int overlayCuboidBlocks(Region region, BaseBlock block)
throws MaxChangedBlocksException {
Vector min = region.getMinimumPoint();
Vector max = region.getMaximumPoint();
int upperY = Math.min(world.getMaxY(), max.getBlockY() + 1);
int lowerY = Math.max(0, min.getBlockY() - 1);
int affected = 0;
int minX = min.getBlockX();
int minZ = min.getBlockZ();
int maxX = max.getBlockX();
int maxZ = max.getBlockZ();
for (int x = minX; x <= maxX; ++x) {
for (int z = minZ; z <= maxZ; ++z) {
for (int y = upperY; y >= lowerY; --y) {
Vector above = new Vector(x, y + 1, z);
if (y + 1 <= world.getMaxY() && !getBlock(new Vector(x, y, z)).isAir()
&& getBlock(above).isAir()) {
if (setBlock(above, block)) {
++affected;
}
break;
}
}
}
}
return affected;
}
/**
* Overlays a layer of blocks over a cuboid area.
*
* @param region
* @param pattern
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int overlayCuboidBlocks(Region region, Pattern pattern)
throws MaxChangedBlocksException {
Vector min = region.getMinimumPoint();
Vector max = region.getMaximumPoint();
int upperY = Math.min(world.getMaxY(), max.getBlockY() + 1);
int lowerY = Math.max(0, min.getBlockY() - 1);
int affected = 0;
int minX = min.getBlockX();
int minZ = min.getBlockZ();
int maxX = max.getBlockX();
int maxZ = max.getBlockZ();
for (int x = minX; x <= maxX; ++x) {
for (int z = minZ; z <= maxZ; ++z) {
for (int y = upperY; y >= lowerY; --y) {
Vector above = new Vector(x, y + 1, z);
if (y + 1 <= world.getMaxY() && !getBlock(new Vector(x, y, z)).isAir()
&& getBlock(above).isAir()) {
if (setBlock(above, pattern.next(above))) {
++affected;
}
break;
}
}
}
}
return affected;
}
/**
* Turns the first 3 layers into dirt/grass and the bottom layers
* into rock, like a natural Minecraft mountain.
*
* @param region
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int naturalizeCuboidBlocks(Region region)
throws MaxChangedBlocksException {
Vector min = region.getMinimumPoint();
Vector max = region.getMaximumPoint();
int upperY = Math.min(world.getMaxY(), max.getBlockY() + 1);
int lowerY = Math.max(0, min.getBlockY() - 1);
int affected = 0;
int minX = min.getBlockX();
int minZ = min.getBlockZ();
int maxX = max.getBlockX();
int maxZ = max.getBlockZ();
BaseBlock grass = new BaseBlock(BlockID.GRASS);
BaseBlock dirt = new BaseBlock(BlockID.DIRT);
BaseBlock stone = new BaseBlock(BlockID.STONE);
for (int x = minX; x <= maxX; ++x) {
for (int z = minZ; z <= maxZ; ++z) {
int level = -1;
for (int y = upperY; y >= lowerY; --y) {
Vector pt = new Vector(x, y, z);
//Vector above = new Vector(x, y + 1, z);
int blockType = getBlockType(pt);
boolean isTransformable =
blockType == BlockID.GRASS
|| blockType == BlockID.DIRT
|| blockType == BlockID.STONE;
// Still searching for the top block
if (level == -1) {
if (!isTransformable) {
continue; // Not transforming this column yet
}
level = 0;
}
if (level >= 0) {
if (isTransformable) {
if (level == 0) {
setBlock(pt, grass);
affected++;
} else if (level <= 2) {
setBlock(pt, dirt);
affected++;
} else {
setBlock(pt, stone);
affected++;
}
}
level++;
}
}
}
}
return affected;
}
/**
* Stack a cuboid region.
*
* @param region
* @param dir
* @param count
* @param copyAir
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int stackCuboidRegion(Region region, Vector dir, int count,
boolean copyAir) throws MaxChangedBlocksException {
int affected = 0;
Vector min = region.getMinimumPoint();
Vector max = region.getMaximumPoint();
int minX = min.getBlockX();
int minY = min.getBlockY();
int minZ = min.getBlockZ();
int maxX = max.getBlockX();
int maxY = max.getBlockY();
int maxZ = max.getBlockZ();
int xs = region.getWidth();
int ys = region.getHeight();
int zs = region.getLength();
for (int x = minX; x <= maxX; ++x) {
for (int z = minZ; z <= maxZ; ++z) {
for (int y = minY; y <= maxY; ++y) {
BaseBlock block = getBlock(new Vector(x, y, z));
if (!block.isAir() || copyAir) {
for (int i = 1; i <= count; ++i) {
Vector pos = new Vector(x + xs * dir.getBlockX()
* i, y + ys * dir.getBlockY() * i, z + zs
* dir.getBlockZ() * i);
if (setBlock(pos, block)) {
++affected;
}
}
}
}
}
}
return affected;
}
/**
* Move a cuboid region.
*
* @param region
* @param dir
* @param distance
* @param copyAir
* @param replace
* @return number of blocks moved
* @throws MaxChangedBlocksException
*/
public int moveCuboidRegion(Region region, Vector dir, int distance,
boolean copyAir, BaseBlock replace)
throws MaxChangedBlocksException {
int affected = 0;
Vector shift = dir.multiply(distance);
Vector min = region.getMinimumPoint();
Vector max = region.getMaximumPoint();
int minX = min.getBlockX();
int minY = min.getBlockY();
int minZ = min.getBlockZ();
int maxX = max.getBlockX();
int maxY = max.getBlockY();
int maxZ = max.getBlockZ();
Vector newMin = min.add(shift);
Vector newMax = min.add(shift);
Map<Vector, BaseBlock> delayed = new LinkedHashMap<Vector, BaseBlock>();
for (int x = minX; x <= maxX; ++x) {
for (int z = minZ; z <= maxZ; ++z) {
for (int y = minY; y <= maxY; ++y) {
Vector pos = new Vector(x, y, z);
BaseBlock block = getBlock(pos);
if (!block.isAir() || copyAir) {
Vector newPos = pos.add(shift);
delayed.put(newPos, getBlock(pos));
// Don't want to replace the old block if it's in
// the new area
if (x >= newMin.getBlockX() && x <= newMax.getBlockX()
&& y >= newMin.getBlockY()
&& y <= newMax.getBlockY()
&& z >= newMin.getBlockZ()
&& z <= newMax.getBlockZ()) {
} else {
setBlock(pos, replace);
}
}
}
}
}
for (Map.Entry<Vector, BaseBlock> entry : delayed.entrySet()) {
setBlock(entry.getKey(), entry.getValue());
++affected;
}
return affected;
}
/**
* Drain nearby pools of water or lava.
*
* @param pos
* @param radius
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int drainArea(Vector pos, double radius)
throws MaxChangedBlocksException {
int affected = 0;
HashSet<BlockVector> visited = new HashSet<BlockVector>();
Stack<BlockVector> queue = new Stack<BlockVector>();
for (int x = pos.getBlockX() - 1; x <= pos.getBlockX() + 1; ++x) {
for (int z = pos.getBlockZ() - 1; z <= pos.getBlockZ() + 1; ++z) {
for (int y = pos.getBlockY() - 1; y <= pos.getBlockY() + 1; ++y) {
queue.push(new BlockVector(x, y, z));
}
}
}
while (!queue.empty()) {
BlockVector cur = queue.pop();
int type = getBlockType(cur);
// Check block type
if (type != BlockID.WATER && type != BlockID.STATIONARY_WATER
&& type != BlockID.LAVA && type != BlockID.STATIONARY_LAVA) {
continue;
}
// Don't want to revisit
if (visited.contains(cur)) {
continue;
}
visited.add(cur);
// Check radius
if (pos.distance(cur) > radius) {
continue;
}
for (int x = cur.getBlockX() - 1; x <= cur.getBlockX() + 1; ++x) {
for (int z = cur.getBlockZ() - 1; z <= cur.getBlockZ() + 1; ++z) {
for (int y = cur.getBlockY() - 1; y <= cur.getBlockY() + 1; ++y) {
BlockVector newPos = new BlockVector(x, y, z);
if (!cur.equals(newPos)) {
queue.push(newPos);
}
}
}
}
if (setBlock(cur, new BaseBlock(BlockID.AIR))) {
++affected;
}
}
return affected;
}
/**
* Level water.
*
* @param pos
* @param radius
* @param moving
* @param stationary
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int fixLiquid(Vector pos, double radius, int moving, int stationary)
throws MaxChangedBlocksException {
int affected = 0;
HashSet<BlockVector> visited = new HashSet<BlockVector>();
Stack<BlockVector> queue = new Stack<BlockVector>();
for (int x = pos.getBlockX() - 1; x <= pos.getBlockX() + 1; ++x) {
for (int z = pos.getBlockZ() - 1; z <= pos.getBlockZ() + 1; ++z) {
for (int y = pos.getBlockY() - 1; y <= pos.getBlockY() + 1; ++y) {
int type = getBlock(new Vector(x, y, z)).getType();
// Check block type
if (type == moving || type == stationary) {
queue.push(new BlockVector(x, y, z));
}
}
}
}
BaseBlock stationaryBlock = new BaseBlock(stationary);
while (!queue.empty()) {
BlockVector cur = queue.pop();
int type = getBlockType(cur);
// Check block type
if (type != moving && type != stationary && type != BlockID.AIR) {
continue;
}
// Don't want to revisit
if (visited.contains(cur)) {
continue;
}
visited.add(cur);
if (setBlock(cur, stationaryBlock)) {
++affected;
}
// Check radius
if (pos.distance(cur) > radius) {
continue;
}
queue.push(cur.add(1, 0, 0).toBlockVector());
queue.push(cur.add(-1, 0, 0).toBlockVector());
queue.push(cur.add(0, 0, 1).toBlockVector());
queue.push(cur.add(0, 0, -1).toBlockVector());
}
return affected;
}
/**
* Makes a cylinder.
*
* @param pos Center of the cylinder
* @param block The block pattern to use
* @param radius The cylinder's radius
* @param height The cylinder's up/down extent. If negative, extend downward.
* @param filled If false, only a shell will be generated.
* @return number of blocks changed
* @throws MaxChangedBlocksException
*/
public int makeCylinder(Vector pos, Pattern block, double radius, int height, boolean filled) throws MaxChangedBlocksException {
return makeCylinder(pos, block, radius, radius, height, filled);
}
/**
* Makes a cylinder.
*
* @param pos Center of the cylinder
* @param block The block pattern to use
* @param radiusX The cylinder's largest north/south extent
* @param radiusZ The cylinder's largest east/west extent
* @param height The cylinder's up/down extent. If negative, extend downward.
* @param filled If false, only a shell will be generated.
* @return number of blocks changed
* @throws MaxChangedBlocksException
*/
public int makeCylinder(Vector pos, Pattern block, double radiusX, double radiusZ, int height, boolean filled) throws MaxChangedBlocksException {
int affected = 0;
radiusX += 0.5;
radiusZ += 0.5;
if (height == 0) {
return 0;
} else if (height < 0) {
height = -height;
pos = pos.subtract(0, height, 0);
}
if (pos.getBlockY() - height - 1 < 0) {
height = pos.getBlockY() + 1;
} else if (pos.getBlockY() + height - 1 > world.getMaxY()) {
height = world.getMaxY() - pos.getBlockY() + 1;
}
final double invRadiusX = 1 / radiusX;
final double invRadiusZ = 1 / radiusZ;
final int ceilRadiusX = (int) Math.ceil(radiusX);
final int ceilRadiusZ = (int) Math.ceil(radiusZ);
double nextXn = 0;
forX: for (int x = 0; x <= ceilRadiusX; ++x) {
final double xn = nextXn;
nextXn = (x + 1) * invRadiusX;
double nextZn = 0;
forZ: for (int z = 0; z <= ceilRadiusZ; ++z) {
final double zn = nextZn;
nextZn = (z + 1) * invRadiusZ;
double distanceSq = lengthSq(xn, zn);
if (distanceSq > 1) {
if (z == 0) {
break forX;
}
break forZ;
}
if (!filled) {
if (lengthSq(nextXn, zn) <= 1 && lengthSq(xn, nextZn) <= 1) {
continue;
}
}
for (int y = 0; y < height; ++y) {
if (setBlock(pos.add(x, y, z), block)) {
++affected;
}
if (setBlock(pos.add(-x, y, z), block)) {
++affected;
}
if (setBlock(pos.add(x, y, -z), block)) {
++affected;
}
if (setBlock(pos.add(-x, y, -z), block)) {
++affected;
}
}
}
}
return affected;
}
/**
* Makes a sphere.
*
* @param pos Center of the sphere or ellipsoid
* @param block The block pattern to use
* @param radius The sphere's radius
* @param filled If false, only a shell will be generated.
* @return number of blocks changed
* @throws MaxChangedBlocksException
*/
public int makeSphere(Vector pos, Pattern block, double radius, boolean filled) throws MaxChangedBlocksException {
return makeSphere(pos, block, radius, radius, radius, filled);
}
/**
* Makes a sphere or ellipsoid.
*
* @param pos Center of the sphere or ellipsoid
* @param block The block pattern to use
* @param radiusX The sphere/ellipsoid's largest north/south extent
* @param radiusY The sphere/ellipsoid's largest up/down extent
* @param radiusZ The sphere/ellipsoid's largest east/west extent
* @param filled If false, only a shell will be generated.
* @return number of blocks changed
* @throws MaxChangedBlocksException
*/
public int makeSphere(Vector pos, Pattern block, double radiusX, double radiusY, double radiusZ, boolean filled) throws MaxChangedBlocksException {
int affected = 0;
radiusX += 0.5;
radiusY += 0.5;
radiusZ += 0.5;
final double invRadiusX = 1 / radiusX;
final double invRadiusY = 1 / radiusY;
final double invRadiusZ = 1 / radiusZ;
final int ceilRadiusX = (int) Math.ceil(radiusX);
final int ceilRadiusY = (int) Math.ceil(radiusY);
final int ceilRadiusZ = (int) Math.ceil(radiusZ);
double nextXn = 0;
forX: for (int x = 0; x <= ceilRadiusX; ++x) {
final double xn = nextXn;
nextXn = (x + 1) * invRadiusX;
double nextYn = 0;
forY: for (int y = 0; y <= ceilRadiusY; ++y) {
final double yn = nextYn;
nextYn = (y + 1) * invRadiusY;
double nextZn = 0;
forZ: for (int z = 0; z <= ceilRadiusZ; ++z) {
final double zn = nextZn;
nextZn = (z + 1) * invRadiusZ;
double distanceSq = lengthSq(xn, yn, zn);
if (distanceSq > 1) {
if (z == 0) {
if (y == 0) {
break forX;
}
break forY;
}
break forZ;
}
if (!filled) {
if (lengthSq(nextXn, yn, zn) <= 1 && lengthSq(xn, nextYn, zn) <= 1 && lengthSq(xn, yn, nextZn) <= 1) {
continue;
}
}
if (setBlock(pos.add(x, y, z), block)) {
++affected;
}
if (setBlock(pos.add(-x, y, z), block)) {
++affected;
}
if (setBlock(pos.add(x, -y, z), block)) {
++affected;
}
if (setBlock(pos.add(x, y, -z), block)) {
++affected;
}
if (setBlock(pos.add(-x, -y, z), block)) {
++affected;
}
if (setBlock(pos.add(x, -y, -z), block)) {
++affected;
}
if (setBlock(pos.add(-x, y, -z), block)) {
++affected;
}
if (setBlock(pos.add(-x, -y, -z), block)) {
++affected;
}
}
}
}
return affected;
}
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.
*
* @param pos
* @param block
* @param size
* @param filled
* @return number of blocks changed
* @throws MaxChangedBlocksException
*/
public int makePyramid(Vector pos, Pattern block, int size,
boolean filled) throws MaxChangedBlocksException {
int affected = 0;
int height = size;
for (int y = 0; y <= height; ++y) {
size--;
for (int x = 0; x <= size; ++x) {
for (int z = 0; z <= size; ++z) {
if ((filled && z <= size && x <= size) || z == size || x == size) {
if (setBlock(pos.add(x, y, z), block)) {
++affected;
}
if (setBlock(pos.add(-x, y, z), block)) {
++affected;
}
if (setBlock(pos.add(x, y, -z), block)) {
++affected;
}
if (setBlock(pos.add(-x, y, -z), block)) {
++affected;
}
}
}
}
}
return affected;
}
/**
* Thaw.
*
* @param pos
* @param radius
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int thaw(Vector pos, double radius)
throws MaxChangedBlocksException {
int affected = 0;
double radiusSq = radius * radius;
int ox = pos.getBlockX();
int oy = pos.getBlockY();
int oz = pos.getBlockZ();
BaseBlock air = new BaseBlock(0);
BaseBlock water = new BaseBlock(BlockID.STATIONARY_WATER);
int ceilRadius = (int) Math.ceil(radius);
for (int x = ox - ceilRadius; x <= ox + ceilRadius; ++x) {
for (int z = oz - ceilRadius; z <= oz + ceilRadius; ++z) {
if ((new Vector(x, oy, z)).distanceSq(pos) > radiusSq) {
continue;
}
for (int y = world.getMaxY(); y >= 1; --y) {
Vector pt = new Vector(x, y, z);
int id = getBlockType(pt);
switch (id) {
case BlockID.ICE:
if (setBlock(pt, water)) {
++affected;
}
break;
case BlockID.SNOW:
if (setBlock(pt, air)) {
++affected;
}
break;
case BlockID.AIR:
continue;
default:
break;
}
break;
}
}
}
return affected;
}
/**
* Make snow.
*
* @param pos
* @param radius
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int simulateSnow(Vector pos, double radius)
throws MaxChangedBlocksException {
int affected = 0;
double radiusSq = radius * radius;
int ox = pos.getBlockX();
int oy = pos.getBlockY();
int oz = pos.getBlockZ();
BaseBlock ice = new BaseBlock(BlockID.ICE);
BaseBlock snow = new BaseBlock(BlockID.SNOW);
int ceilRadius = (int) Math.ceil(radius);
for (int x = ox - ceilRadius; x <= ox + ceilRadius; ++x) {
for (int z = oz - ceilRadius; z <= oz + ceilRadius; ++z) {
if ((new Vector(x, oy, z)).distanceSq(pos) > radiusSq) {
continue;
}
for (int y = world.getMaxY(); y >= 1; --y) {
Vector pt = new Vector(x, y, z);
int id = getBlockType(pt);
if (id == BlockID.AIR) {
continue;
}
// Ice!
if (id == BlockID.WATER || id == BlockID.STATIONARY_WATER) {
if (setBlock(pt, ice)) {
++affected;
}
break;
}
// Snow should not cover these blocks
if (BlockType.canPassThrough(id)) {
break;
}
// Too high?
if (y == world.getMaxY()) {
break;
}
// add snow cover
if (setBlock(pt.add(0, 1, 0), snow)) {
++affected;
}
break;
}
}
}
return affected;
}
/**
* Green.
*
* @param pos
* @param radius
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int green(Vector pos, double radius)
throws MaxChangedBlocksException {
int affected = 0;
final double radiusSq = radius * radius;
final int ox = pos.getBlockX();
final int oy = pos.getBlockY();
final int oz = pos.getBlockZ();
final BaseBlock grass = new BaseBlock(BlockID.GRASS);
final int ceilRadius = (int) Math.ceil(radius);
for (int x = ox - ceilRadius; x <= ox + ceilRadius; ++x) {
for (int z = oz - ceilRadius; z <= oz + ceilRadius; ++z) {
if ((new Vector(x, oy, z)).distanceSq(pos) > radiusSq) {
continue;
}
loop: for (int y = world.getMaxY(); y >= 1; --y) {
final Vector pt = new Vector(x, y, z);
final int id = getBlockType(pt);
switch (id) {
case BlockID.DIRT:
if (setBlock(pt, grass)) {
++affected;
}
break loop;
case BlockID.WATER:
case BlockID.STATIONARY_WATER:
case BlockID.LAVA:
case BlockID.STATIONARY_LAVA:
// break on liquids...
break loop;
default:
// ...and all non-passable blocks
if (!BlockType.canPassThrough(id)) {
break loop;
}
}
}
}
}
return affected;
}
/**
* Makes a pumpkin patch.
*
* @param basePos
*/
private void makePumpkinPatch(Vector basePos)
throws MaxChangedBlocksException {
// BaseBlock logBlock = new BaseBlock(BlockID.LOG);
BaseBlock leavesBlock = new BaseBlock(BlockID.LEAVES);
// setBlock(basePos.subtract(0, 1, 0), logBlock);
setBlockIfAir(basePos, leavesBlock);
makePumpkinPatchVine(basePos, basePos.add(0, 0, 1));
makePumpkinPatchVine(basePos, basePos.add(0, 0, -1));
makePumpkinPatchVine(basePos, basePos.add(1, 0, 0));
makePumpkinPatchVine(basePos, basePos.add(-1, 0, 0));
}
/**
* Make a pumpkin patch fine.
*
* @param basePos
* @param pos
*/
private void makePumpkinPatchVine(Vector basePos, Vector pos)
throws MaxChangedBlocksException {
if (pos.distance(basePos) > 4) return;
if (getBlockType(pos) != 0) return;
for (int i = -1; i > -3; --i) {
Vector testPos = pos.add(0, i, 0);
if (getBlockType(testPos) == BlockID.AIR) {
pos = testPos;
} else {
break;
}
}
setBlockIfAir(pos, new BaseBlock(BlockID.LEAVES));
int t = prng.nextInt(4);
int h = prng.nextInt(3) - 1;
BaseBlock log = new BaseBlock(BlockID.LOG);
BaseBlock pumpkin = new BaseBlock(BlockID.PUMPKIN);
switch (t) {
case 0:
if (prng.nextBoolean()) {
makePumpkinPatchVine(basePos, pos.add(1, 0, 0));
}
if (prng.nextBoolean()) {
setBlockIfAir(pos.add(1, h, -1), log);
}
setBlockIfAir(pos.add(0, 0, -1), pumpkin);
break;
case 1:
if (prng.nextBoolean()) {
makePumpkinPatchVine(basePos, pos.add(0, 0, 1));
}
if (prng.nextBoolean()) {
setBlockIfAir(pos.add(1, h, 0), log);
}
setBlockIfAir(pos.add(1, 0, 1), pumpkin);
break;
case 2:
if (prng.nextBoolean()) {
makePumpkinPatchVine(basePos, pos.add(0, 0, -1));
}
if (prng.nextBoolean()) {
setBlockIfAir(pos.add(-1, h, 0), log);
}
setBlockIfAir(pos.add(-1, 0, 1), pumpkin);
break;
case 3:
if (prng.nextBoolean()) {
makePumpkinPatchVine(basePos, pos.add(-1, 0, 0));
}
if (prng.nextBoolean()) {
setBlockIfAir(pos.add(-1, h, -1), log);
}
setBlockIfAir(pos.add(-1, 0, -1), pumpkin);
break;
}
}
/**
* Makes pumpkin patches.
*
* @param basePos
* @param size
* @return number of trees created
* @throws MaxChangedBlocksException
*/
public int makePumpkinPatches(Vector basePos, int size)
throws MaxChangedBlocksException {
int affected = 0;
for (int x = basePos.getBlockX() - size; x <= basePos.getBlockX()
+ size; ++x) {
for (int z = basePos.getBlockZ() - size; z <= basePos.getBlockZ()
+ size; ++z) {
// Don't want to be in the ground
if (!getBlock(new Vector(x, basePos.getBlockY(), z)).isAir()) {
continue;
}
// The gods don't want a pumpkin patch here
if (Math.random() < 0.98) {
continue;
}
for (int y = basePos.getBlockY(); y >= basePos.getBlockY() - 10; --y) {
// Check if we hit the ground
int t = getBlock(new Vector(x, y, z)).getType();
if (t == BlockID.GRASS || t == BlockID.DIRT) {
makePumpkinPatch(new Vector(x, y + 1, z));
++affected;
break;
} else if (t != BlockID.AIR) { // Trees won't grow on this!
break;
}
}
}
}
return affected;
}
/**
* Makes a forest.
*
* @param basePos
* @param size
* @param density
* @param treeGenerator
* @return number of trees created
* @throws MaxChangedBlocksException
*/
public int makeForest(Vector basePos, int size, double density,
TreeGenerator treeGenerator) throws MaxChangedBlocksException {
int affected = 0;
for (int x = basePos.getBlockX() - size; x <= basePos.getBlockX()
+ size; ++x) {
for (int z = basePos.getBlockZ() - size; z <= basePos.getBlockZ()
+ size; ++z) {
// Don't want to be in the ground
if (!getBlock(new Vector(x, basePos.getBlockY(), z)).isAir()) {
continue;
}
// The gods don't want a tree here
if (Math.random() >= density) {
continue;
} // def 0.05
for (int y = basePos.getBlockY(); y >= basePos.getBlockY() - 10; --y) {
// Check if we hit the ground
int t = getBlock(new Vector(x, y, z)).getType();
if (t == BlockID.GRASS || t == BlockID.DIRT) {
treeGenerator.generate(this, new Vector(x, y + 1, z));
++affected;
break;
} else if (t != BlockID.AIR) { // Trees won't grow on this!
break;
}
}
}
}
return affected;
}
/**
* Get the block distribution inside a region.
*
* @param region
* @return
*/
public List<Countable<Integer>> getBlockDistribution(Region region) {
List<Countable<Integer>> distribution = new ArrayList<Countable<Integer>>();
Map<Integer, Countable<Integer>> map = new HashMap<Integer, Countable<Integer>>();
if (region instanceof CuboidRegion) {
// Doing this for speed
Vector min = region.getMinimumPoint();
Vector max = region.getMaximumPoint();
int minX = min.getBlockX();
int minY = min.getBlockY();
int minZ = min.getBlockZ();
int maxX = max.getBlockX();
int maxY = max.getBlockY();
int maxZ = max.getBlockZ();
for (int x = minX; x <= maxX; ++x) {
for (int y = minY; y <= maxY; ++y) {
for (int z = minZ; z <= maxZ; ++z) {
Vector pt = new Vector(x, y, z);
int id = getBlockType(pt);
if (map.containsKey(id)) {
map.get(id).increment();
} else {
Countable<Integer> c = new Countable<Integer>(id, 1);
map.put(id, c);
distribution.add(c);
}
}
}
}
} else {
for (Vector pt : region) {
int id = getBlockType(pt);
if (map.containsKey(id)) {
map.get(id).increment();
} else {
Countable<Integer> c = new Countable<Integer>(id, 1);
map.put(id, c);
}
}
}
Collections.sort(distribution);
// Collections.reverse(distribution);
return distribution;
}
public int makeShape(final Region region, final Vector zero, final Vector unit, final Pattern pattern, final String expressionString, final boolean hollow) throws ExpressionException, MaxChangedBlocksException {
final Expression expression = Expression.compile(expressionString, "x", "y", "z", "type", "data");
expression.optimize();
final RValue typeVariable = expression.getVariable("type", false);
final RValue dataVariable = expression.getVariable("data", false);
final ArbitraryShape shape = new ArbitraryShape(region) {
@Override
protected BaseBlock getMaterial(int x, int y, int z, BaseBlock defaultMaterial) {
final Vector scaled = new Vector(x, y, z).subtract(zero).divide(unit);
try {
if (expression.evaluate(scaled.getX(), scaled.getY(), scaled.getZ(), defaultMaterial.getType(), defaultMaterial.getData()) <= 0) {
return null;
}
return new BaseBlock((int) typeVariable.getValue(), (int) dataVariable.getValue());
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
};
return shape.generate(this, pattern, hollow);
}
public int deformRegion(final Region region, final Vector zero, final Vector unit, final String expressionString) throws ExpressionException, MaxChangedBlocksException {
final Expression expression = Expression.compile(expressionString, "x", "y", "z");
expression.optimize();
final RValue x = expression.getVariable("x", false);
final RValue y = expression.getVariable("y", false);
final RValue z = expression.getVariable("z", false);
Vector zero2 = zero.add(0.5, 0.5, 0.5);
final DoubleArrayList<BlockVector, BaseBlock> queue = new DoubleArrayList<BlockVector, BaseBlock>(false);
for (BlockVector position : region) {
// offset, scale
final Vector scaled = position.subtract(zero).divide(unit);
// transform
expression.evaluate(scaled.getX(), scaled.getY(), scaled.getZ());
final Vector sourceScaled = new Vector(x.getValue(), y.getValue(), z.getValue());
// unscale, unoffset, round-nearest
final BlockVector sourcePosition = sourceScaled.multiply(unit).add(zero2).toBlockPoint();
// read block from world
BaseBlock material = new BaseBlock(world.getBlockType(sourcePosition), world.getBlockData(sourcePosition));
// queue operation
queue.put(position, material);
}
int affected = 0;
for (Map.Entry<BlockVector, BaseBlock> entry : queue) {
BlockVector position = entry.getKey();
BaseBlock material = entry.getValue();
// set at new position
if (setBlock(position, material)) {
++affected;
}
}
return affected;
}
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).
*
* @param region the region to hollow out.
* @param thickness the thickness of the shell to leave (manhattan distance)
* @param patternThe block pattern to use
*
* @return number of blocks affected
* @throws MaxChangedBlocksException
*/
public int hollowOutRegion(Region region, int thickness, Pattern pattern) throws MaxChangedBlocksException {
int affected = 0;
final Set<BlockVector> outside = new HashSet<BlockVector>();
final Vector min = region.getMinimumPoint();
final Vector max = region.getMaximumPoint();
final int minX = min.getBlockX();
final int minY = min.getBlockY();
final int minZ = min.getBlockZ();
final int maxX = max.getBlockX();
final int maxY = max.getBlockY();
final int maxZ = max.getBlockZ();
for (int x = minX; x <= maxX; ++x) {
for (int y = minY; y <= maxY; ++y) {
recurseHollow(region, new BlockVector(x, y, minZ), outside);
recurseHollow(region, new BlockVector(x, y, maxZ), outside);
}
}
for (int y = minY; y <= maxY; ++y) {
for (int z = minZ; z <= maxZ; ++z) {
recurseHollow(region, new BlockVector(minX, y, z), outside);
recurseHollow(region, new BlockVector(maxX, y, z), outside);
}
}
for (int z = minZ; z <= maxZ; ++z) {
for (int x = minX; x <= maxX; ++x) {
recurseHollow(region, new BlockVector(x, minY, z), outside);
recurseHollow(region, new BlockVector(x, maxY, z), outside);
}
}
for (int i = 1; i < thickness; ++i) {
final Set<BlockVector> newOutside = new HashSet<BlockVector>();
outer: for (BlockVector position : region) {
for (Vector recurseDirection: recurseDirections) {
BlockVector neighbor = position.add(recurseDirection).toBlockVector();
if (outside.contains(neighbor)) {
newOutside.add(position);
continue outer;
}
}
}
outside.addAll(newOutside);
}
outer: for (BlockVector position : region) {
for (Vector recurseDirection: recurseDirections) {
BlockVector neighbor = position.add(recurseDirection).toBlockVector();
if (outside.contains(neighbor)) {
continue outer;
}
}
if (setBlock(position, pattern.next(position))) {
++affected;
}
}
return affected;
}
private void recurseHollow(Region region, BlockVector origin, Set<BlockVector> outside) {
final LinkedList<BlockVector> queue = new LinkedList<BlockVector>();
queue.addLast(origin);
while (!queue.isEmpty()) {
final BlockVector current = queue.removeFirst();
if (!BlockType.canPassThrough(getBlockType(current))) {
continue;
}
if (!outside.add(current)) {
continue;
}
if (!region.contains(current)) {
continue;
}
for (Vector recurseDirection: recurseDirections) {
queue.addLast(current.add(recurseDirection).toBlockVector());
}
} // while
}
}