#include "Item.h" #include #include "ObjectIDManager.h" #include "GeneralUtils.h" #include "GameMessages.h" #include "Entity.h" #include "Game.h" #include "Logger.h" #include "EntityManager.h" #include "RenderComponent.h" #include "PossessableComponent.h" #include "CharacterComponent.h" #include "eItemType.h" #include "AssetManager.h" #include "InventoryComponent.h" #include "Loot.h" #include "eObjectBits.h" #include "eReplicaComponentType.h" #include "eUseItemResponse.h" #include "dZoneManager.h" #include "ChatPackets.h" #include "eDeletionRestrictionsCheckType.h" #include "CDBrickIDTableTable.h" #include "CDObjectSkillsTable.h" #include "CDComponentsRegistryTable.h" #include "CDPackageComponentTable.h" #include "CDDeletionRestrictionsTable.h" namespace { const std::map ExtraSettingAbbreviations = { { "assemblyPartLOTs", "ma" }, { "blueprintID", "b" }, { "userModelID", "ui" }, { "userModelName", "un" }, { "userModelDesc", "ud" }, { "userModelHasBhvr", "ub" }, { "userModelBehaviors", "ubh" }, { "userModelBehaviorSourceID", "ubs" }, { "userModelPhysicsType", "up" }, { "userModelMod", "um" }, { "userModelOpt", "uo" }, { "reforgedLOT", "rl" }, }; } Item::Item(const LWOOBJID id, const LOT lot, Inventory* inventory, const uint32_t slot, const uint32_t count, const bool bound, const std::vector& config, const LWOOBJID parent, LWOOBJID subKey, eLootSourceType lootSourceType) { if (!Inventory::IsValidItem(lot)) { return; } this->id = id; this->lot = lot; this->inventory = inventory; this->slot = slot; this->count = count; this->bound = bound; this->config = config; this->parent = parent; this->info = &Inventory::FindItemComponent(lot); this->preconditions = new PreconditionExpression(this->info->reqPrecondition); this->subKey = subKey; inventory->AddManagedItem(this); } Item::Item( const LOT lot, Inventory* inventory, const uint32_t slot, const uint32_t count, const std::vector& config, const LWOOBJID parent, bool showFlyingLoot, bool isModMoveAndEquip, LWOOBJID subKey, bool bound, eLootSourceType lootSourceType) { if (!Inventory::IsValidItem(lot)) { return; } if (isModMoveAndEquip) { showFlyingLoot = false; } this->lot = lot; this->inventory = inventory; this->slot = slot; this->count = count; this->config = config; this->parent = parent; this->id = LWOOBJID_EMPTY; this->info = &Inventory::FindItemComponent(lot); this->bound = info->isBOP || bound; this->preconditions = new PreconditionExpression(this->info->reqPrecondition); this->subKey = subKey; LWOOBJID id = ObjectIDManager::GenerateRandomObjectID(); GeneralUtils::SetBit(id, eObjectBits::CHARACTER); GeneralUtils::SetBit(id, eObjectBits::PERSISTENT); const auto type = static_cast(info->itemType); if (type == eItemType::MOUNT) { GeneralUtils::SetBit(id, eObjectBits::CLIENT); } this->id = id; inventory->AddManagedItem(this); auto* entity = inventory->GetComponent()->GetParent(); GameMessages::SendAddItemToInventoryClientSync(entity, entity->GetSystemAddress(), this, id, showFlyingLoot, static_cast(this->count), subKey, lootSourceType); if (isModMoveAndEquip) { Equip(); LOG("Move and equipped (%i) from (%i)", this->lot, this->inventory->GetType()); Game::entityManager->SerializeEntity(inventory->GetComponent()->GetParent()); } } LWOOBJID Item::GetId() const { return id; } LOT Item::GetLot() const { return lot; } uint32_t Item::GetCount() const { return count; } uint32_t Item::GetSlot() const { return slot; } std::vector Item::GetConfig() const { return config; } std::vector& Item::GetConfig() { return config; } const CDItemComponent& Item::GetInfo() const { return *info; } bool Item::GetBound() const { return bound; } Inventory* Item::GetInventory() const { return inventory; } LWOOBJID Item::GetParent() const { return parent; } LWOOBJID Item::GetSubKey() const { return subKey; } PreconditionExpression* Item::GetPreconditionExpression() const { return preconditions; } void Item::SetCount(const uint32_t value, const bool silent, const bool disassemble, const bool showFlyingLoot, eLootSourceType lootSourceType) { if (value == count) { return; } const auto delta = std::abs(static_cast(value) - static_cast(count)); const auto type = static_cast(info->itemType); if (disassemble) { if (value < count) { for (auto i = 0; i < delta; ++i) { Disassemble(); } } } if (!silent) { auto* entity = inventory->GetComponent()->GetParent(); if (value > count) { GameMessages::SendAddItemToInventoryClientSync(entity, entity->GetSystemAddress(), this, id, showFlyingLoot, delta, LWOOBJID_EMPTY, lootSourceType); } else { GameMessages::SendRemoveItemFromInventory(entity, entity->GetSystemAddress(), id, lot, inventory->GetType(), delta, value); } } count = value; if (count == 0) { RemoveFromInventory(); } } void Item::SetSlot(const uint32_t value) { if (slot == value) { return; } for (const auto& pair : inventory->GetItems()) { auto* item = pair.second; if (item->slot == value) { item->slot = slot; } } slot = value; } void Item::SetBound(const bool value) { bound = value; } void Item::SetSubKey(LWOOBJID value) { subKey = value; } void Item::SetInventory(Inventory* value) { inventory->RemoveManagedItem(this); inventory = value; inventory->AddManagedItem(this); } void Item::Equip(const bool skipChecks) { if (IsEquipped()) { return; } inventory->GetComponent()->EquipItem(this, skipChecks); } void Item::UnEquip() { if (!IsEquipped()) { return; } inventory->GetComponent()->UnEquipItem(this); } bool Item::IsEquipped() const { auto* component = inventory->GetComponent(); for (const auto& pair : component->GetEquippedItems()) { const auto item = pair.second; if (item.id == id) { return true; } } return false; } bool Item::Consume() { auto* skillsTable = CDClientManager::GetTable(); auto skills = skillsTable->Query([this](const CDObjectSkills entry) { return entry.objectTemplate == static_cast(lot); }); auto success = false; for (auto& skill : skills) { if (skill.castOnType == 3) // Consumable type { success = true; } } LOG_DEBUG("Consumed LOT (%i) itemID (%llu). Success=(%d)", lot, id, success); GameMessages::SendUseItemResult(inventory->GetComponent()->GetParent(), lot, success); if (success) { inventory->GetComponent()->RemoveItem(lot, 1); } return success; } void Item::UseNonEquip(Item* item) { LOT thisLot = this->GetLot(); if (!GetInventory()) { LOG_DEBUG("item %i has no inventory??", this->GetLot()); return; } auto* playerInventoryComponent = GetInventory()->GetComponent(); if (!playerInventoryComponent) { LOG_DEBUG("no inventory component attached to item id %llu lot %i", this->GetId(), this->GetLot()); return; } auto* playerEntity = playerInventoryComponent->GetParent(); if (!playerEntity) { LOG_DEBUG("no player entity attached to inventory? item id is %llu", this->GetId()); return; } const auto type = static_cast(info->itemType); if (type == eItemType::MOUNT) { if (Game::zoneManager->GetMountsAllowed()) { playerInventoryComponent->HandlePossession(this); } else { ChatPackets::SendSystemMessage(playerEntity->GetSystemAddress(), u"Mounts are not allowed in this zone"); } } else if (type == eItemType::PET_INVENTORY_ITEM && subKey != LWOOBJID_EMPTY) { if (Game::zoneManager->GetPetsAllowed()) { const auto& databasePet = playerInventoryComponent->GetDatabasePet(subKey); if (databasePet.lot != LOT_NULL) { playerInventoryComponent->SpawnPet(this); } } else { ChatPackets::SendSystemMessage(playerEntity->GetSystemAddress(), u"Pets are not allowed in this zone"); } // This precondition response is taken care of in SpawnPet(). } else { bool success = false; auto inventory = item->GetInventory(); if (inventory && inventory->GetType() == eInventoryType::ITEMS) { auto* compRegistryTable = CDClientManager::GetTable(); const auto packageComponentId = compRegistryTable->GetByIDAndType(lot, eReplicaComponentType::PACKAGE); if (packageComponentId == 0) return; auto* packCompTable = CDClientManager::GetTable(); auto packages = packCompTable->Query([=](const CDPackageComponent entry) {return entry.id == static_cast(packageComponentId); }); auto success = !packages.empty(); if (success) { if (this->GetPreconditionExpression()->Check(playerInventoryComponent->GetParent())) { auto* entityParent = playerInventoryComponent->GetParent(); // Roll the loot for all the packages then see if it all fits. If it fits, give it to the player, otherwise don't. std::unordered_map rolledLoot{}; for (auto& pack : packages) { auto thisPackage = Loot::RollLootMatrix(entityParent, pack.LootMatrixIndex); for (auto& loot : thisPackage) { // If we already rolled this lot, add it to the existing one, otherwise create a new entry. auto existingLoot = rolledLoot.find(loot.first); if (existingLoot == rolledLoot.end()) { rolledLoot.insert(loot); } else { existingLoot->second += loot.second; } } } if (playerInventoryComponent->HasSpaceForLoot(rolledLoot)) { Loot::GiveLoot(playerInventoryComponent->GetParent(), rolledLoot, eLootSourceType::CONSUMPTION); item->SetCount(item->GetCount() - 1); } else { success = false; } } else { GameMessages::SendUseItemRequirementsResponse( playerInventoryComponent->GetParent()->GetObjectID(), playerInventoryComponent->GetParent()->GetSystemAddress(), eUseItemResponse::FailedPrecondition ); success = false; } } } LOG_DEBUG("Player %llu %s used item %i", playerEntity->GetObjectID(), success ? "successfully" : "unsuccessfully", thisLot); GameMessages::SendUseItemResult(playerInventoryComponent->GetParent(), thisLot, success); } } void Item::Disassemble(const eInventoryType inventoryType) { for (auto* data : config) { if (data->GetKey() == u"assemblyPartLOTs") { auto modStr = data->GetValueAsString(); // This shouldn't be null but always check your pointers. if (GetInventory()) { auto inventoryComponent = GetInventory()->GetComponent(); if (inventoryComponent) { auto entity = inventoryComponent->GetParent(); if (entity) entity->SetVar(u"currentModifiedBuild", modStr); } } std::vector modArray; std::stringstream ssData(modStr); std::string token; const auto deliminator = '+'; while (std::getline(ssData, token, deliminator)) { const auto modLot = std::stoi(token.substr(2, token.size() - 1)); modArray.push_back(modLot); } for (const auto mod : modArray) { inventory->GetComponent()->AddItem(mod, 1, eLootSourceType::DELETION, inventoryType); } } } } void Item::DisassembleModel(uint32_t numToDismantle) { auto* table = CDClientManager::GetTable(); const auto componentId = table->GetByIDAndType(GetLot(), eReplicaComponentType::RENDER); auto query = CDClientDatabase::CreatePreppedStmt("SELECT render_asset, LXFMLFolder FROM RenderComponent WHERE id = ?;"); query.bind(1, static_cast(componentId)); auto result = query.execQuery(); if (result.eof() || result.fieldIsNull("render_asset")) { return; } std::string renderAsset = std::string(result.getStringField("render_asset")); // normalize path slashes for (auto& c : renderAsset) { if (c == '\\') c = '/'; } std::string lxfmlFolderName = std::string(result.getStringField("LXFMLFolder")); if (!lxfmlFolderName.empty()) lxfmlFolderName.insert(0, "/"); std::vector renderAssetSplit = GeneralUtils::SplitString(renderAsset, '/'); if (renderAssetSplit.empty()) return; std::string lxfmlPath = "BrickModels" + lxfmlFolderName + "/" + GeneralUtils::SplitString(renderAssetSplit.back(), '.').at(0) + ".lxfml"; auto file = Game::assetManager->GetFile(lxfmlPath.c_str()); if (!file) { LOG("Failed to load %s to disassemble model into bricks, check that this file exists", lxfmlPath.c_str()); return; } std::stringstream data; data << file.rdbuf(); uint32_t fileSize; file.seekg(0, std::ios::end); fileSize = static_cast(file.tellg()); file.seekg(0, std::ios::beg); if (fileSize == 0) return; tinyxml2::XMLDocument doc; if (doc.Parse(data.str().c_str(), data.str().size()) != tinyxml2::XML_SUCCESS) { return; } auto* lxfml = doc.FirstChildElement("LXFML"); if (!lxfml) return; auto* bricks = lxfml->FirstChildElement("Bricks"); std::string searchTerm = "Brick"; if (!bricks) { searchTerm = "Part"; auto* scene = lxfml->FirstChildElement("Scene"); if (!scene) return; auto* model = scene->FirstChildElement("Model"); if (!model) return; bricks = model->FirstChildElement("Group"); if (!bricks) return; } auto* currentBrick = bricks->FirstChildElement(searchTerm.c_str()); // First iteration gets the count std::map parts; while (currentBrick) { const char* const designID = currentBrick->Attribute("designID"); if (designID) { const auto designId = GeneralUtils::TryParse(designID); if (!designId) { LOG("Failed to parse designID %s", designID); continue; } parts[designId.value()]++; } currentBrick = currentBrick->NextSiblingElement(searchTerm.c_str()); } auto* brickIDTable = CDClientManager::GetTable(); // Second iteration actually distributes the bricks for (const auto& [part, count] : parts) { const auto partLocal = part; const auto brickID = brickIDTable->Query([&](const CDBrickIDTable& entry) { return entry.LEGOBrickID == partLocal; }); if (brickID.empty()) continue; GetInventory()->GetComponent()->AddItem(brickID[0].NDObjectID, count * numToDismantle, eLootSourceType::DELETION); } } void Item::RemoveFromInventory() { UnEquip(); count = 0; inventory->RemoveManagedItem(this); delete this; } Item::~Item() { delete preconditions; for (auto* value : config) { delete value; } config.clear(); } void Item::SaveConfigXml(tinyxml2::XMLElement& i) const { tinyxml2::XMLElement* x = nullptr; for (const auto* config : this->config) { const auto& key = GeneralUtils::UTF16ToWTF8(config->GetKey()); const auto saveKey = ExtraSettingAbbreviations.find(key); if (saveKey == ExtraSettingAbbreviations.end()) { continue; } if (!x) { x = i.InsertNewChildElement("x"); } const auto dataToSave = config->GetString(false); x->SetAttribute(saveKey->second.c_str(), dataToSave.c_str()); } } void Item::LoadConfigXml(const tinyxml2::XMLElement& i) { const auto* x = i.FirstChildElement("x"); if (!x) return; for (const auto& pair : ExtraSettingAbbreviations) { const auto* data = x->Attribute(pair.second.c_str()); if (!data) continue; const auto value = pair.first + "=" + data; config.push_back(LDFBaseData::DataFromString(value)); } } bool Item::CanDeleteItem(Item* item) { if (!item) return false; // TODO: // Check if item is being possessed // Check if the owner is mounting item? (how is this different than the above) // Allow GM 9 to freely delete // Finally, check Deletion Restriction const auto& itemComponent = item->inventory->FindItemComponent(item->lot); if (itemComponent.delResIndex == -1) return true; return CheckDeletionRestriction(itemComponent.delResIndex, item->lot); } bool Item::CheckDeletionRestriction(uint32_t delResIndex, LOT item) { auto* delresTable = CDClientManager::GetTable(); const auto restriction = delresTable->GetByID(delResIndex); switch(restriction.checkType) { case eDeletionRestrictionsCheckType::INCLUDE_LOTS: if (std::ranges::find(restriction.ids, item) != restriction.ids.end()) return false; else return true; case eDeletionRestrictionsCheckType::EXCLUDE_LOTS: if (std::ranges::find(restriction.ids, item) != restriction.ids.end()) return true; else return false; case eDeletionRestrictionsCheckType::ANY_OF_THESE: // TODO: Implement return true; case eDeletionRestrictionsCheckType::ALL_OF_THESE: // TODO: Implement return true; case eDeletionRestrictionsCheckType::WHILE_IN_ZONE: if (std::ranges::find(restriction.ids, Game::zoneManager->GetZoneID().GetMapID()) != restriction.ids.end()) return false; else return true; case eDeletionRestrictionsCheckType::ALWAYS_RESTRICTED: return false; case eDeletionRestrictionsCheckType::MAX: default: return true; } }