/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.jcwhatever.nucleus.utils;
import com.jcwhatever.nucleus.Nucleus;
import com.jcwhatever.nucleus.managed.scheduler.Scheduler;
import com.jcwhatever.nucleus.utils.coords.LocationUtils;
import com.jcwhatever.nucleus.utils.materials.MaterialProperty;
import com.jcwhatever.nucleus.utils.materials.Materials;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.entity.FallingBlock;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* {@link org.bukkit.block.Block} related utilities.
*/
public final class BlockUtils {
private static Location BLOCK_LOCATION = new Location(null, 0, 0, 0);
private static BlockFace[] CARDINAL_DIRECTIONS = new BlockFace[] {
BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST
};
private static BlockFace[] DIRECTIONS_3D = new BlockFace[] {
BlockFace.UP, BlockFace.DOWN,
BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST
};
private BlockUtils() {}
/**
* Get an immutable list of block faces representing the 4
* cardinal directions.
*/
public static List<BlockFace> getCardinalFaces() {
return Arrays.asList(CARDINAL_DIRECTIONS);
}
/**
* Get an immutable list of block faces representing the 4
* cardinal directions as well as up and down.
*/
public static List<BlockFace> get3DFaces() {
return Arrays.asList(DIRECTIONS_3D);
}
/**
* Get all adjacent blocks that match all of the specified properties.
*
* <p>If no properties are specified, all adjacent blocks are returned.</p>
*
* @param block The block to check.
* @param properties The properties to match.
*/
public static List<Block> getAdjacent(Block block, MaterialProperty... properties) {
return getAdjacent(block, new ArrayList<Block>(6), properties);
}
/**
* Get all adjacent blocks that match all of the specified properties and add them
* to the specified output collection.
*
* <p>If no properties are specified, all adjacent blocks are returned.</p>
*
* @param block The block to check.
* @param output The output collection.
* @param properties The properties to match.
*
* @return The output collection.
*/
public static <T extends Collection<Block>> T getAdjacent(Block block, T output,
MaterialProperty... properties) {
PreCon.notNull(block);
if (output instanceof ArrayList)
((ArrayList) output).ensureCapacity(output.size() + 6);
for (BlockFace face : DIRECTIONS_3D) {
if (block.getY() == 254 && face == BlockFace.UP)
continue;
if (block.getY() == 0 && face == BlockFace.DOWN)
continue;
Block adjacent = block.getRelative(face);
if (properties.length > 0) {
boolean isValid = true;
for (MaterialProperty property : properties) {
if (!Materials.hasProperty(adjacent.getType(), property)) {
isValid = false;
break;
}
}
if (!isValid)
continue;
}
output.add(adjacent);
}
return output;
}
/**
* Get all adjacent blocks that match one of the specified materials.
*
* <p>If no properties are specified, all adjacent blocks are returned.</p>
*
* @param block The block to check.
* @param materials The materials to match.
*/
public static List<Block> getAdjacent(Block block, Material... materials) {
return getAdjacent(block, new ArrayList<Block>(6), materials);
}
/**
* Get all adjacent blocks that match one of the specified materials and add them
* to the specified output collection.
*
* <p>If no properties are specified, all adjacent blocks are returned.</p>
*
* @param block The block to check.
* @param output The output collection.
* @param materials The materials to match.
*
* @return The output collection.
*/
public static <T extends Collection<Block>> T getAdjacent(Block block, T output,
Material... materials) {
PreCon.notNull(block);
if (output instanceof ArrayList)
((ArrayList) output).ensureCapacity(output.size() + 6);
for (BlockFace face : DIRECTIONS_3D) {
if (block.getY() == 254 && face == BlockFace.UP)
continue;
if (block.getY() == 0 && face == BlockFace.DOWN)
continue;
Block adjacent = block.getRelative(face);
if (materials.length > 0) {
boolean isValid = false;
for (Material material : materials) {
if (adjacent.getType() == material) {
isValid = true;
break;
}
}
if (!isValid)
continue;
}
output.add(adjacent);
}
return output;
}
/**
* Get the first adjacent block that matches the specified properties.
*
* <p>Checks block in the order that block faces are returned from {@link #get3DFaces()}.</p>
*
* <p>If no properties are specified, all adjacent blocks are considered.</p>
*
* @param block The block to check.
* @param properties The properties to match.
*
* @return The matching adjacent block or null if none found.
*/
@Nullable
public static Block getFirstAdjacent(Block block, MaterialProperty... properties) {
PreCon.notNull(block);
for (BlockFace face : DIRECTIONS_3D) {
if (block.getY() == 254 && face == BlockFace.UP)
continue;
if (block.getY() == 0 && face == BlockFace.DOWN)
continue;
Block adjacent = block.getRelative(face);
if (properties.length > 0) {
boolean isValid = true;
for (MaterialProperty property : properties) {
if (!Materials.hasProperty(adjacent.getType(), property)) {
isValid = false;
break;
}
}
if (!isValid)
continue;
}
return adjacent;
}
return null;
}
/**
* Get the first adjacent block that matches one of the specified materials.
*
* <p>Checks block in the order that block faces are returned from {@link #get3DFaces()}.</p>
*
* <p>If no properties are specified, all adjacent blocks are considered.</p>
*
* @param block The block to check.
* @param materials The properties to match.
*
* @return The matching adjacent block or null if none found.
*/
@Nullable
public static Block getFirstAdjacent(Block block, Material... materials) {
PreCon.notNull(block);
for (BlockFace face : DIRECTIONS_3D) {
if (block.getY() == 254 && face == BlockFace.UP)
continue;
if (block.getY() == 0 && face == BlockFace.DOWN)
continue;
Block adjacent = block.getRelative(face);
if (materials.length > 0) {
boolean isValid = false;
for (Material material : materials) {
if (adjacent.getType() == material) {
isValid = true;
break;
}
}
if (!isValid)
continue;
}
return adjacent;
}
return null;
}
/**
* Converts the specified block to a falling block.
*
* @param block The block to drop.
*/
public static FallingBlock dropBlock(Block block) {
PreCon.notNull(block);
Location location = Bukkit.isPrimaryThread()
? block.getLocation(BLOCK_LOCATION)
: block.getLocation();
return dropBlock(location);
}
/**
* Converts the block at the specified location to a falling block.
*
* @param location The location of the block to drop.
*/
public static FallingBlock dropBlock(Location location) {
PreCon.notNull(location);
Block block = location.getBlock();
Material material = block.getType();
byte data = block.getData();
block.setType(Material.AIR);
// spawn a falling block
return block.getWorld().spawnFallingBlock(
location, material, data);
}
/**
* Converts the specified block to a falling block and removes the fallen
* block after the specified delay.
*
* <p>If the falling block has not reached the ground and turned into
* a block after the delay has elapsed, the falling block is removed.</p>
*
* @param block The block to drop.
* @param removeDelayTicks The delay in ticks before removing the fallen block.
*/
public static void dropRemoveBlock(Block block, int removeDelayTicks) {
dropRemoveBlock(block.getLocation(), removeDelayTicks);
}
/**
* Converts the block at the specified location to a falling block and
* removes the fallen block after the specified delay.
*
* <p>If the falling block has not reached the ground and turned into
* a block after the delay has elapsed, the falling block is removed.</p>
*
* @param location The location of the block to drop.
* @param removeDelayTicks The delay in ticks before removing the fallen block.
*/
public static void dropRemoveBlock(final Location location, final int removeDelayTicks) {
PreCon.notNull(location);
final BlockState startBlock = location.getBlock().getState();
location.getBlock().setType(Material.AIR);
// schedule task so the block has a chance to turn into air
Scheduler.runTaskLater(Nucleus.getPlugin(), new Runnable() {
@Override
public void run() {
// spawn a falling block
final FallingBlock fallBlock = location.getWorld().spawnFallingBlock(
location, startBlock.getType(), startBlock.getData().getData());
// schedule the removal of the block
Scheduler.runTaskLater(Nucleus.getPlugin(), removeDelayTicks, new Runnable() {
@Override
public void run() {
if (fallBlock.isOnGround() || fallBlock.isDead()) {
// find the fallen block
Location landedLoc = LocationUtils.findSurfaceBelow(location);
if (landedLoc == null)
return;
if (fallBlock.getFallDistance() == 0.0) {
Block landedBlock = landedLoc.getBlock();
if (landedBlock.getType() != startBlock.getType())
return;
landedBlock.setType(Material.AIR);
}
} else {
// remove the falling block if it has not
// become a block yet.
fallBlock.remove();
}
}
});
}
});
}
}