1
0

Refactored cInventory to use cItemGrid for the actual Storage

This makes the API more orthogonal and is easier to use in the plugins. Also changes in the inventory are now propagated to the needed places (armor updates to BroadcastEntityEquipment etc.) even when the inventory is changed by a plugin.

git-svn-id: http://mc-server.googlecode.com/svn/trunk@1503 0a769ca7-a7f5-676a-18bf-c427514a06d6
This commit is contained in:
madmaxoft@gmail.com 2013-05-24 07:30:39 +00:00
parent 3b429a9bdf
commit cf87169737
25 changed files with 1581 additions and 534 deletions

View File

@ -1,36 +1,39 @@
function HandleItemCommand(Split, Player) function HandleItemCommand(Split, Player)
if( #Split ~= 2 and #Split ~=3 ) then if ((#Split ~= 2) and (#Split ~=3)) then
Player:SendMessage( cChatColor.Green .. "Usage: /item [ItemType/Name:Dmg] <Amount>" ) Player:SendMessage(cChatColor.Green .. "Usage: /item [ItemType/Name:Dmg] <Amount>");
return true return true;
end end
local Item = cItem(E_ITEM_EMPTY, 1) local Item = cItem(E_ITEM_EMPTY, 1);
local FoundItem = StringToItem( Split[2], Item ) local FoundItem = StringToItem(Split[2], Item);
if( IsValidItem( Item.m_ItemType ) == false ) then -- StringToItem does not check if item is valid if not(IsValidItem(Item.m_ItemType)) then -- StringToItem does not check if item is valid
FoundItem = false FoundItem = false
end end
if( FoundItem == false ) then if not(FoundItem) then
Player:SendMessage( cChatColor.Green .. "Invalid Item type / name !" ) Player:SendMessage( cChatColor.Green .. "Invalid Item type / name !" )
return true return true
end end
local ItemAmount = 1;
if (#Split == 3) then if (#Split == 3) then
ItemAmount = tonumber( Split[3] ) ItemAmount = tonumber(Split[3]);
if( ItemAmount == nil or ItemAmount < 1 or ItemAmount > 512 ) then if ((ItemAmount == nil) or (ItemAmount < 1) or (ItemAmount > 512)) then
Player:SendMessage( cChatColor.Green .. "Invalid Amount !" ) Player:SendMessage(cChatColor.Green .. "Invalid Amount!");
return true return true;
else
Item.m_ItemCount = ItemAmount
end end
end end
if( Player:GetInventory():AddItem( Item ) == true ) then Item.m_ItemCount = ItemAmount;
Player:SendMessage( cChatColor.Green .. "There you go !" )
LOG("Gave " .. Player:GetName() .. " " .. Item.m_ItemCount .. " times " .. Item.m_ItemType .. ":" .. Item.m_ItemDamage) local ItemsGiven = Player:GetInventory():AddItem(Item);
if (ItemsGiven == ItemAmount) then
Player:SendMessage( cChatColor.Green .. "There you go !");
LOG("Gave " .. Player:GetName() .. " " .. Item.m_ItemCount .. " times " .. Item.m_ItemType .. ":" .. Item.m_ItemDamage);
else else
Player:SendMessage( cChatColor.Green .. "Not enough space in inventory !" ) Player:SendMessage(cChatColor.Green .. "Not enough space in inventory, only gave " .. ItemsGiven);
LOG("Player " .. Player:GetName() .. " asked for " .. Item.m_ItemCount .. " times " .. Item.m_ItemType .. ":" .. Item.m_ItemDamage ..", but only could fit " .. ItemsGiven);
end end
return true return true;
end end

View File

@ -456,10 +456,10 @@ end
function HandleWoolCmd(Split, Player) function HandleWoolCmd(Split, Player)
local Wool = cItem(E_BLOCK_WOOL, 1, E_META_WOOL_BLUE); local Wool = cItem(E_BLOCK_WOOL, 1, E_META_WOOL_BLUE);
Player:GetInventory():SetSlot(5, Wool); Player:GetInventory():SetArmorSlot(0, Wool);
Player:GetInventory():SetSlot(6, Wool); Player:GetInventory():SetArmorSlot(1, Wool);
Player:GetInventory():SetSlot(7, Wool); Player:GetInventory():SetArmorSlot(2, Wool);
Player:GetInventory():SetSlot(8, Wool); Player:GetInventory():SetArmorSlot(3, Wool);
Player:SendMessage("You have been bluewooled :)"); Player:SendMessage("You have been bluewooled :)");
return true; return true;
end end

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/* /*
** Lua binding: AllToLua ** Lua binding: AllToLua
** Generated automatically by tolua++-1.0.92 on 05/21/13 14:54:11. ** Generated automatically by tolua++-1.0.92 on 05/24/13 09:11:00.
*/ */
/* Exported function */ /* Exported function */

View File

@ -29,8 +29,7 @@ public:
case E_ITEM_WATER_BUCKET: case E_ITEM_WATER_BUCKET:
{ {
a_World->SetBlockMeta( a_BlockX, a_BlockY, a_BlockZ, 3 ); a_World->SetBlockMeta( a_BlockX, a_BlockY, a_BlockZ, 3 );
cItem Item(a_Player->GetEquippedItem().m_ItemType, 1); a_Player->GetInventory().RemoveOneEquippedItem();
a_Player->GetInventory().RemoveItem(Item);
cItem NewItem(E_ITEM_BUCKET, 1); cItem NewItem(E_ITEM_BUCKET, 1);
a_Player->GetInventory().AddItem(NewItem); a_Player->GetInventory().AddItem(NewItem);
break; break;
@ -40,8 +39,7 @@ public:
if( Meta > 0 ) if( Meta > 0 )
{ {
a_World->SetBlockMeta( a_BlockX, a_BlockY, a_BlockZ, --Meta); a_World->SetBlockMeta( a_BlockX, a_BlockY, a_BlockZ, --Meta);
cItem Item(a_Player->GetEquippedItem().m_ItemType, 1); a_Player->GetInventory().RemoveOneEquippedItem();
a_Player->GetInventory().RemoveItem(Item);
cItem NewItem(E_ITEM_POTIONS, 1, 0); cItem NewItem(E_ITEM_POTIONS, 1, 0);
a_Player->GetInventory().AddItem(NewItem); a_Player->GetInventory().AddItem(NewItem);
} }

View File

@ -86,10 +86,9 @@ public:
} }
} }
if (a_Player->GetGameMode() != eGameMode_Creative) if (a_Player->GetGameMode() != gmCreative)
{ {
cItem Item(a_Player->GetEquippedItem().m_ItemType, 1); a_Player->GetInventory().RemoveOneEquippedItem();
a_Player->GetInventory().RemoveItem(Item);
} }
a_World->SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, Meta); a_World->SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, Meta);
} }

View File

@ -791,7 +791,7 @@ void cClientHandle::HandleRightClick(int a_BlockX, int a_BlockY, int a_BlockZ, c
if (ItemHandler->EatItem(m_Player, &Item)) if (ItemHandler->EatItem(m_Player, &Item))
{ {
ItemHandler->OnFoodEaten(World, m_Player, &Item); ItemHandler->OnFoodEaten(World, m_Player, &Item);
m_Player->GetInventory().RemoveItem(Item); m_Player->GetInventory().RemoveOneEquippedItem();
return; return;
} }
} }
@ -877,10 +877,9 @@ void cClientHandle::HandlePlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, c
// The actual block placement: // The actual block placement:
World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta); World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta);
if (m_Player->GetGameMode() == eGameMode_Survival) if (m_Player->GetGameMode() != gmCreative)
{ {
cItem Item(m_Player->GetEquippedItem().m_ItemType, 1); m_Player->GetInventory().RemoveOneEquippedItem();
m_Player->GetInventory().RemoveItem(Item);
} }
NewBlock->OnPlacedByPlayer(World, m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta); NewBlock->OnPlacedByPlayer(World, m_Player, a_BlockX, a_BlockY, a_BlockZ, a_BlockFace, a_CursorX, a_CursorY, a_CursorZ, BlockType, BlockMeta);

View File

@ -18,12 +18,15 @@
cInventory::cInventory(cPlayer & a_Owner) : cInventory::cInventory(cPlayer & a_Owner) :
m_ArmorSlots (1, 4), // 1 x 4 slots
m_InventorySlots(9, 3), // 9 x 3 slots
m_HotbarSlots (9, 1), // 9 x 1 slots
m_Owner(a_Owner) m_Owner(a_Owner)
{ {
m_CraftSlots = m_Slots + c_CraftOffset; // Ask each ItemGrid to report changes to us:
m_ArmorSlots = m_Slots + c_ArmorOffset; m_ArmorSlots.AddListener(*this);
m_MainSlots = m_Slots + c_MainOffset; m_InventorySlots.AddListener(*this);
m_HotSlots = m_Slots + c_HotOffset; m_HotbarSlots.AddListener(*this);
SetEquippedSlotNum(0); SetEquippedSlotNum(0);
} }
@ -32,82 +35,130 @@ cInventory::cInventory(cPlayer & a_Owner) :
cInventory::~cInventory() void cInventory::Clear(void)
{ {
/* m_ArmorSlots.Clear();
// TODO m_InventorySlots.Clear();
cWindow wnd = GetWindow(); m_HotbarSlots.Clear();
if (wnd != NULL)
{
wnd->Close(*m_Owner);
}
CloseWindow();
*/
} }
bool cInventory::AddItem( cItem & a_Item ) int cInventory::HowManyCanFit(const cItem & a_ItemStack, bool a_ConsiderEmptySlots)
{ {
cItem BackupSlots[c_NumSlots]; return HowManyCanFit(a_ItemStack, 0, invNumSlots - 1, a_ConsiderEmptySlots);
memcpy( BackupSlots, m_Slots, c_NumSlots * sizeof( cItem ) ); }
bool ChangedSlots[c_NumSlots];
memset( ChangedSlots, false, c_NumSlots * sizeof( bool ) );
if( a_Item.m_ItemCount > 0 ) AddToBar( a_Item, c_HotOffset, c_HotSlots, ChangedSlots, 0 );
if( a_Item.m_ItemCount > 0 ) AddToBar( a_Item, c_MainOffset, c_MainSlots, ChangedSlots, 0 );
if( a_Item.m_ItemCount > 0 ) AddToBar( a_Item, c_HotOffset, c_HotSlots, ChangedSlots, 2 );
if( a_Item.m_ItemCount > 0 ) AddToBar( a_Item, c_MainOffset, c_MainSlots, ChangedSlots, 2 );
if( a_Item.m_ItemCount > 0 ) // Could not add all items
int cInventory::HowManyCanFit(const cItem & a_ItemStack, int a_BeginSlotNum, int a_EndSlotNum, bool a_ConsiderEmptySlots)
{
if ((a_BeginSlotNum < 0) || (a_BeginSlotNum >= invNumSlots))
{
LOGWARNING("%s: Bad BeginSlotNum, got %d, there are %d slots; correcting to 0.", __FUNCTION__, a_BeginSlotNum, invNumSlots - 1);
a_BeginSlotNum = 0;
}
if ((a_EndSlotNum < 0) || (a_EndSlotNum >= invNumSlots))
{
LOGWARNING("%s: Bad EndSlotNum, got %d, there are %d slots; correcting to %d.", __FUNCTION__, a_BeginSlotNum, invNumSlots, invNumSlots - 1);
a_EndSlotNum = invNumSlots - 1;
}
if (a_BeginSlotNum > a_EndSlotNum)
{
std::swap(a_BeginSlotNum, a_EndSlotNum);
}
char NumLeft = a_ItemStack.m_ItemCount;
int MaxStack = ItemHandler(a_ItemStack.m_ItemType)->GetMaxStackSize();
for (int i = a_BeginSlotNum; i <= a_EndSlotNum; i++)
{
const cItem & Slot = GetSlot(i);
if (Slot.IsEmpty())
{
NumLeft -= MaxStack;
}
else if (Slot.IsStackableWith(a_ItemStack))
{
NumLeft -= MaxStack - Slot.m_ItemCount;
}
if (NumLeft <= 0)
{
// All items fit
return a_ItemStack.m_ItemCount;
}
} // for i - m_Slots[]
return a_ItemStack.m_ItemCount - NumLeft;
}
int cInventory::AddItem(const cItem & a_Item, bool a_AllowNewStacks)
{
cItem ToAdd(a_Item);
int res = 0;
if (ItemCategory::IsArmor(a_Item.m_ItemType))
{
res = m_ArmorSlots.AddItem(ToAdd, a_AllowNewStacks);
ToAdd.m_ItemCount -= res;
if (ToAdd.m_ItemCount == 0)
{
return res;
}
}
res += m_HotbarSlots.AddItem(ToAdd, a_AllowNewStacks);
ToAdd.m_ItemCount = a_Item.m_ItemCount - res;
if (ToAdd.m_ItemCount == 0)
{
return res;
}
res += m_InventorySlots.AddItem(ToAdd, a_AllowNewStacks);
return res;
}
int cInventory::AddItems(cItems & a_ItemStackList, bool a_AllowNewStacks)
{
int TotalAdded = 0;
for (cItems::iterator itr = a_ItemStackList.begin(); itr != a_ItemStackList.end();)
{
int NumAdded = AddItem(*itr, a_AllowNewStacks);
if (itr->m_ItemCount == NumAdded)
{
itr = a_ItemStackList.erase(itr);
}
else
{
itr->m_ItemCount -= NumAdded;
++itr;
}
TotalAdded += NumAdded;
}
return TotalAdded;
}
bool cInventory::RemoveOneEquippedItem(void)
{
if (m_HotbarSlots.GetSlot(m_EquippedSlotNum).IsEmpty())
{ {
// retore backup
memcpy( m_Slots, BackupSlots, c_NumSlots * sizeof( cItem ) );
return false; return false;
} }
for (unsigned int i = 0; i < c_NumSlots; i++) m_HotbarSlots.ChangeSlotCount(m_EquippedSlotNum, -1);
{
if (ChangedSlots[i])
{
LOGD("cInventory::AddItem(): Item was added to %i ID:%i Count:%i", i, m_Slots[i].m_ItemType, m_Slots[i].m_ItemCount);
SendSlot(i);
}
}
return (a_Item.m_ItemCount == 0);
}
bool cInventory::AddItemAnyAmount( cItem & a_Item )
{
bool ChangedSlots[c_NumSlots];
memset( ChangedSlots, false, c_NumSlots * sizeof( bool ) );
char StartCount = a_Item.m_ItemCount;
if( a_Item.m_ItemCount > 0 ) AddToBar( a_Item, c_HotOffset, c_HotSlots, ChangedSlots, 0 );
if( a_Item.m_ItemCount > 0 ) AddToBar( a_Item, c_MainOffset, c_MainSlots, ChangedSlots, 0 );
if( a_Item.m_ItemCount > 0 ) AddToBar( a_Item, c_HotOffset, c_HotSlots, ChangedSlots, 2 );
if( a_Item.m_ItemCount > 0 ) AddToBar( a_Item, c_MainOffset, c_MainSlots, ChangedSlots, 2 );
if (a_Item.m_ItemCount == StartCount)
return false;
for (unsigned int i = 0; i < c_NumSlots; i++)
{
if (ChangedSlots[i])
{
LOGD("cInventory::AddItemAnyAmount(): Item was added to %i ID:%i Count:%i", i, m_Slots[i].m_ItemType, m_Slots[i].m_ItemCount);
SendSlot(i);
}
}
return true; return true;
} }
@ -115,68 +166,22 @@ bool cInventory::AddItemAnyAmount( cItem & a_Item )
// TODO: Right now if you dont have enough items, the items you did have are removed, and the function returns false anyway int cInventory::HowManyItems(const cItem & a_Item)
bool cInventory::RemoveItem(cItem & a_Item)
{ {
// First check equipped slot return
if ((m_EquippedSlotNum >= 0) && (m_EquippedSlotNum < 9)) m_ArmorSlots.HowManyItems(a_Item) +
{ m_InventorySlots.HowManyItems(a_Item) +
if (m_HotSlots[m_EquippedSlotNum].m_ItemType == a_Item.m_ItemType) m_HotbarSlots.HowManyItems(a_Item);
{
cItem & Item = m_HotSlots[m_EquippedSlotNum];
if (Item.m_ItemCount > a_Item.m_ItemCount)
{
Item.m_ItemCount -= a_Item.m_ItemCount;
SendSlot(m_EquippedSlotNum + c_HotOffset);
return true;
}
else if (Item.m_ItemCount > 0)
{
a_Item.m_ItemCount -= Item.m_ItemCount;
Item.Empty();
SendSlot(m_EquippedSlotNum + c_HotOffset);
}
}
}
// Then check other slotz
if (a_Item.m_ItemCount > 0)
{
for (int i = 0; i < c_MainSlots; i++)
{
cItem & Item = m_MainSlots[i];
if (Item.m_ItemType == a_Item.m_ItemType)
{
if (Item.m_ItemCount > a_Item.m_ItemCount)
{
Item.m_ItemCount -= a_Item.m_ItemCount;
SendSlot(i + c_MainOffset);
return true;
}
else if (Item.m_ItemCount > 0)
{
a_Item.m_ItemCount -= Item.m_ItemCount;
Item.Empty();
SendSlot(i + c_MainOffset);
}
}
}
}
return (a_Item.m_ItemCount == 0);
} }
void cInventory::Clear() bool cInventory::HasItems(const cItem & a_ItemStack)
{ {
for (unsigned int i = 0; i < ARRAYCOUNT(m_Slots); i++) int CurrentlyHave = HowManyItems(a_ItemStack);
{ return (CurrentlyHave >= a_ItemStack.m_ItemCount);
m_Slots[i].Empty();
}
// TODO: Broadcast / send the changes to wherever needed
} }
@ -185,29 +190,47 @@ void cInventory::Clear()
void cInventory::SetSlot(int a_SlotNum, const cItem & a_Item) void cInventory::SetSlot(int a_SlotNum, const cItem & a_Item)
{ {
if ((a_SlotNum < 0) || (a_SlotNum >= ARRAYCOUNT(m_Slots))) if ((a_SlotNum < 0) || (a_SlotNum >= invNumSlots))
{ {
LOGWARNING("%s requesting an invalid slot index: %d out of %d. Ignoring.", __FUNCTION__, a_SlotNum, ARRAYCOUNT(m_Slots)); LOGWARNING("%s: requesting an invalid slot index: %d out of %d. Ignoring.", __FUNCTION__, a_SlotNum, invNumSlots - 1);
return; return;
} }
m_Slots[a_SlotNum] = a_Item;
// If an armor slot was touched, broadcast an EntityEquipment packet int GridSlotNum = 0;
if ((a_SlotNum >= c_ArmorOffset) && (a_SlotNum < c_MainOffset)) cItemGrid * Grid = GetGridForSlotNum(a_SlotNum, GridSlotNum);
if (Grid == NULL)
{ {
m_Owner.GetWorld()->BroadcastEntityEquipment(m_Owner, SlotNumToEntityEquipmentID(a_SlotNum), a_Item, m_Owner.GetClientHandle()); LOGWARNING("%s(%d): requesting an invalid itemgrid. Ignoring.", __FUNCTION__, a_SlotNum);
return;
} }
Grid->SetSlot(GridSlotNum, a_Item);
SendSlot(a_SlotNum);
} }
void cInventory::SetHotBarSlot(int a_HotBarSlotNum, const cItem & a_Item) void cInventory::SetArmorSlot(int a_ArmorSlotNum, const cItem & a_Item)
{ {
SetSlot(a_HotBarSlotNum + c_HotSlots, a_Item); m_ArmorSlots.SetSlot(a_ArmorSlotNum, a_Item);
}
void cInventory::SetInventorySlot(int a_InventorySlotNum, const cItem & a_Item)
{
m_InventorySlots.SetSlot(a_InventorySlotNum, a_Item);
}
void cInventory::SetHotbarSlot(int a_HotBarSlotNum, const cItem & a_Item)
{
m_HotbarSlots.SetSlot(a_HotBarSlotNum, a_Item);
} }
@ -216,27 +239,62 @@ void cInventory::SetHotBarSlot(int a_HotBarSlotNum, const cItem & a_Item)
const cItem & cInventory::GetSlot(int a_SlotNum) const const cItem & cInventory::GetSlot(int a_SlotNum) const
{ {
if ((a_SlotNum < 0) || (a_SlotNum >= ARRAYCOUNT(m_Slots))) if ((a_SlotNum < 0) || (a_SlotNum >= invNumSlots))
{ {
LOGWARNING("%s requesting an invalid slot index: %d out of %d. Returning the first one instead.", __FUNCTION__, a_SlotNum, ARRAYCOUNT(m_Slots)); LOGWARNING("%s: requesting an invalid slot index: %d out of %d. Returning the first inventory slot instead.", __FUNCTION__, a_SlotNum, invNumSlots - 1);
return m_Slots[0]; return m_InventorySlots.GetSlot(0);
} }
int GridSlotNum = 0;
return m_Slots[a_SlotNum]; const cItemGrid * Grid = GetGridForSlotNum(a_SlotNum, GridSlotNum);
if (Grid == NULL)
{
// Something went wrong, but we don't know what. We must return a value, so return the first inventory slot
LOGWARNING("%s(%d): requesting an invalid ItemGrid, returning the first inventory slot instead.", __FUNCTION__, a_SlotNum);
return m_InventorySlots.GetSlot(0);
}
return Grid->GetSlot(GridSlotNum);
} }
const cItem & cInventory::GetHotBarSlot(int a_SlotNum) const const cItem & cInventory::GetArmorSlot(int a_ArmorSlotNum) const
{ {
if ((a_SlotNum < 0) || (a_SlotNum >= 9)) if ((a_ArmorSlotNum < 0) || (a_ArmorSlotNum >= invArmorCount))
{ {
LOGWARNING("%s requesting an invalid slot index: %d out of 9. Returning the first one instead", __FUNCTION__, a_SlotNum); LOGWARNING("%s: requesting an invalid slot index: %d out of %d. Returning the first one instead", __FUNCTION__, a_ArmorSlotNum, invArmorCount - 1);
return m_HotSlots[0]; return m_ArmorSlots.GetSlot(0);
} }
return m_HotSlots[a_SlotNum]; return m_ArmorSlots.GetSlot(a_ArmorSlotNum);
}
const cItem & cInventory::GetInventorySlot(int a_InventorySlotNum) const
{
if ((a_InventorySlotNum < 0) || (a_InventorySlotNum >= invInventoryCount))
{
LOGWARNING("%s: requesting an invalid slot index: %d out of %d. Returning the first one instead", __FUNCTION__, a_InventorySlotNum, invInventoryCount - 1);
return m_InventorySlots.GetSlot(0);
}
return m_InventorySlots.GetSlot(a_InventorySlotNum);
}
const cItem & cInventory::GetHotbarSlot(int a_SlotNum) const
{
if ((a_SlotNum < 0) || (a_SlotNum >= invHotbarCount))
{
LOGWARNING("%s: requesting an invalid slot index: %d out of %d. Returning the first one instead", __FUNCTION__, a_SlotNum, invHotbarCount - 1);
return m_HotbarSlots.GetSlot(0);
}
return m_HotbarSlots.GetSlot(a_SlotNum);
} }
@ -245,7 +303,7 @@ const cItem & cInventory::GetHotBarSlot(int a_SlotNum) const
const cItem & cInventory::GetEquippedItem(void) const const cItem & cInventory::GetEquippedItem(void) const
{ {
return GetHotBarSlot(m_EquippedSlotNum); return GetHotbarSlot(m_EquippedSlotNum);
} }
@ -254,14 +312,14 @@ const cItem & cInventory::GetEquippedItem(void) const
void cInventory::SetEquippedSlotNum(int a_SlotNum) void cInventory::SetEquippedSlotNum(int a_SlotNum)
{ {
if ((a_SlotNum < 0) || (a_SlotNum >= 9)) if ((a_SlotNum < 0) || (a_SlotNum >= invHotbarCount))
{ {
LOGWARNING("%s requesting invalid slot index: %d out of 9. Setting 0 instead.", __FUNCTION__, a_SlotNum); LOGWARNING("%s: requesting invalid slot index: %d out of %d. Setting 0 instead.", __FUNCTION__, a_SlotNum, invHotbarCount - 1);
m_EquippedSlotNum = 0; m_EquippedSlotNum = 0;
} }
else else
{ {
m_EquippedSlotNum = (short)a_SlotNum; m_EquippedSlotNum = a_SlotNum;
} }
} }
@ -271,7 +329,7 @@ void cInventory::SetEquippedSlotNum(int a_SlotNum)
bool cInventory::DamageEquippedItem(short a_Amount) bool cInventory::DamageEquippedItem(short a_Amount)
{ {
return DamageItem(c_HotOffset + m_EquippedSlotNum, a_Amount); return DamageItem(invHotbarOffset + m_EquippedSlotNum, a_Amount);
} }
@ -280,22 +338,27 @@ bool cInventory::DamageEquippedItem(short a_Amount)
bool cInventory::DamageItem(int a_SlotNum, short a_Amount) bool cInventory::DamageItem(int a_SlotNum, short a_Amount)
{ {
if ((a_SlotNum < 0) || (a_SlotNum >= ARRAYCOUNT(m_Slots))) if ((a_SlotNum < 0) || (a_SlotNum >= invNumSlots))
{ {
LOGWARNING("%s requesting an invalid slot index: %d out of %d", __FUNCTION__, a_SlotNum, ARRAYCOUNT(m_Slots)); LOGWARNING("%s: requesting an invalid slot index: %d out of %d", __FUNCTION__, a_SlotNum, invNumSlots - 1);
return false; return false;
} }
if (!m_Slots[a_SlotNum].DamageItem(a_Amount)) int GridSlotNum = 0;
cItemGrid * Grid = GetGridForSlotNum(a_SlotNum, GridSlotNum);
if (Grid == NULL)
{ {
LOGWARNING("%s(%d, %d): requesting an invalid grid, ignoring.", __FUNCTION__, a_SlotNum, a_Amount);
return false;
}
if (!Grid->DamageItem(GridSlotNum, a_Amount))
{
// The item has been damaged, but did not break yet
return false; return false;
} }
// The item has broken, remove it: // The item has broken, remove it:
m_Slots[a_SlotNum].Empty(); Grid->EmptySlot(a_SlotNum);
SendSlot(a_SlotNum);
// TODO: If it was a special slot (armor / equipped), broadcast the change
return true; return true;
} }
@ -303,6 +366,17 @@ bool cInventory::DamageItem(int a_SlotNum, short a_Amount)
void cInventory::CopyToItems(cItems & a_Items)
{
m_ArmorSlots.CopyToItems(a_Items);
m_InventorySlots.CopyToItems(a_Items);
m_HotbarSlots.CopyToItems(a_Items);
}
void cInventory::SendWholeInventory(cClientHandle & a_Client) void cInventory::SendWholeInventory(cClientHandle & a_Client)
{ {
a_Client.SendWholeInventory(*this); a_Client.SendWholeInventory(*this);
@ -320,35 +394,14 @@ void cInventory::SendSlot(int a_SlotNum)
// Sanitize items that are not completely empty (ie. count == 0, but type != empty) // Sanitize items that are not completely empty (ie. count == 0, but type != empty)
Item.Empty(); Item.Empty();
} }
m_Owner.GetClientHandle()->SendInventorySlot(0, a_SlotNum, Item); m_Owner.GetClientHandle()->SendInventorySlot(0, a_SlotNum + 5, Item); // Slots in the client are numbered "+ 5" because of crafting grid and result
}
int cInventory::HowManyCanFit(short a_ItemType, short a_ItemDamage, int a_BeginSlot, int a_EndSlot)
{
int res = 0;
for (int i = a_BeginSlot; i <= a_EndSlot; i++)
{
if (
m_Slots[i].IsEmpty() ||
((m_Slots[i].m_ItemType == a_ItemType) && (m_Slots[i].m_ItemDamage == a_ItemDamage))
)
{
int MaxCount = ItemHandler(a_ItemType)->GetMaxStackSize();
ASSERT(m_Slots[i].m_ItemCount <= MaxCount);
res += MaxCount - m_Slots[i].m_ItemCount;
}
} // for i - m_Slots[]
return res;
} }
/*
int cInventory::MoveItem(short a_ItemType, short a_ItemDamage, int a_Count, int a_BeginSlot, int a_EndSlot) int cInventory::MoveItem(short a_ItemType, short a_ItemDamage, int a_Count, int a_BeginSlot, int a_EndSlot)
{ {
int res = 0; int res = 0;
@ -378,21 +431,22 @@ int cInventory::MoveItem(short a_ItemType, short a_ItemDamage, int a_Count, int
// No more space to distribute to // No more space to distribute to
return res; return res;
} }
*/
int cInventory::SlotNumToEntityEquipmentID(short a_SlotNum) int cInventory::ArmorSlotNumToEntityEquipmentID(short a_ArmorSlotNum)
{ {
switch (a_SlotNum) switch (a_ArmorSlotNum)
{ {
case 5: return 4; // Helmet case 0: return 4; // Helmet
case 6: return 3; // Chestplate case 1: return 3; // Chestplate
case 7: return 2; // Leggings case 2: return 2; // Leggings
case 8: return 1; // Boots case 3: return 1; // Boots
} }
LOGWARN("%s: invalid slot number: %d", __FUNCTION__, a_SlotNum); LOGWARN("%s: invalid armor slot number: %d", __FUNCTION__, a_ArmorSlotNum);
return 0; return 0;
} }
@ -400,6 +454,7 @@ int cInventory::SlotNumToEntityEquipmentID(short a_SlotNum)
#if 0
bool cInventory::AddToBar( cItem & a_Item, const int a_Offset, const int a_Size, bool* a_bChangedSlots, int a_Mode /* = 0 */ ) bool cInventory::AddToBar( cItem & a_Item, const int a_Offset, const int a_Size, bool* a_bChangedSlots, int a_Mode /* = 0 */ )
{ {
// Fill already present stacks // Fill already present stacks
@ -447,6 +502,7 @@ bool cInventory::AddToBar( cItem & a_Item, const int a_Offset, const int a_Size,
return true; return true;
} }
#endif
@ -454,10 +510,36 @@ bool cInventory::AddToBar( cItem & a_Item, const int a_Offset, const int a_Size,
void cInventory::SaveToJson(Json::Value & a_Value) void cInventory::SaveToJson(Json::Value & a_Value)
{ {
for(unsigned int i = 0; i < c_NumSlots; i++) // The JSON originally included the 4 crafting slots and the result, so we have to put empty items there, too:
cItem EmptyItem;
Json::Value EmptyItemJson;
EmptyItem.GetJson(EmptyItemJson);
for (int i = 0; i < 5; i++)
{
a_Value.append(EmptyItemJson);
}
// The 4 armor slots follow:
for (int i = 0; i < invArmorCount; i++)
{ {
Json::Value JSON_Item; Json::Value JSON_Item;
m_Slots[i].GetJson( JSON_Item ); m_ArmorSlots.GetSlot(i).GetJson(JSON_Item);
a_Value.append(JSON_Item);
}
// Next comes the main inventory:
for (int i = 0; i < invInventoryCount; i++)
{
Json::Value JSON_Item;
m_InventorySlots.GetSlot(i).GetJson(JSON_Item);
a_Value.append(JSON_Item);
}
// The hotbar is the last:
for (int i = 0; i < invHotbarCount; i++)
{
Json::Value JSON_Item;
m_HotbarSlots.GetSlot(i).GetJson(JSON_Item);
a_Value.append(JSON_Item); a_Value.append(JSON_Item);
} }
} }
@ -470,18 +552,124 @@ bool cInventory::LoadFromJson(Json::Value & a_Value)
{ {
int SlotIdx = 0; int SlotIdx = 0;
for( Json::Value::iterator itr = a_Value.begin(); itr != a_Value.end(); ++itr ) for (Json::Value::iterator itr = a_Value.begin(); itr != a_Value.end(); ++itr, SlotIdx++)
{ {
m_Slots[SlotIdx].FromJson( *itr ); cItem Item;
SlotIdx++; Item.FromJson(*itr);
if (SlotIdx >= ARRAYCOUNT(m_Slots))
// The JSON originally included the 4 crafting slots and the result slot, so we need to skip the first 5 items:
if (SlotIdx < 5)
{
continue;
}
// If we loaded all the slots, stop now, even if the JSON has more:
if (SlotIdx - 5 >= invNumSlots)
{ {
break; break;
} }
}
int GridSlotNum = 0;
cItemGrid * Grid = GetGridForSlotNum(SlotIdx - 5, GridSlotNum);
ASSERT(Grid != NULL);
Grid->SetSlot(GridSlotNum, Item);
} // for itr - a_Value[]
return true; return true;
} }
const cItemGrid * cInventory::GetGridForSlotNum(int a_SlotNum, int & a_GridSlotNum) const
{
ASSERT(a_SlotNum >= 0);
if (a_SlotNum < invArmorCount)
{
a_GridSlotNum = a_SlotNum;
return &m_ArmorSlots;
}
a_SlotNum -= invArmorCount;
if (a_SlotNum < invInventoryCount)
{
a_GridSlotNum = a_SlotNum;
return &m_InventorySlots;
}
a_GridSlotNum = a_SlotNum - invInventoryCount;
return &m_HotbarSlots;
}
cItemGrid * cInventory::GetGridForSlotNum(int a_SlotNum, int & a_GridSlotNum)
{
ASSERT(a_SlotNum >= 0);
if (a_SlotNum < invArmorCount)
{
a_GridSlotNum = a_SlotNum;
return &m_ArmorSlots;
}
a_SlotNum -= invArmorCount;
if (a_SlotNum < invInventoryCount)
{
a_GridSlotNum = a_SlotNum;
return &m_InventorySlots;
}
a_GridSlotNum = a_SlotNum - invInventoryCount;
return &m_HotbarSlots;
}
void cInventory::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum)
{
// Send the neccessary updates to whoever needs them
if (m_Owner.IsDestroyed())
{
// Owner is not (yet) valid, skip for now
return;
}
// Armor update needs broadcast to other players:
cWorld * World = m_Owner.GetWorld();
if ((a_ItemGrid == &m_ArmorSlots) && (World != NULL))
{
World->BroadcastEntityEquipment(
m_Owner, ArmorSlotNumToEntityEquipmentID(a_SlotNum),
m_ArmorSlots.GetSlot(a_SlotNum), m_Owner.GetClientHandle()
);
}
// Convert the grid-local a_SlotNum to our global SlotNum:
int Base = 0;
if (a_ItemGrid == &m_ArmorSlots)
{
Base = invArmorOffset;
}
else if (a_ItemGrid == &m_InventorySlots)
{
Base = invInventoryOffset;
}
else if (a_ItemGrid == &m_HotbarSlots)
{
Base = invHotbarOffset;
}
else
{
ASSERT(!"Unknown ItemGrid calling OnSlotChanged()");
return;
}
SendSlot(Base + a_SlotNum);
}

View File

@ -1,7 +1,7 @@
#pragma once #pragma once
#include "Item.h" #include "ItemGrid.h"
@ -18,36 +18,108 @@ class cPlayer;
// tolua_begin
class cInventory // tolua_export /** This class represents the player's inventory
{ // tolua_export The slots are divided into three areas:
- armor slots (1 x 4)
- inventory slots (9 x 3)
- hotbar slots (9 x 1)
The generic GetSlot(), SetSlot() and HowManyCanFit() functions take the index of the slots,
as if armor slots, inventory slots and then hotbar slots were put one after another.
You can use the invArmorOffset, invInventoryOffset and invHotbarOffset constants.
*/
class cInventory :
public cItemGrid::cListener
{
public: public:
// Counts and offsets to individual parts of the inventory, as used by GetSlot() / SetSlot() / HowManyCanFit():
enum
{
invArmorCount = 4,
invInventoryCount = 9 * 3,
invHotbarCount = 9,
invArmorOffset = 0,
invInventoryOffset = invArmorOffset + invArmorCount,
invHotbarOffset = invInventoryOffset + invInventoryCount,
invNumSlots = invHotbarOffset + invHotbarCount
} ;
// tolua_end
cInventory(cPlayer & a_Owner); cInventory(cPlayer & a_Owner);
~cInventory();
void Clear(); // tolua_export // tolua_begin
// cItem * GetSlotsForType( int a_Type ); /// Removes all items from the entire inventory
// int GetSlotCountForType( int a_Type ); void Clear(void);
bool AddItem( cItem & a_Item ); // tolua_export /// Returns number of items out of a_ItemStack that can fit in the storage
bool AddItemAnyAmount( cItem & a_Item ); // tolua_export int HowManyCanFit(const cItem & a_ItemStack, bool a_ConsiderEmptySlots);
bool RemoveItem( cItem & a_Item ); // tolua_export
void SaveToJson(Json::Value & a_Value); /// Returns how many items of the specified type would fit into the slot range specified
bool LoadFromJson(Json::Value & a_Value); int HowManyCanFit(const cItem & a_ItemStack, int a_BeginSlotNum, int a_EndSlotNum, bool a_ConsiderEmptySlots);
/** Adds as many items out of a_ItemStack as can fit.
If a_AllowNewStacks is set to false, only existing stacks can be topped up;
if a_AllowNewStacks is set to true, empty slots can be used for the rest.
Returns the number of items that fit.
*/
int AddItem(const cItem & a_ItemStack, bool a_AllowNewStacks = true);
/** Same as AddItem, but works on an entire list of item stacks.
The a_ItemStackList is modified to reflect the leftover items.
If a_AllowNewStacks is set to false, only existing stacks can be topped up;
if a_AllowNewStacks is set to true, empty slots can be used for the rest
Returns the total number of items that fit.
*/
int AddItems(cItems & a_ItemStackList, bool a_AllowNewStacks);
/// Removes one item out of the currently equipped item stack, returns true if successful, false if empty-handed
bool RemoveOneEquippedItem(void);
/// Returns the number of items of type a_Item that are stored
int HowManyItems(const cItem & a_Item);
/// Returns true if there are at least as many items of type a_ItemStack as in a_ItemStack
bool HasItems(const cItem & a_ItemStack);
/// Returns the cItemGrid object representing the armor slots
cItemGrid & GetArmorGrid(void) { return m_ArmorSlots; }
/// Returns the cItemGrid object representing the main inventory slots
cItemGrid & GetInventoryGrid(void) { return m_InventorySlots; }
/// Returns the cItemGrid object representing the hotbar slots
cItemGrid & GetHotbarGrid(void) { return m_HotbarSlots; }
/// Returns the player associated with this inventory
cPlayer & GetOwner(void) { return m_Owner; }
/// Copies the non-empty slots into a_ItemStacks; preserves the original a_Items contents
void CopyToItems(cItems & a_Items);
// tolua_end
void SendWholeInventory(cClientHandle & a_Client); void SendWholeInventory(cClientHandle & a_Client);
const cItem * GetSlots(void) const { return m_Slots; } /// Returns the player associated with this inventory (const version)
const cPlayer & GetOwner(void) const { return m_Owner; }
// tolua_begin // tolua_begin
const cItem & GetSlot(int a_SlotNum) const; const cItem & GetSlot(int a_SlotNum) const;
const cItem & GetHotBarSlot(int a_HotBarSlotNum) const; const cItem & GetArmorSlot(int a_ArmorSlotNum) const;
const cItem & GetInventorySlot(int a_InventorySlotNum) const;
const cItem & GetHotbarSlot(int a_HotBarSlotNum) const;
const cItem & GetEquippedItem(void) const; const cItem & GetEquippedItem(void) const;
void SetSlot(int a_SlotNum, const cItem & a_Item); void SetSlot(int a_SlotNum, const cItem & a_Item);
void SetHotBarSlot(int a_HotBarSlotNum, const cItem & a_Item); void SetArmorSlot(int a_ArmorSlotNum, const cItem & a_Item);
void SetInventorySlot(int a_InventorySlotNum, const cItem & a_Item);
void SetHotbarSlot(int a_HotBarSlotNum, const cItem & a_Item);
void SetEquippedSlotNum(int a_SlotNum); void SetEquippedSlotNum(int a_SlotNum);
int GetEquippedSlotNum(void) { return m_EquippedSlotNum; } int GetEquippedSlotNum(void) { return m_EquippedSlotNum; }
@ -58,48 +130,41 @@ public:
/// Adds the specified damage to the currently held item; deletes the item and returns true if the item broke. /// Adds the specified damage to the currently held item; deletes the item and returns true if the item broke.
bool DamageEquippedItem(short a_Amount = 1); bool DamageEquippedItem(short a_Amount = 1);
const cItem & GetEquippedHelmet (void) const { return m_Slots[c_ArmorOffset]; } const cItem & GetEquippedHelmet (void) const { return m_ArmorSlots.GetSlot(0); }
const cItem & GetEquippedChestplate(void) const { return m_Slots[c_ArmorOffset + 1]; } const cItem & GetEquippedChestplate(void) const { return m_ArmorSlots.GetSlot(1); }
const cItem & GetEquippedLeggings (void) const { return m_Slots[c_ArmorOffset + 2]; } const cItem & GetEquippedLeggings (void) const { return m_ArmorSlots.GetSlot(2); }
const cItem & GetEquippedBoots (void) const { return m_Slots[c_ArmorOffset + 3]; } const cItem & GetEquippedBoots (void) const { return m_ArmorSlots.GetSlot(3); }
/// Sends the slot contents to the owner
void SendSlot(int a_SlotNum);
// tolua_end // tolua_end
void SendSlot( int a_SlotNum ); // tolua_export /// Converts an armor slot number into the ID for the EntityEquipment packet
static int ArmorSlotNumToEntityEquipmentID(short a_ArmorSlotNum);
/// Returns how many items of the specified type would fit into the slot range specified void SaveToJson(Json::Value & a_Value);
int HowManyCanFit(short a_ItemType, short a_ItemDamage, int a_BeginSlot, int a_EndSlot); bool LoadFromJson(Json::Value & a_Value);
/// Moves items, fitting them into the slot range specified, up to a_Count items. Returns the number of items moved
int MoveItem(short a_ItemType, short a_ItemDamage, int a_Count, int a_BeginSlot, int a_EndSlot);
static const unsigned int c_NumSlots = 45;
static const unsigned int c_MainSlots = 27;
static const unsigned int c_HotSlots = 9;
static const unsigned int c_CraftSlots = 4;
static const unsigned int c_ArmorSlots = 4;
static const unsigned int c_CraftOffset = 0;
static const unsigned int c_ArmorOffset = 5;
static const unsigned int c_MainOffset = 9;
static const unsigned int c_HotOffset = 36;
/// Converts a slot number into the ID for the EntityEquipment packet
static int SlotNumToEntityEquipmentID(short a_SlotNum);
protected: protected:
bool AddToBar( cItem & a_Item, const int a_Offset, const int a_Size, bool* a_bChangedSlots, int a_Mode = 0 ); bool AddToBar( cItem & a_Item, const int a_Offset, const int a_Size, bool* a_bChangedSlots, int a_Mode = 0 );
cItem m_Slots[c_NumSlots]; cItemGrid m_ArmorSlots;
cItemGrid m_InventorySlots;
cItem * m_MainSlots; cItemGrid m_HotbarSlots;
cItem * m_CraftSlots;
cItem * m_ArmorSlots;
cItem * m_HotSlots;
int m_EquippedSlotNum; int m_EquippedSlotNum;
cPlayer & m_Owner; cPlayer & m_Owner;
/// Returns the ItemGrid and the (grid-local) slot number for a (global) slot number; return NULL for invalid SlotNum
const cItemGrid * GetGridForSlotNum(int a_SlotNum, int & a_GridSlotNum) const;
/// Returns the ItemGrid and the (grid-local) slot number for a (global) slot number; return NULL for invalid SlotNum
cItemGrid * GetGridForSlotNum(int a_SlotNum, int & a_GridSlotNum);
// cItemGrid::cListener override:
virtual void OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) override;
}; // tolua_export }; // tolua_export

View File

@ -75,7 +75,7 @@ bool cItem::DamageItem(short a_Amount)
bool cItem::IsStackableWith(const cItem & a_OtherStack) bool cItem::IsStackableWith(const cItem & a_OtherStack) const
{ {
if (a_OtherStack.m_ItemType != m_ItemType) if (a_OtherStack.m_ItemType != m_ItemType)
{ {

View File

@ -74,7 +74,7 @@ public:
inline bool IsDamageable(void) const { return (GetMaxDamage() > 0); } inline bool IsDamageable(void) const { return (GetMaxDamage() > 0); }
/// Returns true if this itemstack can stack with the specified stack (types match, enchantments etc.) ItemCounts are ignored! /// Returns true if this itemstack can stack with the specified stack (types match, enchantments etc.) ItemCounts are ignored!
bool IsStackableWith(const cItem & a_OtherStack); bool IsStackableWith(const cItem & a_OtherStack) const;
// tolua_end // tolua_end
void GetJson( Json::Value & a_OutValue ) const; void GetJson( Json::Value & a_OutValue ) const;

View File

@ -16,7 +16,8 @@ cItemGrid::cItemGrid(int a_Width, int a_Height) :
m_Width(a_Width), m_Width(a_Width),
m_Height(a_Height), m_Height(a_Height),
m_NumSlots(a_Width * a_Height), m_NumSlots(a_Width * a_Height),
m_Slots(new cItem[a_Width * a_Height]) m_Slots(new cItem[a_Width * a_Height]),
m_IsInTriggerListeners(false)
{ {
} }
@ -149,6 +150,7 @@ void cItemGrid::SetSlot(int a_SlotNum, const cItem & a_Item)
return; return;
} }
m_Slots[a_SlotNum] = a_Item; m_Slots[a_SlotNum] = a_Item;
TriggerListeners(a_SlotNum);
} }
@ -164,11 +166,46 @@ void cItemGrid::SetSlot(int a_SlotNum, short a_ItemType, char a_ItemCount, short
void cItemGrid::EmptySlot(int a_X, int a_Y)
{
EmptySlot(GetSlotNum(a_X, a_Y));
}
void cItemGrid::EmptySlot(int a_SlotNum)
{
if ((a_SlotNum < 0) || (a_SlotNum >= m_NumSlots))
{
LOGWARNING("%s: Invalid slot number %d out of %d slots",
__FUNCTION__, a_SlotNum, m_NumSlots
);
return;
}
// Check if already empty:
if (m_Slots[a_SlotNum].IsEmpty())
{
return;
}
// Empty and notify
m_Slots[a_SlotNum].Empty();
TriggerListeners(a_SlotNum);
}
void cItemGrid::Clear(void) void cItemGrid::Clear(void)
{ {
for (int i = 0; i < m_NumSlots; i++) for (int i = 0; i < m_NumSlots; i++)
{ {
m_Slots[i].Empty(); m_Slots[i].Empty();
TriggerListeners(i);
} }
} }
@ -203,10 +240,33 @@ int cItemGrid::HowManyCanFit(const cItem & a_ItemStack)
bool cItemGrid::AddItem(cItem & a_ItemStack) int cItemGrid::AddItem(cItem & a_ItemStack, bool a_AllowNewStacks)
{ {
int NumLeft = a_ItemStack.m_ItemCount; int NumLeft = a_ItemStack.m_ItemCount;
int MaxStack = ItemHandler(a_ItemStack.m_ItemType)->GetMaxStackSize(); int MaxStack = ItemHandler(a_ItemStack.m_ItemType)->GetMaxStackSize();
// Scan existing stacks:
for (int i = m_NumSlots - 1; i >= 0; i--)
{
if (m_Slots[i].IsStackableWith(a_ItemStack))
{
int PrevCount = m_Slots[i].m_ItemCount;
m_Slots[i].m_ItemCount = std::min(MaxStack, PrevCount + NumLeft);
NumLeft -= m_Slots[i].m_ItemCount - PrevCount;
TriggerListeners(i);
}
if (NumLeft <= 0)
{
// All items fit
return a_ItemStack.m_ItemCount;
}
} // for i - m_Slots[]
if (!a_AllowNewStacks)
{
return (a_ItemStack.m_ItemCount - NumLeft);
}
for (int i = m_NumSlots - 1; i >= 0; i--) for (int i = m_NumSlots - 1; i >= 0; i--)
{ {
if (m_Slots[i].IsEmpty()) if (m_Slots[i].IsEmpty())
@ -214,40 +274,87 @@ bool cItemGrid::AddItem(cItem & a_ItemStack)
m_Slots[i] = a_ItemStack; m_Slots[i] = a_ItemStack;
m_Slots[i].m_ItemCount = std::min(MaxStack, NumLeft); m_Slots[i].m_ItemCount = std::min(MaxStack, NumLeft);
NumLeft -= m_Slots[i].m_ItemCount; NumLeft -= m_Slots[i].m_ItemCount;
} TriggerListeners(i);
else if (m_Slots[i].IsStackableWith(a_ItemStack))
{
int PrevCount = m_Slots[i].m_ItemCount;
m_Slots[i].m_ItemCount = std::min(MaxStack, PrevCount + NumLeft);
NumLeft -= m_Slots[i].m_ItemCount - PrevCount;
} }
if (NumLeft <= 0) if (NumLeft <= 0)
{ {
// All items fit // All items fit
return true; return a_ItemStack.m_ItemCount;
} }
} // for i - m_Slots[] } // for i - m_Slots[]
return (a_ItemStack.m_ItemCount > NumLeft); return (a_ItemStack.m_ItemCount - NumLeft);
} }
bool cItemGrid::AddItems(cItems & a_ItemStackList) int cItemGrid::AddItems(cItems & a_ItemStackList, bool a_AllowNewStacks)
{ {
bool res; int TotalAdded = 0;
for (cItems::iterator itr = a_ItemStackList.begin(); itr != a_ItemStackList.end();) for (cItems::iterator itr = a_ItemStackList.begin(); itr != a_ItemStackList.end();)
{ {
res = AddItem(*itr) | res; int NumAdded = AddItem(*itr, a_AllowNewStacks);
if (itr->IsEmpty()) if (itr->m_ItemCount == NumAdded)
{ {
itr = a_ItemStackList.erase(itr); itr = a_ItemStackList.erase(itr);
} }
else else
{ {
itr->m_ItemCount -= NumAdded;
++itr; ++itr;
} }
TotalAdded += NumAdded;
}
return TotalAdded;
}
int cItemGrid::ChangeSlotCount(int a_SlotNum, int a_AddToCount)
{
if ((a_SlotNum < 0) || (a_SlotNum >= m_NumSlots))
{
LOGWARNING("%s: Invalid slot number %d out of %d slots, ignoring the call, returning empty item",
__FUNCTION__, a_SlotNum, m_NumSlots
);
return 0;
}
if (m_Slots[a_SlotNum].IsEmpty())
{
// The item is empty, it's not gonna change
return 0;
}
if (m_Slots[a_SlotNum].m_ItemCount < -a_AddToCount)
{
// Trying to remove more items than there already are, make the item empty
m_Slots[a_SlotNum].Empty();
TriggerListeners(a_SlotNum);
return 0;
}
m_Slots[a_SlotNum].m_ItemCount += a_AddToCount;
TriggerListeners(a_SlotNum);
return m_Slots[a_SlotNum].m_ItemCount;
}
int cItemGrid::HowManyItems(const cItem & a_Item)
{
int res = 0;
for (int i = 0; i < m_NumSlots; i++)
{
if (m_Slots[i].IsStackableWith(a_Item))
{
res += m_Slots[i].m_ItemCount;
}
} }
return res; return res;
} }
@ -256,6 +363,16 @@ bool cItemGrid::AddItems(cItems & a_ItemStackList)
bool cItemGrid::HasItems(const cItem & a_ItemStack)
{
int CurrentlyHave = HowManyItems(a_ItemStack);
return (CurrentlyHave >= a_ItemStack.m_ItemCount);
}
int cItemGrid::GetFirstEmptySlot(void) const int cItemGrid::GetFirstEmptySlot(void) const
{ {
return GetNextEmptySlot(-1); return GetNextEmptySlot(-1);
@ -312,6 +429,20 @@ void cItemGrid::CopyToItems(cItems & a_Items) const
bool cItemGrid::DamageItem(int a_SlotNum, short a_Amount)
{
if ((a_SlotNum < 0) || (a_SlotNum >= m_NumSlots))
{
LOGWARNING("%s: invalid slot number %d out of %d slots, ignoring.", __FUNCTION__, a_SlotNum, m_NumSlots);
return false;
}
return m_Slots[a_SlotNum].DamageItem(a_Amount);
}
void cItemGrid::GenerateRandomLootWithBooks(const cLootProbab * a_LootProbabs, int a_CountLootProbabs, int a_NumSlots, int a_Seed) void cItemGrid::GenerateRandomLootWithBooks(const cLootProbab * a_LootProbabs, int a_CountLootProbabs, int a_NumSlots, int a_Seed)
{ {
// Calculate the total weight: // Calculate the total weight:
@ -347,3 +478,47 @@ void cItemGrid::GenerateRandomLootWithBooks(const cLootProbab * a_LootProbabs, i
void cItemGrid::AddListener(cListener & a_Listener)
{
cCSLock Lock(m_CSListeners);
ASSERT(!m_IsInTriggerListeners); // Must not call this while in TriggerListeners()
m_Listeners.push_back(&a_Listener);
}
void cItemGrid::RemoveListener(cListener & a_Listener)
{
cCSLock Lock(m_CSListeners);
ASSERT(!m_IsInTriggerListeners); // Must not call this while in TriggerListeners()
for (cListeners::iterator itr = m_Listeners.begin(), end = m_Listeners.end(); itr != end; ++itr)
{
if (*itr == &a_Listener)
{
m_Listeners.erase(itr);
return;
}
} // for itr - m_Listeners[]
}
void cItemGrid::TriggerListeners(int a_SlotNum)
{
cCSLock Lock(m_CSListeners);
m_IsInTriggerListeners = true;
for (cListeners::iterator itr = m_Listeners.begin(), end = m_Listeners.end(); itr != end; ++itr)
{
(*itr)->OnSlotChanged(this, a_SlotNum);
} // for itr - m_Listeners[]
m_IsInTriggerListeners = false;
}

View File

@ -19,6 +19,16 @@ class cItemGrid
{ {
public: public:
// tolua_end // tolua_end
/// This class is used as a callback for when a slot changes
class cListener
{
public:
/// Called whenever a slot changes
virtual void OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum) = 0;
} ;
typedef std::vector<cListener *> cListeners;
cItemGrid(int a_Width, int a_Height); cItemGrid(int a_Width, int a_Height);
~cItemGrid(); ~cItemGrid();
@ -48,17 +58,42 @@ public:
void SetSlot(int a_SlotNum, const cItem & a_Item); void SetSlot(int a_SlotNum, const cItem & a_Item);
void SetSlot(int a_SlotNum, short a_ItemType, char a_ItemCount, short a_ItemDamage); void SetSlot(int a_SlotNum, short a_ItemType, char a_ItemCount, short a_ItemDamage);
// Empty the specified slot; Logs warning and doesn't set on invalid coords / slotnum
void EmptySlot(int a_X, int a_Y);
void EmptySlot(int a_SlotNum);
/// Sets all items as empty /// Sets all items as empty
void Clear(void); void Clear(void);
/// Returns number of items out of a_ItemStack that can fit in the storage /// Returns number of items out of a_ItemStack that can fit in the storage
int HowManyCanFit(const cItem & a_ItemStack); int HowManyCanFit(const cItem & a_ItemStack);
/// Adds as many items out of a_ItemStack as can fit; the rest is left in a_ItemStack; returns true if any items fit. /** Adds as many items out of a_ItemStack as can fit.
bool AddItem(cItem & a_ItemStack); If a_AllowNewStacks is set to false, only existing stacks can be topped up;
if a_AllowNewStacks is set to true, empty slots can be used for the rest
Returns the number of items that fit.
*/
int AddItem(cItem & a_ItemStack, bool a_AllowNewStacks);
/// Same as AddItem, but works on an entire list of item stacks /** Same as AddItem, but works on an entire list of item stacks.
bool AddItems(cItems & a_ItemStackList); The a_ItemStackList is modified to reflect the leftover items.
If a_AllowNewStacks is set to false, only existing stacks can be topped up;
if a_AllowNewStacks is set to true, empty slots can be used for the rest
Returns the total number of items that fit.
*/
int AddItems(cItems & a_ItemStackList, bool a_AllowNewStacks);
/** Adds (or subtracts, if a_AddToCount is negative) to the count of items in the specified slot.
If the slot is empty, ignores the call.
Returns the new count.
*/
int ChangeSlotCount(int a_SlotNum, int a_AddToCount);
/// Returns the number of items of type a_Item that are stored
int HowManyItems(const cItem & a_Item);
/// Returns true if there are at least as many items of type a_ItemStack as in a_ItemStack
bool HasItems(const cItem & a_ItemStack);
/// Returns the index of the first empty slot; -1 if all full /// Returns the index of the first empty slot; -1 if all full
int GetFirstEmptySlot(void) const; int GetFirstEmptySlot(void) const;
@ -69,17 +104,27 @@ public:
/// Returns the index of the first empty slot following a_StartFrom (a_StartFrom is not checked) /// Returns the index of the first empty slot following a_StartFrom (a_StartFrom is not checked)
int GetNextEmptySlot(int a_StartFrom) const; int GetNextEmptySlot(int a_StartFrom) const;
/// Copies the contents into a cItems object /// Copies the contents into a cItems object; preserves the original a_Items contents
void CopyToItems(cItems & a_Items) const; void CopyToItems(cItems & a_Items) const;
/// Adds the specified damage to the specified item; returns true if the item broke (but the item is left intact)
bool DamageItem(int a_SlotNum, short a_Amount);
// tolua_end // tolua_end
/** Generates random loot from the specified loot probability table, with a chance of enchanted books added. /** Generates random loot from the specified loot probability table, with a chance of enchanted books added.
A total of a_NumSlots are taken by the loot. A total of a_NumSlots are taken by the loot.
Cannot export to Lua due to raw array a_LootProbabs. Cannot export to Lua due to raw array a_LootProbabs. TODO: Make this exportable / export through ManualBindings.cpp with a Lua table as LootProbabs
*/ */
void GenerateRandomLootWithBooks(const cLootProbab * a_LootProbabs, int a_CountLootProbabs, int a_NumSlots, int a_Seed); void GenerateRandomLootWithBooks(const cLootProbab * a_LootProbabs, int a_CountLootProbabs, int a_NumSlots, int a_Seed);
/// Adds a callback that gets called whenever a slot changes. Must not be called from within the listener callback!
void AddListener(cListener & a_Listener);
/// Removes a slot-change-callback. Must not be called from within the listener callback!
void RemoveListener(cListener & a_Listener);
// tolua_begin // tolua_begin
protected: protected:
@ -87,6 +132,13 @@ protected:
int m_Height; int m_Height;
int m_NumSlots; // m_Width * m_Height, for easier validity checking in the access functions int m_NumSlots; // m_Width * m_Height, for easier validity checking in the access functions
cItem * m_Slots; // x + m_Width * y cItem * m_Slots; // x + m_Width * y
cListeners m_Listeners; ///< Listeners which should be notified on slot changes; the pointers are not owned by this object
cCriticalSection m_CSListeners; ///< CS that guards the m_Listeners against multi-thread access
bool m_IsInTriggerListeners; ///< Set to true while TriggerListeners is running, to detect attempts to manipulate listener list while triggerring
/// Calls all m_Listeners for the specified slot number
void TriggerListeners(int a_SlotNum);
} ; } ;
// tolua_end // tolua_end

View File

@ -81,8 +81,7 @@ public:
} }
// Remove the bucket from the inventory // Remove the bucket from the inventory
cItem Item(a_Item.m_ItemType, 1); if (!a_Player->GetInventory().RemoveOneEquippedItem())
if (!a_Player->GetInventory().RemoveItem(Item))
{ {
LOG("Clicked with an empty bucket, but cannot remove one from the inventory? WTF?"); LOG("Clicked with an empty bucket, but cannot remove one from the inventory? WTF?");
ASSERT(!"Inventory bucket mismatch"); ASSERT(!"Inventory bucket mismatch");
@ -90,8 +89,7 @@ public:
} }
// Give new bucket, filled with fluid: // Give new bucket, filled with fluid:
Item.m_ItemType = NewItem; cItem Item(NewItem, 1);
Item.m_ItemCount = 1;
a_Player->GetInventory().AddItem(Item); a_Player->GetInventory().AddItem(Item);
// Remove water / lava block // Remove water / lava block
@ -131,15 +129,13 @@ public:
if (a_Player->GetGameMode() != gmCreative) if (a_Player->GetGameMode() != gmCreative)
{ {
// Remove fluid bucket, add empty bucket: // Remove fluid bucket, add empty bucket:
cItem Item(a_Item.m_ItemType, 1); if (!a_Player->GetInventory().RemoveOneEquippedItem())
if (!a_Player->GetInventory().RemoveItem(Item))
{ {
LOG("Clicked with a full bucket, but cannot remove one from the inventory? WTF?"); LOG("Clicked with a full bucket, but cannot remove one from the inventory? WTF?");
ASSERT(!"Inventory bucket mismatch"); ASSERT(!"Inventory bucket mismatch");
return false; return false;
} }
Item.m_ItemType = E_ITEM_BUCKET; cItem Item(E_ITEM_BUCKET, 1);
Item.m_ItemCount = 1;
if (!a_Player->GetInventory().AddItem(Item)) if (!a_Player->GetInventory().AddItem(Item))
{ {
return false; return false;

View File

@ -22,15 +22,15 @@ public:
virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Dir) override virtual bool OnItemUse(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Dir) override
{ {
// TODO: Handle coloring the sheep, too (OnItemUseOnEntity maybe) // TODO: Handle coloring the sheep, too (OnItemUseOnEntity maybe)
// Handle growing the plants: // Handle growing the plants:
if (a_Item.m_ItemDamage == E_META_DYE_WHITE) if (a_Item.m_ItemDamage == E_META_DYE_WHITE)
{ {
if (a_World->GrowRipePlant(a_BlockX, a_BlockY, a_BlockZ, true)) if (a_World->GrowRipePlant(a_BlockX, a_BlockY, a_BlockZ, true))
{ {
if (a_Player->GetGameMode() != eGameMode_Creative) if (a_Player->GetGameMode() != gmCreative)
{ {
cItem Item(a_Item.m_ItemType, 1, a_Item.m_ItemDamage); a_Player->GetInventory().RemoveOneEquippedItem();
a_Player->GetInventory().RemoveItem(Item);
return true; return true;
} }
} }

View File

@ -36,8 +36,7 @@ public:
} }
else else
{ {
cItem Item(a_Item.m_ItemType, 1); if (a_Player->GetInventory().RemoveOneEquippedItem())
if (a_Player->GetInventory().RemoveItem(Item))
{ {
a_World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, Block - 1, Meta); // Block - 1 simple hack to save one if statement a_World->SetBlock(a_BlockX, a_BlockY, a_BlockZ, Block - 1, Meta); // Block - 1 simple hack to save one if statement
return true; return true;

View File

@ -38,8 +38,7 @@ public:
if (a_Player->GetGameMode() != 1) if (a_Player->GetGameMode() != 1)
{ {
// The mob was spawned, "use" the item: // The mob was spawned, "use" the item:
cItem Equipped(a_Player->GetEquippedItem()); a_Player->GetInventory().RemoveOneEquippedItem();
a_Player->GetInventory().RemoveItem(Equipped);
} }
return true; return true;
} }

View File

@ -40,8 +40,7 @@ void cJukeboxEntity::UsedBy(cPlayer * a_Player)
if (HeldItem.m_ItemType >= 2256 && HeldItem.m_ItemType <= 2267) if (HeldItem.m_ItemType >= 2256 && HeldItem.m_ItemType <= 2267)
{ {
m_Record = HeldItem.m_ItemType; m_Record = HeldItem.m_ItemType;
cItem Equipped(a_Player->GetInventory().GetEquippedItem()); a_Player->GetInventory().RemoveOneEquippedItem();
a_Player->GetInventory().RemoveItem(Equipped);
PlayRecord(); PlayRecord();
} }
} }

View File

@ -149,17 +149,17 @@ bool cPickup::CollectedBy(cPlayer * a_Dest)
return false; return false;
} }
if (a_Dest->GetInventory().AddItemAnyAmount(m_Item)) int NumAdded = a_Dest->GetInventory().AddItem(m_Item);
if (NumAdded > 0)
{ {
m_Item.m_ItemCount -= NumAdded;
m_World->BroadcastCollectPickup(*this, *a_Dest); m_World->BroadcastCollectPickup(*this, *a_Dest);
m_bCollected = true; if (m_Item.m_ItemCount == 0)
m_Timer = 0;
if (m_Item.m_ItemCount != 0)
{ {
cItems Pickup; // All of the pickup has been collected, schedule the pickup for destroying
Pickup.push_back(cItem(m_Item)); m_bCollected = true;
m_World->SpawnItemPickups(Pickup, GetPosX(), GetPosY(), GetPosZ());
} }
m_Timer = 0;
return true; return true;
} }

View File

@ -360,15 +360,8 @@ void cPlayer::KilledBy(cPawn * a_Killer)
m_bVisible = false; // So new clients don't see the player m_bVisible = false; // So new clients don't see the player
// Puke out all the items // Puke out all the items
const cItem * Items = m_Inventory.GetSlots();
cItems Pickups; cItems Pickups;
for (unsigned int i = 1; i < m_Inventory.c_NumSlots; ++i) m_Inventory.CopyToItems(Pickups);
{
if (!Items[i].IsEmpty())
{
Pickups.push_back(Items[i]);
}
}
m_Inventory.Clear(); m_Inventory.Clear();
m_World->SpawnItemPickups(Pickups, GetPosX(), GetPosY(), GetPosZ(), 10); m_World->SpawnItemPickups(Pickups, GetPosX(), GetPosY(), GetPosZ(), 10);
SaveToDisk(); // Save it, yeah the world is a tough place ! SaveToDisk(); // Save it, yeah the world is a tough place !
@ -378,7 +371,7 @@ void cPlayer::KilledBy(cPawn * a_Killer)
void cPlayer::Respawn() void cPlayer::Respawn(void)
{ {
m_Health = GetMaxHealth(); m_Health = GetMaxHealth();
@ -768,7 +761,7 @@ void cPlayer::TossItem(
) )
{ {
cItems Drops; cItems Drops;
if (a_CreateType) if (a_CreateType != 0)
{ {
// Just create item without touching the inventory (used in creative mode) // Just create item without touching the inventory (used in creative mode)
Drops.push_back(cItem(a_CreateType, a_Amount, a_CreateHealth)); Drops.push_back(cItem(a_CreateType, a_Amount, a_CreateHealth));
@ -800,8 +793,7 @@ void cPlayer::TossItem(
cItem DroppedItem(GetInventory().GetEquippedItem()); cItem DroppedItem(GetInventory().GetEquippedItem());
if (!DroppedItem.IsEmpty()) if (!DroppedItem.IsEmpty())
{ {
DroppedItem.m_ItemCount = 1; if (GetInventory().RemoveOneEquippedItem())
if (GetInventory().RemoveItem(DroppedItem))
{ {
DroppedItem.m_ItemCount = 1; // RemoveItem decreases the count, so set it to 1 again DroppedItem.m_ItemCount = 1; // RemoveItem decreases the count, so set it to 1 again
Drops.push_back(DroppedItem); Drops.push_back(DroppedItem);

View File

@ -81,10 +81,12 @@ public:
void LoginSetGameMode( eGameMode a_GameMode ); void LoginSetGameMode( eGameMode a_GameMode );
void SetIP(const AString & a_IP); void SetIP(const AString & a_IP);
// Tries to move to a new position, with collision checks and stuff /// Tries to move to a new position, with collision checks and stuff
virtual void MoveTo( const Vector3d & a_NewPos ); // tolua_export virtual void MoveTo( const Vector3d & a_NewPos ); // tolua_export
cWindow* GetWindow() { return m_CurrentWindow; } cWindow * GetWindow(void) { return m_CurrentWindow; }
const cWindow * GetWindow(void) const { return m_CurrentWindow; }
void OpenWindow( cWindow* a_Window ); void OpenWindow( cWindow* a_Window );
void CloseWindow(char a_WindowType); void CloseWindow(char a_WindowType);

View File

@ -874,8 +874,7 @@ void cProtocol125::SendWeather(eWeather a_Weather)
void cProtocol125::SendWholeInventory(const cInventory & a_Inventory) void cProtocol125::SendWholeInventory(const cInventory & a_Inventory)
{ {
cCSLock Lock(m_CSPacket); SendWholeInventory(*(a_Inventory.GetOwner().GetWindow()));
SendWindowSlots(0, a_Inventory.c_NumSlots, a_Inventory.GetSlots());
} }

View File

@ -438,14 +438,24 @@ void cProtocol132::SendWholeInventory(const cWindow & a_Window)
{ {
// 1.3.2 requires player inventory slots to be sent as SetSlot packets, // 1.3.2 requires player inventory slots to be sent as SetSlot packets,
// otherwise it sometimes fails to update the window // otherwise it sometimes fails to update the window
// Send the entire window:
super::SendWholeInventory(a_Window); super::SendWholeInventory(a_Window);
const cItem * Slots = m_Client->GetPlayer()->GetInventory().GetSlots();
int BaseOffset = a_Window.GetNumSlots() - cInventory::c_NumSlots + cInventory::c_MainOffset; // the number of non-inventory slots the window has; inventory follows // Send the player inventory and hotbar:
const cInventory & Inventory = m_Client->GetPlayer()->GetInventory();
int BaseOffset = a_Window.GetNumSlots() - (cInventory::invNumSlots - cInventory::invInventoryOffset); // Number of non-inventory slots
char WindowID = a_Window.GetWindowID(); char WindowID = a_Window.GetWindowID();
for (int i = 0; i < cInventory::c_NumSlots - cInventory::c_MainOffset; i++) for (int i = 0; i < cInventory::invInventoryCount; i++)
{ {
SendInventorySlot(WindowID, BaseOffset + i, Slots[i + cInventory::c_MainOffset]); SendInventorySlot(WindowID, BaseOffset + i, Inventory.GetInventorySlot(i));
} // for i - Slots[] } // for i - Inventory[]
BaseOffset += cInventory::invInventoryCount;
for (int i = 0; i < cInventory::invHotbarCount; i++)
{
SendInventorySlot(WindowID, BaseOffset + i, Inventory.GetHotbarSlot(i));
} // for i - Hotbar[]
// Send even the item being dragged: // Send even the item being dragged:
SendInventorySlot(-1, -1, m_Client->GetPlayer()->GetDraggingItem()); SendInventorySlot(-1, -1, m_Client->GetPlayer()->GetDraggingItem());
} }

View File

@ -613,7 +613,7 @@ void cSlotAreaInventoryBase::Clicked(cPlayer & a_Player, int a_SlotNum, eClickAc
const cItem * cSlotAreaInventoryBase::GetSlot(int a_SlotNum, cPlayer & a_Player) const cItem * cSlotAreaInventoryBase::GetSlot(int a_SlotNum, cPlayer & a_Player)
{ {
// a_SlotNum ranges from 0 to 35, map that to the player's inventory slots 9 to 44 // a_SlotNum ranges from 0 to 35, map that to the player's inventory slots according to the internal offset
return &a_Player.GetInventory().GetSlot(a_SlotNum + m_SlotOffset); return &a_Player.GetInventory().GetSlot(a_SlotNum + m_SlotOffset);
} }

View File

@ -8,7 +8,7 @@
#pragma once #pragma once
#include "../Item.h" #include "../Inventory.h"
@ -97,7 +97,7 @@ class cSlotAreaInventory :
public: public:
cSlotAreaInventory(cWindow & a_ParentWindow) : cSlotAreaInventory(cWindow & a_ParentWindow) :
cSlotAreaInventoryBase(27, 9, a_ParentWindow) // 27 slots, starting at inventory index 9 cSlotAreaInventoryBase(cInventory::invInventoryCount, cInventory::invInventoryOffset, a_ParentWindow)
{ {
} }
} ; } ;
@ -114,7 +114,7 @@ class cSlotAreaHotBar :
public: public:
cSlotAreaHotBar(cWindow & a_ParentWindow) : cSlotAreaHotBar(cWindow & a_ParentWindow) :
cSlotAreaInventoryBase(9, 36, a_ParentWindow) cSlotAreaInventoryBase(cInventory::invHotbarCount, cInventory::invHotbarOffset, a_ParentWindow)
{ {
} }
} ; } ;
@ -129,7 +129,7 @@ class cSlotAreaArmor :
{ {
public: public:
cSlotAreaArmor(cWindow & a_ParentWindow) : cSlotAreaArmor(cWindow & a_ParentWindow) :
cSlotAreaInventoryBase(4, 5, a_ParentWindow) cSlotAreaInventoryBase(cInventory::invArmorCount, cInventory::invArmorOffset, a_ParentWindow)
{ {
} }