/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "inventory_profile.h"
#include "common/data_verification.h"
#include "common/eqemu_logsys.h"
#include "common/light_source.h"
#include "common/strings.h"
#include "common/textures.h"
#include
std::list dirty_inst;
//
// class ItemInstQueue
//
ItemInstQueue::~ItemInstQueue()
{
for (auto iter = m_list.begin(); iter != m_list.end(); ++iter) {
safe_delete(*iter);
}
m_list.clear();
}
// Put item onto back of queue
void ItemInstQueue::push(EQ::ItemInstance* inst)
{
m_list.push_back(inst);
}
// Put item onto front of queue
void ItemInstQueue::push_front(EQ::ItemInstance* inst)
{
m_list.push_front(inst);
}
// Remove item from front of queue
EQ::ItemInstance* ItemInstQueue::pop()
{
if (m_list.empty()) {
return nullptr;
}
EQ::ItemInstance* inst = m_list.front();
m_list.pop_front();
return inst;
}
// Remove item from back of queue
EQ::ItemInstance* ItemInstQueue::pop_back()
{
if (m_list.empty()) {
return nullptr;
}
EQ::ItemInstance* inst = m_list.back();
m_list.pop_back();
return inst;
}
// Look at item at front of queue
EQ::ItemInstance* ItemInstQueue::peek_front() const
{
return (m_list.empty()) ? nullptr : m_list.front();
}
//
// class EQ::InventoryProfile
//
EQ::InventoryProfile::~InventoryProfile()
{
for (auto iter = m_worn.begin(); iter != m_worn.end(); ++iter) {
safe_delete(iter->second);
}
m_worn.clear();
for (auto iter = m_inv.begin(); iter != m_inv.end(); ++iter) {
safe_delete(iter->second);
}
m_inv.clear();
for (auto iter = m_bank.begin(); iter != m_bank.end(); ++iter) {
safe_delete(iter->second);
}
m_bank.clear();
for (auto iter = m_shbank.begin(); iter != m_shbank.end(); ++iter) {
safe_delete(iter->second);
}
m_shbank.clear();
for (auto iter = m_trade.begin(); iter != m_trade.end(); ++iter) {
safe_delete(iter->second);
}
m_trade.clear();
}
void EQ::InventoryProfile::SetInventoryVersion(versions::MobVersion inventory_version) {
m_mob_version = versions::ValidateMobVersion(inventory_version);
SetGMInventory(m_gm_inventory);
}
void EQ::InventoryProfile::SetGMInventory(bool gmi_flag) {
m_gm_inventory = gmi_flag;
m_lookup = inventory::DynamicLookup(m_mob_version, gmi_flag);
}
void EQ::InventoryProfile::CleanDirty() {
auto iter = dirty_inst.begin();
while (iter != dirty_inst.end()) {
delete (*iter);
++iter;
}
dirty_inst.clear();
}
void EQ::InventoryProfile::MarkDirty(ItemInstance *inst) {
if (inst) {
dirty_inst.push_back(inst);
}
}
// Retrieve item at specified slot; returns false if item not found
EQ::ItemInstance* EQ::InventoryProfile::GetItem(int16 slot_id) const
{
ItemInstance* result = nullptr;
// Cursor
if (slot_id == invslot::slotCursor) {
// Cursor slot
result = m_cursor.peek_front();
}
// Non bag slots
if (EQ::ValueWithin(slot_id, invslot::TRADE_BEGIN, invslot::TRADE_END)) {
result = _GetItem(m_trade, slot_id);
} else if (EQ::ValueWithin(slot_id, invslot::SHARED_BANK_BEGIN, invslot::SHARED_BANK_END)) {
result = _GetItem(m_shbank, slot_id);
} else if (EQ::ValueWithin(slot_id, invslot::BANK_BEGIN, invslot::BANK_END)) {
result = _GetItem(m_bank, slot_id);
} else if (EQ::ValueWithin(slot_id, invslot::GENERAL_BEGIN, invslot::GENERAL_END)) {
result = _GetItem(m_inv, slot_id);
} else if (
EQ::ValueWithin(slot_id, invslot::EQUIPMENT_BEGIN, invslot::EQUIPMENT_END) ||
EQ::ValueWithin(slot_id, invslot::TRIBUTE_BEGIN, invslot::TRIBUTE_END) ||
EQ::ValueWithin(slot_id, invslot::GUILD_TRIBUTE_BEGIN, invslot::GUILD_TRIBUTE_END)
) {
result = _GetItem(m_worn, slot_id);
}
// Inner bag slots
else if (EQ::ValueWithin(slot_id, invbag::TRADE_BAGS_BEGIN, invbag::TRADE_BAGS_END)) {
ItemInstance* inst = _GetItem(m_trade, InventoryProfile::CalcSlotId(slot_id));
if (inst && inst->IsClassBag()) {
result = inst->GetItem(InventoryProfile::CalcBagIdx(slot_id));
}
} else if (EQ::ValueWithin(slot_id, invbag::SHARED_BANK_BAGS_BEGIN, invbag::SHARED_BANK_BAGS_END)) {
ItemInstance* inst = _GetItem(m_shbank, InventoryProfile::CalcSlotId(slot_id));
if (inst && inst->IsClassBag()) {
result = inst->GetItem(InventoryProfile::CalcBagIdx(slot_id));
}
} else if (EQ::ValueWithin(slot_id, invbag::BANK_BAGS_BEGIN, invbag::BANK_BAGS_END)) {
ItemInstance* inst = _GetItem(m_bank, InventoryProfile::CalcSlotId(slot_id));
if (inst && inst->IsClassBag()) {
result = inst->GetItem(InventoryProfile::CalcBagIdx(slot_id));
}
} else if (EQ::ValueWithin(slot_id, invbag::CURSOR_BAG_BEGIN, invbag::CURSOR_BAG_END)) {
ItemInstance* inst = m_cursor.peek_front();
if (inst && inst->IsClassBag()) {
result = inst->GetItem(InventoryProfile::CalcBagIdx(slot_id));
}
} else if (EQ::ValueWithin(slot_id, invbag::GENERAL_BAGS_BEGIN, invbag::GENERAL_BAGS_END)) {
ItemInstance* inst = _GetItem(m_inv, InventoryProfile::CalcSlotId(slot_id));
if (inst && inst->IsClassBag()) {
result = inst->GetItem(InventoryProfile::CalcBagIdx(slot_id));
}
}
return result;
}
// Retrieve item at specified position within bag
EQ::ItemInstance* EQ::InventoryProfile::GetItem(int16 slot_id, uint8 bagidx) const
{
return GetItem(InventoryProfile::CalcSlotId(slot_id, bagidx));
}
// Put an item into specified slot
int16 EQ::InventoryProfile::PutItem(int16 slot_id, const ItemInstance& inst)
{
if (EQ::ValueWithin(slot_id, EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END)) {
if ((((uint64) 1 << slot_id) & m_lookup->PossessionsBitmask) == 0) {
return EQ::invslot::SLOT_INVALID;
}
} else if (EQ::ValueWithin(slot_id, EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END)) {
auto temp_slot = EQ::invslot::GENERAL_BEGIN + ((slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT);
if ((((uint64) 1 << temp_slot) & m_lookup->PossessionsBitmask) == 0) {
return EQ::invslot::SLOT_INVALID;
}
} else if (EQ::ValueWithin(slot_id, EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END)) {
if ((slot_id - EQ::invslot::BANK_BEGIN) >= m_lookup->InventoryTypeSize.Bank) {
return EQ::invslot::SLOT_INVALID;
}
} else if (EQ::ValueWithin(slot_id, EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END)) {
auto temp_slot = (slot_id - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT;
if (temp_slot >= m_lookup->InventoryTypeSize.Bank) {
return EQ::invslot::SLOT_INVALID;
}
}
// Clean up item already in slot (if exists)
DeleteItem(slot_id);
if (!inst) {
// User is effectively deleting the item
// in the slot, why hold a null ptr in map<>?
return slot_id;
}
// Delegate to internal method
return _PutItem(slot_id, inst.Clone());
}
int16 EQ::InventoryProfile::PushCursor(const ItemInstance &inst) {
m_cursor.push(inst.Clone());
return invslot::slotCursor;
}
EQ::ItemInstance* EQ::InventoryProfile::GetCursorItem() {
return m_cursor.peek_front();
}
// Swap items in inventory
bool EQ::InventoryProfile::SwapItem(
int16 source_slot,
int16 destination_slot,
SwapItemFailState &fail_state,
uint16 race_id,
uint8 class_id,
uint32 deity_id,
uint8 level
) {
fail_state = swapInvalid;
if (EQ::ValueWithin(source_slot, EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END)) {
if ((((uint64)1 << source_slot) & m_lookup->PossessionsBitmask) == 0) {
fail_state = swapNotAllowed;
return false;
}
} else if (EQ::ValueWithin(source_slot, EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END)) {
auto temp_slot = EQ::invslot::GENERAL_BEGIN + ((source_slot - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT);
if ((((uint64)1 << temp_slot) & m_lookup->PossessionsBitmask) == 0) {
fail_state = swapNotAllowed;
return false;
}
} else if (EQ::ValueWithin(source_slot, EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END)) {
if ((source_slot - EQ::invslot::BANK_BEGIN) >= m_lookup->InventoryTypeSize.Bank) {
fail_state = swapNotAllowed;
return false;
}
} else if (EQ::ValueWithin(source_slot, EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END)) {
auto temp_slot = (source_slot - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT;
if (temp_slot >= m_lookup->InventoryTypeSize.Bank) {
fail_state = swapNotAllowed;
return false;
}
}
if (EQ::ValueWithin(destination_slot, EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END)) {
if ((((uint64)1 << destination_slot) & m_lookup->PossessionsBitmask) == 0) {
fail_state = swapNotAllowed;
return false;
}
} else if (EQ::ValueWithin(destination_slot, EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END)) {
auto temp_slot = EQ::invslot::GENERAL_BEGIN + ((destination_slot - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT);
if ((((uint64)1 << temp_slot) & m_lookup->PossessionsBitmask) == 0) {
fail_state = swapNotAllowed;
return false;
}
} else if (EQ::ValueWithin(destination_slot, EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END)) {
if ((destination_slot - EQ::invslot::BANK_BEGIN) >= m_lookup->InventoryTypeSize.Bank) {
fail_state = swapNotAllowed;
return false;
}
} else if (EQ::ValueWithin(destination_slot, EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END)) {
auto temp_slot = (destination_slot - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT;
if (temp_slot >= m_lookup->InventoryTypeSize.Bank) {
fail_state = swapNotAllowed;
return false;
}
}
// Temp holding areas for source and destination
ItemInstance *source_item_instance = GetItem(source_slot);
ItemInstance *destination_item_instance = GetItem(destination_slot);
if (source_item_instance) {
if (!source_item_instance->IsSlotAllowed(destination_slot)) {
fail_state = swapNotAllowed;
return false;
}
source_item_instance->SetEvolveEquipped(false);
if (EQ::ValueWithin(destination_slot, invslot::EQUIPMENT_BEGIN, invslot::EQUIPMENT_END)) {
auto source_item = source_item_instance->GetItem();
if (!source_item) {
fail_state = swapNullData;
return false;
}
if (race_id && class_id && !source_item->IsEquipable(race_id, class_id)) {
fail_state = swapRaceClass;
return false;
}
if (deity_id && source_item->Deity && !(Deity::GetBitmask(deity_id) & source_item->Deity)) {
fail_state = swapDeity;
return false;
}
if (level && source_item->ReqLevel && level < source_item->ReqLevel) {
fail_state = swapLevel;
return false;
}
if (source_item_instance->IsEvolving() > 0) {
source_item_instance->SetEvolveEquipped(true);
}
}
}
if (destination_item_instance) {
if (!destination_item_instance->IsSlotAllowed(source_slot)) {
fail_state = swapNotAllowed;
return false;
}
destination_item_instance->SetEvolveEquipped(false);
if (EQ::ValueWithin(source_slot, invslot::EQUIPMENT_BEGIN, invslot::EQUIPMENT_END)) {
auto destination_item = destination_item_instance->GetItem();
if (!destination_item) {
fail_state = swapNullData;
return false;
}
if (race_id && class_id && !destination_item->IsEquipable(race_id, class_id)) {
fail_state = swapRaceClass;
return false;
}
if (deity_id && destination_item->Deity && !(Deity::GetBitmask(deity_id) & destination_item->Deity)) {
fail_state = swapDeity;
return false;
}
if (level && destination_item->ReqLevel && level < destination_item->ReqLevel) {
fail_state = swapLevel;
return false;
}
if (destination_item_instance->IsEvolving()) {
destination_item_instance->SetEvolveEquipped(true);
}
}
}
_PutItem(source_slot, destination_item_instance); // Assign destination -> source
_PutItem(destination_slot, source_item_instance); // Assign source -> destination
fail_state = swapPass;
return true;
}
// Remove item from inventory (with memory delete)
bool EQ::InventoryProfile::DeleteItem(int16 slot_id, int16 quantity) {
// Pop item out of inventory map (or queue)
ItemInstance *item_to_delete = PopItem(slot_id);
// Determine if object should be fully deleted, or
// just a quantity of charges of the item can be deleted
if (item_to_delete && (quantity > 0)) {
item_to_delete->SetCharges(item_to_delete->GetCharges() - quantity);
// If there are no charges left on the item,
if (item_to_delete->GetCharges() <= 0) {
// If the item is stackable (e.g arrows), or
// the item is not a charged item, or is expendable, delete it
if (
item_to_delete->IsStackable() ||
item_to_delete->GetItem()->MaxCharges == 0 ||
item_to_delete->IsExpendable()
) {
// Item can now be destroyed
InventoryProfile::MarkDirty(item_to_delete);
return true;
}
}
// Charges still exist, or it is a charged item that is not expendable. Put back into inventory
_PutItem(slot_id, item_to_delete);
return false;
}
InventoryProfile::MarkDirty(item_to_delete);
return true;
}
// Checks All items in a bag for No Drop
bool EQ::InventoryProfile::CheckNoDrop(int16 slot_id, bool recurse)
{
ItemInstance* inst = GetItem(slot_id);
return inst ? !inst->IsDroppable(recurse) : false;
}
// Remove item from bucket without memory delete
// Returns item pointer if full delete was successful
EQ::ItemInstance* EQ::InventoryProfile::PopItem(int16 slot_id)
{
ItemInstance* p = nullptr;
if (slot_id == invslot::slotCursor) {
p = m_cursor.pop();
} else if (EQ::ValueWithin(slot_id, invslot::EQUIPMENT_BEGIN, invslot::EQUIPMENT_END)) {
p = m_worn[slot_id];
m_worn.erase(slot_id);
} else if (EQ::ValueWithin(slot_id, invslot::GENERAL_BEGIN, invslot::GENERAL_END)) {
p = m_inv[slot_id];
m_inv.erase(slot_id);
} else if (EQ::ValueWithin(slot_id, invslot::TRIBUTE_BEGIN, invslot::TRIBUTE_END)) {
p = m_worn[slot_id];
m_worn.erase(slot_id);
} else if (EQ::ValueWithin(slot_id, invslot::GUILD_TRIBUTE_BEGIN, invslot::GUILD_TRIBUTE_END)) {
p = m_worn[slot_id];
m_worn.erase(slot_id);
} else if (EQ::ValueWithin(slot_id, invslot::BANK_BEGIN, invslot::BANK_END)) {
p = m_bank[slot_id];
m_bank.erase(slot_id);
} else if (EQ::ValueWithin(slot_id, invslot::SHARED_BANK_BEGIN, invslot::SHARED_BANK_END)) {
p = m_shbank[slot_id];
m_shbank.erase(slot_id);
} else if (EQ::ValueWithin(slot_id, invslot::TRADE_BEGIN, invslot::TRADE_END)) {
p = m_trade[slot_id];
m_trade.erase(slot_id);
} else {
// Is slot inside bag?
ItemInstance* bag_inst = GetItem(InventoryProfile::CalcSlotId(slot_id));
if (bag_inst && bag_inst->IsClassBag()) {
p = bag_inst->PopItem(InventoryProfile::CalcBagIdx(slot_id));
}
}
// Return pointer that needs to be deleted (or otherwise managed)
return p;
}
bool EQ::InventoryProfile::HasSpaceForItem(const ItemData* ItemToTry, int16 Quantity)
{
if (ItemToTry->Stackable) {
for (int16 i = invslot::GENERAL_BEGIN; i <= invslot::GENERAL_END; i++) {
if ((((uint64) 1 << i) & m_lookup->PossessionsBitmask) == 0) {
continue;
}
ItemInstance* inv_item = GetItem(i);
if (
inv_item &&
inv_item->GetItem()->ID == ItemToTry->ID &&
inv_item->GetCharges() < inv_item->GetItem()->StackSize
) {
int charges_left = inv_item->GetItem()->StackSize - inv_item->GetCharges();
if (Quantity <= charges_left) {
return true;
}
Quantity -= charges_left;
}
if (inv_item && inv_item->IsClassBag()) {
int16 base_slot_id = InventoryProfile::CalcSlotId(i, invbag::SLOT_BEGIN);
uint8 bag_slots = inv_item->GetItem()->BagSlots;
for (uint8 bag_slot = invbag::SLOT_BEGIN; bag_slot < bag_slots; bag_slot++) {
inv_item = GetItem(base_slot_id + bag_slot);
if (
inv_item &&
inv_item->GetItem()->ID == ItemToTry->ID &&
inv_item->GetCharges() < inv_item->GetItem()->StackSize
) {
int charges_left = inv_item->GetItem()->StackSize - inv_item->GetCharges();
if (Quantity <= charges_left) {
return true;
}
Quantity -= charges_left;
}
}
}
}
}
for (int16 i = invslot::GENERAL_BEGIN; i <= invslot::GENERAL_END; i++) {
if ((((uint64) 1 << i) & m_lookup->PossessionsBitmask) == 0) {
continue;
}
ItemInstance* inv_item = GetItem(i);
if (!inv_item) {
if (!ItemToTry->Stackable) {
if (Quantity == 1) {
return true;
} else {
Quantity--;
}
} else {
if (Quantity <= ItemToTry->StackSize) {
return true;
} else {
Quantity -= ItemToTry->StackSize;
}
}
} else if (inv_item->IsClassBag() && CanItemFitInContainer(ItemToTry, inv_item->GetItem())) {
int16 base_slot_id = InventoryProfile::CalcSlotId(i, invbag::SLOT_BEGIN);
uint8 bag_slots = inv_item->GetItem()->BagSlots;
for (uint8 bag_slot = invbag::SLOT_BEGIN; bag_slot < bag_slots; bag_slot++) {
inv_item = GetItem(base_slot_id + bag_slot);
if (!inv_item) {
if (!ItemToTry->Stackable) {
if (Quantity == 1) {
return true;
} else {
Quantity--;
}
} else {
if (Quantity <= ItemToTry->StackSize) {
return true;
} else {
Quantity -= ItemToTry->StackSize;
}
}
}
}
}
}
return false;
}
// Checks that user has at least 'quantity' number of items in a given inventory slot
// Returns first slot it was found in, or SLOT_INVALID if not found
bool EQ::InventoryProfile::HasAugmentEquippedByID(uint32 item_id)
{
bool has_equipped = false;
ItemInstance* item = nullptr;
for (int slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) {
item = GetItem(slot_id);
if (item && item->ContainsAugmentByID(item_id)) {
has_equipped = true;
break;
}
}
return has_equipped;
}
uint32 EQ::InventoryProfile::CountAugmentEquippedByID(uint32 item_id)
{
uint32 quantity = 0;
ItemInstance* item = nullptr;
for (int slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) {
item = GetItem(slot_id);
if (item && item->ContainsAugmentByID(item_id)) {
quantity += item->CountAugmentByID(item_id);
}
}
return quantity;
}
bool EQ::InventoryProfile::HasItemEquippedByID(uint32 item_id)
{
bool has_equipped = false;
ItemInstance* item = nullptr;
for (int slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) {
item = GetItem(slot_id);
if (item && item->GetID() == item_id) {
has_equipped = true;
break;
}
}
return has_equipped;
}
uint32 EQ::InventoryProfile::CountItemEquippedByID(uint32 item_id)
{
uint32 quantity = 0;
ItemInstance* item = nullptr;
for (int slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; ++slot_id) {
item = GetItem(slot_id);
if (item && item->GetID() == item_id) {
quantity += item->IsStackable() ? item->GetCharges() : 1;
}
}
return quantity;
}
//This function has a flaw in that it only returns the last stack that it looked at
//when quantity is greater than 1 and not all of quantity can be found in 1 stack.
int16 EQ::InventoryProfile::HasItem(uint32 item_id, uint8 quantity, uint8 where)
{
int16 slot_id = INVALID_INDEX;
//Altered by Father Nitwit to support a specification of
//where to search, with a default value to maintain compatibility
// Check each inventory bucket
if (where & invWhereWorn) {
slot_id = _HasItem(m_worn, item_id, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
if (where & invWherePersonal) {
slot_id = _HasItem(m_inv, item_id, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
if (where & invWhereBank) {
slot_id = _HasItem(m_bank, item_id, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
if (where & invWhereSharedBank) {
slot_id = _HasItem(m_shbank, item_id, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
if (where & invWhereTrading) {
slot_id = _HasItem(m_trade, item_id, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
// Behavioral change - Limbo is no longer checked due to improper handling of return value
if (where & invWhereCursor) {
// Check cursor queue
slot_id = _HasItem(m_cursor, item_id, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
return slot_id;
}
//this function has the same quantity flaw mentioned above in HasItem()
int16 EQ::InventoryProfile::HasItemByUse(uint8 use, uint8 quantity, uint8 where)
{
int16 slot_id = INVALID_INDEX;
// Check each inventory bucket
if (where & invWhereWorn) {
slot_id = _HasItemByUse(m_worn, use, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
if (where & invWherePersonal) {
slot_id = _HasItemByUse(m_inv, use, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
if (where & invWhereBank) {
slot_id = _HasItemByUse(m_bank, use, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
if (where & invWhereSharedBank) {
slot_id = _HasItemByUse(m_shbank, use, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
if (where & invWhereTrading) {
slot_id = _HasItemByUse(m_trade, use, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
// Behavioral change - Limbo is no longer checked due to improper handling of return value
if (where & invWhereCursor) {
// Check cursor queue
slot_id = _HasItemByUse(m_cursor, use, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
return slot_id;
}
int16 EQ::InventoryProfile::HasItemByLoreGroup(uint32 loregroup, uint8 where)
{
int16 slot_id = INVALID_INDEX;
// Check each inventory bucket
if (where & invWhereWorn) {
slot_id = _HasItemByLoreGroup(m_worn, loregroup);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
if (where & invWherePersonal) {
slot_id = _HasItemByLoreGroup(m_inv, loregroup);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
if (where & invWhereBank) {
slot_id = _HasItemByLoreGroup(m_bank, loregroup);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
if (where & invWhereSharedBank) {
slot_id = _HasItemByLoreGroup(m_shbank, loregroup);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
if (where & invWhereTrading) {
slot_id = _HasItemByLoreGroup(m_trade, loregroup);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
// Behavioral change - Limbo is no longer checked due to improper handling of return value
if (where & invWhereCursor) {
// Check cursor queue
slot_id = _HasItemByLoreGroup(m_cursor, loregroup);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
return slot_id;
}
// Locate an available inventory slot
// Returns slot_id when there's one available, else SLOT_INVALID
int16 EQ::InventoryProfile::FindFreeSlot(bool for_bag, bool try_cursor, uint8 min_size, bool is_arrow)
{
const int16 last_bag_slot = (RuleI(World, ExpansionSettings) == -1 || RuleI(World, ExpansionSettings) & EQ::expansions::bitHoT) ? EQ::invslot::slotGeneral10 : EQ::invslot::slotGeneral8;
for (int16 i = invslot::GENERAL_BEGIN; i <= last_bag_slot; i++) { // Check basic inventory
if ((((uint64) 1 << i) & m_lookup->PossessionsBitmask) == 0) {
continue;
}
if (!GetItem(i)) {
return i; // Found available slot in personal inventory
}
}
if (!for_bag) {
for (int16 i = invslot::GENERAL_BEGIN; i <= last_bag_slot; i++) {
if ((((uint64) 1 << i) & m_lookup->PossessionsBitmask) == 0) {
continue;
}
const EQ::ItemInstance* inst = GetItem(i);
if (inst && inst->IsClassBag() && inst->GetItem()->BagSize >= min_size) {
if (inst->GetItem()->BagType == item::BagTypeQuiver &&
inst->GetItem()->ItemType != item::ItemTypeArrow) {
continue;
}
const int16 base_slot_id = InventoryProfile::CalcSlotId(i, invbag::SLOT_BEGIN);
const uint8 slots = inst->GetItem()->BagSlots;
for (uint8 j = invbag::SLOT_BEGIN; j < slots; j++) {
if (!GetItem(base_slot_id + j)) {
// Found available slot within bag
return (base_slot_id + j);
}
}
}
}
}
if (try_cursor) {
// Always room on cursor (it's a queue)
// (we may wish to cap this in the future)
return invslot::slotCursor;
}
// No available slots
return INVALID_INDEX;
}
// This is a mix of HasSpaceForItem and FindFreeSlot..due to existing coding behavior, it was better to add a new helper function...
int16 EQ::InventoryProfile::FindFreeSlotForTradeItem(const ItemInstance* inst, int16 general_start, uint8 bag_start) {
// Do not arbitrarily use this function..it is designed for use with Client::ResetTrade() and Client::FinishTrade().
// If you have a need, use it..but, understand it is not a suitable replacement for InventoryProfile::FindFreeSlot().
//
// I'll probably implement a bitmask in the new inventory system to avoid having to adjust stack bias
if (
!EQ::ValueWithin(general_start, invslot::GENERAL_BEGIN, invslot::GENERAL_END) ||
bag_start > invbag::SLOT_END ||
!inst ||
!inst->GetID()
) {
return INVALID_INDEX;
}
// step 1: find room for bags (caller should really ask for slots for bags first to avoid sending them to cursor..and bag item loss)
if (inst->IsClassBag()) {
for (int16 free_slot = general_start; free_slot <= invslot::GENERAL_END; ++free_slot) {
if ((((uint64) 1 << free_slot) & m_lookup->PossessionsBitmask) == 0) {
continue;
}
if (!m_inv[free_slot]) {
return free_slot;
}
}
return invslot::slotCursor; // return cursor since bags do not stack and will not fit inside other bags..yet...)
}
// step 2: find partial room for stackables
if (inst->IsStackable()) {
for (int16 free_slot = general_start; free_slot <= invslot::GENERAL_END; ++free_slot) {
if ((((uint64) 1 << free_slot) & m_lookup->PossessionsBitmask) == 0) {
continue;
}
const ItemInstance* main_inst = m_inv[free_slot];
if (!main_inst) {
continue;
}
if (
main_inst->GetID() == inst->GetID() &&
main_inst->GetCharges() < main_inst->GetItem()->StackSize
) {
return free_slot;
}
}
for (int16 free_slot = general_start; free_slot <= invslot::GENERAL_END; ++free_slot) {
if ((((uint64) 1 << free_slot) & m_lookup->PossessionsBitmask) == 0) {
continue;
}
const ItemInstance* main_inst = m_inv[free_slot];
if (!main_inst) {
continue;
}
if (main_inst->IsClassBag()) { // if item-specific containers already have bad items, we won't fix it here...
uint8 _bag_start = (free_slot > general_start) ? invbag::SLOT_BEGIN : bag_start;
for (
uint8 free_bag_slot = _bag_start;
(free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot <= invbag::SLOT_END);
++free_bag_slot
) {
const ItemInstance* sub_inst = main_inst->GetItem(free_bag_slot);
if (!sub_inst) {
continue;
}
if (
sub_inst->GetID() == inst->GetID() &&
sub_inst->GetCharges() < sub_inst->GetItem()->StackSize
) {
return InventoryProfile::CalcSlotId(free_slot, free_bag_slot);
}
}
}
}
}
// step 3a: find room for container-specific items (ItemClassArrow)
if (inst->GetItem()->ItemType == item::ItemTypeArrow) {
for (int16 free_slot = general_start; free_slot <= invslot::GENERAL_END; ++free_slot) {
if ((((uint64) 1 << free_slot) & m_lookup->PossessionsBitmask) == 0) {
continue;
}
const ItemInstance* main_inst = m_inv[free_slot];
if (
!main_inst ||
main_inst->GetItem()->BagType != item::BagTypeQuiver ||
!main_inst->IsClassBag()
) {
continue;
}
uint8 _bag_start = (free_slot > general_start) ? invbag::SLOT_BEGIN : bag_start;
for (
uint8 free_bag_slot = _bag_start;
(free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot <= invbag::SLOT_END);
++free_bag_slot
) {
if (!main_inst->GetItem(free_bag_slot)) {
return InventoryProfile::CalcSlotId(free_slot, free_bag_slot);
}
}
}
}
// step 3b: find room for container-specific items (ItemClassSmallThrowing)
if (inst->GetItem()->ItemType == item::ItemTypeSmallThrowing) {
for (int16 free_slot = general_start; free_slot <= invslot::GENERAL_END; ++free_slot) {
if ((((uint64) 1 << free_slot) & m_lookup->PossessionsBitmask) == 0) {
continue;
}
const ItemInstance* main_inst = m_inv[free_slot];
if (
!main_inst ||
main_inst->GetItem()->BagType != item::BagTypeBandolier ||
!main_inst->IsClassBag()
) {
continue;
}
uint8 _bag_start = (free_slot > general_start) ? invbag::SLOT_BEGIN : bag_start;
for (
uint8 free_bag_slot = _bag_start;
(free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot <= invbag::SLOT_END);
++free_bag_slot
) {
if (!main_inst->GetItem(free_bag_slot)) {
return InventoryProfile::CalcSlotId(free_slot, free_bag_slot);
}
}
}
}
// step 4: just find an empty slot
for (int16 free_slot = general_start; free_slot <= invslot::GENERAL_END; ++free_slot) {
if ((((uint64) 1 << free_slot) & m_lookup->PossessionsBitmask) == 0) {
continue;
}
const ItemInstance* main_inst = m_inv[free_slot];
if (!main_inst) {
return free_slot;
}
}
for (int16 free_slot = general_start; free_slot <= invslot::GENERAL_END; ++free_slot) {
if ((((uint64) 1 << free_slot) & m_lookup->PossessionsBitmask) == 0) {
continue;
}
const ItemInstance* main_inst = m_inv[free_slot];
if (main_inst && main_inst->IsClassBag()) {
if (
main_inst->GetItem()->BagSize < inst->GetItem()->Size ||
main_inst->GetItem()->BagType == item::BagTypeBandolier ||
main_inst->GetItem()->BagType == item::BagTypeQuiver
) {
continue;
}
uint8 _bag_start = (free_slot > general_start) ? invbag::SLOT_BEGIN : bag_start;
for (
uint8 free_bag_slot = _bag_start;
(free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot <= invbag::SLOT_END);
++free_bag_slot
) {
if (!main_inst->GetItem(free_bag_slot)) {
return InventoryProfile::CalcSlotId(free_slot, free_bag_slot);
}
}
}
}
//return INVALID_INDEX; // everything else pushes to the cursor
return invslot::slotCursor;
}
// Opposite of below: Get parent bag slot_id from a slot inside of bag
int16 EQ::InventoryProfile::CalcSlotId(int16 slot_id)
{
int16 parent_slot_id = INVALID_INDEX;
// this is not a bag range... using this risks over-writing existing items
//else if (slot_id >= EmuConstants::BANK_BEGIN && slot_id <= EmuConstants::BANK_END)
// parent_slot_id = EmuConstants::BANK_BEGIN + (slot_id - EmuConstants::BANK_BEGIN) / EmuConstants::ITEM_CONTAINER_SIZE;
//else if (slot_id >= 3100 && slot_id <= 3179) should be {3031..3110}..where did this range come from!!? (verified db save range)
if (EQ::ValueWithin(slot_id, invbag::GENERAL_BAGS_BEGIN, invbag::GENERAL_BAGS_END)) {
parent_slot_id = invslot::GENERAL_BEGIN + (slot_id - invbag::GENERAL_BAGS_BEGIN) / invbag::SLOT_COUNT;
} else if (EQ::ValueWithin(slot_id, invbag::CURSOR_BAG_BEGIN, invbag::CURSOR_BAG_END)) {
parent_slot_id = invslot::slotCursor;
} else if (EQ::ValueWithin(slot_id, invbag::BANK_BAGS_BEGIN, invbag::BANK_BAGS_END)) {
parent_slot_id = invslot::BANK_BEGIN + (slot_id - invbag::BANK_BAGS_BEGIN) / invbag::SLOT_COUNT;
} else if (EQ::ValueWithin(slot_id, invbag::SHARED_BANK_BAGS_BEGIN, invbag::SHARED_BANK_BAGS_END)) {
parent_slot_id = invslot::SHARED_BANK_BEGIN + (slot_id - invbag::SHARED_BANK_BAGS_BEGIN) / invbag::SLOT_COUNT;
} else if (EQ::ValueWithin(slot_id, invbag::TRADE_BAGS_BEGIN, invbag::TRADE_BAGS_END)) {
parent_slot_id = invslot::TRADE_BEGIN + (slot_id - invbag::TRADE_BAGS_BEGIN) / invbag::SLOT_COUNT;
}
return parent_slot_id;
}
// Calculate slot_id for an item within a bag
int16 EQ::InventoryProfile::CalcSlotId(int16 bagslot_id, uint8 bagidx)
{
if (!InventoryProfile::SupportsContainers(bagslot_id)) {
return INVALID_INDEX;
}
int16 slot_id = INVALID_INDEX;
if (bagslot_id == invslot::slotCursor) {
slot_id = invbag::CURSOR_BAG_BEGIN + bagidx;
} else if (EQ::ValueWithin(bagslot_id, invslot::GENERAL_BEGIN, invslot::GENERAL_END)) {
slot_id = invbag::GENERAL_BAGS_BEGIN + (bagslot_id - invslot::GENERAL_BEGIN) * invbag::SLOT_COUNT + bagidx;
} else if (EQ::ValueWithin(bagslot_id, invslot::BANK_BEGIN, invslot::BANK_END)) {
slot_id = invbag::BANK_BAGS_BEGIN + (bagslot_id - invslot::BANK_BEGIN) * invbag::SLOT_COUNT + bagidx;
} else if (EQ::ValueWithin(bagslot_id, invslot::SHARED_BANK_BEGIN, invslot::SHARED_BANK_END)) {
slot_id = invbag::SHARED_BANK_BAGS_BEGIN + (bagslot_id - invslot::SHARED_BANK_BEGIN) * invbag::SLOT_COUNT + bagidx;
} else if (EQ::ValueWithin(bagslot_id, invslot::TRADE_BEGIN, invslot::TRADE_END)) {
slot_id = invbag::TRADE_BAGS_BEGIN + (bagslot_id - invslot::TRADE_BEGIN) * invbag::SLOT_COUNT + bagidx;
}
return slot_id;
}
uint8 EQ::InventoryProfile::CalcBagIdx(int16 slot_id) {
uint8 index = 0;
if (EQ::ValueWithin(slot_id, invbag::GENERAL_BAGS_BEGIN, invbag::GENERAL_BAGS_END)) {
index = (slot_id - invbag::GENERAL_BAGS_BEGIN) % invbag::SLOT_COUNT;
} else if (EQ::ValueWithin(slot_id, invbag::CURSOR_BAG_BEGIN, invbag::CURSOR_BAG_END)) {
index = (slot_id - invbag::CURSOR_BAG_BEGIN); // % invbag::SLOT_COUNT; - not needed since range is 10 slots
} else if (EQ::ValueWithin(slot_id, invbag::BANK_BAGS_BEGIN, invbag::BANK_BAGS_END)) {
index = (slot_id - invbag::BANK_BAGS_BEGIN) % invbag::SLOT_COUNT;
} else if (EQ::ValueWithin(slot_id, invbag::SHARED_BANK_BAGS_BEGIN, invbag::SHARED_BANK_BAGS_END)) {
index = (slot_id - invbag::SHARED_BANK_BAGS_BEGIN) % invbag::SLOT_COUNT;
} else if (EQ::ValueWithin(slot_id, invbag::TRADE_BAGS_BEGIN, invbag::TRADE_BAGS_END)) {
index = (slot_id - invbag::TRADE_BAGS_BEGIN) % invbag::SLOT_COUNT;
} else if (EQ::ValueWithin(slot_id, invslot::WORLD_BEGIN, invslot::WORLD_END)) {
index = (slot_id - invslot::WORLD_BEGIN); // % invbag::SLOT_COUNT; - not needed since range is 10 slots
}
return index;
}
int16 EQ::InventoryProfile::CalcSlotFromMaterial(uint8 material)
{
switch (material)
{
case textures::armorHead:
return invslot::slotHead;
case textures::armorChest:
return invslot::slotChest;
case textures::armorArms:
return invslot::slotArms;
case textures::armorWrist:
return invslot::slotWrist1; // there's 2 bracers, only one bracer material
case textures::armorHands:
return invslot::slotHands;
case textures::armorLegs:
return invslot::slotLegs;
case textures::armorFeet:
return invslot::slotFeet;
case textures::weaponPrimary:
return invslot::slotPrimary;
case textures::weaponSecondary:
return invslot::slotSecondary;
default:
return INVALID_INDEX;
}
}
uint8 EQ::InventoryProfile::CalcMaterialFromSlot(int16 equipslot)
{
switch (equipslot) {
case invslot::slotHead:
return textures::armorHead;
case invslot::slotChest:
return textures::armorChest;
case invslot::slotArms:
return textures::armorArms;
case invslot::slotWrist1:
return textures::armorWrist;
case invslot::slotHands:
return textures::armorHands;
case invslot::slotLegs:
return textures::armorLegs;
case invslot::slotFeet:
return textures::armorFeet;
case invslot::slotPrimary:
return textures::weaponPrimary;
case invslot::slotSecondary:
return textures::weaponSecondary;
default:
return textures::materialInvalid;
}
}
bool EQ::InventoryProfile::CanItemFitInContainer(const ItemData *ItemToTry, const ItemData *Container) {
if (!ItemToTry || !Container) {
return false;
}
if (ItemToTry->Size > Container->BagSize) {
return false;
}
if (
Container->BagType == item::BagTypeQuiver &&
ItemToTry->ItemType != item::ItemTypeArrow
) {
return false;
}
if (
Container->BagType == item::BagTypeBandolier &&
ItemToTry->ItemType != item::ItemTypeSmallThrowing
) {
return false;
}
return true;
}
bool EQ::InventoryProfile::SupportsClickCasting(int16 slot_id)
{
// there are a few non-potion items that identify as ItemTypePotion..so, we still need to ubiquitously include the equipment range
if (EQ::ValueWithin(slot_id, invslot::EQUIPMENT_BEGIN, invslot::EQUIPMENT_END)) {
return true;
} else if (EQ::ValueWithin(slot_id, invslot::GENERAL_BEGIN, invslot::GENERAL_END)) {
return true;
} else if (EQ::ValueWithin(slot_id, invbag::GENERAL_BAGS_BEGIN, invbag::GENERAL_BAGS_END)) {
if (inventory::StaticLookup(m_mob_version)->AllowClickCastFromBag) {
return true;
}
}
return false;
}
bool EQ::InventoryProfile::SupportsPotionBeltCasting(int16 slot_id)
{
// does this have the same criteria as 'SupportsClickCasting' above? (bag clicking per client)
if (EQ::ValueWithin(slot_id, invslot::EQUIPMENT_BEGIN, invslot::EQUIPMENT_END)) {
return true;
} else if (EQ::ValueWithin(slot_id, invslot::GENERAL_BEGIN, invslot::GENERAL_END)) {
return true;
} else if (EQ::ValueWithin(slot_id, invbag::GENERAL_BAGS_BEGIN, invbag::GENERAL_BAGS_END)) {
return true;
}
return false;
}
// Test whether a given slot can support a container item
bool EQ::InventoryProfile::SupportsContainers(int16 slot_id)
{
if (
slot_id == invslot::slotCursor ||
EQ::ValueWithin(slot_id, invslot::GENERAL_BEGIN, invslot::GENERAL_END) ||
EQ::ValueWithin(slot_id, invslot::BANK_BEGIN, invslot::BANK_END) ||
EQ::ValueWithin(slot_id, invslot::SHARED_BANK_BEGIN, invslot::SHARED_BANK_END) ||
EQ::ValueWithin(slot_id, invslot::TRADE_BEGIN, invslot::TRADE_END)
) {
return true;
}
return false;
}
int EQ::InventoryProfile::GetSlotByItemInst(ItemInstance *inst) {
if (!inst)
return INVALID_INDEX;
int i = GetSlotByItemInstCollection(m_worn, inst);
if (i != INVALID_INDEX) {
return i;
}
i = GetSlotByItemInstCollection(m_inv, inst);
if (i != INVALID_INDEX) {
return i;
}
i = GetSlotByItemInstCollection(m_bank, inst);
if (i != INVALID_INDEX) {
return i;
}
i = GetSlotByItemInstCollection(m_shbank, inst);
if (i != INVALID_INDEX) {
return i;
}
i = GetSlotByItemInstCollection(m_trade, inst);
if (i != INVALID_INDEX) {
return i;
}
if (m_cursor.peek_front() == inst) {
return invslot::slotCursor;
}
return INVALID_INDEX;
}
uint8 EQ::InventoryProfile::FindBrightestLightType()
{
uint8 brightest_light_type = 0;
for (auto iter = m_worn.begin(); iter != m_worn.end(); ++iter) {
if (!EQ::ValueWithin(iter->first, invslot::EQUIPMENT_BEGIN, invslot::EQUIPMENT_END)) {
continue;
}
if (iter->first == invslot::slotAmmo) {
continue;
}
EQ::ItemInstance* inst = iter->second;
if (!inst) {
continue;
}
const EQ::ItemData* item = inst->GetItem();
if (!item) {
continue;
}
if (lightsource::IsLevelGreater(item->Light, brightest_light_type)) {
brightest_light_type = item->Light;
}
}
uint8 general_light_type = 0;
for (auto iter = m_inv.begin(); iter != m_inv.end(); ++iter) {
if (!EQ::ValueWithin(iter->first, invslot::GENERAL_BEGIN, invslot::GENERAL_END)) {
continue;
}
EQ::ItemInstance* inst = iter->second;
if (!inst) {
continue;
}
const EQ::ItemData* item = inst->GetItem();
if (!item) {
continue;
}
if (!item->IsClassCommon()) {
continue;
}
if (!EQ::ValueWithin(item->Light, 9, 13)) {
continue;
}
if (lightsource::TypeToLevel(item->Light)) {
general_light_type = item->Light;
}
}
if (lightsource::IsLevelGreater(general_light_type, brightest_light_type)) {
brightest_light_type = general_light_type;
}
return brightest_light_type;
}
int EQ::InventoryProfile::GetSlotByItemInstCollection(const std::map &collection, ItemInstance *inst) {
for (auto iter = collection.begin(); iter != collection.end(); ++iter) {
ItemInstance *t_inst = iter->second;
if (t_inst == inst) {
return iter->first;
}
if (t_inst && !t_inst->IsClassBag()) {
for (auto b_iter = t_inst->_cbegin(); b_iter != t_inst->_cend(); ++b_iter) {
if (b_iter->second == inst) {
return InventoryProfile::CalcSlotId(iter->first, b_iter->first);
}
}
}
}
return EQ::invslot::SLOT_INVALID;
}
// Internal Method: Retrieves item within an inventory bucket
EQ::ItemInstance* EQ::InventoryProfile::_GetItem(const std::map& bucket, int16 slot_id) const
{
if (EQ::ValueWithin(slot_id, EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END)) {
if ((((uint64) 1 << slot_id) & m_lookup->PossessionsBitmask) == 0) {
return nullptr;
}
} else if (EQ::ValueWithin(slot_id, EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END)) {
if (slot_id - EQ::invslot::BANK_BEGIN >= m_lookup->InventoryTypeSize.Bank) {
return nullptr;
}
}
auto it = bucket.find(slot_id);
return it != bucket.end() ? it->second : nullptr;
}
// Internal Method: "put" item into bucket, without regard for what is currently in bucket
// Assumes item has already been allocated
int16 EQ::InventoryProfile::_PutItem(int16 slot_id, ItemInstance* inst)
{
// What happens here when we _PutItem(MainCursor)? Bad things..really bad things...
//
// If putting a nullptr into slot, we need to remove slot without memory delete
if (!inst) {
//Why do we not delete the poped item here????
PopItem(slot_id);
return slot_id;
}
int16 result = INVALID_INDEX;
int16 parent_slot = INVALID_INDEX;
inst->SetEvolveEquipped(false);
if (slot_id == invslot::slotCursor) {
// Replace current item on cursor, if exists
m_cursor.pop(); // no memory delete, clients of this function know what they are doing
m_cursor.push_front(inst);
result = slot_id;
} else if (EQ::ValueWithin(slot_id, invslot::EQUIPMENT_BEGIN, invslot::EQUIPMENT_END)) {
if ((((uint64)1 << slot_id) & m_lookup->PossessionsBitmask) != 0) {
if (inst->IsEvolving()) {
inst->SetEvolveEquipped(true);
}
m_worn[slot_id] = inst;
result = slot_id;
}
} else if (EQ::ValueWithin(slot_id, invslot::GENERAL_BEGIN, invslot::GENERAL_END)) {
if ((((uint64) 1 << slot_id) & m_lookup->PossessionsBitmask) != 0) {
m_inv[slot_id] = inst;
result = slot_id;
}
} else if (EQ::ValueWithin(slot_id, invslot::TRIBUTE_BEGIN, invslot::TRIBUTE_END)) {
m_worn[slot_id] = inst;
result = slot_id;
} else if (EQ::ValueWithin(slot_id, invslot::GUILD_TRIBUTE_BEGIN, invslot::GUILD_TRIBUTE_END)) {
m_worn[slot_id] = inst;
result = slot_id;
} else if (EQ::ValueWithin(slot_id, invslot::BANK_BEGIN, invslot::BANK_END)) {
if (slot_id - EQ::invslot::BANK_BEGIN < m_lookup->InventoryTypeSize.Bank) {
m_bank[slot_id] = inst;
result = slot_id;
}
} else if (EQ::ValueWithin(slot_id, invslot::SHARED_BANK_BEGIN, invslot::SHARED_BANK_END)) {
m_shbank[slot_id] = inst;
result = slot_id;
} else if (EQ::ValueWithin(slot_id, invslot::TRADE_BEGIN, invslot::TRADE_END)) {
m_trade[slot_id] = inst;
result = slot_id;
} else {
// Slot must be within a bag
parent_slot = InventoryProfile::CalcSlotId(slot_id);
ItemInstance* baginst = GetItem(parent_slot); // Get parent bag
if (baginst && baginst->IsClassBag()) {
baginst->_PutItem(InventoryProfile::CalcBagIdx(slot_id), inst);
result = slot_id;
}
}
if (result == INVALID_INDEX) {
LogError("Invalid slot_id specified ({}) with parent slot id ({})", slot_id, parent_slot);
InventoryProfile::MarkDirty(inst); // Slot not found, clean up
}
return result;
}
// Internal Method: Checks an inventory bucket for a particular item
int16 EQ::InventoryProfile::_HasItem(std::map& bucket, uint32 item_id, uint8 quantity)
{
uint32 quantity_found = 0;
for (auto iter = bucket.begin(); iter != bucket.end(); ++iter) {
if (EQ::ValueWithin(iter->first, EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END)) {
if ((((uint64) 1 << iter->first) & m_lookup->PossessionsBitmask) == 0) {
continue;
}
} else if (EQ::ValueWithin(iter->first, EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END)) {
if (iter->first - EQ::invslot::BANK_BEGIN >= m_lookup->InventoryTypeSize.Bank) {
continue;
}
}
EQ::ItemInstance* inst = iter->second;
if (!inst) {
continue;
}
if (inst->GetID() == item_id) {
quantity_found += (inst->GetCharges() <= 0) ? 1 : inst->GetCharges();
if (quantity_found >= quantity) {
return iter->first;
}
}
for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) {
if (inst->GetAugmentItemID(index) == item_id && quantity <= 1) {
return invslot::SLOT_AUGMENT_GENERIC_RETURN;
}
}
if (!inst->IsClassBag()) {
continue;
}
for (auto bag_iter = inst->_cbegin(); bag_iter != inst->_cend(); ++bag_iter) {
EQ::ItemInstance* bag_inst = bag_iter->second;
if (!bag_inst) {
continue;
}
if (bag_inst->GetID() == item_id) {
quantity_found += (bag_inst->GetCharges() <= 0) ? 1 : bag_inst->GetCharges();
if (quantity_found >= quantity) {
return InventoryProfile::CalcSlotId(iter->first, bag_iter->first);
}
}
for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) {
if (bag_inst->GetAugmentItemID(index) == item_id && quantity <= 1) {
return invslot::SLOT_AUGMENT_GENERIC_RETURN;
}
}
}
}
return INVALID_INDEX;
}
// Internal Method: Checks an inventory queue type bucket for a particular item
int16 EQ::InventoryProfile::_HasItem(ItemInstQueue& iqueue, uint32 item_id, uint8 quantity)
{
// The downfall of this (these) queue procedure is that callers presume that when an item is
// found, it is presented as being available on the cursor. In cases of a parity check, this
// is sufficient. However, in cases where referential criteria is considered, this can lead
// to unintended results. Funtionality should be observed when referencing the return value
// of this query
uint32 quantity_found = 0;
for (auto iter = iqueue.cbegin(); iter != iqueue.cend(); ++iter) {
EQ::ItemInstance* inst = *iter;
if (!inst) {
continue;
}
if (inst->GetID() == item_id) {
quantity_found += (inst->GetCharges() <= 0) ? 1 : inst->GetCharges();
if (quantity_found >= quantity) {
return invslot::slotCursor;
}
}
for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) {
if (inst->GetAugmentItemID(index) == item_id && quantity <= 1) {
return invslot::SLOT_AUGMENT_GENERIC_RETURN;
}
}
if (!inst->IsClassBag()) {
continue;
}
for (auto bag_iter = inst->_cbegin(); bag_iter != inst->_cend(); ++bag_iter) {
EQ::ItemInstance* bag_inst = bag_iter->second;
if (!bag_inst) {
continue;
}
if (bag_inst->GetID() == item_id) {
quantity_found += (bag_inst->GetCharges() <= 0) ? 1 : bag_inst->GetCharges();
if (quantity_found >= quantity) {
return InventoryProfile::CalcSlotId(invslot::slotCursor, bag_iter->first);
}
}
for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) {
if (bag_inst->GetAugmentItemID(index) == item_id && quantity <= 1) {
return invslot::SLOT_AUGMENT_GENERIC_RETURN;
}
}
}
// We only check the visible cursor due to lack of queue processing ability (client allows duplicate in limbo)
break;
}
return INVALID_INDEX;
}
// Internal Method: Checks an inventory bucket for a particular item
int16 EQ::InventoryProfile::_HasItemByUse(std::map& bucket, uint8 use, uint8 quantity)
{
uint32 quantity_found = 0;
for (auto iter = bucket.begin(); iter != bucket.end(); ++iter) {
if (EQ::ValueWithin(iter->first, EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END)) {
if ((((uint64) 1 << iter->first) & m_lookup->PossessionsBitmask) == 0) {
continue;
}
} else if (EQ::ValueWithin(iter->first, EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END)) {
if (iter->first - EQ::invslot::BANK_BEGIN >= m_lookup->InventoryTypeSize.Bank) {
continue;
}
}
EQ::ItemInstance* inst = iter->second;
if (!inst) {
continue;
}
if (inst->IsClassCommon() && inst->GetItem()->ItemType == use) {
quantity_found += (inst->GetCharges() <= 0) ? 1 : inst->GetCharges();
if (quantity_found >= quantity) {
return iter->first;
}
}
if (!inst->IsClassBag()) {
continue;
}
for (auto bag_iter = inst->_cbegin(); bag_iter != inst->_cend(); ++bag_iter) {
EQ::ItemInstance* bag_inst = bag_iter->second;
if (!bag_inst) {
continue;
}
if (bag_inst->IsClassCommon() && bag_inst->GetItem()->ItemType == use) {
quantity_found += (bag_inst->GetCharges() <= 0) ? 1 : bag_inst->GetCharges();
if (quantity_found >= quantity) {
return InventoryProfile::CalcSlotId(iter->first, bag_iter->first);
}
}
}
}
return INVALID_INDEX;
}
// Internal Method: Checks an inventory queue type bucket for a particular item
int16 EQ::InventoryProfile::_HasItemByUse(ItemInstQueue& iqueue, uint8 use, uint8 quantity)
{
uint32 quantity_found = 0;
for (auto iter = iqueue.cbegin(); iter != iqueue.cend(); ++iter) {
EQ::ItemInstance* inst = *iter;
if (!inst) {
continue;
}
if (inst->IsClassCommon() && inst->GetItem()->ItemType == use) {
quantity_found += (inst->GetCharges() <= 0) ? 1 : inst->GetCharges();
if (quantity_found >= quantity) {
return invslot::slotCursor;
}
}
if (!inst->IsClassBag()) {
continue;
}
for (auto bag_iter = inst->_cbegin(); bag_iter != inst->_cend(); ++bag_iter) {
EQ::ItemInstance* bag_inst = bag_iter->second;
if (!bag_inst) {
continue;
}
if (bag_inst->IsClassCommon() && bag_inst->GetItem()->ItemType == use) {
quantity_found += (bag_inst->GetCharges() <= 0) ? 1 : bag_inst->GetCharges();
if (quantity_found >= quantity) {
return InventoryProfile::CalcSlotId(invslot::slotCursor, bag_iter->first);
}
}
}
// We only check the visible cursor due to lack of queue processing ability (client allows duplicate in limbo)
break;
}
return INVALID_INDEX;
}
int16 EQ::InventoryProfile::_HasItemByLoreGroup(std::map& bucket, uint32 loregroup)
{
for (auto iter = bucket.begin(); iter != bucket.end(); ++iter) {
if (EQ::ValueWithin(iter->first, EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END)) {
if ((((uint64) 1 << iter->first) & m_lookup->PossessionsBitmask) == 0) {
continue;
}
} else if (EQ::ValueWithin(iter->first, EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END)) {
if (iter->first - EQ::invslot::BANK_BEGIN >= m_lookup->InventoryTypeSize.Bank) {
continue;
}
}
EQ::ItemInstance* inst = iter->second;
if (!inst) {
continue;
}
if (inst->GetItem()->LoreGroup == loregroup) {
return iter->first;
}
for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) {
auto aug_inst = inst->GetAugment(index);
if (aug_inst == nullptr) { continue; }
if (aug_inst->GetItem()->LoreGroup == loregroup) {
return invslot::SLOT_AUGMENT_GENERIC_RETURN;
}
}
if (!inst->IsClassBag()) {
continue;
}
for (auto bag_iter = inst->_cbegin(); bag_iter != inst->_cend(); ++bag_iter) {
EQ::ItemInstance* bag_inst = bag_iter->second;
if (!bag_inst) {
continue;
}
if (bag_inst->IsClassCommon() && bag_inst->GetItem()->LoreGroup == loregroup) {
return InventoryProfile::CalcSlotId(iter->first, bag_iter->first);
}
for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) {
EQ::ItemInstance* aug_inst = bag_inst->GetAugment(index);
if (!aug_inst) {
continue;
}
if (aug_inst->GetItem()->LoreGroup == loregroup) {
return invslot::SLOT_AUGMENT_GENERIC_RETURN;
}
}
}
}
return EQ::invslot::SLOT_INVALID;
}
// Internal Method: Checks an inventory queue type bucket for a particular item
int16 EQ::InventoryProfile::_HasItemByLoreGroup(ItemInstQueue& iqueue, uint32 loregroup)
{
for (auto iter = iqueue.cbegin(); iter != iqueue.cend(); ++iter) {
EQ::ItemInstance* inst = *iter;
if (!inst) {
continue;
}
if (inst->GetItem()->LoreGroup == loregroup) {
return invslot::slotCursor;
}
for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) {
EQ::ItemInstance* aug_inst = inst->GetAugment(index);
if (!aug_inst) {
continue;
}
if (aug_inst->GetItem()->LoreGroup == loregroup) {
return invslot::SLOT_AUGMENT_GENERIC_RETURN;
}
}
if (!inst->IsClassBag()) {
continue;
}
for (auto bag_iter = inst->_cbegin(); bag_iter != inst->_cend(); ++bag_iter) {
EQ::ItemInstance* bag_inst = bag_iter->second;
if (!bag_inst) {
continue;
}
if (bag_inst->IsClassCommon() && bag_inst->GetItem()->LoreGroup == loregroup) {
return InventoryProfile::CalcSlotId(invslot::slotCursor, bag_iter->first);
}
for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) {
EQ::ItemInstance* aug_inst = bag_inst->GetAugment(index);
if (!aug_inst) {
continue;
}
if (aug_inst->GetItem()->LoreGroup == loregroup) {
return invslot::SLOT_AUGMENT_GENERIC_RETURN;
}
}
}
// We only check the visible cursor due to lack of queue processing ability (client allows duplicate in limbo)
break;
}
return EQ::invslot::SLOT_INVALID;
}
std::vector EQ::InventoryProfile::GetAugmentIDsBySlotID(int16 slot_id)
{
std::vector augments;
const auto* item = GetItem(slot_id);
if (item) {
for (uint8 i = invaug::SOCKET_BEGIN; i <= invaug::SOCKET_END; i++) {
augments.push_back(item->GetAugment(i) ? item->GetAugmentItemID(i) : 0);
}
}
return augments;
}
int16 EQ::InventoryProfile::FindFirstFreeSlotThatFitsItem(const EQ::ItemData *item_data)
{
for (int16 i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) {
if ((((uint64) 1 << i) & GetLookup()->PossessionsBitmask) == 0) {
continue;
}
EQ::ItemInstance *inv_item = GetItem(i);
if (!inv_item) {
// Found available slot in personal inventory
return i;
}
if (inv_item->IsClassBag() &&
EQ::InventoryProfile::CanItemFitInContainer(item_data, inv_item->GetItem())) {
int16 base_slot_id = EQ::InventoryProfile::CalcSlotId(i, EQ::invbag::SLOT_BEGIN);
uint8 bag_size = inv_item->GetItem()->BagSlots;
for (uint8 bag_slot = EQ::invbag::SLOT_BEGIN; bag_slot < bag_size; bag_slot++) {
auto bag_item = GetItem(base_slot_id + bag_slot);
if (!bag_item) {
// Found available slot within bag
return base_slot_id + bag_slot;
}
}
}
}
return INVALID_INDEX;
}
//This function has the same flaw as noted above
// Helper functions for evolving items
int16 EQ::InventoryProfile::HasEvolvingItem(uint64 evolve_unique_id, uint8 quantity, uint8 where)
{
int16 slot_id = INVALID_INDEX;
// Altered by Father Nitwit to support a specification of
// where to search, with a default value to maintain compatibility
// Check each inventory bucket
if (where & invWhereWorn) {
slot_id = _HasEvolvingItem(m_worn, evolve_unique_id, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
if (where & invWherePersonal) {
slot_id = _HasEvolvingItem(m_inv, evolve_unique_id, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
if (where & invWhereBank) {
slot_id = _HasEvolvingItem(m_bank, evolve_unique_id, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
if (where & invWhereSharedBank) {
slot_id = _HasEvolvingItem(m_shbank, evolve_unique_id, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
if (where & invWhereTrading) {
slot_id = _HasEvolvingItem(m_trade, evolve_unique_id, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
// Behavioral change - Limbo is no longer checked due to improper handling of return value
if (where & invWhereCursor) {
// Check cursor queue
slot_id = _HasEvolvingItem(m_cursor, evolve_unique_id, quantity);
if (slot_id != INVALID_INDEX) {
return slot_id;
}
}
return slot_id;
}
// Internal Method: Checks an inventory bucket for a particular evolving item unique id
int16 EQ::InventoryProfile::_HasEvolvingItem(
std::map &bucket, uint64 evolve_unique_id, uint8 quantity)
{
uint32 quantity_found = 0;
for (auto const &[key, value]: bucket) {
if (!value) {
continue;
}
if (key <= EQ::invslot::POSSESSIONS_END && key >= EQ::invslot::POSSESSIONS_BEGIN) {
if (((uint64) 1 << key & m_lookup->PossessionsBitmask) == 0) {
continue;
}
}
else if (key <= EQ::invslot::BANK_END && key >= EQ::invslot::BANK_BEGIN) {
if (key - EQ::invslot::BANK_BEGIN >= m_lookup->InventoryTypeSize.Bank) {
continue;
}
}
if (value->GetEvolveUniqueID() == evolve_unique_id) {
quantity_found += value->GetCharges() <= 0 ? 1 : value->GetCharges();
if (quantity_found >= quantity) {
return key;
}
}
for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) {
if (value->GetAugmentEvolveUniqueID(index) == evolve_unique_id && quantity <= 1) {
return invslot::SLOT_AUGMENT_GENERIC_RETURN;
}
}
if (!value->IsClassBag()) {
continue;
}
for (auto const &[bag_key, bag_value]: *value->GetContents()) {
if (!bag_value) {
continue;
}
if (bag_value->GetEvolveUniqueID() == evolve_unique_id) {
quantity_found += bag_value->GetCharges() <= 0 ? 1 : bag_value->GetCharges();
if (quantity_found >= quantity) {
return CalcSlotId(key, bag_key);
}
}
for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) {
if (bag_value->GetAugmentEvolveUniqueID(index) == evolve_unique_id && quantity <= 1) {
return invslot::SLOT_AUGMENT_GENERIC_RETURN;
}
}
}
}
return INVALID_INDEX;
}
// Internal Method: Checks an inventory queue type bucket for a particular item
int16 EQ::InventoryProfile::_HasEvolvingItem(ItemInstQueue &iqueue, uint64 evolve_unique_id, uint8 quantity)
{
// The downfall of this (these) queue procedure is that callers presume that when an item is
// found, it is presented as being available on the cursor. In cases of a parity check, this
// is sufficient. However, in cases where referential criteria is considered, this can lead
// to unintended results. Funtionality should be observed when referencing the return value
// of this query
uint32 quantity_found = 0;
for (auto const &inst: iqueue) {
if (!inst) {
continue;
}
if (inst->GetEvolveUniqueID() == evolve_unique_id) {
quantity_found += inst->GetCharges() <= 0 ? 1 : inst->GetCharges();
if (quantity_found >= quantity) {
return invslot::slotCursor;
}
}
for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) {
if (inst->GetAugmentEvolveUniqueID(index) == evolve_unique_id && quantity <= 1) {
return invslot::SLOT_AUGMENT_GENERIC_RETURN;
}
}
if (!inst->IsClassBag()) {
continue;
}
for (auto const &[bag_key, bag_value]: *inst->GetContents()) {
if (!bag_value) {
continue;
}
if (bag_value->GetEvolveUniqueID() == evolve_unique_id) {
quantity_found += bag_value->GetCharges() <= 0 ? 1 : bag_value->GetCharges();
if (quantity_found >= quantity) {
return CalcSlotId(invslot::slotCursor, bag_key);
}
}
for (int index = invaug::SOCKET_BEGIN; index <= invaug::SOCKET_END; ++index) {
if (bag_value->GetAugmentEvolveUniqueID(index) == evolve_unique_id && quantity <= 1) {
return invslot::SLOT_AUGMENT_GENERIC_RETURN;
}
}
}
// We only check the visible cursor due to lack of queue processing ability (client allows duplicate in limbo)
break;
}
return INVALID_INDEX;
}
int16 EQ::InventoryProfile::FindFirstFreeSlotThatFitsItemWithStacking(ItemInstance *item_inst) const
{
auto item_data = item_inst->GetItem();
if (!item_data) {
return INVALID_INDEX;
}
for (int16 i = invslot::GENERAL_BEGIN; i <= invslot::GENERAL_END; i++) {
auto const inv_item = GetItem(i);
if (!inv_item) {
// Found available slot in personal inventory
// Anything will fit here
return i;
}
if (item_data->IsClassBag() && item_inst->IsNoneEmptyContainer()) {
// If the inbound item is a bag with items, it cannot be stored within a bag
// Move to next potential slot
continue;
}
if (inv_item->GetID() == item_data->ID && item_data->Stackable) {
if (item_inst->GetCharges() + inv_item->GetCharges() <= item_data->StackSize) {
// Found a personal inventory slot that has room for a stackable addition
return i;
}
}
if (inv_item->IsClassBag() && CanItemFitInContainer(item_data, inv_item->GetItem())) {
int16 const base_slot_id = CalcSlotId(i, invbag::SLOT_BEGIN);
uint8 const bag_size = inv_item->GetItem()->BagSlots;
uint8 const item_size = item_data->Size;
for (uint8 bag_slot = invbag::SLOT_BEGIN; bag_slot < bag_size; bag_slot++) {
auto bag_item = GetItem(base_slot_id + bag_slot);
if (!bag_item) {
// Found available slot within bag that will hold inbound item
return base_slot_id + bag_slot;
}
if (bag_item && item_data->Stackable && bag_item->GetID() == item_data->ID) {
if (item_inst->GetCharges() + bag_item->GetCharges() <= item_data->StackSize) {
// Found a bag slot has room for a stackable addition
return base_slot_id + bag_slot;
}
}
}
}
}
return INVALID_INDEX;
}