mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
Live, on every equip and unequip (captures: 22,422 ChangeObjectWorldState, 4,269 equip effects, 245 UncastSkill): - ChangeObjectWorldState on the item to everyone: ATTACHED when equipped, INVENTORY when unequipped, proxies (sub-items) included. - The equip-<slot> / unequip-<slot> effect on the wearer to everyone (effect -1, priority 1.07), the slot picked from the item's equip location as the client's ItemComponent does (0x00cdafb0): special_l left, special_r right, hair head, clavicle, chest, legs; none for others (e.g. a rocket's Extra_1); the item's equipEffects with "equip"/"unequip" when it has one; no equip effect for noEquipAnimation items, no unequip effect for proxies. - Order when an item replaces another: the new item's state and effect, then the old item's, then the old item's proxies taken away (RemoveItemFromInventory, UnEquipInventory with ignoreCooldown, ChangeObjectWorldState INVENTORY, to the player), then UncastSkill for the old item's equip skills (castOnType 1, not its proxies'), then its skill removed and the new one added, then the new item's proxies. - An equipped item taken out of the inventory is followed by UnEquipInventory (ignoreCooldown) to the player. DLU sent none of these (only the rocket's ChangeObjectWorldState, which now comes from equipping it, before RocketEquipped as live). Check in game (with a second player watching): equip and unequip a hat, a sword, a shield, a shirt, pants and a backpack; both players see the equip animation/effect. Put a ninja hood on, then another hat over it: the hood's back piece disappears and its passive effects end (no lingering aura or stat). Launch a rocket: it is still shown during the launch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
645 lines
18 KiB
C++
645 lines
18 KiB
C++
#include "Item.h"
|
|
#include "EconomyLedger.h"
|
|
|
|
#include <sstream>
|
|
|
|
#include "ObjectIDManager.h"
|
|
#include "GeneralUtils.h"
|
|
#include "GameMessages.h"
|
|
#include "InventoryMessages.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 "MissionComponent.h"
|
|
#include "eMissionTaskType.h"
|
|
|
|
#include "CDBrickIDTableTable.h"
|
|
#include "CDObjectSkillsTable.h"
|
|
#include "CDComponentsRegistryTable.h"
|
|
#include "CDPackageComponentTable.h"
|
|
#include <ranges>
|
|
|
|
namespace {
|
|
const std::map<std::string, std::string> ExtraSettingAbbreviations = {
|
|
{ "assemblyPartLOTs", "ma" },
|
|
{ "blueprintID", "b" },
|
|
// A brick built model item's blueprint (BrickByBrick::ModelItemConfig), kept so the model can be placed again after a reload
|
|
{ "blueprintid", "bp" },
|
|
{ "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 LwoNameValue& 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 LwoNameValue& 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;
|
|
|
|
auto* const inventoryComponent = inventory->GetComponent();
|
|
GenerateID();
|
|
|
|
inventory->AddManagedItem(this);
|
|
|
|
auto* entity = inventoryComponent->GetParent();
|
|
if (parent == LWOOBJID_EMPTY) EconomyLedger::RecordItems(entity, lot, this->count, static_cast<uint32_t>(lootSourceType));
|
|
GameMessages::AddItemToInventoryClientSync addItem;
|
|
addItem.target = entity->GetObjectID();
|
|
addItem.SetItem(*this);
|
|
addItem.eLootTypeSource = lootSourceType;
|
|
addItem.iSubkey = subKey;
|
|
addItem.itemCount = static_cast<int>(this->count);
|
|
addItem.newObjID = id;
|
|
addItem.showFlyingLoot = showFlyingLoot;
|
|
addItem.SendToClient(entity->GetSystemAddress());
|
|
if (auto* characterComponent = entity->GetComponent<CharacterComponent>()) characterComponent->TrackItemsAdded(inventory->GetType(), this->count);
|
|
|
|
if (isModMoveAndEquip) {
|
|
Equip();
|
|
|
|
LOG("Move and equipped (%i) from (%i)", this->lot, this->inventory->GetType());
|
|
|
|
Game::entityManager->SerializeEntity(inventoryComponent->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;
|
|
}
|
|
|
|
const LwoNameValue& Item::GetConfig() const {
|
|
return config;
|
|
}
|
|
|
|
LwoNameValue& 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<int32_t>(value) - static_cast<int32_t>(count));
|
|
|
|
const auto type = static_cast<eItemType>(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::AddItemToInventoryClientSync addItem;
|
|
addItem.target = entity->GetObjectID();
|
|
addItem.SetItem(*this);
|
|
addItem.eLootTypeSource = lootSourceType;
|
|
addItem.itemCount = delta;
|
|
addItem.newObjID = id;
|
|
addItem.showFlyingLoot = showFlyingLoot;
|
|
addItem.SendToClient(entity->GetSystemAddress());
|
|
if (auto* characterComponent = entity->GetComponent<CharacterComponent>()) characterComponent->TrackItemsAdded(inventory->GetType(), delta);
|
|
} else {
|
|
GameMessages::RemoveItemFromInventory removeItem;
|
|
removeItem.target = entity->GetObjectID();
|
|
removeItem.bConfirmed = true;
|
|
removeItem.eInvType = inventory->GetType();
|
|
removeItem.eLootTypeSource = LOOTTYPE_NONE;
|
|
removeItem.iObjID = id;
|
|
removeItem.iObjTemplate = lot;
|
|
removeItem.iStackCount = delta;
|
|
removeItem.iStackRemaining = value;
|
|
removeItem.SendToClient(entity->GetSystemAddress());
|
|
}
|
|
}
|
|
|
|
// Proxies (sub-items of a set) come and go with their parent and are not items of their own
|
|
if (parent == LWOOBJID_EMPTY) {
|
|
EconomyLedger::RecordItems(inventory->GetComponent()->GetParent(), lot, static_cast<int64_t>(value) - static_cast<int64_t>(count), static_cast<uint32_t>(lootSourceType));
|
|
}
|
|
|
|
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* const skillsTable = CDClientManager::GetTable<CDObjectSkillsTable>();
|
|
|
|
const auto skills = skillsTable->Query([this](const CDObjectSkills& entry) {
|
|
return entry.objectTemplate == static_cast<uint32_t>(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);
|
|
|
|
auto* const owner = inventory->GetComponent()->GetParent();
|
|
GameMessages::UseItemResult useItemResult;
|
|
useItemResult.target = owner->GetObjectID();
|
|
useItemResult.itemTemplateID = lot;
|
|
useItemResult.useItemResult = success;
|
|
useItemResult.SendToClient(owner->GetSystemAddress());
|
|
|
|
const auto myLot = this->lot;
|
|
if (success && inventory->GetComponent()->RemoveItem(lot, 1, eInventoryType::ALL)) {
|
|
// Save this because if this is the last item in the inventory
|
|
// we may delete ourself (lol)
|
|
auto* missionComponent = inventory->GetComponent()->GetParent()->GetComponent<MissionComponent>();
|
|
if (missionComponent) missionComponent->Progress(eMissionTaskType::GATHER, myLot, LWOOBJID_EMPTY, "", -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<eItemType>(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<CDComponentsRegistryTable>();
|
|
const auto packageComponentId = compRegistryTable->GetByIDAndType(lot, eReplicaComponentType::PACKAGE);
|
|
|
|
if (packageComponentId == 0) return;
|
|
|
|
auto* packCompTable = CDClientManager::GetTable<CDPackageComponentTable>();
|
|
auto packages = packCompTable->Query([=](const CDPackageComponent entry) {return entry.id == static_cast<uint32_t>(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.
|
|
Loot::Return rolledLoot{};
|
|
for (auto& pack : packages) {
|
|
const 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::UseItemRequirementsResponse requirementsResponse;
|
|
requirementsResponse.target = playerInventoryComponent->GetParent()->GetObjectID();
|
|
requirementsResponse.eUseResponse = eUseItemResponse::FailedPrecondition;
|
|
requirementsResponse.SendToClient(playerInventoryComponent->GetParent()->GetSystemAddress());
|
|
success = false;
|
|
}
|
|
}
|
|
}
|
|
LOG_DEBUG("Player %llu %s used item %i", playerEntity->GetObjectID(), success ? "successfully" : "unsuccessfully", thisLot);
|
|
GameMessages::UseItemResult useItemResult;
|
|
useItemResult.target = playerInventoryComponent->GetParent()->GetObjectID();
|
|
useItemResult.itemTemplateID = thisLot;
|
|
useItemResult.useItemResult = success;
|
|
useItemResult.SendToClient(playerInventoryComponent->GetParent()->GetSystemAddress());
|
|
}
|
|
}
|
|
|
|
void Item::Disassemble(const eInventoryType inventoryType) {
|
|
for (const auto& data : config.values | std::views::values) {
|
|
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<std::string>(u"currentModifiedBuild", modStr);
|
|
}
|
|
}
|
|
|
|
std::vector<LOT> modArray;
|
|
|
|
std::stringstream ssData(modStr);
|
|
|
|
std::string token;
|
|
|
|
const auto deliminator = '+';
|
|
|
|
while (std::getline(ssData, token, deliminator)) {
|
|
if (token.size() <= 2) continue; // invalid token, must have size of at least 3.
|
|
const auto modLot = GeneralUtils::TryParse(token.substr(2, token.size() - 1), LOT_NULL);
|
|
|
|
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<CDComponentsRegistryTable>();
|
|
|
|
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<int>(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<std::string> renderAssetSplit = GeneralUtils::SplitString(renderAsset, '/');
|
|
if (renderAssetSplit.empty()) return;
|
|
|
|
const auto renderAssetSplitSplit = GeneralUtils::SplitString(renderAssetSplit.back(), '.');
|
|
if (renderAssetSplitSplit.empty()) return;
|
|
|
|
std::string lxfmlPath = "BrickModels" + lxfmlFolderName + "/" + renderAssetSplitSplit[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<uint32_t>(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<int32_t, int32_t> parts;
|
|
while (currentBrick) {
|
|
const char* const designID = currentBrick->Attribute("designID");
|
|
if (designID) {
|
|
const auto designId = GeneralUtils::TryParse<uint32_t>(designID);
|
|
if (!designId) {
|
|
LOG("Failed to parse designID %s", designID);
|
|
continue;
|
|
}
|
|
parts[designId.value()]++;
|
|
}
|
|
|
|
currentBrick = currentBrick->NextSiblingElement(searchTerm.c_str());
|
|
}
|
|
|
|
auto* brickIDTable = CDClientManager::GetTable<CDBrickIDTableTable>();
|
|
|
|
// 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() {
|
|
// Live told the client an equipped item taken out of the inventory was unequipped (after RemoveItemFromInventory)
|
|
if (IsEquipped()) {
|
|
auto* const entity = inventory->GetComponent()->GetParent();
|
|
GameMessages::UnEquipInventory unequip;
|
|
unequip.target = entity->GetObjectID();
|
|
unequip.bIgnoreCooldown = true;
|
|
unequip.itemToUnequip = id;
|
|
if (entity->IsPlayer()) unequip.SendToClient(entity->GetSystemAddress());
|
|
}
|
|
|
|
UnEquip();
|
|
|
|
count = 0;
|
|
|
|
inventory->RemoveManagedItem(this);
|
|
|
|
delete this;
|
|
}
|
|
|
|
Item::~Item() {
|
|
delete preconditions;
|
|
}
|
|
|
|
void Item::SaveConfigXml(tinyxml2::XMLElement& i) const {
|
|
tinyxml2::XMLElement* x = nullptr;
|
|
|
|
for (const auto& config : config.values | std::views::values) {
|
|
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& [fullName, abbreviation] : ExtraSettingAbbreviations) {
|
|
const auto* data = x->Attribute(abbreviation.c_str());
|
|
if (!data) continue;
|
|
|
|
config.ParseInsert(fullName + "=" + data);
|
|
}
|
|
}
|
|
|
|
LWOOBJID Item::GenerateID() {
|
|
auto* const inventoryComponent = inventory->GetComponent();
|
|
const bool isPlayer = inventoryComponent->GetParent()->IsPlayer();
|
|
LWOOBJID id{};
|
|
|
|
// Only players and non-proxy items get persistent IDs (since they are the only ones that will persist between worlds)
|
|
if (isPlayer && parent == LWOOBJID_EMPTY) {
|
|
id = ObjectIDManager::GetPersistentID();
|
|
} else {
|
|
id = ObjectIDManager::GenerateObjectID();
|
|
GeneralUtils::SetBit(id, eObjectBits::SPAWNED);
|
|
GeneralUtils::SetBit(id, eObjectBits::CLIENT);
|
|
}
|
|
|
|
LOG("Parent %llu lot %u Generated id %u:%llu", parent, GetLot(), static_cast<uint32_t>(id), id);
|
|
const auto type = static_cast<eItemType>(info->itemType);
|
|
|
|
if (type == eItemType::MOUNT) {
|
|
GeneralUtils::SetBit(id, eObjectBits::CLIENT);
|
|
}
|
|
|
|
this->id = id;
|
|
return id;
|
|
}
|