DarkflameServer/dGame/dComponents/InventoryComponent.cpp
Aaron Kimbre ec207838d4 Proper Rocket Holding
Sanity checks on Prop and LUP launchpads to not open if no valid rocket
Add serialization for sending item configs
so that rockets show for other players
2022-05-08 19:57:36 -05:00

1791 lines
39 KiB
C++

#include "InventoryComponent.h"
#include <sstream>
#include "Entity.h"
#include "Item.h"
#include "Game.h"
#include "dLogger.h"
#include "CDClientManager.h"
#include "../dWorldServer/ObjectIDManager.h"
#include "MissionComponent.h"
#include "GameMessages.h"
#include "SkillComponent.h"
#include "Character.h"
#include "EntityManager.h"
#include "ItemSet.h"
#include "Player.h"
#include "PetComponent.h"
#include "PossessorComponent.h"
#include "PossessableComponent.h"
#include "ModuleAssemblyComponent.h"
#include "VehiclePhysicsComponent.h"
#include "CharacterComponent.h"
#include "dZoneManager.h"
#include "PropertyManagementComponent.h"
#include "DestroyableComponent.h"
InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document) : 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)
{
LoadXml(document);
CheckProxyIntegrity();
return;
}
auto* compRegistryTable = CDClientManager::Instance()->GetTable<CDComponentsRegistryTable>("ComponentsRegistry");
const auto componentId = compRegistryTable->GetByIDAndType(lot, COMPONENT_TYPE_INVENTORY);
auto* inventoryComponentTable = CDClientManager::Instance()->GetTable<CDInventoryComponentTable>("InventoryComponent");
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::Instance()->GenerateObjectID();
const auto& info = Inventory::FindItemComponent(item.itemid);
UpdateSlot(info.equipLocation, { id, static_cast<LOT>(item.itemid), 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;
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)
{
Game::logger->Log("InventoryComponent", "Attempted to add 0 of item (%i) to the inventory!\n", lot);
return;
}
if (!Inventory::IsValidItem(lot))
{
if (lot > 0)
{
Game::logger->Log("InventoryComponent", "Attempted to add invalid item (%i) to the inventory!\n", lot);
}
return;
}
if (inventoryType == INVALID)
{
inventoryType = Inventory::FindInventoryTypeForLot(lot);
}
auto* missions = static_cast<MissionComponent*>(this->m_Parent->GetComponent(COMPONENT_TYPE_MISSION));
auto* inventory = GetInventory(inventoryType);
if (!config.empty() || bound)
{
const auto slot = preferredSlot != -1 && inventory->IsSlotEmpty(preferredSlot) ? preferredSlot : inventory->FindEmptySlot();
if (slot == -1)
{
Game::logger->Log("InventoryComponent", "Failed to find empty slot for inventory (%i)!\n", inventoryType);
return;
}
auto* item = new Item(lot, inventory, slot, count, config, parent, showFlyingLoot, isModMoveAndEquip, subKey, bound, lootSourceType);
if (missions != nullptr && !IsTransferInventory(inventoryType))
{
missions->Progress(MissionTaskType::MISSION_TASK_TYPE_ITEM_COLLECTION, 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);
if (inventoryType == eInventoryType::BRICKS)
{
stack = 999;
}
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;
}
}
while (left > 0)
{
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)
{
auto* player = dynamic_cast<Player*>(GetParent());
if (player == nullptr)
{
return;
}
outOfSpace += size;
switch (sourceType)
{
case 0:
player->SendMail(LWOOBJID_EMPTY, "Darkflame Universe", "Lost Reward", "You received an item and didn&apos;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(MissionTaskType::MISSION_TASK_TYPE_ITEM_COLLECTION, lot, LWOOBJID_EMPTY, "", count - outOfSpace, IsTransferInventory(inventorySourceType));
}
}
void InventoryComponent::RemoveItem(const LOT lot, const uint32_t count, eInventoryType inventoryType, const bool ignoreBound)
{
if (count == 0)
{
Game::logger->Log("InventoryComponent", "Attempted to remove 0 of item (%i) from the inventory!\n", lot);
return;
}
if (inventoryType == INVALID)
{
inventoryType = Inventory::FindInventoryTypeForLot(lot);
}
auto* inventory = GetInventory(inventoryType);
if (inventory == nullptr)
{
return;
}
auto left = std::min<uint32_t>(count, inventory->GetLotCount(lot));
while (left > 0)
{
auto* item = FindItemByLot(lot, inventoryType, false, ignoreBound);
if (item == nullptr)
{
break;
}
const auto delta = std::min<uint32_t>(left, item->GetCount());
item->SetCount(item->GetCount() - delta);
left -= delta;
}
}
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();
if (item->GetConfig().empty() && !item->GetBound() || (item->GetBound() && item->GetInfo().isBOP))
{
auto left = std::min<uint32_t>(count, origin->GetLotCount(lot));
while (left > 0)
{
item = origin->FindItemByLot(lot, ignoreEquipped);
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::LOOT_SOURCE_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::LOOT_SOURCE_NONE, inventory, config, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, LWOOBJID_EMPTY, 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(MissionTaskType::MISSION_TASK_TYPE_ITEM_COLLECTION, 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(tinyxml2::XMLDocument* document)
{
LoadPetXml(document);
auto* inventoryElement = document->FirstChildElement("obj")->FirstChildElement("inv");
if (inventoryElement == nullptr)
{
Game::logger->Log("InventoryComponent", "Failed to find 'inv' xml element!\n");
return;
}
auto* bags = inventoryElement->FirstChildElement("bag");
if (bags == nullptr)
{
Game::logger->Log("InventoryComponent", "Failed to find 'bags' xml element!\n");
return;
}
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)
{
Game::logger->Log("InventoryComponent", "Failed to find 'items' xml element!\n");
return;
}
bag = items->FirstChildElement();
while (bag != nullptr)
{
unsigned int type;
bag->QueryAttribute("t", &type);
auto* inventory = GetInventory(static_cast<eInventoryType>(type));
if (inventory == nullptr)
{
Game::logger->Log("InventoryComponent", "Failed to find inventory (%i)!\n", 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
std::vector<LDFBaseData*> config;
auto* extraInfo = itemElement->FirstChildElement("x");
if (extraInfo)
{
std::string modInfo = extraInfo->Attribute("ma");
LDFBaseData* moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(modInfo.substr(2, modInfo.size() - 1)));
config.push_back(moduleAssembly);
}
const auto* item = new Item(id, lot, inventory, slot, count, bound, config, parent, subKey);
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)
{
Game::logger->Log("InventoryComponent", "Failed to find 'inv' xml element!\n");
return;
}
std::vector<Inventory*> inventories;
for (const auto& pair : this->m_Inventories)
{
auto* inventory = pair.second;
if (inventory->GetType() == VENDOR_BUYBACK)
{
continue;
}
inventories.push_back(inventory);
}
inventoryElement->SetAttribute("csl", m_Consumable);
auto* bags = inventoryElement->FirstChildElement("bag");
if (bags == nullptr)
{
Game::logger->Log("InventoryComponent", "Failed to find 'bags' xml element!\n");
return;
}
bags->DeleteChildren();
for (const auto* inventory : inventories)
{
auto* bag = document->NewElement("b");
bag->SetAttribute("t", inventory->GetType());
bag->SetAttribute("m", static_cast<unsigned int>(inventory->GetSize()));
bags->LinkEndChild(bag);
}
auto* items = inventoryElement->FirstChildElement("items");
if (items == nullptr)
{
Game::logger->Log("InventoryComponent", "Failed to find 'items' xml element!\n");
return;
}
items->DeleteChildren();
for (auto* inventory : inventories)
{
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
for (auto* data : item->GetConfig())
{
if (data->GetKey() != u"assemblyPartLOTs")
{
continue;
}
auto* extraInfo = document->NewElement("x");
extraInfo->SetAttribute("ma", data->GetString(false).c_str());
itemElement->LinkEndChild(extraInfo);
}
bagElement->LinkEndChild(itemElement);
}
items->LinkEndChild(bagElement);
}
}
void InventoryComponent::Serialize(RakNet::BitStream* outBitStream, const bool bIsInitialUpdate, unsigned& flags)
{
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::ResetFlags()
{
m_Dirty = 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 = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_ROCKET_LAUNCH);
const auto position = m_Parent->GetPosition();
for (auto* lauchPad : rocketLauchPads)
{
if (Vector3::DistanceSquared(lauchPad->GetPosition(), position) > 13 * 13) continue;
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
if (characterComponent != nullptr)
{
characterComponent->SetLastRocketItemID(item->GetId());
}
lauchPad->OnUse(m_Parent);
break;
}
return;
}
const auto building = character->GetBuildMode();
const auto type = static_cast<eItemType>(item->GetInfo().itemType);
if (item->GetLot() == 8092 && m_Parent->GetGMLevel() >= GAME_MASTER_LEVEL_OPERATOR && hasCarEquipped == false)
{
auto startPosition = m_Parent->GetPosition();
auto startRotation = NiQuaternion::LookAt(startPosition, startPosition + NiPoint3::UNIT_X);
auto angles = startRotation.GetEulerAngles();
angles.y -= PI;
startRotation = NiQuaternion::FromEulerAngles(angles);
GameMessages::SendTeleport(m_Parent->GetObjectID(), startPosition, startRotation, m_Parent->GetSystemAddress(), true, true);
EntityInfo info {};
info.lot = 8092;
info.pos = startPosition;
info.rot = startRotation;
info.spawnerID = m_Parent->GetObjectID();
auto* carEntity = EntityManager::Instance()->CreateEntity(info, nullptr, m_Parent);
m_Parent->AddChild(carEntity);
auto *destroyableComponent = carEntity->GetComponent<DestroyableComponent>();
// Setup the vehicle stats.
if (destroyableComponent != nullptr) {
destroyableComponent->SetIsSmashable(false);
destroyableComponent->SetIsImmune(true);
}
// #108
auto* possessableComponent = carEntity->GetComponent<PossessableComponent>();
if (possessableComponent != nullptr)
{
previousPossessableID = possessableComponent->GetPossessor();
possessableComponent->SetPossessor(m_Parent->GetObjectID());
}
auto* moduleAssemblyComponent = carEntity->GetComponent<ModuleAssemblyComponent>();
if (moduleAssemblyComponent != nullptr)
{
moduleAssemblyComponent->SetSubKey(item->GetSubKey());
moduleAssemblyComponent->SetUseOptionalParts(false);
for (auto* config : item->GetConfig())
{
if (config->GetKey() == u"assemblyPartLOTs")
{
moduleAssemblyComponent->SetAssemblyPartsLOTs(GeneralUtils::ASCIIToUTF16(config->GetValueAsString()));
}
}
}
// #107
auto* possessorComponent = m_Parent->GetComponent<PossessorComponent>();
if (possessorComponent != nullptr)
{
previousPossessorID = possessorComponent->GetPossessable();
possessorComponent->SetPossessable(carEntity->GetObjectID());
}
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
if (characterComponent != nullptr)
{
characterComponent->SetIsRacing(true);
characterComponent->SetVehicleObjectID(carEntity->GetObjectID());
}
EntityManager::Instance()->ConstructEntity(carEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
GameMessages::SendSetJetPackMode(m_Parent, false);
GameMessages::SendNotifyVehicleOfRacingObject(carEntity->GetObjectID(), m_Parent->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
GameMessages::SendRacingPlayerLoaded(LWOOBJID_EMPTY, m_Parent->GetObjectID(), carEntity->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
GameMessages::SendVehicleUnlockInput(carEntity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS);
GameMessages::SendTeleport(m_Parent->GetObjectID(), startPosition, startRotation, m_Parent->GetSystemAddress(), true, true);
GameMessages::SendTeleport(carEntity->GetObjectID(), startPosition, startRotation, m_Parent->GetSystemAddress(), true, true);
EntityManager::Instance()->SerializeEntity(m_Parent);
hasCarEquipped = true;
equippedCarEntity = carEntity;
return;
} else if (item->GetLot() == 8092 && m_Parent->GetGMLevel() >= GAME_MASTER_LEVEL_OPERATOR && hasCarEquipped == true)
{
GameMessages::SendNotifyRacingClient(LWOOBJID_EMPTY, 3, 0, LWOOBJID_EMPTY, u"", m_Parent->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
auto player = dynamic_cast<Player*>(m_Parent);
player->SendToZone(player->GetCharacter()->GetZoneID());
equippedCarEntity->Kill();
hasCarEquipped = false;
equippedCarEntity = nullptr;
return;
}
if (!building)
{
if (item->GetLot() == 6086)
{
return;
}
if (type == ITEM_TYPE_LOOT_MODEL || type == ITEM_TYPE_VEHICLE)
{
return;
}
}
if (type != ITEM_TYPE_LOOT_MODEL && type != ITEM_TYPE_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());
EntityManager::Instance()->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);
PurgeProxies(item);
EntityManager::Instance()->SerializeEntity(m_Parent);
// Trigger property event
if (PropertyManagementComponent::Instance() != nullptr && item->GetCount() > 0 && Inventory::FindInventoryTypeForLot(item->GetLot()) == MODELS)
{
PropertyManagementComponent::Instance()->GetParent()->OnZonePropertyModelRemovedWhileEquipped(m_Parent);
dZoneManager::Instance()->GetZoneControlObject()->OnZonePropertyModelRemovedWhileEquipped(m_Parent);
}
}
void InventoryComponent::ApplyBuff(Item* item) const
{
const auto buffs = FindBuffs(item, true);
for (const auto buff : buffs)
{
SkillComponent::HandleUnmanaged(buff, m_Parent->GetObjectID());
}
}
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_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;
}
std::stringstream query;
query << "SELECT setID FROM ItemSets WHERE itemIDs LIKE '%" << std::to_string(lot) << "%'";
auto result = CDClientDatabase::ExecuteQuery(query.str());
while (!result.eof())
{
const auto id = result.getIntField(0);
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);
if (skill == 0)
{
return;
}
if (index != m_Skills.end())
{
const auto old = index->second;
GameMessages::SendRemoveSkill(m_Parent, old);
}
GameMessages::SendAddSkill(m_Parent, skill, static_cast<int>(slot));
m_Skills.insert_or_assign(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, static_cast<int>(BehaviorSlot::Primary));
}
}
void InventoryComponent::TriggerPassiveAbility(PassiveAbilityTrigger trigger)
{
for (auto* set : m_Itemsets)
{
set->TriggerPassiveAbility(trigger);
}
}
bool InventoryComponent::HasAnyPassive(const std::vector<ItemSetPassiveAbilityID>& 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<ItemSetPassiveAbilityID>(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;
}
}
EntityInfo info {};
info.lot = item->GetLot();
info.pos = m_Parent->GetPosition();
info.rot = NiQuaternion::IDENTITY;
info.spawnerID = m_Parent->GetObjectID();
auto* pet = EntityManager::Instance()->CreateEntity(info);
auto* petComponent = pet->GetComponent<PetComponent>();
if (petComponent != nullptr)
{
petComponent->Activate(item);
}
EntityManager::Instance()->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 ITEM_TYPE_HAT:
return BehaviorSlot::Head;
case ITEM_TYPE_NECK:
return BehaviorSlot::Neck;
case ITEM_TYPE_LEFT_HAND:
return BehaviorSlot::Offhand;
case ITEM_TYPE_RIGHT_HAND:
return BehaviorSlot::Primary;
case ITEM_TYPE_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;
}
uint32_t InventoryComponent::FindSkill(const LOT lot)
{
auto* table = CDClientManager::Instance()->GetTable<CDObjectSkillsTable>("ObjectSkills");
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::Instance()->GetTable<CDObjectSkillsTable>("ObjectSkills");
auto* behaviors = CDClientManager::Instance()->GetTable<CDSkillBehaviorTable>("SkillBehavior");
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(COMPONENT_TYPE_MISSION));
for (const auto& result : results)
{
if (result.castOnType == 1)
{
const auto entry = behaviors->GetSkillByID(result.skillID);
if (entry.skillID == 0)
{
Game::logger->Log("InventoryComponent", "Failed to find buff behavior for skill (%i)!\n", result.skillID);
continue;
}
if (missions != nullptr && castOnEquip)
{
missions->Progress(MissionTaskType::MISSION_TASK_TYPE_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::Instance()->GenerateObjectID();
const auto& info = Inventory::FindItemComponent(item);
UpdateSlot(info.equipLocation, { id, static_cast<LOT>(item), 1, slot++ }, true);
}
EntityManager::Instance()->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)
{
Game::logger->Log("InventoryComponent", "Failed to parse proxy (%s): (%s)!\n", 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 = FindItemById(root);
if (item != nullptr)
{
UnEquipItem(item);
}
return;
}
auto proxies = FindProxies(item->GetId());
for (auto* proxy : proxies)
{
proxy->UnEquip();
proxy->RemoveFromInventory();
}
}
void InventoryComponent::LoadPetXml(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);
}
}