mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
Item::RemoveFromInventory ended with `delete this`, so every caller that still used the item afterwards (SetCount(0) returning into its caller, loops that remove several items, proxies purged while their parent is handled) touched freed memory. The inventory now takes the removed item and frees it at the inventory component's next update, or with the inventory. Check in game: sell, drop, delete, trade, mail and use up stacks (including the last of a stack); unequip and remove an item set piece and a proxy-bearing item (rocket, modular car); donate items; nothing crashes and the inventory shows the right counts after relogging. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
647 lines
19 KiB
C++
647 lines
19 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);
|
|
|
|
// The inventory frees the item at its next update, so callers still holding it (SetCount(0) and the loops that
|
|
// remove several items) are not left with a freed item
|
|
inventory->RetireItem(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;
|
|
}
|