package com.bergerkiller.bukkit.common.utils;
import java.util.ArrayList;
import java.util.Collection;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.Chest;
import org.bukkit.block.Sign;
import org.bukkit.event.block.BlockCanBuildEvent;
import org.bukkit.event.block.BlockRedstoneEvent;
import org.bukkit.material.Attachable;
import org.bukkit.material.Lever;
import org.bukkit.material.MaterialData;
import org.bukkit.material.PoweredRail;
import org.bukkit.material.Rails;
import org.bukkit.material.Directional;
import com.bergerkiller.bukkit.common.bases.IntVector3;
import com.bergerkiller.bukkit.common.conversion.Conversion;
import com.bergerkiller.bukkit.common.internal.CommonNMS;
import com.bergerkiller.bukkit.common.protocol.CommonPacket;
import com.bergerkiller.bukkit.common.reflection.SafeField;
import com.bergerkiller.bukkit.common.reflection.classes.BlockStateRef;
import com.bergerkiller.bukkit.common.reflection.classes.TileEntityRef;
import com.bergerkiller.bukkit.common.reflection.classes.WorldRef;
/**
* Multiple Block utilities you can use to manipulate blocks and get block information
*/
public class BlockUtil extends MaterialUtil {
static {
// Temporary hack because Bukkit is updating far too slowly
try {
if (Material.ACTIVATOR_RAIL.getData() == MaterialData.class) {
SafeField.set(Material.ACTIVATOR_RAIL, "ctor", PoweredRail.class.getConstructor(int.class, byte.class));
}
} catch (Throwable t) {
t.printStackTrace();
}
}
/**
* Performs an event asking other plugins whether a block can change to a different Material
*
* @param block to check
* @param type that the block is about to be set to (built)
* @return True if permitted, False if not
*/
public static boolean canBuildBlock(org.bukkit.block.Block block, Material type) {
return canBuildBlock(block, type, true);
}
/**
* Performs an event asking other plugins whether a block can change to a different Material
*
* @param block to check
* @param type that the block is about to be set to (built)
* @param isBuildable - Initial allow state
* @return True if permitted, False if not
*/
@SuppressWarnings("deprecation")
public static boolean canBuildBlock(org.bukkit.block.Block block, Material type, boolean isBuildable) {
return CommonUtil.callEvent(new BlockCanBuildEvent(block, type.getId(), true)).isBuildable();
}
/**
* Sets the Block type and data at once, then performs physics
*
* @param block to set the type and data of
* @param type to set to
* @param data to set to
*/
public static void setTypeAndData(org.bukkit.block.Block block, Material type, MaterialData data) {
setTypeAndData(block, type, data, true);
}
/**
* Sets the Block type and data at once
*
* @param block to set the type and data of
* @param type to set to
* @param data to set to
* @param update - whether to perform physics afterwards
*/
@SuppressWarnings("deprecation")
public static void setTypeAndData(org.bukkit.block.Block block, Material type, MaterialData data, boolean update) {
block.setTypeIdAndData(type.getId(), data.getData(), update);
}
/**
* Sets the Block type and data at once, then performs physics
*
* @param block to set the type and data of
* @param type to set to
* @param data to set to
*/
public static void setTypeAndRawData(org.bukkit.block.Block block, Material type, int data) {
setTypeAndRawData(block, type, data, true);
}
/**
* Sets the Block type and data at once
*
* @param block to set the type and data of
* @param type to set to
* @param data to set to
* @param update - whether to perform physics afterwards
*/
@SuppressWarnings("deprecation")
public static void setTypeAndRawData(org.bukkit.block.Block block, Material type, int data, boolean update) {
block.setTypeIdAndData(type.getId(), (byte) data, update);
}
/**
* Sets the Material Data for a Block
*
* @param block to set it for
* @param materialData to set to
*/
@SuppressWarnings("deprecation")
public static void setData(org.bukkit.block.Block block, MaterialData materialData) {
block.setData(materialData.getData());
}
/**
* Sets the Material Data for a Block
*
* @param block to set it for
* @param materialData to set to
* @param doPhysics - True to perform physics, False for 'silent'
*/
@SuppressWarnings("deprecation")
public static void setData(org.bukkit.block.Block block, MaterialData materialData, boolean doPhysics) {
block.setData(materialData.getData(), doPhysics);
}
/**
* Directly obtains the Material Data from the block<br>
* This alternative does not create a Block State and is preferred if you only need material data
*/
public static MaterialData getData(org.bukkit.block.Block block) {
return getData(MaterialUtil.getTypeId(block), MaterialUtil.getRawData(block));
}
/**
* Gets the Material data for the block specified and attempts to cast it
*
* @param block to get the Material data for
* @param type to cast to
* @return The cast material data, or null if there was no data or if casting failed
*/
public static <T> T getData(org.bukkit.block.Block block, Class<T> type) {
return CommonUtil.tryCast(getData(block), type);
}
/**
* Calculates the so-called 'Manhatten Distance' between two locations<br>
* This is the distance between two points without going diagonally
*
* @param b1 location
* @param b2 location
* @param checkY state, True to include the y distance, False to exclude it
* @return The manhattan distance
*/
public static int getManhattanDistance(Location b1, Location b2, boolean checkY) {
int d = Math.abs(b1.getBlockX() - b2.getBlockX());
d += Math.abs(b1.getBlockZ() - b2.getBlockZ());
if (checkY)
d += Math.abs(b1.getBlockY() - b2.getBlockY());
return d;
}
/**
* Calculates the so-called 'Manhatten Distance' between two blocks<br>
* This is the distance between two points without going diagonally
*
* @param b1 block
* @param b2 block
* @param checkY state, True to include the y distance, False to exclude it
* @return The Manhattan distance
*/
public static int getManhattanDistance(org.bukkit.block.Block b1, org.bukkit.block.Block b2, boolean checkY) {
int d = Math.abs(b1.getX() - b2.getX());
d += Math.abs(b1.getZ() - b2.getZ());
if (checkY)
d += Math.abs(b1.getY() - b2.getY());
return d;
}
/**
* Checks if two Blocks are equal
*
* @param block1 to evaluate
* @param block2 to evaluate
* @return True if the blocks are the same, False if not
*/
public static boolean equals(org.bukkit.block.Block block1, org.bukkit.block.Block block2) {
if (block1 == null || block2 == null)
return false;
if (block1 == block2)
return true;
return block1.getX() == block2.getX() && block1.getZ() == block2.getZ() && block1.getY() == block2.getY() && block1.getWorld() == block2.getWorld();
}
/**
* Gets all the Blocks relative to a main block using multiple Block Faces
*
* @param main block
* @param faces to get the blocks relative to the main of
* @return An array of relative blocks to the main based on the input faces
*/
public static org.bukkit.block.Block[] getRelative(org.bukkit.block.Block main, BlockFace... faces) {
if (main == null) {
return new org.bukkit.block.Block[0];
}
org.bukkit.block.Block[] rval = new org.bukkit.block.Block[faces.length];
for (int i = 0; i < rval.length; i++) {
rval[i] = main.getRelative(faces[i]);
}
return rval;
}
/**
* Gets the Block at the coordinates specified
*
* @param world of the block
* @param at coordinates
* @return Block at the coordinates in the world
*/
public static org.bukkit.block.Block getBlock(org.bukkit.World world, IntVector3 at) {
return world.getBlockAt(at.x, at.y, at.z);
}
/**
* Gets the face an attachable block is attached to<br>
* Returns DOWN if the block is not attachable
*
* @param attachable block
* @return Attached face
*/
public static BlockFace getAttachedFace(org.bukkit.block.Block attachable) {
Attachable data = getData(attachable, Attachable.class);
return data == null ? BlockFace.DOWN : data.getAttachedFace();
}
/**
* Gets the Block an attachable block is attached to
*
* @param attachable block
* @return Block the attachable is attached to
*/
public static org.bukkit.block.Block getAttachedBlock(org.bukkit.block.Block attachable) {
return attachable.getRelative(getAttachedFace(attachable));
}
/**
* Gets the facing direction of a Directional block
*
* @param directional block
* @return facing direction
*/
public static BlockFace getFacing(org.bukkit.block.Block directional) {
Directional data = getData(directional, Directional.class);
return data == null ? BlockFace.NORTH : data.getFacing();
}
/**
* Sets the facing direction of a Directional block
*
* @param block to set
* @param facing direction to set to
*/
public static void setFacing(org.bukkit.block.Block block, BlockFace facing) {
MaterialData data = getData(block);
if (data != null && data instanceof Directional) {
((Directional) data).setFacingDirection(facing);
setData(block, data, true);
}
}
/**
* Sets the toggled state for all levers attached to a certain block
*
* @param block center
* @param down state to set to
*/
public static void setLeversAroundBlock(org.bukkit.block.Block block, boolean down) {
org.bukkit.block.Block b;
for (BlockFace dir : FaceUtil.ATTACHEDFACES) {
// Attached lever at this direction?
if (isType(b = block.getRelative(dir), Material.LEVER) && getAttachedFace(b) == dir.getOppositeFace()) {
setLever(b, down);
}
}
}
/**
* Checks if a given lever block is in the down state<br>
* The block type is not checked.
*
* @param lever block
* @return True if the lever is down, False if not
*/
public static boolean isLeverDown(org.bukkit.block.Block lever) {
int dat = getRawData(lever);
return dat == (dat | 0x8);
}
/**
* Sets the toggled state of a single lever<br>
* <b>No Lever type check is performed</b>
*
* @param lever block
* @param down state to set to
*/
public static void setLever(org.bukkit.block.Block lever, boolean down) {
int data = getRawData(lever);
Lever newMaterialData = (Lever) getData(Material.LEVER, data);
newMaterialData.setPowered(down);
if (getRawData(newMaterialData) != data) {
// CraftBukkit start - Redstone event for lever
int old = !down ? 1 : 0;
int current = down ? 1 : 0;
BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(lever, old, current);
CommonUtil.callEvent(eventRedstone);
if ((eventRedstone.getNewCurrent() > 0) != down) {
return;
}
// CraftBukkit end
setData(lever, newMaterialData, true);
applyPhysics(getAttachedBlock(lever), Material.LEVER);
}
}
/**
* Performs Physics at the block specified
*
* @param block to apply physics to
* @param callertypeid of the Material, the source of these physics (use 0 if there is no caller)
*/
@Deprecated
public static void applyPhysics(org.bukkit.block.Block block, int callertypeid) {
applyPhysics(block, getType(callertypeid));
}
/**
* Performs Physics at the block specified
*
* @param block to apply physics to
* @param callerType Material, the source of these physics (use Air if there is no caller)
*/
public static void applyPhysics(org.bukkit.block.Block block, Material callerType) {
CommonNMS.getNative(block.getWorld()).applyPhysics(block.getX(), block.getY(), block.getZ(), CommonNMS.getBlock(callerType));
}
/**
* Obtains a new packet that can be used to update tile entity information to nearby players<br>
* Returns null if none are needed or supported by the tile entity
*
* @param tileEntity to get the update packet for
* @return update packet
*/
public static CommonPacket getUpdatePacket(Object tileEntity) {
return TileEntityRef.getUpdatePacket(tileEntity);
}
/**
* Sets the alignment of Rails
* Note that this only supports the rails that can curve.
*
* @param rails to set the alignment for
* @param from direction
* @param to direction
*/
public static void setRails(org.bukkit.block.Block rails, BlockFace from, BlockFace to) {
setRails(rails, FaceUtil.combine(from, to).getOppositeFace());
}
/**
* Sets the alignment of Rails.
* Note that this only supports the rails that can curve.
*
* @param rails to set the alignment for
* @param direction alignment
*/
public static void setRails(org.bukkit.block.Block rails, BlockFace direction) {
Material type = rails.getType();
if (type == Material.RAILS) {
int olddata = getRawData(rails);
Rails r = (Rails) MaterialUtil.getData(type, olddata);
r.setDirection(FaceUtil.toRailsDirection(direction), r.isOnSlope());
// If changed, update the data
if (MaterialUtil.getRawData(r) != olddata) {
setData(rails, r);
}
}
}
/**
* Gets the state of a block, with additional safety measures taken
*
* @param block to get the state for
* @return the Block State
*/
public static BlockState getState(org.bukkit.block.Block block) {
return BlockStateRef.toBlockState(block);
}
/**
* Gets the State of a block, cast to a certain type
*
* @param block to get the state for
* @param type to cast to
* @return The block state cast to the type, or null if casting is not possible
*/
public static <T extends BlockState> T getState(org.bukkit.block.Block block, Class<T> type) {
return CommonUtil.tryCast(getState(block), type);
}
public static Rails getRails(org.bukkit.block.Block railsblock) {
return getData(railsblock, Rails.class);
}
public static Sign getSign(org.bukkit.block.Block signblock) {
return getState(signblock, Sign.class);
}
public static Chest getChest(org.bukkit.block.Block chestblock) {
return getState(chestblock, Chest.class);
}
public static Collection<BlockState> getBlockStates(org.bukkit.block.Block middle) {
return getBlockStates(middle, 0, 0, 0);
}
public static Collection<BlockState> getBlockStates(org.bukkit.block.Block middle, int radius) {
return getBlockStates(middle, radius, radius, radius);
}
public static Collection<BlockState> getBlockStates(org.bukkit.block.Block middle, int radiusX, int radiusY, int radiusZ) {
return getBlockStates(middle.getWorld(), middle.getX(), middle.getY(), middle.getZ(), radiusX, radiusY, radiusZ);
}
private static final ArrayList<BlockState> blockStateBuff = new ArrayList<BlockState>();
public static Collection<BlockState> getBlockStates(org.bukkit.World world, int x, int y, int z, int radiusX, int radiusY, int radiusZ) {
try {
if (radiusX == 0 && radiusY == 0 && radiusZ == 0) {
// simplified coding instead
offerTile(world, x, y, z);
} else {
// loop through tile entity list
int xMin = x - radiusX;
int yMin = y - radiusY;
int zMin = z - radiusZ;
int xMax = x + radiusX;
int yMax = y + radiusY;
int zMax = z + radiusZ;
int tx, ty, tz;
for (Object tile : WorldRef.tileEntityList.get(Conversion.toWorldHandle.convert(world))) {
tx = TileEntityRef.x.get(tile);
ty = TileEntityRef.y.get(tile);
tz = TileEntityRef.z.get(tile);
if (tx < xMin || ty < yMin || tz < zMin || tx > xMax || ty > yMax || tz > zMax) {
continue;
}
// Get again - security against ghost tiles
offerTile(world, tx, ty, tz);
}
}
return new ArrayList<BlockState>(blockStateBuff);
} finally {
blockStateBuff.clear();
}
}
private static void offerTile(World world, int x, int y, int z) {
BlockState state = Conversion.toBlockState.convert(TileEntityRef.getFromWorld(world, x, y, z));
if (state != null) {
blockStateBuff.add(state);
}
}
}