package com.supaham.commons.bukkit.utils;
import com.google.common.base.Preconditions;
import com.supaham.commons.bukkit.CommonPlugin;
import com.supaham.commons.bukkit.SingleSound;
import com.supaham.commons.bukkit.TickerTask;
import org.bukkit.Effect;
import org.bukkit.Material;
import org.bukkit.block.Banner;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.inventory.meta.BannerMeta;
import org.bukkit.material.TrapDoor;
import javax.annotation.Nonnull;
/**
* Utility methods for working with {@link Block} instances. This class contains methods such as
* {@link #isDoor(Block)}, {@link #openDoor(Block)}, {@link #closeDoor(Block)}, and more.
*
* @since 0.1
*/
public final class BlockUtils {
/**
* Checks whether a {@link Block} is a door.
*
* @param block block to check
*
* @return whether the {@code block} is a door
*/
public static boolean isDoor(Block block) {
return block.getType().name().contains("DOOR");
}
/**
* Checks whether a {@link Block} is the top part of a door.
* <br/>
* <b>NOTE: this doesn't actually test if the block is a door!</b>
*
* @param block block to check
*
* @return whether the {@code block} is the top part of a door
*/
public static boolean isTopDoorBlock(Block block) {
return (block.getData() & 0x8) == 0x8;
}
/**
* Gets the bottom part of a door {@link Block} from a {@link Block}.
* <br/>
* <b>NOTE: this doesn't actually test if the block is a door!</b>
*
* @param block block to get bottom door part from
*
* @return bottom part of a door. The same {@code block} that was passed to this method is
* returned if it is already the bottom door block.
*/
public static Block getBottomDoorBlock(Block block) {
return isTopDoorBlock(block) ? block.getRelative(BlockFace.DOWN) : block;
}
/**
* Checks whether a door {@link Block} is closed. This method supports:
* <br/>
* <ul>
* <li>{@link Material#TRAP_DOOR}</li>
* <li>All types of two high doors</li>
* </ul>
*
* <br/>
* <b>NOTE: this doesn't actually test if the block is a door!</b>
*
* @param block door block to check
*
* @return whether the {@code block} is closed
*/
public static boolean isDoorClosed(Block block) {
if (!isDoor(block)) {
return false;
}
if (block.getType() == Material.TRAP_DOOR) {
TrapDoor trapdoor = (TrapDoor) block.getState().getData();
return !trapdoor.isOpen();
} else {
return ((getBottomDoorBlock(block).getData() & 0x4) == 0);
}
}
/**
* Toggles a door's state (represents as a {@link Block}). If the door is open it will be closed,
* vice versa.
*
* @param block block where the door is
*
* @return true if the door was opened, false if the door was closed
*/
public static boolean toggleDoor(Block block) {
boolean closed = isDoorClosed(block);
if (closed) {
openDoor(block);
} else {
closeDoor(block);
}
return closed;
}
/**
* Toggles a door's state (represents as a {@link Block}). Unlike {@link #toggleDoor(Block)}
* this method takes a boolean, which if set to true calls {@link #openDoor(Block)} and if set to
* false calls {@link #closeDoor(Block)}. There is no actual automated toggling.
*
* @param block block where the door is
* @param open whether to open or close the door
*/
public static void toggleDoor(Block block, boolean open) {
if (open) {
openDoor(block);
} else {
closeDoor(block);
}
}
/**
* Opens a door {@link Block}. This method takes no action if the given block is not a door, or
* if it is already open.
*
* @param block door block to open
*
* @return whether any action was taken. The cases in which it is false is if the block is not a
* door, or the door is already open.
*/
public static boolean openDoor(Block block) {
if (!isDoor(block)) {
return false;
}
if (block.getType() == Material.TRAP_DOOR) {
BlockState state = block.getState();
TrapDoor trapdoor = (TrapDoor) state.getData();
trapdoor.setOpen(true);
state.update();
return true;
} else {
block = getBottomDoorBlock(block);
if (isDoorClosed(block)) {
block.setData((byte) (block.getData() | 0x4), true);
block.getWorld().playEffect(block.getLocation(), Effect.DOOR_TOGGLE, 0);
return true;
}
}
return false;
}
/**
* Closes a door {@link Block}. This method takes no action if the given block is not a door, or
* if it is already open.
*
* @param block door block to open
*
* @return whether any action was taken. The cases in which it is false is if the block is not a
* door, or the door is already closed.
*/
public static boolean closeDoor(Block block) {
if (!isDoor(block)) {
return false;
}
if (block.getType() == Material.TRAP_DOOR) {
BlockState state = block.getState();
TrapDoor trapdoor = (TrapDoor) state.getData();
trapdoor.setOpen(false);
state.update();
return true;
} else {
block = getBottomDoorBlock(block);
if (!isDoorClosed(block)) {
block.setData((byte) (block.getData() & 0xb), true);
block.getWorld().playEffect(block.getLocation(), Effect.DOOR_TOGGLE, 0);
return true;
}
}
return false;
}
/**
* Returns whether snow may be placed under a {@link Block}.
*
* @param block block to check
*
* @return whether snow may be placed
*/
public static boolean canPlaceSnowUnderneath(@Nonnull Block block) {
Preconditions.checkNotNull(block, "block cannot be null.");
block = block.getRelative(BlockFace.DOWN);
byte d = block.getData();
Material type = block.getType();
return type != Material.ICE && type != Material.PACKED_ICE
&& (MaterialUtils.isLeaves(type) || (type == Material.SNOW && d >= 7 || type.isSolid()));
}
/**
* Sets up a {@link Block} with the properties of a {@link BannerMeta}.
*
* @param block block to modify
* @param bannerMeta banner metadata to apply to {@code block}
*
* @throws IllegalArgumentException if the {@code block} is not {@link Material#STANDING_BANNER}
* or {@link Material#WALL_BANNER}
*/
public static void setBlockBanner(Block block, BannerMeta bannerMeta)
throws IllegalArgumentException {
setBlockBanner(block.getState(), bannerMeta);
}
/**
* Sets up a {@link BlockState} with the properties of a {@link BannerMeta}. This method finally
* calls {@link BlockState#update(boolean, boolean)} with the first boolean as {@code true} and
* the second as {@code false}, causing an update but without any physics checks.
*
* @param state block state to modify
* @param bannerMeta banner metadata to apply to {@code block}
*
* @throws IllegalArgumentException if the {@code block} is not {@link Material#STANDING_BANNER}
* or {@link Material#WALL_BANNER}
*/
public static void setBlockBanner(BlockState state, BannerMeta bannerMeta) {
Preconditions.checkArgument(state instanceof Banner, "block state is not of Banner.");
Banner bannerState = (Banner) state;
bannerState.setBaseColor(bannerMeta.getBaseColor());
bannerState.setPatterns(bannerMeta.getPatterns());
bannerState.update(true, false);
}
public static boolean pressPressurePlate(@Nonnull CommonPlugin plugin, @Nonnull Block block) {
return pressPressurePlate(plugin, block, true);
}
public static boolean pressPressurePlate(@Nonnull CommonPlugin plugin, @Nonnull Block block,
boolean playSound) {
return pressPressurePlate(plugin, block, 10, playSound);
}
public static boolean pressPressurePlate(@Nonnull CommonPlugin plugin, @Nonnull final Block block,
int pressedTicks) {
return pressPressurePlate(plugin, block, pressedTicks, true);
}
public static boolean pressPressurePlate(@Nonnull CommonPlugin plugin, @Nonnull final Block block,
int pressedTicks, final boolean playSound) {
Preconditions.checkNotNull(plugin, "plugin cannot be null.");
Preconditions.checkNotNull(block, "block cannot be null.");
Preconditions.checkArgument(pressedTicks >= 0, "pressed ticks cannot be less than 0.");
if (!MaterialUtils.equals(block.getType(), Material.GOLD_PLATE, Material.IRON_AXE,
Material.WOOD_PLATE, Material.STONE_PLATE)) {
return false;
}
block.setData((byte) 1, false);
if (playSound) {
new SingleSound("random.click", 0.3f, 0.5f)
.play(block.getWorld(), block.getLocation().add(0.5, 0.1, 0.5));
}
new TickerTask(plugin, pressedTicks) {
@Override public void run() {
if (MaterialUtils.equals(block.getType(), Material.GOLD_PLATE, Material.IRON_AXE,
Material.WOOD_PLATE, Material.STONE_PLATE)) {
block.setData((byte) 0, false);
if (playSound) {
new SingleSound("random.click", 0.3f, 0.5f)
.play(block.getWorld(), block.getLocation().add(0.5, 0.1, 0.5));
}
}
}
}.start();
return true;
}
private BlockUtils() {}
}