36eab1b323
* Introduce recipe book functionality The recipe book helps especially new players. Missing it gives the impression that cuberite is not as advanced as it is. The handling of the recipe book uses the following functions: - Unlock Recipes (https://wiki.vg/index.php?title=Protocol&oldid=14204#Unlock_Recipes) to make recipes available and show the notification for new recipes. Initialization is done on player login for known ones, the update is done when new items are discovered. - Craft Recipe Request (https://wiki.vg/index.php?title=Protocol&oldid=14204#Craft_Recipe_Request) when the user selects a recipe from the recipe book to fill the slots. Known recipes are initialized on player login via `Unlock Recipes` with `Action` 0. As soon as a new recipe is discovered this is added via `Unlock Recipes` with `Action` 1. To be able to know and recognize new recipes the player class is extended with `KnownItems` and `KnownRecipes`. As soon as a player touches an item this is compared to the list of `KnownItems`, if the item is unknown the recipes are checked for this item and the other ingredients are checked with the list of `KnownItems`. If a full match is discovered the recipe is unlocked with the client and stored in the `KnownRecipes`. To unlock recipes the recipe ID is sent to the client. A mapping file (for protocol 1.12.2) translated the minecraft recipe names to ids. The crafting.txt is extended with and minecraft recipe names is possible. Limitations: Only a single recipe is added to the crafting area. Multiple clicks or shift click does not increase the number of builds. Co-authored-by: peterbell10 <peterbell10@live.co.uk> * Address first issues mentioned by @peterbell10 - Some linting - Extract loading of recipe specific protocol mapping into a function - Build `RecipeNameMap` only once - Use `std::optional` - Extract `LoadRecipe` from `Window` * Start to implement new suggestions * Update with suggestions from @peterbell10 * Some minor cleanup * Update protocol packet IDs * Remove unused include * Include header in cmake * Change a vector to integer counter * Change dromedaryCase method names to PascalCase * Address suggestions from @madmaxoft * Read Protocol subdirectories to load recipe books To load all recipebooks iterate over the `Protocol` subdirectories to find mapping files. Co-authored-by: peterbell10 <peterbell10@live.co.uk>
888 lines
18 KiB
C++
888 lines
18 KiB
C++
|
|
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
|
|
|
|
#include "Inventory.h"
|
|
#include "Entities/Player.h"
|
|
#include "ClientHandle.h"
|
|
#include "UI/Window.h"
|
|
#include "Item.h"
|
|
#include "Root.h"
|
|
#include "World.h"
|
|
|
|
#include "json/json.h"
|
|
|
|
#include "Items/ItemHandler.h"
|
|
|
|
|
|
|
|
|
|
|
|
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_ShieldSlots (1, 1), // 1 x 1 slots
|
|
m_Owner(a_Owner)
|
|
{
|
|
// Ask each ItemGrid to report changes to us:
|
|
m_ArmorSlots.AddListener(*this);
|
|
m_InventorySlots.AddListener(*this);
|
|
m_HotbarSlots.AddListener(*this);
|
|
m_ShieldSlots.AddListener(*this);
|
|
|
|
SetEquippedSlotNum(0);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cInventory::Clear(void)
|
|
{
|
|
m_ArmorSlots.Clear();
|
|
m_InventorySlots.Clear();
|
|
m_HotbarSlots.Clear();
|
|
m_ShieldSlots.Clear();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int cInventory::HowManyCanFit(const cItem & a_ItemStack, bool a_ConsiderEmptySlots)
|
|
{
|
|
return HowManyCanFit(a_ItemStack, 0, invNumSlots - 1, a_ConsiderEmptySlots);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int cInventory::HowManyCanFit(const cItem & a_ItemStack, int a_BeginSlotNum, int a_EndSlotNum, bool a_ConsiderEmptySlots)
|
|
{
|
|
|
|
UNUSED(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.IsEqual(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)
|
|
{
|
|
m_Owner.AddKnownItem(a_Item);
|
|
|
|
cItem ToAdd(a_Item);
|
|
int res = 0;
|
|
|
|
// When the item is a armor, try to set it directly to the armor slot.
|
|
if (ItemCategory::IsArmor(a_Item.m_ItemType))
|
|
{
|
|
for (int i = 0; i < m_ArmorSlots.GetNumSlots(); i++)
|
|
{
|
|
if (m_ArmorSlots.GetSlot(i).IsEmpty() && cSlotAreaArmor::CanPlaceArmorInSlot(i, a_Item))
|
|
{
|
|
m_ArmorSlots.SetSlot(i, a_Item);
|
|
return a_Item.m_ItemCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add to existing stacks in the hotbar.
|
|
res += m_HotbarSlots.AddItem(ToAdd, false);
|
|
ToAdd.m_ItemCount = static_cast<char>(a_Item.m_ItemCount - res);
|
|
if (ToAdd.m_ItemCount == 0)
|
|
{
|
|
return res;
|
|
}
|
|
|
|
// Add to existing stacks in main inventory.
|
|
res += m_InventorySlots.AddItem(ToAdd, false);
|
|
ToAdd.m_ItemCount = static_cast<char>(a_Item.m_ItemCount - res);
|
|
if (ToAdd.m_ItemCount == 0)
|
|
{
|
|
return res;
|
|
}
|
|
|
|
// All existing stacks are now filled.
|
|
if (!a_AllowNewStacks)
|
|
{
|
|
return res;
|
|
}
|
|
|
|
// Try adding new stacks to the hotbar.
|
|
res += m_HotbarSlots.AddItem(ToAdd, true);
|
|
ToAdd.m_ItemCount = static_cast<char>(a_Item.m_ItemCount - res);
|
|
if (ToAdd.m_ItemCount == 0)
|
|
{
|
|
return res;
|
|
}
|
|
|
|
// Try adding new stacks to the main inventory.
|
|
res += m_InventorySlots.AddItem(ToAdd, true);
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int cInventory::RemoveItem(const cItem & a_ItemStack)
|
|
{
|
|
int RemovedItems = m_ShieldSlots.RemoveItem(a_ItemStack);
|
|
|
|
if (RemovedItems < a_ItemStack.m_ItemCount)
|
|
{
|
|
RemovedItems += m_HotbarSlots.RemoveItem(a_ItemStack);
|
|
}
|
|
|
|
if (RemovedItems < a_ItemStack.m_ItemCount)
|
|
{
|
|
cItem Temp(a_ItemStack);
|
|
Temp.m_ItemCount -= RemovedItems;
|
|
RemovedItems += m_InventorySlots.RemoveItem(Temp);
|
|
}
|
|
|
|
return RemovedItems;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cItem * cInventory::FindItem(const cItem & a_RecipeItem)
|
|
{
|
|
cItem * Item = m_ShieldSlots.FindItem(a_RecipeItem);
|
|
if (Item != nullptr)
|
|
{
|
|
return Item;
|
|
}
|
|
Item = m_HotbarSlots.FindItem(a_RecipeItem);
|
|
if (Item != nullptr)
|
|
{
|
|
return Item;
|
|
}
|
|
|
|
return m_InventorySlots.FindItem(a_RecipeItem);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool cInventory::RemoveOneEquippedItem(void)
|
|
{
|
|
if (m_HotbarSlots.GetSlot(m_EquippedSlotNum).IsEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_HotbarSlots.ChangeSlotCount(m_EquippedSlotNum, -1);
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int cInventory::ReplaceOneEquippedItem(const cItem & a_Item, bool a_TryOtherSlots)
|
|
{
|
|
// Ignore whether there was an item in the slot to remove.
|
|
RemoveOneEquippedItem();
|
|
|
|
auto EquippedItem = GetEquippedItem();
|
|
if (EquippedItem.IsEmpty())
|
|
{
|
|
SetEquippedItem(a_Item);
|
|
return a_Item.m_ItemCount;
|
|
}
|
|
|
|
// Handle case when equipped item is the same as the replacement item.
|
|
cItem ItemsToAdd = a_Item;
|
|
if (EquippedItem.IsEqual(ItemsToAdd))
|
|
{
|
|
cItemHandler Handler(ItemsToAdd.m_ItemType);
|
|
auto AmountToAdd = std::min(static_cast<char>(Handler.GetMaxStackSize() - EquippedItem.m_ItemCount), ItemsToAdd.m_ItemCount);
|
|
|
|
EquippedItem.m_ItemCount += AmountToAdd;
|
|
SetEquippedItem(EquippedItem);
|
|
ItemsToAdd.m_ItemCount -= AmountToAdd;
|
|
}
|
|
|
|
auto ItemsAdded = a_Item.m_ItemCount - ItemsToAdd.m_ItemCount;
|
|
|
|
if (ItemsToAdd.m_ItemCount == 0)
|
|
{
|
|
return ItemsAdded;
|
|
}
|
|
|
|
if (!a_TryOtherSlots)
|
|
{
|
|
return ItemsAdded;
|
|
}
|
|
|
|
// Try the rest of the inventory.
|
|
return AddItem(ItemsToAdd) + ItemsAdded;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int cInventory::HowManyItems(const cItem & a_Item)
|
|
{
|
|
return
|
|
m_ArmorSlots.HowManyItems(a_Item) +
|
|
m_InventorySlots.HowManyItems(a_Item) +
|
|
m_HotbarSlots.HowManyItems(a_Item) +
|
|
m_ShieldSlots.HowManyItems(a_Item);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool cInventory::HasItems(const cItem & a_ItemStack)
|
|
{
|
|
int CurrentlyHave = HowManyItems(a_ItemStack);
|
|
return (CurrentlyHave >= a_ItemStack.m_ItemCount);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cInventory::SetSlot(int a_SlotNum, const cItem & a_Item)
|
|
{
|
|
if ((a_SlotNum < 0) || (a_SlotNum >= invNumSlots))
|
|
{
|
|
LOGWARNING("%s: requesting an invalid slot index: %d out of %d. Ignoring.", __FUNCTION__, a_SlotNum, invNumSlots - 1);
|
|
return;
|
|
}
|
|
|
|
int GridSlotNum = 0;
|
|
cItemGrid * Grid = GetGridForSlotNum(a_SlotNum, GridSlotNum);
|
|
if (Grid == nullptr)
|
|
{
|
|
LOGWARNING("%s(%d): requesting an invalid itemgrid. Ignoring.", __FUNCTION__, a_SlotNum);
|
|
return;
|
|
}
|
|
Grid->SetSlot(GridSlotNum, a_Item);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cInventory::SetArmorSlot(int a_ArmorSlotNum, const cItem & 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);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cInventory::SetShieldSlot(const cItem & a_Item)
|
|
{
|
|
m_ShieldSlots.SetSlot(0, a_Item);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cInventory::SetEquippedItem(const cItem & a_Item)
|
|
{
|
|
SetHotbarSlot(GetEquippedSlotNum(), a_Item);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cInventory::SendEquippedSlot()
|
|
{
|
|
int EquippedSlotNum = cInventory::invArmorCount + cInventory::invInventoryCount + GetEquippedSlotNum();
|
|
SendSlot(EquippedSlotNum);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const cItem & cInventory::GetSlot(int a_SlotNum) const
|
|
{
|
|
if ((a_SlotNum < 0) || (a_SlotNum >= invNumSlots))
|
|
{
|
|
LOGWARNING("%s: requesting an invalid slot index: %d out of %d. Returning the first inventory slot instead.", __FUNCTION__, a_SlotNum, invNumSlots - 1);
|
|
return m_InventorySlots.GetSlot(0);
|
|
}
|
|
int GridSlotNum = 0;
|
|
const cItemGrid * Grid = GetGridForSlotNum(a_SlotNum, GridSlotNum);
|
|
if (Grid == nullptr)
|
|
{
|
|
// 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::GetArmorSlot(int a_ArmorSlotNum) const
|
|
{
|
|
if ((a_ArmorSlotNum < 0) || (a_ArmorSlotNum >= invArmorCount))
|
|
{
|
|
LOGWARNING("%s: requesting an invalid slot index: %d out of %d. Returning the first one instead", __FUNCTION__, a_ArmorSlotNum, invArmorCount - 1);
|
|
return m_ArmorSlots.GetSlot(0);
|
|
}
|
|
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);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const cItem & cInventory::GetShieldSlot() const
|
|
{
|
|
return m_ShieldSlots.GetSlot(0);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const cItem & cInventory::GetEquippedItem(void) const
|
|
{
|
|
return GetHotbarSlot(m_EquippedSlotNum);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cInventory::SetEquippedSlotNum(int a_SlotNum)
|
|
{
|
|
if ((a_SlotNum < 0) || (a_SlotNum >= invHotbarCount))
|
|
{
|
|
LOGWARNING("%s: requesting invalid slot index: %d out of %d. Setting 0 instead.", __FUNCTION__, a_SlotNum, invHotbarCount - 1);
|
|
m_EquippedSlotNum = 0;
|
|
}
|
|
else
|
|
{
|
|
m_EquippedSlotNum = a_SlotNum;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool cInventory::DamageEquippedItem(short a_Amount)
|
|
{
|
|
return DamageItem(invHotbarOffset + m_EquippedSlotNum, a_Amount);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int cInventory::ChangeSlotCount(int a_SlotNum, int a_AddToCount)
|
|
{
|
|
int GridSlotNum = 0;
|
|
cItemGrid * Grid = GetGridForSlotNum(a_SlotNum, GridSlotNum);
|
|
if (Grid == nullptr)
|
|
{
|
|
LOGWARNING("%s: invalid slot number, expected 0 .. %d, got %d; ignoring", __FUNCTION__, invNumSlots, a_SlotNum);
|
|
return -1;
|
|
}
|
|
return Grid->ChangeSlotCount(GridSlotNum, a_AddToCount);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool cInventory::DamageItem(int a_SlotNum, short a_Amount)
|
|
{
|
|
if ((a_SlotNum < 0) || (a_SlotNum >= invNumSlots))
|
|
{
|
|
LOGWARNING("%s: requesting an invalid slot index: %d out of %d", __FUNCTION__, a_SlotNum, invNumSlots - 1);
|
|
return false;
|
|
}
|
|
if (a_Amount <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int GridSlotNum = 0;
|
|
cItemGrid * Grid = GetGridForSlotNum(a_SlotNum, GridSlotNum);
|
|
if (Grid == nullptr)
|
|
{
|
|
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
|
|
SendSlot(a_SlotNum);
|
|
return false;
|
|
}
|
|
|
|
// The item has broken, remove it:
|
|
Grid->EmptySlot(GridSlotNum);
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cInventory::CopyToItems(cItems & a_Items)
|
|
{
|
|
m_ArmorSlots.CopyToItems(a_Items);
|
|
m_InventorySlots.CopyToItems(a_Items);
|
|
m_HotbarSlots.CopyToItems(a_Items);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cInventory::SendSlot(int a_SlotNum)
|
|
{
|
|
cItem Item(GetSlot(a_SlotNum));
|
|
if (Item.IsEmpty())
|
|
{
|
|
// Sanitize items that are not completely empty (ie. count == 0, but type != empty)
|
|
Item.Empty();
|
|
}
|
|
m_Owner.GetClientHandle()->SendInventorySlot(0, static_cast<short>(a_SlotNum + 5), Item); // Slots in the client are numbered "+ 5" because of crafting grid and result
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
int cInventory::MoveItem(short a_ItemType, short a_ItemDamage, int a_Count, 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);
|
|
int NumToMove = std::min(a_Count, MaxCount - m_Slots[i].m_ItemCount);
|
|
m_Slots[i].m_ItemCount += NumToMove;
|
|
m_Slots[i].m_ItemDamage = a_ItemDamage;
|
|
m_Slots[i].m_ItemType = a_ItemType;
|
|
SendSlot(i);
|
|
res += NumToMove;
|
|
a_Count -= NumToMove;
|
|
if (a_Count <= 0)
|
|
{
|
|
// No more items to distribute
|
|
return res;
|
|
}
|
|
}
|
|
} // for i - m_Slots[]
|
|
// No more space to distribute to
|
|
return res;
|
|
}
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
int cInventory::ArmorSlotNumToEntityEquipmentID(short a_ArmorSlotNum)
|
|
{
|
|
switch (a_ArmorSlotNum)
|
|
{
|
|
case 0: return 4; // Helmet
|
|
case 1: return 3; // Chestplate
|
|
case 2: return 2; // Leggings
|
|
case 3: return 1; // Boots
|
|
}
|
|
LOGWARN("%s: invalid armor slot number: %d", __FUNCTION__, a_ArmorSlotNum);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#if 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
|
|
if (a_Mode < 2)
|
|
{
|
|
int MaxStackSize = cItemHandler::GetItemHandler(a_Item.m_ItemType)->GetMaxStackSize();
|
|
for (int i = 0; i < a_Size; i++)
|
|
{
|
|
if (
|
|
(m_Slots[i + a_Offset].m_ItemType == a_Item.m_ItemType) &&
|
|
(m_Slots[i + a_Offset].m_ItemCount < MaxStackSize) &&
|
|
(m_Slots[i + a_Offset].m_ItemDamage == a_Item.m_ItemDamage)
|
|
)
|
|
{
|
|
int NumFree = MaxStackSize - m_Slots[i + a_Offset].m_ItemCount;
|
|
if (NumFree >= a_Item.m_ItemCount)
|
|
{
|
|
|
|
// printf("1. Adding %i items ( free: %i)\n", a_Item.m_ItemCount, NumFree);
|
|
m_Slots[i + a_Offset].m_ItemCount += a_Item.m_ItemCount;
|
|
a_Item.m_ItemCount = 0;
|
|
a_bChangedSlots[i + a_Offset] = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// printf("2. Adding %i items\n", NumFree);
|
|
m_Slots[i + a_Offset].m_ItemCount += (char)NumFree;
|
|
a_Item.m_ItemCount -= (char)NumFree;
|
|
a_bChangedSlots[i + a_Offset] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (a_Mode > 0)
|
|
{
|
|
// If we got more left, find first empty slot
|
|
for (int i = 0; (i < a_Size) && (a_Item.m_ItemCount > 0); i++)
|
|
{
|
|
if (m_Slots[i + a_Offset].m_ItemType == -1)
|
|
{
|
|
m_Slots[i + a_Offset] = a_Item;
|
|
a_Item.m_ItemCount = 0;
|
|
a_bChangedSlots[i + a_Offset] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
void cInventory::UpdateItems(void)
|
|
{
|
|
const cItem & Slot = GetEquippedItem();
|
|
if (!Slot.IsEmpty())
|
|
{
|
|
ItemHandler(Slot.m_ItemType)->OnUpdate(m_Owner.GetWorld(), &m_Owner, Slot);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cInventory::SaveToJson(Json::Value & a_Value)
|
|
{
|
|
// 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;
|
|
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:
|
|
for (int i = 0; i < invHotbarCount; i++)
|
|
{
|
|
Json::Value JSON_Item;
|
|
m_HotbarSlots.GetSlot(i).GetJson(JSON_Item);
|
|
a_Value.append(JSON_Item);
|
|
}
|
|
|
|
// Shield slot is the last
|
|
Json::Value JSON_Item;
|
|
m_ShieldSlots.GetSlot(0).GetJson(JSON_Item);
|
|
a_Value.append(JSON_Item);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool cInventory::LoadFromJson(Json::Value & a_Value)
|
|
{
|
|
int SlotIdx = 0;
|
|
|
|
for (Json::Value::iterator itr = a_Value.begin(); itr != a_Value.end(); ++itr, SlotIdx++)
|
|
{
|
|
cItem Item;
|
|
Item.FromJson(*itr);
|
|
|
|
// 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;
|
|
}
|
|
|
|
int GridSlotNum = 0;
|
|
cItemGrid * Grid = GetGridForSlotNum(SlotIdx - 5, GridSlotNum);
|
|
ASSERT(Grid != nullptr);
|
|
Grid->SetSlot(GridSlotNum, Item);
|
|
} // for itr - a_Value[]
|
|
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_SlotNum -= invInventoryCount;
|
|
if (a_SlotNum < invHotbarCount)
|
|
{
|
|
a_GridSlotNum = a_SlotNum;
|
|
return &m_HotbarSlots;
|
|
}
|
|
a_GridSlotNum = a_SlotNum - invHotbarCount;
|
|
return &m_ShieldSlots;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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_SlotNum -= invInventoryCount;
|
|
if (a_SlotNum < invHotbarCount)
|
|
{
|
|
a_GridSlotNum = a_SlotNum;
|
|
return &m_HotbarSlots;
|
|
}
|
|
a_GridSlotNum = a_SlotNum - invHotbarCount;
|
|
return &m_ShieldSlots;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cInventory::OnSlotChanged(cItemGrid * a_ItemGrid, int a_SlotNum)
|
|
{
|
|
// Send the neccessary updates to whoever needs them
|
|
|
|
if (!m_Owner.IsTicking())
|
|
{
|
|
// 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 != nullptr))
|
|
{
|
|
World->BroadcastEntityEquipment(
|
|
m_Owner, static_cast<short>(ArmorSlotNumToEntityEquipmentID(static_cast<short>(a_SlotNum))),
|
|
m_ArmorSlots.GetSlot(a_SlotNum), m_Owner.GetClientHandle()
|
|
);
|
|
}
|
|
|
|
// Broadcast the Equipped Item, if the Slot is changed.
|
|
if ((a_ItemGrid == &m_HotbarSlots) && (m_EquippedSlotNum == a_SlotNum))
|
|
{
|
|
m_Owner.GetWorld()->BroadcastEntityEquipment(m_Owner, 0, GetEquippedItem(), 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 if (a_ItemGrid == &m_ShieldSlots)
|
|
{
|
|
Base = invShieldOffset;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(!"Unknown ItemGrid calling OnSlotChanged()");
|
|
return;
|
|
}
|
|
|
|
SendSlot(Base + a_SlotNum);
|
|
}
|