summaryrefslogtreecommitdiffstats
path: root/src/ClientHandle.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ClientHandle.cpp')
-rw-r--r--src/ClientHandle.cpp278
1 files changed, 130 insertions, 148 deletions
diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp
index 8470e7de6..3a07de7f1 100644
--- a/src/ClientHandle.cpp
+++ b/src/ClientHandle.cpp
@@ -1395,186 +1395,113 @@ void cClientHandle::FinishDigAnimation()
-void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, const cItem & a_HeldItem)
-{
- // TODO: Rewrite this function
-
+void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, eHand a_Hand)
+{
+ // This function handles three actions:
+ // (1) Place a block;
+ // (2) "Use" a block: Interactive with the block, like opening a chest/crafting table/furnace;
+ // (3) Use the held item targeting a block. E.g. farming.
+ //
+ // Sneaking player will not use the block if hand is not empty.
+ // Frozen player can do nothing.
+ // In Game Mode Spectator, player cannot use item or place block, but can interactive with some block depending on cBlockInfo::IsUseableBySpectator(BlockType)
+ //
+ // If the action failed, we need to send an update of the placed block or inventory to the client.
+ //
+ // Actions rejected by plugin will not lead to other attempts.
+ // E.g., when opening a chest with a dirt in hand, if the plugin rejects opening the chest, the dirt will not be placed.
+
+ // TODO: We are still consuming the items in main hand. Remove this override when the off-hand consumption is handled correctly.
+ a_Hand = eHand::hMain;
+ const cItem & HeldItem = (a_Hand == eHand::hOff) ? m_Player->GetInventory().GetShieldSlot() : m_Player->GetEquippedItem();
+ cItemHandler * ItemHandler = cItemHandler::GetItemHandler(HeldItem.m_ItemType);
+
+ // TODO: This distance should be calculated from the point that the cursor pointing at, instead of the center of the block
// Distance from the block's center to the player's eye height
- double dist = (Vector3d(a_BlockX, a_BlockY, a_BlockZ) + Vector3d(0.5, 0.5, 0.5) - m_Player->GetEyePosition()).Length();
- LOGD("HandleRightClick: {%d, %d, %d}, face %d, HeldItem: %s; dist: %.02f",
- a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, ItemToFullString(a_HeldItem).c_str(), dist
+ double Dist = (Vector3d(a_BlockX, a_BlockY, a_BlockZ) + Vector3d(0.5, 0.5, 0.5) - m_Player->GetEyePosition()).Length();
+ LOGD("HandleRightClick: {%d, %d, %d}, face %d, Hand: %d, HeldItem: %s; Dist: %.02f",
+ a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_Hand, ItemToFullString(HeldItem).c_str(), Dist
);
// Check the reach distance:
// _X 2014-11-25: I've maxed at 5.26 with a Survival client and 5.78 with a Creative client in my tests
- double maxDist = m_Player->IsGameModeCreative() ? 5.78 : 5.26;
- bool AreRealCoords = (dist <= maxDist);
-
+ double MaxDist = m_Player->IsGameModeCreative() ? 5.78 : 5.26;
+ bool IsWithinReach = (Dist <= MaxDist);
cWorld * World = m_Player->GetWorld();
-
- if (
- (a_BlockFace != BLOCK_FACE_NONE) && // The client is interacting with a specific block
- IsValidBlock(a_HeldItem.m_ItemType) &&
- !AreRealCoords
- )
- {
- AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
- if ((a_BlockX != -1) && (a_BlockY >= 0) && (a_BlockZ != -1))
- {
- if (cChunkDef::IsValidHeight(a_BlockY))
- {
- World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, *m_Player);
- }
- if (cChunkDef::IsValidHeight(a_BlockY + 1))
- {
- World->SendBlockTo(a_BlockX, a_BlockY + 1, a_BlockZ, *m_Player); // 2 block high things
- }
- if (cChunkDef::IsValidHeight(a_BlockY - 1))
- {
- World->SendBlockTo(a_BlockX, a_BlockY - 1, a_BlockZ, *m_Player); // 2 block high things
- }
- }
- m_Player->GetInventory().SendEquippedSlot();
- return;
- }
-
- if (!AreRealCoords)
- {
- a_BlockFace = BLOCK_FACE_NONE;
- }
-
cPluginManager * PlgMgr = cRoot::Get()->GetPluginManager();
- if (m_Player->IsFrozen() || PlgMgr->CallHookPlayerRightClick(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ))
- {
- // A plugin doesn't agree with the action, replace the block on the client and quit:
- if (AreRealCoords)
- {
- cChunkInterface ChunkInterface(World->GetChunkMap());
- BLOCKTYPE BlockType = World->GetBlock(a_BlockX, a_BlockY, a_BlockZ);
- cBlockHandler * BlockHandler = cBlockInfo::GetHandler(BlockType);
- BlockHandler->OnCancelRightClick(ChunkInterface, *World, *m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
-
- if (a_BlockFace != BLOCK_FACE_NONE)
- {
- AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
- if (cChunkDef::IsValidHeight(a_BlockY))
- {
- World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, *m_Player);
- }
- if (cChunkDef::IsValidHeight(a_BlockY + 1))
- {
- World->SendBlockTo(a_BlockX, a_BlockY + 1, a_BlockZ, *m_Player); // 2 block high things
- }
- if (cChunkDef::IsValidHeight(a_BlockY - 1))
- {
- World->SendBlockTo(a_BlockX, a_BlockY - 1, a_BlockZ, *m_Player); // 2 block high things
- }
- m_Player->GetInventory().SendEquippedSlot();
- }
- }
- return;
- }
-
- m_NumBlockChangeInteractionsThisTick++;
-
- if (!CheckBlockInteractionsRate())
- {
- Kick("Too many blocks were placed / interacted with per unit time - hacked client?");
- return;
- }
-
- const cItem & Equipped = m_Player->GetInventory().GetEquippedItem();
-
- if ((Equipped.m_ItemType != a_HeldItem.m_ItemType) && (a_HeldItem.m_ItemType != -1))
- {
- // Only compare ItemType, not meta (torches have different metas)
- // The -1 check is there because sometimes the client sends -1 instead of the held item
- // Ref.: https://forum.cuberite.org/thread-549-post-4502.html#pid4502
- LOGWARN("Player %s tried to place a block that was not equipped (exp %d, got %d)",
- m_Username.c_str(), Equipped.m_ItemType, a_HeldItem.m_ItemType
- );
-
- // Let's send the current world block to the client, so that it can immediately "let the user know" that they haven't placed the block
- if (a_BlockFace != BLOCK_FACE_NONE)
- {
- AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
- World->SendBlockTo(a_BlockX, a_BlockY, a_BlockZ, *m_Player);
- }
- return;
- }
- if (AreRealCoords)
+ bool Success = false;
+ if (IsWithinReach && !m_Player->IsFrozen())
{
BLOCKTYPE BlockType;
NIBBLETYPE BlockMeta;
World->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta);
cBlockHandler * BlockHandler = cBlockInfo::GetHandler(BlockType);
- if (BlockHandler->IsUseable() && !m_Player->IsCrouched() && (!m_Player->IsGameModeSpectator() || cBlockInfo::IsUseableBySpectator(BlockType)))
+ bool Placeable = ItemHandler->IsPlaceable() && !m_Player->IsGameModeSpectator();
+ bool BlockUsable = BlockHandler->IsUseable() && (!m_Player->IsGameModeSpectator() || cBlockInfo::IsUseableBySpectator(BlockType));
+
+ if (BlockUsable && !(m_Player->IsCrouched() && !HeldItem.IsEmpty()))
{
+ // use a block
+ cChunkInterface ChunkInterface(World->GetChunkMap());
if (!PlgMgr->CallHookPlayerUsingBlock(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta))
{
- cChunkInterface ChunkInterface(World->GetChunkMap());
if (BlockHandler->OnUse(ChunkInterface, *World, *m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ))
{
// block use was successful, we're done
PlgMgr->CallHookPlayerUsedBlock(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta);
- return;
+ Success = true;
}
}
+ else
+ {
+ // TODO: OnCancelRightClick seems to do the same thing with updating blocks at the end of this function. Need to double check
+ // A plugin doesn't agree with the action, replace the block on the client and quit:
+ BlockHandler->OnCancelRightClick(ChunkInterface, *World, *m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
+ }
}
- }
-
- // Players, who spectate cannot use their items
- if (m_Player->IsGameModeSpectator())
- {
- return;
- }
-
- short EquippedDamage = Equipped.m_ItemDamage;
- cItemHandler * ItemHandler = cItemHandler::GetItemHandler(Equipped.m_ItemType);
-
- if (ItemHandler->IsPlaceable() && (a_BlockFace != BLOCK_FACE_NONE))
- {
- if (!ItemHandler->OnPlayerPlace(*World, *m_Player, Equipped, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ))
- {
- // Placement failed, bail out
- return;
- }
- }
- else if ((ItemHandler->IsFood() || ItemHandler->IsDrinkable(EquippedDamage)))
- {
- if (
- (m_Player->IsSatiated() || m_Player->IsGameModeCreative()) && // Only creative or hungry players can eat
- ItemHandler->IsFood() &&
- (Equipped.m_ItemType != E_ITEM_GOLDEN_APPLE) // Golden apple is a special case, it is used instead of eaten
- )
+ else if (Placeable)
{
- // The player is satiated or in creative, and trying to eat
- return;
+ // TODO: Double check that we don't need this for using item and for packet out of range
+ m_NumBlockChangeInteractionsThisTick++;
+ if (!CheckBlockInteractionsRate())
+ {
+ Kick("Too many blocks were placed / interacted with per unit time - hacked client?");
+ return;
+ }
+ if (!PlgMgr->CallHookPlayerRightClick(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ))
+ {
+ // place a block
+ Success = ItemHandler->OnPlayerPlace(*World, *m_Player, HeldItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ);
+ }
}
- m_Player->StartEating();
- if (m_Player->IsFrozen() || PlgMgr->CallHookPlayerEating(*m_Player))
+ else
{
- // A plugin won't let us eat, abort (send the proper packets to the client, too):
- m_Player->AbortEating();
+ // Use an item in hand with a target block
+ if (!PlgMgr->CallHookPlayerUsingItem(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ))
+ {
+ // All plugins agree with using the item
+ cBlockInServerPluginInterface PluginInterface(*World);
+ ItemHandler->OnItemUse(World, m_Player, PluginInterface, HeldItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
+ PlgMgr->CallHookPlayerUsedItem(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ);
+ Success = true;
+ }
}
}
- else
+ if (!Success)
{
- if (m_Player->IsFrozen() || PlgMgr->CallHookPlayerUsingItem(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ))
+ // Update the target block including the block above and below for 2 block high things
+ AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
+ for (int y = a_BlockY - 1; y <= a_BlockY + 1; y++)
{
- // A plugin doesn't agree with using the item, abort
- return;
+ if (cChunkDef::IsValidHeight(y))
+ {
+ World->SendBlockTo(a_BlockX, y, a_BlockZ, *m_Player);
+ }
}
- cBlockInServerPluginInterface PluginInterface(*World);
- ItemHandler->OnItemUse(World, m_Player, PluginInterface, Equipped, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace);
- PlgMgr->CallHookPlayerUsedItem(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ);
- }
- // Charge bow when it's in slot off-hand / shield
- if ((a_BlockFace == BLOCK_FACE_NONE) && (m_Player->GetInventory().GetShieldSlot().m_ItemType == E_ITEM_BOW))
- {
- m_Player->StartChargingBow();
+ // TODO: Send corresponding slot based on hand
+ m_Player->GetInventory().SendEquippedSlot();
}
}
@@ -1810,6 +1737,61 @@ void cClientHandle::HandleUseEntity(UInt32 a_TargetEntityID, bool a_IsLeftClick)
+void cClientHandle::HandleUseItem(eHand a_Hand)
+{
+ // Use the held item without targeting a block: eating, drinking, charging a bow, using buckets
+ // In version 1.8.x, this function shares the same packet id with HandleRightClick.
+ // In version >= 1.9, there is a new packet id for "Use Item".
+
+ // TODO: We are still consuming the items in main hand. Remove this override when the off-hand consumption is handled correctly.
+ a_Hand = eHand::hMain;
+ const cItem & HeldItem = (a_Hand == eHand::hOff) ? m_Player->GetInventory().GetShieldSlot() : m_Player->GetEquippedItem();
+ cItemHandler * ItemHandler = cItemHandler::GetItemHandler(HeldItem.m_ItemType);
+ cWorld * World = m_Player->GetWorld();
+ cPluginManager * PlgMgr = cRoot::Get()->GetPluginManager();
+
+ LOGD("HandleUseItem: Hand: %d; HeldItem: %s", a_Hand, ItemToFullString(HeldItem).c_str());
+
+ // Use item in main / off hand
+ // TODO: do we need to sync the current inventory with client if it fails?
+ if (m_Player->IsFrozen() || m_Player->IsGameModeSpectator())
+ {
+ return;
+ }
+
+ if (ItemHandler->IsFood() || ItemHandler->IsDrinkable(HeldItem.m_ItemDamage))
+ {
+ if (
+ ItemHandler->IsFood() &&
+ (m_Player->IsSatiated() || m_Player->IsGameModeCreative()) && // Only non-creative or hungry players can eat
+ (HeldItem.m_ItemType != E_ITEM_GOLDEN_APPLE) // Golden apple is a special case, it is used instead of eaten
+ )
+ {
+ // The player is satiated or in creative, and trying to eat
+ return;
+ }
+ if (!PlgMgr->CallHookPlayerEating(*m_Player))
+ {
+ m_Player->StartEating();
+ }
+ }
+ else
+ {
+ // Use an item in hand without a target block
+ if (!PlgMgr->CallHookPlayerUsingItem(*m_Player, -1, 255, -1, BLOCK_FACE_NONE, 0, 0, 0))
+ {
+ // All plugins agree with using the item
+ cBlockInServerPluginInterface PluginInterface(*World);
+ ItemHandler->OnItemUse(World, m_Player, PluginInterface, HeldItem, -1, 255, -1, BLOCK_FACE_NONE);
+ PlgMgr->CallHookPlayerUsedItem(*m_Player, -1, 255, -1, BLOCK_FACE_NONE, 0, 0, 0);
+ }
+ }
+}
+
+
+
+
+
void cClientHandle::HandleRespawn(void)
{
if (m_Player == nullptr)