package net.glowstone.block.blocktype; import net.glowstone.EventFactory; import net.glowstone.GlowChunk; import net.glowstone.GlowServer; import net.glowstone.block.GlowBlock; import net.glowstone.block.GlowBlockState; import net.glowstone.block.ItemTable; import net.glowstone.block.entity.TileEntity; import net.glowstone.block.itemtype.ItemType; import net.glowstone.entity.GlowPlayer; import net.glowstone.util.SoundInfo; import org.bukkit.*; import org.bukkit.block.BlockFace; import org.bukkit.event.block.BlockCanBuildEvent; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.material.MaterialData; import org.bukkit.util.Vector; import java.util.*; /** * Base class for specific types of blocks. */ public class BlockType extends ItemType { protected static final Random random = new Random(); protected List<ItemStack> drops = null; protected SoundInfo placeSound = new SoundInfo(Sound.DIG_WOOD, 1F, 0.75F); //////////////////////////////////////////////////////////////////////////// // Setters for subclass use protected final void setDrops(ItemStack... drops) { this.drops = Arrays.asList(drops); } //////////////////////////////////////////////////////////////////////////// // Public accessors /** * Get the items that will be dropped by digging the block. * @param block The block being dug. * @param tool The tool used or {@code null} if fists or no tool was used. * @return The drops that should be returned. */ public Collection<ItemStack> getDrops(GlowBlock block, ItemStack tool) { if (drops == null) { // default calculation return Arrays.asList(new ItemStack(block.getType(), 1, block.getData())); } else { return Collections.unmodifiableList(drops); } } /** * Gets the sound that will be played when a player places the block. * @return The sound to be played */ public SoundInfo getPlaceSound() { return placeSound; } /** * Gets the sound that will be played when a player places the block. * @return The sound to be played */ public void setPlaceSound(Sound sound) { placeSound = new SoundInfo(sound, 1F, 0.75F); } /** * Get the items that will be dropped as if the block would be successfully mined. * This is used f.e. to calculate TNT drops. * @param block The block. * @return The drops from that block. */ public Collection<ItemStack> getMinedDrops(GlowBlock block) { return getDrops(block, null); } //////////////////////////////////////////////////////////////////////////// // Actions /** * Create a new tile entity at the given location. * @param chunk The chunk to create the tile entity at. * @param cx The x coordinate in the chunk. * @param cy The y coordinate in the chunk. * @param cz The z coordinate in the chunk. * @return The new TileEntity, or null if no tile entity is used. */ public TileEntity createTileEntity(GlowChunk chunk, int cx, int cy, int cz) { return null; } /** * Check whether the block can be placed at the given location. * @param block The location the block is being placed at. * @param against The face the block is being placed against. * @return Whether the placement is valid. */ public boolean canPlaceAt(GlowBlock block, BlockFace against) { return true; } /** * Called when a block is placed to calculate what the block will become. * @param player the player who placed the block * @param state the BlockState to edit * @param holding the ItemStack that was being held * @param face the face off which the block is being placed * @param clickedLoc where in the block the click occurred */ public void placeBlock(GlowPlayer player, GlowBlockState state, BlockFace face, ItemStack holding, Vector clickedLoc) { state.setType(getMaterial()); state.setRawData((byte) holding.getDurability()); } /** * Called after a block has been placed by a player. * @param player the player who placed the block * @param block the block that was placed * @param holding the the ItemStack that was being held */ public void afterPlace(GlowPlayer player, GlowBlock block, ItemStack holding, GlowBlockState oldState) { block.applyPhysics(oldState.getType(), block.getTypeId(), oldState.getRawData(), block.getData()); } /** * Called when a player attempts to interact with (right-click) a block of * this type already in the world. * @param player the player interacting * @param block the block interacted with * @param face the clicked face * @param clickedLoc where in the block the click occurred * @return Whether the interaction occurred. */ public boolean blockInteract(GlowPlayer player, GlowBlock block, BlockFace face, Vector clickedLoc) { return false; } /** * Called when a player attempts to destroy a block. * @param player The player interacting * @param block The block the player destroyed * @param face The block face */ public void blockDestroy(GlowPlayer player, GlowBlock block, BlockFace face) { // do nothing } /** * Called after a player succesfully destroys a block. * @param player The player interacting * @param block The block the player destroyed * @param face The block face */ public void afterDestroy(GlowPlayer player, GlowBlock block, BlockFace face, GlowBlockState oldState) { block.applyPhysics(oldState.getType(), block.getTypeId(), oldState.getRawData(), block.getData()); } /** * Called when the BlockType gets pulsed as requested. * @param block The block that was pulsed pulsed */ public void receivePulse(GlowBlock block) { // Cancel if pulse sent to empty block data (caused when updated and not removed). block.getWorld().cancelPulse(block); } /** * Called when a player attempts to place a block on an existing block of * this type. Used to determine if the placement should occur into the air * adjacent to the block (normal behavior), or absorbed into the block * clicked on. * @param block The block the player right-clicked * @param face The face on which the click occurred * @param holding The ItemStack the player was holding * @return Whether the place should occur into the block given. */ public boolean canAbsorb(GlowBlock block, BlockFace face, ItemStack holding) { return false; } /** * Called to check if this block can be overridden by a block place * which would occur inside it. * @param block The block being targeted by the placement * @param face The face on which the click occurred * @param holding The ItemStack the player was holding * @return Whether this block can be overridden. */ public boolean canOverride(GlowBlock block, BlockFace face, ItemStack holding) { return block.isLiquid(); } /** * Called when a neighboring block (within a 3x3x3 cube) has changed its * type or data and physics checks should occur. * @param block The block to perform physics checks for * @param face The BlockFace to the changed block, or null if unavailable * @param changedBlock The neighboring block that has changed * @param oldType The old type of the changed block * @param oldData The old data of the changed block * @param newType The new type of the changed block * @param newData The new data of the changed block */ public void onNearBlockChanged(GlowBlock block, BlockFace face, GlowBlock changedBlock, Material oldType, byte oldData, Material newType, byte newData) { } /** * Called when this block has just changed to some other type. This is * called whenever {@link GlowBlock#setTypeIdAndData}, {@link GlowBlock#setType} * or {@link GlowBlock#setData} is called with physics enabled, and might * be called from plugins or other means of changing the block. * @param block The block that was changed * @param oldType The old Material * @param oldData The old data * @param newType The new Material * @param data The new data */ public void onBlockChanged(GlowBlock block, Material oldType, byte oldData, Material newType, byte data) { // do nothing } /** * Called when the BlockType should calculate the current physics. * @param block The block */ public void updatePhysics(GlowBlock block) { // do nothing } @Override public final void rightClickBlock(GlowPlayer player, GlowBlock against, BlockFace face, ItemStack holding, Vector clickedLoc) { GlowBlock target = against.getRelative(face); // prevent building above the height limit if (target.getLocation().getY() >= target.getWorld().getMaxHeight()) { player.sendMessage(ChatColor.RED + "The height limit for this world is " + target.getWorld().getMaxHeight() + " blocks"); return; } // check whether the block clicked against should absorb the placement BlockType againstType = ItemTable.instance().getBlock(against.getTypeId()); if (againstType.canAbsorb(against, face, holding)) { target = against; } else if (!target.isEmpty()) { // air can always be overridden BlockType targetType = ItemTable.instance().getBlock(target.getTypeId()); if (!targetType.canOverride(target, face, holding)) { return; } } // call canBuild event boolean canBuild = canPlaceAt(target, face); BlockCanBuildEvent canBuildEvent = new BlockCanBuildEvent(target, getId(), canBuild); if (!EventFactory.callEvent(canBuildEvent).isBuildable()) { //revert(player, target); return; } // grab states and update block GlowBlockState oldState = target.getState(), newState = target.getState(); placeBlock(player, newState, face, holding, clickedLoc); newState.update(true); // call blockPlace event BlockPlaceEvent event = new BlockPlaceEvent(target, oldState, against, holding, player, canBuild); EventFactory.callEvent(event); if (event.isCancelled() || !event.canBuild()) { oldState.update(true); return; } // play a sound effect getPlaceSound().play(target.getLocation()); // do any after-place actions afterPlace(player, target, holding, oldState); // deduct from stack if not in creative mode if (player.getGameMode() != GameMode.CREATIVE) { holding.setAmount(holding.getAmount() - 1); } } /** * Called to check if this block can perform random tick updates. * @return Whether this block updates on tick. */ public boolean canTickRandomly() { return false; } /** * Called when this block needs to be updated. * @param block The block that needs an update */ public void updateBlock(GlowBlock block) { // do nothing } /** * Called when a player left clicks a block * @param player the player who clicked the block * @param block the block that was clicked * @param holding the ItemStack that was being held */ public void leftClickBlock(GlowPlayer player, GlowBlock block, ItemStack holding) { // do nothing } //////////////////////////////////////////////////////////////////////////// // Helper methods /** * Display the warning for finding the wrong MaterialData subclass. * @param clazz The expected subclass of MaterialData. * @param data The actual MaterialData found. */ protected void warnMaterialData(Class<?> clazz, MaterialData data) { GlowServer.logger.warning("Wrong MaterialData for " + getMaterial() + " (" + getClass().getSimpleName() + "): expected " + clazz.getSimpleName() + ", got " + data); } /** * Gets the BlockFace opposite of the direction the location is facing. * Usually used to set the way container blocks face when being placed. * @param location Location to get opposite of * @param inverted If up/down should be used * @return Opposite BlockFace or EAST if yaw is invalid */ protected static BlockFace getOppositeBlockFace(Location location, boolean inverted) { double rot = location.getYaw() % 360; if (inverted) { // todo: Check the 67.5 pitch in source. This is based off of WorldEdit's number for this. double pitch = location.getPitch(); if (pitch < -67.5D) { return BlockFace.DOWN; } else if (pitch > 67.5D) { return BlockFace.UP; } } if (rot < 0) { rot += 360.0; } if (0 <= rot && rot < 45) { return BlockFace.NORTH; } else if (45 <= rot && rot < 135) { return BlockFace.EAST; } else if (135 <= rot && rot < 225) { return BlockFace.SOUTH; } else if (225 <= rot && rot < 315) { return BlockFace.WEST; } else if (315 <= rot && rot < 360.0) { return BlockFace.NORTH; } else { return BlockFace.EAST; } } public void onRedstoneUpdate(GlowBlock block) { // do nothing } }