// ItemGrid.cpp
// Implements the cItemGrid class representing a storage for items in a XY grid (chests, dispensers, inventory etc.)
#include "Globals.h"
#include "ItemGrid.h"
#include "Items/ItemHandler.h"
#include "Noise.h"
cItemGrid::cItemGrid(int a_Width, int a_Height) :
m_Width(a_Width),
m_Height(a_Height),
m_NumSlots(a_Width * a_Height),
m_Slots(new cItem[a_Width * a_Height]),
m_IsInTriggerListeners(false)
{
}
cItemGrid::~cItemGrid()
{
delete[] m_Slots;
m_Slots = NULL;
}
int cItemGrid::GetSlotNum(int a_X, int a_Y) const
{
if (
(a_X < 0) || (a_X >= m_Width) ||
(a_Y < 0) || (a_Y >= m_Height)
)
{
LOGWARNING("%s: coords out of range: (%d, %d) in grid of size (%d, %d)",
__FUNCTION__, a_X, a_Y, m_Width, m_Height
);
return -1;
}
return a_X + m_Width * a_Y;
}
void cItemGrid::GetSlotCoords(int a_SlotNum, int & a_X, int & a_Y) const
{
if ((a_SlotNum < 0) || (a_SlotNum >= m_NumSlots))
{
LOGWARNING("%s: SlotNum out of range: %d in grid of range %d",
__FUNCTION__, a_SlotNum, m_NumSlots
);
a_X = -1;
a_Y = -1;
return;
}
a_X = a_SlotNum % m_Width;
a_Y = a_SlotNum / m_Width;
}
const cItem & cItemGrid::GetSlot(int a_X, int a_Y) const
{
return GetSlot(GetSlotNum(a_X, a_Y));
}
const cItem & cItemGrid::GetSlot(int a_SlotNum) const
{
if ((a_SlotNum < 0) || (a_SlotNum >= m_NumSlots))
{
LOGWARNING("%s: Invalid slot number, %d out of %d slots",
__FUNCTION__, a_SlotNum, m_NumSlots
);
return m_Slots[0];
}
return m_Slots[a_SlotNum];
}
void cItemGrid::SetSlot(int a_X, int a_Y, const cItem & a_Item)
{
SetSlot(GetSlotNum(a_X, a_Y), a_Item);
}
void cItemGrid::SetSlot(int a_X, int a_Y, short a_ItemType, char a_ItemCount, short a_ItemDamage)
{
SetSlot(GetSlotNum(a_X, a_Y), cItem(a_ItemType, a_ItemCount, a_ItemDamage));
}
void cItemGrid::SetSlot(int a_SlotNum, const cItem & a_Item)
{
if ((a_SlotNum < 0) || (a_SlotNum >= m_NumSlots))
{
LOGWARNING("%s: Invalid slot number %d out of %d slots",
__FUNCTION__, a_SlotNum, m_NumSlots
);
return;
}
m_Slots[a_SlotNum] = a_Item;
TriggerListeners(a_SlotNum);
}
void cItemGrid::SetSlot(int a_SlotNum, short a_ItemType, char a_ItemCount, short a_ItemDamage)
{
SetSlot(a_SlotNum, cItem(a_ItemType, a_ItemCount, a_ItemDamage));
}
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);
}
bool cItemGrid::IsSlotEmpty(int a_SlotNum) const
{
if ((a_SlotNum < 0) || (a_SlotNum >= m_NumSlots))
{
LOGWARNING("%s: Invalid slot number %d out of %d slots",
__FUNCTION__, a_SlotNum, m_NumSlots
);
return true;
}
return m_Slots[a_SlotNum].IsEmpty();
}
bool cItemGrid::IsSlotEmpty(int a_X, int a_Y) const
{
return IsSlotEmpty(GetSlotNum(a_X, a_Y));
}
void cItemGrid::Clear(void)
{
for (int i = 0; i < m_NumSlots; i++)
{
m_Slots[i].Empty();
TriggerListeners(i);
}
}
int cItemGrid::HowManyCanFit(const cItem & a_ItemStack, bool a_AllowNewStacks)
{
char NumLeft = a_ItemStack.m_ItemCount;
int MaxStack = ItemHandler(a_ItemStack.m_ItemType)->GetMaxStackSize();
for (int i = m_NumSlots - 1; i >= 0; i--)
{
if (m_Slots[i].IsEmpty())
{
if (a_AllowNewStacks)
{
NumLeft -= MaxStack;
}
}
else if (m_Slots[i].IsEqual(a_ItemStack))
{
NumLeft -= MaxStack - m_Slots[i].m_ItemCount;
}
if (NumLeft <= 0)
{
// All items fit
return a_ItemStack.m_ItemCount;
}
} // for i - m_Slots[]
return a_ItemStack.m_ItemCount - NumLeft;
}
int cItemGrid::AddItemToSlot(const cItem & a_ItemStack, int a_Slot, int a_Num, int a_MaxStack)
{
int PrevCount = 0;
if (m_Slots[a_Slot].IsEmpty())
{
m_Slots[a_Slot] = a_ItemStack;
PrevCount = 0;
}
else
{
PrevCount = m_Slots[a_Slot].m_ItemCount;
}
m_Slots[a_Slot].m_ItemCount = std::min(a_MaxStack, PrevCount + a_Num);
int toReturn = m_Slots[a_Slot].m_ItemCount - PrevCount;
TriggerListeners(a_Slot);
return toReturn;
}
int cItemGrid::AddItem(cItem & a_ItemStack, bool a_AllowNewStacks, int a_PrioritarySlot)
{
int NumLeft = a_ItemStack.m_ItemCount;
int MaxStack = a_ItemStack.GetMaxStackSize();
// Try prioritarySlot first:
if (
(a_PrioritarySlot != -1) &&
(
m_Slots[a_PrioritarySlot].IsEmpty() ||
m_Slots[a_PrioritarySlot].IsEqual(a_ItemStack)
)
)
{
NumLeft -= AddItemToSlot(a_ItemStack, a_PrioritarySlot, NumLeft, MaxStack);
}
// Scan existing stacks:
for (int i = 0; i < m_NumSlots; i++)
{
if (m_Slots[i].IsEqual(a_ItemStack))
{
NumLeft -= AddItemToSlot(a_ItemStack, i, NumLeft, MaxStack);
}
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 = 0; i < m_NumSlots; i++)
{
if (m_Slots[i].IsEmpty())
{
NumLeft -= AddItemToSlot(a_ItemStack, i, NumLeft, MaxStack);
}
if (NumLeft <= 0)
{
// All items fit
return a_ItemStack.m_ItemCount;
}
} // for i - m_Slots[]
return (a_ItemStack.m_ItemCount - NumLeft);
}
int cItemGrid::AddItems(cItems & a_ItemStackList, bool a_AllowNewStacks, int a_PrioritarySlot)
{
int TotalAdded = 0;
for (cItems::iterator itr = a_ItemStackList.begin(); itr != a_ItemStackList.end();)
{
int NumAdded = AddItem(*itr, a_AllowNewStacks, a_PrioritarySlot);
if (itr->m_ItemCount == NumAdded)
{
itr = a_ItemStackList.erase(itr);
}
else
{
itr->m_ItemCount -= NumAdded;
++itr;
}
TotalAdded += NumAdded;
}
return TotalAdded;
}
int cItemGrid::RemoveItem(const cItem & a_ItemStack)
{
int NumLeft = a_ItemStack.m_ItemCount;
for (int i = 0; i < m_NumSlots; i++)
{
if (NumLeft <= 0)
{
break;
}
if (m_Slots[i].IsEqual(a_ItemStack))
{
int NumToRemove = std::min(NumLeft, (int)m_Slots[i].m_ItemCount);
NumLeft -= NumToRemove;
m_Slots[i].m_ItemCount -= NumToRemove;
if (m_Slots[i].m_ItemCount <= 0)
{
m_Slots[i].Empty();
}
TriggerListeners(i);
}
}
return (a_ItemStack.m_ItemCount - NumLeft);
}
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 -1",
__FUNCTION__, a_SlotNum, m_NumSlots
);
return -1;
}
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;
cItemHandler * Handler = cItemHandler::GetItemHandler(m_Slots[a_SlotNum].m_ItemType);
if (m_Slots[a_SlotNum].m_ItemCount > Handler->GetMaxStackSize())
{
m_Slots[a_SlotNum].m_ItemCount = Handler->GetMaxStackSize();
}
TriggerListeners(a_SlotNum);
return m_Slots[a_SlotNum].m_ItemCount;
}
int cItemGrid::ChangeSlotCount(int a_X, int a_Y, int a_AddToCount)
{
return ChangeSlotCount(GetSlotNum(a_X, a_Y), a_AddToCount);
}
cItem cItemGrid::RemoveOneItem(int a_SlotNum)
{
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 cItem();
}
// If the slot is empty, return an empty item
if (m_Slots[a_SlotNum].IsEmpty())
{
return cItem();
}
// Make a copy of the item in slot, set count to 1 and remove one from the slot
cItem res = m_Slots[a_SlotNum];
res.m_ItemCount = 1;
m_Slots[a_SlotNum].m_ItemCount -= 1;
// Emptying the slot correctly if appropriate
if (m_Slots[a_SlotNum].m_ItemCount == 0)
{
m_Slots[a_SlotNum].Empty();
}
// Notify everyone of the change
TriggerListeners(a_SlotNum);
// Return the stored one item
return res;
}
cItem cItemGrid::RemoveOneItem(int a_X, int a_Y)
{
return RemoveOneItem(GetSlotNum(a_X, a_Y));
}
int cItemGrid::HowManyItems(const cItem & a_Item)
{
int res = 0;
for (int i = 0; i < m_NumSlots; i++)
{
if (m_Slots[i].IsEqual(a_Item))
{
res += m_Slots[i].m_ItemCount;
}
}
return res;
}
bool cItemGrid::HasItems(const cItem & a_ItemStack)
{
int CurrentlyHave = HowManyItems(a_ItemStack);
return (CurrentlyHave >= a_ItemStack.m_ItemCount);
}
int cItemGrid::GetFirstEmptySlot(void) const
{
return GetNextEmptySlot(-1);
}
int cItemGrid::GetFirstUsedSlot(void) const
{
return GetNextUsedSlot(-1);
}
int cItemGrid::GetLastEmptySlot(void) const
{
for (int i = m_NumSlots - 1; i >= 0; i--)
{
if (m_Slots[i].IsEmpty())
{
return i;
}
}
return -1;
}
int cItemGrid::GetLastUsedSlot(void) const
{
for (int i = m_NumSlots - 1; i >= 0; i--)
{
if (!m_Slots[i].IsEmpty())
{
return i;
}
}
return -1;
}
int cItemGrid::GetNextEmptySlot(int a_StartFrom) const
{
for (int i = a_StartFrom + 1; i < m_NumSlots; i++)
{
if (m_Slots[i].IsEmpty())
{
return i;
}
}
return -1;
}
int cItemGrid::GetNextUsedSlot(int a_StartFrom) const
{
for (int i = a_StartFrom + 1; i < m_NumSlots; i++)
{
if (!m_Slots[i].IsEmpty())
{
return i;
}
}
return -1;
}
void cItemGrid::CopyToItems(cItems & a_Items) const
{
for (int i = 0; i < m_NumSlots; i++)
{
if (!m_Slots[i].IsEmpty())
{
a_Items.push_back(m_Slots[i]);
}
} // for i - m_Slots[]
}
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);
}
bool cItemGrid::DamageItem(int a_X, int a_Y, short a_Amount)
{
return DamageItem(GetSlotNum(a_X, a_Y), a_Amount);
}
void cItemGrid::GenerateRandomLootWithBooks(const cLootProbab * a_LootProbabs, size_t a_CountLootProbabs, int a_NumSlots, int a_Seed)
{
// Calculate the total weight:
int TotalProbab = 1;
for (size_t i = 0; i < a_CountLootProbabs; i++)
{
TotalProbab += a_LootProbabs[i].m_Weight;
}
// Pick the loot items:
cNoise Noise(a_Seed);
for (int i = 0; i < a_NumSlots; i++)
{
int Rnd = (Noise.IntNoise1DInt(i) / 7);
int LootRnd = Rnd % TotalProbab;
Rnd >>= 8;
cItem CurrentLoot = cItem(E_ITEM_ENCHANTED_BOOK, 1, 0);
// Choose the enchantments
cWeightedEnchantments Enchantments;
cEnchantments::AddItemEnchantmentWeights(Enchantments, E_ITEM_BOOK, 24 + Noise.IntNoise2DInt(a_Seed, TotalProbab) % 7);
int NumEnchantments = Noise.IntNoise3DInt(TotalProbab, Rnd, a_Seed) % 5; // The number of enchantments this book wil get.
for (int j = 0; j <= NumEnchantments; j++)
{
cEnchantments Enchantment = cEnchantments::GenerateEnchantmentFromVector(Enchantments, Noise.IntNoise2DInt(NumEnchantments, i));
CurrentLoot.m_Enchantments.AddFromString(Enchantment.ToString());
cEnchantments::RemoveEnchantmentWeightFromVector(Enchantments, Enchantment);
}
for (size_t j = 0; j < a_CountLootProbabs; j++)
{
LootRnd -= a_LootProbabs[i].m_Weight;
if (LootRnd < 0)
{
CurrentLoot = a_LootProbabs[i].m_Item;
CurrentLoot.m_ItemCount = a_LootProbabs[i].m_MinAmount + (Rnd % (a_LootProbabs[i].m_MaxAmount - a_LootProbabs[i].m_MinAmount));
Rnd >>= 8;
break;
}
} // for j - a_LootProbabs[]
SetSlot(Rnd % m_NumSlots, CurrentLoot);
} // for i - NumSlots
}
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)
{
cListeners Listeners;
{
cCSLock Lock(m_CSListeners);
m_IsInTriggerListeners = true;
Listeners = m_Listeners;
}
for (cListeners::iterator itr = Listeners.begin(), end = Listeners.end(); itr != end; ++itr)
{
(*itr)->OnSlotChanged(this, a_SlotNum);
} // for itr - m_Listeners[]
m_IsInTriggerListeners = false;
}