package net.glowstone.net.handler.play.player;
import com.flowpowered.network.MessageHandler;
import net.glowstone.EventFactory;
import net.glowstone.GlowServer;
import net.glowstone.block.GlowBlock;
import net.glowstone.block.ItemTable;
import net.glowstone.block.blocktype.BlockType;
import net.glowstone.block.entity.BlockEntity;
import net.glowstone.block.itemtype.ItemType;
import net.glowstone.entity.GlowPlayer;
import net.glowstone.net.GlowSession;
import net.glowstone.net.message.play.player.BlockPlacementMessage;
import net.glowstone.util.InventoryUtil;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import org.bukkit.event.Event.Result;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
public final class BlockPlacementHandler implements MessageHandler<GlowSession, BlockPlacementMessage> {
private static final BlockFace[] faces = {
BlockFace.DOWN, BlockFace.UP, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST
};
static boolean selectResult(Result result, boolean def) {
return result == Result.DEFAULT ? def : result == Result.ALLOW;
}
static void revert(GlowPlayer player, GlowBlock target) {
player.sendBlockChange(target.getLocation(), target.getType(), target.getData());
BlockEntity entity = target.getBlockEntity();
if (entity != null) {
entity.update(player);
}
}
static BlockFace convertFace(int direction) {
if (direction >= 0 && direction < faces.length) {
return faces[direction];
} else {
return BlockFace.SELF;
}
}
@Override
public void handle(GlowSession session, BlockPlacementMessage message) {
//TODO: Hand handling instead of .getHeldItem()
GlowPlayer player = session.getPlayer();
if (player == null)
return;
//GlowServer.logger.info(session + ": " + message);
/*
* The client sends this packet for the following cases:
* Right click air:
* - Send direction=-1 packet for any non-null item
* Right click block:
* - Send packet with all values filled
* - If client DOES NOT expect a block placement to result:
* - Send direction=-1 packet (unless item is null)
*
* Client will expect a block placement to result from blocks and from
* certain items (e.g. sugarcane, sign). We *could* opt to trust the
* client on this, but the server's view of events (particularly under
* the Bukkit API, or custom ItemTypes) may differ from the client's.
*
* In order to avoid firing two events for one interact, the two
* packet case must be handled here. Care must also be taken that a
* right-click air of an expected-place item immediately after is
* not considered part of the same action.
*/
Action action = Action.RIGHT_CLICK_BLOCK;
GlowBlock clicked = player.getWorld().getBlockAt(message.getX(), message.getY(), message.getZ());
/*
* Check if the message is a -1. If we *just* got a message with the
* values filled, discard it, otherwise perform right-click-air.
*/
if (message.getDirection() == -1) {
BlockPlacementMessage previous = session.getPreviousPlacement();
//if (previous == null || !previous.getHeldItem().equals(message.getHeldItem())) {
// perform normal right-click-air actions
// action = Action.RIGHT_CLICK_AIR;
// clicked = null;
//} else {
// terminate processing of this event
// session.setPreviousPlacement(null);
return;
// }
}
// Set previous placement message
session.setPreviousPlacement(message);
// Get values from the message
Vector clickedLoc = new Vector(message.getCursorX(), message.getCursorY(), message.getCursorZ());
BlockFace face = convertFace(message.getDirection());
ItemStack holding = player.getItemInHand();
boolean rightClickedAir = false;
// check that a block-click wasn't against air
if (clicked == null || clicked.getType() == Material.AIR) {
action = Action.RIGHT_CLICK_AIR;
// inform the player their perception of reality is wrong
if (holding.getType().isBlock()) {
player.sendBlockChange(clicked.getLocation(), Material.AIR, (byte) 0);
return;
} else {
rightClickedAir = true;
}
}
// call interact event
PlayerInteractEvent event = EventFactory.onPlayerInteract(player, action, rightClickedAir ? null : clicked, face);
//GlowServer.logger.info("Interact: " + action + " " + clicked + " " + face);
// attempt to use interacted block
// DEFAULT is treated as ALLOW, and sneaking is always considered
boolean useInteractedBlock = event.useInteractedBlock() != Result.DENY;
if (useInteractedBlock && !rightClickedAir && (!player.isSneaking() || InventoryUtil.isEmpty(holding))) {
BlockType blockType = ItemTable.instance().getBlock(clicked.getType());
if (blockType != null) {
useInteractedBlock = blockType.blockInteract(player, clicked, face, clickedLoc);
} else {
GlowServer.logger.info("Unknown clicked block, " + clicked.getType());
}
} else {
useInteractedBlock = false;
}
// attempt to use item in hand
// follows ALLOW/DENY: default to if no block was interacted with
if (selectResult(event.useItemInHand(), !useInteractedBlock) && holding != null) {
ItemType type = ItemTable.instance().getItem(holding.getType());
if (!rightClickedAir && holding.getType() != Material.AIR && !type.canOnlyUseSelf()) {
type.rightClickBlock(player, clicked, face, holding, clickedLoc);
}
}
// if anything was actually clicked, make sure the player's up to date
// in case something is unimplemented or otherwise screwy on our side
if (!rightClickedAir) {
revert(player, clicked);
revert(player, clicked.getRelative(face));
}
// if there's been a change in the held item, make it valid again
if (!InventoryUtil.isEmpty(holding)) {
if (holding.getType().getMaxDurability() > 0 && holding.getDurability() > holding.getType().getMaxDurability()) {
holding.setAmount(holding.getAmount() - 1);
holding.setDurability((short) 0);
}
if (holding.getAmount() <= 0) {
holding = null;
}
}
player.setItemInHand(holding);
}
}