mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-11-27 07:57:21 +00:00
aaf446fe6e
* Added feature grouping logic * Add saving of brick buckets * Add edge case check for max group count * Use vector for storing groups * Update InventoryComponent.cpp * Update InventoryComponent.h * Update InventoryComponent.h * fix string log format * Update GameMessages.cpp
1730 lines
45 KiB
C++
1730 lines
45 KiB
C++
#include "InventoryComponent.h"
|
|
|
|
#include <sstream>
|
|
|
|
#include "Entity.h"
|
|
#include "Item.h"
|
|
#include "Game.h"
|
|
#include "Logger.h"
|
|
#include "CDClientManager.h"
|
|
#include "ObjectIDManager.h"
|
|
#include "MissionComponent.h"
|
|
#include "GameMessages.h"
|
|
#include "SkillComponent.h"
|
|
#include "Character.h"
|
|
#include "EntityManager.h"
|
|
#include "ItemSet.h"
|
|
#include "PetComponent.h"
|
|
#include "PossessorComponent.h"
|
|
#include "PossessableComponent.h"
|
|
#include "ModuleAssemblyComponent.h"
|
|
#include "HavokVehiclePhysicsComponent.h"
|
|
#include "CharacterComponent.h"
|
|
#include "dZoneManager.h"
|
|
#include "PropertyManagementComponent.h"
|
|
#include "DestroyableComponent.h"
|
|
#include "dConfig.h"
|
|
#include "eItemType.h"
|
|
#include "eUnequippableActiveType.h"
|
|
#include "CppScripts.h"
|
|
#include "eMissionTaskType.h"
|
|
#include "eStateChangeType.h"
|
|
#include "eUseItemResponse.h"
|
|
#include "Mail.h"
|
|
|
|
#include "CDComponentsRegistryTable.h"
|
|
#include "CDInventoryComponentTable.h"
|
|
#include "CDScriptComponentTable.h"
|
|
#include "CDObjectSkillsTable.h"
|
|
#include "CDSkillBehaviorTable.h"
|
|
#include "StringifiedEnum.h"
|
|
|
|
#include <ranges>
|
|
|
|
InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) {
|
|
this->m_Dirty = true;
|
|
this->m_Equipped = {};
|
|
this->m_Pushed = {};
|
|
this->m_Consumable = LOT_NULL;
|
|
this->m_Pets = {};
|
|
|
|
const auto lot = parent->GetLOT();
|
|
|
|
if (lot == 1) {
|
|
auto* character = m_Parent->GetCharacter();
|
|
if (character) LoadXml(character->GetXMLDoc());
|
|
|
|
CheckProxyIntegrity();
|
|
|
|
return;
|
|
}
|
|
|
|
auto* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
|
|
const auto componentId = compRegistryTable->GetByIDAndType(lot, eReplicaComponentType::INVENTORY);
|
|
|
|
auto* inventoryComponentTable = CDClientManager::GetTable<CDInventoryComponentTable>();
|
|
auto items = inventoryComponentTable->Query([=](const CDInventoryComponent entry) { return entry.id == componentId; });
|
|
|
|
auto slot = 0u;
|
|
|
|
for (const auto& item : items) {
|
|
if (!item.equip || !Inventory::IsValidItem(item.itemid)) {
|
|
continue;
|
|
}
|
|
|
|
const LWOOBJID id = ObjectIDManager::GenerateObjectID();
|
|
|
|
const auto& info = Inventory::FindItemComponent(item.itemid);
|
|
|
|
UpdateSlot(info.equipLocation, { id, static_cast<LOT>(item.itemid), item.count, slot++ });
|
|
|
|
// Equip this items proxies.
|
|
auto subItems = info.subItems;
|
|
|
|
subItems.erase(std::remove_if(subItems.begin(), subItems.end(), ::isspace), subItems.end());
|
|
|
|
if (!subItems.empty()) {
|
|
const auto subItemsSplit = GeneralUtils::SplitString(subItems, ',');
|
|
|
|
for (auto proxyLotAsString : subItemsSplit) {
|
|
const auto proxyLOT = static_cast<LOT>(std::stoi(proxyLotAsString));
|
|
|
|
const auto& proxyInfo = Inventory::FindItemComponent(proxyLOT);
|
|
const LWOOBJID proxyId = ObjectIDManager::GenerateObjectID();
|
|
|
|
// Use item.count since we equip item.count number of the item this is a requested proxy of
|
|
UpdateSlot(proxyInfo.equipLocation, { proxyId, proxyLOT, item.count, slot++ });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Inventory* InventoryComponent::GetInventory(const eInventoryType type) {
|
|
const auto index = m_Inventories.find(type);
|
|
|
|
if (index != m_Inventories.end()) {
|
|
return index->second;
|
|
}
|
|
|
|
// Create new empty inventory
|
|
uint32_t size = 240u;
|
|
|
|
switch (type) {
|
|
case eInventoryType::ITEMS:
|
|
size = 20u;
|
|
break;
|
|
case eInventoryType::VAULT_MODELS:
|
|
case eInventoryType::VAULT_ITEMS:
|
|
size = 40u;
|
|
break;
|
|
case eInventoryType::VENDOR_BUYBACK:
|
|
size = 27u;
|
|
break;
|
|
case eInventoryType::DONATION:
|
|
size = 24u;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
auto* inventory = new Inventory(type, size, {}, this);
|
|
|
|
m_Inventories.insert_or_assign(type, inventory);
|
|
|
|
return inventory;
|
|
}
|
|
|
|
const std::map<eInventoryType, Inventory*>& InventoryComponent::GetInventories() const {
|
|
return m_Inventories;
|
|
}
|
|
|
|
uint32_t InventoryComponent::GetLotCount(const LOT lot) const {
|
|
uint32_t count = 0;
|
|
|
|
for (const auto& inventory : m_Inventories) {
|
|
count += inventory.second->GetLotCount(lot);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
uint32_t InventoryComponent::GetLotCountNonTransfer(LOT lot) const {
|
|
uint32_t count = 0;
|
|
|
|
for (const auto& inventory : m_Inventories) {
|
|
if (IsTransferInventory(inventory.second->GetType())) continue;
|
|
|
|
count += inventory.second->GetLotCount(lot);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
const EquipmentMap& InventoryComponent::GetEquippedItems() const {
|
|
return m_Equipped;
|
|
}
|
|
|
|
void InventoryComponent::AddItem(
|
|
const LOT lot,
|
|
const uint32_t count,
|
|
eLootSourceType lootSourceType,
|
|
eInventoryType inventoryType,
|
|
const std::vector<LDFBaseData*>& config,
|
|
const LWOOBJID parent,
|
|
const bool showFlyingLoot,
|
|
bool isModMoveAndEquip,
|
|
const LWOOBJID subKey,
|
|
const eInventoryType inventorySourceType,
|
|
const int32_t sourceType,
|
|
const bool bound,
|
|
int32_t preferredSlot) {
|
|
if (count == 0) {
|
|
LOG("Attempted to add 0 of item (%i) to the inventory!", lot);
|
|
|
|
return;
|
|
}
|
|
|
|
if (!Inventory::IsValidItem(lot)) {
|
|
if (lot > 0) {
|
|
LOG("Attempted to add invalid item (%i) to the inventory!", lot);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (inventoryType == INVALID) {
|
|
inventoryType = Inventory::FindInventoryTypeForLot(lot);
|
|
}
|
|
|
|
auto* missions = static_cast<MissionComponent*>(this->m_Parent->GetComponent(eReplicaComponentType::MISSION));
|
|
|
|
auto* inventory = GetInventory(inventoryType);
|
|
|
|
if (!config.empty() || bound) {
|
|
const auto slot = preferredSlot != -1 && inventory->IsSlotEmpty(preferredSlot) ? preferredSlot : inventory->FindEmptySlot();
|
|
|
|
if (slot == -1) {
|
|
LOG("Failed to find empty slot for inventory (%i)!", inventoryType);
|
|
|
|
return;
|
|
}
|
|
|
|
auto* item = new Item(lot, inventory, slot, count, config, parent, showFlyingLoot, isModMoveAndEquip, subKey, bound, lootSourceType);
|
|
|
|
if (missions != nullptr && !IsTransferInventory(inventoryType)) {
|
|
missions->Progress(eMissionTaskType::GATHER, lot, LWOOBJID_EMPTY, "", count, IsTransferInventory(inventorySourceType));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
const auto info = Inventory::FindItemComponent(lot);
|
|
|
|
auto left = count;
|
|
|
|
int32_t outOfSpace = 0;
|
|
|
|
auto stack = static_cast<uint32_t>(info.stackSize);
|
|
|
|
bool isBrick = inventoryType == eInventoryType::BRICKS || (stack == 0 && info.itemType == 1);
|
|
|
|
// info.itemType of 1 is item type brick
|
|
if (isBrick) {
|
|
stack = UINT32_MAX;
|
|
} else if (stack == 0) {
|
|
stack = 1;
|
|
}
|
|
|
|
auto* existing = FindItemByLot(lot, inventoryType);
|
|
|
|
if (existing != nullptr) {
|
|
const auto delta = std::min<uint32_t>(left, stack - existing->GetCount());
|
|
|
|
left -= delta;
|
|
|
|
existing->SetCount(existing->GetCount() + delta, false, true, showFlyingLoot, lootSourceType);
|
|
|
|
if (isModMoveAndEquip) {
|
|
existing->Equip();
|
|
|
|
isModMoveAndEquip = false;
|
|
}
|
|
}
|
|
|
|
// If we have some leftover and we aren't bricks, make a new stack
|
|
while (left > 0 && (!isBrick || (isBrick && !existing))) {
|
|
const auto size = std::min(left, stack);
|
|
|
|
left -= size;
|
|
|
|
int32_t slot;
|
|
|
|
if (preferredSlot != -1 && inventory->IsSlotEmpty(preferredSlot)) {
|
|
slot = preferredSlot;
|
|
|
|
preferredSlot = -1;
|
|
} else {
|
|
slot = inventory->FindEmptySlot();
|
|
}
|
|
|
|
if (slot == -1) {
|
|
outOfSpace += size;
|
|
|
|
switch (sourceType) {
|
|
case 0:
|
|
Mail::SendMail(LWOOBJID_EMPTY, "Darkflame Universe", m_Parent, "Lost Reward", "You received an item and didn't have room for it.", lot, size);
|
|
break;
|
|
|
|
case 1:
|
|
for (size_t i = 0; i < size; i++) {
|
|
GameMessages::SendDropClientLoot(this->m_Parent, this->m_Parent->GetObjectID(), lot, 0, this->m_Parent->GetPosition(), 1);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
auto* item = new Item(lot, inventory, slot, size, {}, parent, showFlyingLoot, isModMoveAndEquip, subKey, false, lootSourceType);
|
|
|
|
isModMoveAndEquip = false;
|
|
}
|
|
|
|
if (missions != nullptr && !IsTransferInventory(inventoryType)) {
|
|
missions->Progress(eMissionTaskType::GATHER, lot, LWOOBJID_EMPTY, "", count - outOfSpace, IsTransferInventory(inventorySourceType));
|
|
}
|
|
}
|
|
|
|
bool InventoryComponent::RemoveItem(const LOT lot, const uint32_t count, eInventoryType inventoryType, const bool ignoreBound, const bool silent) {
|
|
if (count == 0) {
|
|
LOG("Attempted to remove 0 of item (%i) from the inventory!", lot);
|
|
return false;
|
|
}
|
|
if (inventoryType == INVALID) inventoryType = Inventory::FindInventoryTypeForLot(lot);
|
|
auto* inventory = GetInventory(inventoryType);
|
|
if (!inventory) return false;
|
|
|
|
auto left = std::min<uint32_t>(count, inventory->GetLotCount(lot));
|
|
if (left != count) return false;
|
|
|
|
while (left > 0) {
|
|
auto* item = FindItemByLot(lot, inventoryType, false, ignoreBound);
|
|
if (!item) break;
|
|
const auto delta = std::min<uint32_t>(left, item->GetCount());
|
|
item->SetCount(item->GetCount() - delta, silent);
|
|
left -= delta;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType inventory, const uint32_t count, const bool showFlyingLot, bool isModMoveAndEquip, const bool ignoreEquipped, const int32_t preferredSlot) {
|
|
if (item == nullptr) {
|
|
return;
|
|
}
|
|
|
|
auto* origin = item->GetInventory();
|
|
|
|
const auto lot = item->GetLot();
|
|
|
|
const auto subkey = item->GetSubKey();
|
|
|
|
if (subkey == LWOOBJID_EMPTY && item->GetConfig().empty() && (!item->GetBound() || (item->GetBound() && item->GetInfo().isBOP))) {
|
|
auto left = std::min<uint32_t>(count, origin->GetLotCount(lot));
|
|
|
|
while (left > 0) {
|
|
if (item == nullptr) {
|
|
item = origin->FindItemByLot(lot, false);
|
|
|
|
if (item == nullptr) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
const auto delta = std::min<uint32_t>(item->GetCount(), left);
|
|
|
|
left -= delta;
|
|
|
|
AddItem(lot, delta, eLootSourceType::NONE, inventory, {}, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, LWOOBJID_EMPTY, origin->GetType(), 0, false, preferredSlot);
|
|
|
|
item->SetCount(item->GetCount() - delta, false, false);
|
|
|
|
isModMoveAndEquip = false;
|
|
}
|
|
} else {
|
|
std::vector<LDFBaseData*> config;
|
|
|
|
for (auto* const data : item->GetConfig()) {
|
|
config.push_back(data->Copy());
|
|
}
|
|
|
|
const auto delta = std::min<uint32_t>(item->GetCount(), count);
|
|
|
|
AddItem(lot, delta, eLootSourceType::NONE, inventory, config, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, subkey, origin->GetType(), 0, item->GetBound(), preferredSlot);
|
|
|
|
item->SetCount(item->GetCount() - delta, false, false);
|
|
}
|
|
|
|
auto* missionComponent = m_Parent->GetComponent<MissionComponent>();
|
|
|
|
if (missionComponent != nullptr) {
|
|
if (IsTransferInventory(inventory)) {
|
|
missionComponent->Progress(eMissionTaskType::GATHER, lot, LWOOBJID_EMPTY, "", -static_cast<int32_t>(count));
|
|
}
|
|
}
|
|
}
|
|
|
|
void InventoryComponent::MoveStack(Item* item, const eInventoryType inventory, const uint32_t slot) {
|
|
if (inventory != INVALID && item->GetInventory()->GetType() != inventory) {
|
|
auto* newInventory = GetInventory(inventory);
|
|
|
|
item->SetInventory(newInventory);
|
|
}
|
|
|
|
item->SetSlot(slot);
|
|
}
|
|
|
|
Item* InventoryComponent::FindItemById(const LWOOBJID id) const {
|
|
for (const auto& inventory : m_Inventories) {
|
|
auto* item = inventory.second->FindItemById(id);
|
|
|
|
if (item != nullptr) {
|
|
return item;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
Item* InventoryComponent::FindItemByLot(const LOT lot, eInventoryType inventoryType, const bool ignoreEquipped, const bool ignoreBound) {
|
|
if (inventoryType == INVALID) {
|
|
inventoryType = Inventory::FindInventoryTypeForLot(lot);
|
|
}
|
|
|
|
auto* inventory = GetInventory(inventoryType);
|
|
|
|
return inventory->FindItemByLot(lot, ignoreEquipped, ignoreBound);
|
|
}
|
|
|
|
Item* InventoryComponent::FindItemBySubKey(LWOOBJID id, eInventoryType inventoryType) {
|
|
if (inventoryType == INVALID) {
|
|
for (const auto& inventory : m_Inventories) {
|
|
auto* item = inventory.second->FindItemBySubKey(id);
|
|
|
|
if (item != nullptr) {
|
|
return item;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
} else {
|
|
return GetInventory(inventoryType)->FindItemBySubKey(id);
|
|
}
|
|
}
|
|
|
|
bool InventoryComponent::HasSpaceForLoot(const std::unordered_map<LOT, int32_t>& loot) {
|
|
std::unordered_map<eInventoryType, int32_t> spaceOffset{};
|
|
|
|
uint32_t slotsNeeded = 0;
|
|
|
|
for (const auto& pair : loot) {
|
|
const auto inventoryType = Inventory::FindInventoryTypeForLot(pair.first);
|
|
|
|
if (inventoryType == BRICKS) {
|
|
continue;
|
|
}
|
|
|
|
auto* inventory = GetInventory(inventoryType);
|
|
|
|
if (inventory == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
const auto info = Inventory::FindItemComponent(pair.first);
|
|
|
|
auto stack = static_cast<uint32_t>(info.stackSize);
|
|
|
|
auto left = pair.second;
|
|
|
|
auto* partial = inventory->FindItemByLot(pair.first);
|
|
|
|
if (partial != nullptr && partial->GetCount() < stack) {
|
|
left -= stack - partial->GetCount();
|
|
}
|
|
|
|
auto requiredSlots = std::ceil(static_cast<double>(left) / stack);
|
|
|
|
const auto& offsetIter = spaceOffset.find(inventoryType);
|
|
|
|
auto freeSpace = inventory->GetEmptySlots() - (offsetIter == spaceOffset.end() ? 0 : offsetIter->second);
|
|
|
|
if (requiredSlots > freeSpace) {
|
|
slotsNeeded += requiredSlots - freeSpace;
|
|
}
|
|
|
|
spaceOffset[inventoryType] = offsetIter == spaceOffset.end() ? requiredSlots : offsetIter->second + requiredSlots;
|
|
}
|
|
|
|
if (slotsNeeded > 0) {
|
|
GameMessages::SendNotifyNotEnoughInvSpace(m_Parent->GetObjectID(), slotsNeeded, ITEMS, m_Parent->GetSystemAddress());
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) {
|
|
LoadPetXml(document);
|
|
|
|
auto* inventoryElement = document.FirstChildElement("obj")->FirstChildElement("inv");
|
|
|
|
if (inventoryElement == nullptr) {
|
|
LOG("Failed to find 'inv' xml element!");
|
|
|
|
return;
|
|
}
|
|
|
|
auto* bags = inventoryElement->FirstChildElement("bag");
|
|
|
|
if (bags == nullptr) {
|
|
LOG("Failed to find 'bags' xml element!");
|
|
|
|
return;
|
|
}
|
|
|
|
auto* const groups = inventoryElement->FirstChildElement("grps");
|
|
if (groups) {
|
|
LoadGroupXml(*groups);
|
|
}
|
|
|
|
m_Consumable = inventoryElement->IntAttribute("csl", LOT_NULL);
|
|
|
|
auto* bag = bags->FirstChildElement();
|
|
|
|
while (bag != nullptr) {
|
|
unsigned int type;
|
|
unsigned int size;
|
|
|
|
bag->QueryAttribute("t", &type);
|
|
bag->QueryAttribute("m", &size);
|
|
|
|
auto* inventory = GetInventory(static_cast<eInventoryType>(type));
|
|
|
|
inventory->SetSize(size);
|
|
|
|
bag = bag->NextSiblingElement();
|
|
}
|
|
|
|
auto* items = inventoryElement->FirstChildElement("items");
|
|
|
|
if (items == nullptr) {
|
|
LOG("Failed to find 'items' xml element!");
|
|
|
|
return;
|
|
}
|
|
|
|
bag = items->FirstChildElement();
|
|
|
|
while (bag != nullptr) {
|
|
unsigned int type;
|
|
|
|
bag->QueryAttribute("t", &type);
|
|
|
|
auto* inventory = GetInventory(static_cast<eInventoryType>(type));
|
|
|
|
if (inventory == nullptr) {
|
|
LOG("Failed to find inventory (%i)!", type);
|
|
|
|
return;
|
|
}
|
|
|
|
auto* itemElement = bag->FirstChildElement();
|
|
|
|
while (itemElement != nullptr) {
|
|
LWOOBJID id;
|
|
LOT lot;
|
|
bool equipped;
|
|
unsigned int slot;
|
|
unsigned int count;
|
|
bool bound;
|
|
LWOOBJID subKey = LWOOBJID_EMPTY;
|
|
|
|
itemElement->QueryAttribute("id", &id);
|
|
itemElement->QueryAttribute("l", &lot);
|
|
itemElement->QueryAttribute("eq", &equipped);
|
|
itemElement->QueryAttribute("s", &slot);
|
|
itemElement->QueryAttribute("c", &count);
|
|
itemElement->QueryAttribute("b", &bound);
|
|
itemElement->QueryAttribute("sk", &subKey);
|
|
|
|
// Begin custom xml
|
|
auto parent = LWOOBJID_EMPTY;
|
|
|
|
itemElement->QueryAttribute("parent", &parent);
|
|
// End custom xml
|
|
|
|
auto* item = new Item(id, lot, inventory, slot, count, bound, {}, parent, subKey);
|
|
|
|
item->LoadConfigXml(*itemElement);
|
|
|
|
if (equipped) {
|
|
const auto info = Inventory::FindItemComponent(lot);
|
|
|
|
UpdateSlot(info.equipLocation, { item->GetId(), item->GetLot(), item->GetCount(), item->GetSlot() });
|
|
|
|
AddItemSkills(item->GetLot());
|
|
}
|
|
|
|
itemElement = itemElement->NextSiblingElement();
|
|
}
|
|
|
|
bag = bag->NextSiblingElement();
|
|
}
|
|
|
|
for (const auto inventory : m_Inventories) {
|
|
const auto itemCount = inventory.second->GetItems().size();
|
|
|
|
if (inventory.second->GetSize() < itemCount) {
|
|
inventory.second->SetSize(itemCount);
|
|
}
|
|
}
|
|
}
|
|
|
|
void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) {
|
|
UpdatePetXml(document);
|
|
|
|
auto* inventoryElement = document.FirstChildElement("obj")->FirstChildElement("inv");
|
|
|
|
if (inventoryElement == nullptr) {
|
|
LOG("Failed to find 'inv' xml element!");
|
|
|
|
return;
|
|
}
|
|
|
|
std::vector<Inventory*> inventoriesToSave;
|
|
|
|
// Need to prevent some transfer inventories from being saved
|
|
for (const auto& pair : this->m_Inventories) {
|
|
auto* inventory = pair.second;
|
|
|
|
if (inventory->GetType() == VENDOR_BUYBACK || inventory->GetType() == eInventoryType::MODELS_IN_BBB) {
|
|
continue;
|
|
}
|
|
|
|
inventoriesToSave.push_back(inventory);
|
|
}
|
|
|
|
inventoryElement->SetAttribute("csl", m_Consumable);
|
|
|
|
auto* bags = inventoryElement->FirstChildElement("bag");
|
|
|
|
if (bags == nullptr) {
|
|
LOG("Failed to find 'bags' xml element!");
|
|
|
|
return;
|
|
}
|
|
|
|
bags->DeleteChildren();
|
|
|
|
for (const auto* inventory : inventoriesToSave) {
|
|
auto* bag = document.NewElement("b");
|
|
|
|
bag->SetAttribute("t", inventory->GetType());
|
|
bag->SetAttribute("m", static_cast<unsigned int>(inventory->GetSize()));
|
|
|
|
bags->LinkEndChild(bag);
|
|
}
|
|
|
|
auto* groups = inventoryElement->FirstChildElement("grps");
|
|
if (groups) {
|
|
groups->DeleteChildren();
|
|
} else {
|
|
groups = inventoryElement->InsertNewChildElement("grps");
|
|
}
|
|
|
|
UpdateGroupXml(*groups);
|
|
|
|
auto* items = inventoryElement->FirstChildElement("items");
|
|
|
|
if (items == nullptr) {
|
|
LOG("Failed to find 'items' xml element!");
|
|
|
|
return;
|
|
}
|
|
|
|
items->DeleteChildren();
|
|
|
|
for (auto* inventory : inventoriesToSave) {
|
|
if (inventory->GetSize() == 0) {
|
|
continue;
|
|
}
|
|
|
|
auto* bagElement = document.NewElement("in");
|
|
|
|
bagElement->SetAttribute("t", inventory->GetType());
|
|
|
|
for (const auto& pair : inventory->GetItems()) {
|
|
auto* item = pair.second;
|
|
|
|
auto* itemElement = document.NewElement("i");
|
|
|
|
itemElement->SetAttribute("l", item->GetLot());
|
|
itemElement->SetAttribute("id", item->GetId());
|
|
itemElement->SetAttribute("s", static_cast<unsigned int>(item->GetSlot()));
|
|
itemElement->SetAttribute("c", static_cast<unsigned int>(item->GetCount()));
|
|
itemElement->SetAttribute("b", item->GetBound());
|
|
itemElement->SetAttribute("eq", item->IsEquipped());
|
|
itemElement->SetAttribute("sk", item->GetSubKey());
|
|
|
|
// Begin custom xml
|
|
itemElement->SetAttribute("parent", item->GetParent());
|
|
// End custom xml
|
|
|
|
item->SaveConfigXml(*itemElement);
|
|
|
|
bagElement->LinkEndChild(itemElement);
|
|
}
|
|
|
|
items->LinkEndChild(bagElement);
|
|
}
|
|
}
|
|
|
|
void InventoryComponent::Serialize(RakNet::BitStream& outBitStream, const bool bIsInitialUpdate) {
|
|
if (bIsInitialUpdate || m_Dirty) {
|
|
outBitStream.Write(true);
|
|
|
|
outBitStream.Write<uint32_t>(m_Equipped.size());
|
|
|
|
for (const auto& pair : m_Equipped) {
|
|
const auto item = pair.second;
|
|
|
|
if (bIsInitialUpdate) {
|
|
AddItemSkills(item.lot);
|
|
}
|
|
|
|
outBitStream.Write(item.id);
|
|
outBitStream.Write(item.lot);
|
|
|
|
outBitStream.Write0();
|
|
|
|
outBitStream.Write(item.count > 0);
|
|
if (item.count > 0) outBitStream.Write(item.count);
|
|
|
|
outBitStream.Write(item.slot != 0);
|
|
if (item.slot != 0) outBitStream.Write<uint16_t>(item.slot);
|
|
|
|
outBitStream.Write0();
|
|
|
|
bool flag = !item.config.empty();
|
|
outBitStream.Write(flag);
|
|
if (flag) {
|
|
RakNet::BitStream ldfStream;
|
|
ldfStream.Write<int32_t>(item.config.size()); // Key count
|
|
for (LDFBaseData* data : item.config) {
|
|
if (data->GetKey() == u"assemblyPartLOTs") {
|
|
std::string newRocketStr = data->GetValueAsString() + ";";
|
|
GeneralUtils::ReplaceInString(newRocketStr, "+", ";");
|
|
LDFData<std::u16string>* ldf_data = new LDFData<std::u16string>(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(newRocketStr));
|
|
ldf_data->WriteToPacket(ldfStream);
|
|
delete ldf_data;
|
|
} else {
|
|
data->WriteToPacket(ldfStream);
|
|
}
|
|
}
|
|
outBitStream.Write(ldfStream.GetNumberOfBytesUsed() + 1);
|
|
outBitStream.Write<uint8_t>(0); // Don't compress
|
|
outBitStream.Write(ldfStream);
|
|
}
|
|
|
|
outBitStream.Write1();
|
|
}
|
|
|
|
m_Dirty = false;
|
|
} else {
|
|
outBitStream.Write(false);
|
|
}
|
|
|
|
outBitStream.Write(false);
|
|
}
|
|
|
|
void InventoryComponent::Update(float deltaTime) {
|
|
for (auto* set : m_Itemsets) {
|
|
set->Update(deltaTime);
|
|
}
|
|
}
|
|
|
|
void InventoryComponent::UpdateSlot(const std::string& location, EquippedItem item, bool keepCurrent) {
|
|
const auto index = m_Equipped.find(location);
|
|
|
|
if (index != m_Equipped.end()) {
|
|
if (keepCurrent) {
|
|
m_Equipped.insert_or_assign(location + std::to_string(m_Equipped.size()), item);
|
|
|
|
m_Dirty = true;
|
|
|
|
return;
|
|
}
|
|
|
|
auto* old = FindItemById(index->second.id);
|
|
|
|
if (old != nullptr) {
|
|
UnEquipItem(old);
|
|
}
|
|
}
|
|
|
|
m_Equipped.insert_or_assign(location, item);
|
|
|
|
m_Dirty = true;
|
|
}
|
|
|
|
void InventoryComponent::RemoveSlot(const std::string& location) {
|
|
if (m_Equipped.find(location) == m_Equipped.end()) {
|
|
return;
|
|
}
|
|
|
|
m_Equipped.erase(location);
|
|
|
|
m_Dirty = true;
|
|
}
|
|
|
|
void InventoryComponent::EquipItem(Item* item, const bool skipChecks) {
|
|
if (!Inventory::IsValidItem(item->GetLot())) return;
|
|
|
|
// Temp items should be equippable but other transfer items shouldn't be (for example the instruments in RB)
|
|
if (item->IsEquipped()
|
|
|| (item->GetInventory()->GetType() != TEMP_ITEMS && IsTransferInventory(item->GetInventory()->GetType()))
|
|
|| IsPet(item->GetSubKey())) {
|
|
return;
|
|
}
|
|
|
|
auto* character = m_Parent->GetCharacter();
|
|
|
|
if (character != nullptr && !skipChecks) {
|
|
// Hacky proximity rocket
|
|
if (item->GetLot() == 6416) {
|
|
const auto rocketLauchPads = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::ROCKET_LAUNCH);
|
|
|
|
const auto position = m_Parent->GetPosition();
|
|
|
|
for (auto* launchPad : rocketLauchPads) {
|
|
if (!launchPad) continue;
|
|
|
|
auto prereq = launchPad->GetVarAsString(u"rocketLaunchPreCondition");
|
|
if (!prereq.empty()) {
|
|
PreconditionExpression expression(prereq);
|
|
if (!expression.Check(m_Parent)) continue;
|
|
}
|
|
|
|
if (Vector3::DistanceSquared(launchPad->GetPosition(), position) > 13 * 13) continue;
|
|
|
|
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
|
|
|
|
if (characterComponent != nullptr) characterComponent->SetLastRocketItemID(item->GetId());
|
|
|
|
launchPad->OnUse(m_Parent);
|
|
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
const auto building = character->GetBuildMode();
|
|
|
|
const auto type = static_cast<eItemType>(item->GetInfo().itemType);
|
|
|
|
|
|
if (!building && (item->GetLot() == 6086 || type == eItemType::LOOT_MODEL || type == eItemType::VEHICLE)) return;
|
|
|
|
if (type != eItemType::LOOT_MODEL && type != eItemType::MODEL) {
|
|
if (!item->GetBound() && !item->GetPreconditionExpression()->Check(m_Parent)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
const auto lot = item->GetLot();
|
|
|
|
CheckItemSet(lot);
|
|
|
|
for (auto* set : m_Itemsets) {
|
|
set->OnEquip(lot);
|
|
}
|
|
|
|
if (item->GetInfo().isBOE) item->SetBound(true);
|
|
|
|
GenerateProxies(item);
|
|
|
|
UpdateSlot(item->GetInfo().equipLocation, { item->GetId(), item->GetLot(), item->GetCount(), item->GetSlot(), item->GetConfig() });
|
|
|
|
ApplyBuff(item);
|
|
|
|
AddItemSkills(item->GetLot());
|
|
|
|
EquipScripts(item);
|
|
|
|
Game::entityManager->SerializeEntity(m_Parent);
|
|
}
|
|
|
|
void InventoryComponent::UnEquipItem(Item* item) {
|
|
if (!item->IsEquipped()) {
|
|
return;
|
|
}
|
|
|
|
const auto lot = item->GetLot();
|
|
|
|
if (!Inventory::IsValidItem(lot)) {
|
|
return;
|
|
}
|
|
|
|
CheckItemSet(lot);
|
|
|
|
for (auto* set : m_Itemsets) {
|
|
set->OnUnEquip(lot);
|
|
}
|
|
|
|
RemoveBuff(item);
|
|
|
|
RemoveItemSkills(item->GetLot());
|
|
|
|
RemoveSlot(item->GetInfo().equipLocation);
|
|
|
|
UnequipScripts(item);
|
|
|
|
Game::entityManager->SerializeEntity(m_Parent);
|
|
|
|
// Trigger property event
|
|
if (PropertyManagementComponent::Instance() != nullptr && item->GetCount() > 0 && Inventory::FindInventoryTypeForLot(item->GetLot()) == MODELS) {
|
|
PropertyManagementComponent::Instance()->GetParent()->OnZonePropertyModelRemovedWhileEquipped(m_Parent);
|
|
Game::zoneManager->GetZoneControlObject()->OnZonePropertyModelRemovedWhileEquipped(m_Parent);
|
|
}
|
|
|
|
PurgeProxies(item);
|
|
}
|
|
|
|
|
|
void InventoryComponent::EquipScripts(Item* equippedItem) {
|
|
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
|
|
if (!compRegistryTable) return;
|
|
int32_t scriptComponentID = compRegistryTable->GetByIDAndType(equippedItem->GetLot(), eReplicaComponentType::SCRIPT, -1);
|
|
if (scriptComponentID > -1) {
|
|
CDScriptComponentTable* scriptCompTable = CDClientManager::GetTable<CDScriptComponentTable>();
|
|
CDScriptComponent scriptCompData = scriptCompTable->GetByID(scriptComponentID);
|
|
auto* itemScript = CppScripts::GetScript(m_Parent, scriptCompData.script_name);
|
|
if (!itemScript) {
|
|
LOG("null script?");
|
|
}
|
|
itemScript->OnFactionTriggerItemEquipped(m_Parent, equippedItem->GetId());
|
|
}
|
|
}
|
|
|
|
void InventoryComponent::UnequipScripts(Item* unequippedItem) {
|
|
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
|
|
if (!compRegistryTable) return;
|
|
int32_t scriptComponentID = compRegistryTable->GetByIDAndType(unequippedItem->GetLot(), eReplicaComponentType::SCRIPT, -1);
|
|
if (scriptComponentID > -1) {
|
|
CDScriptComponentTable* scriptCompTable = CDClientManager::GetTable<CDScriptComponentTable>();
|
|
CDScriptComponent scriptCompData = scriptCompTable->GetByID(scriptComponentID);
|
|
auto* itemScript = CppScripts::GetScript(m_Parent, scriptCompData.script_name);
|
|
if (!itemScript) {
|
|
LOG("null script?");
|
|
}
|
|
itemScript->OnFactionTriggerItemUnequipped(m_Parent, unequippedItem->GetId());
|
|
}
|
|
}
|
|
|
|
void InventoryComponent::HandlePossession(Item* item) {
|
|
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
|
|
if (!characterComponent) return;
|
|
|
|
auto* possessorComponent = m_Parent->GetComponent<PossessorComponent>();
|
|
if (!possessorComponent) return;
|
|
|
|
// Don't do anything if we are busy dismounting
|
|
if (possessorComponent->GetIsDismounting()) return;
|
|
|
|
// Check to see if we are already mounting something
|
|
auto* currentlyPossessedEntity = Game::entityManager->GetEntity(possessorComponent->GetPossessable());
|
|
auto currentlyPossessedItem = possessorComponent->GetMountItemID();
|
|
|
|
if (currentlyPossessedItem) {
|
|
if (currentlyPossessedEntity) possessorComponent->Dismount(currentlyPossessedEntity);
|
|
return;
|
|
}
|
|
|
|
GameMessages::SendSetStunned(m_Parent->GetObjectID(), eStateChangeType::PUSH, m_Parent->GetSystemAddress(), LWOOBJID_EMPTY, true, false, true, false, false, false, false, true, true, true, true, true, true, true, true, true);
|
|
|
|
// Set the mount Item ID so that we know what were handling
|
|
possessorComponent->SetMountItemID(item->GetId());
|
|
GameMessages::SendSetMountInventoryID(m_Parent, item->GetId(), UNASSIGNED_SYSTEM_ADDRESS);
|
|
|
|
// Create entity to mount
|
|
auto startRotation = m_Parent->GetRotation();
|
|
|
|
EntityInfo info{};
|
|
info.lot = item->GetLot();
|
|
info.pos = m_Parent->GetPosition();
|
|
info.rot = startRotation;
|
|
info.spawnerID = m_Parent->GetObjectID();
|
|
|
|
auto* mount = Game::entityManager->CreateEntity(info, nullptr, m_Parent);
|
|
|
|
// Check to see if the mount is a vehicle, if so, flip it
|
|
auto* vehicleComponent = mount->GetComponent<HavokVehiclePhysicsComponent>();
|
|
if (vehicleComponent) characterComponent->SetIsRacing(true);
|
|
|
|
// Setup the destroyable stats
|
|
auto* destroyableComponent = mount->GetComponent<DestroyableComponent>();
|
|
if (destroyableComponent) {
|
|
destroyableComponent->SetIsImmune(true);
|
|
}
|
|
|
|
// Mount it
|
|
auto* possessableComponent = mount->GetComponent<PossessableComponent>();
|
|
if (possessableComponent) {
|
|
possessableComponent->SetIsItemSpawned(true);
|
|
possessableComponent->SetPossessor(m_Parent->GetObjectID());
|
|
// Possess it
|
|
possessorComponent->SetPossessable(mount->GetObjectID());
|
|
possessorComponent->SetPossessableType(possessableComponent->GetPossessionType());
|
|
}
|
|
|
|
GameMessages::SendSetJetPackMode(m_Parent, false);
|
|
|
|
// Make it go to the client
|
|
Game::entityManager->ConstructEntity(mount);
|
|
// Update the possessor
|
|
Game::entityManager->SerializeEntity(m_Parent);
|
|
|
|
// have to unlock the input so it vehicle can be driven
|
|
if (vehicleComponent) GameMessages::SendVehicleUnlockInput(mount->GetObjectID(), false, m_Parent->GetSystemAddress());
|
|
GameMessages::SendMarkInventoryItemAsActive(m_Parent->GetObjectID(), true, eUnequippableActiveType::MOUNT, item->GetId(), m_Parent->GetSystemAddress());
|
|
}
|
|
|
|
void InventoryComponent::ApplyBuff(Item* item) const {
|
|
const auto buffs = FindBuffs(item, true);
|
|
|
|
for (const auto buff : buffs) {
|
|
SkillComponent::HandleUnmanaged(buff, m_Parent->GetObjectID());
|
|
}
|
|
}
|
|
|
|
// TODO Something needs to send the remove buff GameMessage as well when it is unequipping items that would remove buffs.
|
|
void InventoryComponent::RemoveBuff(Item* item) const {
|
|
const auto buffs = FindBuffs(item, false);
|
|
|
|
for (const auto buff : buffs) {
|
|
SkillComponent::HandleUnCast(buff, m_Parent->GetObjectID());
|
|
}
|
|
}
|
|
|
|
void InventoryComponent::PushEquippedItems() {
|
|
m_Pushed = m_Equipped;
|
|
|
|
m_Dirty = true;
|
|
}
|
|
|
|
void InventoryComponent::PopEquippedItems() {
|
|
auto current = m_Equipped;
|
|
|
|
for (const auto& pair : current) {
|
|
auto* const item = FindItemById(pair.second.id);
|
|
|
|
if (item == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
item->UnEquip();
|
|
}
|
|
|
|
for (const auto& pair : m_Pushed) {
|
|
auto* const item = FindItemById(pair.second.id);
|
|
|
|
if (item == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
item->Equip();
|
|
}
|
|
|
|
m_Pushed.clear();
|
|
|
|
auto destroyableComponent = m_Parent->GetComponent<DestroyableComponent>();
|
|
|
|
// Reset stats to full
|
|
if (destroyableComponent) {
|
|
destroyableComponent->SetHealth(static_cast<int32_t>(destroyableComponent->GetMaxHealth()));
|
|
destroyableComponent->SetArmor(static_cast<int32_t>(destroyableComponent->GetMaxArmor()));
|
|
destroyableComponent->SetImagination(static_cast<int32_t>(destroyableComponent->GetMaxImagination()));
|
|
Game::entityManager->SerializeEntity(m_Parent);
|
|
}
|
|
|
|
m_Dirty = true;
|
|
}
|
|
|
|
|
|
bool InventoryComponent::IsEquipped(const LOT lot) const {
|
|
for (const auto& pair : m_Equipped) {
|
|
if (pair.second.lot == lot) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void InventoryComponent::CheckItemSet(const LOT lot) {
|
|
// Check if the lot is in the item set cache
|
|
if (std::find(m_ItemSetsChecked.begin(), m_ItemSetsChecked.end(), lot) != m_ItemSetsChecked.end()) {
|
|
return;
|
|
}
|
|
|
|
const std::string lot_query = "%" + std::to_string(lot) + "%";
|
|
|
|
auto query = CDClientDatabase::CreatePreppedStmt(
|
|
"SELECT setID FROM ItemSets WHERE itemIDs LIKE ?;");
|
|
query.bind(1, lot_query.c_str());
|
|
|
|
auto result = query.execQuery();
|
|
|
|
while (!result.eof()) {
|
|
const auto id = result.getIntField("setID");
|
|
|
|
bool found = false;
|
|
|
|
// Check if we have the set already
|
|
for (auto* itemset : m_Itemsets) {
|
|
if (itemset->GetID() == id) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
auto* set = new ItemSet(id, this);
|
|
|
|
m_Itemsets.push_back(set);
|
|
}
|
|
|
|
result.nextRow();
|
|
}
|
|
|
|
m_ItemSetsChecked.push_back(lot);
|
|
|
|
result.finalize();
|
|
}
|
|
|
|
void InventoryComponent::SetConsumable(LOT lot) {
|
|
m_Consumable = lot;
|
|
}
|
|
|
|
LOT InventoryComponent::GetConsumable() const {
|
|
return m_Consumable;
|
|
}
|
|
|
|
void InventoryComponent::AddItemSkills(const LOT lot) {
|
|
const auto info = Inventory::FindItemComponent(lot);
|
|
|
|
const auto slot = FindBehaviorSlot(static_cast<eItemType>(info.itemType));
|
|
|
|
if (slot == BehaviorSlot::Invalid) {
|
|
return;
|
|
}
|
|
|
|
const auto index = m_Skills.find(slot);
|
|
|
|
const auto skill = FindSkill(lot);
|
|
|
|
SetSkill(slot, skill);
|
|
}
|
|
|
|
void InventoryComponent::RemoveItemSkills(const LOT lot) {
|
|
const auto info = Inventory::FindItemComponent(lot);
|
|
|
|
const auto slot = FindBehaviorSlot(static_cast<eItemType>(info.itemType));
|
|
|
|
if (slot == BehaviorSlot::Invalid) {
|
|
return;
|
|
}
|
|
|
|
const auto index = m_Skills.find(slot);
|
|
|
|
if (index == m_Skills.end()) {
|
|
return;
|
|
}
|
|
|
|
const auto old = index->second;
|
|
|
|
GameMessages::SendRemoveSkill(m_Parent, old);
|
|
|
|
m_Skills.erase(slot);
|
|
|
|
if (slot == BehaviorSlot::Primary) {
|
|
m_Skills.insert_or_assign(BehaviorSlot::Primary, 1);
|
|
|
|
GameMessages::SendAddSkill(m_Parent, 1, BehaviorSlot::Primary);
|
|
}
|
|
}
|
|
|
|
void InventoryComponent::TriggerPassiveAbility(PassiveAbilityTrigger trigger, Entity* target) {
|
|
for (auto* set : m_Itemsets) {
|
|
set->TriggerPassiveAbility(trigger, target);
|
|
}
|
|
}
|
|
|
|
bool InventoryComponent::HasAnyPassive(const std::vector<eItemSetPassiveAbilityID>& passiveIDs, int32_t equipmentRequirement) const {
|
|
for (auto* set : m_Itemsets) {
|
|
if (set->GetEquippedCount() < equipmentRequirement) {
|
|
continue;
|
|
}
|
|
|
|
// Check if the set has any of the passive abilities
|
|
if (std::find(passiveIDs.begin(), passiveIDs.end(), static_cast<eItemSetPassiveAbilityID>(set->GetID())) != passiveIDs.end()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void InventoryComponent::DespawnPet() {
|
|
auto* current = PetComponent::GetActivePet(m_Parent->GetObjectID());
|
|
|
|
if (current != nullptr) {
|
|
current->Deactivate();
|
|
}
|
|
}
|
|
|
|
void InventoryComponent::SpawnPet(Item* item) {
|
|
auto* current = PetComponent::GetActivePet(m_Parent->GetObjectID());
|
|
|
|
if (current != nullptr) {
|
|
current->Deactivate();
|
|
|
|
if (current->GetDatabaseId() == item->GetSubKey()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// First check if we can summon the pet. You need 1 imagination to do so.
|
|
auto destroyableComponent = m_Parent->GetComponent<DestroyableComponent>();
|
|
|
|
if (Game::config->GetValue("pets_take_imagination") == "1" && destroyableComponent && destroyableComponent->GetImagination() <= 0) {
|
|
GameMessages::SendUseItemRequirementsResponse(m_Parent->GetObjectID(), m_Parent->GetSystemAddress(), eUseItemResponse::NoImaginationForPet);
|
|
return;
|
|
}
|
|
|
|
EntityInfo info{};
|
|
info.lot = item->GetLot();
|
|
info.pos = m_Parent->GetPosition();
|
|
info.rot = NiQuaternionConstant::IDENTITY;
|
|
info.spawnerID = m_Parent->GetObjectID();
|
|
|
|
auto* pet = Game::entityManager->CreateEntity(info);
|
|
|
|
auto* petComponent = pet->GetComponent<PetComponent>();
|
|
|
|
if (petComponent != nullptr) {
|
|
petComponent->Activate(item);
|
|
}
|
|
|
|
Game::entityManager->ConstructEntity(pet);
|
|
}
|
|
|
|
void InventoryComponent::SetDatabasePet(LWOOBJID id, const DatabasePet& data) {
|
|
m_Pets.insert_or_assign(id, data);
|
|
}
|
|
|
|
const DatabasePet& InventoryComponent::GetDatabasePet(LWOOBJID id) const {
|
|
const auto& pair = m_Pets.find(id);
|
|
|
|
if (pair == m_Pets.end()) return DATABASE_PET_INVALID;
|
|
|
|
return pair->second;
|
|
}
|
|
|
|
bool InventoryComponent::IsPet(LWOOBJID id) const {
|
|
const auto& pair = m_Pets.find(id);
|
|
|
|
return pair != m_Pets.end();
|
|
}
|
|
|
|
void InventoryComponent::RemoveDatabasePet(LWOOBJID id) {
|
|
m_Pets.erase(id);
|
|
}
|
|
|
|
BehaviorSlot InventoryComponent::FindBehaviorSlot(const eItemType type) {
|
|
switch (type) {
|
|
case eItemType::HAT:
|
|
return BehaviorSlot::Head;
|
|
case eItemType::NECK:
|
|
return BehaviorSlot::Neck;
|
|
case eItemType::LEFT_HAND:
|
|
return BehaviorSlot::Offhand;
|
|
case eItemType::RIGHT_HAND:
|
|
return BehaviorSlot::Primary;
|
|
case eItemType::CONSUMABLE:
|
|
return BehaviorSlot::Consumable;
|
|
default:
|
|
return BehaviorSlot::Invalid;
|
|
}
|
|
}
|
|
|
|
bool InventoryComponent::IsTransferInventory(eInventoryType type) {
|
|
return type == VENDOR_BUYBACK || type == VAULT_ITEMS || type == VAULT_MODELS || type == TEMP_ITEMS || type == TEMP_MODELS || type == MODELS_IN_BBB;
|
|
}
|
|
|
|
uint32_t InventoryComponent::FindSkill(const LOT lot) {
|
|
auto* table = CDClientManager::GetTable<CDObjectSkillsTable>();
|
|
|
|
const auto results = table->Query([=](const CDObjectSkills& entry) {
|
|
return entry.objectTemplate == static_cast<unsigned int>(lot);
|
|
});
|
|
|
|
for (const auto& result : results) {
|
|
if (result.castOnType == 0) {
|
|
return result.skillID;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
std::vector<uint32_t> InventoryComponent::FindBuffs(Item* item, bool castOnEquip) const {
|
|
std::vector<uint32_t> buffs;
|
|
if (item == nullptr) return buffs;
|
|
auto* table = CDClientManager::GetTable<CDObjectSkillsTable>();
|
|
auto* behaviors = CDClientManager::GetTable<CDSkillBehaviorTable>();
|
|
|
|
const auto results = table->Query([=](const CDObjectSkills& entry) {
|
|
return entry.objectTemplate == static_cast<unsigned int>(item->GetLot());
|
|
});
|
|
|
|
auto* missions = static_cast<MissionComponent*>(m_Parent->GetComponent(eReplicaComponentType::MISSION));
|
|
|
|
for (const auto& result : results) {
|
|
if (result.castOnType == 1) {
|
|
const auto entry = behaviors->GetSkillByID(result.skillID);
|
|
|
|
if (entry.skillID == 0) {
|
|
LOG("Failed to find buff behavior for skill (%i)!", result.skillID);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (missions != nullptr && castOnEquip) {
|
|
missions->Progress(eMissionTaskType::USE_SKILL, result.skillID);
|
|
}
|
|
|
|
// If item is not a proxy, add its buff to the added buffs.
|
|
if (item->GetParent() == LWOOBJID_EMPTY) buffs.push_back(static_cast<uint32_t>(entry.behaviorID));
|
|
}
|
|
}
|
|
|
|
return buffs;
|
|
}
|
|
|
|
void InventoryComponent::SetNPCItems(const std::vector<LOT>& items) {
|
|
m_Equipped.clear();
|
|
|
|
auto slot = 0u;
|
|
|
|
for (const auto& item : items) {
|
|
const LWOOBJID id = ObjectIDManager::GenerateObjectID();
|
|
|
|
const auto& info = Inventory::FindItemComponent(item);
|
|
|
|
UpdateSlot(info.equipLocation, { id, static_cast<LOT>(item), 1, slot++ }, true);
|
|
}
|
|
|
|
Game::entityManager->SerializeEntity(m_Parent);
|
|
}
|
|
|
|
InventoryComponent::~InventoryComponent() {
|
|
for (const auto& inventory : m_Inventories) {
|
|
delete inventory.second;
|
|
}
|
|
|
|
m_Inventories.clear();
|
|
|
|
for (auto* set : m_Itemsets) {
|
|
delete set;
|
|
}
|
|
|
|
m_Itemsets.clear();
|
|
m_Pets.clear();
|
|
}
|
|
|
|
std::vector<Item*> InventoryComponent::GenerateProxies(Item* parent) {
|
|
std::vector<Item*> proxies;
|
|
|
|
auto subItems = parent->GetInfo().subItems;
|
|
|
|
if (subItems.empty()) {
|
|
return proxies;
|
|
}
|
|
|
|
subItems.erase(std::remove_if(subItems.begin(), subItems.end(), ::isspace), subItems.end());
|
|
|
|
std::stringstream stream(subItems);
|
|
std::string segment;
|
|
std::vector<uint32_t> lots;
|
|
|
|
while (std::getline(stream, segment, ',')) {
|
|
try {
|
|
lots.push_back(std::stoi(segment));
|
|
} catch (std::invalid_argument& exception) {
|
|
LOG("Failed to parse proxy (%s): (%s)!", segment.c_str(), exception.what());
|
|
}
|
|
}
|
|
|
|
for (const auto lot : lots) {
|
|
if (!Inventory::IsValidItem(lot)) {
|
|
continue;
|
|
}
|
|
|
|
auto* inventory = GetInventory(ITEM_SETS);
|
|
|
|
auto* proxy = new Item(lot, inventory, inventory->FindEmptySlot(), 1, {}, parent->GetId(), false);
|
|
|
|
EquipItem(proxy);
|
|
|
|
proxies.push_back(proxy);
|
|
}
|
|
|
|
return proxies;
|
|
}
|
|
|
|
std::vector<Item*> InventoryComponent::FindProxies(const LWOOBJID parent) {
|
|
auto* inventory = GetInventory(ITEM_SETS);
|
|
|
|
std::vector<Item*> proxies;
|
|
|
|
for (const auto& pair : inventory->GetItems()) {
|
|
auto* item = pair.second;
|
|
|
|
if (item->GetParent() == parent) {
|
|
proxies.push_back(item);
|
|
}
|
|
}
|
|
|
|
return proxies;
|
|
}
|
|
|
|
bool InventoryComponent::IsValidProxy(const LWOOBJID parent) {
|
|
for (const auto& pair : m_Inventories) {
|
|
const auto items = pair.second->GetItems();
|
|
|
|
for (const auto& candidate : items) {
|
|
auto* item = candidate.second;
|
|
|
|
if (item->GetId() == parent) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool InventoryComponent::IsParentValid(Item* root) {
|
|
if (root->GetInfo().subItems.empty()) {
|
|
return true;
|
|
}
|
|
|
|
const auto id = root->GetId();
|
|
|
|
for (const auto& pair : m_Inventories) {
|
|
const auto items = pair.second->GetItems();
|
|
|
|
for (const auto& candidate : items) {
|
|
auto* item = candidate.second;
|
|
|
|
if (item->GetParent() == id) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void InventoryComponent::CheckProxyIntegrity() {
|
|
std::vector<Item*> dead;
|
|
|
|
for (const auto& pair : m_Inventories) {
|
|
const auto& items = pair.second->GetItems();
|
|
|
|
for (const auto& candidate : items) {
|
|
auto* item = candidate.second;
|
|
|
|
const auto parent = item->GetParent();
|
|
|
|
if (parent == LWOOBJID_EMPTY) {
|
|
continue;
|
|
}
|
|
|
|
if (IsValidProxy(parent)) {
|
|
continue;
|
|
}
|
|
|
|
dead.push_back(item);
|
|
}
|
|
}
|
|
|
|
for (auto* item : dead) {
|
|
item->RemoveFromInventory();
|
|
}
|
|
|
|
dead.clear();
|
|
|
|
/*
|
|
for (const auto& pair : inventories)
|
|
{
|
|
const auto& items = pair.second->GetItems();
|
|
|
|
for (const auto& candidate : items)
|
|
{
|
|
auto* item = candidate.second;
|
|
|
|
const auto parent = item->GetParent();
|
|
|
|
if (parent != LWOOBJID_EMPTY)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!item->IsEquipped())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (IsParentValid(item))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
dead.push_back(item);
|
|
}
|
|
}
|
|
|
|
for (auto* item : dead)
|
|
{
|
|
item->RemoveFromInventory();
|
|
}
|
|
*/
|
|
}
|
|
|
|
void InventoryComponent::PurgeProxies(Item* item) {
|
|
const auto root = item->GetParent();
|
|
|
|
if (root != LWOOBJID_EMPTY) {
|
|
Item* itemRoot = FindItemById(root);
|
|
|
|
if (itemRoot != nullptr) {
|
|
UnEquipItem(itemRoot);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
auto proxies = FindProxies(item->GetId());
|
|
|
|
for (auto* proxy : proxies) {
|
|
proxy->UnEquip();
|
|
|
|
proxy->RemoveFromInventory();
|
|
}
|
|
}
|
|
|
|
void InventoryComponent::LoadPetXml(const tinyxml2::XMLDocument& document) {
|
|
auto* petInventoryElement = document.FirstChildElement("obj")->FirstChildElement("pet");
|
|
|
|
if (petInventoryElement == nullptr) {
|
|
m_Pets.clear();
|
|
|
|
return;
|
|
}
|
|
|
|
auto* petElement = petInventoryElement->FirstChildElement();
|
|
|
|
while (petElement != nullptr) {
|
|
LWOOBJID id;
|
|
LOT lot;
|
|
int32_t moderationStatus;
|
|
|
|
petElement->QueryAttribute("id", &id);
|
|
petElement->QueryAttribute("l", &lot);
|
|
petElement->QueryAttribute("m", &moderationStatus);
|
|
const char* name = petElement->Attribute("n");
|
|
|
|
DatabasePet databasePet;
|
|
databasePet.lot = lot;
|
|
databasePet.moderationState = moderationStatus;
|
|
databasePet.name = std::string(name);
|
|
|
|
SetDatabasePet(id, databasePet);
|
|
|
|
petElement = petElement->NextSiblingElement();
|
|
}
|
|
}
|
|
|
|
void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument& document) {
|
|
auto* petInventoryElement = document.FirstChildElement("obj")->FirstChildElement("pet");
|
|
|
|
if (petInventoryElement == nullptr) {
|
|
petInventoryElement = document.NewElement("pet");
|
|
|
|
document.FirstChildElement("obj")->LinkEndChild(petInventoryElement);
|
|
}
|
|
|
|
petInventoryElement->DeleteChildren();
|
|
|
|
for (const auto& pet : m_Pets) {
|
|
auto* petElement = document.NewElement("p");
|
|
|
|
petElement->SetAttribute("id", pet.first);
|
|
petElement->SetAttribute("l", pet.second.lot);
|
|
petElement->SetAttribute("m", pet.second.moderationState);
|
|
petElement->SetAttribute("n", pet.second.name.c_str());
|
|
petElement->SetAttribute("t", 0);
|
|
|
|
petInventoryElement->LinkEndChild(petElement);
|
|
}
|
|
}
|
|
|
|
|
|
bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId) {
|
|
BehaviorSlot behaviorSlot = BehaviorSlot::Invalid;
|
|
if (slot == 1) behaviorSlot = BehaviorSlot::Primary;
|
|
else if (slot == 2) behaviorSlot = BehaviorSlot::Offhand;
|
|
else if (slot == 3) behaviorSlot = BehaviorSlot::Neck;
|
|
else if (slot == 4) behaviorSlot = BehaviorSlot::Head;
|
|
else if (slot == 5) behaviorSlot = BehaviorSlot::Consumable;
|
|
else return false;
|
|
return SetSkill(behaviorSlot, skillId);
|
|
}
|
|
|
|
bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId) {
|
|
if (skillId == 0) return false;
|
|
const auto index = m_Skills.find(slot);
|
|
if (index != m_Skills.end()) {
|
|
const auto old = index->second;
|
|
GameMessages::SendRemoveSkill(m_Parent, old);
|
|
}
|
|
|
|
GameMessages::SendAddSkill(m_Parent, skillId, slot);
|
|
m_Skills.insert_or_assign(slot, skillId);
|
|
return true;
|
|
}
|
|
|
|
void InventoryComponent::UpdateGroup(const GroupUpdate& groupUpdate) {
|
|
if (groupUpdate.groupId.empty()) return;
|
|
|
|
if (groupUpdate.inventory != eInventoryType::BRICKS && groupUpdate.inventory != eInventoryType::MODELS) {
|
|
LOG("Invalid inventory type for grouping %s", StringifiedEnum::ToString(groupUpdate.inventory).data());
|
|
return;
|
|
}
|
|
|
|
auto& groups = m_Groups[groupUpdate.inventory];
|
|
auto groupItr = std::ranges::find_if(groups, [&groupUpdate](const Group& group) {
|
|
return group.groupId == groupUpdate.groupId;
|
|
});
|
|
|
|
if (groupUpdate.command != GroupUpdateCommand::ADD && groupItr == groups.end()) {
|
|
LOG("Group %s not found in inventory %s. Cannot process command.", groupUpdate.groupId.c_str(), StringifiedEnum::ToString(groupUpdate.inventory).data());
|
|
return;
|
|
}
|
|
|
|
if (groupUpdate.command == GroupUpdateCommand::ADD && groups.size() >= MaximumGroupCount) {
|
|
LOG("Cannot add group to inventory %s. Maximum group count reached.", StringifiedEnum::ToString(groupUpdate.inventory).data());
|
|
return;
|
|
}
|
|
|
|
switch (groupUpdate.command) {
|
|
case GroupUpdateCommand::ADD: {
|
|
auto& group = groups.emplace_back();
|
|
group.groupId = groupUpdate.groupId;
|
|
group.groupName = groupUpdate.groupName;
|
|
break;
|
|
}
|
|
case GroupUpdateCommand::ADD_LOT: {
|
|
groupItr->lots.insert(groupUpdate.lot);
|
|
break;
|
|
}
|
|
case GroupUpdateCommand::REMOVE: {
|
|
groups.erase(groupItr);
|
|
break;
|
|
}
|
|
case GroupUpdateCommand::REMOVE_LOT: {
|
|
groupItr->lots.erase(groupUpdate.lot);
|
|
break;
|
|
}
|
|
case GroupUpdateCommand::MODIFY: {
|
|
groupItr->groupName = groupUpdate.groupName;
|
|
break;
|
|
}
|
|
default: {
|
|
LOG("Invalid group update command %i", groupUpdate.command);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void InventoryComponent::UpdateGroupXml(tinyxml2::XMLElement& groups) const {
|
|
for (const auto& [inventory, groupsData] : m_Groups) {
|
|
for (const auto& group : groupsData) {
|
|
auto* const groupElement = groups.InsertNewChildElement("grp");
|
|
|
|
groupElement->SetAttribute("id", group.groupId.c_str());
|
|
groupElement->SetAttribute("n", group.groupName.c_str());
|
|
groupElement->SetAttribute("t", static_cast<uint32_t>(inventory));
|
|
groupElement->SetAttribute("u", 0);
|
|
std::ostringstream lots;
|
|
bool first = true;
|
|
for (const auto lot : group.lots) {
|
|
if (!first) lots << ' ';
|
|
first = false;
|
|
|
|
lots << lot;
|
|
}
|
|
groupElement->SetAttribute("l", lots.str().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void InventoryComponent::LoadGroupXml(const tinyxml2::XMLElement& groups) {
|
|
auto* groupElement = groups.FirstChildElement("grp");
|
|
|
|
while (groupElement) {
|
|
const char* groupId = nullptr;
|
|
const char* groupName = nullptr;
|
|
const char* lots = nullptr;
|
|
uint32_t inventory = eInventoryType::INVALID;
|
|
|
|
groupElement->QueryStringAttribute("id", &groupId);
|
|
groupElement->QueryStringAttribute("n", &groupName);
|
|
groupElement->QueryStringAttribute("l", &lots);
|
|
groupElement->QueryAttribute("t", &inventory);
|
|
|
|
if (!groupId || !groupName || !lots) {
|
|
LOG("Failed to load group from xml id %i name %i lots %i",
|
|
groupId == nullptr, groupName == nullptr, lots == nullptr);
|
|
} else {
|
|
auto& group = m_Groups[static_cast<eInventoryType>(inventory)].emplace_back();
|
|
group.groupId = groupId;
|
|
group.groupName = groupName;
|
|
|
|
for (const auto& lotStr : GeneralUtils::SplitString(lots, ' ')) {
|
|
auto lot = GeneralUtils::TryParse<LOT>(lotStr);
|
|
if (lot) group.lots.insert(*lot);
|
|
}
|
|
}
|
|
|
|
groupElement = groupElement->NextSiblingElement("grp");
|
|
}
|
|
}
|