/**
Copyright (C) <2015> <coolAlias>
This file is part of coolAlias' Zelda Sword Skills Minecraft Mod; as such,
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 zeldaswordskills.util;
import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.block.BlockChest;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.BlockPos;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.Vec3i;
import net.minecraft.world.World;
import net.minecraft.world.gen.structure.StructureBoundingBox;
import zeldaswordskills.block.BlockChestLocked;
import zeldaswordskills.ref.Config;
import zeldaswordskills.world.gen.structure.RoomBase;
/**
*
* A collection of methods useful for manipulating StructureBoundingBox during structure
* generation.
*
*/
public class StructureGenUtils
{
/** Used for metadata rotation */
public static final int[] rotateRight = new int[] {1, 2, 3, 0};
/**
* Scans the surface of the chunk and returns the average height for the entire chunk
* by sampling the world height value of 25 of the 256 surface blocks
*/
public static int getAverageSurfaceHeight(World world, BlockPos pos) {
int height = world.getHeight(pos).getY();
int count = 1;
for (int i = pos.getX() + 3; i < pos.getX() + 16; i += 3) {
for (int k = pos.getZ() + 3; k < pos.getZ() + 16; k += 3) {
height += world.getHeight(new BlockPos(i, 64, k)).getY();
++count;
}
}
return height / count;
}
/**
* Returns the distance squared between the centers of two bounding boxes
*/
public static double getDistanceSqBetween(StructureBoundingBox box1, StructureBoundingBox box2) {
Vec3i c1 = box1.getCenter();
Vec3i c2 = box2.getCenter();
int dx = c1.getX() - c2.getX();
int dy = c1.getY() - c2.getY();
int dz = c1.getZ() - c2.getZ();
return (dx * dx + dy * dy + dz * dz);
}
/**
* Returns average distance to ground based on 5 points in bounding box's lowest layer
* @param max if any distance exceeds this threshold, this value will be returned
*/
public static int getAverageDistanceToGround(World world, StructureBoundingBox box, int max) {
Vec3i center = box.getCenter();
int i = getDistanceToGround(world, center.getX(), box.minY, center.getZ());
int total = i;
if (i > max) { return max; }
i = getDistanceToGround(world, box.minX, box.minY, box.minZ);
total += i;
if (i > max) { return max; }
i = getDistanceToGround(world, box.minX, box.minY, box.maxZ);
total += i;
if (i > max) { return max; }
i = getDistanceToGround(world, box.maxX, box.minY, box.minZ);
total += i;
if (i > max) { return max; }
i = getDistanceToGround(world, box.maxX, box.minY, box.maxZ);
total += i;
if (i > max) { return max; }
return total / 5;
}
/**
* Returns number of blocks between coordinates given and solid ground, or 0 if solid ground is above
*/
public static int getDistanceToGround(World world, int x, int y, int z) {
int i = 0;
while (!world.isSideSolid(new BlockPos(x, y - 1, z), EnumFacing.UP) && y > 5) {
--y;
++i;
}
return i;
/*
int i = world.getTopSolidOrLiquidBlock(x, z);
if (i > 0 && y > i) {
return y - i;
} else {
return 0;
}
*/
}
/**
* Fills area defined by arguments and within the structure's bounding box with given metadata block,
* up to but not including the max boundary
*/
public static void fillWithBlocks(World world, StructureBoundingBox box, int minX, int maxX, int minY, int maxY, int minZ, int maxZ, IBlockState state) {
fillWithBlocks(world, box, minX, maxX, minY, maxY, minZ, maxZ, state, false);
}
/**
* Fills area defined by arguments with given metadata block, up to but not including the max boundary
* @param ignoreBounds if true, will fill in blocks even outside of the structure's bounding box bounds
*/
public static void fillWithBlocks(World world, StructureBoundingBox box, int minX, int maxX, int minY, int maxY, int minZ, int maxZ, IBlockState state, boolean ignoreBounds) {
for (int i = minX; i < maxX; ++i) {
for (int j = minY; j < maxY; ++j) {
for (int k = minZ; k < maxZ; ++k) {
setBlockAtPosition(world, box, i, j, k, state, ignoreBounds);
}
}
}
}
/**
* Fills area defined by arguments with given metadata block, up to but not including the max boundary
* and without replacing any currently existing solid blocks
* @param flag block notification flag; see setBlock for details
*/
public static void fillWithoutReplace(World world, int minX, int maxX, int minY, int maxY, int minZ, int maxZ, IBlockState state, int flag) {
BlockPos pos;
for (int i = minX; i < maxX; ++i) {
for (int j = minY; j < maxY; ++j) {
for (int k = minZ; k < maxZ; ++k) {
pos = new BlockPos(i, j, k);
if (!world.getBlockState(pos).getBlock().getMaterial().isSolid()) {
world.setBlockState(pos, state, flag);
}
}
}
}
}
/**
* Fills downward from the structure's bottom layer to the ground level, replacing
* any non-solid or leaf blocks with the block and meta provided
*/
public static void fillDown(World world, StructureBoundingBox box, IBlockState state) {
for (int i = box.minX; i <= box.maxX; ++i) {
for (int k = box.minZ; k <= box.maxZ; ++k) {
for (int j = box.minY - 1; j > 4 && canReplace(world.getBlockState(new BlockPos(i, j, k)).getBlock().getMaterial()); --j) {
world.setBlockState(new BlockPos(i, j, k), state, 2);
}
}
}
}
/**
* Returns true if material is not solid or is leaves
* @param material
*/
private static boolean canReplace(Material material) {
return !material.isSolid() || material == Material.leaves;
}
/**
* Destroys all blocks within bounds provided, up to but excluding the max bounds
* @param block the block to destroy, or null for all blocks in the area
* @param drop whether destroyed blocks should drop items
*/
public static void destroyBlocksAround(World world, int minX, int maxX, int minY, int maxY, int minZ, int maxZ, Block block, boolean drop) {
for (int i = minX; i < maxX; ++i) {
for (int j = minY; j < maxY; ++j) {
for (int k = minZ; k < maxZ; ++k) {
BlockPos pos = new BlockPos(i, j, k);
if (block == null || world.getBlockState(pos).getBlock() == block) {
world.destroyBlock(pos, drop);
}
}
}
}
}
public static int getXWithOffset(StructureBoundingBox box, int x, int z) {
return box.minX + x;
/*
switch (coordBaseMode) {
case 0:
case 2: return boundingBox.minX + x;
case 1: return boundingBox.maxX - z;
case 3: return boundingBox.minX + z;
default: return x;
}
*/
}
public static int getYWithOffset(StructureBoundingBox box, int y) {
//return this.coordBaseMode == -1 ? y : y + this.boundingBox.minY;
return box.minY + y;
}
public static int getZWithOffset(StructureBoundingBox box, int x, int z) {
return box.minZ + z;
/*
switch (coordBaseMode) {
case 0: return boundingBox.minZ + z;
case 1:
case 3: return boundingBox.minZ + x;
case 2: return boundingBox.maxZ - z;
default: return z;
}
*/
}
/**
* Returns true if the block at position is a chest or locked chest
*/
public static boolean isBlockChest(World world, BlockPos pos) {
Block block = world.getBlockState(pos).getBlock();
return (block instanceof BlockChest || block instanceof BlockChestLocked);
}
/**
* Replaces all blocks of given material in area with block and meta provided
*/
public static void replaceMaterialWith(World world, int minX, int maxX, int minY, int maxY, int minZ, int maxZ, Material material, IBlockState state) {
for (int i = minX; i < maxX; ++i) {
for (int j = minY; j < maxY; ++j) {
for (int k = minZ; k < maxZ; ++k) {
BlockPos pos = new BlockPos(i, j, k);
if (world.getBlockState(pos).getBlock().getMaterial() == material) {
world.setBlockState(pos, state, 3);
}
}
}
}
}
/**
* Sets the block at a position offset by the amounts x/y/z within the bounding box
*/
public static void setBlockAtPosition(World world, StructureBoundingBox box, int x, int y, int z, IBlockState state) {
setBlockAtPosition(world, box, x, y, z, state, false);
}
/**
* Sets the block at a position offset by the amounts x/y/z within the bounding box
* @param ignoreBounds if true, will set a block even if it is outside of the structure's bounding box bounds
*/
public static void setBlockAtPosition(World world, StructureBoundingBox box, int x, int y, int z, IBlockState state, boolean ignoreBounds) {
int j1 = getXWithOffset(box, x, z);
int k1 = getYWithOffset(box, y);
int l1 = getZWithOffset(box, x, z);
if (ignoreBounds || box.isVecInside(new Vec3i(j1, k1, l1))) {
world.setBlockState(new BlockPos(j1, k1, l1), state, 2);
}
}
/**
* Sets the block at this position only if the current block is replaceable
*/
public static void setBlockIfReplaceable(World world, BlockPos pos, IBlockState state) {
if (world.getBlockState(pos).getBlock().isReplaceable(world, pos)) {
world.setBlockState(pos, state, 2);
}
}
/**
* Adjusts bounding box up to n blocks up or down if there is any of the given material
* above or below any of the four corners
* @param checkAbove whether to check the blocks above the structure
* @param moveUp whether the structure is moving up
*/
public static void adjustCornersForMaterial(World world, StructureBoundingBox box, Material material, int n, boolean checkAbove, boolean moveUp) {
int count = n;
int i = (moveUp ? 1 : -1);
while (count > 0 && world.getBlockState(new BlockPos(box.maxX, (checkAbove ? box.maxY + 1 : box.minY - 1), box.maxZ)).getBlock().getMaterial() == material) {
--count;
box.offset(0, i, 0);
}
while (count > 0 && world.getBlockState(new BlockPos(box.maxX, (checkAbove ? box.maxY + 1 : box.minY - 1), box.minZ)).getBlock().getMaterial() == material) {
--count;
box.offset(0, i, 0);
}
while (count > 0 && world.getBlockState(new BlockPos(box.minX, (checkAbove ? box.maxY + 1 : box.minY - 1), box.maxZ)).getBlock().getMaterial() == material) {
--count;
box.offset(0, i, 0);
}
while (count > 0 && world.getBlockState(new BlockPos(box.minX, (checkAbove ? box.maxY + 1 : box.minY - 1), box.minZ)).getBlock().getMaterial() == material) {
--count;
box.offset(0, i, 0);
}
}
/**
* Adjusts a bounding box for air blocks above or below so that as little of the structure
* is showing as possible; call before final generation begins
*/
public static void adjustForAir(World world, RoomBase room, StructureBoundingBox box) {
int worldHeight = (room.inNether ? 128 : 160);
int difficulty = (room.inNether ? Config.getNetherDungeonDifficulty() : Config.getMainDungeonDifficulty());
// Ocean and difficulty setting one make no adjustments
if (room.inOcean || difficulty == 1) { return; }
int topCount = getNumBlocksOfMaterial(world, box, Material.air, 1);
int bottomCount = getNumBlocksOfMaterial(world, box, Material.air, -1);
// same number of air blocks on both sides
if (topCount == bottomCount) { return; }
// shifting up needs to check below the structure, and vice versa
boolean shiftUp = bottomCount > topCount;
int maxShift = box.getYSize();
int i = (shiftUp ? 1 : -1);
Vec3i center = box.getCenter();
// Adjust center position first
while (maxShift > 0 && world.isAirBlock(new BlockPos(center.getX(), (shiftUp ? box.minY : box.maxY) - i, center.getZ())) && box.maxY < worldHeight && box.minY > 8) {
--maxShift;
box.offset(0, i, 0);
}
// Adjust corner positions
if (maxShift > 0) {
adjustCornersForMaterial(world, box, Material.air, maxShift, !shiftUp, shiftUp);
}
int newCount = getNumBlocksOfMaterial(world, box, Material.air, shiftUp ? -1 : 1);
// Dungeon surface no longer showing at all; chance of resurfacing depending on difficulty
if (newCount == 0) {
if ((room.inNether && difficulty != 3) || world.rand.nextFloat() < (1.0F - (0.3F * difficulty))) {
box.offset(0, (shiftUp ? -1 : 1), 0);
}
} else if (newCount > (shiftUp ? bottomCount : topCount)) {
box.offset(0, (shiftUp ? -1 : 1) * (box.getYSize() - maxShift), 0);
}
}
/**
* Returns true if the area specified is devoid of any blocks, i.e. there is only air.
* Positions are all absolute coordinates, not relative.
* @param maxX, maxY, and maxZ values are inclusive
* @param minX, minY, and minZ are the lowest values to check - they must not be greater in value than the max values
*/
public static boolean isAreaClear(World world, int minX, int maxX, int minY, int maxY, int minZ, int maxZ) {
for (int i = minX; i <= maxX; ++i) {
for (int j = minY; j <= maxY; ++j) {
for (int k = minZ; k <= maxZ; ++k) {
if (!world.isAirBlock(new BlockPos(i, j, k))) {
return false;
}
}
}
}
return true;
}
/**
* Returns number of blocks of given material either directly above or below the structure
* Only checks 5 points: center and 4 corners
* @param offY checks layer above or below this many blocks; positive value checks above
*/
public static int getNumBlocksOfMaterial(World world, StructureBoundingBox box, Material material, int offY) {
int count = 0;
int y = (offY > 0 ? box.maxY + offY : box.minY + offY);
Vec3i center = box.getCenter();
count += (world.getBlockState(new BlockPos(center.getX(), y, center.getZ())).getBlock().getMaterial() == material ? 1 : 0);
count += (world.getBlockState(new BlockPos(box.maxX, y, box.maxZ)).getBlock().getMaterial() == material ? 1 : 0);
count += (world.getBlockState(new BlockPos(box.maxX, y, box.minZ)).getBlock().getMaterial() == material ? 1 : 0);
count += (world.getBlockState(new BlockPos(box.minX, y, box.maxZ)).getBlock().getMaterial() == material ? 1 : 0);
count += (world.getBlockState(new BlockPos(box.minX, y, box.minZ)).getBlock().getMaterial() == material ? 1 : 0);
return count;
}
/**
* Returns the total number of blocks of the given material within the defined range
* above or below the bounding box
* @param y negative values check that many blocks below; positive above
*/
public static int getNumBlocksOfMaterialInArea(World world, StructureBoundingBox box, Material material, int y) {
if (y < 0) {
return getNumBlocksOfMaterialInArea(world, Material.air, box.minX, box.maxX + 1, box.minY + y - 1, box.minY + y, box.minZ, box.maxZ + 1);
} else {
return getNumBlocksOfMaterialInArea(world, Material.air, box.minX, box.maxX + 1, box.maxY + y, box.maxY + y + 1, box.minZ, box.maxZ + 1);
}
}
/**
* Returns the total number of blocks of the given material within the defined range,
* up to but not including the max boundary
*/
public static int getNumBlocksOfMaterialInArea(World world, Material material, int minX, int maxX, int minY, int maxY, int minZ, int maxZ) {
int count = 0;
for (int i = minX; i < maxX; ++i) {
for (int j = minY; j < maxY; ++j) {
for (int k = minZ; k < maxZ; ++k) {
if (world.getBlockState(new BlockPos(i, j, k)).getBlock().getMaterial() == material) {
++count;
}
}
}
}
return count;
}
//=========================================================================//
//======================= ROTATION-FRIENDLY METHODS =======================//
//=========================================================================//
/** The directional values associated with player facing: */
public static final int SOUTH = 0, WEST = 1, NORTH = 2, EAST = 3;
/**
* Returns amount to offset the x coordinate based on facing, assuming default facing of EAST
* @param dx The default offset for x coordinate
* @param dz The default offset for z coordinate
* @return Value r such that (x + r) is the correct x position for this facing
*/
public static int getOffsetX(int dx, int dz, int facing) {
switch (facing) {
case EAST: return dx;
case NORTH: return dz;
case WEST: return -dx;
case SOUTH: return -dz;
default: return dx;
}
}
/**
* Returns amount to offset the z coordinate based on facing, assuming default facing of EAST
* @param dx The default offset for x coordinate
* @param dz The default offset for z coordinate
* @return Value r such that (z + r) is the correct z position for this facing
*/
public static int getOffsetZ(int dx, int dz, int facing) {
switch (facing) {
case EAST: return dz;
case NORTH: return -dx;
case WEST: return -dz;
case SOUTH: return dx;
default: return dz;
}
}
/**
* Returns the correct metadata value for the block type based on the facing given
* and the original metadata, where the original metadata should be the metadata
* providing the desired orientation for a facing of EAST.
*
* @param facing The facing to which this block is being rotated
* @param rotations The number of rotations to apply
* @param block The block being rotated
* @param origMeta The block's original metadata value
*/
public static final int getMetadata(int facing, Block block, int origMeta) {
if (BlockRotationData.getBlockRotationType(block) == null) {
return origMeta; // no rotation data, return original metadata value
}
int meta = origMeta;
int bitface;
int tickDelay = (meta >> 2);// used by repeaters, comparators, etc.
int bit4 = (meta & 4); // most commonly used for actual rotation
int bit8 = (meta & 8); // usually 'on' or 'off' flag, but also top/bottom for doors
int bit9 = (meta >> 3); // used by pistons for something, can't remember what...
int extra = (meta & ~3); // used by doors for hinge orientation, I think
// east is 3 (0 rotations), north 2 (3 rot), west 1 (2 rot), south 0 (1 rot)
int rotations = rotateRight[facing];
for (int i = 0; i < rotations; ++i) {
bitface = meta % 4;
switch(BlockRotationData.getBlockRotationType(block)) {
case ANVIL:
meta ^= 1;
break;
case DOOR:
if (bit8 != 0) return meta;
meta = (bitface == 3 ? 0 : bitface + 1);
meta |= extra;
break;
case GENERIC:
meta = (bitface == 3 ? 0 : bitface + 1) | bit4 | bit8;
break;
case PISTON_CONTAINER:
meta -= meta > 7 ? 8 : 0;
if (meta > 1) meta = meta == 2 ? 5 : meta == 5 ? 3 : meta == 3 ? 4 : 2;
meta |= bit8 | bit9 << 3;
break;
case QUARTZ:
meta = meta == 3 ? 4 : meta == 4 ? 3 : meta;
break;
case RAIL:
if (meta < 2) meta ^= 1;
else if (meta < 6) meta = meta == 2 ? 5 : meta == 5 ? 3 : meta == 3 ? 4 : 2;
else meta = meta == 9 ? 6 : meta + 1;
break;
case REPEATER:
meta = (bitface == 3 ? 0 : bitface + 1) | (tickDelay << 2);
break;
case SIGNPOST:
meta = meta < 12 ? meta + 4 : meta - 12;
break;
case SKULL:
meta = meta == 1 ? 1 : meta == 4 ? 2 : meta == 2 ? 5 : meta == 5 ? 3 : 4;
break;
case STAIRS:
meta = (bitface == 0 ? 2 : bitface == 2 ? 1 : bitface == 1 ? 3 : 0) | bit4;
break;
case TRAPDOOR:
meta = (bitface == 0 ? 3 : bitface == 3 ? 1 : bitface == 1 ? 2 : 0) | bit4 | bit8;
break;
case VINE:
meta = meta == 1 ? 2 : meta == 2 ? 4 : meta == 4 ? 8 : 1;
break;
case WALL_MOUNTED:
if (meta > 0 && meta < 5) meta = meta == 4 ? 1 : meta == 1 ? 3 : meta == 3 ? 2 : 4;
break;
case LEVER:
meta -= meta > 7 ? 8 : 0;
if (meta > 0 && meta < 5) meta = meta == 4 ? 1 : meta == 1 ? 3 : meta == 3 ? 2 : 4;
else if (meta == 5 || meta == 6) meta = meta == 5 ? 6 : 5;
else meta = meta == 7 ? 0 : 7;
meta |= bit8;
break;
case WOOD:
if (meta > 4 && meta < 12) meta = meta < 8 ? meta + 4 : meta - 4;
break;
default:
break;
}
}
return meta;
}
/**
* Fixes blocks' state after they've been placed in the world, specifically for blocks
* such as rails, furnaces, etc. whose orientation is automatically determined by the block
* when placed via the onBlockAdded method.
*/
public static final void setMetadata(World world, BlockPos pos, IBlockState origState) {
Block block = world.getBlockState(pos).getBlock();
if (BlockRotationData.getBlockRotationType(block) == null) {
return;
}
switch(BlockRotationData.getBlockRotationType(block)) {
case PISTON_CONTAINER: world.setBlockState(pos, origState, 2); break;
case RAIL: world.setBlockState(pos, origState, 2); break;
default: break;
}
}
/**
* Fills area with the given block, with the area rotated based on facing such that,
* if a player's facing is given, minX is always closest to the player, maxX furthest
* away, minZ to the left, and maxZ to the right, all relative to x/y/z.
*
* All min/max parameters are inclusive, so min/maxX={0,4} will place 5 blocks, starting at
* x and going up to and including x+4.
*
* Negative values are acceptable: min/maxX={-2,2} will place blocks from x-2 to x+2.
*
* @param x x-coordinate around which to set blocks (and rotate around, if necessary)
* @param z z-coordinate around which to set blocks (and rotate around, if necessary)
* @param minX x+minX is the initial x coordinate at which to begin setting blocks
* @param maxX x+maxX is the highest x coordinate to set
* @param minY initial y position
* @param maxY maximum y position
* @param minZ z+minZ is the initial z coordinate at which to begin setting blocks
* @param maxZ z+maxZ is the highest z coordinate to set
* @param facing Default is considered EAST
* @param meta This should already be rotated to the correct value, to avoid processing every single call to setBlock
*/
public static void rotatedFillWithBlocks(World world, int x, int z, int minX, int maxX, int minY, int maxY, int minZ, int maxZ, int facing, IBlockState state) {
for (int i = minX; i <= maxX; ++i) {
for (int j = minY; j <= maxY; ++j) {
for (int k = minZ; k <= maxZ; ++k) {
world.setBlockState(new BlockPos(x + getOffsetX(i, k, facing), j, z + getOffsetZ(i, k, facing)), state, 2);
}
}
}
}
/**
* Returns whether the designated area is devoid of entities and non-air blocks, with the
* area defined in relative terms and rotated as necessary based on the given facing.
* Relative positions define the size of the structure, such that:
* [backX - frontX] + 1 = the total length of the structure, thus backX must be greater than frontX
* [maxY - minY] + 1 = the total height of the structure
* [rightZ - leftZ] + 1 = the total width of the structure, thus rightZ must be greater than leftZ
*
* All bounds are inclusive (thus the +1 used above when determining structure dimensions).
*
* Example:
* canGenerate(world, x, z, 0, 9, 64, 68, -3, 6, facing), starting from the block clicked
* at x/64/z, will check the area enclosed by going 9 blocks forward, 3 blocks to the left,
* and 6 blocks to the right, starting from y=64 to y=68, for a total area of 10x5x10.
*
* @param frontX The starting position, relative to x, at which to check for blocks (usually 0)
* @param backX The furthest position, relative to x, at which to check for blocks (usually > 0)
* @param minY Absolute minimum y position at which to check for blocks
* @param maxY Absolute topmost y position at which to check for blocks
* @param leftZ The leftmost position, relative to z, at which to check for blocks (usually < 0)
* @param rightZ The rightmost position, relative to z, at which to check for blocks (usually > 0)
* @param facing Usually the direction the player is facing when generating the structure
* @return True if there were no blocks (other than air) or entities found in the area
*/
public static boolean isRotatedAreaClear(World world, int x, int z, int frontX, int backX, int minY, int maxY, int leftZ, int rightZ, int facing) {
int front = Math.min(x + getOffsetX(frontX, leftZ, facing), x + getOffsetX(frontX, rightZ, facing));
int back = Math.max(x + getOffsetX(backX, leftZ, facing), x + getOffsetX(backX, rightZ, facing));
int left = Math.min(z + getOffsetZ(frontX, leftZ, facing), z + getOffsetZ(frontX, rightZ, facing));
int right = Math.max(z + getOffsetZ(backX, leftZ, facing), z + getOffsetZ(backX, rightZ, facing));
// determine min and max after accounting for rotation
int minX = Math.min(front, back);
int maxX = Math.max(front, back);
int minZ = Math.min(left, right);
int maxZ = Math.max(left, right);
List<Entity> entities = world.getEntitiesWithinAABB(Entity.class, new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ));
return (entities == null || entities.size() == 0) && isAreaClear(world, minX, maxX, minY, maxY, minZ, maxZ);
}
}