1
0

Rewrite cClientHandle::HandleRightClick (#4089)

* Add hand parameter to distinguish main hand/off hand.
* Add a new function cClientHandle::HandleUseItem to separate the functionality of using an item without a target block. This matches the protocol with client version >= 1.9
* Always actively update the status of a block if the placement fails (by out of reach or rejected by plugin).
* Do not call plugin callback CallHookPlayerRightClick(-1, 255, -1, -1, 0, 0, 0) when using item.
   The CallHookPlayerUsingItem will still be called.
   Now at most one of CallHookPlayerRightClick, CallHookPlayerUsingBlock,
   CallHookPlayerUsingItem and CallHookPlayerEating will be called based on
   the type of action (not including the used version of callbacks).
* Do not count using item as BlockInteractionsRate check (Using item takes time).
* Now we can open chests(etc.) when sneaking as long as the player's hand is empty.
   This is what vanilla server does.
This commit is contained in:
9caihezi 2018-01-09 00:37:10 +11:00 committed by peterbell10
parent 6e522d05da
commit 4242431407
10 changed files with 177 additions and 160 deletions

1
.gitignore vendored
View File

@ -92,6 +92,7 @@ src/AllFiles.lst
*.VC.opendb
*.VC.db
*.idb
.vs/
# cmake output folders and files
ZERO_CHECK.dir/

View File

@ -5,6 +5,7 @@ If you contribute to this software you must add yourself to this file, to
indicate your agreement to license your contributions according to the license
as provided in the LICENSE file.
9caihezi
Altenius
BasedDoge (Donated AlchemistVillage prefabs)
bearbin (Alexander Harkness)

View File

@ -81,7 +81,8 @@ public:
returns true if the use was successful, return false to use the block as a "normal" block */
virtual bool OnUse(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ) { return false; }
/** Called when a right click to this block is cancelled */
/** Called when a right click to this block is cancelled.
It forces the server to send the real state of a block to the client to prevent client assuming the operation is successfull */
virtual void OnCancelRightClick(cChunkInterface & a_ChunkInterface, cWorldInterface & a_WorldInterface, cPlayer & a_Player, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace) {}
/** Called when the item is mined to convert it into pickups. Pickups may specify multiple items. Appends items to a_Pickups, preserves its original contents */

View File

@ -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)
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)
{
// TODO: Rewrite this function
// 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;
}
}
}
}
// 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
)
{
// The player is satiated or in creative, and trying to eat
return;
}
m_Player->StartEating();
if (m_Player->IsFrozen() || PlgMgr->CallHookPlayerEating(*m_Player))
{
// A plugin won't let us eat, abort (send the proper packets to the client, too):
m_Player->AbortEating();
Success = true;
}
}
else
{
if (m_Player->IsFrozen() || PlgMgr->CallHookPlayerUsingItem(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ))
// 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);
}
}
else if (Placeable)
{
// A plugin doesn't agree with using the item, abort
// 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;
}
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))
if (!PlgMgr->CallHookPlayerRightClick(*m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ))
{
m_Player->StartChargingBow();
// place a block
Success = ItemHandler->OnPlayerPlace(*World, *m_Player, HeldItem, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ);
}
}
else
{
// 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;
}
}
}
if (!Success)
{
// 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++)
{
if (cChunkDef::IsValidHeight(y))
{
World->SendBlockTo(a_BlockX, y, a_BlockZ, *m_Player);
}
}
// 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)

View File

@ -356,7 +356,7 @@ public: // tolua_export
void HandlePluginMessage (const AString & a_Channel, const AString & a_Message);
void HandleRespawn (void);
void 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);
void 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);
void HandleSlotSelected (Int16 a_SlotNum);
void HandleSpectate (const cUUID & a_PlayerUUID);
void HandleSteerVehicle (float Forward, float Sideways);
@ -368,6 +368,7 @@ public: // tolua_export
);
void HandleUnmount (void);
void HandleUseEntity (UInt32 a_TargetEntityID, bool a_IsLeftClick);
void HandleUseItem (eHand a_Hand);
void HandleWindowClick (UInt8 a_WindowID, Int16 a_SlotNum, eClickAction a_ClickAction, const cItem & a_HeldItem);
void HandleWindowClose (UInt8 a_WindowID);

View File

@ -550,7 +550,7 @@ void cProtocol_1_11_0::HandlePacketBlockPlace(cByteBuffer & a_ByteBuffer)
HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, CursorX);
HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, CursorY);
HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, CursorZ);
m_Client->HandleRightClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), FloorC(CursorX * 16), FloorC(CursorY * 16), FloorC(CursorZ * 16), m_Client->GetPlayer()->GetEquippedItem());
m_Client->HandleRightClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), FloorC(CursorX * 16), FloorC(CursorY * 16), FloorC(CursorZ * 16), HandIntToEnum(Hand));
}

View File

@ -363,7 +363,7 @@ void cProtocol_1_12::HandlePacketBlockPlace(cByteBuffer & a_ByteBuffer)
HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, CursorX);
HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, CursorY);
HANDLE_READ(a_ByteBuffer, ReadBEFloat, float, CursorZ);
m_Client->HandleRightClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), FloorC(CursorX * 16), FloorC(CursorY * 16), FloorC(CursorZ * 16), m_Client->GetPlayer()->GetEquippedItem());
m_Client->HandleRightClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), FloorC(CursorX * 16), FloorC(CursorY * 16), FloorC(CursorZ * 16), HandIntToEnum(Hand));
}

View File

@ -2329,13 +2329,21 @@ void cProtocol_1_8_0::HandlePacketBlockPlace(cByteBuffer & a_ByteBuffer)
HANDLE_READ(a_ByteBuffer, ReadBEInt8, Int8, Face);
cItem Item;
cItem Item; // Ignored
ReadItem(a_ByteBuffer, Item, 3);
HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, CursorX);
HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, CursorY);
HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, CursorZ);
m_Client->HandleRightClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), CursorX, CursorY, CursorZ, m_Client->GetPlayer()->GetEquippedItem());
eBlockFace blockFace = FaceIntToBlockFace(Face);
if (blockFace == eBlockFace::BLOCK_FACE_NONE)
{
m_Client->HandleUseItem(eHand::hMain);
}
else
{
m_Client->HandleRightClick(BlockX, BlockY, BlockZ, blockFace, CursorX, CursorY, CursorZ, eHand::hMain);
}
}

View File

@ -66,6 +66,7 @@ static const Int16 SLOT_NUM_OUTSIDE = -999;
/** Value for main hand in Hand parameter for Protocol 1.9. */
static const UInt32 MAIN_HAND = 0;
static const UInt32 OFF_HAND = 1;
@ -2384,7 +2385,7 @@ void cProtocol_1_9_0::HandlePacketBlockPlace(cByteBuffer & a_ByteBuffer)
HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, CursorX);
HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, CursorY);
HANDLE_READ(a_ByteBuffer, ReadBEUInt8, UInt8, CursorZ);
m_Client->HandleRightClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), CursorX, CursorY, CursorZ, m_Client->GetPlayer()->GetEquippedItem());
m_Client->HandleRightClick(BlockX, BlockY, BlockZ, FaceIntToBlockFace(Face), CursorX, CursorY, CursorZ, HandIntToEnum(Hand));
}
@ -2784,10 +2785,9 @@ void cProtocol_1_9_0::HandlePacketUseEntity(cByteBuffer & a_ByteBuffer)
void cProtocol_1_9_0::HandlePacketUseItem(cByteBuffer & a_ByteBuffer)
{
HANDLE_READ(a_ByteBuffer, ReadVarInt, UInt64, Hand);
HANDLE_READ(a_ByteBuffer, ReadVarInt, Int32, Hand);
// Didn't click a block - emulate old values used with place block of -1, -1, -1 (and BLOCK_FACE_NONE).
m_Client->HandleRightClick(-1, 255, -1, BLOCK_FACE_NONE, 0, 0, 0, m_Client->GetPlayer()->GetEquippedItem());
m_Client->HandleUseItem(HandIntToEnum(Hand));
}
@ -3267,6 +3267,25 @@ eBlockFace cProtocol_1_9_0::FaceIntToBlockFace(Int32 a_BlockFace)
eHand cProtocol_1_9_0::HandIntToEnum(Int32 a_Hand)
{
// Convert hand parameter into eHand enum
switch (a_Hand)
{
case MAIN_HAND: return eHand::hMain;
case OFF_HAND: return eHand::hOff;
default:
{
ASSERT(!"Unknown hand value");
return eHand::hMain;
}
}
}
////////////////////////////////////////////////////////////////////////////////
// cProtocol_1_9_0::cPacketizer:

View File

@ -255,6 +255,10 @@ protected:
If the received value doesn't match any of our eBlockFace constants, BLOCK_FACE_NONE is returned. */
eBlockFace FaceIntToBlockFace(Int32 a_FaceInt);
/** Converts the hand parameter received by the protocol into eHand constants.
If the received value doesn't match any of the know value, raise an assertion fail or return hMain. */
eHand HandIntToEnum(Int32 a_Hand);
/** Writes the item data into a packet. */
void WriteItem(cPacketizer & a_Pkt, const cItem & a_Item);