From 3da24fffa4a73ce80c7e7a8dfd4dc511c13cf921 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Mon, 26 Aug 2024 23:58:07 -0300 Subject: [PATCH] [Bug Fix] Fix client hotbar exchanging items when zoning (#4460) * Add an exception process to assigning item serial numbers to correct a bug in the client hot bar clicky system. * fixed missing guid in replace statement * added snapshot support * upate #show inventory command to protect against crash conditions --- common/database/database_update_manifest.cpp | 13 ++ common/item_instance.cpp | 32 +++- common/item_instance.h | 2 + .../base/base_inventory_repository.h | 12 ++ .../base_inventory_snapshots_repository.h | 12 ++ common/shareddb.cpp | 166 +++++++++++------- zone/gm_commands/show/inventory.cpp | 23 ++- zone/zonedb.cpp | 12 +- 8 files changed, 181 insertions(+), 91 deletions(-) diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 2bd27e603..6debac639 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -5733,6 +5733,19 @@ CHANGE COLUMN `value` `bucket_value` varchar(100) CHARACTER SET latin1 COLLATE l ADD COLUMN `bucket_comparison` tinyint UNSIGNED NOT NULL DEFAULT 0 AFTER `bucket_value`, DROP PRIMARY KEY, ADD PRIMARY KEY (`spell_id`) USING BTREE; +)" + }, + ManifestEntry{ + .version = 9283, + .description = "2024_08_05_fix_client_hotbar", + .check = "SHOW COLUMNS FROM `inventory` LIKE 'guid'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `inventory` + ADD COLUMN `guid` BIGINT UNSIGNED NULL DEFAULT '0' AFTER `ornament_hero_model`; +ALTER TABLE `inventory_snapshots` + ADD COLUMN `guid` BIGINT UNSIGNED NULL DEFAULT '0' AFTER `ornament_hero_model`; )" } // -- template; copy/paste this when you need to create a new entry diff --git a/common/item_instance.cpp b/common/item_instance.cpp index 56e0a9b75..6aeb32223 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -32,10 +32,11 @@ //#include -int32 NextItemInstSerialNumber = 1; - -static inline int32 GetNextItemInstSerialNumber() { +int32 next_item_serial_number = 1; +std::unordered_set guids{}; +static inline int32 GetNextItemInstSerialNumber() +{ // The Bazaar relies on each item a client has up for Trade having a unique // identifier. This 'SerialNumber' is sent in Serialized item packets and // is used in Bazaar packets to identify the item a player is buying or inspecting. @@ -46,12 +47,18 @@ static inline int32 GetNextItemInstSerialNumber() { // NextItemInstSerialNumber is the next one to hand out. // // It is very unlikely to reach 2,147,483,647. Maybe we should call abort(), rather than wrapping back to 1. - if(NextItemInstSerialNumber >= INT_MAX) - NextItemInstSerialNumber = 1; - else - NextItemInstSerialNumber++; + if (next_item_serial_number >= INT32_MAX) { + next_item_serial_number = 1; + } + else { + next_item_serial_number++; + } - return NextItemInstSerialNumber; + while (guids.contains(next_item_serial_number)) { + next_item_serial_number++; + } + + return next_item_serial_number; } // @@ -1935,6 +1942,15 @@ int EQ::ItemInstance::GetItemSkillsStat(EQ::skills::SkillType skill, bool augmen return stat; } +void EQ::ItemInstance::AddGUIDToMap(uint64 existing_serial_number) +{ + guids.emplace(existing_serial_number); +} + +void EQ::ItemInstance::ClearGUIDMap() +{ + guids.clear(); +} // // class EvolveInfo // diff --git a/common/item_instance.h b/common/item_instance.h index f245c7139..928a6aabc 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -309,6 +309,8 @@ namespace EQ int GetItemSkillsStat(EQ::skills::SkillType skill, bool augments = false) const; uint32 GetItemGuildFavor() const; std::vector GetAugmentIDs() const; + static void AddGUIDToMap(uint64 existing_serial_number); + static void ClearGUIDMap(); protected: ////////////////////////// diff --git a/common/repositories/base/base_inventory_repository.h b/common/repositories/base/base_inventory_repository.h index 7cc15789b..a029ac6b4 100644 --- a/common/repositories/base/base_inventory_repository.h +++ b/common/repositories/base/base_inventory_repository.h @@ -35,6 +35,7 @@ public: uint32_t ornamenticon; uint32_t ornamentidfile; int32_t ornament_hero_model; + uint64_t guid; }; static std::string PrimaryKey() @@ -61,6 +62,7 @@ public: "ornamenticon", "ornamentidfile", "ornament_hero_model", + "guid", }; } @@ -83,6 +85,7 @@ public: "ornamenticon", "ornamentidfile", "ornament_hero_model", + "guid", }; } @@ -139,6 +142,7 @@ public: e.ornamenticon = 0; e.ornamentidfile = 0; e.ornament_hero_model = 0; + e.guid = 0; return e; } @@ -191,6 +195,7 @@ public: e.ornamenticon = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; e.ornamentidfile = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; e.ornament_hero_model = row[15] ? static_cast(atoi(row[15])) : 0; + e.guid = row[16] ? strtoull(row[16], nullptr, 10) : 0; return e; } @@ -240,6 +245,7 @@ public: v.push_back(columns[13] + " = " + std::to_string(e.ornamenticon)); v.push_back(columns[14] + " = " + std::to_string(e.ornamentidfile)); v.push_back(columns[15] + " = " + std::to_string(e.ornament_hero_model)); + v.push_back(columns[16] + " = " + std::to_string(e.guid)); auto results = db.QueryDatabase( fmt::format( @@ -277,6 +283,7 @@ public: v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamentidfile)); v.push_back(std::to_string(e.ornament_hero_model)); + v.push_back(std::to_string(e.guid)); auto results = db.QueryDatabase( fmt::format( @@ -322,6 +329,7 @@ public: v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamentidfile)); v.push_back(std::to_string(e.ornament_hero_model)); + v.push_back(std::to_string(e.guid)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -371,6 +379,7 @@ public: e.ornamenticon = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; e.ornamentidfile = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; e.ornament_hero_model = row[15] ? static_cast(atoi(row[15])) : 0; + e.guid = row[16] ? strtoull(row[16], nullptr, 10) : 0; all_entries.push_back(e); } @@ -411,6 +420,7 @@ public: e.ornamenticon = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; e.ornamentidfile = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; e.ornament_hero_model = row[15] ? static_cast(atoi(row[15])) : 0; + e.guid = row[16] ? strtoull(row[16], nullptr, 10) : 0; all_entries.push_back(e); } @@ -501,6 +511,7 @@ public: v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamentidfile)); v.push_back(std::to_string(e.ornament_hero_model)); + v.push_back(std::to_string(e.guid)); auto results = db.QueryDatabase( fmt::format( @@ -539,6 +550,7 @@ public: v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamentidfile)); v.push_back(std::to_string(e.ornament_hero_model)); + v.push_back(std::to_string(e.guid)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/repositories/base/base_inventory_snapshots_repository.h b/common/repositories/base/base_inventory_snapshots_repository.h index 8a8aa22a9..0b6863727 100644 --- a/common/repositories/base/base_inventory_snapshots_repository.h +++ b/common/repositories/base/base_inventory_snapshots_repository.h @@ -36,6 +36,7 @@ public: uint32_t ornamenticon; uint32_t ornamentidfile; int32_t ornament_hero_model; + uint64_t guid; }; static std::string PrimaryKey() @@ -63,6 +64,7 @@ public: "ornamenticon", "ornamentidfile", "ornament_hero_model", + "guid", }; } @@ -86,6 +88,7 @@ public: "ornamenticon", "ornamentidfile", "ornament_hero_model", + "guid", }; } @@ -143,6 +146,7 @@ public: e.ornamenticon = 0; e.ornamentidfile = 0; e.ornament_hero_model = 0; + e.guid = 0; return e; } @@ -196,6 +200,7 @@ public: e.ornamenticon = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; e.ornamentidfile = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; e.ornament_hero_model = row[16] ? static_cast(atoi(row[16])) : 0; + e.guid = row[17] ? strtoull(row[17], nullptr, 10) : 0; return e; } @@ -246,6 +251,7 @@ public: v.push_back(columns[14] + " = " + std::to_string(e.ornamenticon)); v.push_back(columns[15] + " = " + std::to_string(e.ornamentidfile)); v.push_back(columns[16] + " = " + std::to_string(e.ornament_hero_model)); + v.push_back(columns[17] + " = " + std::to_string(e.guid)); auto results = db.QueryDatabase( fmt::format( @@ -284,6 +290,7 @@ public: v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamentidfile)); v.push_back(std::to_string(e.ornament_hero_model)); + v.push_back(std::to_string(e.guid)); auto results = db.QueryDatabase( fmt::format( @@ -330,6 +337,7 @@ public: v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamentidfile)); v.push_back(std::to_string(e.ornament_hero_model)); + v.push_back(std::to_string(e.guid)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -380,6 +388,7 @@ public: e.ornamenticon = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; e.ornamentidfile = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; e.ornament_hero_model = row[16] ? static_cast(atoi(row[16])) : 0; + e.guid = row[17] ? strtoull(row[17], nullptr, 10) : 0; all_entries.push_back(e); } @@ -421,6 +430,7 @@ public: e.ornamenticon = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; e.ornamentidfile = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; e.ornament_hero_model = row[16] ? static_cast(atoi(row[16])) : 0; + e.guid = row[17] ? strtoull(row[17], nullptr, 10) : 0; all_entries.push_back(e); } @@ -512,6 +522,7 @@ public: v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamentidfile)); v.push_back(std::to_string(e.ornament_hero_model)); + v.push_back(std::to_string(e.guid)); auto results = db.QueryDatabase( fmt::format( @@ -551,6 +562,7 @@ public: v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamentidfile)); v.push_back(std::to_string(e.ornament_hero_model)); + v.push_back(std::to_string(e.guid)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 65333fdce..f853dbb59 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -46,6 +46,7 @@ #include "repositories/character_item_recast_repository.h" #include "repositories/character_corpses_repository.h" #include "repositories/skill_caps_repository.h" +#include "repositories/inventory_repository.h" namespace ItemField { @@ -300,15 +301,15 @@ bool SharedDatabase::UpdateInventorySlot(uint32 char_id, const EQ::ItemInstance* // Update/Insert item const std::string query = StringFormat("REPLACE INTO inventory " "(charid, slotid, itemid, charges, instnodrop, custom_data, color, " - "augslot1, augslot2, augslot3, augslot4, augslot5, augslot6, ornamenticon, ornamentidfile, ornament_hero_model) " + "augslot1, augslot2, augslot3, augslot4, augslot5, augslot6, ornamenticon, ornamentidfile, ornament_hero_model, guid) " "VALUES( %lu, %lu, %lu, %lu, %lu, '%s', %lu, " - "%lu, %lu, %lu, %lu, %lu, %lu, %lu, %lu, %lu)", + "%lu, %lu, %lu, %lu, %lu, %lu, %lu, %lu, %lu, %lu)", static_cast(char_id), static_cast(slot_id), static_cast(inst->GetItem()->ID), static_cast(charges), static_cast(inst->IsAttuned() ? 1 : 0), inst->GetCustomDataString().c_str(), static_cast(inst->GetColor()), static_cast(augslot[0]), static_cast(augslot[1]), static_cast(augslot[2]), static_cast(augslot[3]), static_cast(augslot[4]), static_cast(augslot[5]), static_cast(inst->GetOrnamentationIcon()), - static_cast(inst->GetOrnamentationIDFile()), static_cast(inst->GetOrnamentHeroModel())); + static_cast(inst->GetOrnamentationIDFile()), static_cast(inst->GetOrnamentHeroModel()), inst->GetSerialNumber()); const auto results = QueryDatabase(query); // Save bag contents, if slot supports bag contents @@ -651,48 +652,67 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) return false; // Retrieve character inventory - const std::string query = - StringFormat("SELECT slotid, itemid, charges, color, augslot1, augslot2, augslot3, augslot4, augslot5, " - "augslot6, instnodrop, custom_data, ornamenticon, ornamentidfile, ornament_hero_model FROM " - "inventory WHERE charid = %i ORDER BY slotid", - char_id); - auto results = QueryDatabase(query); - if (!results.Success()) { - LogError("If you got an error related to the 'instnodrop' field, run the " - "following SQL Queries:\nalter table inventory add instnodrop " - "tinyint(1) unsigned default 0 not null;\n"); + auto results = InventoryRepository::GetWhere(*this, fmt::format("`charid` = '{}' ORDER BY `slotid`;", char_id)); + if (results.empty()) { + LogError("Error loading inventory for char_id {} from the database.", char_id); return false; } + for (auto const &row: results) { + if (row.guid != 0) { + EQ::ItemInstance::AddGUIDToMap(row.guid); + } + } + const auto timestamps = GetItemRecastTimestamps(char_id); + auto cv_conflict = false; + const auto pmask = inv->GetLookup()->PossessionsBitmask; + const auto bank_size = inv->GetLookup()->InventoryTypeSize.Bank; - auto cv_conflict = false; - const auto pmask = inv->GetLookup()->PossessionsBitmask; - const auto bank_size = inv->GetLookup()->InventoryTypeSize.Bank; + std::vector queue{}; + for (auto &row: results) { + const int16 slot_id = row.slotid; + const uint32 item_id = row.itemid; + const uint16 charges = row.charges; + const uint32 color = row.color; + const bool instnodrop = row.instnodrop; + const uint32 ornament_icon = row.ornamenticon; + const uint32 ornament_idfile = row.ornamentidfile; + const uint32 ornament_hero_model = row.ornament_hero_model; - for (auto& row = results.begin(); row != results.end(); ++row) { - int16 slot_id = Strings::ToInt(row[0]); + uint32 aug[EQ::invaug::SOCKET_COUNT]; + aug[0] = row.augslot1; + aug[1] = row.augslot2; + aug[2] = row.augslot3; + aug[3] = row.augslot4; + aug[4] = row.augslot5; + aug[5] = row.augslot6; - if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) { // Titanium thru UF check + if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) { + // Titanium thru UF check if (((static_cast(1) << slot_id) & pmask) == 0) { cv_conflict = true; continue; } } - else if (slot_id <= EQ::invbag::GENERAL_BAGS_END && slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN) { // Titanium thru UF check - const auto parent_slot = EQ::invslot::GENERAL_BEGIN + ((slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT); + else if (slot_id <= EQ::invbag::GENERAL_BAGS_END && slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN) { + // Titanium thru UF check + const auto parent_slot = EQ::invslot::GENERAL_BEGIN + ( + (slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT); if (((static_cast(1) << parent_slot) & pmask) == 0) { cv_conflict = true; continue; } } - else if (slot_id <= EQ::invslot::BANK_END && slot_id >= EQ::invslot::BANK_BEGIN) { // Titanium check + else if (slot_id <= EQ::invslot::BANK_END && slot_id >= EQ::invslot::BANK_BEGIN) { + // Titanium check if ((slot_id - EQ::invslot::BANK_BEGIN) >= bank_size) { cv_conflict = true; continue; } } - else if (slot_id <= EQ::invbag::BANK_BAGS_END && slot_id >= EQ::invbag::BANK_BAGS_BEGIN) { // Titanium check + else if (slot_id <= EQ::invbag::BANK_BAGS_END && slot_id >= EQ::invbag::BANK_BAGS_BEGIN) { + // Titanium check const auto parent_index = ((slot_id - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT); if (parent_index >= bank_size) { cv_conflict = true; @@ -700,64 +720,55 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) } } - uint32 item_id = Strings::ToUnsignedInt(row[1]); - const uint16 charges = Strings::ToUnsignedInt(row[2]); - const uint32 color = Strings::ToUnsignedInt(row[3]); - - uint32 aug[EQ::invaug::SOCKET_COUNT]; - - aug[0] = Strings::ToUnsignedInt(row[4]); - aug[1] = Strings::ToUnsignedInt(row[5]); - aug[2] = Strings::ToUnsignedInt(row[6]); - aug[3] = Strings::ToUnsignedInt(row[7]); - aug[4] = Strings::ToUnsignedInt(row[8]); - aug[5] = Strings::ToUnsignedInt(row[9]); - - const bool instnodrop = (row[10] && static_cast(Strings::ToUnsignedInt(row[10]))); - - const uint32 ornament_icon = Strings::ToUnsignedInt(row[12]); - const uint32 ornament_idfile = Strings::ToUnsignedInt(row[13]); - uint32 ornament_hero_model = Strings::ToUnsignedInt(row[14]); - - const EQ::ItemData *item = GetItem(item_id); - + auto *item = GetItem(item_id); if (!item) { - LogError("Warning: charid [{}] has an invalid item_id [{}] in inventory slot [{}]", char_id, item_id, - slot_id); + LogError( + "Warning: charid [{}] has an invalid item_id [{}] in inventory slot [{}]", + char_id, + item_id, + slot_id + ); continue; } - EQ::ItemInstance *inst = CreateBaseItem(item, charges); - - if (inst == nullptr) + auto *inst = CreateBaseItem(item, charges); + if (!inst) { continue; + } - if (row[11]) { - std::string data_str(row[11]); - inst->SetCustomDataString(data_str); + if (!row.custom_data.empty()) { + inst->SetCustomDataString(row.custom_data); } inst->SetOrnamentIcon(ornament_icon); inst->SetOrnamentationIDFile(ornament_idfile); inst->SetOrnamentHeroModel(item->HerosForgeModel); - if (instnodrop || (inst->GetItem()->Attuneable && slot_id >= EQ::invslot::EQUIPMENT_BEGIN && slot_id <= EQ::invslot::EQUIPMENT_END)) + if (instnodrop || (inst->GetItem()->Attuneable && slot_id >= EQ::invslot::EQUIPMENT_BEGIN && slot_id <= + EQ::invslot::EQUIPMENT_END)) { inst->SetAttuned(true); + } - if (color > 0) + if (color > 0) { inst->SetColor(color); + } - if (charges == 0x7FFF) + if (charges == 0x7FFF) { inst->SetCharges(-1); - else if (charges == 0 && inst->IsStackable()) // Stackable items need a minimum charge of 1 remain moveable. + } + else if (charges == 0 && inst->IsStackable()) { + // Stackable items need a minimum charge of 1 remain moveable. inst->SetCharges(1); - else + } + else { inst->SetCharges(charges); + } if (item->RecastDelay) { if (item->RecastType != RECAST_TYPE_UNLINKED_ITEM && timestamps.count(item->RecastType)) { inst->SetRecastTimestamp(timestamps.at(item->RecastType)); - } else if (item->RecastType == RECAST_TYPE_UNLINKED_ITEM && timestamps.count(item->ID)) { + } + else if (item->RecastType == RECAST_TYPE_UNLINKED_ITEM && timestamps.count(item->ID)) { inst->SetRecastTimestamp(timestamps.at(item->ID)); } else { @@ -767,35 +778,50 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) if (item->IsClassCommon()) { for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) { - if (aug[i]) + if (aug[i]) { inst->PutAugment(this, i, aug[i]); + } } } int16 put_slot_id; if (slot_id >= 8000 && slot_id <= 8999) { put_slot_id = inv->PushCursor(*inst); - } else if (slot_id >= 3111 && slot_id <= 3179) { + } + else if (slot_id >= 3111 && slot_id <= 3179) { // Admins: please report any occurrences of this error - LogError("Warning: Defunct location for item in inventory: charid={}, item_id={}, slot_id={} .. pushing to cursor...", - char_id, item_id, slot_id); + LogError( + "Warning: Defunct location for item in inventory: charid={}, item_id={}, slot_id={} .. pushing to cursor...", + char_id, + item_id, + slot_id + ); put_slot_id = inv->PushCursor(*inst); - } else { + } + else { put_slot_id = inv->PutItem(slot_id, *inst); } + row.guid = inst->GetSerialNumber(); + queue.push_back(row); + safe_delete(inst); // Save ptr to item in inventory if (put_slot_id == INVALID_INDEX) { - LogError("Warning: Invalid slot_id for item in inventory: charid=[{}], item_id=[{}], slot_id=[{}]", - char_id, item_id, slot_id); + LogError( + "Warning: Invalid slot_id for item in inventory: charid=[{}], item_id=[{}], slot_id=[{}]", + char_id, + item_id, + slot_id + ); } } if (cv_conflict) { - const std::string& char_name = GetCharName(char_id); - LogError("ClientVersion/Expansion conflict during inventory load at zone entry for [{}] (charid: [{}], inver: [{}], gmi: [{}])", + const std::string &char_name = GetCharName(char_id); + LogError( + "ClientVersion/Expansion conflict during inventory load at zone entry for [{}] (charid: [{}], inver: [{}], gmi: [{}])", char_name, char_id, EQ::versions::MobVersionName(inv->InventoryVersion()), @@ -803,6 +829,12 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv) ); } + if (!queue.empty()) { + InventoryRepository::ReplaceMany(*this, queue); + } + + EQ::ItemInstance::ClearGUIDMap(); + // Retrieve shared inventory return GetSharedBank(char_id, inv, true); } diff --git a/zone/gm_commands/show/inventory.cpp b/zone/gm_commands/show/inventory.cpp index affcd4750..c2d22b2d9 100644 --- a/zone/gm_commands/show/inventory.cpp +++ b/zone/gm_commands/show/inventory.cpp @@ -160,23 +160,22 @@ void ShowInventory(Client *c, const Seperator *sep) linker.SetItemInst(inst_main); - if (item_data) { + if (item_data && inst_main) { + //auto inst = c->GetInv().GetItem(scope_bit & peekWorld ? EQ::invslot::WORLD_BEGIN + index_main : index_main); c->Message( Chat::White, fmt::format( "Slot {} | {} ({}/{}){}", - ((scope_bit & peekWorld) ? (EQ::invslot::WORLD_BEGIN + index_main) : index_main), + scope_bit & peekWorld ? EQ::invslot::WORLD_BEGIN + index_main : index_main, linker.GenerateLink(), item_data->ID, - c->GetInv().GetItem(((scope_bit &peekWorld) ? (EQ::invslot::WORLD_BEGIN + index_main) : index_main))->GetSerialNumber(), - ( - inst_main->IsStackable() && inst_main->GetCharges() > 0 ? - fmt::format( - " (Stack of {})", - inst_main->GetCharges() - ) : - "" - ) + inst_main->GetSerialNumber(), + inst_main->IsStackable() && inst_main->GetCharges() > 0 ? + fmt::format( + " (Stack of {})", + inst_main->GetCharges() + ) : + "" ).c_str() ); } @@ -239,7 +238,7 @@ void ShowInventory(Client *c, const Seperator *sep) sub_index, linker.GenerateLink(), item_data->ID, - c->GetInv().GetItem(EQ::InventoryProfile::CalcSlotId(index_main, sub_index))->GetSerialNumber(), + inst_sub->GetSerialNumber(), ( inst_sub->IsStackable() && inst_sub->GetCharges() > 0 ? fmt::format( diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 3e26e19cd..9916d3c65 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1327,7 +1327,8 @@ bool ZoneDatabase::SaveCharacterInvSnapshot(uint32 character_id) { " `custom_data`," " `ornamenticon`," " `ornamentidfile`," - " `ornament_hero_model`" + " `ornament_hero_model`," + " `guid`" ") " "SELECT" " %u," @@ -1346,7 +1347,8 @@ bool ZoneDatabase::SaveCharacterInvSnapshot(uint32 character_id) { " `custom_data`," " `ornamenticon`," " `ornamentidfile`," - " `ornament_hero_model` " + " `ornament_hero_model`," + " `guid` " "FROM" " `inventory` " "WHERE" @@ -1607,7 +1609,8 @@ bool ZoneDatabase::RestoreCharacterInvSnapshot(uint32 character_id, uint32 times " `custom_data`," " `ornamenticon`," " `ornamentidfile`," - " `ornament_hero_model`" + " `ornament_hero_model`," + " `guid`" ") " "SELECT" " `charid`," @@ -1625,7 +1628,8 @@ bool ZoneDatabase::RestoreCharacterInvSnapshot(uint32 character_id, uint32 times " `custom_data`," " `ornamenticon`," " `ornamentidfile`," - " `ornament_hero_model` " + " `ornament_hero_model`, " + " `guid` " "FROM" " `inventory_snapshots` " "WHERE"