[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
This commit is contained in:
Mitch Freeman 2024-08-26 23:58:07 -03:00 committed by GitHub
parent 8d8ef6d480
commit 3da24fffa4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 181 additions and 91 deletions

View File

@ -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`, ADD COLUMN `bucket_comparison` tinyint UNSIGNED NOT NULL DEFAULT 0 AFTER `bucket_value`,
DROP PRIMARY KEY, DROP PRIMARY KEY,
ADD PRIMARY KEY (`spell_id`) USING BTREE; 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 // -- template; copy/paste this when you need to create a new entry

View File

@ -32,10 +32,11 @@
//#include <iostream> //#include <iostream>
int32 NextItemInstSerialNumber = 1; int32 next_item_serial_number = 1;
std::unordered_set<uint64> guids{};
static inline int32 GetNextItemInstSerialNumber() {
static inline int32 GetNextItemInstSerialNumber()
{
// The Bazaar relies on each item a client has up for Trade having a unique // 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 // 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. // 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. // 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. // 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) if (next_item_serial_number >= INT32_MAX) {
NextItemInstSerialNumber = 1; next_item_serial_number = 1;
else }
NextItemInstSerialNumber++; 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; return stat;
} }
void EQ::ItemInstance::AddGUIDToMap(uint64 existing_serial_number)
{
guids.emplace(existing_serial_number);
}
void EQ::ItemInstance::ClearGUIDMap()
{
guids.clear();
}
// //
// class EvolveInfo // class EvolveInfo
// //

View File

@ -309,6 +309,8 @@ namespace EQ
int GetItemSkillsStat(EQ::skills::SkillType skill, bool augments = false) const; int GetItemSkillsStat(EQ::skills::SkillType skill, bool augments = false) const;
uint32 GetItemGuildFavor() const; uint32 GetItemGuildFavor() const;
std::vector<uint32> GetAugmentIDs() const; std::vector<uint32> GetAugmentIDs() const;
static void AddGUIDToMap(uint64 existing_serial_number);
static void ClearGUIDMap();
protected: protected:
////////////////////////// //////////////////////////

View File

@ -35,6 +35,7 @@ public:
uint32_t ornamenticon; uint32_t ornamenticon;
uint32_t ornamentidfile; uint32_t ornamentidfile;
int32_t ornament_hero_model; int32_t ornament_hero_model;
uint64_t guid;
}; };
static std::string PrimaryKey() static std::string PrimaryKey()
@ -61,6 +62,7 @@ public:
"ornamenticon", "ornamenticon",
"ornamentidfile", "ornamentidfile",
"ornament_hero_model", "ornament_hero_model",
"guid",
}; };
} }
@ -83,6 +85,7 @@ public:
"ornamenticon", "ornamenticon",
"ornamentidfile", "ornamentidfile",
"ornament_hero_model", "ornament_hero_model",
"guid",
}; };
} }
@ -139,6 +142,7 @@ public:
e.ornamenticon = 0; e.ornamenticon = 0;
e.ornamentidfile = 0; e.ornamentidfile = 0;
e.ornament_hero_model = 0; e.ornament_hero_model = 0;
e.guid = 0;
return e; return e;
} }
@ -191,6 +195,7 @@ public:
e.ornamenticon = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0; e.ornamenticon = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
e.ornamentidfile = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0; e.ornamentidfile = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.ornament_hero_model = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0; e.ornament_hero_model = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0;
e.guid = row[16] ? strtoull(row[16], nullptr, 10) : 0;
return e; return e;
} }
@ -240,6 +245,7 @@ public:
v.push_back(columns[13] + " = " + std::to_string(e.ornamenticon)); v.push_back(columns[13] + " = " + std::to_string(e.ornamenticon));
v.push_back(columns[14] + " = " + std::to_string(e.ornamentidfile)); 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[15] + " = " + std::to_string(e.ornament_hero_model));
v.push_back(columns[16] + " = " + std::to_string(e.guid));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@ -277,6 +283,7 @@ public:
v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile)); 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.ornament_hero_model));
v.push_back(std::to_string(e.guid));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@ -322,6 +329,7 @@ public:
v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile)); 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.ornament_hero_model));
v.push_back(std::to_string(e.guid));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
} }
@ -371,6 +379,7 @@ public:
e.ornamenticon = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0; e.ornamenticon = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
e.ornamentidfile = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0; e.ornamentidfile = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.ornament_hero_model = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0; e.ornament_hero_model = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0;
e.guid = row[16] ? strtoull(row[16], nullptr, 10) : 0;
all_entries.push_back(e); all_entries.push_back(e);
} }
@ -411,6 +420,7 @@ public:
e.ornamenticon = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0; e.ornamenticon = row[13] ? static_cast<uint32_t>(strtoul(row[13], nullptr, 10)) : 0;
e.ornamentidfile = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0; e.ornamentidfile = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.ornament_hero_model = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0; e.ornament_hero_model = row[15] ? static_cast<int32_t>(atoi(row[15])) : 0;
e.guid = row[16] ? strtoull(row[16], nullptr, 10) : 0;
all_entries.push_back(e); 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.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile)); 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.ornament_hero_model));
v.push_back(std::to_string(e.guid));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@ -539,6 +550,7 @@ public:
v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile)); 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.ornament_hero_model));
v.push_back(std::to_string(e.guid));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
} }

View File

@ -36,6 +36,7 @@ public:
uint32_t ornamenticon; uint32_t ornamenticon;
uint32_t ornamentidfile; uint32_t ornamentidfile;
int32_t ornament_hero_model; int32_t ornament_hero_model;
uint64_t guid;
}; };
static std::string PrimaryKey() static std::string PrimaryKey()
@ -63,6 +64,7 @@ public:
"ornamenticon", "ornamenticon",
"ornamentidfile", "ornamentidfile",
"ornament_hero_model", "ornament_hero_model",
"guid",
}; };
} }
@ -86,6 +88,7 @@ public:
"ornamenticon", "ornamenticon",
"ornamentidfile", "ornamentidfile",
"ornament_hero_model", "ornament_hero_model",
"guid",
}; };
} }
@ -143,6 +146,7 @@ public:
e.ornamenticon = 0; e.ornamenticon = 0;
e.ornamentidfile = 0; e.ornamentidfile = 0;
e.ornament_hero_model = 0; e.ornament_hero_model = 0;
e.guid = 0;
return e; return e;
} }
@ -196,6 +200,7 @@ public:
e.ornamenticon = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0; e.ornamenticon = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.ornamentidfile = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0; e.ornamentidfile = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
e.ornament_hero_model = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0; e.ornament_hero_model = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
e.guid = row[17] ? strtoull(row[17], nullptr, 10) : 0;
return e; return e;
} }
@ -246,6 +251,7 @@ public:
v.push_back(columns[14] + " = " + std::to_string(e.ornamenticon)); v.push_back(columns[14] + " = " + std::to_string(e.ornamenticon));
v.push_back(columns[15] + " = " + std::to_string(e.ornamentidfile)); 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[16] + " = " + std::to_string(e.ornament_hero_model));
v.push_back(columns[17] + " = " + std::to_string(e.guid));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@ -284,6 +290,7 @@ public:
v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile)); 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.ornament_hero_model));
v.push_back(std::to_string(e.guid));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@ -330,6 +337,7 @@ public:
v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile)); 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.ornament_hero_model));
v.push_back(std::to_string(e.guid));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
} }
@ -380,6 +388,7 @@ public:
e.ornamenticon = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0; e.ornamenticon = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.ornamentidfile = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0; e.ornamentidfile = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
e.ornament_hero_model = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0; e.ornament_hero_model = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
e.guid = row[17] ? strtoull(row[17], nullptr, 10) : 0;
all_entries.push_back(e); all_entries.push_back(e);
} }
@ -421,6 +430,7 @@ public:
e.ornamenticon = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0; e.ornamenticon = row[14] ? static_cast<uint32_t>(strtoul(row[14], nullptr, 10)) : 0;
e.ornamentidfile = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0; e.ornamentidfile = row[15] ? static_cast<uint32_t>(strtoul(row[15], nullptr, 10)) : 0;
e.ornament_hero_model = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0; e.ornament_hero_model = row[16] ? static_cast<int32_t>(atoi(row[16])) : 0;
e.guid = row[17] ? strtoull(row[17], nullptr, 10) : 0;
all_entries.push_back(e); 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.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile)); 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.ornament_hero_model));
v.push_back(std::to_string(e.guid));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@ -551,6 +562,7 @@ public:
v.push_back(std::to_string(e.ornamenticon)); v.push_back(std::to_string(e.ornamenticon));
v.push_back(std::to_string(e.ornamentidfile)); 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.ornament_hero_model));
v.push_back(std::to_string(e.guid));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
} }

View File

@ -46,6 +46,7 @@
#include "repositories/character_item_recast_repository.h" #include "repositories/character_item_recast_repository.h"
#include "repositories/character_corpses_repository.h" #include "repositories/character_corpses_repository.h"
#include "repositories/skill_caps_repository.h" #include "repositories/skill_caps_repository.h"
#include "repositories/inventory_repository.h"
namespace ItemField namespace ItemField
{ {
@ -300,15 +301,15 @@ bool SharedDatabase::UpdateInventorySlot(uint32 char_id, const EQ::ItemInstance*
// Update/Insert item // Update/Insert item
const std::string query = StringFormat("REPLACE INTO inventory " const std::string query = StringFormat("REPLACE INTO inventory "
"(charid, slotid, itemid, charges, instnodrop, custom_data, color, " "(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, " "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<unsigned long>(char_id), static_cast<unsigned long>(slot_id), static_cast<unsigned long>(inst->GetItem()->ID), static_cast<unsigned long>(char_id), static_cast<unsigned long>(slot_id), static_cast<unsigned long>(inst->GetItem()->ID),
static_cast<unsigned long>(charges), static_cast<unsigned long>(inst->IsAttuned() ? 1 : 0), static_cast<unsigned long>(charges), static_cast<unsigned long>(inst->IsAttuned() ? 1 : 0),
inst->GetCustomDataString().c_str(), static_cast<unsigned long>(inst->GetColor()), inst->GetCustomDataString().c_str(), static_cast<unsigned long>(inst->GetColor()),
static_cast<unsigned long>(augslot[0]), static_cast<unsigned long>(augslot[1]), static_cast<unsigned long>(augslot[2]), static_cast<unsigned long>(augslot[0]), static_cast<unsigned long>(augslot[1]), static_cast<unsigned long>(augslot[2]),
static_cast<unsigned long>(augslot[3]), static_cast<unsigned long>(augslot[4]), static_cast<unsigned long>(augslot[5]), static_cast<unsigned long>(inst->GetOrnamentationIcon()), static_cast<unsigned long>(augslot[3]), static_cast<unsigned long>(augslot[4]), static_cast<unsigned long>(augslot[5]), static_cast<unsigned long>(inst->GetOrnamentationIcon()),
static_cast<unsigned long>(inst->GetOrnamentationIDFile()), static_cast<unsigned long>(inst->GetOrnamentHeroModel())); static_cast<unsigned long>(inst->GetOrnamentationIDFile()), static_cast<unsigned long>(inst->GetOrnamentHeroModel()), inst->GetSerialNumber());
const auto results = QueryDatabase(query); const auto results = QueryDatabase(query);
// Save bag contents, if slot supports bag contents // Save bag contents, if slot supports bag contents
@ -651,48 +652,67 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv)
return false; return false;
// Retrieve character inventory // Retrieve character inventory
const std::string query = auto results = InventoryRepository::GetWhere(*this, fmt::format("`charid` = '{}' ORDER BY `slotid`;", char_id));
StringFormat("SELECT slotid, itemid, charges, color, augslot1, augslot2, augslot3, augslot4, augslot5, " if (results.empty()) {
"augslot6, instnodrop, custom_data, ornamenticon, ornamentidfile, ornament_hero_model FROM " LogError("Error loading inventory for char_id {} from the database.", char_id);
"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");
return false; return false;
} }
for (auto const &row: results) {
if (row.guid != 0) {
EQ::ItemInstance::AddGUIDToMap(row.guid);
}
}
const auto timestamps = GetItemRecastTimestamps(char_id); 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; std::vector<InventoryRepository::Inventory> queue{};
const auto pmask = inv->GetLookup()->PossessionsBitmask; for (auto &row: results) {
const auto bank_size = inv->GetLookup()->InventoryTypeSize.Bank; 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) { uint32 aug[EQ::invaug::SOCKET_COUNT];
int16 slot_id = Strings::ToInt(row[0]); 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<uint64>(1) << slot_id) & pmask) == 0) { if (((static_cast<uint64>(1) << slot_id) & pmask) == 0) {
cv_conflict = true; cv_conflict = true;
continue; continue;
} }
} }
else if (slot_id <= EQ::invbag::GENERAL_BAGS_END && slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN) { // Titanium thru UF check else if (slot_id <= EQ::invbag::GENERAL_BAGS_END && slot_id >= EQ::invbag::GENERAL_BAGS_BEGIN) {
const auto parent_slot = EQ::invslot::GENERAL_BEGIN + ((slot_id - EQ::invbag::GENERAL_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT); // 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<uint64>(1) << parent_slot) & pmask) == 0) { if (((static_cast<uint64>(1) << parent_slot) & pmask) == 0) {
cv_conflict = true; cv_conflict = true;
continue; 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) { if ((slot_id - EQ::invslot::BANK_BEGIN) >= bank_size) {
cv_conflict = true; cv_conflict = true;
continue; 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); const auto parent_index = ((slot_id - EQ::invbag::BANK_BAGS_BEGIN) / EQ::invbag::SLOT_COUNT);
if (parent_index >= bank_size) { if (parent_index >= bank_size) {
cv_conflict = true; cv_conflict = true;
@ -700,64 +720,55 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv)
} }
} }
uint32 item_id = Strings::ToUnsignedInt(row[1]); auto *item = GetItem(item_id);
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<uint16>(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);
if (!item) { if (!item) {
LogError("Warning: charid [{}] has an invalid item_id [{}] in inventory slot [{}]", char_id, item_id, LogError(
slot_id); "Warning: charid [{}] has an invalid item_id [{}] in inventory slot [{}]",
char_id,
item_id,
slot_id
);
continue; continue;
} }
EQ::ItemInstance *inst = CreateBaseItem(item, charges); auto *inst = CreateBaseItem(item, charges);
if (!inst) {
if (inst == nullptr)
continue; continue;
}
if (row[11]) { if (!row.custom_data.empty()) {
std::string data_str(row[11]); inst->SetCustomDataString(row.custom_data);
inst->SetCustomDataString(data_str);
} }
inst->SetOrnamentIcon(ornament_icon); inst->SetOrnamentIcon(ornament_icon);
inst->SetOrnamentationIDFile(ornament_idfile); inst->SetOrnamentationIDFile(ornament_idfile);
inst->SetOrnamentHeroModel(item->HerosForgeModel); 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); inst->SetAttuned(true);
}
if (color > 0) if (color > 0) {
inst->SetColor(color); inst->SetColor(color);
}
if (charges == 0x7FFF) if (charges == 0x7FFF) {
inst->SetCharges(-1); 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); inst->SetCharges(1);
else }
else {
inst->SetCharges(charges); inst->SetCharges(charges);
}
if (item->RecastDelay) { if (item->RecastDelay) {
if (item->RecastType != RECAST_TYPE_UNLINKED_ITEM && timestamps.count(item->RecastType)) { if (item->RecastType != RECAST_TYPE_UNLINKED_ITEM && timestamps.count(item->RecastType)) {
inst->SetRecastTimestamp(timestamps.at(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)); inst->SetRecastTimestamp(timestamps.at(item->ID));
} }
else { else {
@ -767,35 +778,50 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQ::InventoryProfile *inv)
if (item->IsClassCommon()) { if (item->IsClassCommon()) {
for (int i = EQ::invaug::SOCKET_BEGIN; i <= EQ::invaug::SOCKET_END; i++) { 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]); inst->PutAugment(this, i, aug[i]);
}
} }
} }
int16 put_slot_id; int16 put_slot_id;
if (slot_id >= 8000 && slot_id <= 8999) { if (slot_id >= 8000 && slot_id <= 8999) {
put_slot_id = inv->PushCursor(*inst); 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 // Admins: please report any occurrences of this error
LogError("Warning: Defunct location for item in inventory: charid={}, item_id={}, slot_id={} .. pushing to cursor...", LogError(
char_id, item_id, slot_id); "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); put_slot_id = inv->PushCursor(*inst);
} else { }
else {
put_slot_id = inv->PutItem(slot_id, *inst); put_slot_id = inv->PutItem(slot_id, *inst);
} }
row.guid = inst->GetSerialNumber();
queue.push_back(row);
safe_delete(inst); safe_delete(inst);
// Save ptr to item in inventory // Save ptr to item in inventory
if (put_slot_id == INVALID_INDEX) { if (put_slot_id == INVALID_INDEX) {
LogError("Warning: Invalid slot_id for item in inventory: charid=[{}], item_id=[{}], slot_id=[{}]", LogError(
char_id, item_id, slot_id); "Warning: Invalid slot_id for item in inventory: charid=[{}], item_id=[{}], slot_id=[{}]",
char_id,
item_id,
slot_id
);
} }
} }
if (cv_conflict) { if (cv_conflict) {
const std::string& char_name = GetCharName(char_id); const std::string &char_name = GetCharName(char_id);
LogError("ClientVersion/Expansion conflict during inventory load at zone entry for [{}] (charid: [{}], inver: [{}], gmi: [{}])", LogError(
"ClientVersion/Expansion conflict during inventory load at zone entry for [{}] (charid: [{}], inver: [{}], gmi: [{}])",
char_name, char_name,
char_id, char_id,
EQ::versions::MobVersionName(inv->InventoryVersion()), 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 // Retrieve shared inventory
return GetSharedBank(char_id, inv, true); return GetSharedBank(char_id, inv, true);
} }

View File

@ -160,23 +160,22 @@ void ShowInventory(Client *c, const Seperator *sep)
linker.SetItemInst(inst_main); 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( c->Message(
Chat::White, Chat::White,
fmt::format( fmt::format(
"Slot {} | {} ({}/{}){}", "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(), linker.GenerateLink(),
item_data->ID, item_data->ID,
c->GetInv().GetItem(((scope_bit &peekWorld) ? (EQ::invslot::WORLD_BEGIN + index_main) : index_main))->GetSerialNumber(), inst_main->GetSerialNumber(),
( inst_main->IsStackable() && inst_main->GetCharges() > 0 ?
inst_main->IsStackable() && inst_main->GetCharges() > 0 ? fmt::format(
fmt::format( " (Stack of {})",
" (Stack of {})", inst_main->GetCharges()
inst_main->GetCharges() ) :
) : ""
""
)
).c_str() ).c_str()
); );
} }
@ -239,7 +238,7 @@ void ShowInventory(Client *c, const Seperator *sep)
sub_index, sub_index,
linker.GenerateLink(), linker.GenerateLink(),
item_data->ID, item_data->ID,
c->GetInv().GetItem(EQ::InventoryProfile::CalcSlotId(index_main, sub_index))->GetSerialNumber(), inst_sub->GetSerialNumber(),
( (
inst_sub->IsStackable() && inst_sub->GetCharges() > 0 ? inst_sub->IsStackable() && inst_sub->GetCharges() > 0 ?
fmt::format( fmt::format(

View File

@ -1327,7 +1327,8 @@ bool ZoneDatabase::SaveCharacterInvSnapshot(uint32 character_id) {
" `custom_data`," " `custom_data`,"
" `ornamenticon`," " `ornamenticon`,"
" `ornamentidfile`," " `ornamentidfile`,"
" `ornament_hero_model`" " `ornament_hero_model`,"
" `guid`"
") " ") "
"SELECT" "SELECT"
" %u," " %u,"
@ -1346,7 +1347,8 @@ bool ZoneDatabase::SaveCharacterInvSnapshot(uint32 character_id) {
" `custom_data`," " `custom_data`,"
" `ornamenticon`," " `ornamenticon`,"
" `ornamentidfile`," " `ornamentidfile`,"
" `ornament_hero_model` " " `ornament_hero_model`,"
" `guid` "
"FROM" "FROM"
" `inventory` " " `inventory` "
"WHERE" "WHERE"
@ -1607,7 +1609,8 @@ bool ZoneDatabase::RestoreCharacterInvSnapshot(uint32 character_id, uint32 times
" `custom_data`," " `custom_data`,"
" `ornamenticon`," " `ornamenticon`,"
" `ornamentidfile`," " `ornamentidfile`,"
" `ornament_hero_model`" " `ornament_hero_model`,"
" `guid`"
") " ") "
"SELECT" "SELECT"
" `charid`," " `charid`,"
@ -1625,7 +1628,8 @@ bool ZoneDatabase::RestoreCharacterInvSnapshot(uint32 character_id, uint32 times
" `custom_data`," " `custom_data`,"
" `ornamenticon`," " `ornamenticon`,"
" `ornamentidfile`," " `ornamentidfile`,"
" `ornament_hero_model` " " `ornament_hero_model`, "
" `guid` "
"FROM" "FROM"
" `inventory_snapshots` " " `inventory_snapshots` "
"WHERE" "WHERE"