From c5dbd1a0c5fc33a0bd93e4e184e3cd08fed59d77 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Fri, 28 Mar 2025 21:51:10 -0300 Subject: [PATCH 01/48] Pass 1 - working hash generator and saving to inventory table --- common/database/database_update_manifest.cpp | 14 ++++ common/inventory_profile.cpp | 3 + common/item_instance.cpp | 39 +++++++--- common/item_instance.h | 60 +++++++++++++++- common/patches/rof2.cpp | 8 ++- .../base/base_inventory_repository.h | 72 ++++++++++++------- common/shareddb.cpp | 22 +++--- world/client.cpp | 2 +- zone/gm_commands/show/inventory.cpp | 4 +- 9 files changed, 171 insertions(+), 53 deletions(-) diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 15dc4a27f..226127b2e 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -7183,6 +7183,20 @@ ALTER TABLE `character_parcels` ALTER TABLE `character_parcels_containers` ADD COLUMN `evolve_amount` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `quantity`; +)", + .content_schema_update = false + }, + ManifestEntry{ + .version = 9329, + .description = "2025_03_27_implement_item_unique_serial_number.sql", + .check = "SHOW COLUMNS FROM `inventory` LIKE 'serial_number'", + .condition = "empty", + .match = "", + .sql = R"( +ALTER TABLE `inventory` + DROP COLUMN `guid`, + ADD COLUMN `serial_number` VARCHAR(16) NULL DEFAULT NULL AFTER `ornament_hero_model`, + ADD UNIQUE INDEX `idx_serial_number` (`serial_number`); )", .content_schema_update = false }, diff --git a/common/inventory_profile.cpp b/common/inventory_profile.cpp index 7017184d9..bbc69c1a7 100644 --- a/common/inventory_profile.cpp +++ b/common/inventory_profile.cpp @@ -252,6 +252,9 @@ int16 EQ::InventoryProfile::PutItem(int16 slot_id, const ItemInstance& inst) return slot_id; } + // if (inst.GetSerialNumber2().empty()) { + // inst.GenerateUniqueSerialNumber(); + // } // Delegate to internal method return _PutItem(slot_id, inst.Clone()); } diff --git a/common/item_instance.cpp b/common/item_instance.cpp index bb048b1fb..c9d48e75c 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -18,14 +18,18 @@ #include "inventory_profile.h" #include "../common/data_verification.h" -//#include "classes.h" -//#include "global_define.h" -//#include "item_instance.h" -//#include "races.h" #include "rulesys.h" #include "shareddb.h" #include "strings.h" #include "evolving_items.h" +#include +#include +#include +#include +#include +#include +#include +#include //#include "../common/light_source.h" @@ -35,6 +39,7 @@ int32 next_item_serial_number = 1; std::unordered_set guids{}; +std::mutex EQ::UniqueHashGenerator::mtx; static inline int32 GetNextItemInstSerialNumber() { @@ -65,8 +70,8 @@ static inline int32 GetNextItemInstSerialNumber() // // class EQ::ItemInstance // -EQ::ItemInstance::ItemInstance(const ItemData* item, int16 charges) { - +EQ::ItemInstance::ItemInstance(const ItemData* item, int16 charges) +{ if (item) { m_item = new ItemData(*item); } @@ -84,8 +89,8 @@ EQ::ItemInstance::ItemInstance(const ItemData* item, int16 charges) { m_SerialNumber = GetNextItemInstSerialNumber(); } -EQ::ItemInstance::ItemInstance(SharedDatabase *db, uint32 item_id, int16 charges) { - +EQ::ItemInstance::ItemInstance(SharedDatabase *db, uint32 item_id, int16 charges) +{ m_item = db->GetItem(item_id); if (m_item) { @@ -149,9 +154,13 @@ EQ::ItemInstance::ItemInstance(const ItemInstance& copy) m_custom_data[iter->first] = iter->second; } - m_SerialNumber = copy.m_SerialNumber; - m_custom_data = copy.m_custom_data; - m_timers = copy.m_timers; + m_SerialNumber = copy.m_SerialNumber; + m_custom_data = copy.m_custom_data; + m_timers = copy.m_timers; + + if (GetSerialNumber2().empty()) { + CreateSerialNumber2(); + } m_exp = copy.m_exp; m_evolveLvl = copy.m_evolveLvl; @@ -2001,3 +2010,11 @@ void EQ::ItemInstance::SetEvolveEquipped(const bool in) const GetTimers().at("evolve").Disable(); } + +std::string EQ::ItemInstance::GenerateUniqueSerialNumber() +{ + std::string unique_hash = UniqueHashGenerator::generate(); + + LogInventoryDetail("Generated an item serial number {}", unique_hash); + return unique_hash; +} \ No newline at end of file diff --git a/common/item_instance.h b/common/item_instance.h index 99edfb570..1f9b12ceb 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -23,6 +23,10 @@ #ifndef COMMON_ITEM_INSTANCE_H #define COMMON_ITEM_INSTANCE_H +#include +#include + + #include "evolving_items.h" @@ -228,8 +232,15 @@ namespace EQ std::string Serialize(int16 slot_id) const { InternalSerializedItem_Struct s; s.slot_id = slot_id; s.inst = (const void*)this; std::string ser; ser.assign((char*)&s, sizeof(InternalSerializedItem_Struct)); return ser; } void Serialize(OutBuffer& ob, int16 slot_id) const { InternalSerializedItem_Struct isi; isi.slot_id = slot_id; isi.inst = (const void*)this; ob.write((const char*)&isi, sizeof(isi)); } - inline int32 GetSerialNumber() const { return m_SerialNumber; } - inline void SetSerialNumber(int32 id) { m_SerialNumber = id; } + int32 GetSerialNumber() const { return m_SerialNumber; } + void SetSerialNumber(int32 id) { m_SerialNumber = id; } + const std::string &GetSerialNumber2() const { return m_serial_number2; } + void SetSerialNumber2(std::string sn) { m_serial_number2 = std::move(sn); } + + void CreateSerialNumber2() + { + m_serial_number2 = GenerateUniqueSerialNumber(); + } std::map& GetTimers() const { return m_timers; } void SetTimer(std::string name, uint32 time); @@ -346,7 +357,8 @@ namespace EQ std::map::const_iterator _cbegin() { return m_contents.cbegin(); } std::map::const_iterator _cend() { return m_contents.cend(); } - void _PutItem(uint8 index, ItemInstance* inst) { m_contents[index] = inst; } + void _PutItem(uint8 index, ItemInstance *inst) { m_contents[index] = inst; } + static std::string GenerateUniqueSerialNumber(); ItemInstTypes m_use_type{ItemInstNormal};// Usage type for item const ItemData * m_item{nullptr}; // Ptr to item data @@ -358,6 +370,7 @@ namespace EQ bool m_attuned{false}; int32 m_merchantcount{1};//number avaliable on the merchant, -1=unlimited int32 m_SerialNumber{0}; // Unique identifier for this instance of an item. Needed for Bazaar. + std::string m_serial_number2{}; // unique serial number across all zones/world TESTING March 2025 uint32 m_exp{0}; int8 m_evolveLvl{0}; ItemData * m_scaledItem{nullptr}; @@ -375,5 +388,46 @@ namespace EQ std::map m_custom_data {}; mutable std::map m_timers {}; }; + + class UniqueHashGenerator + { + private: + static constexpr char ALPHANUM[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + static constexpr size_t ALPHANUM_SIZE = sizeof(ALPHANUM) - 1; + static std::mutex mtx; + + // Generate machine-specific seed + static uint64_t getMachineSeed() + { + std::random_device rd; + return (static_cast(rd()) << 32) | rd(); + } + + public: + static std::string generate() + { + std::lock_guard lock(mtx); + + // Get current timestamp in nanoseconds + auto now = std::chrono::high_resolution_clock::now(); + auto nanos = std::chrono::duration_cast(now.time_since_epoch()).count(); + + // Combine timestamp with machine-specific data + static const uint64_t machine_seed = getMachineSeed(); + uint64_t seed = nanos ^ machine_seed ^ std::hash{}(std::this_thread::get_id()); + + // Generate random component + std::mt19937_64 rng(seed); + std::uniform_int_distribution dist(0, ALPHANUM_SIZE - 1); + + // Create 16-byte result + std::array result; + for (int i = 0; i < 16; ++i) { + result[i] = ALPHANUM[dist(rng)]; + } + + return std::string(result.begin(), result.end()); + } + }; } #endif /*COMMON_ITEM_INSTANCE_H*/ diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index b250a5de6..f0b7786f6 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -6446,7 +6446,13 @@ namespace RoF2 RoF2::structs::ItemSerializationHeader hdr; //sprintf(hdr.unknown000, "06e0002Y1W00"); - strn0cpy(hdr.unknown000, fmt::format("{:016}\0", inst->GetSerialNumber()).c_str(),sizeof(hdr.unknown000)); + // strn0cpy(hdr.unknown000, fmt::format("{:016}\0", inst->GetSerialNumber()).c_str(),sizeof(hdr.unknown000)); + strn0cpy( + hdr.unknown000, + inst->GetSerialNumber2().empty() ? "0000000000000000" : inst->GetSerialNumber2().c_str(), + sizeof(hdr.unknown000) + ); + hdr.unknown000[16] = '\0'; hdr.stacksize = 1; diff --git a/common/repositories/base/base_inventory_repository.h b/common/repositories/base/base_inventory_repository.h index 9a4b5122b..975fec324 100644 --- a/common/repositories/base/base_inventory_repository.h +++ b/common/repositories/base/base_inventory_repository.h @@ -32,10 +32,12 @@ public: uint32_t augment_six; uint8_t instnodrop; std::string custom_data; + uint32_t ornamenticon; + uint32_t ornamentidfile; uint32_t ornament_icon; uint32_t ornament_idfile; int32_t ornament_hero_model; - uint64_t guid; + std::string serial_number; }; static std::string PrimaryKey() @@ -59,10 +61,12 @@ public: "augment_six", "instnodrop", "custom_data", + "ornamenticon", + "ornamentidfile", "ornament_icon", "ornament_idfile", "ornament_hero_model", - "guid", + "serial_number", }; } @@ -82,10 +86,12 @@ public: "augment_six", "instnodrop", "custom_data", + "ornamenticon", + "ornamentidfile", "ornament_icon", "ornament_idfile", "ornament_hero_model", - "guid", + "serial_number", }; } @@ -139,10 +145,12 @@ public: e.augment_six = 0; e.instnodrop = 0; e.custom_data = ""; + e.ornamenticon = 0; + e.ornamentidfile = 0; e.ornament_icon = 0; e.ornament_idfile = 0; e.ornament_hero_model = 0; - e.guid = 0; + e.serial_number = ""; return e; } @@ -192,10 +200,12 @@ public: e.augment_six = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; e.instnodrop = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.custom_data = row[12] ? row[12] : ""; - e.ornament_icon = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; - e.ornament_idfile = 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; + 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_icon = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; + e.ornament_idfile = row[16] ? static_cast(strtoul(row[16], nullptr, 10)) : 0; + e.ornament_hero_model = row[17] ? static_cast(atoi(row[17])) : 0; + e.serial_number = row[18] ? row[18] : ""; return e; } @@ -242,10 +252,12 @@ public: v.push_back(columns[10] + " = " + std::to_string(e.augment_six)); v.push_back(columns[11] + " = " + std::to_string(e.instnodrop)); v.push_back(columns[12] + " = '" + Strings::Escape(e.custom_data) + "'"); - v.push_back(columns[13] + " = " + std::to_string(e.ornament_icon)); - v.push_back(columns[14] + " = " + std::to_string(e.ornament_idfile)); - v.push_back(columns[15] + " = " + std::to_string(e.ornament_hero_model)); - v.push_back(columns[16] + " = " + std::to_string(e.guid)); + 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_icon)); + v.push_back(columns[16] + " = " + std::to_string(e.ornament_idfile)); + v.push_back(columns[17] + " = " + std::to_string(e.ornament_hero_model)); + v.push_back(columns[18] + " = '" + Strings::Escape(e.serial_number) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -280,10 +292,12 @@ public: v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.instnodrop)); v.push_back("'" + Strings::Escape(e.custom_data) + "'"); + v.push_back(std::to_string(e.ornamenticon)); + v.push_back(std::to_string(e.ornamentidfile)); v.push_back(std::to_string(e.ornament_icon)); v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); - v.push_back(std::to_string(e.guid)); + v.push_back("'" + Strings::Escape(e.serial_number) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -326,10 +340,12 @@ public: v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.instnodrop)); v.push_back("'" + Strings::Escape(e.custom_data) + "'"); + v.push_back(std::to_string(e.ornamenticon)); + v.push_back(std::to_string(e.ornamentidfile)); v.push_back(std::to_string(e.ornament_icon)); v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); - v.push_back(std::to_string(e.guid)); + v.push_back("'" + Strings::Escape(e.serial_number) + "'"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -376,10 +392,12 @@ public: e.augment_six = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; e.instnodrop = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.custom_data = row[12] ? row[12] : ""; - e.ornament_icon = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; - e.ornament_idfile = 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; + 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_icon = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; + e.ornament_idfile = row[16] ? static_cast(strtoul(row[16], nullptr, 10)) : 0; + e.ornament_hero_model = row[17] ? static_cast(atoi(row[17])) : 0; + e.serial_number = row[18] ? row[18] : ""; all_entries.push_back(e); } @@ -417,10 +435,12 @@ public: e.augment_six = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; e.instnodrop = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.custom_data = row[12] ? row[12] : ""; - e.ornament_icon = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; - e.ornament_idfile = 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; + 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_icon = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; + e.ornament_idfile = row[16] ? static_cast(strtoul(row[16], nullptr, 10)) : 0; + e.ornament_hero_model = row[17] ? static_cast(atoi(row[17])) : 0; + e.serial_number = row[18] ? row[18] : ""; all_entries.push_back(e); } @@ -508,10 +528,12 @@ public: v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.instnodrop)); v.push_back("'" + Strings::Escape(e.custom_data) + "'"); + v.push_back(std::to_string(e.ornamenticon)); + v.push_back(std::to_string(e.ornamentidfile)); v.push_back(std::to_string(e.ornament_icon)); v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); - v.push_back(std::to_string(e.guid)); + v.push_back("'" + Strings::Escape(e.serial_number) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -547,10 +569,12 @@ public: v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.instnodrop)); v.push_back("'" + Strings::Escape(e.custom_data) + "'"); + v.push_back(std::to_string(e.ornamenticon)); + v.push_back(std::to_string(e.ornamentidfile)); v.push_back(std::to_string(e.ornament_icon)); v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); - v.push_back(std::to_string(e.guid)); + v.push_back("'" + Strings::Escape(e.serial_number) + "'"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 0ef0cbe4e..5e52c2a5f 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -285,7 +285,7 @@ bool SharedDatabase::UpdateInventorySlot(uint32 char_id, const EQ::ItemInstance* e.ornament_icon = inst->GetOrnamentationIcon(); e.ornament_idfile = inst->GetOrnamentationIDFile(); e.ornament_hero_model = inst->GetOrnamentHeroModel(); - e.guid = inst->GetSerialNumber(); + e.serial_number = inst->GetSerialNumber2(); const int replaced = InventoryRepository::ReplaceOne(*this, e); @@ -640,12 +640,6 @@ bool SharedDatabase::GetInventory(Client *c) 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; @@ -723,6 +717,15 @@ bool SharedDatabase::GetInventory(Client *c) inst->SetOrnamentationIDFile(ornament_idfile); inst->SetOrnamentHeroModel(item->HerosForgeModel); + if (row.serial_number.empty()) { + inst->CreateSerialNumber2(); + row.serial_number = inst->GetSerialNumber2(); + queue.push_back(row); + } + else { + inst->SetSerialNumber2(row.serial_number); + } + if ( instnodrop || ( @@ -818,8 +821,7 @@ bool SharedDatabase::GetInventory(Client *c) put_slot_id = inv.PutItem(slot_id, *inst); } - row.guid = inst->GetSerialNumber(); - queue.push_back(row); + //queue.push_back(row); safe_delete(inst); @@ -849,8 +851,6 @@ bool SharedDatabase::GetInventory(Client *c) InventoryRepository::ReplaceMany(*this, queue); } - EQ::ItemInstance::ClearGUIDMap(); - // Retrieve shared inventory return GetSharedBank(char_id, &inv, true); } diff --git a/world/client.cpp b/world/client.cpp index a2d35a485..94746bee7 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -2400,7 +2400,7 @@ bool Client::StoreCharacter( e.ornament_icon = inst->GetOrnamentationIcon(); e.ornament_idfile = inst->GetOrnamentationIDFile(); e.ornament_hero_model = inst->GetOrnamentHeroModel(); - e.guid = inst->GetSerialNumber(); + e.serial_number = inst->GetSerialNumber2(); v.emplace_back(e); } diff --git a/zone/gm_commands/show/inventory.cpp b/zone/gm_commands/show/inventory.cpp index 2a3e2a377..655f796df 100644 --- a/zone/gm_commands/show/inventory.cpp +++ b/zone/gm_commands/show/inventory.cpp @@ -185,7 +185,7 @@ void ShowInventory(Client *c, const Seperator *sep) scope_bit & peekWorld ? EQ::invslot::WORLD_BEGIN + index_main : index_main, linker.GenerateLink(), item_data->ID, - inst_main->GetSerialNumber(), + inst_main->GetSerialNumber2().c_str(), inst_main->IsStackable() && inst_main->GetCharges() > 0 ? fmt::format( " (Stack of {})", @@ -254,7 +254,7 @@ void ShowInventory(Client *c, const Seperator *sep) sub_index, linker.GenerateLink(), item_data->ID, - inst_sub->GetSerialNumber(), + inst_sub->GetSerialNumber2().c_str(), ( inst_sub->IsStackable() && inst_sub->GetCharges() > 0 ? fmt::format( From 51b40d5fdcf5e4b5545744fd0f9c337e11d32638 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sat, 29 Mar 2025 21:36:53 -0300 Subject: [PATCH 02/48] Pass 2 - change serial_number to string --- common/bazaar.cpp | 2 +- common/eq_packet_structs.h | 28 +++- common/item_instance.cpp | 3 +- common/item_instance.h | 1 + common/patches/rof2.cpp | 80 ++++++----- common/patches/rof2_structs.h | 4 +- common/patches/titanium.cpp | 4 +- common/patches/uf.cpp | 4 +- .../base/base_trader_repository.h | 54 ++++---- common/repositories/trader_repository.h | 24 +--- zone/client.cpp | 4 +- zone/client.h | 14 +- zone/client_evolving_items.cpp | 6 +- zone/client_packet.cpp | 3 +- zone/parcels.cpp | 2 +- zone/trading.cpp | 130 ++++++++++-------- zone/worldserver.cpp | 6 +- zone/zonedb.cpp | 6 +- zone/zonedb.h | 2 +- 19 files changed, 207 insertions(+), 170 deletions(-) diff --git a/common/bazaar.cpp b/common/bazaar.cpp index 8244d5c52..c3c828be6 100644 --- a/common/bazaar.cpp +++ b/common/bazaar.cpp @@ -307,7 +307,7 @@ Bazaar::GetSearchResults( r.trader_zone_id = t.trader.char_zone_id; r.trader_zone_instance_id = t.trader.char_zone_instance_id; r.trader_entity_id = t.trader.char_entity_id; - r.serial_number_RoF = fmt::format("{:016}\0", t.trader.item_sn); + r.serial_number_RoF = t.trader.item_sn; r.item_name = fmt::format("{:.63}\0", item_results.at(t.trader.item_id).name); r.trader_name = fmt::format("{:.63}\0", t.trader_name); r.item_stat = item_results.at(t.trader.item_id).stats; diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 0a1349254..cb05be8b2 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -3093,7 +3093,7 @@ struct BazaarSearchCriteria_Struct { struct BazaarInspect_Struct { uint32 action; char player_name[64]; - uint32 serial_number; + char serial_number[16]; uint32 item_id; uint32 trader_id; }; @@ -3742,6 +3742,28 @@ struct GetItems_Struct{ int32 charges[EQ::invtype::BAZAAR_SIZE]; }; +struct Trader2_Struct { + uint32 action; + uint32 unknown_004; + uint64 items[EQ::invtype::BAZAAR_SIZE]; + uint32 item_cost[EQ::invtype::BAZAAR_SIZE]; + std::string serial_number[EQ::invtype::BAZAAR_SIZE]; +}; + +struct ClickTrader2_Struct { + uint32 action; + uint32 unknown_004; + uint64 items[EQ::invtype::BAZAAR_SIZE]; + uint32 item_cost[EQ::invtype::BAZAAR_SIZE]; + std::string serial_number[EQ::invtype::BAZAAR_SIZE]; +}; + +struct GetItems2_Struct { + uint64 items[EQ::invtype::BAZAAR_SIZE]; + std::string serial_number[EQ::invtype::BAZAAR_SIZE]; + uint32 charges[EQ::invtype::BAZAAR_SIZE]; +}; + struct BecomeTrader_Struct { uint32 action; uint16 zone_id; @@ -3795,7 +3817,7 @@ struct TraderItemUpdate_Struct{ struct TraderPriceUpdate_Struct { /*000*/ uint32 Action; /*004*/ uint32 SubAction; -/*008*/ int32 SerialNumber; +/*008*/ char serial_number[16]; /*012*/ uint32 Unknown012; /*016*/ uint32 NewPrice; /*020*/ uint32 Unknown016; @@ -6418,7 +6440,7 @@ struct BazaarSearchResultsFromDB_Struct { uint32 count; uint32 trader_id; uint32 item_id; - uint32 serial_number; + std::string serial_number; uint32 charges; uint32 cost; uint32 slot_id; diff --git a/common/item_instance.cpp b/common/item_instance.cpp index c9d48e75c..5aadebf63 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -158,7 +158,8 @@ EQ::ItemInstance::ItemInstance(const ItemInstance& copy) m_custom_data = copy.m_custom_data; m_timers = copy.m_timers; - if (GetSerialNumber2().empty()) { + m_serial_number2 = copy.m_serial_number2; + if (copy.GetSerialNumber2().empty()) { CreateSerialNumber2(); } diff --git a/common/item_instance.h b/common/item_instance.h index 1f9b12ceb..25d977fd5 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -235,6 +235,7 @@ namespace EQ int32 GetSerialNumber() const { return m_SerialNumber; } void SetSerialNumber(int32 id) { m_SerialNumber = id; } const std::string &GetSerialNumber2() const { return m_serial_number2; } + //std::string &GetSerialNumber2() const { return m_serial_number2; } void SetSerialNumber2(std::string sn) { m_serial_number2 = std::move(sn); } void CreateSerialNumber2() diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index f0b7786f6..c8e8eb6d6 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -4256,6 +4256,7 @@ namespace RoF2 OUT_str(seller_name); OUT_str(item_name); OUT_str(serial_number); + //strn0cpy(eq->serial_number, emu->serial_number.c_str(), sizeof(eq->serial_number)); FINISH_ENCODE(); } @@ -4321,6 +4322,7 @@ namespace RoF2 OUT_str(seller_name); OUT_str(item_name); OUT_str(serial_number); + //strn0cpy(eq->serial_number, emu->serial_number.c_str(), sizeof(eq->serial_number)); FINISH_ENCODE(); break; @@ -6152,19 +6154,20 @@ namespace RoF2 switch (action) { case structs::RoF2BazaarTraderBuyerActions::BeginTraderMode: { DECODE_LENGTH_EXACT(structs::BeginTrader_Struct); - SETUP_DIRECT_DECODE(ClickTrader_Struct, structs::BeginTrader_Struct); + SETUP_DIRECT_DECODE(ClickTrader2_Struct, structs::BeginTrader_Struct); LogTrading("(RoF2) BeginTraderMode action [{}]", action); emu->action = TraderOn; std::copy_n(eq->item_cost, RoF2::invtype::BAZAAR_SIZE, emu->item_cost); - std::transform( - std::begin(eq->items), - std::end(eq->items), - std::begin(emu->serial_number), - [&](const structs::TraderItemSerial_Struct x) { - return Strings::ToUnsignedBigInt(x.serial_number,0); - } - ); + std::copy_n(eq->items->serial_number, RoF2::invtype::BAZAAR_SIZE, emu->serial_number); + // std::transform( + // std::begin(eq->items), + // std::end(eq->items), + // std::begin(emu->serial_number), + // [&](const structs::TraderItemSerial_Struct x) { + // return Strings::ToUnsignedBigInt(x.serial_number,0); + // } + // ); FINISH_DIRECT_DECODE(); break; @@ -6190,10 +6193,11 @@ namespace RoF2 LogTrading("(RoF2) PriceUpdate action [{}]", action); emu->Action = PriceUpdate; - emu->SerialNumber = Strings::ToUnsignedBigInt(eq->serial_number, 0); - if (emu->SerialNumber == 0) { - LogTrading("(RoF2) Price change with invalid serial number [{}]", eq->serial_number); - } + strn0cpy(emu->serial_number, eq->serial_number, sizeof(emu->serial_number)); + //FIXemu->serial_number = Strings::ToUnsignedBigInt(eq->serial_number, 0); + // if (emu->SerialNumber == 0) { + // LogTrading("(RoF2) Price change with invalid serial number [{}]", eq->serial_number); + // } emu->NewPrice = eq->new_price; FINISH_DIRECT_DECODE(); @@ -6284,22 +6288,23 @@ namespace RoF2 IN(item_id); IN(trader_id); emu->action = BazaarInspect; - emu->serial_number = Strings::ToUnsignedInt(eq->serial_number, 0); - if (emu->serial_number == 0) { - LogTrading( - "(RoF2) trader_id = [{}] requested a BazaarInspect with an invalid serial number of [{}]", - eq->trader_id, - eq->serial_number - ); - FINISH_DIRECT_DECODE(); - return; - } + strn0cpy(emu->serial_number, eq->serial_number, sizeof(emu->serial_number)); + //FIX emu->serial_number = Strings::ToUnsignedInt(eq->serial_number, 0); + // if (emu->serial_number == 0) { + // LogTrading( + // "(RoF2) trader_id = [{}] requested a BazaarInspect with an invalid serial number of [{}]", + // eq->trader_id, + // eq->serial_number + // ); + // FINISH_DIRECT_DECODE(); + // return; + // } - LogTrading("(RoF2) BazaarInspect action [{}] item_id [{}] serial_number [{}]", - action, - eq->item_id, - eq->serial_number - ); + // LogTrading("(RoF2) BazaarInspect action [{}] item_id [{}] serial_number [{}]", + // action, + // eq->item_id, + // eq->serial_number + // ); FINISH_DIRECT_DECODE(); break; } @@ -6335,7 +6340,9 @@ namespace RoF2 IN_str(buyer_name); IN_str(seller_name); IN_str(item_name); - IN_str(serial_number); + //IN_str(serial_number); + strn0cpy(emu->serial_number, eq->serial_number, sizeof(emu->serial_number)); +//FIX emu->serial_number = eq->serial_number; FINISH_DIRECT_DECODE(); break; @@ -6446,14 +6453,17 @@ namespace RoF2 RoF2::structs::ItemSerializationHeader hdr; //sprintf(hdr.unknown000, "06e0002Y1W00"); - // strn0cpy(hdr.unknown000, fmt::format("{:016}\0", inst->GetSerialNumber()).c_str(),sizeof(hdr.unknown000)); - strn0cpy( - hdr.unknown000, - inst->GetSerialNumber2().empty() ? "0000000000000000" : inst->GetSerialNumber2().c_str(), - sizeof(hdr.unknown000) - ); + //strn0cpy(hdr.unknown000, fmt::format("{:016}\0", inst->GetSerialNumber()).c_str(),sizeof(hdr.unknown000)); + strn0cpy(hdr.unknown000, inst->GetSerialNumber2().c_str(),sizeof(hdr.unknown000)); hdr.unknown000[16] = '\0'; + // strn0cpy( + // hdr.unknown000, + // inst->GetSerialNumber2().empty() ? "0000000000000000" : inst->GetSerialNumber2().c_str(), + // sizeof(hdr.unknown000) + // ); + //hdr.unknown000[16] = '\0'; + hdr.stacksize = 1; if (item->ID == PARCEL_MONEY_ITEM_ID) { diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index b6ad6bbf1..08e953e13 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -3554,8 +3554,8 @@ struct WhoAllPlayerPart4 { }; struct TraderItemSerial_Struct { - char serial_number[17]; - uint8 unknown_018; + char serial_number[16]; + uint8 unknown_018[2]; void operator=(uint32 a) { auto _tmp = fmt::format("{:016}", a); diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index c93b92581..1320e6a2e 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -228,7 +228,7 @@ namespace Titanium VARSTRUCT_ENCODE_TYPE(uint32, bufptr, row->trader_entity_id); bufptr += 4; VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->item_id); - VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->serial_number); + //FIX VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->serial_number); bufptr += 4; if (row->stackable) { strn0cpy( @@ -2529,7 +2529,7 @@ namespace Titanium IN(action); memcpy(emu->player_name, eq->player_name, sizeof(emu->player_name)); - IN(serial_number); + //FIXIN(serial_number); FINISH_DIRECT_DECODE(); break; diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 6433621f5..90fecd6fd 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -338,7 +338,7 @@ namespace UF bufptr += 64; VARSTRUCT_ENCODE_TYPE(uint32, bufptr, 1); VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->item_id); - VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->serial_number); + //FIX VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->serial_number); bufptr += 4; if (row->stackable) { strn0cpy( @@ -3618,7 +3618,7 @@ namespace UF IN(action); memcpy(emu->player_name, eq->player_name, sizeof(emu->player_name)); - IN(serial_number); + //FIXIN(serial_number); FINISH_DIRECT_DECODE(); break; diff --git a/common/repositories/base/base_trader_repository.h b/common/repositories/base/base_trader_repository.h index 99236fcac..a28ef2db5 100644 --- a/common/repositories/base/base_trader_repository.h +++ b/common/repositories/base/base_trader_repository.h @@ -19,24 +19,24 @@ class BaseTraderRepository { public: struct Trader { - uint64_t id; - uint32_t char_id; - uint32_t item_id; - uint32_t aug_slot_1; - uint32_t aug_slot_2; - uint32_t aug_slot_3; - uint32_t aug_slot_4; - uint32_t aug_slot_5; - uint32_t aug_slot_6; - uint32_t item_sn; - int32_t item_charges; - uint32_t item_cost; - uint8_t slot_id; - uint32_t char_entity_id; - uint32_t char_zone_id; - int32_t char_zone_instance_id; - uint8_t active_transaction; - time_t listing_date; + uint64_t id; + uint32_t char_id; + uint32_t item_id; + uint32_t aug_slot_1; + uint32_t aug_slot_2; + uint32_t aug_slot_3; + uint32_t aug_slot_4; + uint32_t aug_slot_5; + uint32_t aug_slot_6; + std::string item_sn; + int32_t item_charges; + uint32_t item_cost; + uint8_t slot_id; + uint32_t char_entity_id; + uint32_t char_zone_id; + int32_t char_zone_instance_id; + uint8_t active_transaction; + time_t listing_date; }; static std::string PrimaryKey() @@ -138,7 +138,7 @@ public: e.aug_slot_4 = 0; e.aug_slot_5 = 0; e.aug_slot_6 = 0; - e.item_sn = 0; + e.item_sn = ""; e.item_charges = 0; e.item_cost = 0; e.slot_id = 0; @@ -192,7 +192,7 @@ public: e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.item_sn = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.item_sn = row[9] ? row[9] : ""; e.item_charges = row[10] ? static_cast(atoi(row[10])) : 0; e.item_cost = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.slot_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; @@ -242,7 +242,7 @@ public: v.push_back(columns[6] + " = " + std::to_string(e.aug_slot_4)); v.push_back(columns[7] + " = " + std::to_string(e.aug_slot_5)); v.push_back(columns[8] + " = " + std::to_string(e.aug_slot_6)); - v.push_back(columns[9] + " = " + std::to_string(e.item_sn)); + v.push_back(columns[9] + " = '" + Strings::Escape(e.item_sn) + "'"); v.push_back(columns[10] + " = " + std::to_string(e.item_charges)); v.push_back(columns[11] + " = " + std::to_string(e.item_cost)); v.push_back(columns[12] + " = " + std::to_string(e.slot_id)); @@ -281,7 +281,7 @@ public: v.push_back(std::to_string(e.aug_slot_4)); v.push_back(std::to_string(e.aug_slot_5)); v.push_back(std::to_string(e.aug_slot_6)); - v.push_back(std::to_string(e.item_sn)); + v.push_back("'" + Strings::Escape(e.item_sn) + "'"); v.push_back(std::to_string(e.item_charges)); v.push_back(std::to_string(e.item_cost)); v.push_back(std::to_string(e.slot_id)); @@ -328,7 +328,7 @@ public: v.push_back(std::to_string(e.aug_slot_4)); v.push_back(std::to_string(e.aug_slot_5)); v.push_back(std::to_string(e.aug_slot_6)); - v.push_back(std::to_string(e.item_sn)); + v.push_back("'" + Strings::Escape(e.item_sn) + "'"); v.push_back(std::to_string(e.item_charges)); v.push_back(std::to_string(e.item_cost)); v.push_back(std::to_string(e.slot_id)); @@ -379,7 +379,7 @@ public: e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.item_sn = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.item_sn = row[9] ? row[9] : ""; e.item_charges = row[10] ? static_cast(atoi(row[10])) : 0; e.item_cost = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.slot_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; @@ -421,7 +421,7 @@ public: e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.item_sn = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.item_sn = row[9] ? row[9] : ""; e.item_charges = row[10] ? static_cast(atoi(row[10])) : 0; e.item_cost = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.slot_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; @@ -513,7 +513,7 @@ public: v.push_back(std::to_string(e.aug_slot_4)); v.push_back(std::to_string(e.aug_slot_5)); v.push_back(std::to_string(e.aug_slot_6)); - v.push_back(std::to_string(e.item_sn)); + v.push_back("'" + Strings::Escape(e.item_sn) + "'"); v.push_back(std::to_string(e.item_charges)); v.push_back(std::to_string(e.item_cost)); v.push_back(std::to_string(e.slot_id)); @@ -553,7 +553,7 @@ public: v.push_back(std::to_string(e.aug_slot_4)); v.push_back(std::to_string(e.aug_slot_5)); v.push_back(std::to_string(e.aug_slot_6)); - v.push_back(std::to_string(e.item_sn)); + v.push_back("'" + Strings::Escape(e.item_sn) + "'"); v.push_back(std::to_string(e.item_charges)); v.push_back(std::to_string(e.item_cost)); v.push_back(std::to_string(e.slot_id)); diff --git a/common/repositories/trader_repository.h b/common/repositories/trader_repository.h index 0354d6437..b5ac3810d 100644 --- a/common/repositories/trader_repository.h +++ b/common/repositories/trader_repository.h @@ -179,7 +179,7 @@ public: return item; } - static int UpdateQuantity(Database &db, uint32 char_id, uint32 serial_number, int16 quantity) + static int UpdateQuantity(Database &db, uint32 char_id, const std::string &serial_number, int16 quantity) { const auto trader_item = GetWhere( db, @@ -197,7 +197,7 @@ public: return UpdateOne(db, m); } - static Trader GetItemBySerialNumber(Database &db, uint32 serial_number, uint32 trader_id) + static Trader GetItemBySerialNumber(Database &db, std::string &serial_number, uint32 trader_id) { Trader e{}; const auto trader_item = GetWhere( @@ -212,22 +212,6 @@ public: return trader_item.at(0); } - static Trader GetItemBySerialNumber(Database &db, std::string serial_number, uint32 trader_id) - { - Trader e{}; - auto sn = Strings::ToUnsignedBigInt(serial_number); - const auto trader_item = GetWhere( - db, - fmt::format("`char_id` = '{}' AND `item_sn` = '{}' LIMIT 1", trader_id, sn) - ); - - if (trader_item.empty()) { - return e; - } - - return trader_item.at(0); - } - static int UpdateActiveTransaction(Database &db, uint32 id, bool status) { auto e = FindOne(db, id); @@ -259,7 +243,7 @@ public: static DistinctTraders_Struct GetTraderByInstanceAndSerialnumber( Database &db, uint32 instance_id, - const char *serial_number + std::string &serial_number ) { DistinctTraders_Struct trader{}; @@ -319,7 +303,7 @@ public: e.trader.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; e.trader.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; e.trader.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.trader.item_sn = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.trader.item_sn = row[9] ? row[9] : std::string(""); e.trader.item_charges = row[10] ? static_cast(atoi(row[10])) : 0; e.trader.item_cost = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.trader.slot_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; diff --git a/zone/client.cpp b/zone/client.cpp index 5ed9eddec..17f03587e 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12700,7 +12700,7 @@ uint16 Client::GetSkill(EQ::skills::SkillType skill_id) const return 0; } -void Client::RemoveItemBySerialNumber(uint32 serial_number, uint32 quantity) +void Client::RemoveItemBySerialNumber(const std::string &serial_number, uint32 quantity) { EQ::ItemInstance *item = nullptr; @@ -12714,7 +12714,7 @@ void Client::RemoveItemBySerialNumber(uint32 serial_number, uint32 quantity) } item = GetInv().GetItem(slot_id); - if (item && item->GetSerialNumber() == serial_number) { + if (item && item->GetSerialNumber2().compare(serial_number) == 0) { uint32 charges = item->IsStackable() ? item->GetCharges() : 0; uint32 stack_size = std::max(charges, static_cast(1)); if ((removed_count + stack_size) <= quantity) { diff --git a/zone/client.h b/zone/client.h index d5e8f3ef4..c7698252b 100644 --- a/zone/client.h +++ b/zone/client.h @@ -350,7 +350,7 @@ public: void SendTraderPacket(Client* trader, uint32 Unknown72 = 51); void SendBuyerPacket(Client* Buyer); void SendBuyerToBarterWindow(Client* buyer, uint32 action); - GetItems_Struct* GetTraderItems(); + GetItems2_Struct* GetTraderItems(); void SendBazaarWelcome(); void SendBarterWelcome(); void DyeArmor(EQ::TintProfile* dye); @@ -371,11 +371,11 @@ public: void SendColoredText(uint32 color, std::string message); void SendTraderItem(uint32 item_id,uint16 quantity, TraderRepository::Trader &trader); void DoBazaarSearch(BazaarSearchCriteria_Struct search_criteria); - uint16 FindTraderItem(int32 SerialNumber,uint16 Quantity); + uint16 FindTraderItem(std::string &SerialNumber,uint16 Quantity); uint32 FindTraderItemSerialNumber(int32 ItemID); - EQ::ItemInstance* FindTraderItemBySerialNumber(int32 SerialNumber); - void FindAndNukeTraderItem(int32 serial_number, int16 quantity, Client* customer, uint16 trader_slot); - void NukeTraderItem(uint16 slot, int16 charges, int16 quantity, Client* customer, uint16 trader_slot, int32 serial_number, int32 item_id = 0); + EQ::ItemInstance* FindTraderItemBySerialNumber(std::string &serial_number); + void FindAndNukeTraderItem(std::string &serial_number, int16 quantity, Client* customer, uint16 trader_slot); + void NukeTraderItem(uint16 slot, int16 charges, int16 quantity, Client* customer, uint16 trader_slot, const std::string &serial_number, int32 item_id = 0); void ReturnTraderReq(const EQApplicationPacket* app,int16 traderitemcharges, uint32 itemid = 0); void TradeRequestFailed(const EQApplicationPacket* app); void BuyTraderItem(TraderBuy_Struct* tbs, Client* trader, const EQApplicationPacket* app); @@ -587,7 +587,7 @@ public: void ServerFilter(SetServerFilter_Struct* filter); void BulkSendTraderInventory(uint32 char_id); - void SendSingleTraderItem(uint32 char_id, int serial_number); + void SendSingleTraderItem(uint32 char_id, const std::string &serial_number); void BulkSendMerchantInventory(int merchant_id, int npcid); inline uint8 GetLanguageSkill(uint8 language_id) const { return m_pp.languages[language_id]; } @@ -1150,7 +1150,7 @@ public: void SetItemCooldown(uint32 item_id, bool use_saved_timer = false, uint32 in_seconds = 1); uint32 GetItemCooldown(uint32 item_id); void RemoveItem(uint32 item_id, uint32 quantity = 1); - void RemoveItemBySerialNumber(uint32 serial_number, uint32 quantity = 1); + void RemoveItemBySerialNumber(const std::string &serial_number, uint32 quantity = 1); bool SwapItem(MoveItem_Struct* move_in); void SwapItemResync(MoveItem_Struct* move_slots); void PutLootInInventory(int16 slot_id, const EQ::ItemInstance &inst, LootItem** bag_item_data = 0); diff --git a/zone/client_evolving_items.cpp b/zone/client_evolving_items.cpp index 77c29a45e..c8320e44a 100644 --- a/zone/client_evolving_items.cpp +++ b/zone/client_evolving_items.cpp @@ -386,7 +386,7 @@ bool Client::DoEvolveCheckProgression(EQ::ItemInstance &inst) PlayerEvent::EvolveItem e{}; - RemoveItemBySerialNumber(inst.GetSerialNumber()); + RemoveItemBySerialNumber(inst.GetSerialNumber2()); EvolvingItemsManager::Instance()->LoadPlayerEvent(inst, e); e.status = "Evolved Item due to obtaining progression - Old Evolve Item removed from inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); @@ -506,7 +506,7 @@ void Client::DoEvolveTransferXP(const EQApplicationPacket *app) PlayerEvent::EvolveItem e{}; - RemoveItemBySerialNumber(inst_from->GetSerialNumber()); + RemoveItemBySerialNumber(inst_from->GetSerialNumber2()); EvolvingItemsManager::Instance()->LoadPlayerEvent(*inst_from, e); e.status = "Transfer XP - Original FROM Evolve Item removed from inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); @@ -516,7 +516,7 @@ void Client::DoEvolveTransferXP(const EQApplicationPacket *app) e.status = "Transfer XP - Updated FROM item placed in inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); - RemoveItemBySerialNumber(inst_to->GetSerialNumber()); + RemoveItemBySerialNumber(inst_to->GetSerialNumber2()); EvolvingItemsManager::Instance()->LoadPlayerEvent(*inst_to, e); e.status = "Transfer XP - Original TO Evolve Item removed from inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index a059bef65..5b8fb836d 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -15470,10 +15470,11 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app) auto in = (TraderBuy_Struct *) app->pBuffer; if (RuleB(Bazaar, UseAlternateBazaarSearch) && in->trader_id >= TraderRepository::TRADER_CONVERT_ID) { + auto sn = std::string(in->serial_number); auto trader = TraderRepository::GetTraderByInstanceAndSerialnumber( database, in->trader_id - TraderRepository::TRADER_CONVERT_ID, - in->serial_number + sn ); if (!trader.trader_id) { diff --git a/zone/parcels.cpp b/zone/parcels.cpp index f1dc874c1..08a2fd30c 100644 --- a/zone/parcels.cpp +++ b/zone/parcels.cpp @@ -455,7 +455,7 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in) CharacterParcelsContainersRepository::InsertMany(database, all_entries); } - RemoveItemBySerialNumber(inst->GetSerialNumber(), parcel_out.quantity == 0 ? 1 : parcel_out.quantity); + RemoveItemBySerialNumber(inst->GetSerialNumber2(), parcel_out.quantity == 0 ? 1 : parcel_out.quantity); std::unique_ptr outapp(new EQApplicationPacket(OP_ShopSendParcel)); QueuePacket(outapp.get()); diff --git a/zone/trading.cpp b/zone/trading.cpp index 9415c9e01..4fc6c22b4 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -763,12 +763,12 @@ void Client::TraderShowItems() uint32 item_limit = trader_items.size() >= GetInv().GetLookup()->InventoryTypeSize.Bazaar ? GetInv().GetLookup()->InventoryTypeSize.Bazaar : trader_items.size(); - - for (int i = 0; i < item_limit; i++) { - data->item_cost[i] = trader_items.at(i).item_cost; - data->items[i] = ClientVersion() == EQ::versions::ClientVersion::RoF2 ? trader_items.at(i).item_sn - : trader_items.at(i).item_id; - } + //FIX + // for (int i = 0; i < item_limit; i++) { + // data->item_cost[i] = trader_items.at(i).item_cost; + // data->items[i] = ClientVersion() == EQ::versions::ClientVersion::RoF2 ? trader_items.at(i).item_sn + // : trader_items.at(i).item_id; + // } data->action = ListTraderItems; @@ -813,18 +813,45 @@ void Client::Trader_CustomerBrowsing(Client *Customer) void Client::TraderStartTrader(const EQApplicationPacket *app) { uint32 max_items = GetInv().GetLookup()->InventoryTypeSize.Bazaar; - auto in = (ClickTrader_Struct *) app->pBuffer; + auto in = (ClickTrader2_Struct *) app->pBuffer; auto inv = GetTraderItems(); bool trade_items_valid = true; std::vector trader_items{}; //Check inventory for no-trade items - for (auto i = 0; i < max_items; i++) { - if (inv->items[i] == 0 || inv->serial_number[i] == 0) { + // for (auto i = 0; i < max_items; i++) { + // if (inv->items[i] == 0 || inv->serial_number[i].empty()) { + // continue; + // } + // + // auto inst = FindTraderItemBySerialNumber(inv->serial_number[i]); + // if (inst) { + // if (inst->GetItem() && inst->GetItem()->NoDrop == 0) { + // Message( + // Chat::Red, + // fmt::format( + // "Item: {} is NODROP and found in a Trader's Satchel. Please remove and restart trader mode", + // inst->GetItem()->Name + // ).c_str() + // ); + // TraderEndTrader(); + // safe_delete(inv); + // return; + // } + // } + // } + + for (uint32 i = 0; i < max_items; i++) { + if (inv->items[i] == 0 || inv->serial_number[i].empty()) { continue; } - auto inst = FindTraderItemBySerialNumber(inv->serial_number[i]); + auto const inst = FindTraderItemBySerialNumber(inv->serial_number[i]); + if (!inst) { + trade_items_valid = false; + break; + } + if (inst) { if (inst->GetItem() && inst->GetItem()->NoDrop == 0) { Message( @@ -839,18 +866,6 @@ void Client::TraderStartTrader(const EQApplicationPacket *app) return; } } - } - - for (uint32 i = 0; i < max_items; i++) { - if (inv->serial_number[i] <= 0) { - continue; - } - - auto inst = FindTraderItemBySerialNumber(inv->serial_number[i]); - if (!inst) { - trade_items_valid = false; - break; - } auto it = std::find(std::begin(in->serial_number), std::end(in->serial_number), inv->serial_number[i]); if (inst && it != std::end(in->serial_number)) { @@ -980,7 +995,7 @@ void Client::SendTraderItem(uint32 ItemID, uint16 Quantity, TraderRepository::Tr } } -void Client::SendSingleTraderItem(uint32 char_id, int serial_number) +void Client::SendSingleTraderItem(uint32 char_id, const std::string &serial_number) { auto inst = database.LoadSingleTraderItem(char_id, serial_number); if (inst) { @@ -1019,14 +1034,14 @@ void Client::BulkSendTraderInventory(uint32 char_id) ) ); if (inst) { - inst->SetSerialNumber(trader_items.at(i).item_sn); + inst->SetSerialNumber2(trader_items.at(i).item_sn); if (trader_items.at(i).item_charges > 0) { inst->SetCharges(trader_items.at(i).item_charges); } if (inst->IsStackable()) { inst->SetMerchantCount(trader_items.at(i).item_charges); - inst->SetMerchantSlot(trader_items.at(i).item_sn); + //inst->SetMerchantSlot(trader_items.at(i).item_sn); } inst->SetPrice(trader_items.at(i).item_cost); @@ -1062,7 +1077,7 @@ uint32 Client::FindTraderItemSerialNumber(int32 ItemID) { return 0; } -EQ::ItemInstance *Client::FindTraderItemBySerialNumber(int32 SerialNumber) +EQ::ItemInstance *Client::FindTraderItemBySerialNumber(std::string &serial_number) { EQ::ItemInstance *item = nullptr; int16 slot_id = 0; @@ -1075,7 +1090,7 @@ EQ::ItemInstance *Client::FindTraderItemBySerialNumber(int32 SerialNumber) slot_id = EQ::InventoryProfile::CalcSlotId(i, x); item = GetInv().GetItem(slot_id); if (item) { - if (item->GetSerialNumber() == SerialNumber) { + if (item->GetSerialNumber2().compare(serial_number) == 0) { return item; } } @@ -1083,17 +1098,17 @@ EQ::ItemInstance *Client::FindTraderItemBySerialNumber(int32 SerialNumber) } } - LogTrading("Couldn't find item! Serial No. was [{}]", SerialNumber); + LogTrading("Couldn't find item! Serial No. was [{}]", serial_number); return nullptr; } -GetItems_Struct *Client::GetTraderItems() +GetItems2_Struct *Client::GetTraderItems() { const EQ::ItemInstance *item = nullptr; int16 slot_id = INVALID_INDEX; - auto gis = new GetItems_Struct{0}; + auto gis = new GetItems2_Struct{0}; uint8 ndx = 0; for (int16 i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) { @@ -1112,7 +1127,7 @@ GetItems_Struct *Client::GetTraderItems() if (item) { gis->items[ndx] = item->GetID(); - gis->serial_number[ndx] = item->GetSerialNumber(); + gis->serial_number[ndx] = item->GetSerialNumber2(); gis->charges[ndx] = item->GetCharges() == 0 ? 1 : item->GetCharges(); ndx++; } @@ -1122,7 +1137,7 @@ GetItems_Struct *Client::GetTraderItems() return gis; } -uint16 Client::FindTraderItem(int32 SerialNumber, uint16 Quantity){ +uint16 Client::FindTraderItem(std::string &serial_number, uint16 Quantity){ const EQ::ItemInstance* item= nullptr; uint16 SlotID = 0; @@ -1134,7 +1149,7 @@ uint16 Client::FindTraderItem(int32 SerialNumber, uint16 Quantity){ item = GetInv().GetItem(SlotID); - if (item && item->GetSerialNumber() == SerialNumber && + if (item && item->GetSerialNumber2().compare(serial_number) == 0 && (item->GetCharges() >= Quantity || (item->GetCharges() <= 0 && Quantity == 1))) { return SlotID; @@ -1143,7 +1158,7 @@ uint16 Client::FindTraderItem(int32 SerialNumber, uint16 Quantity){ } } LogTrading("Could NOT find a match for Item: [{}] with a quantity of: [{}] on Trader: [{}]\n", - SerialNumber , Quantity, GetName()); + serial_number , Quantity, GetName()); return 0; } @@ -1154,7 +1169,7 @@ void Client::NukeTraderItem( int16 quantity, Client *customer, uint16 trader_slot, - int32 serial_number, + const std::string &serial_number, int32 item_id ) { @@ -1173,7 +1188,7 @@ void Client::NukeTraderItem( tdis->unknown_000 = 0; tdis->trader_id = customer->GetID(); - tdis->item_id = serial_number; + tdis->item_id = Strings::ToUnsignedBigInt(serial_number); tdis->unknown_012 = 0; customer->QueuePacket(outapp); safe_delete(outapp); @@ -1210,7 +1225,7 @@ void Client::NukeTraderItem( safe_delete(outapp2); } -void Client::FindAndNukeTraderItem(int32 serial_number, int16 quantity, Client *customer, uint16 trader_slot) +void Client::FindAndNukeTraderItem(std::string &serial_number, int16 quantity, Client *customer, uint16 trader_slot) { const EQ::ItemInstance *item = nullptr; bool stackable = false; @@ -1247,7 +1262,7 @@ void Client::FindAndNukeTraderItem(int32 serial_number, int16 quantity, Client * std::vector delete_queue{}; for (int i = 0; i < item_limit; i++) { - if (test_slot && trader_items.at(i).item_sn == serial_number) { + if (test_slot && trader_items.at(i).item_sn.compare(serial_number) == 0) { delete_queue.push_back(trader_items.at(i)); NukeTraderItem( slot_id, @@ -1273,8 +1288,8 @@ void Client::FindAndNukeTraderItem(int32 serial_number, int16 quantity, Client * return; } else { - TraderRepository::UpdateQuantity(database, CharacterID(), item->GetSerialNumber(), charges - quantity); - NukeTraderItem(slot_id, charges, quantity, customer, trader_slot, item->GetSerialNumber(), item->GetID()); + TraderRepository::UpdateQuantity(database, CharacterID(), item->GetSerialNumber2(), charges - quantity); + NukeTraderItem(slot_id, charges, quantity, customer, trader_slot, item->GetSerialNumber2(), item->GetID()); return; } } @@ -1362,7 +1377,8 @@ void Client::BuyTraderItem(TraderBuy_Struct *tbs, Client *Trader, const EQApplic tbs->item_id = Strings::ToUnsignedBigInt(tbs->serial_number); } - buy_item = Trader->FindTraderItemBySerialNumber(tbs->item_id); + auto sn = std::string(tbs->serial_number); + buy_item = Trader->FindTraderItemBySerialNumber(sn); if (!buy_item) { LogTrading("Unable to find item id [{}] item_sn [{}] on trader", tbs->item_id, tbs->serial_number); @@ -1519,7 +1535,7 @@ void Client::BuyTraderItem(TraderBuy_Struct *tbs, Client *Trader, const EQApplic strn0cpy(outtbs->item_name, buy_item->GetItem()->Name, sizeof(outtbs->item_name)); strn0cpy( outtbs->serial_number, - fmt::format("{:016}", buy_item->GetSerialNumber()).c_str(), + buy_item->GetSerialNumber2().data(), sizeof(outtbs->serial_number) ); @@ -1545,7 +1561,7 @@ void Client::BuyTraderItem(TraderBuy_Struct *tbs, Client *Trader, const EQApplic BazaarAuditTrail(Trader->GetName(), GetName(), buy_item->GetItem()->Name, outtbs->quantity, outtbs->price, 0); } - Trader->FindAndNukeTraderItem(tbs->item_id, outtbs->quantity, this, 0); + Trader->FindAndNukeTraderItem(sn, outtbs->quantity, this, 0); if (item_id > 0 && Trader->ClientVersion() >= EQ::versions::ClientVersion::RoF) { // Convert Serial Number back to ItemID for RoF+ @@ -1646,8 +1662,8 @@ static void UpdateTraderCustomerItemsAdded( inst->SetCharges(i.item_charges); inst->SetPrice(i.item_cost); - inst->SetSerialNumber(i.item_sn); - inst->SetMerchantSlot(i.item_sn); + inst->SetSerialNumber2(i.item_sn); + //FIXinst->SetMerchantSlot(i.item_sn); if (inst->IsStackable()) { inst->SetMerchantCount(i.item_charges); } @@ -1698,10 +1714,10 @@ static void UpdateTraderCustomerPriceChanged( // RoF+ use Item IDs for now tdis->item_id = trader_items.at(i).item_id; } - else { - tdis->item_id = trader_items.at(i).item_sn; - } - tdis->item_id = trader_items.at(i).item_sn; + //FIX else { + // tdis->item_id = trader_items.at(i).item_sn; + // } + //tdis->item_id = trader_items.at(i).item_sn; LogTrading("Telling customer to remove item [{}] with [{}] charges and S/N [{}]", item_id, charges, trader_items.at(i).item_sn); @@ -1750,8 +1766,8 @@ static void UpdateTraderCustomerPriceChanged( continue; } - inst->SetSerialNumber(trader_items.at(i).item_sn); - inst->SetMerchantSlot(trader_items.at(i).item_sn); + inst->SetSerialNumber2(trader_items.at(i).item_sn); + //inst->SetMerchantSlot(trader_items.at(i).item_sn); LogTrading("Sending price update for [{}], Serial No. [{}] with [{}] charges", item->Name, trader_items.at(i).item_sn, trader_items.at(i).item_charges); @@ -2465,7 +2481,7 @@ void Client::TraderPriceUpdate(const EQApplicationPacket *app) LogTrading( "Received Price Update for [{}] Item Serial No. [{}] New Price [{}]", GetName(), - tpus->SerialNumber, + tpus->serial_number, tpus->NewPrice ); @@ -2486,7 +2502,7 @@ void Client::TraderPriceUpdate(const EQApplicationPacket *app) uint32 old_price = 0; for (int i = 0; i < item_limit; i++) { - if ((trader_items.at(i).item_id > 0) && (trader_items.at(i).item_sn == tpus->SerialNumber)) { + if ((trader_items.at(i).item_id > 0) && (trader_items.at(i).item_sn.compare(tpus->serial_number) == 0)) { // We found the item that the Trader wants to change the price of (or add back up for sale). // id_of_item_to_update = trader_items.at(i).item_id; @@ -2521,7 +2537,7 @@ void Client::TraderPriceUpdate(const EQApplicationPacket *app) int32 charges_on_item_to_add = 0; for (int i = 0; i < GetInv().GetLookup()->InventoryTypeSize.Bazaar; i++) { - if ((newgis->items[i] > 0) && (newgis->serial_number[i] == tpus->SerialNumber)) { + if (newgis->items[i] > 0 && newgis->serial_number[i].compare(tpus->serial_number) == 0) { id_of_item_to_add = newgis->items[i]; charges_on_item_to_add = newgis->charges[i]; @@ -2772,10 +2788,11 @@ void Client::DoBazaarInspect(BazaarInspect_Struct &in) { if (RuleB(Bazaar, UseAlternateBazaarSearch)) { if (in.trader_id >= TraderRepository::TRADER_CONVERT_ID) { + auto sn = std::string(in.serial_number); auto trader = TraderRepository::GetTraderByInstanceAndSerialnumber( database, in.trader_id - TraderRepository::TRADER_CONVERT_ID, - fmt::format("{}", in.serial_number).c_str() + sn ); if (!trader.trader_id) { @@ -2865,7 +2882,8 @@ std::string Client::DetermineMoneyString(uint64 cp) void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicationPacket *app) { auto in = (TraderBuy_Struct *) app->pBuffer; - auto trader_item = TraderRepository::GetItemBySerialNumber(database, tbs->serial_number, tbs->trader_id); + auto sn = std::string(tbs->serial_number); + auto trader_item = TraderRepository::GetItemBySerialNumber(database, sn, tbs->trader_id); if (!trader_item.id || GetTraderTransactionDate() < trader_item.listing_date) { LogTrading("Attempt to purchase an item outside of the Bazaar trader_id [{}] item serial_number " "[{}] The Traders data was outdated.", diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 747302e24..840979d8d 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -3791,8 +3791,8 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } } - auto item_sn = Strings::ToUnsignedBigInt(in->trader_buy_struct.serial_number); auto outapp = std::make_unique(OP_Trader, static_cast(sizeof(TraderBuy_Struct))); + auto sn = std::string(in->trader_buy_struct.serial_number); auto data = (TraderBuy_Struct *) outapp->pBuffer; memcpy(data, &in->trader_buy_struct, sizeof(TraderBuy_Struct)); @@ -3803,7 +3803,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) TraderRepository::UpdateActiveTransaction(database, in->id, false); - auto item = trader_pc->FindTraderItemBySerialNumber(item_sn); + auto item = trader_pc->FindTraderItemBySerialNumber(sn); if (item && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_SELL)) { auto e = PlayerEvent::TraderSellEvent{ @@ -3826,7 +3826,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) RecordPlayerEventLogWithClient(trader_pc, PlayerEvent::TRADER_SELL, e); } - trader_pc->RemoveItemBySerialNumber(item_sn, in->trader_buy_struct.quantity); + trader_pc->RemoveItemBySerialNumber(sn, in->trader_buy_struct.quantity); trader_pc->AddMoneyToPP(in->trader_buy_struct.price * in->trader_buy_struct.quantity, true); trader_pc->QueuePacket(outapp.get()); diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 0dc143675..3c48ff7ae 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -306,7 +306,7 @@ void ZoneDatabase::DeleteWorldContainer(uint32 parent_id, uint32 zone_id) ); } -std::unique_ptr ZoneDatabase::LoadSingleTraderItem(uint32 char_id, int serial_number) +std::unique_ptr ZoneDatabase::LoadSingleTraderItem(uint32 char_id, const std::string &serial_number) { auto results = TraderRepository::GetWhere( database, @@ -354,8 +354,8 @@ std::unique_ptr ZoneDatabase::LoadSingleTraderItem(uint32 char } inst->SetCharges(charges); - inst->SetSerialNumber(serial_number); - inst->SetMerchantSlot(serial_number); + inst->SetSerialNumber2(serial_number); + //FIX inst->SetMerchantSlot(serial_number); inst->SetPrice(cost); if (inst->IsStackable()) { diff --git a/zone/zonedb.h b/zone/zonedb.h index cda0a5ab2..6e54108e8 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -394,7 +394,7 @@ public: void DeleteTraderItem(uint32 char_id); void DeleteTraderItem(uint32 char_id,uint16 slot_id); - std::unique_ptr LoadSingleTraderItem(uint32 char_id, int serial_number); + std::unique_ptr LoadSingleTraderItem(uint32 char_id, const std::string &serial_number); Trader_Struct* LoadTraderItem(uint32 char_id); TraderCharges_Struct* LoadTraderItemWithCharges(uint32 char_id); From 08d9c2d4f6651cc22516b9bbecbc705ecabc81b5 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sat, 29 Mar 2025 23:25:49 -0300 Subject: [PATCH 03/48] Pass 3 - start to update trader functionality - WIP --- common/eq_packet_structs.h | 9 +++++++++ common/patches/rof2.cpp | 21 +++++++++++++++------ common/patches/rof2_structs.h | 10 +++++----- zone/trading.cpp | 13 ++++++------- 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index cb05be8b2..dac02de2c 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -3729,6 +3729,15 @@ struct Trader_Struct { /*648*/ uint32 item_cost[EQ::invtype::BAZAAR_SIZE]; }; +struct Trader3_Struct { + /*000*/ uint32 action; + /*004*/ uint32 unknown_004; + ///*008*/ uint64 items[EQ::invtype::BAZAAR_SIZE]; + /*008*/ char serial_number[17][EQ::invtype::BAZAAR_SIZE]; + char unknown[1]{}; + /*648*/ uint32 item_cost[EQ::invtype::BAZAAR_SIZE]; +}; + struct ClickTrader_Struct { /*000*/ uint32 action; /*004*/ uint32 unknown_004; diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index c8e8eb6d6..e3aaa977d 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -4115,19 +4115,20 @@ namespace RoF2 break; } case ListTraderItems: { - ENCODE_LENGTH_EXACT(Trader_Struct); - SETUP_DIRECT_ENCODE(Trader_Struct, structs::ClickTrader_Struct); + ENCODE_LENGTH_EXACT(Trader3_Struct); + SETUP_DIRECT_ENCODE(Trader3_Struct, structs::ClickTrader_Struct); LogTrading("(RoF2) action [{}]", action); eq->action = structs::RoF2BazaarTraderBuyerActions::ListTraderItems; std::transform( - std::begin(emu->items), - std::end(emu->items), + std::begin(emu->serial_number), + std::end(emu->serial_number), std::begin(eq->items), - [&](const uint32 x) { + [&](const char* x) { return x; } ); +// std::ranges::copy(emu->serial_number, eq->items.begin(), eq->items.end()); std::copy_n( std::begin(emu->item_cost), EQ::invtype::BAZAAR_SIZE, @@ -6159,7 +6160,15 @@ namespace RoF2 emu->action = TraderOn; std::copy_n(eq->item_cost, RoF2::invtype::BAZAAR_SIZE, emu->item_cost); - std::copy_n(eq->items->serial_number, RoF2::invtype::BAZAAR_SIZE, emu->serial_number); + std::transform( + std::begin(eq->items), + std::end(eq->items), + std::begin(emu->serial_number), + [&](const structs::TraderItemSerial_Struct x) { + return std::string(x.serial_number); + }); + //std::ranges::copy(eq->items->serial_number, emu->serial_number); + //std::copy_n(eq->items->serial_number, RoF2::invtype::BAZAAR_SIZE, emu->serial_number); // std::transform( // std::begin(eq->items), // std::end(eq->items), diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 08e953e13..92f0a1d90 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -3554,12 +3554,12 @@ struct WhoAllPlayerPart4 { }; struct TraderItemSerial_Struct { - char serial_number[16]; - uint8 unknown_018[2]; + char serial_number[17]; + uint8 unknown_018; - void operator=(uint32 a) { - auto _tmp = fmt::format("{:016}", a); - strn0cpy(this->serial_number, _tmp.c_str(), sizeof(this->serial_number)); + void operator=(const char* a) { + //auto _tmp = fmt::format("{:016}", a); + strn0cpy(this->serial_number, a, sizeof(this->serial_number)); } }; diff --git a/zone/trading.cpp b/zone/trading.cpp index 4fc6c22b4..2b902153a 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -756,19 +756,18 @@ bool Client::CheckTradeNonDroppable() void Client::TraderShowItems() { - auto outapp = new EQApplicationPacket(OP_Trader, sizeof(Trader_Struct)); - auto data = (Trader_Struct *) outapp->pBuffer; + auto outapp = new EQApplicationPacket(OP_Trader, sizeof(Trader3_Struct)); + auto data = (Trader3_Struct *) outapp->pBuffer; auto trader_items = TraderRepository::GetWhere(database, fmt::format("`char_id` = '{}'", CharacterID())); uint32 item_limit = trader_items.size() >= GetInv().GetLookup()->InventoryTypeSize.Bazaar ? GetInv().GetLookup()->InventoryTypeSize.Bazaar : trader_items.size(); //FIX - // for (int i = 0; i < item_limit; i++) { - // data->item_cost[i] = trader_items.at(i).item_cost; - // data->items[i] = ClientVersion() == EQ::versions::ClientVersion::RoF2 ? trader_items.at(i).item_sn - // : trader_items.at(i).item_id; - // } + for (int i = 0; i < item_limit; i++) { + data->item_cost[i] = trader_items.at(i).item_cost; + strn0cpy(data->serial_number[i], trader_items.at(i).item_sn.data(), sizeof(data->serial_number[i])); + } data->action = ListTraderItems; From 8af139aef084b8b27b012a5d045277925e497035 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 30 Mar 2025 18:41:25 -0300 Subject: [PATCH 04/48] Pass 4 - Start Trader working --- common/eq_packet_structs.h | 33 +++++++++++++++++++----- common/item_instance.cpp | 1 + common/patches/rof2.cpp | 46 ++++++++++++++++++++-------------- zone/inventory.cpp | 1 + zone/trading.cpp | 51 ++++++++++++++++++++++++++++++-------- 5 files changed, 97 insertions(+), 35 deletions(-) diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index dac02de2c..bcd8f1ad7 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -3729,13 +3729,34 @@ struct Trader_Struct { /*648*/ uint32 item_cost[EQ::invtype::BAZAAR_SIZE]; }; -struct Trader3_Struct { +struct TraderItems_Struct { + std::string serial_number; + uint32 item_id; + uint64 item_cost; + + template + void serialize(Archive &archive) + { + archive( + CEREAL_NVP(serial_number), + CEREAL_NVP(item_id), + CEREAL_NVP(item_cost) + ); + } +}; + +struct TraderClientMessaging_Struct { /*000*/ uint32 action; - /*004*/ uint32 unknown_004; - ///*008*/ uint64 items[EQ::invtype::BAZAAR_SIZE]; - /*008*/ char serial_number[17][EQ::invtype::BAZAAR_SIZE]; - char unknown[1]{}; - /*648*/ uint32 item_cost[EQ::invtype::BAZAAR_SIZE]; + /*008*/ std::vector items; + + template + void serialize(Archive &archive) + { + archive( + CEREAL_NVP(action), + CEREAL_NVP(items) + ); + } }; struct ClickTrader_Struct { diff --git a/common/item_instance.cpp b/common/item_instance.cpp index 5aadebf63..e401511fa 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -160,6 +160,7 @@ EQ::ItemInstance::ItemInstance(const ItemInstance& copy) m_serial_number2 = copy.m_serial_number2; if (copy.GetSerialNumber2().empty()) { + LogError("Creating Serial Number as part of Clone command"); CreateSerialNumber2(); } diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index e3aaa977d..7fba9c3f6 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -4115,27 +4115,37 @@ namespace RoF2 break; } case ListTraderItems: { - ENCODE_LENGTH_EXACT(Trader3_Struct); - SETUP_DIRECT_ENCODE(Trader3_Struct, structs::ClickTrader_Struct); LogTrading("(RoF2) action [{}]", action); - eq->action = structs::RoF2BazaarTraderBuyerActions::ListTraderItems; - std::transform( - std::begin(emu->serial_number), - std::end(emu->serial_number), - std::begin(eq->items), - [&](const char* x) { - return x; - } - ); -// std::ranges::copy(emu->serial_number, eq->items.begin(), eq->items.end()); - std::copy_n( - std::begin(emu->item_cost), - EQ::invtype::BAZAAR_SIZE, - std::begin(eq->item_cost) - ); + EQApplicationPacket *in = *p; + *p = nullptr; - FINISH_ENCODE(); + TraderClientMessaging_Struct tcm{}; + EQ::Util::MemoryStreamReader ss(reinterpret_cast(in->pBuffer), in->size); + cereal::BinaryInputArchive ar(ss); + { ar(tcm); } + + auto buffer = new char[4404]{}; //4404 is the fixed size of the packet for 200 item limit of RoF2 + auto pos = buffer; + + VARSTRUCT_ENCODE_TYPE(uint32, pos, structs::RoF2BazaarTraderBuyerActions::ListTraderItems); + for (auto const &t: tcm.items) { + strn0cpy(pos, t.serial_number.data(), t.serial_number.length() + 1); + pos += 3600; + VARSTRUCT_ENCODE_TYPE(uint32, pos, t.item_cost); + pos -= 3604 - 18; + } + + for (int i = tcm.items.size(); i < EQ::invtype::BAZAAR_SIZE; i++) { + strn0cpy(pos, "0000000000000000", 18); + pos += 18; + } + + safe_delete_array(in->pBuffer); + in->pBuffer = reinterpret_cast(buffer); + in->size = 4404; + + dest->FastQueuePacket(&in); break; } case TraderAck2: { diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 1c2b62c4f..839cfa784 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -650,6 +650,7 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, // put item into inventory if (to_slot == EQ::invslot::slotCursor) { + inst->CreateSerialNumber2(); PushItemOnCursor(*inst); SendItemPacket(EQ::invslot::slotCursor, inst, ItemPacketLimbo); } else { diff --git a/zone/trading.cpp b/zone/trading.cpp index 2b902153a..9defe0709 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -756,20 +756,49 @@ bool Client::CheckTradeNonDroppable() void Client::TraderShowItems() { - auto outapp = new EQApplicationPacket(OP_Trader, sizeof(Trader3_Struct)); - auto data = (Trader3_Struct *) outapp->pBuffer; + std::stringstream ss{}; + cereal::BinaryOutputArchive ar(ss); - auto trader_items = TraderRepository::GetWhere(database, fmt::format("`char_id` = '{}'", CharacterID())); - uint32 item_limit = trader_items.size() >= GetInv().GetLookup()->InventoryTypeSize.Bazaar ? - GetInv().GetLookup()->InventoryTypeSize.Bazaar : - trader_items.size(); - //FIX - for (int i = 0; i < item_limit; i++) { - data->item_cost[i] = trader_items.at(i).item_cost; - strn0cpy(data->serial_number[i], trader_items.at(i).item_sn.data(), sizeof(data->serial_number[i])); + auto trader_items = TraderRepository::GetWhere(database, fmt::format("`char_id` = '{}'", CharacterID())); + if (trader_items.empty()) { + return; } - data->action = ListTraderItems; + TraderClientMessaging_Struct tcm{}; + tcm.action = ListTraderItems; + + for (auto const &t: trader_items) { + TraderItems_Struct items{}; + items.serial_number = t.item_sn; + items.item_id = t.item_id; + items.item_cost = t.item_cost; + + tcm.items.push_back(items); + } + + { ar(tcm); } + + uint32 packet_size = ss.str().length(); + auto outapp = new EQApplicationPacket(OP_Trader, packet_size); + + memcpy(outapp->pBuffer, ss.str().data(), packet_size); + // // uint32 item_limit = trader_items.size() >= GetInv().GetLookup()->InventoryTypeSize.Bazaar ? + // // GetInv().GetLookup()->InventoryTypeSize.Bazaar : + // // trader_items.size(); + // uint32 item_limit = GetInv().GetLookup()->InventoryTypeSize.Bazaar; + // //FIX + // + // for (int i = 0; i < trader_items.size(); i++) { + // data->items[i].cost = trader_items.at(i).item_cost; + // strn0cpy(data->items[i].sn, trader_items.at(i).item_sn.data(), sizeof(data->items[i].sn[i])); + // } + // + // for (int i = trader_items.size(); i < item_limit; i++) { + // data->items[i].cost = 0; + // strn0cpy(data->items[i].sn, "0000000000000000", sizeof(data->items[i].sn)); + // } + // + // data->action = ListTraderItems; QueuePacket(outapp); safe_delete(outapp); From caa335cd85fda36cbd88ec7927feab73ee19abf5 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 30 Mar 2025 20:19:41 -0300 Subject: [PATCH 05/48] Pass 4 - Start Trader working --- common/patches/rof2.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 7fba9c3f6..6d573c3c2 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -4117,15 +4117,17 @@ namespace RoF2 case ListTraderItems: { LogTrading("(RoF2) action [{}]", action); - EQApplicationPacket *in = *p; - *p = nullptr; + EQApplicationPacket *in = *p; + *p = nullptr; TraderClientMessaging_Struct tcm{}; EQ::Util::MemoryStreamReader ss(reinterpret_cast(in->pBuffer), in->size); cereal::BinaryInputArchive ar(ss); - { ar(tcm); } + { + ar(tcm); + } - auto buffer = new char[4404]{}; //4404 is the fixed size of the packet for 200 item limit of RoF2 + auto buffer = new char[4404]{}; // 4404 is the fixed size of the packet for 200 item limit of RoF2 auto pos = buffer; VARSTRUCT_ENCODE_TYPE(uint32, pos, structs::RoF2BazaarTraderBuyerActions::ListTraderItems); From 1519c2429ad7dc3f9b6dc5036232268cb21cae29 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:05:42 -0300 Subject: [PATCH 06/48] Pass 5 - WIP --- common/inventory_profile.cpp | 4 ++++ common/item_instance.h | 4 ++-- zone/inventory.cpp | 9 ++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/common/inventory_profile.cpp b/common/inventory_profile.cpp index bbc69c1a7..ac1e46fa6 100644 --- a/common/inventory_profile.cpp +++ b/common/inventory_profile.cpp @@ -260,6 +260,10 @@ int16 EQ::InventoryProfile::PutItem(int16 slot_id, const ItemInstance& inst) } int16 EQ::InventoryProfile::PushCursor(const ItemInstance &inst) { + if (inst.GetSerialNumber2().empty()) { + inst.CreateSerialNumber2(); + } + m_cursor.push(inst.Clone()); return invslot::slotCursor; } diff --git a/common/item_instance.h b/common/item_instance.h index 25d977fd5..517d1f539 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -238,7 +238,7 @@ namespace EQ //std::string &GetSerialNumber2() const { return m_serial_number2; } void SetSerialNumber2(std::string sn) { m_serial_number2 = std::move(sn); } - void CreateSerialNumber2() + void CreateSerialNumber2() const { m_serial_number2 = GenerateUniqueSerialNumber(); } @@ -371,7 +371,7 @@ namespace EQ bool m_attuned{false}; int32 m_merchantcount{1};//number avaliable on the merchant, -1=unlimited int32 m_SerialNumber{0}; // Unique identifier for this instance of an item. Needed for Bazaar. - std::string m_serial_number2{}; // unique serial number across all zones/world TESTING March 2025 + mutable std::string m_serial_number2{}; // unique serial number across all zones/world TESTING March 2025 uint32 m_exp{0}; int8 m_evolveLvl{0}; ItemData * m_scaledItem{nullptr}; diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 839cfa784..8845e055f 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -650,9 +650,8 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, // put item into inventory if (to_slot == EQ::invslot::slotCursor) { - inst->CreateSerialNumber2(); - PushItemOnCursor(*inst); - SendItemPacket(EQ::invslot::slotCursor, inst, ItemPacketLimbo); + PushItemOnCursor(*inst, true); + //SendItemPacket(EQ::invslot::slotCursor, inst, ItemPacketLimbo); } else { PutItemInInventory(to_slot, *inst, true); } @@ -1056,6 +1055,10 @@ bool Client::PushItemOnCursor(const EQ::ItemInstance& inst, bool client_update) bool Client::PutItemInInventory(int16 slot_id, const EQ::ItemInstance& inst, bool client_update) { LogInventory("Putting item [{}] ([{}]) into slot [{}]", inst.GetItem()->Name, inst.GetItem()->ID, slot_id); + if (inst.GetSerialNumber2().empty()) { + inst.CreateSerialNumber2(); + } + if (slot_id == EQ::invslot::slotCursor) { // don't trust macros before conditional statements... return PushItemOnCursor(inst, client_update); } From 6a9bc7e6c1d42f2e4d9a0d1e3a2a877f6ed92a4c Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sat, 5 Apr 2025 22:51:11 -0300 Subject: [PATCH 07/48] Update parcels for item_unique_id --- common/database/database_update_manifest.cpp | 13 +- common/events/player_events.h | 6 + common/inventory_profile.cpp | 4 +- common/item_instance.cpp | 8 +- common/item_instance.h | 53 ++++--- common/patches/rof2.cpp | 11 +- ..._character_parcels_containers_repository.h | 148 ++++++++++-------- .../base/base_character_parcels_repository.h | 114 ++++++++------ common/shareddb.cpp | 8 +- world/client.cpp | 2 +- zone/client.cpp | 12 +- zone/client.h | 2 +- zone/client_evolving_items.cpp | 6 +- zone/gm_commands/show/inventory.cpp | 4 +- zone/inventory.cpp | 4 +- zone/parcels.cpp | 35 +++-- zone/trading.cpp | 18 +-- zone/zonedb.cpp | 2 +- 18 files changed, 249 insertions(+), 201 deletions(-) diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 226127b2e..fb0632201 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -7188,15 +7188,22 @@ ALTER TABLE `character_parcels_containers` }, ManifestEntry{ .version = 9329, - .description = "2025_03_27_implement_item_unique_serial_number.sql", + .description = "2025_03_27_implement_item_unique_unique_id.sql", .check = "SHOW COLUMNS FROM `inventory` LIKE 'serial_number'", .condition = "empty", .match = "", .sql = R"( ALTER TABLE `inventory` DROP COLUMN `guid`, - ADD COLUMN `serial_number` VARCHAR(16) NULL DEFAULT NULL AFTER `ornament_hero_model`, - ADD UNIQUE INDEX `idx_serial_number` (`serial_number`); + ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `ornament_hero_model`, + ADD UNIQUE INDEX `idx_item_unique_id` (`item_unique_id`); + +ALTER TABLE `character_parcels` + ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `item_id`; + +ALTER TABLE `character_parcels_containers` + ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `item_id`; + )", .content_schema_update = false }, diff --git a/common/events/player_events.h b/common/events/player_events.h index 02ea2a26b..4a3850cac 100644 --- a/common/events/player_events.h +++ b/common/events/player_events.h @@ -1426,6 +1426,7 @@ namespace PlayerEvent { struct ParcelRetrieve { uint32 item_id; + std::string item_unique_id; uint32 augment_1_id; uint32 augment_2_id; uint32 augment_3_id; @@ -1471,6 +1472,7 @@ namespace PlayerEvent { { ar( CEREAL_NVP(item_id), + CEREAL_NVP(item_unique_id), CEREAL_NVP(augment_1_id), CEREAL_NVP(augment_2_id), CEREAL_NVP(augment_3_id), @@ -1486,6 +1488,7 @@ namespace PlayerEvent { struct ParcelSend { uint32 item_id; + std::string item_unique_id; uint32 augment_1_id; uint32 augment_2_id; uint32 augment_3_id; @@ -1535,6 +1538,7 @@ namespace PlayerEvent { { ar( CEREAL_NVP(item_id), + CEREAL_NVP(item_unique_id), CEREAL_NVP(augment_1_id), CEREAL_NVP(augment_2_id), CEREAL_NVP(augment_3_id), @@ -1553,6 +1557,7 @@ namespace PlayerEvent { struct ParcelDelete { uint32 char_id; uint32 item_id; + std::string item_unique_id; uint32 augment_1_id; uint32 augment_2_id; uint32 augment_3_id; @@ -1601,6 +1606,7 @@ namespace PlayerEvent { { ar( CEREAL_NVP(item_id), + CEREAL_NVP(item_unique_id), CEREAL_NVP(augment_1_id), CEREAL_NVP(augment_2_id), CEREAL_NVP(augment_3_id), diff --git a/common/inventory_profile.cpp b/common/inventory_profile.cpp index ac1e46fa6..158ac4dab 100644 --- a/common/inventory_profile.cpp +++ b/common/inventory_profile.cpp @@ -260,8 +260,8 @@ int16 EQ::InventoryProfile::PutItem(int16 slot_id, const ItemInstance& inst) } int16 EQ::InventoryProfile::PushCursor(const ItemInstance &inst) { - if (inst.GetSerialNumber2().empty()) { - inst.CreateSerialNumber2(); + if (inst.GetUniqueID().empty()) { + inst.CreateUniqueID(); } m_cursor.push(inst.Clone()); diff --git a/common/item_instance.cpp b/common/item_instance.cpp index e401511fa..1f2c1af76 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -158,11 +158,11 @@ EQ::ItemInstance::ItemInstance(const ItemInstance& copy) m_custom_data = copy.m_custom_data; m_timers = copy.m_timers; - m_serial_number2 = copy.m_serial_number2; - if (copy.GetSerialNumber2().empty()) { + if (copy.GetUniqueID().empty()) { LogError("Creating Serial Number as part of Clone command"); - CreateSerialNumber2(); + copy.CreateUniqueID(); } + m_unique_id = copy.m_unique_id; m_exp = copy.m_exp; m_evolveLvl = copy.m_evolveLvl; @@ -2013,7 +2013,7 @@ void EQ::ItemInstance::SetEvolveEquipped(const bool in) const GetTimers().at("evolve").Disable(); } -std::string EQ::ItemInstance::GenerateUniqueSerialNumber() +std::string EQ::ItemInstance::GenerateUniqueID() { std::string unique_hash = UniqueHashGenerator::generate(); diff --git a/common/item_instance.h b/common/item_instance.h index 517d1f539..e80f1f23c 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -234,13 +234,14 @@ namespace EQ int32 GetSerialNumber() const { return m_SerialNumber; } void SetSerialNumber(int32 id) { m_SerialNumber = id; } - const std::string &GetSerialNumber2() const { return m_serial_number2; } + const std::string &GetSerialNumber2() const { return m_unique_id; } + const std::string &GetUniqueID() const { return m_unique_id; } //std::string &GetSerialNumber2() const { return m_serial_number2; } - void SetSerialNumber2(std::string sn) { m_serial_number2 = std::move(sn); } + void SetUniqueID(std::string sn) { m_unique_id = std::move(sn); } - void CreateSerialNumber2() const + void CreateUniqueID() const { - m_serial_number2 = GenerateUniqueSerialNumber(); + m_unique_id = GenerateUniqueID(); } std::map& GetTimers() const { return m_timers; } @@ -359,29 +360,29 @@ namespace EQ std::map::const_iterator _cend() { return m_contents.cend(); } void _PutItem(uint8 index, ItemInstance *inst) { m_contents[index] = inst; } - static std::string GenerateUniqueSerialNumber(); + static std::string GenerateUniqueID(); - ItemInstTypes m_use_type{ItemInstNormal};// Usage type for item - const ItemData * m_item{nullptr}; // Ptr to item data - int16 m_charges{0}; // # of charges for chargeable items - uint32 m_price{0}; // Bazaar /trader price - uint32 m_color{0}; - uint32 m_merchantslot{0}; - int16 m_currentslot{0}; - bool m_attuned{false}; - int32 m_merchantcount{1};//number avaliable on the merchant, -1=unlimited - int32 m_SerialNumber{0}; // Unique identifier for this instance of an item. Needed for Bazaar. - mutable std::string m_serial_number2{}; // unique serial number across all zones/world TESTING March 2025 - uint32 m_exp{0}; - int8 m_evolveLvl{0}; - ItemData * m_scaledItem{nullptr}; - bool m_scaling{false}; - uint32 m_ornamenticon{0}; - uint32 m_ornamentidfile{0}; - uint32 m_new_id_file{0}; - uint32 m_ornament_hero_model{0}; - uint32 m_recast_timestamp{0}; - int m_task_delivered_count{0}; + ItemInstTypes m_use_type{ ItemInstNormal }; // Usage type for item + const ItemData *m_item{ nullptr }; // Ptr to item data + int16 m_charges{ 0 }; // # of charges for chargeable items + uint32 m_price{ 0 }; // Bazaar /trader price + uint32 m_color{ 0 }; + uint32 m_merchantslot{ 0 }; + int16 m_currentslot{ 0 }; + bool m_attuned{ false }; + int32 m_merchantcount{ 1 }; // number avaliable on the merchant, -1=unlimited + int32 m_SerialNumber{ 0 }; // Unique identifier for this instance of an item. Needed for Bazaar. + mutable std::string m_unique_id{}; // unique serial number across all zones/world TESTING March 2025 + uint32 m_exp{ 0 }; + int8 m_evolveLvl{ 0 }; + ItemData *m_scaledItem{ nullptr }; + bool m_scaling{ false }; + uint32 m_ornamenticon{ 0 }; + uint32 m_ornamentidfile{ 0 }; + uint32 m_new_id_file{ 0 }; + uint32 m_ornament_hero_model{ 0 }; + uint32 m_recast_timestamp{ 0 }; + int m_task_delivered_count{ 0 }; mutable CharacterEvolvingItemsRepository::CharacterEvolvingItems m_evolving_details{}; // Items inside of this item (augs or contents) {}; diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 6d573c3c2..b5ede82cb 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -6474,9 +6474,14 @@ namespace RoF2 RoF2::structs::ItemSerializationHeader hdr; //sprintf(hdr.unknown000, "06e0002Y1W00"); - //strn0cpy(hdr.unknown000, fmt::format("{:016}\0", inst->GetSerialNumber()).c_str(),sizeof(hdr.unknown000)); - strn0cpy(hdr.unknown000, inst->GetSerialNumber2().c_str(),sizeof(hdr.unknown000)); - hdr.unknown000[16] = '\0'; + + if (inst->GetUniqueID().empty()) { + strn0cpy(hdr.unknown000, fmt::format("{:016}\0", inst->GetSerialNumber()).c_str(),sizeof(hdr.unknown000)); + } + else { + strn0cpy(hdr.unknown000, inst->GetUniqueID().c_str(),sizeof(hdr.unknown000)); + hdr.unknown000[16] = '\0'; + } // strn0cpy( // hdr.unknown000, diff --git a/common/repositories/base/base_character_parcels_containers_repository.h b/common/repositories/base/base_character_parcels_containers_repository.h index 12ca611b7..8b255d693 100644 --- a/common/repositories/base/base_character_parcels_containers_repository.h +++ b/common/repositories/base/base_character_parcels_containers_repository.h @@ -19,18 +19,19 @@ class BaseCharacterParcelsContainersRepository { public: struct CharacterParcelsContainers { - uint32_t id; - uint32_t parcels_id; - uint32_t slot_id; - uint32_t item_id; - uint32_t aug_slot_1; - uint32_t aug_slot_2; - uint32_t aug_slot_3; - uint32_t aug_slot_4; - uint32_t aug_slot_5; - uint32_t aug_slot_6; - uint32_t quantity; - uint32_t evolve_amount; + uint32_t id; + uint32_t parcels_id; + uint32_t slot_id; + uint32_t item_id; + std::string item_unique_id; + uint32_t aug_slot_1; + uint32_t aug_slot_2; + uint32_t aug_slot_3; + uint32_t aug_slot_4; + uint32_t aug_slot_5; + uint32_t aug_slot_6; + uint32_t quantity; + uint32_t evolve_amount; }; static std::string PrimaryKey() @@ -45,6 +46,7 @@ public: "parcels_id", "slot_id", "item_id", + "item_unique_id", "aug_slot_1", "aug_slot_2", "aug_slot_3", @@ -63,6 +65,7 @@ public: "parcels_id", "slot_id", "item_id", + "item_unique_id", "aug_slot_1", "aug_slot_2", "aug_slot_3", @@ -111,18 +114,19 @@ public: { CharacterParcelsContainers e{}; - e.id = 0; - e.parcels_id = 0; - e.slot_id = 0; - e.item_id = 0; - e.aug_slot_1 = 0; - e.aug_slot_2 = 0; - e.aug_slot_3 = 0; - e.aug_slot_4 = 0; - e.aug_slot_5 = 0; - e.aug_slot_6 = 0; - e.quantity = 0; - e.evolve_amount = 0; + e.id = 0; + e.parcels_id = 0; + e.slot_id = 0; + e.item_id = 0; + e.item_unique_id = ""; + e.aug_slot_1 = 0; + e.aug_slot_2 = 0; + e.aug_slot_3 = 0; + e.aug_slot_4 = 0; + e.aug_slot_5 = 0; + e.aug_slot_6 = 0; + e.quantity = 0; + e.evolve_amount = 0; return e; } @@ -159,18 +163,19 @@ public: if (results.RowCount() == 1) { CharacterParcelsContainers e{}; - e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.parcels_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.slot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_1 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_2 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_3 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_4 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_5 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.aug_slot_6 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; - e.evolve_amount = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.parcels_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.slot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.item_unique_id = row[4] ? row[4] : ""; + e.aug_slot_1 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.aug_slot_2 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.aug_slot_3 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.aug_slot_4 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.aug_slot_5 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.aug_slot_6 = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.quantity = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.evolve_amount = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; return e; } @@ -207,14 +212,15 @@ public: v.push_back(columns[1] + " = " + std::to_string(e.parcels_id)); v.push_back(columns[2] + " = " + std::to_string(e.slot_id)); v.push_back(columns[3] + " = " + std::to_string(e.item_id)); - v.push_back(columns[4] + " = " + std::to_string(e.aug_slot_1)); - v.push_back(columns[5] + " = " + std::to_string(e.aug_slot_2)); - v.push_back(columns[6] + " = " + std::to_string(e.aug_slot_3)); - v.push_back(columns[7] + " = " + std::to_string(e.aug_slot_4)); - v.push_back(columns[8] + " = " + std::to_string(e.aug_slot_5)); - v.push_back(columns[9] + " = " + std::to_string(e.aug_slot_6)); - v.push_back(columns[10] + " = " + std::to_string(e.quantity)); - v.push_back(columns[11] + " = " + std::to_string(e.evolve_amount)); + v.push_back(columns[4] + " = '" + Strings::Escape(e.item_unique_id) + "'"); + v.push_back(columns[5] + " = " + std::to_string(e.aug_slot_1)); + v.push_back(columns[6] + " = " + std::to_string(e.aug_slot_2)); + v.push_back(columns[7] + " = " + std::to_string(e.aug_slot_3)); + v.push_back(columns[8] + " = " + std::to_string(e.aug_slot_4)); + v.push_back(columns[9] + " = " + std::to_string(e.aug_slot_5)); + v.push_back(columns[10] + " = " + std::to_string(e.aug_slot_6)); + v.push_back(columns[11] + " = " + std::to_string(e.quantity)); + v.push_back(columns[12] + " = " + std::to_string(e.evolve_amount)); auto results = db.QueryDatabase( fmt::format( @@ -240,6 +246,7 @@ public: v.push_back(std::to_string(e.parcels_id)); v.push_back(std::to_string(e.slot_id)); v.push_back(std::to_string(e.item_id)); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); v.push_back(std::to_string(e.aug_slot_1)); v.push_back(std::to_string(e.aug_slot_2)); v.push_back(std::to_string(e.aug_slot_3)); @@ -281,6 +288,7 @@ public: v.push_back(std::to_string(e.parcels_id)); v.push_back(std::to_string(e.slot_id)); v.push_back(std::to_string(e.item_id)); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); v.push_back(std::to_string(e.aug_slot_1)); v.push_back(std::to_string(e.aug_slot_2)); v.push_back(std::to_string(e.aug_slot_3)); @@ -322,18 +330,19 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { CharacterParcelsContainers e{}; - e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.parcels_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.slot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_1 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_2 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_3 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_4 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_5 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.aug_slot_6 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; - e.evolve_amount = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.parcels_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.slot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.item_unique_id = row[4] ? row[4] : ""; + e.aug_slot_1 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.aug_slot_2 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.aug_slot_3 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.aug_slot_4 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.aug_slot_5 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.aug_slot_6 = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.quantity = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.evolve_amount = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -358,18 +367,19 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { CharacterParcelsContainers e{}; - e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.parcels_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.slot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_1 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_2 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_3 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_4 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_5 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.aug_slot_6 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; - e.evolve_amount = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.parcels_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.slot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.item_unique_id = row[4] ? row[4] : ""; + e.aug_slot_1 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.aug_slot_2 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.aug_slot_3 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.aug_slot_4 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.aug_slot_5 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.aug_slot_6 = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.quantity = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.evolve_amount = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -448,6 +458,7 @@ public: v.push_back(std::to_string(e.parcels_id)); v.push_back(std::to_string(e.slot_id)); v.push_back(std::to_string(e.item_id)); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); v.push_back(std::to_string(e.aug_slot_1)); v.push_back(std::to_string(e.aug_slot_2)); v.push_back(std::to_string(e.aug_slot_3)); @@ -482,6 +493,7 @@ public: v.push_back(std::to_string(e.parcels_id)); v.push_back(std::to_string(e.slot_id)); v.push_back(std::to_string(e.item_id)); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); v.push_back(std::to_string(e.aug_slot_1)); v.push_back(std::to_string(e.aug_slot_2)); v.push_back(std::to_string(e.aug_slot_3)); diff --git a/common/repositories/base/base_character_parcels_repository.h b/common/repositories/base/base_character_parcels_repository.h index df7242aed..1641d4a57 100644 --- a/common/repositories/base/base_character_parcels_repository.h +++ b/common/repositories/base/base_character_parcels_repository.h @@ -28,6 +28,7 @@ public: uint32_t aug_slot_4; uint32_t aug_slot_5; uint32_t aug_slot_6; + std::string item_unique_id; uint32_t slot_id; uint32_t quantity; uint32_t evolve_amount; @@ -53,6 +54,7 @@ public: "aug_slot_4", "aug_slot_5", "aug_slot_6", + "item_unique_id", "slot_id", "quantity", "evolve_amount", @@ -74,6 +76,7 @@ public: "aug_slot_4", "aug_slot_5", "aug_slot_6", + "item_unique_id", "slot_id", "quantity", "evolve_amount", @@ -129,6 +132,7 @@ public: e.aug_slot_4 = 0; e.aug_slot_5 = 0; e.aug_slot_6 = 0; + e.item_unique_id = ""; e.slot_id = 0; e.quantity = 0; e.evolve_amount = 0; @@ -171,21 +175,22 @@ public: if (results.RowCount() == 1) { CharacterParcels e{}; - e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.slot_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; - e.evolve_amount = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; - e.from_name = row[12] ? row[12] : ""; - e.note = row[13] ? row[13] : ""; - e.sent_date = strtoll(row[14] ? row[14] : "-1", nullptr, 10); + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.item_unique_id = row[9] ? row[9] : ""; + e.slot_id = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.quantity = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.evolve_amount = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; + e.from_name = row[13] ? row[13] : ""; + e.note = row[14] ? row[14] : ""; + e.sent_date = strtoll(row[15] ? row[15] : "-1", nullptr, 10); return e; } @@ -227,12 +232,13 @@ public: v.push_back(columns[6] + " = " + std::to_string(e.aug_slot_4)); v.push_back(columns[7] + " = " + std::to_string(e.aug_slot_5)); v.push_back(columns[8] + " = " + std::to_string(e.aug_slot_6)); - v.push_back(columns[9] + " = " + std::to_string(e.slot_id)); - v.push_back(columns[10] + " = " + std::to_string(e.quantity)); - v.push_back(columns[11] + " = " + std::to_string(e.evolve_amount)); - v.push_back(columns[12] + " = '" + Strings::Escape(e.from_name) + "'"); - v.push_back(columns[13] + " = '" + Strings::Escape(e.note) + "'"); - v.push_back(columns[14] + " = FROM_UNIXTIME(" + (e.sent_date > 0 ? std::to_string(e.sent_date) : "null") + ")"); + v.push_back(columns[9] + " = '" + Strings::Escape(e.item_unique_id) + "'"); + v.push_back(columns[10] + " = " + std::to_string(e.slot_id)); + v.push_back(columns[11] + " = " + std::to_string(e.quantity)); + v.push_back(columns[12] + " = " + std::to_string(e.evolve_amount)); + v.push_back(columns[13] + " = '" + Strings::Escape(e.from_name) + "'"); + v.push_back(columns[14] + " = '" + Strings::Escape(e.note) + "'"); + v.push_back(columns[15] + " = FROM_UNIXTIME(" + (e.sent_date > 0 ? std::to_string(e.sent_date) : "null") + ")"); auto results = db.QueryDatabase( fmt::format( @@ -263,6 +269,7 @@ public: v.push_back(std::to_string(e.aug_slot_4)); v.push_back(std::to_string(e.aug_slot_5)); v.push_back(std::to_string(e.aug_slot_6)); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); v.push_back(std::to_string(e.slot_id)); v.push_back(std::to_string(e.quantity)); v.push_back(std::to_string(e.evolve_amount)); @@ -307,6 +314,7 @@ public: v.push_back(std::to_string(e.aug_slot_4)); v.push_back(std::to_string(e.aug_slot_5)); v.push_back(std::to_string(e.aug_slot_6)); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); v.push_back(std::to_string(e.slot_id)); v.push_back(std::to_string(e.quantity)); v.push_back(std::to_string(e.evolve_amount)); @@ -346,21 +354,22 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { CharacterParcels e{}; - e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.slot_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; - e.evolve_amount = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; - e.from_name = row[12] ? row[12] : ""; - e.note = row[13] ? row[13] : ""; - e.sent_date = strtoll(row[14] ? row[14] : "-1", nullptr, 10); + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.item_unique_id = row[9] ? row[9] : ""; + e.slot_id = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.quantity = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.evolve_amount = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; + e.from_name = row[13] ? row[13] : ""; + e.note = row[14] ? row[14] : ""; + e.sent_date = strtoll(row[15] ? row[15] : "-1", nullptr, 10); all_entries.push_back(e); } @@ -385,21 +394,22 @@ public: for (auto row = results.begin(); row != results.end(); ++row) { CharacterParcels e{}; - e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.slot_id = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.quantity = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; - e.evolve_amount = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; - e.from_name = row[12] ? row[12] : ""; - e.note = row[13] ? row[13] : ""; - e.sent_date = strtoll(row[14] ? row[14] : "-1", nullptr, 10); + e.id = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; + e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.item_unique_id = row[9] ? row[9] : ""; + e.slot_id = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.quantity = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; + e.evolve_amount = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; + e.from_name = row[13] ? row[13] : ""; + e.note = row[14] ? row[14] : ""; + e.sent_date = strtoll(row[15] ? row[15] : "-1", nullptr, 10); all_entries.push_back(e); } @@ -483,6 +493,7 @@ public: v.push_back(std::to_string(e.aug_slot_4)); v.push_back(std::to_string(e.aug_slot_5)); v.push_back(std::to_string(e.aug_slot_6)); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); v.push_back(std::to_string(e.slot_id)); v.push_back(std::to_string(e.quantity)); v.push_back(std::to_string(e.evolve_amount)); @@ -520,6 +531,7 @@ public: v.push_back(std::to_string(e.aug_slot_4)); v.push_back(std::to_string(e.aug_slot_5)); v.push_back(std::to_string(e.aug_slot_6)); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); v.push_back(std::to_string(e.slot_id)); v.push_back(std::to_string(e.quantity)); v.push_back(std::to_string(e.evolve_amount)); diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 5e52c2a5f..355a00fde 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -285,7 +285,7 @@ bool SharedDatabase::UpdateInventorySlot(uint32 char_id, const EQ::ItemInstance* e.ornament_icon = inst->GetOrnamentationIcon(); e.ornament_idfile = inst->GetOrnamentationIDFile(); e.ornament_hero_model = inst->GetOrnamentHeroModel(); - e.serial_number = inst->GetSerialNumber2(); + e.serial_number = inst->GetUniqueID(); const int replaced = InventoryRepository::ReplaceOne(*this, e); @@ -718,12 +718,12 @@ bool SharedDatabase::GetInventory(Client *c) inst->SetOrnamentHeroModel(item->HerosForgeModel); if (row.serial_number.empty()) { - inst->CreateSerialNumber2(); - row.serial_number = inst->GetSerialNumber2(); + inst->CreateUniqueID(); + row.serial_number = inst->GetUniqueID(); queue.push_back(row); } else { - inst->SetSerialNumber2(row.serial_number); + inst->SetUniqueID(row.serial_number); } if ( diff --git a/world/client.cpp b/world/client.cpp index 94746bee7..6d52f46b4 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -2400,7 +2400,7 @@ bool Client::StoreCharacter( e.ornament_icon = inst->GetOrnamentationIcon(); e.ornament_idfile = inst->GetOrnamentationIDFile(); e.ornament_hero_model = inst->GetOrnamentHeroModel(); - e.serial_number = inst->GetSerialNumber2(); + e.serial_number = inst->GetUniqueID(); v.emplace_back(e); } diff --git a/zone/client.cpp b/zone/client.cpp index 17f03587e..f1500e126 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12700,13 +12700,11 @@ uint16 Client::GetSkill(EQ::skills::SkillType skill_id) const return 0; } -void Client::RemoveItemBySerialNumber(const std::string &serial_number, uint32 quantity) +void Client::RemoveItemBySerialNumber(const std::string &item_unique_id, uint32 quantity) { - EQ::ItemInstance *item = nullptr; - - uint32 removed_count = 0; - - const auto& slot_ids = GetInventorySlots(); + EQ::ItemInstance *item = nullptr; + uint32 removed_count = 0; + const auto &slot_ids = GetInventorySlots(); for (const int16& slot_id : slot_ids) { if (removed_count == quantity) { @@ -12714,7 +12712,7 @@ void Client::RemoveItemBySerialNumber(const std::string &serial_number, uint32 q } item = GetInv().GetItem(slot_id); - if (item && item->GetSerialNumber2().compare(serial_number) == 0) { + if (item && item->GetUniqueID().compare(item_unique_id) == 0) { uint32 charges = item->IsStackable() ? item->GetCharges() : 0; uint32 stack_size = std::max(charges, static_cast(1)); if ((removed_count + stack_size) <= quantity) { diff --git a/zone/client.h b/zone/client.h index c7698252b..b2ec5483c 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1150,7 +1150,7 @@ public: void SetItemCooldown(uint32 item_id, bool use_saved_timer = false, uint32 in_seconds = 1); uint32 GetItemCooldown(uint32 item_id); void RemoveItem(uint32 item_id, uint32 quantity = 1); - void RemoveItemBySerialNumber(const std::string &serial_number, uint32 quantity = 1); + void RemoveItemBySerialNumber(const std::string &item_unique_id, uint32 quantity = 1); bool SwapItem(MoveItem_Struct* move_in); void SwapItemResync(MoveItem_Struct* move_slots); void PutLootInInventory(int16 slot_id, const EQ::ItemInstance &inst, LootItem** bag_item_data = 0); diff --git a/zone/client_evolving_items.cpp b/zone/client_evolving_items.cpp index c8320e44a..44184e229 100644 --- a/zone/client_evolving_items.cpp +++ b/zone/client_evolving_items.cpp @@ -386,7 +386,7 @@ bool Client::DoEvolveCheckProgression(EQ::ItemInstance &inst) PlayerEvent::EvolveItem e{}; - RemoveItemBySerialNumber(inst.GetSerialNumber2()); + RemoveItemBySerialNumber(inst.GetUniqueID()); EvolvingItemsManager::Instance()->LoadPlayerEvent(inst, e); e.status = "Evolved Item due to obtaining progression - Old Evolve Item removed from inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); @@ -506,7 +506,7 @@ void Client::DoEvolveTransferXP(const EQApplicationPacket *app) PlayerEvent::EvolveItem e{}; - RemoveItemBySerialNumber(inst_from->GetSerialNumber2()); + RemoveItemBySerialNumber(inst_from->GetUniqueID()); EvolvingItemsManager::Instance()->LoadPlayerEvent(*inst_from, e); e.status = "Transfer XP - Original FROM Evolve Item removed from inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); @@ -516,7 +516,7 @@ void Client::DoEvolveTransferXP(const EQApplicationPacket *app) e.status = "Transfer XP - Updated FROM item placed in inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); - RemoveItemBySerialNumber(inst_to->GetSerialNumber2()); + RemoveItemBySerialNumber(inst_to->GetUniqueID()); EvolvingItemsManager::Instance()->LoadPlayerEvent(*inst_to, e); e.status = "Transfer XP - Original TO Evolve Item removed from inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); diff --git a/zone/gm_commands/show/inventory.cpp b/zone/gm_commands/show/inventory.cpp index 655f796df..8baac4371 100644 --- a/zone/gm_commands/show/inventory.cpp +++ b/zone/gm_commands/show/inventory.cpp @@ -185,7 +185,7 @@ void ShowInventory(Client *c, const Seperator *sep) scope_bit & peekWorld ? EQ::invslot::WORLD_BEGIN + index_main : index_main, linker.GenerateLink(), item_data->ID, - inst_main->GetSerialNumber2().c_str(), + inst_main->GetUniqueID().c_str(), inst_main->IsStackable() && inst_main->GetCharges() > 0 ? fmt::format( " (Stack of {})", @@ -254,7 +254,7 @@ void ShowInventory(Client *c, const Seperator *sep) sub_index, linker.GenerateLink(), item_data->ID, - inst_sub->GetSerialNumber2().c_str(), + inst_sub->GetUniqueID().c_str(), ( inst_sub->IsStackable() && inst_sub->GetCharges() > 0 ? fmt::format( diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 8845e055f..3c10ee909 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -1055,8 +1055,8 @@ bool Client::PushItemOnCursor(const EQ::ItemInstance& inst, bool client_update) bool Client::PutItemInInventory(int16 slot_id, const EQ::ItemInstance& inst, bool client_update) { LogInventory("Putting item [{}] ([{}]) into slot [{}]", inst.GetItem()->Name, inst.GetItem()->ID, slot_id); - if (inst.GetSerialNumber2().empty()) { - inst.CreateSerialNumber2(); + if (inst.GetUniqueID().empty()) { + inst.CreateUniqueID(); } if (slot_id == EQ::invslot::slotCursor) { // don't trust macros before conditional statements... diff --git a/zone/parcels.cpp b/zone/parcels.cpp index 08a2fd30c..d6bb7a494 100644 --- a/zone/parcels.cpp +++ b/zone/parcels.cpp @@ -396,6 +396,7 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in) parcel_out.sent_date = time(nullptr); parcel_out.quantity = quantity; parcel_out.item_id = inst->GetID(); + parcel_out.item_unique_id = inst->GetUniqueID(); parcel_out.char_id = send_to_client.at(0).char_id; parcel_out.slot_id = next_slot; parcel_out.evolve_amount = inst->GetEvolveCurrentAmount(); @@ -433,13 +434,14 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in) std::vector all_entries{}; if (inst->IsNoneEmptyContainer()) { - for (auto const &kv: *inst->GetContents()) { + for (auto const &[slot, item]: *inst->GetContents()) { CharacterParcelsContainersRepository::CharacterParcelsContainers cpc{}; - cpc.parcels_id = result.id; - cpc.slot_id = kv.first; - cpc.item_id = kv.second->GetID(); - if (kv.second->IsAugmented()) { - auto augs = kv.second->GetAugmentIDs(); + cpc.parcels_id = result.id; + cpc.slot_id = slot; + cpc.item_id = item->GetID(); + cpc.item_unique_id = item->GetUniqueID(); + if (item->IsAugmented()) { + auto augs = item->GetAugmentIDs(); cpc.aug_slot_1 = augs.at(0); cpc.aug_slot_2 = augs.at(1); cpc.aug_slot_3 = augs.at(2); @@ -448,14 +450,15 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in) cpc.aug_slot_6 = augs.at(5); } - cpc.quantity = kv.second->GetCharges() >= 0 ? kv.second->GetCharges() : 1; - cpc.evolve_amount = kv.second->GetEvolveCurrentAmount(); + cpc.quantity = item->GetCharges() >= 0 ? item->GetCharges() : 1; + cpc.evolve_amount = item->GetEvolveCurrentAmount(); + cpc.quantity = item->GetCharges() >= 0 ? item->GetCharges() : 1; all_entries.push_back(cpc); } CharacterParcelsContainersRepository::InsertMany(database, all_entries); } - RemoveItemBySerialNumber(inst->GetSerialNumber2(), parcel_out.quantity == 0 ? 1 : parcel_out.quantity); + RemoveItemBySerialNumber(inst->GetUniqueID(), parcel_out.quantity == 0 ? 1 : parcel_out.quantity); std::unique_ptr outapp(new EQApplicationPacket(OP_ShopSendParcel)); QueuePacket(outapp.get()); @@ -477,6 +480,7 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in) e.from_player_name = parcel_out.from_name; e.to_player_name = send_to_client.at(0).character_name; e.item_id = parcel_out.item_id; + e.item_unique_id = parcel_out.item_unique_id; e.augment_1_id = parcel_out.aug_slot_1; e.augment_2_id = parcel_out.aug_slot_2; e.augment_3_id = parcel_out.aug_slot_3; @@ -493,6 +497,7 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in) e.from_player_name = parcel_out.from_name; e.to_player_name = send_to_client.at(0).character_name; e.item_id = i.item_id; + e.item_unique_id = i.item_unique_id; e.augment_1_id = i.aug_slot_1; e.augment_2_id = i.aug_slot_2; e.augment_3_id = i.aug_slot_3; @@ -655,8 +660,9 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in) } ); if (p != m_parcels.end()) { - uint32 item_id = parcel_in.parcel_item_id; - uint32 item_quantity = p->second.quantity; + uint32 item_id = parcel_in.parcel_item_id; + uint32 item_quantity = p->second.quantity; + std::string item_unique_id = p->second.item_unique_id; if (!item_id) { LogError( "Attempt to retrieve parcel with erroneous item id for client character id {}.", @@ -698,6 +704,7 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in) break; } default: { + inst->SetUniqueID(item_unique_id); std::vector results{}; if (inst->IsClassBag() && inst->GetItem()->BagSlots > 0) { auto contents = inst->GetContents(); @@ -722,7 +729,7 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in) } item->SetEvolveCurrentAmount(i.evolve_amount); - + item->SetUniqueID(i.item_unique_id); if (CheckLoreConflict(item->GetItem())) { if (RuleB(Parcel, DeleteOnDuplicate)) { MessageString(Chat::Yellow, PARCEL_DUPLICATE_DELETE, inst->GetItem()->Name); @@ -771,6 +778,7 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in) PlayerEvent::ParcelRetrieve e{}; e.from_player_name = p->second.from_name; e.item_id = p->second.item_id; + e.item_unique_id = p->second.item_unique_id; e.augment_1_id = p->second.aug_slot_1; e.augment_2_id = p->second.aug_slot_2; e.augment_3_id = p->second.aug_slot_3; @@ -784,6 +792,7 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in) for (auto const &i:results) { e.from_player_name = p->second.from_name; e.item_id = i.item_id; + e.item_unique_id = i.item_unique_id; e.augment_1_id = i.aug_slot_1; e.augment_2_id = i.aug_slot_2; e.augment_3_id = i.aug_slot_3; @@ -793,8 +802,6 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in) e.quantity = i.quantity; e.sent_date = p->second.sent_date; RecordPlayerEventLog(PlayerEvent::PARCEL_RETRIEVE, e); - - } } } diff --git a/zone/trading.cpp b/zone/trading.cpp index 9defe0709..fed062a1a 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1062,7 +1062,7 @@ void Client::BulkSendTraderInventory(uint32 char_id) ) ); if (inst) { - inst->SetSerialNumber2(trader_items.at(i).item_sn); + inst->SetUniqueID(trader_items.at(i).item_sn); if (trader_items.at(i).item_charges > 0) { inst->SetCharges(trader_items.at(i).item_charges); } @@ -1118,7 +1118,7 @@ EQ::ItemInstance *Client::FindTraderItemBySerialNumber(std::string &serial_numbe slot_id = EQ::InventoryProfile::CalcSlotId(i, x); item = GetInv().GetItem(slot_id); if (item) { - if (item->GetSerialNumber2().compare(serial_number) == 0) { + if (item->GetUniqueID().compare(serial_number) == 0) { return item; } } @@ -1155,7 +1155,7 @@ GetItems2_Struct *Client::GetTraderItems() if (item) { gis->items[ndx] = item->GetID(); - gis->serial_number[ndx] = item->GetSerialNumber2(); + gis->serial_number[ndx] = item->GetUniqueID(); gis->charges[ndx] = item->GetCharges() == 0 ? 1 : item->GetCharges(); ndx++; } @@ -1177,7 +1177,7 @@ uint16 Client::FindTraderItem(std::string &serial_number, uint16 Quantity){ item = GetInv().GetItem(SlotID); - if (item && item->GetSerialNumber2().compare(serial_number) == 0 && + if (item && item->GetUniqueID().compare(serial_number) == 0 && (item->GetCharges() >= Quantity || (item->GetCharges() <= 0 && Quantity == 1))) { return SlotID; @@ -1316,8 +1316,8 @@ void Client::FindAndNukeTraderItem(std::string &serial_number, int16 quantity, C return; } else { - TraderRepository::UpdateQuantity(database, CharacterID(), item->GetSerialNumber2(), charges - quantity); - NukeTraderItem(slot_id, charges, quantity, customer, trader_slot, item->GetSerialNumber2(), item->GetID()); + TraderRepository::UpdateQuantity(database, CharacterID(), item->GetUniqueID(), charges - quantity); + NukeTraderItem(slot_id, charges, quantity, customer, trader_slot, item->GetUniqueID(), item->GetID()); return; } } @@ -1563,7 +1563,7 @@ void Client::BuyTraderItem(TraderBuy_Struct *tbs, Client *Trader, const EQApplic strn0cpy(outtbs->item_name, buy_item->GetItem()->Name, sizeof(outtbs->item_name)); strn0cpy( outtbs->serial_number, - buy_item->GetSerialNumber2().data(), + buy_item->GetUniqueID().data(), sizeof(outtbs->serial_number) ); @@ -1690,7 +1690,7 @@ static void UpdateTraderCustomerItemsAdded( inst->SetCharges(i.item_charges); inst->SetPrice(i.item_cost); - inst->SetSerialNumber2(i.item_sn); + inst->SetUniqueID(i.item_sn); //FIXinst->SetMerchantSlot(i.item_sn); if (inst->IsStackable()) { inst->SetMerchantCount(i.item_charges); @@ -1794,7 +1794,7 @@ static void UpdateTraderCustomerPriceChanged( continue; } - inst->SetSerialNumber2(trader_items.at(i).item_sn); + inst->SetUniqueID(trader_items.at(i).item_sn); //inst->SetMerchantSlot(trader_items.at(i).item_sn); LogTrading("Sending price update for [{}], Serial No. [{}] with [{}] charges", diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 3c48ff7ae..d1c65edef 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -354,7 +354,7 @@ std::unique_ptr ZoneDatabase::LoadSingleTraderItem(uint32 char } inst->SetCharges(charges); - inst->SetSerialNumber2(serial_number); + inst->SetUniqueID(serial_number); //FIX inst->SetMerchantSlot(serial_number); inst->SetPrice(cost); From c797fcca8726ed88dcdf64478f8bdb4a771252e1 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sat, 5 Apr 2025 22:55:21 -0300 Subject: [PATCH 08/48] Cleanup --- .../base/base_inventory_repository.h | 36 ++++++++++++------- common/shareddb.cpp | 8 ++--- world/client.cpp | 2 +- zone/client.cpp | 8 ++--- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/common/repositories/base/base_inventory_repository.h b/common/repositories/base/base_inventory_repository.h index 975fec324..b02648483 100644 --- a/common/repositories/base/base_inventory_repository.h +++ b/common/repositories/base/base_inventory_repository.h @@ -37,7 +37,8 @@ public: uint32_t ornament_icon; uint32_t ornament_idfile; int32_t ornament_hero_model; - std::string serial_number; + std::string item_unique_id; + uint32_t guid; }; static std::string PrimaryKey() @@ -66,7 +67,8 @@ public: "ornament_icon", "ornament_idfile", "ornament_hero_model", - "serial_number", + "item_unique_id", + "guid", }; } @@ -91,7 +93,8 @@ public: "ornament_icon", "ornament_idfile", "ornament_hero_model", - "serial_number", + "item_unique_id", + "guid", }; } @@ -150,7 +153,8 @@ public: e.ornament_icon = 0; e.ornament_idfile = 0; e.ornament_hero_model = 0; - e.serial_number = ""; + e.item_unique_id = ""; + e.guid = 0; return e; } @@ -205,7 +209,8 @@ public: e.ornament_icon = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; e.ornament_idfile = row[16] ? static_cast(strtoul(row[16], nullptr, 10)) : 0; e.ornament_hero_model = row[17] ? static_cast(atoi(row[17])) : 0; - e.serial_number = row[18] ? row[18] : ""; + e.item_unique_id = row[18] ? row[18] : ""; + e.guid = row[19] ? static_cast(strtoul(row[19], nullptr, 10)) : 0; return e; } @@ -257,7 +262,8 @@ public: v.push_back(columns[15] + " = " + std::to_string(e.ornament_icon)); v.push_back(columns[16] + " = " + std::to_string(e.ornament_idfile)); v.push_back(columns[17] + " = " + std::to_string(e.ornament_hero_model)); - v.push_back(columns[18] + " = '" + Strings::Escape(e.serial_number) + "'"); + v.push_back(columns[18] + " = '" + Strings::Escape(e.item_unique_id) + "'"); + v.push_back(columns[19] + " = " + std::to_string(e.guid)); auto results = db.QueryDatabase( fmt::format( @@ -297,7 +303,8 @@ public: v.push_back(std::to_string(e.ornament_icon)); v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); - v.push_back("'" + Strings::Escape(e.serial_number) + "'"); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); + v.push_back(std::to_string(e.guid)); auto results = db.QueryDatabase( fmt::format( @@ -345,7 +352,8 @@ public: v.push_back(std::to_string(e.ornament_icon)); v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); - v.push_back("'" + Strings::Escape(e.serial_number) + "'"); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); + v.push_back(std::to_string(e.guid)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -397,7 +405,8 @@ public: e.ornament_icon = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; e.ornament_idfile = row[16] ? static_cast(strtoul(row[16], nullptr, 10)) : 0; e.ornament_hero_model = row[17] ? static_cast(atoi(row[17])) : 0; - e.serial_number = row[18] ? row[18] : ""; + e.item_unique_id = row[18] ? row[18] : ""; + e.guid = row[19] ? static_cast(strtoul(row[19], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -440,7 +449,8 @@ public: e.ornament_icon = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; e.ornament_idfile = row[16] ? static_cast(strtoul(row[16], nullptr, 10)) : 0; e.ornament_hero_model = row[17] ? static_cast(atoi(row[17])) : 0; - e.serial_number = row[18] ? row[18] : ""; + e.item_unique_id = row[18] ? row[18] : ""; + e.guid = row[19] ? static_cast(strtoul(row[19], nullptr, 10)) : 0; all_entries.push_back(e); } @@ -533,7 +543,8 @@ public: v.push_back(std::to_string(e.ornament_icon)); v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); - v.push_back("'" + Strings::Escape(e.serial_number) + "'"); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); + v.push_back(std::to_string(e.guid)); auto results = db.QueryDatabase( fmt::format( @@ -574,7 +585,8 @@ public: v.push_back(std::to_string(e.ornament_icon)); v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); - v.push_back("'" + Strings::Escape(e.serial_number) + "'"); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); + 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 355a00fde..0f1622000 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -285,7 +285,7 @@ bool SharedDatabase::UpdateInventorySlot(uint32 char_id, const EQ::ItemInstance* e.ornament_icon = inst->GetOrnamentationIcon(); e.ornament_idfile = inst->GetOrnamentationIDFile(); e.ornament_hero_model = inst->GetOrnamentHeroModel(); - e.serial_number = inst->GetUniqueID(); + e.item_unique_id = inst->GetUniqueID(); const int replaced = InventoryRepository::ReplaceOne(*this, e); @@ -717,13 +717,13 @@ bool SharedDatabase::GetInventory(Client *c) inst->SetOrnamentationIDFile(ornament_idfile); inst->SetOrnamentHeroModel(item->HerosForgeModel); - if (row.serial_number.empty()) { + if (row.item_unique_id.empty()) { inst->CreateUniqueID(); - row.serial_number = inst->GetUniqueID(); + row.item_unique_id = inst->GetUniqueID(); queue.push_back(row); } else { - inst->SetUniqueID(row.serial_number); + inst->SetUniqueID(row.item_unique_id); } if ( diff --git a/world/client.cpp b/world/client.cpp index 6d52f46b4..57704d49b 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -2400,7 +2400,7 @@ bool Client::StoreCharacter( e.ornament_icon = inst->GetOrnamentationIcon(); e.ornament_idfile = inst->GetOrnamentationIDFile(); e.ornament_hero_model = inst->GetOrnamentHeroModel(); - e.serial_number = inst->GetUniqueID(); + e.item_unique_id = inst->GetUniqueID(); v.emplace_back(e); } diff --git a/zone/client.cpp b/zone/client.cpp index f1500e126..19d4fd1f2 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -9055,11 +9055,11 @@ void Client::QuestReward(Mob* target, const QuestReward_Struct &reward, bool fac void Client::CashReward(uint32 copper, uint32 silver, uint32 gold, uint32 platinum) { - auto outapp = std::make_unique(OP_CashReward, sizeof(CashReward_Struct)); + auto outapp = std::make_unique(OP_CashReward, static_cast(sizeof(CashReward_Struct))); auto outbuf = reinterpret_cast(outapp->pBuffer); - outbuf->copper = copper; - outbuf->silver = silver; - outbuf->gold = gold; + outbuf->copper = copper; + outbuf->silver = silver; + outbuf->gold = gold; outbuf->platinum = platinum; AddMoneyToPP(copper, silver, gold, platinum); From 32dcb634f004df7d2f094dacd42aa34293f27e51 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 6 Apr 2025 10:15:11 -0300 Subject: [PATCH 09/48] Start up of inventory snapshots --- common/database/database_update_manifest.cpp | 16 + .../base_inventory_snapshots_repository.h | 284 +++++++++--------- .../inventory_snapshots_repository.h | 153 +++++++++- zone/client.cpp | 8 +- zone/zonedb.cpp | 225 +++----------- 5 files changed, 359 insertions(+), 327 deletions(-) diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index fb0632201..976168c44 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -7204,6 +7204,22 @@ ALTER TABLE `character_parcels` ALTER TABLE `character_parcels_containers` ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `item_id`; +ALTER TABLE `inventory_snapshots` + CHANGE COLUMN `charid` `character_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `time_index`, + CHANGE COLUMN `slotid` `slot_id` MEDIUMINT(7) UNSIGNED NOT NULL DEFAULT '0' AFTER `character_id`, + CHANGE COLUMN `itemid` `item_id` INT(11) UNSIGNED NULL DEFAULT '0' AFTER `slot_id`, + CHANGE COLUMN `augslot1` `augment_one` MEDIUMINT(7) UNSIGNED NOT NULL DEFAULT '0' AFTER `color`, + CHANGE COLUMN `augslot2` `augment_two` MEDIUMINT(7) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_one`, + CHANGE COLUMN `augslot3` `augment_three` MEDIUMINT(7) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_two`, + CHANGE COLUMN `augslot4` `augment_four` MEDIUMINT(7) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_three`, + CHANGE COLUMN `augslot5` `augment_five` MEDIUMINT(7) UNSIGNED NULL DEFAULT '0' AFTER `augment_four`, + CHANGE COLUMN `augslot6` `augment_six` MEDIUMINT(7) NOT NULL DEFAULT '0' AFTER `augment_five`, + CHANGE COLUMN `ornamenticon` `ornament_icon` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `custom_data`, + CHANGE COLUMN `ornamentidfile` `ornament_idfile` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `ornament_icon`, + ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `ornament_hero_model`; + DROP PRIMARY KEY, + ADD PRIMARY KEY (`time_index`, `character_id`, `slot_id`) USING BTREE; + )", .content_schema_update = false }, diff --git a/common/repositories/base/base_inventory_snapshots_repository.h b/common/repositories/base/base_inventory_snapshots_repository.h index 0b6863727..0088a8784 100644 --- a/common/repositories/base/base_inventory_snapshots_repository.h +++ b/common/repositories/base/base_inventory_snapshots_repository.h @@ -20,22 +20,23 @@ class BaseInventorySnapshotsRepository { public: struct InventorySnapshots { uint32_t time_index; - uint32_t charid; - uint32_t slotid; - uint32_t itemid; + uint32_t character_id; + uint32_t slot_id; + uint32_t item_id; uint16_t charges; uint32_t color; - uint32_t augslot1; - uint32_t augslot2; - uint32_t augslot3; - uint32_t augslot4; - uint32_t augslot5; - int32_t augslot6; + uint32_t augment_one; + uint32_t augment_two; + uint32_t augment_three; + uint32_t augment_four; + uint32_t augment_five; + int32_t augment_six; uint8_t instnodrop; std::string custom_data; - uint32_t ornamenticon; - uint32_t ornamentidfile; + uint32_t ornament_icon; + uint32_t ornament_idfile; int32_t ornament_hero_model; + std::string item_unique_id; uint64_t guid; }; @@ -48,22 +49,23 @@ public: { return { "time_index", - "charid", - "slotid", - "itemid", + "character_id", + "slot_id", + "item_id", "charges", "color", - "augslot1", - "augslot2", - "augslot3", - "augslot4", - "augslot5", - "augslot6", + "augment_one", + "augment_two", + "augment_three", + "augment_four", + "augment_five", + "augment_six", "instnodrop", "custom_data", - "ornamenticon", - "ornamentidfile", + "ornament_icon", + "ornament_idfile", "ornament_hero_model", + "item_unique_id", "guid", }; } @@ -72,22 +74,23 @@ public: { return { "time_index", - "charid", - "slotid", - "itemid", + "character_id", + "slot_id", + "item_id", "charges", "color", - "augslot1", - "augslot2", - "augslot3", - "augslot4", - "augslot5", - "augslot6", + "augment_one", + "augment_two", + "augment_three", + "augment_four", + "augment_five", + "augment_six", "instnodrop", "custom_data", - "ornamenticon", - "ornamentidfile", + "ornament_icon", + "ornament_idfile", "ornament_hero_model", + "item_unique_id", "guid", }; } @@ -130,22 +133,23 @@ public: InventorySnapshots e{}; e.time_index = 0; - e.charid = 0; - e.slotid = 0; - e.itemid = 0; + e.character_id = 0; + e.slot_id = 0; + e.item_id = 0; e.charges = 0; e.color = 0; - e.augslot1 = 0; - e.augslot2 = 0; - e.augslot3 = 0; - e.augslot4 = 0; - e.augslot5 = 0; - e.augslot6 = 0; + e.augment_one = 0; + e.augment_two = 0; + e.augment_three = 0; + e.augment_four = 0; + e.augment_five = 0; + e.augment_six = 0; e.instnodrop = 0; e.custom_data = ""; - e.ornamenticon = 0; - e.ornamentidfile = 0; + e.ornament_icon = 0; + e.ornament_idfile = 0; e.ornament_hero_model = 0; + e.item_unique_id = ""; e.guid = 0; return e; @@ -184,23 +188,24 @@ public: InventorySnapshots e{}; e.time_index = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.charid = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.slotid = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.itemid = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.slot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; e.charges = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; e.color = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.augslot1 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.augslot2 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.augslot3 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.augslot4 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.augslot5 = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; - e.augslot6 = row[11] ? static_cast(atoi(row[11])) : 0; + e.augment_one = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_two = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_three = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_four = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_five = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.augment_six = row[11] ? static_cast(atoi(row[11])) : 0; e.instnodrop = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; e.custom_data = row[13] ? row[13] : ""; - 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_icon = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.ornament_idfile = 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; + e.item_unique_id = row[17] ? row[17] : ""; + e.guid = row[18] ? strtoull(row[18], nullptr, 10) : 0; return e; } @@ -235,23 +240,24 @@ public: auto columns = Columns(); v.push_back(columns[0] + " = " + std::to_string(e.time_index)); - v.push_back(columns[1] + " = " + std::to_string(e.charid)); - v.push_back(columns[2] + " = " + std::to_string(e.slotid)); - v.push_back(columns[3] + " = " + std::to_string(e.itemid)); + v.push_back(columns[1] + " = " + std::to_string(e.character_id)); + v.push_back(columns[2] + " = " + std::to_string(e.slot_id)); + v.push_back(columns[3] + " = " + std::to_string(e.item_id)); v.push_back(columns[4] + " = " + std::to_string(e.charges)); v.push_back(columns[5] + " = " + std::to_string(e.color)); - v.push_back(columns[6] + " = " + std::to_string(e.augslot1)); - v.push_back(columns[7] + " = " + std::to_string(e.augslot2)); - v.push_back(columns[8] + " = " + std::to_string(e.augslot3)); - v.push_back(columns[9] + " = " + std::to_string(e.augslot4)); - v.push_back(columns[10] + " = " + std::to_string(e.augslot5)); - v.push_back(columns[11] + " = " + std::to_string(e.augslot6)); + v.push_back(columns[6] + " = " + std::to_string(e.augment_one)); + v.push_back(columns[7] + " = " + std::to_string(e.augment_two)); + v.push_back(columns[8] + " = " + std::to_string(e.augment_three)); + v.push_back(columns[9] + " = " + std::to_string(e.augment_four)); + v.push_back(columns[10] + " = " + std::to_string(e.augment_five)); + v.push_back(columns[11] + " = " + std::to_string(e.augment_six)); v.push_back(columns[12] + " = " + std::to_string(e.instnodrop)); v.push_back(columns[13] + " = '" + Strings::Escape(e.custom_data) + "'"); - v.push_back(columns[14] + " = " + std::to_string(e.ornamenticon)); - v.push_back(columns[15] + " = " + std::to_string(e.ornamentidfile)); + v.push_back(columns[14] + " = " + std::to_string(e.ornament_icon)); + v.push_back(columns[15] + " = " + std::to_string(e.ornament_idfile)); v.push_back(columns[16] + " = " + std::to_string(e.ornament_hero_model)); - v.push_back(columns[17] + " = " + std::to_string(e.guid)); + v.push_back(columns[17] + " = '" + Strings::Escape(e.item_unique_id) + "'"); + v.push_back(columns[18] + " = " + std::to_string(e.guid)); auto results = db.QueryDatabase( fmt::format( @@ -274,22 +280,23 @@ public: std::vector v; v.push_back(std::to_string(e.time_index)); - v.push_back(std::to_string(e.charid)); - v.push_back(std::to_string(e.slotid)); - v.push_back(std::to_string(e.itemid)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.slot_id)); + v.push_back(std::to_string(e.item_id)); v.push_back(std::to_string(e.charges)); v.push_back(std::to_string(e.color)); - v.push_back(std::to_string(e.augslot1)); - v.push_back(std::to_string(e.augslot2)); - v.push_back(std::to_string(e.augslot3)); - v.push_back(std::to_string(e.augslot4)); - v.push_back(std::to_string(e.augslot5)); - v.push_back(std::to_string(e.augslot6)); + v.push_back(std::to_string(e.augment_one)); + v.push_back(std::to_string(e.augment_two)); + v.push_back(std::to_string(e.augment_three)); + v.push_back(std::to_string(e.augment_four)); + v.push_back(std::to_string(e.augment_five)); + v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.instnodrop)); v.push_back("'" + Strings::Escape(e.custom_data) + "'"); - v.push_back(std::to_string(e.ornamenticon)); - v.push_back(std::to_string(e.ornamentidfile)); + v.push_back(std::to_string(e.ornament_icon)); + v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); v.push_back(std::to_string(e.guid)); auto results = db.QueryDatabase( @@ -321,22 +328,23 @@ public: std::vector v; v.push_back(std::to_string(e.time_index)); - v.push_back(std::to_string(e.charid)); - v.push_back(std::to_string(e.slotid)); - v.push_back(std::to_string(e.itemid)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.slot_id)); + v.push_back(std::to_string(e.item_id)); v.push_back(std::to_string(e.charges)); v.push_back(std::to_string(e.color)); - v.push_back(std::to_string(e.augslot1)); - v.push_back(std::to_string(e.augslot2)); - v.push_back(std::to_string(e.augslot3)); - v.push_back(std::to_string(e.augslot4)); - v.push_back(std::to_string(e.augslot5)); - v.push_back(std::to_string(e.augslot6)); + v.push_back(std::to_string(e.augment_one)); + v.push_back(std::to_string(e.augment_two)); + v.push_back(std::to_string(e.augment_three)); + v.push_back(std::to_string(e.augment_four)); + v.push_back(std::to_string(e.augment_five)); + v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.instnodrop)); v.push_back("'" + Strings::Escape(e.custom_data) + "'"); - v.push_back(std::to_string(e.ornamenticon)); - v.push_back(std::to_string(e.ornamentidfile)); + v.push_back(std::to_string(e.ornament_icon)); + v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); v.push_back(std::to_string(e.guid)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); @@ -372,23 +380,24 @@ public: InventorySnapshots e{}; e.time_index = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.charid = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.slotid = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.itemid = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.slot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; e.charges = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; e.color = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.augslot1 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.augslot2 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.augslot3 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.augslot4 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.augslot5 = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; - e.augslot6 = row[11] ? static_cast(atoi(row[11])) : 0; + e.augment_one = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_two = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_three = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_four = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_five = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.augment_six = row[11] ? static_cast(atoi(row[11])) : 0; e.instnodrop = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; e.custom_data = row[13] ? row[13] : ""; - 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_icon = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.ornament_idfile = 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; + e.item_unique_id = row[17] ? row[17] : ""; + e.guid = row[18] ? strtoull(row[18], nullptr, 10) : 0; all_entries.push_back(e); } @@ -414,23 +423,24 @@ public: InventorySnapshots e{}; e.time_index = row[0] ? static_cast(strtoul(row[0], nullptr, 10)) : 0; - e.charid = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; - e.slotid = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.itemid = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; + e.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.slot_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_id = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; e.charges = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; e.color = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.augslot1 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.augslot2 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.augslot3 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.augslot4 = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; - e.augslot5 = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; - e.augslot6 = row[11] ? static_cast(atoi(row[11])) : 0; + e.augment_one = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_two = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_three = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_four = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; + e.augment_five = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; + e.augment_six = row[11] ? static_cast(atoi(row[11])) : 0; e.instnodrop = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; e.custom_data = row[13] ? row[13] : ""; - 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_icon = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.ornament_idfile = 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; + e.item_unique_id = row[17] ? row[17] : ""; + e.guid = row[18] ? strtoull(row[18], nullptr, 10) : 0; all_entries.push_back(e); } @@ -506,22 +516,23 @@ public: std::vector v; v.push_back(std::to_string(e.time_index)); - v.push_back(std::to_string(e.charid)); - v.push_back(std::to_string(e.slotid)); - v.push_back(std::to_string(e.itemid)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.slot_id)); + v.push_back(std::to_string(e.item_id)); v.push_back(std::to_string(e.charges)); v.push_back(std::to_string(e.color)); - v.push_back(std::to_string(e.augslot1)); - v.push_back(std::to_string(e.augslot2)); - v.push_back(std::to_string(e.augslot3)); - v.push_back(std::to_string(e.augslot4)); - v.push_back(std::to_string(e.augslot5)); - v.push_back(std::to_string(e.augslot6)); + v.push_back(std::to_string(e.augment_one)); + v.push_back(std::to_string(e.augment_two)); + v.push_back(std::to_string(e.augment_three)); + v.push_back(std::to_string(e.augment_four)); + v.push_back(std::to_string(e.augment_five)); + v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.instnodrop)); v.push_back("'" + Strings::Escape(e.custom_data) + "'"); - v.push_back(std::to_string(e.ornamenticon)); - v.push_back(std::to_string(e.ornamentidfile)); + v.push_back(std::to_string(e.ornament_icon)); + v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); v.push_back(std::to_string(e.guid)); auto results = db.QueryDatabase( @@ -546,22 +557,23 @@ public: std::vector v; v.push_back(std::to_string(e.time_index)); - v.push_back(std::to_string(e.charid)); - v.push_back(std::to_string(e.slotid)); - v.push_back(std::to_string(e.itemid)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.slot_id)); + v.push_back(std::to_string(e.item_id)); v.push_back(std::to_string(e.charges)); v.push_back(std::to_string(e.color)); - v.push_back(std::to_string(e.augslot1)); - v.push_back(std::to_string(e.augslot2)); - v.push_back(std::to_string(e.augslot3)); - v.push_back(std::to_string(e.augslot4)); - v.push_back(std::to_string(e.augslot5)); - v.push_back(std::to_string(e.augslot6)); + v.push_back(std::to_string(e.augment_one)); + v.push_back(std::to_string(e.augment_two)); + v.push_back(std::to_string(e.augment_three)); + v.push_back(std::to_string(e.augment_four)); + v.push_back(std::to_string(e.augment_five)); + v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.instnodrop)); v.push_back("'" + Strings::Escape(e.custom_data) + "'"); - v.push_back(std::to_string(e.ornamenticon)); - v.push_back(std::to_string(e.ornamentidfile)); + v.push_back(std::to_string(e.ornament_icon)); + v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); v.push_back(std::to_string(e.guid)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); diff --git a/common/repositories/inventory_snapshots_repository.h b/common/repositories/inventory_snapshots_repository.h index 0fb1f815c..71406c9b5 100644 --- a/common/repositories/inventory_snapshots_repository.h +++ b/common/repositories/inventory_snapshots_repository.h @@ -4,6 +4,7 @@ #include "../database.h" #include "../strings.h" #include "base/base_inventory_snapshots_repository.h" +#include "inventory_repository.h" class InventorySnapshotsRepository: public BaseInventorySnapshotsRepository { public: @@ -46,16 +47,15 @@ public: // Custom extended repository methods here static int64 CountInventorySnapshots(Database& db) { - const std::string& query = "SELECT COUNT(*) FROM (SELECT * FROM `inventory_snapshots` a GROUP BY `charid`, `time_index`) b"; + const std::string &query = + "SELECT COUNT(*) FROM (SELECT * FROM `inventory_snapshots` a GROUP BY `character_id`, `time_index`) b"; auto results = db.QueryDatabase(query); - if (!results.Success() || !results.RowCount()) { return -1; } - auto row = results.begin(); - + auto row = results.begin(); const int64 count = Strings::ToBigInt(row[0]); if (count > std::numeric_limits::max()) { @@ -68,6 +68,151 @@ public: return count; } + + static int64 CountCharacterInvSnapshots(Database& db, uint32 character_id) + { + const std::string &query = fmt::format( + "SELECT COUNT(*) FROM (SELECT * FROM `inventory_snapshots` a WHERE " + "`character_id` = '{}' GROUP BY `time_index`) b", + character_id + ); + + auto results = db.QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { + return -1; + } + + auto &row = results.begin(); + const int64 count = Strings::ToBigInt(row[0]); + + if (count > std::numeric_limits::max()) { + return -2; + } + + if (count < 0) { + return -3; + } + + return count; + } + + static void ClearCharacterInvSnapshots(Database &db, uint32 character_id, bool from_now) + { + uint32 del_time = time(nullptr); + if (!from_now) { + del_time -= RuleI(Character, InvSnapshotHistoryD) * 86400; + } + + DeleteWhere(db, fmt::format("`character_id` = '{}' AND `time_index` = '{}'", character_id, del_time)); + } + + static void ListCharacterInvSnapshots(Database &db, uint32 character_id, std::list> &is_list) + { + const std::string &query = fmt::format( + "SELECT `time_index`, COUNT(*) FROM `inventory_snapshots` WHERE " + "`character_id` = '{}' GROUP BY `time_index` ORDER BY `time_index` DESC", + character_id + ); + auto results = db.QueryDatabase(query); + + if (!results.Success()) + return; + + for (auto row: results) { + is_list.emplace_back(std::pair(Strings::ToUnsignedInt(row[0]), Strings::ToInt(row[1]))); + } + } + + static bool ValidateCharacterInvSnapshotTimestamp(Database &db, uint32 character_id, uint32 timestamp) + { + if (!character_id || !timestamp) { + return false; + } + + const std::string &query = fmt::format( + "SELECT * FROM `inventory_snapshots` WHERE `character_id` = '{}' " + "AND `time_index` = '{}' LIMIT 1", + character_id, + timestamp + ); + auto results = db.QueryDatabase(query); + + if (!results.Success() || results.RowCount() == 0) { + return false; + } + + return true; + } + + static void ParseCharacterInvSnapshot( + Database &db, + uint32 character_id, + uint32 timestamp, + std::list> &parse_list) + { + const std::string &query = fmt::format( + "SELECT `slot_id`, `item_id` FROM `inventory_snapshots` " + "WHERE `character_id` = '{}' AND `time_index` = '{}' ORDER BY `slot_id`", + character_id, + timestamp + ); + auto results = db.QueryDatabase(query); + + if (!results.Success()) { + return; + } + + for (auto row: results) { + parse_list.emplace_back(std::pair(Strings::ToInt(row[0]), Strings::ToUnsignedInt(row[1]))); + } + } + + static void TransformToInv(InventorySnapshots &out, const InventoryRepository::Inventory &in, uint32 time_index) + { + out.character_id = in.character_id; + out.item_id = in.item_id; + out.item_unique_id = in.item_unique_id; + out.augment_one = in.augment_one; + out.augment_two = in.augment_two; + out.augment_three = in.augment_three; + out.augment_four = in.augment_four; + out.augment_five = in.augment_five; + out.augment_six = in.augment_six; + out.charges = in.charges; + out.color = in.color; + out.custom_data = in.custom_data; + out.instnodrop = in.instnodrop; + out.ornament_hero_model = in.ornament_hero_model; + out.ornament_icon = in.ornament_icon; + out.ornament_idfile = in.ornament_idfile; + out.guid = in.guid; + out.slot_id = in.slot_id; + out.time_index = time_index; + } + + static bool SaveCharacterInvSnapshot(Database &db, uint32 character_id) + { + uint32 time_index = time(nullptr); + std::vector queue{}; + + auto inventory = InventoryRepository::GetWhere(db, fmt::format("`character_id` = '{}'", character_id)); + + for (auto const &i: inventory) { + auto snapshot = NewEntity(); + TransformToInv(snapshot, i, time_index); + queue.push_back(snapshot); + } + + if (!queue.empty()) { + InsertMany(db, queue); + LogInventory("Created inventory snapshot for [{}] with ([{}]) items", character_id, queue.size()); + return true; + } + + LogInventory("Failed to created inventory snapshot for [{}]", character_id); + return false; + } + }; #endif //EQEMU_INVENTORY_SNAPSHOTS_REPOSITORY_H diff --git a/zone/client.cpp b/zone/client.cpp index 19d4fd1f2..fa00b9609 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -15,11 +15,13 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "../common/global_define.h" #include -#include -#include #include +#include +#include +#include "../common/global_define.h" + +#include "../common/repositories/inventory_snapshots_repository.h" // for windows compile #ifndef _WINDOWS diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index d1c65edef..56ccaee42 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -55,8 +55,11 @@ #include "../common/repositories/character_evolving_items_repository.h" #include -#include #include +#include + +#include "../common/repositories/inventory_repository.h" +#include "../common/repositories/inventory_snapshots_repository.h" extern Zone* zone; @@ -1313,202 +1316,56 @@ bool ZoneDatabase::NoRentExpired(const std::string& name) return seconds > 1800; } -bool ZoneDatabase::SaveCharacterInvSnapshot(uint32 character_id) { - uint32 time_index = time(nullptr); - std::string query = StringFormat( - "INSERT " - "INTO" - " `inventory_snapshots` " - "(`time_index`," - " `charid`," - " `slotid`," - " `itemid`," - " `charges`," - " `color`," - " `augslot1`," - " `augslot2`," - " `augslot3`," - " `augslot4`," - " `augslot5`," - " `augslot6`," - " `instnodrop`," - " `custom_data`," - " `ornamenticon`," - " `ornamentidfile`," - " `ornament_hero_model`," - " `guid`" - ") " - "SELECT" - " %u," - " `character_id`," - " `slot_id`," - " `item_id`," - " `charges`," - " `color`," - " `augment_one`," - " `augment_two`," - " `augment_three`," - " `augment_four`," - " `augment_five`," - " `augment_six`," - " `instnodrop`," - " `custom_data`," - " `ornament_icon`," - " `ornament_idfile`," - " `ornament_hero_model`," - " `guid` " - "FROM" - " `inventory` " - "WHERE" - " `character_id` = %u", - time_index, - character_id - ); - auto results = database.QueryDatabase(query); - LogInventory("[{}] ([{}])", character_id, (results.Success() ? "pass" : "fail")); - return results.Success(); -} - -int ZoneDatabase::CountCharacterInvSnapshots(uint32 character_id) { - std::string query = StringFormat( - "SELECT" - " COUNT(*) " - "FROM " - "(" - "SELECT * FROM" - " `inventory_snapshots` a " - "WHERE" - " `charid` = %u " - "GROUP BY" - " `time_index`" - ") b", - character_id - ); - auto results = QueryDatabase(query); - - if (!results.Success()) - return -1; - - auto& row = results.begin(); - - int64 count = Strings::ToBigInt(row[0]); - if (count > 2147483647) - return -2; - if (count < 0) - return -3; - - return count; -} - -void ZoneDatabase::ClearCharacterInvSnapshots(uint32 character_id, bool from_now) { - uint32 del_time = time(nullptr); - if (!from_now) { del_time -= RuleI(Character, InvSnapshotHistoryD) * 86400; } - - std::string query = StringFormat( - "DELETE " - "FROM" - " `inventory_snapshots` " - "WHERE" - " `charid` = %u " - "AND" - " `time_index` <= %lu", - character_id, - (unsigned long)del_time - ); - QueryDatabase(query); -} - -void ZoneDatabase::ListCharacterInvSnapshots(uint32 character_id, std::list> &is_list) { - std::string query = StringFormat( - "SELECT" - " `time_index`," - " COUNT(*) " - "FROM" - " `inventory_snapshots` " - "WHERE" - " `charid` = %u " - "GROUP BY" - " `time_index` " - "ORDER BY" - " `time_index` " - "DESC", - character_id - ); - auto results = QueryDatabase(query); - - if (!results.Success()) - return; - - for (auto row : results) - is_list.emplace_back(std::pair(Strings::ToUnsignedInt(row[0]), Strings::ToInt(row[1]))); -} - -bool ZoneDatabase::ValidateCharacterInvSnapshotTimestamp(uint32 character_id, uint32 timestamp) { - if (!character_id || !timestamp) - return false; - - std::string query = StringFormat( - "SELECT" - " * " - "FROM" - " `inventory_snapshots` " - "WHERE" - " `charid` = %u " - "AND" - " `time_index` = %u " - "LIMIT 1", - character_id, - timestamp - ); - auto results = QueryDatabase(query); - - if (!results.Success() || results.RowCount() == 0) +bool ZoneDatabase::SaveCharacterInvSnapshot(uint32 character_id) +{ + if (!InventorySnapshotsRepository::SaveCharacterInvSnapshot(database, character_id)) { return false; + } return true; } -void ZoneDatabase::ParseCharacterInvSnapshot(uint32 character_id, uint32 timestamp, std::list> &parse_list) { - std::string query = StringFormat( - "SELECT" - " `slotid`," - " `itemid` " - "FROM" - " `inventory_snapshots` " - "WHERE" - " `charid` = %u " - "AND" - " `time_index` = %u " - "ORDER BY" - " `slotid`", - character_id, - timestamp - ); - auto results = QueryDatabase(query); +int ZoneDatabase::CountCharacterInvSnapshots(uint32 character_id) +{ + return InventorySnapshotsRepository::CountCharacterInvSnapshots(*this, character_id); +} - if (!results.Success()) - return; +void ZoneDatabase::ClearCharacterInvSnapshots(uint32 character_id, bool from_now) +{ + InventorySnapshotsRepository::ClearCharacterInvSnapshots(*this, character_id, from_now); +} - for (auto row : results) - parse_list.emplace_back(std::pair(Strings::ToInt(row[0]), Strings::ToUnsignedInt(row[1]))); +void ZoneDatabase::ListCharacterInvSnapshots(uint32 character_id, std::list> &is_list) +{ + InventorySnapshotsRepository::ListCharacterInvSnapshots(*this, character_id, is_list); +} + +bool ZoneDatabase::ValidateCharacterInvSnapshotTimestamp(uint32 character_id, uint32 timestamp) +{ + return InventorySnapshotsRepository::ValidateCharacterInvSnapshotTimestamp(*this, character_id, timestamp); +} + +void ZoneDatabase::ParseCharacterInvSnapshot(uint32 character_id, uint32 timestamp, std::list> &parse_list) +{ + InventorySnapshotsRepository::ParseCharacterInvSnapshot(*this, character_id, timestamp, parse_list); } void ZoneDatabase::DivergeCharacterInvSnapshotFromInventory(uint32 character_id, uint32 timestamp, std::list> &compare_list) { std::string query = StringFormat( "SELECT" - " slotid," - " itemid " + " slot_id," + " item_id " "FROM" " `inventory_snapshots` " "WHERE" " `time_index` = %u " "AND" - " `charid` = %u " + " `character_id` = %u " "AND" - " `slotid` NOT IN " + " `slot_id` NOT IN " "(" "SELECT" - " a.`slotid` " + " a.`slot_id` " "FROM" " `inventory_snapshots` a " "JOIN" @@ -1518,7 +1375,7 @@ void ZoneDatabase::DivergeCharacterInvSnapshotFromInventory(uint32 character_id, "WHERE" " a.`time_index` = %u " "AND" - " a.`charid` = %u " + " a.`character_id` = %u " "AND" " b.`character_id` = %u" ")", @@ -1540,27 +1397,27 @@ void ZoneDatabase::DivergeCharacterInvSnapshotFromInventory(uint32 character_id, void ZoneDatabase::DivergeCharacterInventoryFromInvSnapshot(uint32 character_id, uint32 timestamp, std::list> &compare_list) { std::string query = StringFormat( "SELECT" - " `slotid`," - " `itemid` " + " `slot_id`," + " `item_id` " "FROM" " `inventory` " "WHERE" " `character_id` = %u " "AND" - " `slotid` NOT IN " + " `slot_id` NOT IN " "(" "SELECT" - " a.`slotid` " + " a.`slot_id` " "FROM" " `inventory` a " "JOIN" " `inventory_snapshots` b " "USING" - " (`slotid`, `itemid`) " + " (`slot_id`, `item_id`) " "WHERE" " b.`time_index` = %u " "AND" - " b.`charid` = %u " + " b.`character_id` = %u " "AND" " a.`character_id` = %u" ")", From af49a4bd10022dde4aa23303be9f31f5afc40866 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 6 Apr 2025 10:27:08 -0300 Subject: [PATCH 10/48] Start up of inventory snapshots --- common/repositories/inventory_snapshots_repository.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/repositories/inventory_snapshots_repository.h b/common/repositories/inventory_snapshots_repository.h index 71406c9b5..487adc37d 100644 --- a/common/repositories/inventory_snapshots_repository.h +++ b/common/repositories/inventory_snapshots_repository.h @@ -103,7 +103,7 @@ public: del_time -= RuleI(Character, InvSnapshotHistoryD) * 86400; } - DeleteWhere(db, fmt::format("`character_id` = '{}' AND `time_index` = '{}'", character_id, del_time)); + DeleteWhere(db, fmt::format("`character_id` = '{}' AND `time_index` <= '{}'", character_id, del_time)); } static void ListCharacterInvSnapshots(Database &db, uint32 character_id, std::list> &is_list) From 69cfa3cdbe8a9348c32209e3493fc3b74fc70c6a Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 6 Apr 2025 11:46:51 -0300 Subject: [PATCH 11/48] Inventory snapshots complete --- .../inventory_snapshots_repository.h | 161 +++++++++++++++--- zone/zonedb.cpp | 155 +---------------- 2 files changed, 140 insertions(+), 176 deletions(-) diff --git a/common/repositories/inventory_snapshots_repository.h b/common/repositories/inventory_snapshots_repository.h index 487adc37d..6acf9ea8d 100644 --- a/common/repositories/inventory_snapshots_repository.h +++ b/common/repositories/inventory_snapshots_repository.h @@ -167,27 +167,51 @@ public: } } - static void TransformToInv(InventorySnapshots &out, const InventoryRepository::Inventory &in, uint32 time_index) + static void DivergeCharacterInvSnapshotFromInventory( + Database &db, + uint32 character_id, + uint32 timestamp, + std::list> &compare_list) { - out.character_id = in.character_id; - out.item_id = in.item_id; - out.item_unique_id = in.item_unique_id; - out.augment_one = in.augment_one; - out.augment_two = in.augment_two; - out.augment_three = in.augment_three; - out.augment_four = in.augment_four; - out.augment_five = in.augment_five; - out.augment_six = in.augment_six; - out.charges = in.charges; - out.color = in.color; - out.custom_data = in.custom_data; - out.instnodrop = in.instnodrop; - out.ornament_hero_model = in.ornament_hero_model; - out.ornament_icon = in.ornament_icon; - out.ornament_idfile = in.ornament_idfile; - out.guid = in.guid; - out.slot_id = in.slot_id; - out.time_index = time_index; + const std::string &query = fmt::format( + "SELECT slot_id, item_id FROM `inventory_snapshots` " + "WHERE `time_index` = '{0}' AND `character_id` = '{1}' AND `slot_id` NOT IN (" + "SELECT a.`slot_id` FROM `inventory_snapshots` a JOIN `inventory` b USING (`slot_id`, `item_id`) " + "WHERE a.`time_index` = '{0}' AND a.`character_id` = '{1}' AND b.`character_id` = '{1}')", + timestamp, + character_id + ); + auto results = db.QueryDatabase(query); + + if (!results.Success()) { + return; + } + + for (auto row: results) { + compare_list.emplace_back(std::pair(Strings::ToInt(row[0]), Strings::ToUnsignedInt(row[1]))); + } + } + + static void DivergeCharacterInventoryFromInvSnapshot( + Database &db, uint32 character_id, uint32 timestamp, std::list> &compare_list) + { + const std::string &query = fmt::format( + "SELECT `slot_id`, `item_id` FROM `inventory` WHERE " + "`character_id` = '{0}' AND `slot_id` NOT IN (" + "SELECT a.`slot_id` FROM `inventory` a JOIN `inventory_snapshots` b USING (`slot_id`, `item_id`) " + "WHERE b.`time_index` = '{1}' AND b.`character_id` = '{0}' AND a.`character_id` = '{0}')", + character_id, + timestamp + ); + + auto results = db.QueryDatabase(query); + if (!results.Success()) { + return; + } + + for (auto row: results) { + compare_list.emplace_back(std::pair(Strings::ToInt(row[0]), Strings::ToUnsignedInt(row[1]))); + } } static bool SaveCharacterInvSnapshot(Database &db, uint32 character_id) @@ -196,23 +220,104 @@ public: std::vector queue{}; auto inventory = InventoryRepository::GetWhere(db, fmt::format("`character_id` = '{}'", character_id)); + if (inventory.empty()) { + LogError("Character ID [{}] inventory is empty. Snapshot not created", character_id); + return false; + } for (auto const &i: inventory) { - auto snapshot = NewEntity(); - TransformToInv(snapshot, i, time_index); + auto snapshot = NewEntity(); + snapshot.character_id = i.character_id; + snapshot.item_id = i.item_id; + snapshot.item_unique_id = i.item_unique_id; + snapshot.augment_one = i.augment_one; + snapshot.augment_two = i.augment_two; + snapshot.augment_three = i.augment_three; + snapshot.augment_four = i.augment_four; + snapshot.augment_five = i.augment_five; + snapshot.augment_six = i.augment_six; + snapshot.charges = i.charges; + snapshot.color = i.color; + snapshot.custom_data = i.custom_data; + snapshot.instnodrop = i.instnodrop; + snapshot.ornament_hero_model = i.ornament_hero_model; + snapshot.ornament_icon = i.ornament_icon; + snapshot.ornament_idfile = i.ornament_idfile; + snapshot.guid = i.guid; + snapshot.slot_id = i.slot_id; + snapshot.time_index = time_index; queue.push_back(snapshot); } - if (!queue.empty()) { - InsertMany(db, queue); - LogInventory("Created inventory snapshot for [{}] with ([{}]) items", character_id, queue.size()); - return true; + if (queue.empty()) { + LogError("Character ID [{}] inventory is empty. Snapshot not created", character_id); + return false; } - LogInventory("Failed to created inventory snapshot for [{}]", character_id); - return false; + if (!InsertMany(db, queue)) { + LogError("Failed to created inventory snapshot for [{}]", character_id); + return false; + } + + LogInventory("Created inventory snapshot for [{}] with ([{}]) items", character_id, queue.size()); + return true; } + static bool RestoreCharacterInvSnapshot(Database &db, uint32 character_id, uint32 timestamp) + { + InventoryRepository::DeleteWhere(db, fmt::format("`character_id` = '{}'", character_id)); + + auto snapshot = GetWhere(db, fmt::format("`character_id` = '{}' AND `time_index` = '{}'", character_id, timestamp)); + if (snapshot.empty()) { + LogError("The snapshot requested could not be found. Restore failed for character id [{}] @ [{}] failed", + character_id, + timestamp + ); + return false; + } + + std::vector queue{}; + for (auto const &i: snapshot) { + auto inventory_entry = InventoryRepository::NewEntity(); + inventory_entry.character_id = i.character_id; + inventory_entry.item_id = i.item_id; + inventory_entry.item_unique_id = i.item_unique_id; + inventory_entry.augment_one = i.augment_one; + inventory_entry.augment_two = i.augment_two; + inventory_entry.augment_three = i.augment_three; + inventory_entry.augment_four = i.augment_four; + inventory_entry.augment_five = i.augment_five; + inventory_entry.augment_six = i.augment_six; + inventory_entry.charges = i.charges; + inventory_entry.color = i.color; + inventory_entry.custom_data = i.custom_data; + inventory_entry.instnodrop = i.instnodrop; + inventory_entry.ornament_hero_model = i.ornament_hero_model; + inventory_entry.ornament_icon = i.ornament_icon; + inventory_entry.ornament_idfile = i.ornament_idfile; + inventory_entry.guid = i.guid; + inventory_entry.slot_id = i.slot_id; + queue.push_back(inventory_entry); + } + + if (queue.empty()) { + LogError("The snapshot is empty. Restore failed for character id [{}] @ [{}] failed", character_id, timestamp); + return false; + } + + if (!InventoryRepository::InsertMany(db, queue)) { + LogError("A database error occurred. Restore failed for character id [{}] @ [{}] failed", character_id, timestamp); + return false; + } + + LogInventory( + "Restore complete for character id [{}] with snapshot @ [{}] with [{}] entries", + character_id, + timestamp, + queue.size() + ); + return true; + } }; #endif //EQEMU_INVENTORY_SNAPSHOTS_REPOSITORY_H diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 56ccaee42..4df6e956d 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1350,89 +1350,14 @@ void ZoneDatabase::ParseCharacterInvSnapshot(uint32 character_id, uint32 timesta InventorySnapshotsRepository::ParseCharacterInvSnapshot(*this, character_id, timestamp, parse_list); } -void ZoneDatabase::DivergeCharacterInvSnapshotFromInventory(uint32 character_id, uint32 timestamp, std::list> &compare_list) { - std::string query = StringFormat( - "SELECT" - " slot_id," - " item_id " - "FROM" - " `inventory_snapshots` " - "WHERE" - " `time_index` = %u " - "AND" - " `character_id` = %u " - "AND" - " `slot_id` NOT IN " - "(" - "SELECT" - " a.`slot_id` " - "FROM" - " `inventory_snapshots` a " - "JOIN" - " `inventory` b " - "USING" - " (`slot_id`, `item_id`) " - "WHERE" - " a.`time_index` = %u " - "AND" - " a.`character_id` = %u " - "AND" - " b.`character_id` = %u" - ")", - timestamp, - character_id, - timestamp, - character_id, - character_id - ); - auto results = QueryDatabase(query); - - if (!results.Success()) - return; - - for (auto row : results) - compare_list.emplace_back(std::pair(Strings::ToInt(row[0]), Strings::ToUnsignedInt(row[1]))); +void ZoneDatabase::DivergeCharacterInvSnapshotFromInventory(uint32 character_id, uint32 timestamp, std::list> &compare_list) +{ + InventorySnapshotsRepository::DivergeCharacterInvSnapshotFromInventory(*this, character_id, timestamp, compare_list); } -void ZoneDatabase::DivergeCharacterInventoryFromInvSnapshot(uint32 character_id, uint32 timestamp, std::list> &compare_list) { - std::string query = StringFormat( - "SELECT" - " `slot_id`," - " `item_id` " - "FROM" - " `inventory` " - "WHERE" - " `character_id` = %u " - "AND" - " `slot_id` NOT IN " - "(" - "SELECT" - " a.`slot_id` " - "FROM" - " `inventory` a " - "JOIN" - " `inventory_snapshots` b " - "USING" - " (`slot_id`, `item_id`) " - "WHERE" - " b.`time_index` = %u " - "AND" - " b.`character_id` = %u " - "AND" - " a.`character_id` = %u" - ")", - character_id, - timestamp, - character_id, - character_id - ); - auto results = QueryDatabase(query); - - if (!results.Success()) - return; - - for (auto row : results) - compare_list.emplace_back(std::pair(Strings::ToInt(row[0]), Strings::ToUnsignedInt(row[1]))); +void ZoneDatabase::DivergeCharacterInventoryFromInvSnapshot(uint32 character_id, uint32 timestamp, std::list> &compare_list) +{ + InventorySnapshotsRepository::DivergeCharacterInventoryFromInvSnapshot(*this, character_id, timestamp, compare_list); } bool ZoneDatabase::RestoreCharacterInvSnapshot(uint32 character_id, uint32 timestamp) { @@ -1443,73 +1368,7 @@ bool ZoneDatabase::RestoreCharacterInvSnapshot(uint32 character_id, uint32 times return false; } - std::string query = StringFormat( - "DELETE " - "FROM" - " `inventory` " - "WHERE" - " `character_id` = %u", - character_id - ); - auto results = database.QueryDatabase(query); - if (!results.Success()) - return false; - - query = StringFormat( - "INSERT " - "INTO" - " `inventory` " - "(`character_id`," - " `slot_id`," - " `item_id`," - " `charges`," - " `color`," - " `augment_one`," - " `augment_two`," - " `augment_three`," - " `augment_four`," - " `augment_five`," - " `augment_six`," - " `instnodrop`," - " `custom_data`," - " `ornament_icon`," - " `ornament_idfile`," - " `ornament_hero_model`," - " `guid` " - ") " - "SELECT" - " `charid`," - " `slotid`," - " `itemid`," - " `charges`," - " `color`," - " `augslot1`," - " `augslot2`," - " `augslot3`," - " `augslot4`," - " `augslot5`," - " `augslot6`," - " `instnodrop`," - " `custom_data`," - " `ornamenticon`," - " `ornamentidfile`," - " `ornament_hero_model`, " - " `guid` " - "FROM" - " `inventory_snapshots` " - "WHERE" - " `charid` = %u " - "AND" - " `time_index` = %u", - character_id, - timestamp - ); - results = database.QueryDatabase(query); - - LogInventory("[{}] snapshot for [{}] @ [{}]", - (results.Success() ? "restored" : "failed to restore"), character_id, timestamp); - - return results.Success(); + return InventorySnapshotsRepository::RestoreCharacterInvSnapshot(database, character_id, timestamp); } const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load /*= false*/) From 868815e65855089d7f83af40ff59e19e08db2ac8 Mon Sep 17 00:00:00 2001 From: neckkola <65987027+neckkola@users.noreply.github.com> Date: Sun, 6 Apr 2025 16:14:46 -0300 Subject: [PATCH 12/48] Start Trader with new unique_id --- common/eq_packet_structs.h | 36 ++++++-- common/item_instance.h | 1 + common/patches/rof2.cpp | 47 ++++++----- common/patches/rof2_structs.h | 14 +-- zone/client.h | 1 + zone/trading.cpp | 155 ++++++++++++++-------------------- 6 files changed, 127 insertions(+), 127 deletions(-) diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index bcd8f1ad7..9c537759b 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -3780,12 +3780,36 @@ struct Trader2_Struct { std::string serial_number[EQ::invtype::BAZAAR_SIZE]; }; -struct ClickTrader2_Struct { - uint32 action; - uint32 unknown_004; - uint64 items[EQ::invtype::BAZAAR_SIZE]; - uint32 item_cost[EQ::invtype::BAZAAR_SIZE]; - std::string serial_number[EQ::invtype::BAZAAR_SIZE]; +struct BazaarTraderDetails { + uint64 item_id; + std::string unique_id; + uint64 cost; + uint64 serial_number; // backwards compatibility. Not used for RoF2 as of March 2025 + + template + void serialize(Archive &archive) + { + archive( + CEREAL_NVP(item_id), + CEREAL_NVP(unique_id), + CEREAL_NVP(cost), + CEREAL_NVP(serial_number) + ); + } +}; + +struct ClickTraderNew_Struct { + uint32 action; + std::vector items; + + template + void serialize(Archive &archive) + { + archive( + CEREAL_NVP(action), + CEREAL_NVP(items) + ); + } }; struct GetItems2_Struct { diff --git a/common/item_instance.h b/common/item_instance.h index e80f1f23c..d11020406 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -40,6 +40,7 @@ class EvolveInfo; // Stores information about an evolving item family #include "../common/deity.h" #include "../common/memory_buffer.h" #include "../common/repositories/character_evolving_items_repository.h" +#include #include diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index b5ede82cb..53da0937c 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -6167,30 +6167,31 @@ namespace RoF2 switch (action) { case structs::RoF2BazaarTraderBuyerActions::BeginTraderMode: { DECODE_LENGTH_EXACT(structs::BeginTrader_Struct); - SETUP_DIRECT_DECODE(ClickTrader2_Struct, structs::BeginTrader_Struct); + + unsigned char *eq_buffer = __packet->pBuffer; + auto eq = (RoF2::structs::BeginTrader_Struct *) eq_buffer; + + ClickTraderNew_Struct out{}; + out.action = TraderOn; + for (auto i = 0; i < RoF2::invtype::BAZAAR_SIZE; i++) { + BazaarTraderDetails btd{}; + btd.unique_id = eq->item_unique_ids[i].item_unique_id; + btd.cost = eq->item_cost[i]; + out.items.push_back(btd); + } + + std::stringstream ss{}; + cereal::BinaryOutputArchive ar(ss); + { + ar(out); + } + + __packet->size = static_cast(ss.str().length()); + __packet->pBuffer = new unsigned char[__packet->size]{}; + memcpy(__packet->pBuffer, ss.str().data(), __packet->size); + safe_delete_array(eq_buffer); + LogTrading("(RoF2) BeginTraderMode action [{}]", action); - - emu->action = TraderOn; - std::copy_n(eq->item_cost, RoF2::invtype::BAZAAR_SIZE, emu->item_cost); - std::transform( - std::begin(eq->items), - std::end(eq->items), - std::begin(emu->serial_number), - [&](const structs::TraderItemSerial_Struct x) { - return std::string(x.serial_number); - }); - //std::ranges::copy(eq->items->serial_number, emu->serial_number); - //std::copy_n(eq->items->serial_number, RoF2::invtype::BAZAAR_SIZE, emu->serial_number); - // std::transform( - // std::begin(eq->items), - // std::end(eq->items), - // std::begin(emu->serial_number), - // [&](const structs::TraderItemSerial_Struct x) { - // return Strings::ToUnsignedBigInt(x.serial_number,0); - // } - // ); - - FINISH_DIRECT_DECODE(); break; } case structs::RoF2BazaarTraderBuyerActions::EndTraderMode: { diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 92f0a1d90..034f97aa7 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -3554,19 +3554,21 @@ struct WhoAllPlayerPart4 { }; struct TraderItemSerial_Struct { - char serial_number[17]; + char item_unique_id[17]; uint8 unknown_018; - void operator=(const char* a) { - //auto _tmp = fmt::format("{:016}", a); - strn0cpy(this->serial_number, a, sizeof(this->serial_number)); + TraderItemSerial_Struct& operator=(const char* a) { + strn0cpy(this->item_unique_id, a, sizeof(this->item_unique_id)); + unknown_018 = 0; + + return *this; } }; struct BeginTrader_Struct { /*0000*/ uint32 action; -/*0004*/ TraderItemSerial_Struct items[200]; -/*3604*/ uint32 item_cost[200]; +/*0004*/ TraderItemSerial_Struct item_unique_ids[RoF2::invtype::BAZAAR_SIZE]; +/*3604*/ uint32 item_cost[RoF2::invtype::BAZAAR_SIZE]; /*4404*/ }; diff --git a/zone/client.h b/zone/client.h index b2ec5483c..3579e05a7 100644 --- a/zone/client.h +++ b/zone/client.h @@ -374,6 +374,7 @@ public: uint16 FindTraderItem(std::string &SerialNumber,uint16 Quantity); uint32 FindTraderItemSerialNumber(int32 ItemID); EQ::ItemInstance* FindTraderItemBySerialNumber(std::string &serial_number); + EQ::ItemInstance* FindTraderItemByUniqueID(std::string &unique_id); void FindAndNukeTraderItem(std::string &serial_number, int16 quantity, Client* customer, uint16 trader_slot); void NukeTraderItem(uint16 slot, int16 charges, int16 quantity, Client* customer, uint16 trader_slot, const std::string &serial_number, int32 item_id = 0); void ReturnTraderReq(const EQApplicationPacket* app,int16 traderitemcharges, uint32 itemid = 0); diff --git a/zone/trading.cpp b/zone/trading.cpp index fed062a1a..55a8a0072 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -782,23 +782,6 @@ void Client::TraderShowItems() auto outapp = new EQApplicationPacket(OP_Trader, packet_size); memcpy(outapp->pBuffer, ss.str().data(), packet_size); - // // uint32 item_limit = trader_items.size() >= GetInv().GetLookup()->InventoryTypeSize.Bazaar ? - // // GetInv().GetLookup()->InventoryTypeSize.Bazaar : - // // trader_items.size(); - // uint32 item_limit = GetInv().GetLookup()->InventoryTypeSize.Bazaar; - // //FIX - // - // for (int i = 0; i < trader_items.size(); i++) { - // data->items[i].cost = trader_items.at(i).item_cost; - // strn0cpy(data->items[i].sn, trader_items.at(i).item_sn.data(), sizeof(data->items[i].sn[i])); - // } - // - // for (int i = trader_items.size(); i < item_limit; i++) { - // data->items[i].cost = 0; - // strn0cpy(data->items[i].sn, "0000000000000000", sizeof(data->items[i].sn)); - // } - // - // data->action = ListTraderItems; QueuePacket(outapp); safe_delete(outapp); @@ -841,40 +824,20 @@ void Client::Trader_CustomerBrowsing(Client *Customer) void Client::TraderStartTrader(const EQApplicationPacket *app) { uint32 max_items = GetInv().GetLookup()->InventoryTypeSize.Bazaar; - auto in = (ClickTrader2_Struct *) app->pBuffer; auto inv = GetTraderItems(); bool trade_items_valid = true; std::vector trader_items{}; + ClickTraderNew_Struct in; - //Check inventory for no-trade items - // for (auto i = 0; i < max_items; i++) { - // if (inv->items[i] == 0 || inv->serial_number[i].empty()) { - // continue; - // } - // - // auto inst = FindTraderItemBySerialNumber(inv->serial_number[i]); - // if (inst) { - // if (inst->GetItem() && inst->GetItem()->NoDrop == 0) { - // Message( - // Chat::Red, - // fmt::format( - // "Item: {} is NODROP and found in a Trader's Satchel. Please remove and restart trader mode", - // inst->GetItem()->Name - // ).c_str() - // ); - // TraderEndTrader(); - // safe_delete(inv); - // return; - // } - // } - // } + EQ::Util::MemoryStreamReader ss(reinterpret_cast(app->pBuffer), app->size); + cereal::BinaryInputArchive ar(ss); + { + ar(in); + } - for (uint32 i = 0; i < max_items; i++) { - if (inv->items[i] == 0 || inv->serial_number[i].empty()) { - continue; - } - - auto const inst = FindTraderItemBySerialNumber(inv->serial_number[i]); + uint32 slot_id = 0; + for (auto &i: in.items) { + auto const inst = FindTraderItemByUniqueID(i.unique_id); if (!inst) { trade_items_valid = false; break; @@ -886,58 +849,41 @@ void Client::TraderStartTrader(const EQApplicationPacket *app) Chat::Red, fmt::format( "Item: {} is NODROP and found in a Trader's Satchel. Please remove and restart trader mode", - inst->GetItem()->Name - ).c_str() - ); + inst->GetItem()->Name) + .c_str()); TraderEndTrader(); safe_delete(inv); return; } } - auto it = std::find(std::begin(in->serial_number), std::end(in->serial_number), inv->serial_number[i]); - if (inst && it != std::end(in->serial_number)) { - inst->SetPrice(in->item_cost[i]); - TraderRepository::Trader trader_item{}; + TraderRepository::Trader trader_item{}; - trader_item.id = 0; - trader_item.char_entity_id = GetID(); - trader_item.char_id = CharacterID(); - trader_item.char_zone_id = GetZoneID(); - trader_item.char_zone_instance_id = GetInstanceID(); - trader_item.item_charges = inst->GetCharges() == 0 ? 1 : inst->GetCharges(); - trader_item.item_cost = inst->GetPrice(); - trader_item.item_id = inst->GetID(); - trader_item.item_sn = in->serial_number[i]; - trader_item.slot_id = i; - trader_item.listing_date = time(nullptr); - if (inst->IsAugmented()) { - auto augs = inst->GetAugmentIDs(); - trader_item.aug_slot_1 = augs.at(0); - trader_item.aug_slot_2 = augs.at(1); - trader_item.aug_slot_3 = augs.at(2); - trader_item.aug_slot_4 = augs.at(3); - trader_item.aug_slot_5 = augs.at(4); - trader_item.aug_slot_6 = augs.at(5); - } + trader_item.id = 0; + trader_item.char_entity_id = GetID(); + trader_item.char_id = CharacterID(); + trader_item.char_zone_id = GetZoneID(); + trader_item.char_zone_instance_id = GetInstanceID(); + trader_item.item_charges = inst->GetCharges() == 0 ? 1 : inst->GetCharges(); + trader_item.item_cost = i.cost; + trader_item.item_id = inst->GetID(); + trader_item.item_sn = i.unique_id; + trader_item.slot_id = slot_id; + trader_item.listing_date = time(nullptr); + if (inst->IsAugmented()) { + auto augs = inst->GetAugmentIDs(); + trader_item.aug_slot_1 = augs.at(0); + trader_item.aug_slot_2 = augs.at(1); + trader_item.aug_slot_3 = augs.at(2); + trader_item.aug_slot_4 = augs.at(3); + trader_item.aug_slot_5 = augs.at(4); + trader_item.aug_slot_6 = augs.at(5); + } - trader_items.emplace_back(trader_item); - continue; - } - else if (inst) { - Message( - Chat::Red, - fmt::format( - "Item: {} has no price set. Please set a price and try again.", - inst->GetItem()->Name - ).c_str() - ); - trade_items_valid = false; - continue; - } + trader_items.emplace_back(trader_item); } - if (!trade_items_valid) { + if (!trade_items_valid || trader_items.empty()) { Message(Chat::Red, "You are not able to become a trader at this time. Invalid item found."); TraderEndTrader(); safe_delete(inv); @@ -952,7 +898,7 @@ void Client::TraderStartTrader(const EQApplicationPacket *app) if (ClientVersion() >= EQ::versions::ClientVersion::RoF) { auto outapp = new EQApplicationPacket(OP_Trader, sizeof(TraderStatus_Struct)); auto data = (TraderStatus_Struct *) outapp->pBuffer; - data->Code = TraderAck2; + data->Code = TraderAck2; QueuePacket(outapp); safe_delete(outapp); } @@ -1105,7 +1051,7 @@ uint32 Client::FindTraderItemSerialNumber(int32 ItemID) { return 0; } -EQ::ItemInstance *Client::FindTraderItemBySerialNumber(std::string &serial_number) +EQ::ItemInstance *Client::FindTraderItemBySerialNumber(std::string &unique_id) { EQ::ItemInstance *item = nullptr; int16 slot_id = 0; @@ -1118,7 +1064,7 @@ EQ::ItemInstance *Client::FindTraderItemBySerialNumber(std::string &serial_numbe slot_id = EQ::InventoryProfile::CalcSlotId(i, x); item = GetInv().GetItem(slot_id); if (item) { - if (item->GetUniqueID().compare(serial_number) == 0) { + if (item->GetUniqueID().compare(unique_id) == 0) { return item; } } @@ -1126,11 +1072,36 @@ EQ::ItemInstance *Client::FindTraderItemBySerialNumber(std::string &serial_numbe } } - LogTrading("Couldn't find item! Serial No. was [{}]", serial_number); + LogTrading("Couldn't find item! Serial No. was [{}]", unique_id); return nullptr; } +EQ::ItemInstance *Client::FindTraderItemByUniqueID(std::string &unique_id) +{ + EQ::ItemInstance *item = nullptr; + int16 slot_id = 0; + + for (int16 i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) { + item = GetInv().GetItem(i); + if (item && item->GetItem()->BagType == EQ::item::BagTypeTradersSatchel) { + for (int16 x = EQ::invbag::SLOT_BEGIN; x <= EQ::invbag::SLOT_END; x++) { + // we already have the parent bag and a contents iterator..why not just iterate the bag!?? + slot_id = EQ::InventoryProfile::CalcSlotId(i, x); + item = GetInv().GetItem(slot_id); + if (item) { + if (item->GetUniqueID().compare(unique_id) == 0) { + return item; + } + } + } + } + } + + LogTrading("Couldn't find item! Serial No. was [{}]", unique_id); + + return nullptr; +} GetItems2_Struct *Client::GetTraderItems() { From 6ede52c0049787d6c7d5ab8c9ec4e066be384f81 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 6 Apr 2025 23:34:05 -0300 Subject: [PATCH 13/48] Trader Direct purchase updated and tested --- common/bazaar.cpp | 9 +- common/database/database_update_manifest.cpp | 19 +- common/eq_packet_structs.h | 27 +- common/item_instance.cpp | 2 +- common/item_instance.h | 8 + common/patches/rof2.cpp | 83 +- common/patches/rof2_structs.h | 22 +- .../base/base_trader_repository.h | 192 ++-- common/repositories/trader_repository.h | 117 ++- zone/client.cpp | 16 +- zone/client.h | 25 +- zone/client_evolving_items.cpp | 6 +- zone/client_packet.cpp | 69 +- zone/inventory.cpp | 6 +- zone/parcels.cpp | 2 +- zone/string_ids.h | 3 + zone/trading.cpp | 853 ++++++++---------- zone/worldserver.cpp | 4 +- zone/zonedb.cpp | 12 +- 19 files changed, 687 insertions(+), 788 deletions(-) diff --git a/common/bazaar.cpp b/common/bazaar.cpp index c3c828be6..2943a9b93 100644 --- a/common/bazaar.cpp +++ b/common/bazaar.cpp @@ -187,11 +187,11 @@ Bazaar::GetSearchResults( ); } else { - search_criteria_trader.append(fmt::format(" AND trader.char_id = {}", search.trader_id)); + search_criteria_trader.append(fmt::format(" AND trader.character_id = {}", search.trader_id)); } } else { - search_criteria_trader.append(fmt::format(" AND trader.char_id = {}", search.trader_id)); + search_criteria_trader.append(fmt::format(" AND trader.character_id = {}", search.trader_id)); } } @@ -297,8 +297,8 @@ Bazaar::GetSearchResults( BazaarSearchResultsFromDB_Struct r{}; r.count = 1; - r.trader_id = t.trader.char_id; - r.serial_number = t.trader.item_sn; + r.trader_id = t.trader.character_id; + r.item_unique_id = t.trader.item_unique_id; r.cost = t.trader.item_cost; r.slot_id = t.trader.slot_id; r.charges = t.trader.item_charges; @@ -307,7 +307,6 @@ Bazaar::GetSearchResults( r.trader_zone_id = t.trader.char_zone_id; r.trader_zone_instance_id = t.trader.char_zone_instance_id; r.trader_entity_id = t.trader.char_entity_id; - r.serial_number_RoF = t.trader.item_sn; r.item_name = fmt::format("{:.63}\0", item_results.at(t.trader.item_id).name); r.trader_name = fmt::format("{:.63}\0", t.trader_name); r.item_stat = item_results.at(t.trader.item_id).stats; diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 976168c44..98e8128b6 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -7189,7 +7189,7 @@ ALTER TABLE `character_parcels_containers` ManifestEntry{ .version = 9329, .description = "2025_03_27_implement_item_unique_unique_id.sql", - .check = "SHOW COLUMNS FROM `inventory` LIKE 'serial_number'", + .check = "SHOW COLUMNS FROM `inventory` LIKE 'item_unique_id'", .condition = "empty", .match = "", .sql = R"( @@ -7220,6 +7220,23 @@ ALTER TABLE `inventory_snapshots` DROP PRIMARY KEY, ADD PRIMARY KEY (`time_index`, `character_id`, `slot_id`) USING BTREE; +ALTER TABLE `trader` + CHANGE COLUMN `char_id` `character_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `id`, + ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `item_id`, + CHANGE COLUMN `aug_slot_1` `augment_one` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `item_unique_id`, + CHANGE COLUMN `aug_slot_2` `augment_two` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_one`, + CHANGE COLUMN `aug_slot_3` `augment_three` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_two`, + CHANGE COLUMN `aug_slot_4` `augment_four` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_three`, + CHANGE COLUMN `aug_slot_5` `augment_five` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_four`, + CHANGE COLUMN `aug_slot_6` `augment_six` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_five`, + DROP COLUMN `item_sn`, + DROP INDEX `idx_trader_item_sn`, + DROP INDEX `charid_slotid`, + ADD INDEX `charid_slotid` (`character_id`, `slot_id`) USING BTREE, + DROP INDEX `idx_trader_char`, + ADD INDEX `idx_trader_char` (`character_id`, `char_zone_id`, `char_zone_instance_id`) USING BTREE, + ADD UNIQUE INDEX `idx_item_unique_id` (`item_unique_id`); + )", .content_schema_update = false }, diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 9c537759b..71495ac37 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -3093,7 +3093,7 @@ struct BazaarSearchCriteria_Struct { struct BazaarInspect_Struct { uint32 action; char player_name[64]; - char serial_number[16]; + char item_unique_id[17]; uint32 item_id; uint32 trader_id; }; @@ -3730,7 +3730,7 @@ struct Trader_Struct { }; struct TraderItems_Struct { - std::string serial_number; + std::string item_unique_id; uint32 item_id; uint64 item_cost; @@ -3738,7 +3738,7 @@ struct TraderItems_Struct { void serialize(Archive &archive) { archive( - CEREAL_NVP(serial_number), + CEREAL_NVP(item_unique_id), CEREAL_NVP(item_id), CEREAL_NVP(item_cost) ); @@ -3850,7 +3850,7 @@ struct TraderBuy_Struct { /*084*/ char seller_name[64]; /*148*/ char unknown_148[32]; /*180*/ char item_name[64]; -/*244*/ char serial_number[17]; +/*244*/ char item_unique_id[17]; /*261*/ char unknown_261[3]; /*264*/ uint32 item_id; /*268*/ uint32 price; @@ -3869,12 +3869,12 @@ struct TraderItemUpdate_Struct{ }; struct TraderPriceUpdate_Struct { -/*000*/ uint32 Action; -/*004*/ uint32 SubAction; -/*008*/ char serial_number[16]; -/*012*/ uint32 Unknown012; -/*016*/ uint32 NewPrice; -/*020*/ uint32 Unknown016; +/*000*/ uint32 action; +/*002*/ uint32 sub_action; +/*004*/ char item_unique_id[17]; +/*021*/ char unknown_021[3]; +/*024*/ uint32 unknown_024; +/*028*/ uint32 new_price; }; struct MoneyUpdate_Struct{ @@ -3889,6 +3889,7 @@ struct TraderDelItem_Struct{ uint32 trader_id; uint32 item_id; uint32 unknown_012; + char item_unique_id[17]; }; struct TraderClick_Struct{ @@ -6494,7 +6495,7 @@ struct BazaarSearchResultsFromDB_Struct { uint32 count; uint32 trader_id; uint32 item_id; - std::string serial_number; + std::string item_unique_id; uint32 charges; uint32 cost; uint32 slot_id; @@ -6506,7 +6507,6 @@ struct BazaarSearchResultsFromDB_Struct { uint32 item_stat; bool stackable; std::string item_name; - std::string serial_number_RoF; std::string trader_name; template @@ -6516,7 +6516,7 @@ struct BazaarSearchResultsFromDB_Struct { CEREAL_NVP(count), CEREAL_NVP(trader_id), CEREAL_NVP(item_id), - CEREAL_NVP(serial_number), + CEREAL_NVP(item_unique_id), CEREAL_NVP(charges), CEREAL_NVP(cost), CEREAL_NVP(slot_id), @@ -6528,7 +6528,6 @@ struct BazaarSearchResultsFromDB_Struct { CEREAL_NVP(item_stat), CEREAL_NVP(stackable), CEREAL_NVP(item_name), - CEREAL_NVP(serial_number_RoF), CEREAL_NVP(trader_name) ); } diff --git a/common/item_instance.cpp b/common/item_instance.cpp index 1f2c1af76..c3a246936 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -159,7 +159,7 @@ EQ::ItemInstance::ItemInstance(const ItemInstance& copy) m_timers = copy.m_timers; if (copy.GetUniqueID().empty()) { - LogError("Creating Serial Number as part of Clone command"); + LogError("Creating unique item ID as part of clone process for item id {}", copy.GetID()); copy.CreateUniqueID(); } m_unique_id = copy.m_unique_id; diff --git a/common/item_instance.h b/common/item_instance.h index d11020406..b30836325 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -168,6 +168,14 @@ namespace EQ int16 GetCharges() const { return m_charges; } void SetCharges(int16 charges) { m_charges = charges; } + int16 GetQuantityFromCharges() const + { + if (GetCharges() > 0 || IsStackable() || GetItem()->MaxCharges > 0) { + return GetCharges(); + } + + return 1; + } uint32 GetPrice() const { return m_price; } void SetPrice(uint32 price) { m_price = price; } diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 53da0937c..5191fa692 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -480,7 +480,7 @@ namespace RoF2 for (auto i: results) { VARSTRUCT_ENCODE_TYPE(uint32, bufptr, i.trader_id); //trader ID - VARSTRUCT_ENCODE_STRING(bufptr, i.serial_number_RoF.c_str()); //serial + VARSTRUCT_ENCODE_STRING(bufptr, i.item_unique_id.c_str()); //serial VARSTRUCT_ENCODE_TYPE(uint32, bufptr, i.cost); //cost VARSTRUCT_ENCODE_TYPE(uint32, bufptr, i.stackable ? i.charges : i.count); //quantity VARSTRUCT_ENCODE_TYPE(uint32, bufptr, i.item_id); //ID @@ -757,7 +757,7 @@ namespace RoF2 ar(bl); //packet size - auto packet_size = bl.item_name.length() + 1 + 34; + uint32 packet_size = bl.item_name.length() + 1 + 34; for (auto const &b: bl.trade_items) { packet_size += b.item_name.length() + 1; packet_size += 12; @@ -4130,17 +4130,19 @@ namespace RoF2 auto buffer = new char[4404]{}; // 4404 is the fixed size of the packet for 200 item limit of RoF2 auto pos = buffer; + auto pos_unique_id = buffer + 4; + auto pos_cost = buffer + 3604; VARSTRUCT_ENCODE_TYPE(uint32, pos, structs::RoF2BazaarTraderBuyerActions::ListTraderItems); for (auto const &t: tcm.items) { - strn0cpy(pos, t.serial_number.data(), t.serial_number.length() + 1); - pos += 3600; - VARSTRUCT_ENCODE_TYPE(uint32, pos, t.item_cost); - pos -= 3604 - 18; + strn0cpy(pos_unique_id, t.item_unique_id.data(), t.item_unique_id.length() + 1); + *(uint32 *) pos_cost = t.item_cost; + pos_unique_id += 18; + pos_cost += 4; } for (int i = tcm.items.size(); i < EQ::invtype::BAZAAR_SIZE; i++) { - strn0cpy(pos, "0000000000000000", 18); - pos += 18; + strn0cpy(pos_unique_id, "0000000000000000", 18); + pos_unique_id += 18; } safe_delete_array(in->pBuffer); @@ -4160,7 +4162,7 @@ namespace RoF2 } case PriceUpdate: { SETUP_DIRECT_ENCODE(TraderPriceUpdate_Struct, structs::TraderPriceUpdate_Struct); - switch (emu->SubAction) { + switch (emu->sub_action) { case BazaarPriceChange_AddItem: { auto outapp = std::make_unique( OP_Trader, @@ -4168,7 +4170,7 @@ namespace RoF2 ); auto data = (structs::TraderStatus_Struct *) outapp->pBuffer; - data->action = emu->Action; + data->action = emu->action; data->sub_action = BazaarPriceChange_AddItem; LogTrading( "(RoF2) PriceUpdate action [{}] AddItem subaction [{}]", @@ -4186,7 +4188,7 @@ namespace RoF2 ); auto data = (structs::TraderStatus_Struct *) outapp->pBuffer; - data->action = emu->Action; + data->action = emu->action; data->sub_action = BazaarPriceChange_RemoveItem; LogTrading( "(RoF2) PriceUpdate action [{}] RemoveItem subaction [{}]", @@ -4204,7 +4206,7 @@ namespace RoF2 ); auto data = (structs::TraderStatus_Struct *) outapp->pBuffer; - data->action = emu->Action; + data->action = emu->action; data->sub_action = BazaarPriceChange_UpdatePrice; LogTrading( "(RoF2) PriceUpdate action [{}] UpdatePrice subaction [{}]", @@ -4229,7 +4231,7 @@ namespace RoF2 "(RoF2) BuyTraderItem action [{}] item_id [{}] item_sn [{}] buyer [{}]", action, eq->item_id, - eq->serial_number, + eq->item_unique_id, eq->buyer_name ); dest->FastQueuePacket(&in); @@ -4268,8 +4270,7 @@ namespace RoF2 OUT_str(buyer_name); OUT_str(seller_name); OUT_str(item_name); - OUT_str(serial_number); - //strn0cpy(eq->serial_number, emu->serial_number.c_str(), sizeof(eq->serial_number)); + OUT_str(item_unique_id); FINISH_ENCODE(); } @@ -4279,15 +4280,13 @@ namespace RoF2 ENCODE_LENGTH_EXACT(TraderDelItem_Struct); SETUP_DIRECT_ENCODE(TraderDelItem_Struct, structs::TraderDelItem_Struct); LogTrading( - "(RoF2) trader_id [{}] item_id [{}]", + "(RoF2) trader_id [{}] item_unique_id [{}]", emu->trader_id, - emu->item_id + emu->item_unique_id ); - eq->TraderID = emu->trader_id; - auto serial = fmt::format("{:016}\n", emu->item_id); - strn0cpy(eq->SerialNumber, serial.c_str(), sizeof(eq->SerialNumber)); - LogTrading("(RoF2) TraderID [{}], SerialNumber: [{}]", emu->trader_id, emu->item_id); + eq->trader_id = emu->trader_id; + strn0cpy(eq->item_unique_id, emu->item_unique_id, sizeof(eq->item_unique_id)); FINISH_ENCODE(); } @@ -4334,8 +4333,7 @@ namespace RoF2 OUT_str(buyer_name); OUT_str(seller_name); OUT_str(item_name); - OUT_str(serial_number); - //strn0cpy(eq->serial_number, emu->serial_number.c_str(), sizeof(eq->serial_number)); + OUT_str(item_unique_id); FINISH_ENCODE(); break; @@ -6174,6 +6172,10 @@ namespace RoF2 ClickTraderNew_Struct out{}; out.action = TraderOn; for (auto i = 0; i < RoF2::invtype::BAZAAR_SIZE; i++) { + if (eq->item_cost[i] == 0) { + continue; + } + BazaarTraderDetails btd{}; btd.unique_id = eq->item_unique_ids[i].item_unique_id; btd.cost = eq->item_cost[i]; @@ -6214,13 +6216,9 @@ namespace RoF2 SETUP_DIRECT_DECODE(TraderPriceUpdate_Struct, structs::TraderPriceUpdate_Struct); LogTrading("(RoF2) PriceUpdate action [{}]", action); - emu->Action = PriceUpdate; - strn0cpy(emu->serial_number, eq->serial_number, sizeof(emu->serial_number)); - //FIXemu->serial_number = Strings::ToUnsignedBigInt(eq->serial_number, 0); - // if (emu->SerialNumber == 0) { - // LogTrading("(RoF2) Price change with invalid serial number [{}]", eq->serial_number); - // } - emu->NewPrice = eq->new_price; + emu->action = PriceUpdate; + strn0cpy(emu->item_unique_id, eq->item_unique_id, sizeof(emu->item_unique_id)); + emu->new_price = eq->new_price; FINISH_DIRECT_DECODE(); break; @@ -6310,23 +6308,13 @@ namespace RoF2 IN(item_id); IN(trader_id); emu->action = BazaarInspect; - strn0cpy(emu->serial_number, eq->serial_number, sizeof(emu->serial_number)); - //FIX emu->serial_number = Strings::ToUnsignedInt(eq->serial_number, 0); - // if (emu->serial_number == 0) { - // LogTrading( - // "(RoF2) trader_id = [{}] requested a BazaarInspect with an invalid serial number of [{}]", - // eq->trader_id, - // eq->serial_number - // ); - // FINISH_DIRECT_DECODE(); - // return; - // } + strn0cpy(emu->item_unique_id, eq->item_unique_id, sizeof(emu->item_unique_id)); - // LogTrading("(RoF2) BazaarInspect action [{}] item_id [{}] serial_number [{}]", - // action, - // eq->item_id, - // eq->serial_number - // ); + LogTrading("(RoF2) BazaarInspect action [{}] item_id [{}] serial_number [{}]", + action, + eq->item_id, + eq->item_unique_id + ); FINISH_DIRECT_DECODE(); break; } @@ -6363,8 +6351,7 @@ namespace RoF2 IN_str(seller_name); IN_str(item_name); //IN_str(serial_number); - strn0cpy(emu->serial_number, eq->serial_number, sizeof(emu->serial_number)); -//FIX emu->serial_number = eq->serial_number; + strn0cpy(emu->item_unique_id, eq->item_unique_id, sizeof(emu->item_unique_id)); FINISH_DIRECT_DECODE(); break; diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 034f97aa7..d544dc1d3 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -3311,7 +3311,7 @@ struct BazaarInspect_Struct { uint32 action; uint32 unknown_004; uint32 trader_id; - char serial_number[17]; + char item_unique_id[17]; char unknown_029[3]; uint32 item_id; uint32 unknown_036; @@ -3594,11 +3594,11 @@ struct BazaarWindowRemoveTrader_Struct { }; struct TraderPriceUpdate_Struct { - uint32 action; - char serial_number[17]; - char unknown_021[3]; - uint32 unknown_024; - uint32 new_price; +/*000*/ uint32 action; +/*004*/ char item_unique_id[17]; +/*021*/ char unknown_021[3]; +/*024*/ uint32 unknown_024; +/*028*/ uint32 new_price; }; struct Trader_ShowItems_Struct { @@ -3625,7 +3625,7 @@ struct TraderBuy_Struct { /*084*/ char seller_name[64]; /*148*/ char unknown_148[32]; /*180*/ char item_name[64]; -/*244*/ char serial_number[17]; +/*244*/ char item_unique_id[17]; /*261*/ char unknown_261[3]; /*264*/ uint32 item_id; /*268*/ uint32 price; @@ -3651,10 +3651,10 @@ struct MoneyUpdate_Struct{ }; struct TraderDelItem_Struct{ - /*000*/ uint32 Unknown000; - /*004*/ uint32 TraderID; - /*008*/ char SerialNumber[17]; - /*024*/ uint32 Unknown012; + /*000*/ uint32 unknown_000; + /*004*/ uint32 trader_id; + /*008*/ char item_unique_id[17]; + /*025*/ uint32 unknown_025; /*028*/ }; diff --git a/common/repositories/base/base_trader_repository.h b/common/repositories/base/base_trader_repository.h index a28ef2db5..b70da0755 100644 --- a/common/repositories/base/base_trader_repository.h +++ b/common/repositories/base/base_trader_repository.h @@ -20,15 +20,15 @@ class BaseTraderRepository { public: struct Trader { uint64_t id; - uint32_t char_id; + uint32_t character_id; uint32_t item_id; - uint32_t aug_slot_1; - uint32_t aug_slot_2; - uint32_t aug_slot_3; - uint32_t aug_slot_4; - uint32_t aug_slot_5; - uint32_t aug_slot_6; - std::string item_sn; + std::string item_unique_id; + uint32_t augment_one; + uint32_t augment_two; + uint32_t augment_three; + uint32_t augment_four; + uint32_t augment_five; + uint32_t augment_six; int32_t item_charges; uint32_t item_cost; uint8_t slot_id; @@ -48,15 +48,15 @@ public: { return { "id", - "char_id", + "character_id", "item_id", - "aug_slot_1", - "aug_slot_2", - "aug_slot_3", - "aug_slot_4", - "aug_slot_5", - "aug_slot_6", - "item_sn", + "item_unique_id", + "augment_one", + "augment_two", + "augment_three", + "augment_four", + "augment_five", + "augment_six", "item_charges", "item_cost", "slot_id", @@ -72,15 +72,15 @@ public: { return { "id", - "char_id", + "character_id", "item_id", - "aug_slot_1", - "aug_slot_2", - "aug_slot_3", - "aug_slot_4", - "aug_slot_5", - "aug_slot_6", - "item_sn", + "item_unique_id", + "augment_one", + "augment_two", + "augment_three", + "augment_four", + "augment_five", + "augment_six", "item_charges", "item_cost", "slot_id", @@ -130,15 +130,15 @@ public: Trader e{}; e.id = 0; - e.char_id = 0; + e.character_id = 0; e.item_id = 0; - e.aug_slot_1 = 0; - e.aug_slot_2 = 0; - e.aug_slot_3 = 0; - e.aug_slot_4 = 0; - e.aug_slot_5 = 0; - e.aug_slot_6 = 0; - e.item_sn = ""; + e.item_unique_id = ""; + e.augment_one = 0; + e.augment_two = 0; + e.augment_three = 0; + e.augment_four = 0; + e.augment_five = 0; + e.augment_six = 0; e.item_charges = 0; e.item_cost = 0; e.slot_id = 0; @@ -184,15 +184,15 @@ public: Trader e{}; e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; - e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.item_sn = row[9] ? row[9] : ""; + e.item_unique_id = row[3] ? row[3] : ""; + e.augment_one = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.augment_two = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.augment_three = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_four = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_five = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_six = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; e.item_charges = row[10] ? static_cast(atoi(row[10])) : 0; e.item_cost = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.slot_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; @@ -234,15 +234,15 @@ public: auto columns = Columns(); - v.push_back(columns[1] + " = " + std::to_string(e.char_id)); + v.push_back(columns[1] + " = " + std::to_string(e.character_id)); v.push_back(columns[2] + " = " + std::to_string(e.item_id)); - v.push_back(columns[3] + " = " + std::to_string(e.aug_slot_1)); - v.push_back(columns[4] + " = " + std::to_string(e.aug_slot_2)); - v.push_back(columns[5] + " = " + std::to_string(e.aug_slot_3)); - v.push_back(columns[6] + " = " + std::to_string(e.aug_slot_4)); - v.push_back(columns[7] + " = " + std::to_string(e.aug_slot_5)); - v.push_back(columns[8] + " = " + std::to_string(e.aug_slot_6)); - v.push_back(columns[9] + " = '" + Strings::Escape(e.item_sn) + "'"); + v.push_back(columns[3] + " = '" + Strings::Escape(e.item_unique_id) + "'"); + v.push_back(columns[4] + " = " + std::to_string(e.augment_one)); + v.push_back(columns[5] + " = " + std::to_string(e.augment_two)); + v.push_back(columns[6] + " = " + std::to_string(e.augment_three)); + v.push_back(columns[7] + " = " + std::to_string(e.augment_four)); + v.push_back(columns[8] + " = " + std::to_string(e.augment_five)); + v.push_back(columns[9] + " = " + std::to_string(e.augment_six)); v.push_back(columns[10] + " = " + std::to_string(e.item_charges)); v.push_back(columns[11] + " = " + std::to_string(e.item_cost)); v.push_back(columns[12] + " = " + std::to_string(e.slot_id)); @@ -273,15 +273,15 @@ public: std::vector v; v.push_back(std::to_string(e.id)); - v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.character_id)); v.push_back(std::to_string(e.item_id)); - v.push_back(std::to_string(e.aug_slot_1)); - v.push_back(std::to_string(e.aug_slot_2)); - v.push_back(std::to_string(e.aug_slot_3)); - v.push_back(std::to_string(e.aug_slot_4)); - v.push_back(std::to_string(e.aug_slot_5)); - v.push_back(std::to_string(e.aug_slot_6)); - v.push_back("'" + Strings::Escape(e.item_sn) + "'"); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); + v.push_back(std::to_string(e.augment_one)); + v.push_back(std::to_string(e.augment_two)); + v.push_back(std::to_string(e.augment_three)); + v.push_back(std::to_string(e.augment_four)); + v.push_back(std::to_string(e.augment_five)); + v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.item_charges)); v.push_back(std::to_string(e.item_cost)); v.push_back(std::to_string(e.slot_id)); @@ -320,15 +320,15 @@ public: std::vector v; v.push_back(std::to_string(e.id)); - v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.character_id)); v.push_back(std::to_string(e.item_id)); - v.push_back(std::to_string(e.aug_slot_1)); - v.push_back(std::to_string(e.aug_slot_2)); - v.push_back(std::to_string(e.aug_slot_3)); - v.push_back(std::to_string(e.aug_slot_4)); - v.push_back(std::to_string(e.aug_slot_5)); - v.push_back(std::to_string(e.aug_slot_6)); - v.push_back("'" + Strings::Escape(e.item_sn) + "'"); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); + v.push_back(std::to_string(e.augment_one)); + v.push_back(std::to_string(e.augment_two)); + v.push_back(std::to_string(e.augment_three)); + v.push_back(std::to_string(e.augment_four)); + v.push_back(std::to_string(e.augment_five)); + v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.item_charges)); v.push_back(std::to_string(e.item_cost)); v.push_back(std::to_string(e.slot_id)); @@ -371,15 +371,15 @@ public: Trader e{}; e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; - e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.item_sn = row[9] ? row[9] : ""; + e.item_unique_id = row[3] ? row[3] : ""; + e.augment_one = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.augment_two = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.augment_three = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_four = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_five = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_six = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; e.item_charges = row[10] ? static_cast(atoi(row[10])) : 0; e.item_cost = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.slot_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; @@ -413,15 +413,15 @@ public: Trader e{}; e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; - e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; e.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.item_sn = row[9] ? row[9] : ""; + e.item_unique_id = row[3] ? row[3] : ""; + e.augment_one = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.augment_two = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.augment_three = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.augment_four = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.augment_five = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.augment_six = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; e.item_charges = row[10] ? static_cast(atoi(row[10])) : 0; e.item_cost = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.slot_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; @@ -505,15 +505,15 @@ public: std::vector v; v.push_back(std::to_string(e.id)); - v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.character_id)); v.push_back(std::to_string(e.item_id)); - v.push_back(std::to_string(e.aug_slot_1)); - v.push_back(std::to_string(e.aug_slot_2)); - v.push_back(std::to_string(e.aug_slot_3)); - v.push_back(std::to_string(e.aug_slot_4)); - v.push_back(std::to_string(e.aug_slot_5)); - v.push_back(std::to_string(e.aug_slot_6)); - v.push_back("'" + Strings::Escape(e.item_sn) + "'"); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); + v.push_back(std::to_string(e.augment_one)); + v.push_back(std::to_string(e.augment_two)); + v.push_back(std::to_string(e.augment_three)); + v.push_back(std::to_string(e.augment_four)); + v.push_back(std::to_string(e.augment_five)); + v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.item_charges)); v.push_back(std::to_string(e.item_cost)); v.push_back(std::to_string(e.slot_id)); @@ -545,15 +545,15 @@ public: std::vector v; v.push_back(std::to_string(e.id)); - v.push_back(std::to_string(e.char_id)); + v.push_back(std::to_string(e.character_id)); v.push_back(std::to_string(e.item_id)); - v.push_back(std::to_string(e.aug_slot_1)); - v.push_back(std::to_string(e.aug_slot_2)); - v.push_back(std::to_string(e.aug_slot_3)); - v.push_back(std::to_string(e.aug_slot_4)); - v.push_back(std::to_string(e.aug_slot_5)); - v.push_back(std::to_string(e.aug_slot_6)); - v.push_back("'" + Strings::Escape(e.item_sn) + "'"); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); + v.push_back(std::to_string(e.augment_one)); + v.push_back(std::to_string(e.augment_two)); + v.push_back(std::to_string(e.augment_three)); + v.push_back(std::to_string(e.augment_four)); + v.push_back(std::to_string(e.augment_five)); + v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.item_charges)); v.push_back(std::to_string(e.item_cost)); v.push_back(std::to_string(e.slot_id)); diff --git a/common/repositories/trader_repository.h b/common/repositories/trader_repository.h index b5ac3810d..88cc7910a 100644 --- a/common/repositories/trader_repository.h +++ b/common/repositories/trader_repository.h @@ -58,9 +58,9 @@ public: if (RuleB(Bazaar, UseAlternateBazaarSearch)) { results = db.QueryDatabase(fmt::format( - "SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name " + "SELECT DISTINCT(t.character_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name " "FROM trader AS t " - "JOIN character_data AS c ON t.char_id = c.id " + "JOIN character_data AS c ON t.character_id = c.id " "WHERE t.char_zone_instance_id = {} " "ORDER BY t.char_zone_instance_id ASC " "LIMIT {}", @@ -69,13 +69,14 @@ public: ); } else { - results = db.QueryDatabase(fmt::format( - "SELECT DISTINCT(t.char_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name " - "FROM trader AS t " - "JOIN character_data AS c ON t.char_id = c.id " - "ORDER BY t.char_zone_instance_id ASC " - "LIMIT {}", - max_results) + results = db.QueryDatabase( + fmt::format( + "SELECT DISTINCT(t.character_id), t.char_zone_id, t.char_zone_instance_id, t.char_entity_id, c.name " + "FROM trader AS t " + "JOIN character_data AS c ON t.character_id = c.id " + "ORDER BY t.char_zone_instance_id ASC " + "LIMIT {}", + max_results) ); } @@ -101,7 +102,7 @@ public: { WelcomeData_Struct e{}; - auto results = db.QueryDatabase("SELECT COUNT(DISTINCT char_id), count(char_id) FROM trader;"); + auto results = db.QueryDatabase("SELECT COUNT(DISTINCT character_id), count(character_id) FROM trader;"); if (!results.RowCount()) { return e; @@ -113,15 +114,15 @@ public: return e; } - static int UpdateItem(Database &db, uint32 char_id, uint32 new_price, uint32 item_id, uint32 item_charges) + static int UpdateItem(Database &db, uint32 character_id, uint32 new_price, uint32 item_id, uint32 item_charges) { std::vector items{}; if (item_charges == 0) { items = GetWhere( db, fmt::format( - "char_id = '{}' AND item_id = '{}'", - char_id, + "character_id = '{}' AND item_id = '{}'", + character_id, item_id ) ); @@ -130,8 +131,8 @@ public: items = GetWhere( db, fmt::format( - "char_id = '{}' AND item_id = '{}' AND item_charges = '{}'", - char_id, + "character_id = '{}' AND item_id = '{}' AND item_charges = '{}'", + character_id, item_id, item_charges ) @@ -155,8 +156,8 @@ public: Trader item{}; auto query = fmt::format( - "SELECT t.char_id, t.item_id, t.serialnumber, t.charges, t.item_cost, t.slot_id, t.entity_id FROM trader AS t " - "WHERE t.entity_id = {} AND t.item_id = {} AND t.item_cost = {} " + "SELECT t.character_id, t.item_id, t.item_unique.id, t.charges, t.item_cost, t.slot_id, t.entity_id FROM trader AS t " + "WHERE t.entity_id = '{}' AND t.item_id = '{}' AND t.item_cost = '{}' " "LIMIT 1;", trader_id, item_id, @@ -168,41 +169,59 @@ public: return item; } - auto row = results.begin(); - item.char_id = Strings::ToInt(row[0]); - item.item_id = Strings::ToInt(row[1]); - item.item_sn = Strings::ToInt(row[2]); - item.item_charges = Strings::ToInt(row[3]); - item.item_cost = Strings::ToInt(row[4]); - item.slot_id = Strings::ToInt(row[5]); + auto row = results.begin(); + item.character_id = Strings::ToInt(row[0]); + item.item_id = Strings::ToInt(row[1]); + item.item_unique_id = row[2] ? row[2] : ""; + item.item_charges = Strings::ToInt(row[3]); + item.item_cost = Strings::ToInt(row[4]); + item.slot_id = Strings::ToInt(row[5]); return item; } - static int UpdateQuantity(Database &db, uint32 char_id, const std::string &serial_number, int16 quantity) + static int UpdateQuantity(Database &db, const std::string &item_unique_id, int16 quantity) { const auto trader_item = GetWhere( db, - fmt::format("char_id = '{}' AND item_sn = '{}' ", char_id, serial_number) + fmt::format("`item_unique_id` = '{}' ", item_unique_id) ); if (trader_item.empty() || trader_item.size() > 1) { return 0; } - auto m = trader_item[0]; + auto m = trader_item[0]; m.item_charges = quantity; m.listing_date = time(nullptr); return UpdateOne(db, m); } - static Trader GetItemBySerialNumber(Database &db, std::string &serial_number, uint32 trader_id) + static int UpdatePrice(Database &db, const std::string &item_unique_id, uint32 price) + { + const auto trader_item = GetWhere( + db, + fmt::format("`item_unique_id` = '{}' ", item_unique_id) + ); + + if (trader_item.empty() || trader_item.size() > 1) { + return 0; + } + + auto m = trader_item[0]; + m.item_cost = price; + m.listing_date = time(nullptr); + + return ReplaceOne(db, m); + } + + static Trader GetItemBySerialNumber(Database &db, std::string &item_unique_id, uint32 trader_id) { Trader e{}; const auto trader_item = GetWhere( db, - fmt::format("`char_id` = '{}' AND `item_sn` = '{}' LIMIT 1", trader_id, serial_number) + fmt::format("`character_id` = '{}' AND `item_unique_id` = '{}' LIMIT 1", trader_id, item_unique_id) ); if (trader_item.empty()) { @@ -240,21 +259,16 @@ public: return DeleteWhere(db, fmt::format("`id` IN({})", Strings::Implode(",", delete_ids))); } - static DistinctTraders_Struct GetTraderByInstanceAndSerialnumber( - Database &db, - uint32 instance_id, - std::string &serial_number - ) + static DistinctTraders_Struct GetTraderByItemUniqueNumber(Database &db, std::string &item_unique_id) { DistinctTraders_Struct trader{}; auto query = fmt::format( - "SELECT t.id, t.char_id, c.name " + "SELECT t.id, t.character_id, c.name " "FROM trader AS t " - "JOIN character_data AS c ON c.id = t.char_id " - "WHERE t.char_zone_id = 151 AND t.char_zone_instance_id = {} AND t.item_sn = {} LIMIT 1", - instance_id, - serial_number + "JOIN character_data AS c ON c.id = t.character_id " + "WHERE t.item_unique_id = '{}' LIMIT 1", + item_unique_id ); auto results = db.QueryDatabase(query); @@ -264,7 +278,6 @@ public: } auto row = results.begin(); - std::string name = row[2]; trader.trader_id = Strings::ToUnsignedInt(row[1]); trader.trader_name = row[2] ? row[2] : ""; @@ -279,8 +292,12 @@ public: std::vector all_entries{}; auto query = fmt::format( - "SELECT trader.*, c.`name` FROM `trader` INNER JOIN character_data AS c ON trader.char_id = c.id " - "WHERE {} ORDER BY trader.char_id ASC", + "SELECT trader.id, trader.character_id, trader.item_id, trader.item_unique_id, trader.augment_one, " + "trader.augment_two, trader.augment_three, trader.augment_four, trader.augment_five, trader.augment_six, " + "trader.item_charges, trader.item_cost, trader.slot_id, trader.char_entity_id, trader.char_zone_id, " + "trader.char_zone_instance_id, trader.active_transaction, c.`name` FROM `trader` " + "INNER JOIN character_data AS c ON trader.character_id = c.id " + "WHERE {} ORDER BY trader.character_id ASC", search_criteria_trader ); @@ -295,15 +312,15 @@ public: BazaarTraderSearch_Struct e{}; e.trader.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; - e.trader.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.trader.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; e.trader.item_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; - e.trader.aug_slot_1 = row[3] ? static_cast(strtoul(row[3], nullptr, 10)) : 0; - e.trader.aug_slot_2 = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; - e.trader.aug_slot_3 = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; - e.trader.aug_slot_4 = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; - e.trader.aug_slot_5 = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; - e.trader.aug_slot_6 = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; - e.trader.item_sn = row[9] ? row[9] : std::string(""); + e.trader.item_unique_id = row[3] ? row[3] : std::string(""); + e.trader.augment_one = row[4] ? static_cast(strtoul(row[4], nullptr, 10)) : 0; + e.trader.augment_two = row[5] ? static_cast(strtoul(row[5], nullptr, 10)) : 0; + e.trader.augment_three = row[6] ? static_cast(strtoul(row[6], nullptr, 10)) : 0; + e.trader.augment_four = row[7] ? static_cast(strtoul(row[7], nullptr, 10)) : 0; + e.trader.augment_five = row[8] ? static_cast(strtoul(row[8], nullptr, 10)) : 0; + e.trader.augment_six = row[9] ? static_cast(strtoul(row[9], nullptr, 10)) : 0; e.trader.item_charges = row[10] ? static_cast(atoi(row[10])) : 0; e.trader.item_cost = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.trader.slot_id = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; diff --git a/zone/client.cpp b/zone/client.cpp index fa00b9609..a27801e2b 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -12702,7 +12702,7 @@ uint16 Client::GetSkill(EQ::skills::SkillType skill_id) const return 0; } -void Client::RemoveItemBySerialNumber(const std::string &item_unique_id, uint32 quantity) +bool Client::RemoveItemByItemUniqueId(const std::string &item_unique_id, uint32 quantity) { EQ::ItemInstance *item = nullptr; uint32 removed_count = 0; @@ -12717,18 +12717,24 @@ void Client::RemoveItemBySerialNumber(const std::string &item_unique_id, uint32 if (item && item->GetUniqueID().compare(item_unique_id) == 0) { uint32 charges = item->IsStackable() ? item->GetCharges() : 0; uint32 stack_size = std::max(charges, static_cast(1)); - if ((removed_count + stack_size) <= quantity) { + if (removed_count + stack_size <= quantity) { removed_count += stack_size; - DeleteItemInInventory(slot_id, charges, true); + if (DeleteItemInInventory(slot_id, charges, true)) { + return true; + } } else { - uint32 amount_left = (quantity - removed_count); + uint32 amount_left = quantity - removed_count; if (amount_left > 0 && stack_size >= amount_left) { removed_count += amount_left; - DeleteItemInInventory(slot_id, amount_left, true); + if (DeleteItemInInventory(slot_id, amount_left, true)) { + return true; + } } } } } + + return false; } void Client::SendTopLevelInventory() diff --git a/zone/client.h b/zone/client.h index 3579e05a7..5cc62aab4 100644 --- a/zone/client.h +++ b/zone/client.h @@ -314,7 +314,8 @@ public: void Trader_CustomerBrowsing(Client *Customer); void TraderEndTrader(); - void TraderPriceUpdate(const EQApplicationPacket *app); + //void TraderPriceUpdate(const EQApplicationPacket *app); + void TraderUpdateItem(const EQApplicationPacket *app); void SendBazaarDone(uint32 trader_id); void SendBulkBazaarTraders(); void SendBulkBazaarBuyers(); @@ -375,11 +376,12 @@ public: uint32 FindTraderItemSerialNumber(int32 ItemID); EQ::ItemInstance* FindTraderItemBySerialNumber(std::string &serial_number); EQ::ItemInstance* FindTraderItemByUniqueID(std::string &unique_id); - void FindAndNukeTraderItem(std::string &serial_number, int16 quantity, Client* customer, uint16 trader_slot); + EQ::ItemInstance* FindTraderItemByUniqueID(const char* unique_id); + void FindAndNukeTraderItem(std::string &item_unique_id, int16 quantity, Client* customer, uint16 trader_slot); void NukeTraderItem(uint16 slot, int16 charges, int16 quantity, Client* customer, uint16 trader_slot, const std::string &serial_number, int32 item_id = 0); void ReturnTraderReq(const EQApplicationPacket* app,int16 traderitemcharges, uint32 itemid = 0); void TradeRequestFailed(const EQApplicationPacket* app); - void BuyTraderItem(TraderBuy_Struct* tbs, Client* trader, const EQApplicationPacket* app); + void BuyTraderItem(const EQApplicationPacket* app); void BuyTraderItemOutsideBazaar(TraderBuy_Struct* tbs, const EQApplicationPacket* app); void FinishTrade( Mob *with, @@ -416,9 +418,15 @@ public: void SendBecomeTraderToWorld(Client *trader, BazaarTraderBarterActions action); void SendBecomeTrader(BazaarTraderBarterActions action, uint32 trader_id); - bool IsThereACustomer() const { return customer_id ? true : false; } + bool IsThereACustomer() const { return customer_id ? true : false; } uint32 GetCustomerID() { return customer_id; } - void SetCustomerID(uint32 id) { customer_id = id; } + void SetCustomerID(uint32 id) { customer_id = id; } + void ClearTraderMerchantList() { m_trader_merchant_list.clear(); } + void AddDataToMerchantList(int16 slot_id, int32 quantity, const std::string &item_unique_id); + std::tuple GetDataFromMerchantListByMerchantSlotId(int16 slot_id); + int16 GetSlotFromMerchantListByItemUniqueId(const std::string &unique_id); + std::pair> GetDataFromMerchantListByItemUniqueId(const std::string &unique_id); + std::map>* GetTraderMerchantList() { return &m_trader_merchant_list; } void SetBuyerID(uint32 id) { m_buyer_id = id; } uint32 GetBuyerID() { return m_buyer_id; } @@ -587,7 +595,7 @@ public: void DisableAreaRegens(); void ServerFilter(SetServerFilter_Struct* filter); - void BulkSendTraderInventory(uint32 char_id); + void BulkSendTraderInventory(uint32 character_id); void SendSingleTraderItem(uint32 char_id, const std::string &serial_number); void BulkSendMerchantInventory(int merchant_id, int npcid); @@ -1145,13 +1153,13 @@ public: bool FindNumberOfFreeInventorySlotsWithSizeCheck(std::vector items); bool PushItemOnCursor(const EQ::ItemInstance& inst, bool client_update = false); void SendCursorBuffer(); - void DeleteItemInInventory(int16 slot_id, int16 quantity = 0, bool client_update = false, bool update_db = true); + bool DeleteItemInInventory(int16 slot_id, int16 quantity = 0, bool client_update = false, bool update_db = true); uint32 CountItem(uint32 item_id); void ResetItemCooldown(uint32 item_id); void SetItemCooldown(uint32 item_id, bool use_saved_timer = false, uint32 in_seconds = 1); uint32 GetItemCooldown(uint32 item_id); void RemoveItem(uint32 item_id, uint32 quantity = 1); - void RemoveItemBySerialNumber(const std::string &item_unique_id, uint32 quantity = 1); + bool RemoveItemByItemUniqueId(const std::string &item_unique_id, uint32 quantity = 1); bool SwapItem(MoveItem_Struct* move_in); void SwapItemResync(MoveItem_Struct* move_slots); void PutLootInInventory(int16 slot_id, const EQ::ItemInstance &inst, LootItem** bag_item_data = 0); @@ -2104,6 +2112,7 @@ private: uint8 mercSlot; // selected merc slot time_t m_trader_transaction_date; uint32 m_trader_count{}; + std::map> m_trader_merchant_list{}; uint32 m_buyer_id; uint32 m_barter_time; int32 m_parcel_platinum; diff --git a/zone/client_evolving_items.cpp b/zone/client_evolving_items.cpp index 44184e229..05f3d4234 100644 --- a/zone/client_evolving_items.cpp +++ b/zone/client_evolving_items.cpp @@ -386,7 +386,7 @@ bool Client::DoEvolveCheckProgression(EQ::ItemInstance &inst) PlayerEvent::EvolveItem e{}; - RemoveItemBySerialNumber(inst.GetUniqueID()); + RemoveItemByItemUniqueId(inst.GetUniqueID()); EvolvingItemsManager::Instance()->LoadPlayerEvent(inst, e); e.status = "Evolved Item due to obtaining progression - Old Evolve Item removed from inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); @@ -506,7 +506,7 @@ void Client::DoEvolveTransferXP(const EQApplicationPacket *app) PlayerEvent::EvolveItem e{}; - RemoveItemBySerialNumber(inst_from->GetUniqueID()); + RemoveItemByItemUniqueId(inst_from->GetUniqueID()); EvolvingItemsManager::Instance()->LoadPlayerEvent(*inst_from, e); e.status = "Transfer XP - Original FROM Evolve Item removed from inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); @@ -516,7 +516,7 @@ void Client::DoEvolveTransferXP(const EQApplicationPacket *app) e.status = "Transfer XP - Updated FROM item placed in inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); - RemoveItemBySerialNumber(inst_to->GetUniqueID()); + RemoveItemByItemUniqueId(inst_to->GetUniqueID()); EvolvingItemsManager::Instance()->LoadPlayerEvent(*inst_to, e); e.status = "Transfer XP - Original TO Evolve Item removed from inventory."; RecordPlayerEventLog(PlayerEvent::EVOLVE_ITEM, e); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 5b8fb836d..00082ce29 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -15434,10 +15434,10 @@ void Client::Handle_OP_Trader(const EQApplicationPacket *app) TraderStartTrader(app); break; } - case PriceUpdate: - case ItemMove: { - LogTrading("Trader Price Update"); - TraderPriceUpdate(app); + case ItemMove: + case PriceUpdate:{ + LogTrading("Trader item updated - removed, added or price change"); + TraderUpdateItem(app); break; } case EndTransaction: { @@ -15456,7 +15456,7 @@ void Client::Handle_OP_Trader(const EQApplicationPacket *app) break; } default: { - LogError("Unknown size for OP_Trader: [{}]\n", app->size); + LogTrading("Unknown size for OP_Trader: [{}]\n", app->size); } } } @@ -15467,29 +15467,12 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app) // // Client has elected to buy an item from a Trader // - auto in = (TraderBuy_Struct *) app->pBuffer; - if (RuleB(Bazaar, UseAlternateBazaarSearch) && in->trader_id >= TraderRepository::TRADER_CONVERT_ID) { - auto sn = std::string(in->serial_number); - auto trader = TraderRepository::GetTraderByInstanceAndSerialnumber( - database, - in->trader_id - TraderRepository::TRADER_CONVERT_ID, - sn - ); - - if (!trader.trader_id) { - LogTrading("Unable to convert trader id for {} and serial number {}. Trader Buy aborted.", - in->trader_id - TraderRepository::TRADER_CONVERT_ID, - in->serial_number - ); - return; - } - - in->trader_id = trader.trader_id; - strn0cpy(in->seller_name, trader.trader_name.c_str(), sizeof(in->seller_name)); - } - - auto trader = entity_list.GetClientByID(in->trader_id); + auto in = (TraderBuy_Struct *) app->pBuffer; + auto item_unique_id = std::string(in->item_unique_id); + auto trader_details = TraderRepository::GetTraderByItemUniqueNumber(database, item_unique_id); + auto trader = entity_list.GetClientByID(in->trader_id); + strn0cpy(in->seller_name, trader_details.trader_name.c_str(), sizeof(in->seller_name)); switch (in->method) { case BazaarByVendor: { @@ -15499,9 +15482,9 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app) in->trader_id, in->item_id, in->quantity, - in->serial_number + in->item_unique_id ); - BuyTraderItem(in, trader, app); + BuyTraderItem(app); } break; } @@ -15525,7 +15508,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app) in->trader_id, in->item_id, in->quantity, - in->serial_number + in->item_unique_id ); BuyTraderItemOutsideBazaar(in, app); break; @@ -15550,7 +15533,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app) in->trader_id, in->item_id, in->quantity, - in->serial_number + in->item_unique_id ); Message( Chat::Yellow, @@ -15561,6 +15544,9 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app) TradeRequestFailed(app); break; } + default: { + + } } } @@ -15651,17 +15637,18 @@ void Client::Handle_OP_TraderShop(const EQApplicationPacket *app) switch (in->Code) { case ClickTrader: { LogTrading("Handle_OP_TraderShop case ClickTrader [{}]", in->Code); - auto outapp = - std::make_unique(OP_TraderShop, static_cast(sizeof(TraderClick_Struct)) + auto outapp = std::make_unique( + OP_TraderShop, + static_cast(sizeof(TraderClick_Struct)) ); auto data = (TraderClick_Struct *) outapp->pBuffer; - auto trader_client = entity_list.GetClientByID(in->TraderID); + auto trader = entity_list.GetClientByID(in->TraderID); - if (trader_client) { - data->Approval = trader_client->WithCustomer(GetID()); + if (trader) { + data->Approval = trader->WithCustomer(GetID()); LogTrading("Client::Handle_OP_TraderShop: Shop Request ([{}]) to ([{}]) with Approval: [{}]", GetCleanName(), - trader_client->GetCleanName(), + trader->GetCleanName(), data->Approval ); } @@ -15669,6 +15656,9 @@ void Client::Handle_OP_TraderShop(const EQApplicationPacket *app) LogTrading("Client::Handle_OP_TraderShop: entity_list.GetClientByID(tcs->traderid)" " returned a nullptr pointer" ); + auto outapp = new EQApplicationPacket(OP_ShopEndConfirm); + QueuePacket(outapp); + safe_delete(outapp); return; } @@ -15678,8 +15668,9 @@ void Client::Handle_OP_TraderShop(const EQApplicationPacket *app) QueuePacket(outapp.get()); if (data->Approval) { - BulkSendTraderInventory(trader_client->CharacterID()); - trader_client->Trader_CustomerBrowsing(this); + ClearTraderMerchantList(); + BulkSendTraderInventory(trader->CharacterID()); + trader->Trader_CustomerBrowsing(this); SetTraderID(in->TraderID); LogTrading("Client::Handle_OP_TraderShop: Trader Inventory Sent to [{}] from [{}]", GetID(), diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 3c10ee909..340ede084 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -958,7 +958,7 @@ void Client::SendCursorBuffer() } // Remove item from inventory -void Client::DeleteItemInInventory(int16 slot_id, int16 quantity, bool client_update, bool update_db) { +bool Client::DeleteItemInInventory(int16 slot_id, int16 quantity, bool client_update, bool update_db) { #if (EQDEBUG >= 5) LogDebug("DeleteItemInInventory([{}], [{}], [{}])", slot_id, quantity, (client_update) ? "true":"false"); #endif @@ -977,7 +977,7 @@ void Client::DeleteItemInInventory(int16 slot_id, int16 quantity, bool client_up QueuePacket(outapp); safe_delete(outapp); } - return; + return false; } uint64 evolve_id = m_inv[slot_id]->GetEvolveUniqueID(); @@ -1031,6 +1031,8 @@ void Client::DeleteItemInInventory(int16 slot_id, int16 quantity, bool client_up safe_delete(outapp); } } + + return true; } bool Client::PushItemOnCursor(const EQ::ItemInstance& inst, bool client_update) diff --git a/zone/parcels.cpp b/zone/parcels.cpp index d6bb7a494..fef298f87 100644 --- a/zone/parcels.cpp +++ b/zone/parcels.cpp @@ -458,7 +458,7 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in) CharacterParcelsContainersRepository::InsertMany(database, all_entries); } - RemoveItemBySerialNumber(inst->GetUniqueID(), parcel_out.quantity == 0 ? 1 : parcel_out.quantity); + RemoveItemByItemUniqueId(inst->GetUniqueID(), parcel_out.quantity == 0 ? 1 : parcel_out.quantity); std::unique_ptr outapp(new EQApplicationPacket(OP_ShopSendParcel)); QueuePacket(outapp.get()); diff --git a/zone/string_ids.h b/zone/string_ids.h index 5e929db02..51e981476 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -309,6 +309,7 @@ #define PLAYER_CHARMED 1461 //You lose control of yourself! #define TRADER_BUSY 1468 //That Trader is currently with a customer. Please wait until their transaction is finished. #define SENSE_CORPSE_DIRECTION 1563 //You sense a corpse in this direction. +#define HOW_CAN_YOU_BUY_MORE 1571 //%1 tells you, 'Your inventory appears full! How can you buy more?' #define DUPE_LORE_MERCHANT 1573 //%1 tells you, 'You already have the lore item, %2, on your person, on your shroud, in the bank, in a real estate, or as an augment in another item. You cannot have more than one of a particular lore item at a time.' #define QUEUED_TELL 2458 //[queued] #define QUEUE_TELL_FULL 2459 //[zoing and queue is full] @@ -466,6 +467,8 @@ #define LDON_NO_LOCKPICK 7564 //You must have a lock pick in your inventory to do this. #define LDON_WAS_NOT_LOCKED 7565 //%1 was not locked. #define LDON_WAS_NOT_TRAPPED 7566 //%1 was not trapped +#define DUPLICATE_LORE 7623 //Transaction failed: Duplicate Lore Item! +#define INSUFFICIENT_FUNDS 7632 //Transaction failed: Insufficient funds! #define GAIN_SINGLE_AA_SINGLE_AA 8019 //You have gained an ability point! You now have %1 ability point. #define GAIN_SINGLE_AA_MULTI_AA 8020 //You have gained an ability point! You now have %1 ability points. #define GAIN_MULTI_AA_MULTI_AA 8021 //You have gained %1 ability point(s)! You now have %2 ability point(s). diff --git a/zone/trading.cpp b/zone/trading.cpp index 55a8a0072..2aecdaafa 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -759,7 +759,7 @@ void Client::TraderShowItems() std::stringstream ss{}; cereal::BinaryOutputArchive ar(ss); - auto trader_items = TraderRepository::GetWhere(database, fmt::format("`char_id` = '{}'", CharacterID())); + auto trader_items = TraderRepository::GetWhere(database, fmt::format("`character_id` = {}", CharacterID())); if (trader_items.empty()) { return; } @@ -769,9 +769,9 @@ void Client::TraderShowItems() for (auto const &t: trader_items) { TraderItems_Struct items{}; - items.serial_number = t.item_sn; - items.item_id = t.item_id; - items.item_cost = t.item_cost; + items.item_unique_id = t.item_unique_id; + items.item_id = t.item_id; + items.item_cost = t.item_cost; tcm.items.push_back(items); } @@ -861,23 +861,23 @@ void Client::TraderStartTrader(const EQApplicationPacket *app) trader_item.id = 0; trader_item.char_entity_id = GetID(); - trader_item.char_id = CharacterID(); + trader_item.character_id = CharacterID(); trader_item.char_zone_id = GetZoneID(); trader_item.char_zone_instance_id = GetInstanceID(); - trader_item.item_charges = inst->GetCharges() == 0 ? 1 : inst->GetCharges(); + trader_item.item_charges = inst->GetCharges(); trader_item.item_cost = i.cost; trader_item.item_id = inst->GetID(); - trader_item.item_sn = i.unique_id; + trader_item.item_unique_id = i.unique_id; trader_item.slot_id = slot_id; trader_item.listing_date = time(nullptr); if (inst->IsAugmented()) { - auto augs = inst->GetAugmentIDs(); - trader_item.aug_slot_1 = augs.at(0); - trader_item.aug_slot_2 = augs.at(1); - trader_item.aug_slot_3 = augs.at(2); - trader_item.aug_slot_4 = augs.at(3); - trader_item.aug_slot_5 = augs.at(4); - trader_item.aug_slot_6 = augs.at(5); + auto augs = inst->GetAugmentIDs(); + trader_item.augment_one = augs.at(0); + trader_item.augment_two = augs.at(1); + trader_item.augment_three = augs.at(2); + trader_item.augment_four = augs.at(3); + trader_item.augment_five = augs.at(4); + trader_item.augment_six = augs.at(5); } trader_items.emplace_back(trader_item); @@ -890,7 +890,7 @@ void Client::TraderStartTrader(const EQApplicationPacket *app) return; } - TraderRepository::DeleteWhere(database, fmt::format("`char_id` = '{}';", CharacterID())); + TraderRepository::DeleteWhere(database, fmt::format("`character_id` = {};", CharacterID())); TraderRepository::ReplaceMany(database, trader_items); safe_delete(inv); @@ -920,7 +920,7 @@ void Client::TraderEndTrader() } } - TraderRepository::DeleteWhere(database, fmt::format("`char_id` = '{}'", CharacterID())); + TraderRepository::DeleteWhere(database, fmt::format("`character_id` = {}", CharacterID())); SendBecomeTraderToWorld(this, TraderOff); SendTraderMode(TraderOff); @@ -945,12 +945,12 @@ void Client::SendTraderItem(uint32 ItemID, uint16 Quantity, TraderRepository::Tr database.CreateItem( item, Quantity, - t.aug_slot_1, - t.aug_slot_2, - t.aug_slot_3, - t.aug_slot_4, - t.aug_slot_5, - t.aug_slot_6 + t.augment_one, + t.augment_two, + t.augment_three, + t.augment_four, + t.augment_five, + t.augment_six ) ); @@ -969,58 +969,50 @@ void Client::SendTraderItem(uint32 ItemID, uint16 Quantity, TraderRepository::Tr } } -void Client::SendSingleTraderItem(uint32 char_id, const std::string &serial_number) +void Client::SendSingleTraderItem(uint32 character_id, const std::string &serial_number) { - auto inst = database.LoadSingleTraderItem(char_id, serial_number); + auto inst = database.LoadSingleTraderItem(character_id, serial_number); if (inst) { SendItemPacket(EQ::invslot::slotCursor, inst.get(), ItemPacketMerchant); // MainCursor? } } -void Client::BulkSendTraderInventory(uint32 char_id) +void Client::BulkSendTraderInventory(uint32 character_id) { const EQ::ItemData *item; - auto trader_items = TraderRepository::GetWhere(database, fmt::format("`char_id` = '{}'", char_id)); + auto trader_items = TraderRepository::GetWhere(database, fmt::format("`character_id` = {}", character_id)); uint32 item_limit = trader_items.size() >= GetInv().GetLookup()->InventoryTypeSize.Bazaar ? GetInv().GetLookup()->InventoryTypeSize.Bazaar : trader_items.size(); - for (uint32 i = 0; i < item_limit; i++) { - if ((trader_items.at(i).item_id == 0) || (trader_items.at(i).item_cost == 0)) { + for (int16 i = 0; i < item_limit; i++) { + if (trader_items.at(i).item_id == 0 || trader_items.at(i).item_cost == 0) { continue; } - else { - item = database.GetItem(trader_items.at(i).item_id); - } + + item = database.GetItem(trader_items.at(i).item_id); if (item && (item->NoDrop != 0)) { std::unique_ptr inst( database.CreateItem( trader_items.at(i).item_id, trader_items.at(i).item_charges, - trader_items.at(i).aug_slot_1, - trader_items.at(i).aug_slot_2, - trader_items.at(i).aug_slot_3, - trader_items.at(i).aug_slot_4, - trader_items.at(i).aug_slot_5, - trader_items.at(i).aug_slot_6 + trader_items.at(i).augment_one, + trader_items.at(i).augment_two, + trader_items.at(i).augment_three, + trader_items.at(i).augment_four, + trader_items.at(i).augment_five, + trader_items.at(i).augment_six ) ); if (inst) { - inst->SetUniqueID(trader_items.at(i).item_sn); - if (trader_items.at(i).item_charges > 0) { - inst->SetCharges(trader_items.at(i).item_charges); - } - - if (inst->IsStackable()) { - inst->SetMerchantCount(trader_items.at(i).item_charges); - //inst->SetMerchantSlot(trader_items.at(i).item_sn); - } - + inst->SetUniqueID(trader_items.at(i).item_unique_id); + inst->SetMerchantCount(inst->IsStackable() ? inst->GetCharges() : 1); + inst->SetMerchantSlot(i + 1); inst->SetPrice(trader_items.at(i).item_cost); - SendItemPacket(EQ::invslot::slotCursor, inst.get(), ItemPacketMerchant); -// safe_delete(inst); + AddDataToMerchantList(i + 1, inst->GetMerchantCount(), inst->GetUniqueID()); + SendItemPacket(i + 1, inst.get(), ItemPacketMerchant); } else LogTrading("Client::BulkSendTraderInventory nullptr inst pointer"); @@ -1098,8 +1090,32 @@ EQ::ItemInstance *Client::FindTraderItemByUniqueID(std::string &unique_id) } } - LogTrading("Couldn't find item! Serial No. was [{}]", unique_id); + LogTrading("Couldn't find item! item_unique_id was [{}]", unique_id); + return nullptr; +} +EQ::ItemInstance *Client::FindTraderItemByUniqueID(const char* unique_id) +{ + EQ::ItemInstance *item = nullptr; + int16 slot_id = 0; + + for (int16 i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) { + item = GetInv().GetItem(i); + if (item && item->GetItem()->BagType == EQ::item::BagTypeTradersSatchel) { + for (int16 x = EQ::invbag::SLOT_BEGIN; x <= EQ::invbag::SLOT_END; x++) { + // we already have the parent bag and a contents iterator..why not just iterate the bag!?? + slot_id = EQ::InventoryProfile::CalcSlotId(i, x); + item = GetInv().GetItem(slot_id); + if (item) { + if (item->GetUniqueID().compare(unique_id) == 0) { + return item; + } + } + } + } + } + + LogTrading("Couldn't find item! item_unique_id was [{}]", unique_id); return nullptr; } @@ -1188,6 +1204,7 @@ void Client::NukeTraderItem( tdis->unknown_000 = 0; tdis->trader_id = customer->GetID(); tdis->item_id = Strings::ToUnsignedBigInt(serial_number); + strn0cpy(tdis->item_unique_id, serial_number.c_str(), sizeof(tdis->item_unique_id)); tdis->unknown_012 = 0; customer->QueuePacket(outapp); safe_delete(outapp); @@ -1224,17 +1241,18 @@ void Client::NukeTraderItem( safe_delete(outapp2); } -void Client::FindAndNukeTraderItem(std::string &serial_number, int16 quantity, Client *customer, uint16 trader_slot) +void Client::FindAndNukeTraderItem(std::string &item_unique_id, int16 quantity, Client *customer, uint16 trader_slot) { - const EQ::ItemInstance *item = nullptr; - bool stackable = false; - int16 charges = 0; - uint16 slot_id = FindTraderItem(serial_number, quantity); + const EQ::ItemInstance *item = nullptr; + bool stackable = false; + int16 charges = 0; + uint16 slot_id = FindTraderItem(item_unique_id, quantity); + if (slot_id > 0) { item = GetInv().GetItem(slot_id); if (!item) { - LogTrading("Could not find Item: [{}] on Trader: [{}]", serial_number, quantity, GetName()); + LogTrading("Could not find Item: [{}] on Trader: [{}]", item_unique_id, quantity, GetName()); return; } @@ -1252,7 +1270,7 @@ void Client::FindAndNukeTraderItem(std::string &serial_number, int16 quantity, C if (charges <= quantity || (charges <= 0 && quantity == 1) || !stackable) { DeleteItemInInventory(slot_id, quantity); - auto trader_items = TraderRepository::GetWhere(database, fmt::format("`char_id` = '{}'", CharacterID())); + auto trader_items = TraderRepository::GetWhere(database, fmt::format("`character_id` = {}", CharacterID())); uint32 item_limit = trader_items.size() >= GetInv().GetLookup()->InventoryTypeSize.Bazaar ? GetInv().GetLookup()->InventoryTypeSize.Bazaar : trader_items.size(); @@ -1261,7 +1279,7 @@ void Client::FindAndNukeTraderItem(std::string &serial_number, int16 quantity, C std::vector delete_queue{}; for (int i = 0; i < item_limit; i++) { - if (test_slot && trader_items.at(i).item_sn.compare(serial_number) == 0) { + if (test_slot && trader_items.at(i).item_unique_id.compare(item_unique_id) == 0) { delete_queue.push_back(trader_items.at(i)); NukeTraderItem( slot_id, @@ -1269,7 +1287,7 @@ void Client::FindAndNukeTraderItem(std::string &serial_number, int16 quantity, C quantity, customer, trader_slot, - trader_items.at(i).item_sn, + trader_items.at(i).item_unique_id, trader_items.at(i).item_id ); test_slot = false; @@ -1287,13 +1305,12 @@ void Client::FindAndNukeTraderItem(std::string &serial_number, int16 quantity, C return; } else { - TraderRepository::UpdateQuantity(database, CharacterID(), item->GetUniqueID(), charges - quantity); NukeTraderItem(slot_id, charges, quantity, customer, trader_slot, item->GetUniqueID(), item->GetID()); return; } } LogTrading("Could NOT find a match for Item: [{}] with a quantity of: [{}] on Trader: [{}]\n", - serial_number, + item_unique_id, quantity, GetName() ); @@ -1354,109 +1371,62 @@ static void BazaarAuditTrail(const char *seller, const char *buyer, const char * database.QueryDatabase(query); } -void Client::BuyTraderItem(TraderBuy_Struct *tbs, Client *Trader, const EQApplicationPacket *app) +void Client::BuyTraderItem(const EQApplicationPacket *app) { - if (!Trader) { - return; - } + auto in = reinterpret_cast(app->pBuffer); + auto trader = entity_list.GetClientByID(in->trader_id); - if (!Trader->IsTrader()) { + if (!trader || !trader->IsTrader()) { + Message(Chat::Red, "The trader could not be found."); TradeRequestFailed(app); return; } - auto outapp = std::make_unique(OP_Trader, static_cast(sizeof(TraderBuy_Struct))); - auto outtbs = (TraderBuy_Struct *) outapp->pBuffer; - outtbs->item_id = tbs->item_id; + auto trader_packet = std::make_unique(OP_Trader, static_cast(sizeof(TraderBuy_Struct))); + auto data = reinterpret_cast(trader_packet->pBuffer); - const EQ::ItemInstance *buy_item = nullptr; - uint32 item_id = 0; - - if (ClientVersion() >= EQ::versions::ClientVersion::RoF) { - tbs->item_id = Strings::ToUnsignedBigInt(tbs->serial_number); - } - - auto sn = std::string(tbs->serial_number); - buy_item = Trader->FindTraderItemBySerialNumber(sn); - - if (!buy_item) { - LogTrading("Unable to find item id [{}] item_sn [{}] on trader", tbs->item_id, tbs->serial_number); + auto buy_inst = trader->FindTraderItemByUniqueID(in->item_unique_id); + if (!buy_inst) { + LogTrading("Unable to find item id [{}] item_sn [{}] on trader", in->item_id, in->item_unique_id); + Message(Chat::Red, "The trader no longer has the item for sale. Please refresh the merchant window."); TradeRequestFailed(app); return; } + uint32 quantity = in->quantity; LogTrading( - "Name: [{}] IsStackable: [{}] Requested Quantity: [{}] Charges on Item [{}]", - buy_item->GetItem()->Name, - buy_item->IsStackable(), - tbs->quantity, - buy_item->GetCharges() + "Name: [{}] IsStackable: [{}] Requested Quantity: [{}]", + buy_inst->GetItem()->Name, + buy_inst->IsStackable(), + quantity ); - // If the item is not stackable, then we can only be buying one of them. - if (!buy_item->IsStackable()) { - outtbs->quantity = 1; // normally you can't send more than 1 here - } - else { - // Stackable items, arrows, diamonds, etc - int32 item_charges = buy_item->GetCharges(); - // ItemCharges for stackables should not be <= 0 - if (item_charges <= 0) { - outtbs->quantity = 1; - // If the purchaser requested more than is in the stack, just sell them how many are actually in the stack. - } - else if (static_cast(item_charges) < tbs->quantity) { - outtbs->quantity = item_charges; - } - else { - outtbs->quantity = tbs->quantity; - } - } - LogTrading("Actual quantity that will be traded is [{}]", outtbs->quantity); - - if ((tbs->price * outtbs->quantity) <= 0) { + if (in->price * quantity <= 0) { Message(Chat::Red, "Internal error. Aborting trade. Please report this to the ServerOP. Error code is 1"); - Trader->Message( - Chat::Red, - "Internal error. Aborting trade. Please report this to the ServerOP. Error code is 1" - ); + trader->Message(Chat::Red, "Internal error. Aborting trade. Please report this to the ServerOP. Error code is 1"); LogError( "Bazaar: Zero price transaction between [{}] and [{}] aborted. Item: [{}] Charges: " "[{}] Qty [{}] Price: [{}]", - GetName(), - Trader->GetName(), - buy_item->GetItem()->Name, - buy_item->GetCharges(), - tbs->quantity, - tbs->price + GetCleanName(), + trader->GetCleanName(), + buy_inst->GetItem()->Name, + buy_inst->GetCharges(), + quantity, + in->price ); TradeRequestFailed(app); return; } - uint64 total_transaction_value = static_cast(tbs->price) * static_cast(outtbs->quantity); - + uint64 total_transaction_value = static_cast(in->price) * static_cast(quantity); if (total_transaction_value > MAX_TRANSACTION_VALUE) { - Message( - Chat::Red, - "That would exceed the single transaction limit of %u platinum.", - MAX_TRANSACTION_VALUE / 1000 - ); + Message(Chat::Red,"That would exceed the single transaction limit of %u platinum.", MAX_TRANSACTION_VALUE / 1000); TradeRequestFailed(app); return; } // This cannot overflow assuming MAX_TRANSACTION_VALUE, checked above, is the default of 2000000000 - uint32 total_cost = tbs->price * outtbs->quantity; - - if (Trader->ClientVersion() >= EQ::versions::ClientVersion::RoF) { - // RoF+ uses individual item price where older clients use total price - outtbs->price = tbs->price; - } - else { - outtbs->price = total_cost; - } - + uint32 total_cost = in->price * quantity; if (!TakeMoneyFromPP(total_cost)) { RecordPlayerEventLog( PlayerEvent::POSSIBLE_HACK, @@ -1464,6 +1434,7 @@ void Client::BuyTraderItem(TraderBuy_Struct *tbs, Client *Trader, const EQApplic .message = "Attempted to buy something in bazaar but did not have enough money." } ); + MessageString(Chat::Red, INSUFFICIENT_FUNDS); TradeRequestFailed(app); return; } @@ -1471,103 +1442,121 @@ void Client::BuyTraderItem(TraderBuy_Struct *tbs, Client *Trader, const EQApplic LogTrading("Customer Paid: [{}] in Copper", total_cost); uint32 platinum = total_cost / 1000; - total_cost -= (platinum * 1000); + total_cost -= platinum * 1000; uint32 gold = total_cost / 100; - total_cost -= (gold * 100); + total_cost -= gold * 100; uint32 silver = total_cost / 10; - total_cost -= (silver * 10); + total_cost -= silver * 10; uint32 copper = total_cost; - Trader->AddMoneyToPP(copper, silver, gold, platinum, true); + LogTrading("Trader Received: [{}] Platinum, [{}] Gold, [{}] Silver, [{}] Copper", platinum, gold, silver, copper); + ReturnTraderReq(app, quantity, buy_inst->GetID()); - if (buy_item && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_PURCHASE)) { + if (CheckLoreConflict(buy_inst->GetItem())) { + MessageString(Chat::Red, DUPLICATE_LORE); + TradeRequestFailed(app); + return; + } + + if (quantity != 1) { + buy_inst->SetCharges(quantity); + } + + if (!trader->RemoveItemByItemUniqueId(buy_inst->GetUniqueID(), quantity)) { + Message(Chat::Red, "The Trader no longer has the item. Please refresh the merchant window."); + TradeRequestFailed(app); + return; + } + + trader->AddMoneyToPP(copper, silver, gold, platinum, true); + + if (!AutoPutLootInInventory(*buy_inst, false, true)) { + MessageString(Chat::Red, HOW_CAN_YOU_BUY_MORE, trader->GetCleanName()); + TradeRequestFailed(app); + return; + } + + auto [slot_id, merchant_data] = GetDataFromMerchantListByItemUniqueId(buy_inst->GetUniqueID()); + auto [merchant_quantity, item_unique_id] = merchant_data; + + data->action = BazaarBuyItem; + data->price = in->price; + data->quantity = quantity; + data->trader_id = trader->GetID(); + strn0cpy(data->seller_name, trader->GetCleanName(), sizeof(data->seller_name)); + strn0cpy(data->buyer_name, GetCleanName(), sizeof(data->buyer_name)); + strn0cpy(data->item_name, buy_inst->GetItem()->Name, sizeof(data->item_name)); + strn0cpy(data->item_unique_id, buy_inst->GetUniqueID().data(), sizeof(data->item_unique_id)); + trader->QueuePacket(trader_packet.get()); + + if (merchant_quantity > quantity) { + buy_inst->SetMerchantCount(merchant_quantity - quantity); + buy_inst->SetMerchantSlot(slot_id); + buy_inst->SetPrice(in->price); + auto list = GetTraderMerchantList(); + std::get<0>(list->at(slot_id)) -= quantity; + SendItemPacket(slot_id, buy_inst, ItemPacketMerchant); + TraderRepository::UpdateQuantity(database, item_unique_id, merchant_quantity - quantity); + } + else { + auto client_packet = new EQApplicationPacket(OP_ShopDelItem, static_cast(sizeof(Merchant_DelItem_Struct))); + auto client_data = reinterpret_cast(client_packet->pBuffer); + + client_data->npcid = trader->GetID(); + client_data->playerid = GetID(); + client_data->itemslot = slot_id; + + QueuePacket(client_packet); + safe_delete(client_packet); + + auto list = GetTraderMerchantList(); + list->erase(slot_id); + TraderRepository::DeleteWhere(database, fmt::format("`item_unique_id` = '{}'", item_unique_id)); + } + + if (buy_inst && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_PURCHASE)) { auto e = PlayerEvent::TraderPurchaseEvent{ - .item_id = buy_item->GetID(), - .augment_1_id = buy_item->GetAugmentItemID(0), - .augment_2_id = buy_item->GetAugmentItemID(1), - .augment_3_id = buy_item->GetAugmentItemID(2), - .augment_4_id = buy_item->GetAugmentItemID(3), - .augment_5_id = buy_item->GetAugmentItemID(4), - .augment_6_id = buy_item->GetAugmentItemID(5), - .item_name = buy_item->GetItem()->Name, - .trader_id = Trader->CharacterID(), - .trader_name = Trader->GetCleanName(), - .price = tbs->price, - .quantity = outtbs->quantity, - .charges = buy_item->GetCharges(), - .total_cost = (tbs->price * outtbs->quantity), + .item_id = buy_inst->GetID(), + .augment_1_id = buy_inst->GetAugmentItemID(0), + .augment_2_id = buy_inst->GetAugmentItemID(1), + .augment_3_id = buy_inst->GetAugmentItemID(2), + .augment_4_id = buy_inst->GetAugmentItemID(3), + .augment_5_id = buy_inst->GetAugmentItemID(4), + .augment_6_id = buy_inst->GetAugmentItemID(5), + .item_name = buy_inst->GetItem()->Name, + .trader_id = trader->CharacterID(), + .trader_name = trader->GetCleanName(), + .price = in->price, + .quantity = quantity, + .charges = buy_inst->GetCharges(), + .total_cost = total_cost, .player_money_balance = GetCarriedMoney(), }; RecordPlayerEventLog(PlayerEvent::TRADER_PURCHASE, e); } - if (buy_item && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_SELL)) { + if (buy_inst && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_SELL)) { auto e = PlayerEvent::TraderSellEvent{ - .item_id = buy_item->GetID(), - .augment_1_id = buy_item->GetAugmentItemID(0), - .augment_2_id = buy_item->GetAugmentItemID(1), - .augment_3_id = buy_item->GetAugmentItemID(2), - .augment_4_id = buy_item->GetAugmentItemID(3), - .augment_5_id = buy_item->GetAugmentItemID(4), - .augment_6_id = buy_item->GetAugmentItemID(5), - .item_name = buy_item->GetItem()->Name, + .item_id = buy_inst->GetID(), + .augment_1_id = buy_inst->GetAugmentItemID(0), + .augment_2_id = buy_inst->GetAugmentItemID(1), + .augment_3_id = buy_inst->GetAugmentItemID(2), + .augment_4_id = buy_inst->GetAugmentItemID(3), + .augment_5_id = buy_inst->GetAugmentItemID(4), + .augment_6_id = buy_inst->GetAugmentItemID(5), + .item_name = buy_inst->GetItem()->Name, .buyer_id = CharacterID(), .buyer_name = GetCleanName(), - .price = tbs->price, - .quantity = outtbs->quantity, - .charges = buy_item->GetCharges(), - .total_cost = (tbs->price * outtbs->quantity), - .player_money_balance = Trader->GetCarriedMoney(), + .price = in->price, + .quantity = quantity, + .charges = buy_inst->GetCharges(), + .total_cost = total_cost, + .player_money_balance = trader->GetCarriedMoney(), }; - RecordPlayerEventLogWithClient(Trader, PlayerEvent::TRADER_SELL, e); + RecordPlayerEventLogWithClient(trader, PlayerEvent::TRADER_SELL, e); } - - LogTrading("Trader Received: [{}] Platinum, [{}] Gold, [{}] Silver, [{}] Copper", platinum, gold, silver, copper); - ReturnTraderReq(app, outtbs->quantity, item_id); - - outtbs->trader_id = GetID(); - outtbs->action = BazaarBuyItem; - strn0cpy(outtbs->seller_name, Trader->GetCleanName(), sizeof(outtbs->seller_name)); - strn0cpy(outtbs->buyer_name, GetCleanName(), sizeof(outtbs->buyer_name)); - strn0cpy(outtbs->item_name, buy_item->GetItem()->Name, sizeof(outtbs->item_name)); - strn0cpy( - outtbs->serial_number, - buy_item->GetUniqueID().data(), - sizeof(outtbs->serial_number) - ); - - TraderRepository::Trader t{}; - t.item_charges = buy_item->IsStackable() ? outtbs->quantity : buy_item->GetCharges(); - t.item_id = buy_item->GetItem()->ID; - t.aug_slot_1 = buy_item->GetAugmentItemID(0); - t.aug_slot_2 = buy_item->GetAugmentItemID(1); - t.aug_slot_3 = buy_item->GetAugmentItemID(2); - t.aug_slot_4 = buy_item->GetAugmentItemID(3); - t.aug_slot_5 = buy_item->GetAugmentItemID(4); - t.aug_slot_6 = buy_item->GetAugmentItemID(5); - t.char_id = CharacterID(); - t.slot_id = FindNextFreeParcelSlot(CharacterID()); - - SendTraderItem( - buy_item->GetItem()->ID, - buy_item->IsStackable() ? outtbs->quantity : buy_item->GetCharges(), - t - ); - - if (RuleB(Bazaar, AuditTrail)) { - BazaarAuditTrail(Trader->GetName(), GetName(), buy_item->GetItem()->Name, outtbs->quantity, outtbs->price, 0); - } - - Trader->FindAndNukeTraderItem(sn, outtbs->quantity, this, 0); - - if (item_id > 0 && Trader->ClientVersion() >= EQ::versions::ClientVersion::RoF) { - // Convert Serial Number back to ItemID for RoF+ - outtbs->item_id = item_id; - } - - Trader->QueuePacket(outapp.get()); } void Client::SendBazaarWelcome() @@ -1647,12 +1636,12 @@ static void UpdateTraderCustomerItemsAdded( database.CreateItem( i.item_id, i.item_charges, - i.aug_slot_1, - i.aug_slot_2, - i.aug_slot_3, - i.aug_slot_4, - i.aug_slot_5, - i.aug_slot_6 + i.augment_one, + i.augment_two, + i.augment_three, + i.augment_four, + i.augment_five, + i.augment_six ) ); if (!inst) { @@ -1661,15 +1650,14 @@ static void UpdateTraderCustomerItemsAdded( inst->SetCharges(i.item_charges); inst->SetPrice(i.item_cost); - inst->SetUniqueID(i.item_sn); - //FIXinst->SetMerchantSlot(i.item_sn); + inst->SetUniqueID(i.item_unique_id); if (inst->IsStackable()) { inst->SetMerchantCount(i.item_charges); } customer->SendItemPacket(EQ::invslot::slotCursor, inst.get(), ItemPacketMerchant); // MainCursor? LogTrading("Sending price update for [{}], Serial No. [{}] with [{}] charges", - item->Name, i.item_sn, i.item_charges); + item->Name, i.item_unique_id, i.item_charges); } } } @@ -1718,7 +1706,7 @@ static void UpdateTraderCustomerPriceChanged( // } //tdis->item_id = trader_items.at(i).item_sn; LogTrading("Telling customer to remove item [{}] with [{}] charges and S/N [{}]", - item_id, charges, trader_items.at(i).item_sn); + item_id, charges, trader_items.at(i).item_unique_id); customer->QueuePacket(outapp); } @@ -1735,12 +1723,12 @@ static void UpdateTraderCustomerPriceChanged( database.CreateItem( it->item_id, it->item_charges, - it->aug_slot_1, - it->aug_slot_2, - it->aug_slot_3, - it->aug_slot_4, - it->aug_slot_5, - it->aug_slot_6 + it->augment_one, + it->augment_two, + it->augment_three, + it->augment_four, + it->augment_five, + it->augment_six ) ); if (!inst) { @@ -1765,15 +1753,13 @@ static void UpdateTraderCustomerPriceChanged( continue; } - inst->SetUniqueID(trader_items.at(i).item_sn); - //inst->SetMerchantSlot(trader_items.at(i).item_sn); + inst->SetUniqueID(trader_items.at(i).item_unique_id); LogTrading("Sending price update for [{}], Serial No. [{}] with [{}] charges", - item->Name, trader_items.at(i).item_sn, trader_items.at(i).item_charges); + item->Name, trader_items.at(i).item_unique_id, trader_items.at(i).item_charges); customer->SendItemPacket(EQ::invslot::slotCursor, inst.get(), ItemPacketMerchant); // MainCursor?? } -// safe_delete(inst); } void Client::SendBuyerResults(BarterSearchRequest_Struct& bsr) @@ -2469,237 +2455,100 @@ void Client::SendTraderMode(BazaarTraderBarterActions status) safe_delete(outapp); } -void Client::TraderPriceUpdate(const EQApplicationPacket *app) +void Client::TraderUpdateItem(const EQApplicationPacket *app) { - // Handle price updates from the Trader and update a customer browsing our stuff if necessary - // This method also handles removing items from sale and adding them back up whilst still in - // Trader mode. - // - auto tpus = (TraderPriceUpdate_Struct *) app->pBuffer; + auto in = reinterpret_cast(app->pBuffer); + uint32 new_price = in->new_price; + auto inst = FindTraderItemByUniqueID(in->item_unique_id); + std::unique_ptr inst_copy(inst ? inst->Clone() : nullptr); - LogTrading( - "Received Price Update for [{}] Item Serial No. [{}] New Price [{}]", - GetName(), - tpus->serial_number, - tpus->NewPrice - ); - - // Pull the items this Trader currently has for sale from the trader table. - // - auto trader_items = TraderRepository::GetWhere(database, fmt::format("`char_id` = '{}'", CharacterID())); - uint32 item_limit = trader_items.size() >= GetInv().GetLookup()->InventoryTypeSize.Bazaar - ? GetInv().GetLookup()->InventoryTypeSize.Bazaar - : trader_items.size(); - - // The client only sends a single update with the Serial Number of the item whose price has been updated. - // We must update the price for all the Trader's items that are identical to that one item, i.e. - // if it is a stackable item like arrows, update the price for all stacks. If it is not stackable, then - // update the prices for all items that have the same number of charges. - // - uint32 id_of_item_to_update = 0; - int32 charges_on_item_to_update = 0; - uint32 old_price = 0; - - for (int i = 0; i < item_limit; i++) { - if ((trader_items.at(i).item_id > 0) && (trader_items.at(i).item_sn.compare(tpus->serial_number) == 0)) { - // We found the item that the Trader wants to change the price of (or add back up for sale). - // - id_of_item_to_update = trader_items.at(i).item_id; - charges_on_item_to_update = trader_items.at(i).item_charges; - old_price = trader_items.at(i).item_cost; - - LogTrading( - "ItemID is [{}] Charges is [{}]", - trader_items.at(i).item_id, - trader_items.at(i).item_charges - ); - break; - } - } - - if (id_of_item_to_update == 0) { - // If the item is not currently in the trader table for this Trader, then they must have removed it from sale while - // still in Trader mode. Check if the item is in their Trader Satchels, and if so, put it back up. - // Quick Sanity check. If the item is not currently up for sale, and the new price is zero, just ack the packet - // and do nothing. - if (tpus->NewPrice == 0) { - tpus->SubAction = BazaarPriceChange_RemoveItem; - QueuePacket(app); + if (new_price == 0) { + auto result = TraderRepository::DeleteWhere(database, fmt::format("`item_unique_id` = '{}'", in->item_unique_id)); + if (!result) { + LogError("Trader {} attempt to remove item_unique_id {} failed", CharacterID(), in->item_unique_id); return; } - LogTrading("Unable to find item to update price for. Rechecking trader satchels"); - - // Find what is in their Trader Satchels - auto newgis = GetTraderItems(); - uint32 id_of_item_to_add = 0; - int32 charges_on_item_to_add = 0; - - for (int i = 0; i < GetInv().GetLookup()->InventoryTypeSize.Bazaar; i++) { - if (newgis->items[i] > 0 && newgis->serial_number[i].compare(tpus->serial_number) == 0) { - id_of_item_to_add = newgis->items[i]; - charges_on_item_to_add = newgis->charges[i]; - - LogTrading( - "Found new Item to Add, ItemID is [{}] Charges is [{}]", - newgis->items[i], - newgis->charges[i] - ); - break; - } - } - - const EQ::ItemData *item = nullptr; - if (id_of_item_to_add) { - item = database.GetItem(id_of_item_to_add); - } - - if (!id_of_item_to_add || !item) { - tpus->SubAction = BazaarPriceChange_Fail; - QueuePacket(app); - TraderEndTrader(); - safe_delete(newgis); - - LogTrading("Item not found in Trader Satchels either"); - return; - } - - // It is a limitation of the client that if you have multiple of the same item, but with different charges, - // although you can set different prices for them before entering Trader mode. If you Remove them and then - // add them back whilst still in Trader mode, they all go up for the same price. We check for this situation - // and give the Trader a warning message. - // - if (!item->Stackable) { - bool same_item_with_differing_charges = false; - - for (int i = 0; i < GetInv().GetLookup()->InventoryTypeSize.Bazaar; i++) { - if ((newgis->items[i] == id_of_item_to_add) && (newgis->charges[i] != charges_on_item_to_add)) { - same_item_with_differing_charges = true; - break; - } - } - - if (same_item_with_differing_charges) { - Message( - Chat::Red, - "Warning: You have more than one %s with different charges. They have all been added for sale " - "at the same price.", - item->Name - ); - } - } - - // Now put all Items with a matching ItemID up for trade. - // - for (int i = 0; i < GetInv().GetLookup()->InventoryTypeSize.Bazaar; i++) { - if (newgis->items[i] == id_of_item_to_add) { - auto item_detail = FindTraderItemBySerialNumber(newgis->serial_number[i]); - - TraderRepository::Trader trader_item{}; - trader_item.id = 0; - trader_item.char_entity_id = GetID(); - trader_item.char_id = CharacterID(); - trader_item.char_zone_id = GetZoneID(); - trader_item.char_zone_instance_id = GetInstanceID(); - trader_item.item_charges = newgis->charges[i]; - trader_item.item_cost = tpus->NewPrice; - trader_item.item_id = newgis->items[i]; - trader_item.item_sn = newgis->serial_number[i]; - trader_item.listing_date = time(nullptr); - if (item_detail->IsAugmented()) { - auto augs = item_detail->GetAugmentIDs(); - trader_item.aug_slot_1 = augs.at(0); - trader_item.aug_slot_2 = augs.at(1); - trader_item.aug_slot_3 = augs.at(2); - trader_item.aug_slot_4 = augs.at(3); - trader_item.aug_slot_5 = augs.at(4); - trader_item.aug_slot_6 = augs.at(5); - } - trader_item.slot_id = i; - - TraderRepository::ReplaceOne(database, trader_item); - - trader_items.push_back(trader_item); - - LogTrading( - "Adding new item for [{}] ItemID [{}] SerialNumber [{}] Charges [{}] " - "Price: [{}] Slot [{}]", - GetName(), - newgis->items[i], - newgis->serial_number[i], - newgis->charges[i], - tpus->NewPrice, - i - ); - } - } - - // If we have a customer currently browsing, update them with the new items. - // - if (GetCustomerID()) { - UpdateTraderCustomerItemsAdded( - GetCustomerID(), - trader_items, - id_of_item_to_add, - GetInv().GetLookup()->InventoryTypeSize.Bazaar - ); - } - - safe_delete(newgis); - - // Acknowledge to the client. - tpus->SubAction = BazaarPriceChange_AddItem; + in->sub_action = BazaarPriceChange_RemoveItem; QueuePacket(app); + auto customer = entity_list.GetClientByID(GetCustomerID()); + if (customer && inst_copy) { + auto list = customer->GetTraderMerchantList(); + auto [slot_id, merchant_data] = customer->GetDataFromMerchantListByItemUniqueId(in->item_unique_id); + + auto client_packet = + new EQApplicationPacket(OP_ShopDelItem, static_cast(sizeof(Merchant_DelItem_Struct))); + auto client_data = reinterpret_cast(client_packet->pBuffer); + + client_data->npcid = GetID(); + client_data->playerid = customer->GetID(); + client_data->itemslot = slot_id; + + customer->QueuePacket(client_packet); + safe_delete(client_packet); + + list->erase(slot_id); + + customer->Message( + Chat::Red, + fmt::format( + "Trader {} removed item {} from the bazaar. Item no longer available.", + GetCleanName(), + inst_copy->GetItem()->Name + ).c_str() + ); + LogTrading("Trader removed item from trader list with item_unique_id {}", in->item_unique_id); + } + return; } - // This is a safeguard against a Trader increasing the price of an item while a customer is browsing and - // unwittingly buying it at a higher price than they were expecting to. - // - if ((old_price != 0) && (tpus->NewPrice > old_price) && GetCustomerID()) { - tpus->SubAction = BazaarPriceChange_Fail; - QueuePacket(app); - TraderEndTrader(); - Message( - Chat::Red, - "You must remove the item from sale before you can increase the price while a customer is browsing." - ); - Message(Chat::Red, "Click 'Begin Trader' to restart Trader mode with the increased price for this item."); - return; - } - - // Send Acknowledgement back to the client. - if (old_price == 0) { - tpus->SubAction = BazaarPriceChange_AddItem; - } - else if (tpus->NewPrice != 0) { - tpus->SubAction = BazaarPriceChange_UpdatePrice; - } - else { - tpus->SubAction = BazaarPriceChange_RemoveItem; + auto result = TraderRepository::UpdatePrice(database, in->item_unique_id, new_price); + if (!result && inst_copy) { + // Item does not exist so it must be re-created, + TraderRepository::Trader trader_item{}; + + trader_item.id = 0; + trader_item.char_entity_id = GetID(); + trader_item.character_id = CharacterID(); + trader_item.char_zone_id = GetZoneID(); + trader_item.char_zone_instance_id = GetInstanceID(); + trader_item.item_charges = inst_copy->GetCharges(); + trader_item.item_cost = new_price; + trader_item.item_id = inst_copy->GetID(); + trader_item.item_unique_id = in->item_unique_id; + trader_item.slot_id = 0; + trader_item.listing_date = time(nullptr); + if (inst_copy->IsAugmented()) { + auto augs = inst_copy->GetAugmentIDs(); + trader_item.augment_one = augs.at(0); + trader_item.augment_two = augs.at(1); + trader_item.augment_three = augs.at(2); + trader_item.augment_four = augs.at(3); + trader_item.augment_five = augs.at(4); + trader_item.augment_six = augs.at(5); + } + + TraderRepository::ReplaceOne(database, trader_item); } + in->sub_action = BazaarPriceChange_UpdatePrice; QueuePacket(app); - if (old_price == tpus->NewPrice) { - LogTrading("The new price is the same as the old one"); - return; - } - // Update the price for all items we have for sale that have this ItemID and number of charges, or remove - // them from the trader table if the new price is zero. - // - database.UpdateTraderItemPrice(CharacterID(), id_of_item_to_update, charges_on_item_to_update, tpus->NewPrice); + auto customer = entity_list.GetClientByID(GetCustomerID()); + if (customer && inst_copy) { + auto [slot_id, merchant_data] = customer->GetDataFromMerchantListByItemUniqueId(inst_copy->GetUniqueID()); + auto [merchant_quantity, item_unique_id] = merchant_data; - // If a customer is browsing our goods, send them the updated prices / remove the items from the Merchant window - if (GetCustomerID()) { - UpdateTraderCustomerPriceChanged( - GetCustomerID(), - trader_items, - id_of_item_to_update, - charges_on_item_to_update, - tpus->NewPrice, - item_limit + inst_copy->SetMerchantCount(merchant_quantity); + inst_copy->SetMerchantSlot(slot_id); + inst_copy->SetPrice(new_price); + customer->SendItemPacket(slot_id, inst_copy.get(), ItemPacketMerchant); + + customer->Message( + Chat::Red, + fmt::format("Trader {} updated the price of item {}", GetCleanName(), inst_copy->GetItem()->Name).c_str() ); } } @@ -2785,33 +2634,12 @@ void Client::SendBulkBazaarTraders() void Client::DoBazaarInspect(BazaarInspect_Struct &in) { - if (RuleB(Bazaar, UseAlternateBazaarSearch)) { - if (in.trader_id >= TraderRepository::TRADER_CONVERT_ID) { - auto sn = std::string(in.serial_number); - auto trader = TraderRepository::GetTraderByInstanceAndSerialnumber( - database, - in.trader_id - TraderRepository::TRADER_CONVERT_ID, - sn - ); - - if (!trader.trader_id) { - LogTrading("Unable to convert trader id for {} and serial number {}. Trader Buy aborted.", - in.trader_id - TraderRepository::TRADER_CONVERT_ID, - in.serial_number - ); - return; - } - - in.trader_id = trader.trader_id; - } - } - auto items = TraderRepository::GetWhere( - database, fmt::format("`char_id` = '{}' AND `item_sn` = '{}'", in.trader_id, in.serial_number) + database, fmt::format("`item_unique_id` = '{}'", in.item_unique_id) ); if (items.empty()) { - LogInfo("Failed to find item with serial number [{}]", in.serial_number); + LogInfo("Failed to find item with serial number [{}]", in.item_unique_id); return; } @@ -2821,12 +2649,12 @@ void Client::DoBazaarInspect(BazaarInspect_Struct &in) database.CreateItem( item.item_id, item.item_charges, - item.aug_slot_1, - item.aug_slot_2, - item.aug_slot_3, - item.aug_slot_4, - item.aug_slot_5, - item.aug_slot_6 + item.augment_one, + item.augment_two, + item.augment_three, + item.augment_four, + item.augment_five, + item.augment_six ) ); @@ -2881,13 +2709,13 @@ std::string Client::DetermineMoneyString(uint64 cp) void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicationPacket *app) { auto in = (TraderBuy_Struct *) app->pBuffer; - auto sn = std::string(tbs->serial_number); + auto sn = std::string(tbs->item_unique_id); auto trader_item = TraderRepository::GetItemBySerialNumber(database, sn, tbs->trader_id); if (!trader_item.id || GetTraderTransactionDate() < trader_item.listing_date) { LogTrading("Attempt to purchase an item outside of the Bazaar trader_id [{}] item serial_number " "[{}] The Traders data was outdated.", tbs->trader_id, - tbs->serial_number + tbs->item_unique_id ); in->method = BazaarByParcel; in->sub_action = DataOutDated; @@ -2899,7 +2727,7 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati LogTrading("Attempt to purchase an item outside of the Bazaar trader_id [{}] item serial_number " "[{}] The item is already within an active transaction.", tbs->trader_id, - tbs->serial_number + tbs->item_unique_id ); in->method = BazaarByParcel; in->sub_action = DataOutDated; @@ -2913,19 +2741,19 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati database.CreateItem( trader_item.item_id, trader_item.item_charges, - trader_item.aug_slot_1, - trader_item.aug_slot_2, - trader_item.aug_slot_3, - trader_item.aug_slot_4, - trader_item.aug_slot_5, - trader_item.aug_slot_6 + trader_item.augment_one, + trader_item.augment_two, + trader_item.augment_three, + trader_item.augment_four, + trader_item.augment_five, + trader_item.augment_six ) ); if (!buy_item) { LogTrading("Unable to find item id [{}] item_sn [{}] on trader", trader_item.item_id, - trader_item.item_sn + trader_item.item_unique_id ); in->method = BazaarByParcel; in->sub_action = Failed; @@ -3087,13 +2915,6 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati if (trader_item.item_charges <= static_cast(tbs->quantity) || !buy_item->IsStackable()) { TraderRepository::DeleteOne(database, trader_item.id); - } else { - TraderRepository::UpdateQuantity( - database, - trader_item.char_id, - trader_item.item_sn, - trader_item.item_charges - tbs->quantity - ); } SendParcelDeliveryToWorld(ps); @@ -3878,3 +3699,43 @@ void Client::CancelTraderTradeWindow() auto end_session = new EQApplicationPacket(OP_ShopEnd); FastQueuePacket(&end_session); } + +void Client::AddDataToMerchantList(int16 slot_id, int32 quantity, const std::string &item_unique_id) +{ + auto list = GetTraderMerchantList(); + list->emplace(std::pair(slot_id, std::make_tuple(quantity, item_unique_id))); +} + +std::tuple Client::GetDataFromMerchantListByMerchantSlotId(int16 slot_id) +{ + auto list = GetTraderMerchantList(); + return list->contains(slot_id) ? list->at(slot_id) : std::make_tuple(INVALID_INDEX, "0000000000000000"); +} + +int16 Client::GetSlotFromMerchantListByItemUniqueId(const std::string &unique_id) +{ + auto list = GetTraderMerchantList(); + + for (auto [slot_id, merchant_data] : *list) { + auto [quantity, item_unique_id] = merchant_data; + if (item_unique_id == unique_id) { + return slot_id; + } + } + + return INVALID_INDEX; +} + +std::pair> Client::GetDataFromMerchantListByItemUniqueId(const std::string &unique_id) +{ + auto list = GetTraderMerchantList(); + + for (auto [slot_id, merchant_data] : *list) { + auto [quantity, item_unique_id] = merchant_data; + if (item_unique_id == unique_id) { + return { slot_id, merchant_data }; + } + } + + return std::make_pair(INVALID_INDEX, std::make_tuple(0, "0000000000000000")); +} diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 840979d8d..d17bfbfb7 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -3791,8 +3791,8 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) } } + auto sn = std::string(in->trader_buy_struct.item_unique_id); auto outapp = std::make_unique(OP_Trader, static_cast(sizeof(TraderBuy_Struct))); - auto sn = std::string(in->trader_buy_struct.serial_number); auto data = (TraderBuy_Struct *) outapp->pBuffer; memcpy(data, &in->trader_buy_struct, sizeof(TraderBuy_Struct)); @@ -3826,7 +3826,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) RecordPlayerEventLogWithClient(trader_pc, PlayerEvent::TRADER_SELL, e); } - trader_pc->RemoveItemBySerialNumber(sn, in->trader_buy_struct.quantity); + trader_pc->RemoveItemByItemUniqueId(sn, in->trader_buy_struct.quantity); trader_pc->AddMoneyToPP(in->trader_buy_struct.price * in->trader_buy_struct.quantity, true); trader_pc->QueuePacket(outapp.get()); diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 4df6e956d..20b62386c 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -343,12 +343,12 @@ std::unique_ptr ZoneDatabase::LoadSingleTraderItem(uint32 char database.CreateItem( item_id, charges, - results.at(0).aug_slot_1, - results.at(0).aug_slot_2, - results.at(0).aug_slot_3, - results.at(0).aug_slot_4, - results.at(0).aug_slot_5, - results.at(0).aug_slot_6 + results.at(0).augment_one, + results.at(0).augment_two, + results.at(0).augment_three, + results.at(0).augment_four, + results.at(0).augment_five, + results.at(0).augment_six ) ); if (!inst) { From e57a789dce374d9a28944c0d30a9491eed6951e8 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Fri, 11 Apr 2025 23:25:52 -0300 Subject: [PATCH 14/48] Trader Direct purchase updated and tested --- zone/inventory.cpp | 90 +++++++++++++++++++++++++++++++++++++++++----- zone/trading.cpp | 32 +++++++++++------ 2 files changed, 103 insertions(+), 19 deletions(-) diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 340ede084..60ab0cd33 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -4673,19 +4673,93 @@ bool Client::HasItemOnCorpse(uint32 item_id) bool Client::PutItemInInventoryWithStacking(EQ::ItemInstance *inst) { - auto free_id = GetInv().FindFirstFreeSlotThatFitsItem(inst->GetItem()); - if (inst->IsStackable()) { - if (TryStacking(inst, ItemPacketTrade, true, false)) { + struct temp { + int16 slot_id; + int32 quantity; + }; + + std::vector queue; + auto free_id = GetInv().FindFirstFreeSlotThatFitsItem(inst->GetItem()); + auto quantity = inst->GetCharges(); // 2 + auto remaining_quantity = 0; + auto item_size = inst->GetItem()->Size; + + for (int i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) { + auto inv_inst = GetInv().GetItem(i); + if (!inv_inst) { + LogError("Found a slot {} in general inventory", i); + PutItemInInventory(i, *inst, true); return true; } - } - if (free_id != INVALID_INDEX) { - if (PutItemInInventory(free_id, *inst, true)) { - return true; + + int16 base_slot_id = EQ::InventoryProfile::CalcSlotId(i, EQ::invbag::SLOT_BEGIN); + uint8 bag_size = inv_inst->GetItem()->BagSlots; + + for (uint8 bag_slot = EQ::invbag::SLOT_BEGIN; bag_slot < bag_size; bag_slot++) { + auto bag_inst = GetInv().GetItem(base_slot_id + bag_slot); + if (!bag_inst && inv_inst->GetItem()->BagSize >= inst->GetItem()->Size) { + LogError("Found a parent {} base_slot_id {} bag_slot {} in bag", i, base_slot_id, bag_slot); + PutItemInInventory(base_slot_id + bag_slot, *inst, true); + return true; + } + + if (bag_inst && bag_inst->IsStackable() && bag_inst->GetID() == inst->GetID()) { + auto stack_size = bag_inst->GetItem()->StackSize; // 100 + auto bag_inst_quantity = bag_inst->GetCharges(); + int16 temp_slot = base_slot_id + bag_slot; + if (stack_size - bag_inst_quantity >= quantity) { + temp tmp = { temp_slot, quantity }; + queue.push_back(tmp); + quantity = 0; + LogError( + "Found an item parent {} base_slot_id {} bag_slot {} in bag with ENOUGH space", + i, + base_slot_id, + bag_slot + ); + break; + } + + if (stack_size - bag_inst_quantity > 0) { + temp tmp = { temp_slot, stack_size - bag_inst_quantity }; + queue.push_back(tmp); + quantity -= stack_size - bag_inst_quantity; + LogError( + "Found an item parent {} base_slot_id {} bag_slot {} in bag with SOME space", + i, + base_slot_id, + bag_slot + ); + } + } } } + + if (!queue.empty()) { + database.TransactionBegin(); + for (auto const &i: queue) { + auto bag_inst = GetInv().GetItem(i.slot_id); + if (!bag_inst) { + LogError("Client inventory error occurred. Character ID {} Slot_ID {}", CharacterID(), i.slot_id); + continue; + } + bag_inst->SetCharges(i.quantity + bag_inst->GetCharges()); + PutItemInInventory(i.slot_id, *bag_inst, true); + LogError("Write out data. Item {} quantity {} slot {}", bag_inst->GetItem()->Name, i.quantity, i.slot_id); + } + + database.TransactionCommit(); + quantity = 0; + } + + if (quantity == 0) { + LogError("Quantity was zero. All items placed in inventory."); + return true; + } + + LogError("Could not find enough room"); return false; -}; +} bool Client::FindNumberOfFreeInventorySlotsWithSizeCheck(std::vector items) { diff --git a/zone/trading.cpp b/zone/trading.cpp index 2aecdaafa..a7f8d3706 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1386,7 +1386,9 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) auto data = reinterpret_cast(trader_packet->pBuffer); auto buy_inst = trader->FindTraderItemByUniqueID(in->item_unique_id); - if (!buy_inst) { + std::unique_ptr inst_copy(buy_inst ? buy_inst->Clone() : nullptr); + + if (!buy_inst || !inst_copy) { LogTrading("Unable to find item id [{}] item_sn [{}] on trader", in->item_id, in->item_unique_id); Message(Chat::Red, "The trader no longer has the item for sale. Please refresh the merchant window."); TradeRequestFailed(app); @@ -1394,6 +1396,11 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) } uint32 quantity = in->quantity; + inst_copy->SetCharges(quantity); + if (inst_copy->IsStackable()) { + inst_copy->CreateUniqueID(); + } + LogTrading( "Name: [{}] IsStackable: [{}] Requested Quantity: [{}]", buy_inst->GetItem()->Name, @@ -1458,10 +1465,6 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) return; } - if (quantity != 1) { - buy_inst->SetCharges(quantity); - } - if (!trader->RemoveItemByItemUniqueId(buy_inst->GetUniqueID(), quantity)) { Message(Chat::Red, "The Trader no longer has the item. Please refresh the merchant window."); TradeRequestFailed(app); @@ -1470,7 +1473,13 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) trader->AddMoneyToPP(copper, silver, gold, platinum, true); - if (!AutoPutLootInInventory(*buy_inst, false, true)) { + // if (buy_inst->IsStackable() && !TryStacking(buy_inst, ItemPacketTrade, false, false)) { + // MessageString(Chat::Red, HOW_CAN_YOU_BUY_MORE, trader->GetCleanName()); + // TradeRequestFailed(app); + // return; + // } + + if (!PutItemInInventoryWithStacking(inst_copy.get())) { MessageString(Chat::Red, HOW_CAN_YOU_BUY_MORE, trader->GetCleanName()); TradeRequestFailed(app); return; @@ -1490,13 +1499,14 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) trader->QueuePacket(trader_packet.get()); if (merchant_quantity > quantity) { - buy_inst->SetMerchantCount(merchant_quantity - quantity); - buy_inst->SetMerchantSlot(slot_id); - buy_inst->SetPrice(in->price); + std::unique_ptr vendor_inst(buy_inst ? buy_inst->Clone() : nullptr); + vendor_inst->SetMerchantCount(merchant_quantity - quantity); + vendor_inst->SetMerchantSlot(slot_id); + vendor_inst->SetPrice(in->price); auto list = GetTraderMerchantList(); - std::get<0>(list->at(slot_id)) -= quantity; - SendItemPacket(slot_id, buy_inst, ItemPacketMerchant); TraderRepository::UpdateQuantity(database, item_unique_id, merchant_quantity - quantity); + std::get<0>(list->at(slot_id)) -= quantity; + SendItemPacket(slot_id, vendor_inst.get(), ItemPacketMerchant); } else { auto client_packet = new EQApplicationPacket(OP_ShopDelItem, static_cast(sizeof(Merchant_DelItem_Struct))); From 32daa4834c39805314c3ca58931fdcf3dc2c5223 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sat, 12 Apr 2025 00:09:30 -0300 Subject: [PATCH 15/48] Trader Direct purchase updated and tested --- zone/inventory.cpp | 5 +---- zone/trading.cpp | 45 +++++++++++++++++---------------------------- 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 60ab0cd33..30d19b6f0 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -4679,10 +4679,7 @@ bool Client::PutItemInInventoryWithStacking(EQ::ItemInstance *inst) }; std::vector queue; - auto free_id = GetInv().FindFirstFreeSlotThatFitsItem(inst->GetItem()); - auto quantity = inst->GetCharges(); // 2 - auto remaining_quantity = 0; - auto item_size = inst->GetItem()->Size; + auto quantity = inst->GetCharges(); for (int i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) { auto inv_inst = GetInv().GetItem(i); diff --git a/zone/trading.cpp b/zone/trading.cpp index a7f8d3706..88fbfbd42 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1373,8 +1373,8 @@ static void BazaarAuditTrail(const char *seller, const char *buyer, const char * void Client::BuyTraderItem(const EQApplicationPacket *app) { - auto in = reinterpret_cast(app->pBuffer); - auto trader = entity_list.GetClientByID(in->trader_id); + auto in = reinterpret_cast(app->pBuffer); + auto trader = entity_list.GetClientByID(in->trader_id); if (!trader || !trader->IsTrader()) { Message(Chat::Red, "The trader could not be found."); @@ -1408,6 +1408,12 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) quantity ); + if (CheckLoreConflict(inst_copy->GetItem())) { + MessageString(Chat::Red, DUPLICATE_LORE); + TradeRequestFailed(app); + return; + } + if (in->price * quantity <= 0) { Message(Chat::Red, "Internal error. Aborting trade. Please report this to the ServerOP. Error code is 1"); trader->Message(Chat::Red, "Internal error. Aborting trade. Please report this to the ServerOP. Error code is 1"); @@ -1446,40 +1452,18 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) return; } - LogTrading("Customer Paid: [{}] in Copper", total_cost); - - uint32 platinum = total_cost / 1000; - total_cost -= platinum * 1000; - uint32 gold = total_cost / 100; - total_cost -= gold * 100; - uint32 silver = total_cost / 10; - total_cost -= silver * 10; - uint32 copper = total_cost; - - LogTrading("Trader Received: [{}] Platinum, [{}] Gold, [{}] Silver, [{}] Copper", platinum, gold, silver, copper); - ReturnTraderReq(app, quantity, buy_inst->GetID()); - - if (CheckLoreConflict(buy_inst->GetItem())) { - MessageString(Chat::Red, DUPLICATE_LORE); - TradeRequestFailed(app); - return; - } - if (!trader->RemoveItemByItemUniqueId(buy_inst->GetUniqueID(), quantity)) { + AddMoneyToPP(total_cost, true); Message(Chat::Red, "The Trader no longer has the item. Please refresh the merchant window."); TradeRequestFailed(app); return; } - trader->AddMoneyToPP(copper, silver, gold, platinum, true); - - // if (buy_inst->IsStackable() && !TryStacking(buy_inst, ItemPacketTrade, false, false)) { - // MessageString(Chat::Red, HOW_CAN_YOU_BUY_MORE, trader->GetCleanName()); - // TradeRequestFailed(app); - // return; - // } + trader->AddMoneyToPP(total_cost, true); if (!PutItemInInventoryWithStacking(inst_copy.get())) { + trader->TakeMoneyFromPP(total_cost, true); + trader->PutItemInInventoryWithStacking(buy_inst); MessageString(Chat::Red, HOW_CAN_YOU_BUY_MORE, trader->GetCleanName()); TradeRequestFailed(app); return; @@ -1498,6 +1482,11 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) strn0cpy(data->item_unique_id, buy_inst->GetUniqueID().data(), sizeof(data->item_unique_id)); trader->QueuePacket(trader_packet.get()); + QueuePacket(app); + + LogTrading("Customer Paid: [{}] in Copper", total_cost); + LogTrading("Trader Received: [{}] in Copper", total_cost); + if (merchant_quantity > quantity) { std::unique_ptr vendor_inst(buy_inst ? buy_inst->Clone() : nullptr); vendor_inst->SetMerchantCount(merchant_quantity - quantity); From 5de05763adcb8a0e5ffdc32c91e27816eb785653 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sat, 12 Apr 2025 15:49:51 -0300 Subject: [PATCH 16/48] Added checks for same item with price change. Client treats them all the same so all need to be updated. Also repaired max stacksize of 32767 on RoF2 --- common/patches/rof2.cpp | 18 +- common/patches/rof2_limits.h | 1 + common/repositories/trader_repository.h | 34 +- common/shareddb.cpp | 2 +- zone/client.h | 14 +- zone/client_packet.cpp | 2 +- zone/trading.cpp | 417 ++++++++++++++---------- 7 files changed, 279 insertions(+), 209 deletions(-) diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 5191fa692..407c021df 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -439,7 +439,7 @@ namespace RoF2 break; } default: { - LogTradingDetail("Unhandled action [{}]", sub_action); + //LogTradingDetail("Unhandled action [{}]", sub_action); dest->FastQueuePacket(&in); } } @@ -533,7 +533,7 @@ namespace RoF2 break; } default: { - LogTrading("(RoF2) Unhandled action [{}]", action); + //LogTradingDetail("(RoF2) Unhandled action [{}]", action); dest->FastQueuePacket(&in, ack_req); } } @@ -622,10 +622,10 @@ namespace RoF2 break; } default: { - LogTrading( - "(RoF2) Unhandled action [{}]", - in->action - ); + // LogTradingDetail( + // "(RoF2) Unhandled action [{}]", + // in->action + // ); dest->QueuePacket(inapp); } } @@ -4339,7 +4339,7 @@ namespace RoF2 break; } default: { - LogTrading("(RoF2) Unhandled action [{}]", action); + // LogTradingDetail("(RoF2) Unhandled action [{}]", action); EQApplicationPacket *in = *p; *p = nullptr; @@ -6224,7 +6224,7 @@ namespace RoF2 break; } default: { - LogTrading("(RoF2) Unhandled action [{}]", action); + //LogTradingDetail("(RoF2) Unhandled action [{}]", action); } } } @@ -6357,7 +6357,7 @@ namespace RoF2 break; } default: { - LogTrading("(RoF2) Unhandled action [{}]", action); + //LogTradingDetail("(RoF2) Unhandled action [{}]", action); } return; } diff --git a/common/patches/rof2_limits.h b/common/patches/rof2_limits.h index 668b0df74..3dc23f0ed 100644 --- a/common/patches/rof2_limits.h +++ b/common/patches/rof2_limits.h @@ -307,6 +307,7 @@ namespace RoF2 const size_t SAY_LINK_BODY_SIZE = 56; const uint32 MAX_GUILD_ID = 50000; const uint32 MAX_BAZAAR_TRADERS = 600; + const uint64 MAX_BAZAAR_TRANSACTION = 3276700000000; //3276700000000 } /*constants*/ diff --git a/common/repositories/trader_repository.h b/common/repositories/trader_repository.h index 88cc7910a..5d33a2f88 100644 --- a/common/repositories/trader_repository.h +++ b/common/repositories/trader_repository.h @@ -198,30 +198,40 @@ public: return UpdateOne(db, m); } - static int UpdatePrice(Database &db, const std::string &item_unique_id, uint32 price) + static std::vector UpdatePrice(Database &db, const std::string &item_unique_id, uint32 price) { - const auto trader_item = GetWhere( - db, - fmt::format("`item_unique_id` = '{}' ", item_unique_id) + std::vector all_entries{}; + + const auto query = fmt::format( + "UPDATE trader t1 SET t1.`item_cost` = '{}', t1.`listing_date` = FROM_UNIXTIME({}) WHERE t1.`item_id` = " + "(SELECT t2.`item_id` FROM trader t2 WHERE t2.`item_unique_id` = '{}')", + price, + time(nullptr), + item_unique_id ); - if (trader_item.empty() || trader_item.size() > 1) { - return 0; + auto results = db.QueryDatabase(query); + if (results.RowsAffected() == 0) { + return all_entries; } - auto m = trader_item[0]; - m.item_cost = price; - m.listing_date = time(nullptr); + all_entries = GetWhere( + db, + fmt::format( + "`item_id` = (SELECT t1.`item_id` FROM trader t1 WHERE t1.`item_unique_id` = '{}');", + item_unique_id + ) + ); - return ReplaceOne(db, m); + return all_entries; } - static Trader GetItemBySerialNumber(Database &db, std::string &item_unique_id, uint32 trader_id) + static Trader GetItemByItemUniqueNumber(Database &db, std::string &item_unique_id) { Trader e{}; const auto trader_item = GetWhere( db, - fmt::format("`character_id` = '{}' AND `item_unique_id` = '{}' LIMIT 1", trader_id, item_unique_id) + fmt::format("`item_unique_id` = '{}' LIMIT 1", item_unique_id) ); if (trader_item.empty()) { diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 0f1622000..95ff53cdc 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -740,7 +740,7 @@ bool SharedDatabase::GetInventory(Client *c) inst->SetColor(color); } - if (charges == std::numeric_limits::max()) { + if (charges > std::numeric_limits::max()) { inst->SetCharges(-1); } else if (charges == 0 && inst->IsStackable()) { // Stackable items need a minimum charge of 1 remain moveable. diff --git a/zone/client.h b/zone/client.h index 5cc62aab4..b17922b06 100644 --- a/zone/client.h +++ b/zone/client.h @@ -377,12 +377,13 @@ public: EQ::ItemInstance* FindTraderItemBySerialNumber(std::string &serial_number); EQ::ItemInstance* FindTraderItemByUniqueID(std::string &unique_id); EQ::ItemInstance* FindTraderItemByUniqueID(const char* unique_id); + std::vector FindTraderItemsByUniqueID(const char* unique_id); void FindAndNukeTraderItem(std::string &item_unique_id, int16 quantity, Client* customer, uint16 trader_slot); void NukeTraderItem(uint16 slot, int16 charges, int16 quantity, Client* customer, uint16 trader_slot, const std::string &serial_number, int32 item_id = 0); void ReturnTraderReq(const EQApplicationPacket* app,int16 traderitemcharges, uint32 itemid = 0); void TradeRequestFailed(const EQApplicationPacket* app); void BuyTraderItem(const EQApplicationPacket* app); - void BuyTraderItemOutsideBazaar(TraderBuy_Struct* tbs, const EQApplicationPacket* app); + void BuyTraderItemFromBazaarWindow(const EQApplicationPacket* app); void FinishTrade( Mob *with, bool finalizer = false, @@ -422,11 +423,12 @@ public: uint32 GetCustomerID() { return customer_id; } void SetCustomerID(uint32 id) { customer_id = id; } void ClearTraderMerchantList() { m_trader_merchant_list.clear(); } - void AddDataToMerchantList(int16 slot_id, int32 quantity, const std::string &item_unique_id); - std::tuple GetDataFromMerchantListByMerchantSlotId(int16 slot_id); + void AddDataToMerchantList(int16 slot_id, uint32 item_id, int32 quantity, const std::string &item_unique_id); + int16 GetNextFreeSlotFromMerchantList(); + std::tuple GetDataFromMerchantListByMerchantSlotId(int16 slot_id); int16 GetSlotFromMerchantListByItemUniqueId(const std::string &unique_id); - std::pair> GetDataFromMerchantListByItemUniqueId(const std::string &unique_id); - std::map>* GetTraderMerchantList() { return &m_trader_merchant_list; } + std::pair> GetDataFromMerchantListByItemUniqueId(const std::string &unique_id); + std::map>* GetTraderMerchantList() { return &m_trader_merchant_list; } void SetBuyerID(uint32 id) { m_buyer_id = id; } uint32 GetBuyerID() { return m_buyer_id; } @@ -2112,7 +2114,7 @@ private: uint8 mercSlot; // selected merc slot time_t m_trader_transaction_date; uint32 m_trader_count{}; - std::map> m_trader_merchant_list{}; + std::map> m_trader_merchant_list{}; // itemid, qty, item_unique_id uint32 m_buyer_id; uint32 m_barter_time; int32 m_parcel_platinum; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 00082ce29..ab3126c70 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -15510,7 +15510,7 @@ void Client::Handle_OP_TraderBuy(const EQApplicationPacket *app) in->quantity, in->item_unique_id ); - BuyTraderItemOutsideBazaar(in, app); + BuyTraderItemFromBazaarWindow(app); break; } case BazaarByDirectToInventory: { diff --git a/zone/trading.cpp b/zone/trading.cpp index 88fbfbd42..3297a5bf8 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1011,7 +1011,7 @@ void Client::BulkSendTraderInventory(uint32 character_id) inst->SetMerchantCount(inst->IsStackable() ? inst->GetCharges() : 1); inst->SetMerchantSlot(i + 1); inst->SetPrice(trader_items.at(i).item_cost); - AddDataToMerchantList(i + 1, inst->GetMerchantCount(), inst->GetUniqueID()); + AddDataToMerchantList(i + 1, inst->GetID(), inst->GetMerchantCount(), inst->GetUniqueID()); SendItemPacket(i + 1, inst.get(), ItemPacketMerchant); } else @@ -1119,6 +1119,32 @@ EQ::ItemInstance *Client::FindTraderItemByUniqueID(const char* unique_id) return nullptr; } +std::vector Client::FindTraderItemsByUniqueID(const char* unique_id) +{ + std::vector items{}; + EQ::ItemInstance *item = nullptr; + int16 slot_id = 0; + + for (int16 i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) { + item = GetInv().GetItem(i); + if (item && item->GetItem()->BagType == EQ::item::BagTypeTradersSatchel) { + for (int16 x = EQ::invbag::SLOT_BEGIN; x <= EQ::invbag::SLOT_END; x++) { + // we already have the parent bag and a contents iterator..why not just iterate the bag!?? + slot_id = EQ::InventoryProfile::CalcSlotId(i, x); + item = GetInv().GetItem(slot_id); + if (item) { + if (item->GetUniqueID().compare(unique_id) == 0) { + items.push_back(item); + } + } + } + } + } + + LogTrading("Couldn't find item! item_unique_id was [{}]", unique_id); + return items; +} + GetItems2_Struct *Client::GetTraderItems() { const EQ::ItemInstance *item = nullptr; @@ -1469,8 +1495,8 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) return; } - auto [slot_id, merchant_data] = GetDataFromMerchantListByItemUniqueId(buy_inst->GetUniqueID()); - auto [merchant_quantity, item_unique_id] = merchant_data; + auto [slot_id, merchant_data] = GetDataFromMerchantListByItemUniqueId(buy_inst->GetUniqueID()); + auto [item_id, merchant_quantity, item_unique_id] = merchant_data; data->action = BazaarBuyItem; data->price = in->price; @@ -1494,7 +1520,7 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) vendor_inst->SetPrice(in->price); auto list = GetTraderMerchantList(); TraderRepository::UpdateQuantity(database, item_unique_id, merchant_quantity - quantity); - std::get<0>(list->at(slot_id)) -= quantity; + std::get<1>(list->at(slot_id)) -= quantity; SendItemPacket(slot_id, vendor_inst.get(), ItemPacketMerchant); } else { @@ -2459,8 +2485,8 @@ void Client::TraderUpdateItem(const EQApplicationPacket *app) auto in = reinterpret_cast(app->pBuffer); uint32 new_price = in->new_price; auto inst = FindTraderItemByUniqueID(in->item_unique_id); - std::unique_ptr inst_copy(inst ? inst->Clone() : nullptr); + auto customer = entity_list.GetClientByID(GetCustomerID()); if (new_price == 0) { auto result = TraderRepository::DeleteWhere(database, fmt::format("`item_unique_id` = '{}'", in->item_unique_id)); if (!result) { @@ -2471,30 +2497,31 @@ void Client::TraderUpdateItem(const EQApplicationPacket *app) in->sub_action = BazaarPriceChange_RemoveItem; QueuePacket(app); - auto customer = entity_list.GetClientByID(GetCustomerID()); - if (customer && inst_copy) { - auto list = customer->GetTraderMerchantList(); - auto [slot_id, merchant_data] = customer->GetDataFromMerchantListByItemUniqueId(in->item_unique_id); - + if (customer && inst) { + auto list = customer->GetTraderMerchantList(); auto client_packet = new EQApplicationPacket(OP_ShopDelItem, static_cast(sizeof(Merchant_DelItem_Struct))); - auto client_data = reinterpret_cast(client_packet->pBuffer); + auto client_data = reinterpret_cast(client_packet->pBuffer); client_data->npcid = GetID(); client_data->playerid = customer->GetID(); - client_data->itemslot = slot_id; - customer->QueuePacket(client_packet); + for (auto const [slot_id, merchant_data]: *list) { + auto const [item_id, merchant_quantity, item_unique_id] = merchant_data; + if (item_id == inst->GetID()) { + client_data->itemslot = slot_id; + customer->QueuePacket(client_packet); + AddDataToMerchantList(slot_id, 0, 0, "0000000000000000"); + } + } safe_delete(client_packet); - list->erase(slot_id); - customer->Message( Chat::Red, fmt::format( "Trader {} removed item {} from the bazaar. Item no longer available.", GetCleanName(), - inst_copy->GetItem()->Name + inst->GetItem()->Name ).c_str() ); LogTrading("Trader removed item from trader list with item_unique_id {}", in->item_unique_id); @@ -2503,53 +2530,73 @@ void Client::TraderUpdateItem(const EQApplicationPacket *app) return; } - auto result = TraderRepository::UpdatePrice(database, in->item_unique_id, new_price); - if (!result && inst_copy) { - // Item does not exist so it must be re-created, - TraderRepository::Trader trader_item{}; + auto result = TraderRepository::UpdatePrice(database, in->item_unique_id, new_price); + if (result.empty()) { + auto trader_items = FindTraderItemsByUniqueID(in->item_unique_id); + std::vector queue{}; + for (auto const& i : trader_items) { + TraderRepository::Trader e{}; - trader_item.id = 0; - trader_item.char_entity_id = GetID(); - trader_item.character_id = CharacterID(); - trader_item.char_zone_id = GetZoneID(); - trader_item.char_zone_instance_id = GetInstanceID(); - trader_item.item_charges = inst_copy->GetCharges(); - trader_item.item_cost = new_price; - trader_item.item_id = inst_copy->GetID(); - trader_item.item_unique_id = in->item_unique_id; - trader_item.slot_id = 0; - trader_item.listing_date = time(nullptr); - if (inst_copy->IsAugmented()) { - auto augs = inst_copy->GetAugmentIDs(); - trader_item.augment_one = augs.at(0); - trader_item.augment_two = augs.at(1); - trader_item.augment_three = augs.at(2); - trader_item.augment_four = augs.at(3); - trader_item.augment_five = augs.at(4); - trader_item.augment_six = augs.at(5); + e.id = 0; + e.char_entity_id = GetID(); + e.character_id = CharacterID(); + e.char_zone_id = GetZoneID(); + e.char_zone_instance_id = GetInstanceID(); + e.item_charges = i->GetCharges(); + e.item_cost = new_price; + e.item_id = i->GetID(); + e.item_unique_id = i->GetUniqueID(); + e.slot_id = 0; + e.listing_date = time(nullptr); + if (i->IsAugmented()) { + auto augs = i->GetAugmentIDs(); + e.augment_one = augs.at(0); + e.augment_two = augs.at(1); + e.augment_three = augs.at(2); + e.augment_four = augs.at(3); + e.augment_five = augs.at(4); + e.augment_six = augs.at(5); + } + + queue.push_back(e); + if (customer) { + int16 next_slot_id = GetNextFreeSlotFromMerchantList(); + if (next_slot_id != INVALID_INDEX) { + std::unique_ptr vendor_inst_copy(i ? i->Clone() : nullptr); + vendor_inst_copy->SetUniqueID(i->GetUniqueID()); + vendor_inst_copy->SetMerchantCount(i->IsStackable() ? i->GetCharges() : 1); + vendor_inst_copy->SetMerchantSlot(next_slot_id ); + vendor_inst_copy->SetPrice(new_price); + AddDataToMerchantList(next_slot_id, i->GetID(), i->GetMerchantCount(), i->GetUniqueID()); + customer->SendItemPacket(next_slot_id, vendor_inst_copy.get(), ItemPacketMerchant); + } + } } - TraderRepository::ReplaceOne(database, trader_item); + if (!queue.empty()) { + TraderRepository::ReplaceMany(database, queue); + } + } + else { + for (auto const i : result) { + auto [slot_id, merchant_data] = customer->GetDataFromMerchantListByItemUniqueId(i.item_unique_id); + auto [item_id, merchant_quantity, item_unique_id] = merchant_data; + std::unique_ptr vendor_inst_copy(inst ? inst->Clone() : nullptr); + vendor_inst_copy->SetUniqueID(i.item_unique_id); + vendor_inst_copy->SetMerchantCount(i.item_charges); + vendor_inst_copy->SetMerchantSlot(slot_id); + vendor_inst_copy->SetPrice(new_price); + customer->SendItemPacket(slot_id, vendor_inst_copy.get(), ItemPacketMerchant); + } + + customer->Message( + Chat::Red, + fmt::format("Trader {} updated the price of item {}", GetCleanName(), inst->GetItem()->Name).c_str() + ); } in->sub_action = BazaarPriceChange_UpdatePrice; QueuePacket(app); - - auto customer = entity_list.GetClientByID(GetCustomerID()); - if (customer && inst_copy) { - auto [slot_id, merchant_data] = customer->GetDataFromMerchantListByItemUniqueId(inst_copy->GetUniqueID()); - auto [merchant_quantity, item_unique_id] = merchant_data; - - inst_copy->SetMerchantCount(merchant_quantity); - inst_copy->SetMerchantSlot(slot_id); - inst_copy->SetPrice(new_price); - customer->SendItemPacket(slot_id, inst_copy.get(), ItemPacketMerchant); - - customer->Message( - Chat::Red, - fmt::format("Trader {} updated the price of item {}", GetCleanName(), inst_copy->GetItem()->Name).c_str() - ); - } } void Client::SendBazaarDone(uint32 trader_id) @@ -2705,16 +2752,53 @@ std::string Client::DetermineMoneyString(uint64 cp) return fmt::format("{}", money); } -void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicationPacket *app) +void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) { - auto in = (TraderBuy_Struct *) app->pBuffer; - auto sn = std::string(tbs->item_unique_id); - auto trader_item = TraderRepository::GetItemBySerialNumber(database, sn, tbs->trader_id); + auto in = reinterpret_cast(app->pBuffer); + auto item_unique_id = std::string(in->item_unique_id); + auto trader_item = TraderRepository::GetItemByItemUniqueNumber(database, item_unique_id); + + LogTradingDetail( + "Packet details: \n" + "Action :{}\n" + "Method :{}\n" + "SubAction :{}\n" + "Unknown_012 :{}\n" + "Trader ID :{}\n" + "Buyer Name :{}\n" + "Seller Name :{}\n" + "Unknown_148 :{}\n" + "Item Name :{}\n" + "Item Unique ID :{}\n" + "Unknown_261 :{}\n" + "Item ID :{}\n" + "Price :{}\n" + "Already Sold :{}\n" + "Unknown_276 :{}\n" + "Quantity :{}\n", + in->action, + in->method, + in->sub_action, + in->unknown_012, + in->trader_id, + in->buyer_name, + in->seller_name, + in->unknown_148, + in->item_name, + in->item_unique_id, + in->unknown_261, + in->item_id, + in->price, + in->already_sold, + in->unknown_276, + in->quantity + ); + if (!trader_item.id || GetTraderTransactionDate() < trader_item.listing_date) { - LogTrading("Attempt to purchase an item outside of the Bazaar trader_id [{}] item serial_number " - "[{}] The Traders data was outdated.", - tbs->trader_id, - tbs->item_unique_id + LogTrading("Attempt to purchase an item outside of the Bazaar trader_id [{}] item unique_id " + "[{}] The Traders data was outdated.", + in->trader_id, + in->item_unique_id ); in->method = BazaarByParcel; in->sub_action = DataOutDated; @@ -2723,10 +2807,10 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati } if (trader_item.active_transaction) { - LogTrading("Attempt to purchase an item outside of the Bazaar trader_id [{}] item serial_number " - "[{}] The item is already within an active transaction.", - tbs->trader_id, - tbs->item_unique_id + LogTrading("Attempt to purchase an item outside of the Bazaar trader_id [{}] item serial_number " + "[{}] The item is already within an active transaction.", + in->trader_id, + in->item_unique_id ); in->method = BazaarByParcel; in->sub_action = DataOutDated; @@ -2736,38 +2820,13 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati TraderRepository::UpdateActiveTransaction(database, trader_item.id, true); - std::unique_ptr buy_item( - database.CreateItem( - trader_item.item_id, - trader_item.item_charges, - trader_item.augment_one, - trader_item.augment_two, - trader_item.augment_three, - trader_item.augment_four, - trader_item.augment_five, - trader_item.augment_six - ) - ); - - if (!buy_item) { - LogTrading("Unable to find item id [{}] item_sn [{}] on trader", - trader_item.item_id, - trader_item.item_unique_id - ); - in->method = BazaarByParcel; - in->sub_action = Failed; - TraderRepository::UpdateActiveTransaction(database, trader_item.id, false); - TradeRequestFailed(app); - return; - } - auto next_slot = FindNextFreeParcelSlot(CharacterID()); if (next_slot == INVALID_INDEX) { LogTrading( "{} attempted to purchase {} from the bazaar with parcel delivery. Unfortunately their parcel limit was reached. " "Purchase unsuccessful.", GetCleanName(), - buy_item->GetItem()->Name + in->item_name ); in->method = BazaarByParcel; in->sub_action = TooManyParcels; @@ -2777,36 +2836,18 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati } LogTrading( - "Name: [{}] IsStackable: [{}] Requested Quantity: [{}] Charges on Item [{}]", - buy_item->GetItem()->Name, - buy_item->IsStackable(), - tbs->quantity, - buy_item->GetCharges() + "Name: [{}] Requested Quantity: [{}] Charges on Item [{}]", + in->item_name, + in->quantity, + trader_item.item_charges ); - // Determine the actual quantity for the purchase - int32 charges = static_cast(tbs->quantity); - if (!buy_item->IsStackable()) { - if (buy_item->GetCharges() <= 0) { - charges = 1; - } - else { - charges = buy_item->GetCharges(); - } - } - - LogTrading( - "Actual quantity that will be traded is [{}] {}", - tbs->quantity, - buy_item->GetCharges() ? fmt::format("with {} charges", buy_item->GetCharges()) : "" - ); - - uint64 total_cost = static_cast(tbs->price) * static_cast(tbs->quantity); - if (total_cost > MAX_TRANSACTION_VALUE) { + uint64 total_cost = static_cast(in->price) * static_cast(in->quantity); + if (total_cost > RoF2::constants::MAX_BAZAAR_TRANSACTION) { Message( Chat::Red, "That would exceed the single transaction limit of %u platinum.", - MAX_TRANSACTION_VALUE / 1000 + RoF2::constants::MAX_BAZAAR_TRANSACTION / 1000 ); TraderRepository::UpdateActiveTransaction(database, trader_item.id, false); TradeRequestFailed(app); @@ -2815,16 +2856,6 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati uint64 fee = std::round(total_cost * RuleR(Bazaar, ParcelDeliveryCostMod)); if (!TakeMoneyFromPP(total_cost + fee, false)) { - RecordPlayerEventLog( - PlayerEvent::POSSIBLE_HACK, - PlayerEvent::PossibleHackEvent{ - .message = fmt::format( - "{} attempted to buy {} in bazaar but did not have enough money.", - GetCleanName(), - buy_item->GetItem()->Name - ) - } - ); in->method = BazaarByParcel; in->sub_action = InsufficientFunds; TraderRepository::UpdateActiveTransaction(database, trader_item.id, false); @@ -2837,19 +2868,19 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati if (buy_item && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_PURCHASE)) { auto e = PlayerEvent::TraderPurchaseEvent{ - .item_id = buy_item->GetID(), - .augment_1_id = buy_item->GetAugmentItemID(0), - .augment_2_id = buy_item->GetAugmentItemID(1), - .augment_3_id = buy_item->GetAugmentItemID(2), - .augment_4_id = buy_item->GetAugmentItemID(3), - .augment_5_id = buy_item->GetAugmentItemID(4), - .augment_6_id = buy_item->GetAugmentItemID(5), - .item_name = buy_item->GetItem()->Name, - .trader_id = tbs->trader_id, - .trader_name = tbs->seller_name, - .price = tbs->price, - .quantity = tbs->quantity, - .charges = buy_item->IsStackable() ? 1 : charges, + .item_id = trader_item.item_id, + .augment_1_id = trader_item.augment_one, + .augment_2_id = trader_item.augment_two, + .augment_3_id = trader_item.augment_three, + .augment_4_id = trader_item.augment_four, + .augment_5_id = trader_item.augment_five, + .augment_6_id = trader_item.augment_six, + .item_name = in->item_name, + .trader_id = in->trader_id, + .trader_name = in->seller_name, + .price = in->price, + .quantity = in->quantity, + .charges = trader_item.item_charges, .total_cost = total_cost, .player_money_balance = GetCarriedMoney(), }; @@ -2858,17 +2889,17 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati } CharacterParcelsRepository::CharacterParcels parcel_out{}; - parcel_out.from_name = tbs->seller_name; + parcel_out.from_name = in->seller_name; parcel_out.note = "Delivered from a Bazaar Purchase"; parcel_out.sent_date = time(nullptr); - parcel_out.quantity = charges; - parcel_out.item_id = buy_item->GetItem()->ID; - parcel_out.aug_slot_1 = buy_item->GetAugmentItemID(0); - parcel_out.aug_slot_2 = buy_item->GetAugmentItemID(1); - parcel_out.aug_slot_3 = buy_item->GetAugmentItemID(2); - parcel_out.aug_slot_4 = buy_item->GetAugmentItemID(3); - parcel_out.aug_slot_5 = buy_item->GetAugmentItemID(4); - parcel_out.aug_slot_6 = buy_item->GetAugmentItemID(5); + parcel_out.quantity = in->quantity; + parcel_out.item_id = trader_item.item_id; + parcel_out.aug_slot_1 = trader_item.augment_one; + parcel_out.aug_slot_2 = trader_item.augment_two; + parcel_out.aug_slot_3 = trader_item.augment_three; + parcel_out.aug_slot_4 = trader_item.augment_four; + parcel_out.aug_slot_5 = trader_item.augment_five; + parcel_out.aug_slot_6 = trader_item.augment_six; parcel_out.char_id = CharacterID(); parcel_out.slot_id = next_slot; parcel_out.id = 0; @@ -2889,7 +2920,7 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati return; } - ReturnTraderReq(app, tbs->quantity, buy_item->GetID()); + ReturnTraderReq(app, in->quantity, trader_item.item_id); if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::PARCEL_SEND)) { PlayerEvent::ParcelSend e{}; e.from_player_name = parcel_out.from_name; @@ -2901,8 +2932,8 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati e.augment_4_id = parcel_out.aug_slot_4; e.augment_5_id = parcel_out.aug_slot_5; e.augment_6_id = parcel_out.aug_slot_6; - e.quantity = tbs->quantity; - e.charges = buy_item->IsStackable() ? 1 : charges; + e.quantity = in->quantity; + e.charges = trader_item.item_charges; e.sent_date = parcel_out.sent_date; RecordPlayerEventLog(PlayerEvent::PARCEL_SEND, e); @@ -2912,32 +2943,41 @@ void Client::BuyTraderItemOutsideBazaar(TraderBuy_Struct *tbs, const EQApplicati ps.item_slot = parcel_out.slot_id; strn0cpy(ps.send_to, GetCleanName(), sizeof(ps.send_to)); - if (trader_item.item_charges <= static_cast(tbs->quantity) || !buy_item->IsStackable()) { + if (trader_item.item_charges <= static_cast(in->quantity) || !trader_item.item_charges <= 0) { TraderRepository::DeleteOne(database, trader_item.id); } SendParcelDeliveryToWorld(ps); - if (RuleB(Bazaar, AuditTrail)) { - BazaarAuditTrail(tbs->seller_name, GetName(), buy_item->GetItem()->Name, tbs->quantity, tbs->price, 0); - } - - auto out_server = std::make_unique( - ServerOP_BazaarPurchase, static_cast(sizeof(BazaarPurchaseMessaging_Struct)) - ); + auto out_server = std::make_unique(ServerOP_BazaarPurchase, static_cast(sizeof(BazaarPurchaseMessaging_Struct))); auto out_data = (BazaarPurchaseMessaging_Struct *) out_server->pBuffer; - out_data->trader_buy_struct = *tbs; - out_data->buyer_id = CharacterID(); - out_data->item_aug_1 = buy_item->GetAugmentItemID(0); - out_data->item_aug_2 = buy_item->GetAugmentItemID(1); - out_data->item_aug_3 = buy_item->GetAugmentItemID(2); - out_data->item_aug_4 = buy_item->GetAugmentItemID(3); - out_data->item_aug_5 = buy_item->GetAugmentItemID(4); - out_data->item_aug_6 = buy_item->GetAugmentItemID(5); - out_data->item_quantity_available = trader_item.item_charges; - out_data->id = trader_item.id; + out_data->trader_buy_struct.action = in->action; + out_data->trader_buy_struct.method = in->method; + out_data->trader_buy_struct.already_sold = in->already_sold; + out_data->trader_buy_struct.item_id = in->item_id; + out_data->trader_buy_struct.price = in->price; + out_data->trader_buy_struct.quantity = in->quantity; + out_data->trader_buy_struct.sub_action = in->sub_action; + out_data->trader_buy_struct.trader_id = in->trader_id; + out_data->buyer_id = CharacterID(); + out_data->item_aug_1 = trader_item.augment_one; + out_data->item_aug_2 = trader_item.augment_two; + out_data->item_aug_3 = trader_item.augment_three; + out_data->item_aug_4 = trader_item.augment_four; + out_data->item_aug_5 = trader_item.augment_five; + out_data->item_aug_6 = trader_item.augment_six; + out_data->item_quantity_available = trader_item.item_charges; + out_data->id = trader_item.id; strn0cpy(out_data->trader_buy_struct.buyer_name, GetCleanName(), sizeof(out_data->trader_buy_struct.buyer_name)); + strn0cpy(out_data->trader_buy_struct.buyer_name, in->buyer_name, sizeof(out_data->trader_buy_struct.buyer_name)); + strn0cpy(out_data->trader_buy_struct.item_name, in->item_name, sizeof(out_data->trader_buy_struct.item_name)); + strn0cpy(out_data->trader_buy_struct.seller_name, in->seller_name, sizeof(out_data->trader_buy_struct.seller_name)); + strn0cpy( + out_data->trader_buy_struct.item_unique_id, + in->item_unique_id, + sizeof(out_data->trader_buy_struct.item_unique_id) + ); worldserver.SendPacket(out_server.get()); @@ -3699,16 +3739,33 @@ void Client::CancelTraderTradeWindow() FastQueuePacket(&end_session); } -void Client::AddDataToMerchantList(int16 slot_id, int32 quantity, const std::string &item_unique_id) +void Client::AddDataToMerchantList(int16 slot_id, uint32 item_id, int32 quantity, const std::string &item_unique_id) { auto list = GetTraderMerchantList(); - list->emplace(std::pair(slot_id, std::make_tuple(quantity, item_unique_id))); + list->emplace(std::pair(slot_id, std::make_tuple(item_id, quantity, item_unique_id))); } -std::tuple Client::GetDataFromMerchantListByMerchantSlotId(int16 slot_id) +int16 Client::GetNextFreeSlotFromMerchantList() { auto list = GetTraderMerchantList(); - return list->contains(slot_id) ? list->at(slot_id) : std::make_tuple(INVALID_INDEX, "0000000000000000"); + for (auto const &[slot_id, merchant_data] : *list) { + auto [item_id, quantity, item_unique_id] = merchant_data; + if (item_id == 0) { + return slot_id; + } + } + + if (list->size() == GetInv().GetLookup()->InventoryTypeSize.Bazaar) { + return INVALID_INDEX; + } + + return list->size() + 1; +} + +std::tuple Client::GetDataFromMerchantListByMerchantSlotId(int16 slot_id) +{ + auto list = GetTraderMerchantList(); + return list->contains(slot_id) ? list->at(slot_id) : std::make_tuple(0, 0, "0000000000000000"); } int16 Client::GetSlotFromMerchantListByItemUniqueId(const std::string &unique_id) @@ -3716,7 +3773,7 @@ int16 Client::GetSlotFromMerchantListByItemUniqueId(const std::string &unique_id auto list = GetTraderMerchantList(); for (auto [slot_id, merchant_data] : *list) { - auto [quantity, item_unique_id] = merchant_data; + auto [item_id, quantity, item_unique_id] = merchant_data; if (item_unique_id == unique_id) { return slot_id; } @@ -3725,16 +3782,16 @@ int16 Client::GetSlotFromMerchantListByItemUniqueId(const std::string &unique_id return INVALID_INDEX; } -std::pair> Client::GetDataFromMerchantListByItemUniqueId(const std::string &unique_id) +std::pair> Client::GetDataFromMerchantListByItemUniqueId(const std::string &unique_id) { auto list = GetTraderMerchantList(); for (auto [slot_id, merchant_data] : *list) { - auto [quantity, item_unique_id] = merchant_data; + auto [item_id, quantity, item_unique_id] = merchant_data; if (item_unique_id == unique_id) { return { slot_id, merchant_data }; } } - return std::make_pair(INVALID_INDEX, std::make_tuple(0, "0000000000000000")); + return std::make_pair(INVALID_INDEX, std::make_tuple(0, 0, "0000000000000000")); } From 8f79ce25de07c9e50f3e4f3f09db2094d5b4436b Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sat, 12 Apr 2025 16:00:15 -0300 Subject: [PATCH 17/48] Added checks for same item with price change. Client treats them all the same so all need to be updated. Also repaired max stacksize of 32767 on RoF2 --- zone/trading.cpp | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/zone/trading.cpp b/zone/trading.cpp index 3297a5bf8..f2c10f14d 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1458,21 +1458,18 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) } uint64 total_transaction_value = static_cast(in->price) * static_cast(quantity); - if (total_transaction_value > MAX_TRANSACTION_VALUE) { - Message(Chat::Red,"That would exceed the single transaction limit of %u platinum.", MAX_TRANSACTION_VALUE / 1000); + if (total_transaction_value > RoF2::constants::MAX_BAZAAR_TRANSACTION) { + Message( + Chat::Red, + "That would exceed the single transaction limit of %u platinum.", + RoF2::constants::MAX_BAZAAR_TRANSACTION / 1000 + ); TradeRequestFailed(app); return; } - // This cannot overflow assuming MAX_TRANSACTION_VALUE, checked above, is the default of 2000000000 - uint32 total_cost = in->price * quantity; + uint64 total_cost = in->price * quantity; if (!TakeMoneyFromPP(total_cost)) { - RecordPlayerEventLog( - PlayerEvent::POSSIBLE_HACK, - PlayerEvent::PossibleHackEvent{ - .message = "Attempted to buy something in bazaar but did not have enough money." - } - ); MessageString(Chat::Red, INSUFFICIENT_FUNDS); TradeRequestFailed(app); return; @@ -1488,6 +1485,7 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) trader->AddMoneyToPP(total_cost, true); if (!PutItemInInventoryWithStacking(inst_copy.get())) { + AddMoneyToPP(total_cost, true); trader->TakeMoneyFromPP(total_cost, true); trader->PutItemInInventoryWithStacking(buy_inst); MessageString(Chat::Red, HOW_CAN_YOU_BUY_MORE, trader->GetCleanName()); @@ -2482,11 +2480,11 @@ void Client::SendTraderMode(BazaarTraderBarterActions status) void Client::TraderUpdateItem(const EQApplicationPacket *app) { - auto in = reinterpret_cast(app->pBuffer); - uint32 new_price = in->new_price; - auto inst = FindTraderItemByUniqueID(in->item_unique_id); + auto in = reinterpret_cast(app->pBuffer); + uint32 new_price = in->new_price; + auto inst = FindTraderItemByUniqueID(in->item_unique_id); + auto customer = entity_list.GetClientByID(GetCustomerID()); - auto customer = entity_list.GetClientByID(GetCustomerID()); if (new_price == 0) { auto result = TraderRepository::DeleteWhere(database, fmt::format("`item_unique_id` = '{}'", in->item_unique_id)); if (!result) { From 116040914cca1f3807e5335d29d9ab378332bd36 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 13 Apr 2025 09:48:38 -0300 Subject: [PATCH 18/48] New BazaarSearch query routine --- common/bazaar.cpp | 93 ++++++++++++++++++------- common/repositories/items_repository.h | 4 +- common/repositories/trader_repository.h | 50 +++++++++++-- common/strings.cpp | 19 +++++ common/strings.h | 2 + zone/client_packet.cpp | 2 +- zone/trading.cpp | 45 ++++++++---- 7 files changed, 165 insertions(+), 50 deletions(-) diff --git a/common/bazaar.cpp b/common/bazaar.cpp index 2943a9b93..a9c2fad21 100644 --- a/common/bazaar.cpp +++ b/common/bazaar.cpp @@ -262,39 +262,78 @@ Bazaar::GetSearchResults( } std::vector all_entries; - std::vector trader_items_ids{}; + std::unordered_set trader_items_ids{}; - auto const trader_results = TraderRepository::GetBazaarTraderDetails(db, search_criteria_trader); - if (trader_results.empty()) { - LogTradingDetail("Bazaar - No traders found in bazaar search."); - return all_entries; - } - - for (auto const &i: trader_results) { - trader_items_ids.push_back(std::to_string(i.trader.item_id)); - } - - auto const item_results = ItemsRepository::GetItemsForBazaarSearch( - content_db, - trader_items_ids, - std::string(search.item_name), +// auto const trader_results = TraderRepository::GetBazaarTraderDetails(db, search_criteria_trader, search.max_results); + auto const item_results = TraderRepository::GetBazaarTraderDetails( + db, + search_criteria_trader, + search.item_name, field_criteria_items, where_criteria_items, search.max_results ); + // if (trader_results.empty()) { + // LogTradingDetail("Bazaar - No traders found in bazaar search."); + // return all_entries; + // } + + // for (auto const &i: trader_results) { + // trader_items_ids.emplace(std::to_string(i.trader.item_id)); + // //trader_items_ids.push_back(std::to_string(i.trader.item_id)); + // } + + // auto const item_results = ItemsRepository::GetItemsForBazaarSearch( + // content_db, + // trader_items_ids, + // std::string(search.item_name), + // field_criteria_items, + // where_criteria_items, + // search.max_results + // ); + if (item_results.empty()) { LogTradingDetail("Bazaar - No items found in bazaar search."); return all_entries; } - all_entries.reserve(trader_results.size()); + //all_entries.reserve(trader_results.size()); - for (auto const& t:trader_results) { - if (!item_results.contains(t.trader.item_id)) { - continue; - } + // for (auto const& t:trader_results) { + // if (!item_results.contains(t.trader.item_id)) { + // continue; + // } + // + // BazaarSearchResultsFromDB_Struct r{}; + // r.count = 1; + // r.trader_id = t.trader.character_id; + // r.item_unique_id = t.trader.item_unique_id; + // r.cost = t.trader.item_cost; + // r.slot_id = t.trader.slot_id; + // r.charges = t.trader.item_charges; + // r.stackable = item_results.at(t.trader.item_id).stackable; + // r.icon_id = item_results.at(t.trader.item_id).icon; + // r.trader_zone_id = t.trader.char_zone_id; + // r.trader_zone_instance_id = t.trader.char_zone_instance_id; + // r.trader_entity_id = t.trader.char_entity_id; + // r.item_name = fmt::format("{:.63}\0", item_results.at(t.trader.item_id).name); + // r.trader_name = fmt::format("{:.63}\0", t.trader_name); + // r.item_stat = item_results.at(t.trader.item_id).stats; + // + // if (RuleB(Bazaar, UseAlternateBazaarSearch)) { + // if (convert || + // char_zone_id != Zones::BAZAAR || + // (char_zone_id == Zones::BAZAAR && r.trader_zone_instance_id != char_zone_instance_id) + // ) { + // r.trader_id = TraderRepository::TRADER_CONVERT_ID + r.trader_zone_instance_id; + // } + // } + // + // all_entries.push_back(r); + // } + for (auto const& t:item_results) { BazaarSearchResultsFromDB_Struct r{}; r.count = 1; r.trader_id = t.trader.character_id; @@ -302,14 +341,14 @@ Bazaar::GetSearchResults( r.cost = t.trader.item_cost; r.slot_id = t.trader.slot_id; r.charges = t.trader.item_charges; - r.stackable = item_results.at(t.trader.item_id).stackable; - r.icon_id = item_results.at(t.trader.item_id).icon; + r.stackable = t.stackable; + r.icon_id = t.icon; r.trader_zone_id = t.trader.char_zone_id; r.trader_zone_instance_id = t.trader.char_zone_instance_id; r.trader_entity_id = t.trader.char_entity_id; - r.item_name = fmt::format("{:.63}\0", item_results.at(t.trader.item_id).name); + r.item_name = fmt::format("{:.63}\0", t.name); r.trader_name = fmt::format("{:.63}\0", t.trader_name); - r.item_stat = item_results.at(t.trader.item_id).stats; + r.item_stat = t.stats; if (RuleB(Bazaar, UseAlternateBazaarSearch)) { if (convert || @@ -323,9 +362,9 @@ Bazaar::GetSearchResults( all_entries.push_back(r); } - if (all_entries.size() > search.max_results) { - all_entries.resize(search.max_results); - } + // if (all_entries.size() > search.max_results) { + // all_entries.resize(search.max_results); + // } LogTrading("Returning [{}] items from search results", all_entries.size()); diff --git a/common/repositories/items_repository.h b/common/repositories/items_repository.h index 4e5a8868e..7ec91a61f 100644 --- a/common/repositories/items_repository.h +++ b/common/repositories/items_repository.h @@ -47,7 +47,7 @@ public: static std::unordered_map GetItemsForBazaarSearch( Database& db, - const std::vector &search_ids, + const std::unordered_set &search_ids, const std::string &name, const std::string &field_criteria_items, const std::string &where_criteria_items, @@ -57,7 +57,7 @@ public: auto query = fmt::format( "SELECT id, name, stackable, icon, {} " "FROM items " - "WHERE `name` LIKE '%%{}%%' AND {} AND id IN({}) " + "WHERE `name` LIKE '%{}%' AND {} AND id IN({}) " "ORDER BY id ASC", field_criteria_items, Strings::Escape(name), diff --git a/common/repositories/trader_repository.h b/common/repositories/trader_repository.h index 5d33a2f88..32381da55 100644 --- a/common/repositories/trader_repository.h +++ b/common/repositories/trader_repository.h @@ -29,8 +29,12 @@ public: }; struct BazaarTraderSearch_Struct { - Trader trader; + Trader trader; std::string trader_name; + std::string name; + bool stackable; + uint32 icon; + uint32 stats; }; struct WelcomeData_Struct { @@ -296,22 +300,50 @@ public: static std::vector GetBazaarTraderDetails( Database &db, - std::string &search_criteria_trader + const std::string &search_criteria_trader, + const std::string &name, + const std::string &field_criteria_items, + const std::string &where_criteria_items, + uint32 max_results ) { std::vector all_entries{}; - auto query = fmt::format( + auto query_2 = fmt::format( + "WITH ranked_trader_items AS (" "SELECT trader.id, trader.character_id, trader.item_id, trader.item_unique_id, trader.augment_one, " "trader.augment_two, trader.augment_three, trader.augment_four, trader.augment_five, trader.augment_six, " "trader.item_charges, trader.item_cost, trader.slot_id, trader.char_entity_id, trader.char_zone_id, " - "trader.char_zone_instance_id, trader.active_transaction, c.`name` FROM `trader` " + "trader.char_zone_instance_id, trader.active_transaction, c.`name`, " + "items.name AS n1, items.stackable, items.icon, {}, " + "ROW_NUMBER() OVER (PARTITION BY trader.character_id) AS row_num " + "FROM trader " "INNER JOIN character_data AS c ON trader.character_id = c.id " - "WHERE {} ORDER BY trader.character_id ASC", - search_criteria_trader + "JOIN peq642024_content.items AS items ON trader.item_id = items.id " + "WHERE items.`name` LIKE '%{}%' AND {} AND {}" + ") " + "SELECT * FROM ranked_trader_items " + "WHERE row_num <= '{}';", + field_criteria_items, + Strings::Escape(name), + where_criteria_items, + search_criteria_trader, + max_results ); - auto results = db.QueryDatabase(query); + // auto query = fmt::format( + // "SELECT trader.id, trader.character_id, trader.item_id, trader.item_unique_id, trader.augment_one, " + // "trader.augment_two, trader.augment_three, trader.augment_four, trader.augment_five, trader.augment_six, " + // "trader.item_charges, trader.item_cost, trader.slot_id, trader.char_entity_id, trader.char_zone_id, " + // "trader.char_zone_instance_id, trader.active_transaction, c.`name` FROM `trader` " + // "INNER JOIN character_data AS c ON trader.character_id = c.id " + // "WHERE {} " + // "GROUP BY trader.item_id " + // "ORDER BY trader.character_id ASC", + // search_criteria_trader + // ); + + auto results = db.QueryDatabase(query_2); if (results.RowCount() == 0) { return all_entries; @@ -339,6 +371,10 @@ public: e.trader.char_zone_instance_id = row[15] ? static_cast(atoi(row[15])) : 0; e.trader.active_transaction = row[16] ? static_cast(strtoul(row[16], nullptr, 10)) : 0; e.trader_name = row[17] ? row[17] : std::string(""); + e.name = row[18] ? row[18] : ""; + e.stackable = atoi(row[19]) ? true : false; + e.icon = row[20] ? static_cast(atoi(row[20])) : 0; + e.stats = row[21] ? static_cast(atoi(row[21])) : 0; all_entries.push_back(e); } diff --git a/common/strings.cpp b/common/strings.cpp index eb56ab2f5..af8c4187b 100644 --- a/common/strings.cpp +++ b/common/strings.cpp @@ -970,3 +970,22 @@ bool Strings::IsValidJson(const std::string &json) return result; } + +std::string Strings::Implode(const std::string& glue, std::unordered_set src) +{ + if (src.empty()) { + return {}; + } + + std::ostringstream output; + std::unordered_set::iterator src_iter; + + for (src_iter = src.begin(); src_iter != src.end(); src_iter++) { + output << *src_iter << glue; + } + + std::string final_output = output.str(); + final_output.resize(output.str().size() - glue.size()); + + return final_output; +} diff --git a/common/strings.h b/common/strings.h index 8ae8fee25..dcb6b01f7 100644 --- a/common/strings.h +++ b/common/strings.h @@ -42,6 +42,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -79,6 +80,7 @@ public: static std::string Escape(const std::string &s); static std::string GetBetween(const std::string &s, std::string start_delim, std::string stop_delim); static std::string Implode(const std::string& glue, std::vector src); + static std::string Implode(const std::string& glue, std::unordered_set src); static std::string Join(const std::vector &ar, const std::string &delim); static std::string Join(const std::vector &ar, const std::string &delim); static std::string MillisecondsToTime(int duration); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index ab3126c70..89b0d1d07 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -15456,7 +15456,7 @@ void Client::Handle_OP_Trader(const EQApplicationPacket *app) break; } default: { - LogTrading("Unknown size for OP_Trader: [{}]\n", app->size); + //LogTradingDetail("Unknown size for OP_Trader: [{}]", app->size); } } } diff --git a/zone/trading.cpp b/zone/trading.cpp index f2c10f14d..5305d5425 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1399,8 +1399,20 @@ static void BazaarAuditTrail(const char *seller, const char *buyer, const char * void Client::BuyTraderItem(const EQApplicationPacket *app) { - auto in = reinterpret_cast(app->pBuffer); - auto trader = entity_list.GetClientByID(in->trader_id); + struct Checks { + bool take_customer_money {false}; + bool give_trader_money {false}; + bool give_customer_item {false}; + bool take_trader_item {false}; + uint64 customer_money {false}; + uint64 trader_money {false}; + EQ::ItemInstance *trader_item {nullptr}; + EQ::ItemInstance *customer_item {nullptr}; + }; + + Checks checks{}; + auto in = reinterpret_cast(app->pBuffer); + auto trader = entity_list.GetClientByID(in->trader_id); if (!trader || !trader->IsTrader()) { Message(Chat::Red, "The trader could not be found."); @@ -1436,6 +1448,8 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) if (CheckLoreConflict(inst_copy->GetItem())) { MessageString(Chat::Red, DUPLICATE_LORE); + in->method = BazaarByParcel; + in->sub_action = DataOutDated; TradeRequestFailed(app); return; } @@ -1474,6 +1488,7 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) TradeRequestFailed(app); return; } + checks.take_customer_money = true; if (!trader->RemoveItemByItemUniqueId(buy_inst->GetUniqueID(), quantity)) { AddMoneyToPP(total_cost, true); @@ -1481,8 +1496,10 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) TradeRequestFailed(app); return; } + checks.take_trader_item = true; trader->AddMoneyToPP(total_cost, true); + checks.give_trader_money = true; if (!PutItemInInventoryWithStacking(inst_copy.get())) { AddMoneyToPP(total_cost, true); @@ -1492,6 +1509,7 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) TradeRequestFailed(app); return; } + checks.give_customer_item = true; auto [slot_id, merchant_data] = GetDataFromMerchantListByItemUniqueId(buy_inst->GetUniqueID()); auto [item_id, merchant_quantity, item_unique_id] = merchant_data; @@ -2576,21 +2594,22 @@ void Client::TraderUpdateItem(const EQApplicationPacket *app) } } else { - for (auto const i : result) { - auto [slot_id, merchant_data] = customer->GetDataFromMerchantListByItemUniqueId(i.item_unique_id); - auto [item_id, merchant_quantity, item_unique_id] = merchant_data; - std::unique_ptr vendor_inst_copy(inst ? inst->Clone() : nullptr); - vendor_inst_copy->SetUniqueID(i.item_unique_id); - vendor_inst_copy->SetMerchantCount(i.item_charges); - vendor_inst_copy->SetMerchantSlot(slot_id); - vendor_inst_copy->SetPrice(new_price); - customer->SendItemPacket(slot_id, vendor_inst_copy.get(), ItemPacketMerchant); - } - + if (customer) { + for (auto const i : result) { + auto [slot_id, merchant_data] = customer->GetDataFromMerchantListByItemUniqueId(i.item_unique_id); + auto [item_id, merchant_quantity, item_unique_id] = merchant_data; + std::unique_ptr vendor_inst_copy(inst ? inst->Clone() : nullptr); + vendor_inst_copy->SetUniqueID(i.item_unique_id); + vendor_inst_copy->SetMerchantCount(i.item_charges); + vendor_inst_copy->SetMerchantSlot(slot_id); + vendor_inst_copy->SetPrice(new_price); + customer->SendItemPacket(slot_id, vendor_inst_copy.get(), ItemPacketMerchant); + } customer->Message( Chat::Red, fmt::format("Trader {} updated the price of item {}", GetCleanName(), inst->GetItem()->Name).c_str() ); + } } in->sub_action = BazaarPriceChange_UpdatePrice; From b06ca47d468130507472d6e8062ad29944c05d77 Mon Sep 17 00:00:00 2001 From: neckkola <65987027+neckkola@users.noreply.github.com> Date: Sun, 13 Apr 2025 12:54:41 -0300 Subject: [PATCH 19/48] WIP --- common/item_instance.h | 6 +----- zone/trading.cpp | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/common/item_instance.h b/common/item_instance.h index b30836325..22f25b6ad 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -247,11 +247,7 @@ namespace EQ const std::string &GetUniqueID() const { return m_unique_id; } //std::string &GetSerialNumber2() const { return m_serial_number2; } void SetUniqueID(std::string sn) { m_unique_id = std::move(sn); } - - void CreateUniqueID() const - { - m_unique_id = GenerateUniqueID(); - } + void CreateUniqueID() const { m_unique_id = GenerateUniqueID(); } std::map& GetTimers() const { return m_timers; } void SetTimer(std::string name, uint32 time); diff --git a/zone/trading.cpp b/zone/trading.cpp index 5305d5425..0b00dcc56 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1433,9 +1433,9 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) return; } - uint32 quantity = in->quantity; + int16 quantity = static_cast(in->quantity); inst_copy->SetCharges(quantity); - if (inst_copy->IsStackable()) { + if (inst_copy->IsStackable() && quantity != buy_inst->GetCharges()) { inst_copy->CreateUniqueID(); } From a0450ac39ec29e30378795a596014f1b6620cb20 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 13 Apr 2025 15:34:13 -0300 Subject: [PATCH 20/48] WIP --- common/bazaar.cpp | 112 ++++++++++++------------ common/patches/rof2_structs.h | 32 +++---- common/repositories/trader_repository.h | 66 +++++++------- zone/client.cpp | 2 - zone/inventory.cpp | 4 + zone/trading.cpp | 31 ++----- 6 files changed, 118 insertions(+), 129 deletions(-) diff --git a/common/bazaar.cpp b/common/bazaar.cpp index a9c2fad21..d670b0880 100644 --- a/common/bazaar.cpp +++ b/common/bazaar.cpp @@ -265,7 +265,7 @@ Bazaar::GetSearchResults( std::unordered_set trader_items_ids{}; // auto const trader_results = TraderRepository::GetBazaarTraderDetails(db, search_criteria_trader, search.max_results); - auto const item_results = TraderRepository::GetBazaarTraderDetails( + auto const trader_results = TraderRepository::GetBazaarTraderDetails( db, search_criteria_trader, search.item_name, @@ -274,37 +274,66 @@ Bazaar::GetSearchResults( search.max_results ); - // if (trader_results.empty()) { - // LogTradingDetail("Bazaar - No traders found in bazaar search."); - // return all_entries; - // } + if (trader_results.empty()) { + LogTradingDetail("Bazaar - No traders found in bazaar search."); + return all_entries; + } - // for (auto const &i: trader_results) { - // trader_items_ids.emplace(std::to_string(i.trader.item_id)); - // //trader_items_ids.push_back(std::to_string(i.trader.item_id)); - // } + for (auto const &i: trader_results) { + trader_items_ids.emplace(std::to_string(i.trader.item_id)); + //trader_items_ids.push_back(std::to_string(i.trader.item_id)); + } - // auto const item_results = ItemsRepository::GetItemsForBazaarSearch( - // content_db, - // trader_items_ids, - // std::string(search.item_name), - // field_criteria_items, - // where_criteria_items, - // search.max_results - // ); + auto const item_results = ItemsRepository::GetItemsForBazaarSearch( + content_db, + trader_items_ids, + std::string(search.item_name), + field_criteria_items, + where_criteria_items, + search.max_results + ); if (item_results.empty()) { LogTradingDetail("Bazaar - No items found in bazaar search."); return all_entries; } - //all_entries.reserve(trader_results.size()); + all_entries.reserve(trader_results.size()); - // for (auto const& t:trader_results) { - // if (!item_results.contains(t.trader.item_id)) { - // continue; - // } - // + for (auto const& t:trader_results) { + if (!item_results.contains(t.trader.item_id)) { + continue; + } + + BazaarSearchResultsFromDB_Struct r{}; + r.count = 1; + r.trader_id = t.trader.character_id; + r.item_unique_id = t.trader.item_unique_id; + r.cost = t.trader.item_cost; + r.slot_id = t.trader.slot_id; + r.charges = t.trader.item_charges; + r.stackable = item_results.at(t.trader.item_id).stackable; + r.icon_id = item_results.at(t.trader.item_id).icon; + r.trader_zone_id = t.trader.char_zone_id; + r.trader_zone_instance_id = t.trader.char_zone_instance_id; + r.trader_entity_id = t.trader.char_entity_id; + r.item_name = fmt::format("{:.63}\0", item_results.at(t.trader.item_id).name); + r.trader_name = fmt::format("{:.63}\0", t.trader_name); + r.item_stat = item_results.at(t.trader.item_id).stats; + + if (RuleB(Bazaar, UseAlternateBazaarSearch)) { + if (convert || + char_zone_id != Zones::BAZAAR || + (char_zone_id == Zones::BAZAAR && r.trader_zone_instance_id != char_zone_instance_id) + ) { + r.trader_id = TraderRepository::TRADER_CONVERT_ID + r.trader_zone_instance_id; + } + } + + all_entries.push_back(r); + } + + // for (auto const& t:item_results) { // BazaarSearchResultsFromDB_Struct r{}; // r.count = 1; // r.trader_id = t.trader.character_id; @@ -312,14 +341,14 @@ Bazaar::GetSearchResults( // r.cost = t.trader.item_cost; // r.slot_id = t.trader.slot_id; // r.charges = t.trader.item_charges; - // r.stackable = item_results.at(t.trader.item_id).stackable; - // r.icon_id = item_results.at(t.trader.item_id).icon; + // r.stackable = t.stackable; + // r.icon_id = t.icon; // r.trader_zone_id = t.trader.char_zone_id; // r.trader_zone_instance_id = t.trader.char_zone_instance_id; // r.trader_entity_id = t.trader.char_entity_id; - // r.item_name = fmt::format("{:.63}\0", item_results.at(t.trader.item_id).name); + // r.item_name = fmt::format("{:.63}\0", t.name); // r.trader_name = fmt::format("{:.63}\0", t.trader_name); - // r.item_stat = item_results.at(t.trader.item_id).stats; + // r.item_stat = t.stats; // // if (RuleB(Bazaar, UseAlternateBazaarSearch)) { // if (convert || @@ -333,35 +362,6 @@ Bazaar::GetSearchResults( // all_entries.push_back(r); // } - for (auto const& t:item_results) { - BazaarSearchResultsFromDB_Struct r{}; - r.count = 1; - r.trader_id = t.trader.character_id; - r.item_unique_id = t.trader.item_unique_id; - r.cost = t.trader.item_cost; - r.slot_id = t.trader.slot_id; - r.charges = t.trader.item_charges; - r.stackable = t.stackable; - r.icon_id = t.icon; - r.trader_zone_id = t.trader.char_zone_id; - r.trader_zone_instance_id = t.trader.char_zone_instance_id; - r.trader_entity_id = t.trader.char_entity_id; - r.item_name = fmt::format("{:.63}\0", t.name); - r.trader_name = fmt::format("{:.63}\0", t.trader_name); - r.item_stat = t.stats; - - if (RuleB(Bazaar, UseAlternateBazaarSearch)) { - if (convert || - char_zone_id != Zones::BAZAAR || - (char_zone_id == Zones::BAZAAR && r.trader_zone_instance_id != char_zone_instance_id) - ) { - r.trader_id = TraderRepository::TRADER_CONVERT_ID + r.trader_zone_instance_id; - } - } - - all_entries.push_back(r); - } - // if (all_entries.size() > search.max_results) { // all_entries.resize(search.max_results); // } diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index d544dc1d3..745ecc470 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -3616,22 +3616,22 @@ struct TraderStatus_Struct { }; struct TraderBuy_Struct { -/*000*/ uint32 action; -/*004*/ uint32 method; -/*008*/ uint32 sub_action; -/*012*/ uint32 unknown_012; -/*016*/ uint32 trader_id; -/*020*/ char buyer_name[64]; -/*084*/ char seller_name[64]; -/*148*/ char unknown_148[32]; -/*180*/ char item_name[64]; -/*244*/ char item_unique_id[17]; -/*261*/ char unknown_261[3]; -/*264*/ uint32 item_id; -/*268*/ uint32 price; -/*272*/ uint32 already_sold; -/*276*/ uint32 unknown_276; -/*280*/ uint32 quantity; +/*000*/ uint32 action; +/*004*/ uint32 method; +/*008*/ uint32 sub_action; +/*012*/ uint32 unknown_012; +/*016*/ uint32 trader_id; +/*020*/ char buyer_name[64]; +/*084*/ char seller_name[64]; +/*148*/ char unknown_148[32]; +/*180*/ char item_name[64]; +/*244*/ char item_unique_id[17]; +/*261*/ char unknown_261[3]; +/*264*/ uint32 item_id; +/*268*/ uint32 price; +/*272*/ uint32 already_sold; +/*276*/ uint32 unknown_276; +/*280*/ uint32 quantity; /*284*/ }; diff --git a/common/repositories/trader_repository.h b/common/repositories/trader_repository.h index 32381da55..edbe0069b 100644 --- a/common/repositories/trader_repository.h +++ b/common/repositories/trader_repository.h @@ -309,41 +309,41 @@ public: { std::vector all_entries{}; - auto query_2 = fmt::format( - "WITH ranked_trader_items AS (" - "SELECT trader.id, trader.character_id, trader.item_id, trader.item_unique_id, trader.augment_one, " - "trader.augment_two, trader.augment_three, trader.augment_four, trader.augment_five, trader.augment_six, " - "trader.item_charges, trader.item_cost, trader.slot_id, trader.char_entity_id, trader.char_zone_id, " - "trader.char_zone_instance_id, trader.active_transaction, c.`name`, " - "items.name AS n1, items.stackable, items.icon, {}, " - "ROW_NUMBER() OVER (PARTITION BY trader.character_id) AS row_num " - "FROM trader " - "INNER JOIN character_data AS c ON trader.character_id = c.id " - "JOIN peq642024_content.items AS items ON trader.item_id = items.id " - "WHERE items.`name` LIKE '%{}%' AND {} AND {}" - ") " - "SELECT * FROM ranked_trader_items " - "WHERE row_num <= '{}';", - field_criteria_items, - Strings::Escape(name), - where_criteria_items, - search_criteria_trader, - max_results - ); - - // auto query = fmt::format( + // auto query_2 = fmt::format( + // "WITH ranked_trader_items AS (" // "SELECT trader.id, trader.character_id, trader.item_id, trader.item_unique_id, trader.augment_one, " // "trader.augment_two, trader.augment_three, trader.augment_four, trader.augment_five, trader.augment_six, " // "trader.item_charges, trader.item_cost, trader.slot_id, trader.char_entity_id, trader.char_zone_id, " - // "trader.char_zone_instance_id, trader.active_transaction, c.`name` FROM `trader` " + // "trader.char_zone_instance_id, trader.active_transaction, c.`name`, " + // "items.name AS n1, items.stackable, items.icon, {}, " + // "ROW_NUMBER() OVER (PARTITION BY trader.character_id) AS row_num " + // "FROM trader " // "INNER JOIN character_data AS c ON trader.character_id = c.id " - // "WHERE {} " - // "GROUP BY trader.item_id " - // "ORDER BY trader.character_id ASC", - // search_criteria_trader + // "JOIN peq642024_content.items AS items ON trader.item_id = items.id " + // "WHERE items.`name` LIKE '%{}%' AND {} AND {}" + // ") " + // "SELECT * FROM ranked_trader_items " + // "WHERE row_num <= '{}';", + // field_criteria_items, + // Strings::Escape(name), + // where_criteria_items, + // search_criteria_trader, + // max_results // ); - auto results = db.QueryDatabase(query_2); + auto query = fmt::format( + "SELECT trader.id, trader.character_id, trader.item_id, trader.item_unique_id, trader.augment_one, " + "trader.augment_two, trader.augment_three, trader.augment_four, trader.augment_five, trader.augment_six, " + "trader.item_charges, trader.item_cost, trader.slot_id, trader.char_entity_id, trader.char_zone_id, " + "trader.char_zone_instance_id, trader.active_transaction, c.`name` FROM `trader` " + "INNER JOIN character_data AS c ON trader.character_id = c.id " + "WHERE {} " + "GROUP BY trader.item_id " + "ORDER BY trader.character_id ASC", + search_criteria_trader + ); + + auto results = db.QueryDatabase(query); if (results.RowCount() == 0) { return all_entries; @@ -371,10 +371,10 @@ public: e.trader.char_zone_instance_id = row[15] ? static_cast(atoi(row[15])) : 0; e.trader.active_transaction = row[16] ? static_cast(strtoul(row[16], nullptr, 10)) : 0; e.trader_name = row[17] ? row[17] : std::string(""); - e.name = row[18] ? row[18] : ""; - e.stackable = atoi(row[19]) ? true : false; - e.icon = row[20] ? static_cast(atoi(row[20])) : 0; - e.stats = row[21] ? static_cast(atoi(row[21])) : 0; + // e.name = row[18] ? row[18] : ""; + // e.stackable = atoi(row[19]) ? true : false; + // e.icon = row[20] ? static_cast(atoi(row[20])) : 0; + // e.stats = row[21] ? static_cast(atoi(row[21])) : 0; all_entries.push_back(e); } diff --git a/zone/client.cpp b/zone/client.cpp index a27801e2b..5b990635a 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -21,8 +21,6 @@ #include #include "../common/global_define.h" -#include "../common/repositories/inventory_snapshots_repository.h" - // for windows compile #ifndef _WINDOWS #include diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 30d19b6f0..3a8eb0260 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -4693,6 +4693,10 @@ bool Client::PutItemInInventoryWithStacking(EQ::ItemInstance *inst) uint8 bag_size = inv_inst->GetItem()->BagSlots; for (uint8 bag_slot = EQ::invbag::SLOT_BEGIN; bag_slot < bag_size; bag_slot++) { + if (quantity == 0) { + break; + } + auto bag_inst = GetInv().GetItem(base_slot_id + bag_slot); if (!bag_inst && inv_inst->GetItem()->BagSize >= inst->GetItem()->Size) { LogError("Found a parent {} base_slot_id {} bag_slot {} in bag", i, base_slot_id, bag_slot); diff --git a/zone/trading.cpp b/zone/trading.cpp index 0b00dcc56..c78cca0fb 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1399,18 +1399,6 @@ static void BazaarAuditTrail(const char *seller, const char *buyer, const char * void Client::BuyTraderItem(const EQApplicationPacket *app) { - struct Checks { - bool take_customer_money {false}; - bool give_trader_money {false}; - bool give_customer_item {false}; - bool take_trader_item {false}; - uint64 customer_money {false}; - uint64 trader_money {false}; - EQ::ItemInstance *trader_item {nullptr}; - EQ::ItemInstance *customer_item {nullptr}; - }; - - Checks checks{}; auto in = reinterpret_cast(app->pBuffer); auto trader = entity_list.GetClientByID(in->trader_id); @@ -1433,7 +1421,7 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) return; } - int16 quantity = static_cast(in->quantity); + auto quantity = in->quantity; inst_copy->SetCharges(quantity); if (inst_copy->IsStackable() && quantity != buy_inst->GetCharges()) { inst_copy->CreateUniqueID(); @@ -1448,8 +1436,6 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) if (CheckLoreConflict(inst_copy->GetItem())) { MessageString(Chat::Red, DUPLICATE_LORE); - in->method = BazaarByParcel; - in->sub_action = DataOutDated; TradeRequestFailed(app); return; } @@ -1488,7 +1474,6 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) TradeRequestFailed(app); return; } - checks.take_customer_money = true; if (!trader->RemoveItemByItemUniqueId(buy_inst->GetUniqueID(), quantity)) { AddMoneyToPP(total_cost, true); @@ -1496,10 +1481,8 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) TradeRequestFailed(app); return; } - checks.take_trader_item = true; trader->AddMoneyToPP(total_cost, true); - checks.give_trader_money = true; if (!PutItemInInventoryWithStacking(inst_copy.get())) { AddMoneyToPP(total_cost, true); @@ -1509,7 +1492,6 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) TradeRequestFailed(app); return; } - checks.give_customer_item = true; auto [slot_id, merchant_data] = GetDataFromMerchantListByItemUniqueId(buy_inst->GetUniqueID()); auto [item_id, merchant_quantity, item_unique_id] = merchant_data; @@ -1526,17 +1508,21 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) QueuePacket(app); - LogTrading("Customer Paid: [{}] in Copper", total_cost); - LogTrading("Trader Received: [{}] in Copper", total_cost); + LogTrading("Customer Paid: [{}] to {}", DetermineMoneyString(total_cost), trader->GetCleanName()); + LogTrading("Customer Received: [{}] {} with unique_id of {}", quantity, in->item_name, inst_copy->GetUniqueID()); + LogTrading("Trader Received: [{}] from {}", DetermineMoneyString(total_cost), GetCleanName()); + LogTrading("Trader Sent: [{}] {} with unique_id of {}", quantity, in->item_name, buy_inst->GetUniqueID()); if (merchant_quantity > quantity) { std::unique_ptr vendor_inst(buy_inst ? buy_inst->Clone() : nullptr); vendor_inst->SetMerchantCount(merchant_quantity - quantity); vendor_inst->SetMerchantSlot(slot_id); vendor_inst->SetPrice(in->price); + auto list = GetTraderMerchantList(); - TraderRepository::UpdateQuantity(database, item_unique_id, merchant_quantity - quantity); std::get<1>(list->at(slot_id)) -= quantity; + + TraderRepository::UpdateQuantity(database, item_unique_id, merchant_quantity - quantity); SendItemPacket(slot_id, vendor_inst.get(), ItemPacketMerchant); } else { @@ -1552,6 +1538,7 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) auto list = GetTraderMerchantList(); list->erase(slot_id); + TraderRepository::DeleteWhere(database, fmt::format("`item_unique_id` = '{}'", item_unique_id)); } From 885b472340c2ac8355ce235351f2717390553def Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 13 Apr 2025 16:05:59 -0300 Subject: [PATCH 21/48] WIP --- zone/trading.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/zone/trading.cpp b/zone/trading.cpp index c78cca0fb..ae69c52fa 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1423,6 +1423,10 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) auto quantity = in->quantity; inst_copy->SetCharges(quantity); + if (buy_inst->GetItem()->MaxCharges > 0) { + inst_copy->SetCharges(buy_inst->GetCharges()); + } + if (inst_copy->IsStackable() && quantity != buy_inst->GetCharges()) { inst_copy->CreateUniqueID(); } @@ -1509,9 +1513,19 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) QueuePacket(app); LogTrading("Customer Paid: [{}] to {}", DetermineMoneyString(total_cost), trader->GetCleanName()); - LogTrading("Customer Received: [{}] {} with unique_id of {}", quantity, in->item_name, inst_copy->GetUniqueID()); + LogTrading("Customer Received: [{}] {} {} with unique_id of {}", + quantity, + in->item_name, + inst_copy->GetItem()->MaxCharges > 0 ? fmt::format("with {} charges ", inst_copy->GetCharges()).c_str() : std::string(""), + inst_copy->GetUniqueID() + ); LogTrading("Trader Received: [{}] from {}", DetermineMoneyString(total_cost), GetCleanName()); - LogTrading("Trader Sent: [{}] {} with unique_id of {}", quantity, in->item_name, buy_inst->GetUniqueID()); + LogTrading("Trader Sent: [{}] {} {} with unique_id of {}", + quantity, + in->item_name, + buy_inst->GetItem()->MaxCharges > 0 ? fmt::format("with {} charges ", buy_inst->GetCharges()).c_str() : std::string(""), + buy_inst->GetUniqueID() + ); if (merchant_quantity > quantity) { std::unique_ptr vendor_inst(buy_inst ? buy_inst->Clone() : nullptr); From 125457afe6e91562e25f07e2e96377cf34906275 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Tue, 15 Apr 2025 21:39:35 -0300 Subject: [PATCH 22/48] WIP --- common/repositories/trader_repository.h | 16 ++- common/servertalk.h | 13 ++- world/zoneserver.cpp | 4 +- zone/trading.cpp | 130 +++++++++++++++++++----- 4 files changed, 135 insertions(+), 28 deletions(-) diff --git a/common/repositories/trader_repository.h b/common/repositories/trader_repository.h index edbe0069b..e3dd79a32 100644 --- a/common/repositories/trader_repository.h +++ b/common/repositories/trader_repository.h @@ -245,6 +245,21 @@ public: return trader_item.at(0); } + static Trader GetItemByItemUniqueNumber(Database &db, const char* item_unique_id) + { + Trader e{}; + const auto trader_item = GetWhere( + db, + fmt::format("`item_unique_id` = '{}' LIMIT 1", item_unique_id) + ); + + if (trader_item.empty()) { + return e; + } + + return trader_item.at(0); + } + static int UpdateActiveTransaction(Database &db, uint32 id, bool status) { auto e = FindOne(db, id); @@ -338,7 +353,6 @@ public: "trader.char_zone_instance_id, trader.active_transaction, c.`name` FROM `trader` " "INNER JOIN character_data AS c ON trader.character_id = c.id " "WHERE {} " - "GROUP BY trader.item_id " "ORDER BY trader.character_id ASC", search_criteria_trader ); diff --git a/common/servertalk.h b/common/servertalk.h index 9985c875c..cfc0d49df 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -368,6 +368,14 @@ enum { UserToWorldStatusAlreadyOnline = -4 }; +enum { + BazaarPurchaseFailed = 0, + BazaarPurchaseSuccess = 1, + BazaarPurchaseSellerFailed = 2, + BazaarPurchaseSellerSuccess = 3, + BazaarPurchaseBuyerFailed = 4, + BazaarPurchaseBuyerSuccess = 5 +}; /************ PACKET RELATED STRUCT ************/ class ServerPacket { @@ -1775,8 +1783,11 @@ struct BazaarPurchaseMessaging_Struct { uint32 item_aug_5; uint32 item_aug_6; uint32 buyer_id; - uint32 item_quantity_available; + uint32 item_charges; uint32 id; + uint32 trader_zone_id; + uint32 trader_zone_instance_id; + uint32 transaction_status; }; diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 30e90cb09..f428aa87b 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1677,7 +1677,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { auto in = (BazaarPurchaseMessaging_Struct *)pack->pBuffer; if (in->trader_buy_struct.trader_id <= 0) { LogTrading( - "World Message [{}] received with invalid trader_id [{}]", + "World Message [{}] received with invalid trader_id [{}]", "ServerOP_BazaarPurchase", in->trader_buy_struct.trader_id ); @@ -1686,7 +1686,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { auto trader = ClientList::Instance()->FindCLEByCharacterID(in->trader_buy_struct.trader_id); if (trader) { - ZSList::Instance()->SendPacket(trader->zone(), trader->instance(), pack); + ZSList::Instance()->SendPacket(in->trader_zone_id, in->trader_zone_instance_id, pack); } break; diff --git a/zone/trading.cpp b/zone/trading.cpp index ae69c52fa..473ebc5cf 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -2773,8 +2773,7 @@ std::string Client::DetermineMoneyString(uint64 cp) void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) { auto in = reinterpret_cast(app->pBuffer); - auto item_unique_id = std::string(in->item_unique_id); - auto trader_item = TraderRepository::GetItemByItemUniqueNumber(database, item_unique_id); + auto trader_item = TraderRepository::GetItemByItemUniqueNumber(database, in->item_unique_id); LogTradingDetail( "Packet details: \n" @@ -2836,13 +2835,11 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) return; } - TraderRepository::UpdateActiveTransaction(database, trader_item.id, true); - auto next_slot = FindNextFreeParcelSlot(CharacterID()); if (next_slot == INVALID_INDEX) { LogTrading( - "{} attempted to purchase {} from the bazaar with parcel delivery. Unfortunately their parcel limit was reached. " - "Purchase unsuccessful.", + "{} attempted to purchase {} from the bazaar with parcel delivery. Unfortunately their parcel limit was " + "reached. Purchase unsuccessful.", GetCleanName(), in->item_name ); @@ -2853,11 +2850,24 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) return; } - LogTrading( - "Name: [{}] Requested Quantity: [{}] Charges on Item [{}]", - in->item_name, - in->quantity, - trader_item.item_charges + TraderRepository::UpdateActiveTransaction(database, trader_item.id, true); + + uint32 quantity = in->quantity; + int16 charges = 1; + auto item = database.GetItem(trader_item.item_id); + + if (trader_item.item_charges > 0 || item->Stackable || item->MaxCharges > 0) { + charges = trader_item.item_charges; + } + + LogTrading("Name: [{}] Requested Quantity: [{}] Charges: [{}]", in->item_name, quantity, charges); + LogTradingDetail( + "Step 1:Bazaar Purchase. Buyer [{}] Seller [{}] Quantity [{}] Charges [{}] Item_Unique_ID [{}]", + CharacterID(), + in->trader_id, + quantity, + charges, + in->item_unique_id ); uint64 total_cost = static_cast(in->price) * static_cast(in->quantity); @@ -2872,7 +2882,7 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) return; } - uint64 fee = std::round(total_cost * RuleR(Bazaar, ParcelDeliveryCostMod)); + uint64 fee = std::round(total_cost * RuleR(Bazaar, ParcelDeliveryCostMod)); if (!TakeMoneyFromPP(total_cost + fee, false)) { in->method = BazaarByParcel; in->sub_action = InsufficientFunds; @@ -2882,7 +2892,8 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) } Message(Chat::Red, fmt::format("You paid {} for the parcel delivery.", DetermineMoneyString(fee)).c_str()); - LogTrading("Customer [{}] Paid: [{}] in Copper", CharacterID(), total_cost); + LogTrading("Customer [{}] Paid: [{}] to trader [{}]", CharacterID(), DetermineMoneyString(total_cost), trader_item.character_id); + LogTradingDetail("Step 2:Bazaar Purchase. Took [{}] from Buyer [{}] ", DetermineMoneyString(total_cost), CharacterID()); if (buy_item && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_PURCHASE)) { auto e = PlayerEvent::TraderPurchaseEvent{ @@ -2910,7 +2921,7 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) parcel_out.from_name = in->seller_name; parcel_out.note = "Delivered from a Bazaar Purchase"; parcel_out.sent_date = time(nullptr); - parcel_out.quantity = in->quantity; + parcel_out.quantity = charges; parcel_out.item_id = trader_item.item_id; parcel_out.aug_slot_1 = trader_item.augment_one; parcel_out.aug_slot_2 = trader_item.augment_two; @@ -2934,6 +2945,7 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) in->method = BazaarByParcel; in->sub_action = Failed; TraderRepository::UpdateActiveTransaction(database, trader_item.id, false); + AddMoneyToPP(total_cost + fee); TradeRequestFailed(app); return; } @@ -2960,20 +2972,41 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) Parcel_Struct ps{}; ps.item_slot = parcel_out.slot_id; strn0cpy(ps.send_to, GetCleanName(), sizeof(ps.send_to)); - - if (trader_item.item_charges <= static_cast(in->quantity) || !trader_item.item_charges <= 0) { - TraderRepository::DeleteOne(database, trader_item.id); - } - SendParcelDeliveryToWorld(ps); - auto out_server = std::make_unique(ServerOP_BazaarPurchase, static_cast(sizeof(BazaarPurchaseMessaging_Struct))); + LogTradingDetail("Step 3:Bazaar Purchase. Sent parcel to Buyer [{}] Item ID [{}] Quantity [{}] Charges [{}]", + CharacterID(), + trader_item.item_id, + quantity, + charges + ); + + if (item->Stackable && quantity != charges) { + TraderRepository::UpdateQuantity(database, in->item_unique_id, trader_item.item_charges - quantity); + LogTradingDetail( + "Step 4a:Bazaar Purchase. Decreased database id {} from [{}] to [{}] charges", + trader_item.item_id, + trader_item.item_charges, + charges + ); + } + else { + TraderRepository::DeleteOne(database, trader_item.id); + LogTradingDetail( + "Step 4b:Bazaar Purchase. Deleted database id [{}] because database quantity [{}] equals [{}] purchased quantity", + trader_item.id, + trader_item.item_charges, + charges + ); + } + + auto out_server = std::make_unique(ServerOP_BazaarPurchase, sizeof(BazaarPurchaseMessaging_Struct)); auto out_data = (BazaarPurchaseMessaging_Struct *) out_server->pBuffer; out_data->trader_buy_struct.action = in->action; out_data->trader_buy_struct.method = in->method; out_data->trader_buy_struct.already_sold = in->already_sold; - out_data->trader_buy_struct.item_id = in->item_id; + out_data->trader_buy_struct.item_id = item->ID; out_data->trader_buy_struct.price = in->price; out_data->trader_buy_struct.quantity = in->quantity; out_data->trader_buy_struct.sub_action = in->sub_action; @@ -2985,12 +3018,13 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) out_data->item_aug_4 = trader_item.augment_four; out_data->item_aug_5 = trader_item.augment_five; out_data->item_aug_6 = trader_item.augment_six; - out_data->item_quantity_available = trader_item.item_charges; + out_data->item_charges = trader_item.item_charges; out_data->id = trader_item.id; + out_data->trader_zone_id = trader_item.char_zone_id; + out_data->trader_zone_instance_id = trader_item.char_zone_instance_id; strn0cpy(out_data->trader_buy_struct.buyer_name, GetCleanName(), sizeof(out_data->trader_buy_struct.buyer_name)); - strn0cpy(out_data->trader_buy_struct.buyer_name, in->buyer_name, sizeof(out_data->trader_buy_struct.buyer_name)); - strn0cpy(out_data->trader_buy_struct.item_name, in->item_name, sizeof(out_data->trader_buy_struct.item_name)); strn0cpy(out_data->trader_buy_struct.seller_name, in->seller_name, sizeof(out_data->trader_buy_struct.seller_name)); + strn0cpy(out_data->trader_buy_struct.item_name, in->item_name, sizeof(out_data->trader_buy_struct.item_name)); strn0cpy( out_data->trader_buy_struct.item_unique_id, in->item_unique_id, @@ -2998,8 +3032,56 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) ); worldserver.SendPacket(out_server.get()); + LogTradingDetail("Step 5:Bazaar Purchase. Send bazaar messaging data to world.\n" + "Action: {} \n" + "Sub Action: {} \n" + "Method: {} \n" + "Item ID: {} \n" + "Item Unique ID: {} \n" + "Item Name: {} \n" + "Price: {} \n" + "Quantity: {} \n" + "Charges: {} \n" + "Augment One: {} \n" + "Augment Two: {} \n" + "Augment Three: {} \n" + "Augment Four: {} \n" + "Augment Five: {} \n" + "Augment Six: {} \n" + "Already Sold: {} \n" + "DB ID: {} \n" + "Trader ID: {} \n" + "Trader: {} \n" + "Trader Zone ID {} \n" + "Trader Zone Instance ID {} \n" + "Buyer ID: {} \n" + "Buyer: {} \n", + out_data->trader_buy_struct.action, + out_data->trader_buy_struct.sub_action, + out_data->trader_buy_struct.method, + out_data->trader_buy_struct.item_id, + out_data->trader_buy_struct.item_unique_id, + out_data->trader_buy_struct.item_name, + out_data->trader_buy_struct.price, + out_data->trader_buy_struct.quantity, + out_data->item_charges, + out_data->item_aug_1, + out_data->item_aug_2, + out_data->item_aug_3, + out_data->item_aug_4, + out_data->item_aug_5, + out_data->item_aug_6, + out_data->trader_buy_struct.already_sold, + out_data->id, + out_data->trader_buy_struct.trader_id, + out_data->trader_buy_struct.seller_name, + out_data->trader_zone_id, + out_data->trader_zone_instance_id, + out_data->buyer_id, + out_data->trader_buy_struct.buyer_name); SendMoneyUpdate(); + LogTradingDetail("Step 6:Bazaar Purchase. Send money update to client {}. Buyer Actions complete.", CharacterID()); } void Client::SetBuyerWelcomeMessage(const char *welcome_message) From 93c38f5e24125b7285d3b5b8ddb9454fb5dcc523 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Thu, 17 Apr 2025 22:37:22 -0300 Subject: [PATCH 23/48] wip --- common/eq_limits.cpp | 10 +- common/eq_limits.h | 7 +- common/servertalk.h | 18 +-- world/zoneserver.cpp | 30 ++--- zone/trading.cpp | 129 +++------------------ zone/worldserver.cpp | 268 ++++++++++++++++++++++++++++++++++--------- 6 files changed, 271 insertions(+), 191 deletions(-) diff --git a/common/eq_limits.cpp b/common/eq_limits.cpp index 1bfbb6d29..56a370ceb 100644 --- a/common/eq_limits.cpp +++ b/common/eq_limits.cpp @@ -48,6 +48,7 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers ClientUnknown::constants::EXPANSIONS_MASK, ClientUnknown::INULL, ClientUnknown::INULL, + ClientUnknown::INULL, ClientUnknown::INULL ), /*[ClientVersion::Client62] =*/ @@ -57,6 +58,7 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers Client62::constants::EXPANSIONS_MASK, Client62::INULL, Client62::INULL, + Client62::INULL, Client62::INULL ), /*[ClientVersion::Titanium] =*/ @@ -66,6 +68,7 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers Titanium::constants::EXPANSIONS_MASK, Titanium::constants::CHARACTER_CREATION_LIMIT, Titanium::constants::SAY_LINK_BODY_SIZE, + Titanium::INULL, Titanium::INULL ), /*[ClientVersion::SoF] =*/ @@ -75,6 +78,7 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers SoF::constants::EXPANSIONS_MASK, SoF::constants::CHARACTER_CREATION_LIMIT, SoF::constants::SAY_LINK_BODY_SIZE, + SoF::INULL, SoF::INULL ), /*[ClientVersion::SoD] =*/ @@ -84,6 +88,7 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers SoD::constants::EXPANSIONS_MASK, SoD::constants::CHARACTER_CREATION_LIMIT, SoD::constants::SAY_LINK_BODY_SIZE, + SoD::INULL, SoD::INULL ), /*[ClientVersion::UF] =*/ @@ -93,6 +98,7 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers UF::constants::EXPANSIONS_MASK, UF::constants::CHARACTER_CREATION_LIMIT, UF::constants::SAY_LINK_BODY_SIZE, + UF::INULL, UF::INULL ), /*[ClientVersion::RoF] =*/ @@ -102,6 +108,7 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers RoF::constants::EXPANSIONS_MASK, RoF::constants::CHARACTER_CREATION_LIMIT, RoF::constants::SAY_LINK_BODY_SIZE, + RoF::INULL, RoF::INULL ), /*[ClientVersion::RoF2] =*/ @@ -111,7 +118,8 @@ static const EQ::constants::LookupEntry constants_static_lookup_entries[EQ::vers RoF2::constants::EXPANSIONS_MASK, RoF2::constants::CHARACTER_CREATION_LIMIT, RoF2::constants::SAY_LINK_BODY_SIZE, - RoF2::constants::MAX_BAZAAR_TRADERS + RoF2::constants::MAX_BAZAAR_TRADERS, + RoF2::constants::MAX_BAZAAR_TRANSACTION ) }; diff --git a/common/eq_limits.h b/common/eq_limits.h index 6c00e411e..ab10edce3 100644 --- a/common/eq_limits.h +++ b/common/eq_limits.h @@ -43,6 +43,7 @@ namespace EQ int16 CharacterCreationLimit; size_t SayLinkBodySize; uint32 BazaarTraderLimit; + uint32 BazaarMaxTransaction; LookupEntry(const LookupEntry *lookup_entry) { } LookupEntry( @@ -51,14 +52,16 @@ namespace EQ uint32 ExpansionsMask, int16 CharacterCreationLimit, size_t SayLinkBodySize, - uint32 BazaarTraderLimit + uint32 BazaarTraderLimit, + uint32 BazaarMaxTransaction ) : Expansion(Expansion), ExpansionBit(ExpansionBit), ExpansionsMask(ExpansionsMask), CharacterCreationLimit(CharacterCreationLimit), SayLinkBodySize(SayLinkBodySize), - BazaarTraderLimit(BazaarTraderLimit) + BazaarTraderLimit(BazaarTraderLimit), + BazaarMaxTransaction(BazaarMaxTransaction) { } }; diff --git a/common/servertalk.h b/common/servertalk.h index cfc0d49df..2180d9c85 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -369,12 +369,13 @@ enum { }; enum { - BazaarPurchaseFailed = 0, - BazaarPurchaseSuccess = 1, - BazaarPurchaseSellerFailed = 2, - BazaarPurchaseSellerSuccess = 3, - BazaarPurchaseBuyerFailed = 4, - BazaarPurchaseBuyerSuccess = 5 + BazaarPurchaseFailed = 0, + BazaarPurchaseSuccess = 1, + BazaarPurchaseBuyerCompleteSendToSeller = 2, + BazaarPurchaseSellerCompleteSendToBuyer = 3, + BazaarPurchaseBuyerFailed = 4, + BazaarPurchaseBuyerSuccess = 5, + BazaarPurchaseTraderFailed = 6 }; /************ PACKET RELATED STRUCT ************/ class ServerPacket @@ -1783,10 +1784,13 @@ struct BazaarPurchaseMessaging_Struct { uint32 item_aug_5; uint32 item_aug_6; uint32 buyer_id; - uint32 item_charges; + uint32 item_quantity; + int16 item_charges; uint32 id; uint32 trader_zone_id; uint32 trader_zone_instance_id; + uint32 buyer_zone_id; + uint32 buyer_zone_instance_id; uint32 transaction_status; }; diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index f428aa87b..4a372cb79 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1674,21 +1674,23 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } case ServerOP_BazaarPurchase: { - auto in = (BazaarPurchaseMessaging_Struct *)pack->pBuffer; - if (in->trader_buy_struct.trader_id <= 0) { - LogTrading( - "World Message [{}] received with invalid trader_id [{}]", - "ServerOP_BazaarPurchase", - in->trader_buy_struct.trader_id - ); - return; + auto in = reinterpret_cast(pack->pBuffer); + switch (in->transaction_status) { + case BazaarPurchaseBuyerCompleteSendToSeller: { + zoneserver_list.SendPacket(in->trader_zone_id, in->trader_zone_instance_id, pack); + break; + } + case BazaarPurchaseTraderFailed: + case BazaarPurchaseSuccess: { + zoneserver_list.SendPacket(in->buyer_zone_id, in->buyer_zone_instance_id, pack); + break; + } + default: { + LogError( + "ServerOP_BazaarPurchase received with no corresponding action for [{}]", + in->transaction_status); + } } - - auto trader = ClientList::Instance()->FindCLEByCharacterID(in->trader_buy_struct.trader_id); - if (trader) { - ZSList::Instance()->SendPacket(in->trader_zone_id, in->trader_zone_instance_id, pack); - } - break; } case ServerOP_BuyerMessaging: { diff --git a/zone/trading.cpp b/zone/trading.cpp index 473ebc5cf..f082d2865 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1462,11 +1462,11 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) } uint64 total_transaction_value = static_cast(in->price) * static_cast(quantity); - if (total_transaction_value > RoF2::constants::MAX_BAZAAR_TRANSACTION) { + if (total_transaction_value > EQ::constants::StaticLookup(ClientVersion())->BazaarMaxTransaction) { Message( Chat::Red, "That would exceed the single transaction limit of %u platinum.", - RoF2::constants::MAX_BAZAAR_TRANSACTION / 1000 + EQ::constants::StaticLookup(ClientVersion())->BazaarMaxTransaction / 1000 ); TradeRequestFailed(app); return; @@ -2853,9 +2853,9 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) TraderRepository::UpdateActiveTransaction(database, trader_item.id, true); uint32 quantity = in->quantity; - int16 charges = 1; - auto item = database.GetItem(trader_item.item_id); + auto item = database.GetItem(trader_item.item_id); + int16 charges = 1; if (trader_item.item_charges > 0 || item->Stackable || item->MaxCharges > 0) { charges = trader_item.item_charges; } @@ -2870,12 +2870,12 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) in->item_unique_id ); - uint64 total_cost = static_cast(in->price) * static_cast(in->quantity); - if (total_cost > RoF2::constants::MAX_BAZAAR_TRANSACTION) { + uint64 total_cost = static_cast(in->price) * static_cast(quantity); + if (total_cost > EQ::constants::StaticLookup(ClientVersion())->BazaarMaxTransaction) { Message( Chat::Red, "That would exceed the single transaction limit of %u platinum.", - RoF2::constants::MAX_BAZAAR_TRANSACTION / 1000 + EQ::constants::StaticLookup(ClientVersion())->BazaarMaxTransaction / 1000 ); TraderRepository::UpdateActiveTransaction(database, trader_item.id, false); TradeRequestFailed(app); @@ -2895,114 +2895,10 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) LogTrading("Customer [{}] Paid: [{}] to trader [{}]", CharacterID(), DetermineMoneyString(total_cost), trader_item.character_id); LogTradingDetail("Step 2:Bazaar Purchase. Took [{}] from Buyer [{}] ", DetermineMoneyString(total_cost), CharacterID()); - if (buy_item && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_PURCHASE)) { - auto e = PlayerEvent::TraderPurchaseEvent{ - .item_id = trader_item.item_id, - .augment_1_id = trader_item.augment_one, - .augment_2_id = trader_item.augment_two, - .augment_3_id = trader_item.augment_three, - .augment_4_id = trader_item.augment_four, - .augment_5_id = trader_item.augment_five, - .augment_6_id = trader_item.augment_six, - .item_name = in->item_name, - .trader_id = in->trader_id, - .trader_name = in->seller_name, - .price = in->price, - .quantity = in->quantity, - .charges = trader_item.item_charges, - .total_cost = total_cost, - .player_money_balance = GetCarriedMoney(), - }; - - RecordPlayerEventLog(PlayerEvent::TRADER_PURCHASE, e); - } - - CharacterParcelsRepository::CharacterParcels parcel_out{}; - parcel_out.from_name = in->seller_name; - parcel_out.note = "Delivered from a Bazaar Purchase"; - parcel_out.sent_date = time(nullptr); - parcel_out.quantity = charges; - parcel_out.item_id = trader_item.item_id; - parcel_out.aug_slot_1 = trader_item.augment_one; - parcel_out.aug_slot_2 = trader_item.augment_two; - parcel_out.aug_slot_3 = trader_item.augment_three; - parcel_out.aug_slot_4 = trader_item.augment_four; - parcel_out.aug_slot_5 = trader_item.augment_five; - parcel_out.aug_slot_6 = trader_item.augment_six; - parcel_out.char_id = CharacterID(); - parcel_out.slot_id = next_slot; - parcel_out.id = 0; - - auto result = CharacterParcelsRepository::InsertOne(database, parcel_out); - if (!result.id) { - LogError("Failed to add parcel to database. From {} to {} item {} quantity {}", - parcel_out.from_name, - GetCleanName(), - parcel_out.item_id, - parcel_out.quantity - ); - Message(Chat::Yellow, "Unable to save parcel to the database. Please contact an administrator."); - in->method = BazaarByParcel; - in->sub_action = Failed; - TraderRepository::UpdateActiveTransaction(database, trader_item.id, false); - AddMoneyToPP(total_cost + fee); - TradeRequestFailed(app); - return; - } - - ReturnTraderReq(app, in->quantity, trader_item.item_id); - if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::PARCEL_SEND)) { - PlayerEvent::ParcelSend e{}; - e.from_player_name = parcel_out.from_name; - e.to_player_name = GetCleanName(); - e.item_id = parcel_out.item_id; - e.augment_1_id = parcel_out.aug_slot_1; - e.augment_2_id = parcel_out.aug_slot_2; - e.augment_3_id = parcel_out.aug_slot_3; - e.augment_4_id = parcel_out.aug_slot_4; - e.augment_5_id = parcel_out.aug_slot_5; - e.augment_6_id = parcel_out.aug_slot_6; - e.quantity = in->quantity; - e.charges = trader_item.item_charges; - e.sent_date = parcel_out.sent_date; - - RecordPlayerEventLog(PlayerEvent::PARCEL_SEND, e); - } - - Parcel_Struct ps{}; - ps.item_slot = parcel_out.slot_id; - strn0cpy(ps.send_to, GetCleanName(), sizeof(ps.send_to)); - SendParcelDeliveryToWorld(ps); - - LogTradingDetail("Step 3:Bazaar Purchase. Sent parcel to Buyer [{}] Item ID [{}] Quantity [{}] Charges [{}]", - CharacterID(), - trader_item.item_id, - quantity, - charges - ); - - if (item->Stackable && quantity != charges) { - TraderRepository::UpdateQuantity(database, in->item_unique_id, trader_item.item_charges - quantity); - LogTradingDetail( - "Step 4a:Bazaar Purchase. Decreased database id {} from [{}] to [{}] charges", - trader_item.item_id, - trader_item.item_charges, - charges - ); - } - else { - TraderRepository::DeleteOne(database, trader_item.id); - LogTradingDetail( - "Step 4b:Bazaar Purchase. Deleted database id [{}] because database quantity [{}] equals [{}] purchased quantity", - trader_item.id, - trader_item.item_charges, - charges - ); - } - auto out_server = std::make_unique(ServerOP_BazaarPurchase, sizeof(BazaarPurchaseMessaging_Struct)); - auto out_data = (BazaarPurchaseMessaging_Struct *) out_server->pBuffer; + auto out_data = reinterpret_cast(out_server->pBuffer); + out_data->transaction_status = BazaarPurchaseBuyerCompleteSendToSeller; out_data->trader_buy_struct.action = in->action; out_data->trader_buy_struct.method = in->method; out_data->trader_buy_struct.already_sold = in->already_sold; @@ -3010,7 +2906,7 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) out_data->trader_buy_struct.price = in->price; out_data->trader_buy_struct.quantity = in->quantity; out_data->trader_buy_struct.sub_action = in->sub_action; - out_data->trader_buy_struct.trader_id = in->trader_id; + out_data->trader_buy_struct.trader_id = trader_item.character_id; out_data->buyer_id = CharacterID(); out_data->item_aug_1 = trader_item.augment_one; out_data->item_aug_2 = trader_item.augment_two; @@ -3018,10 +2914,13 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) out_data->item_aug_4 = trader_item.augment_four; out_data->item_aug_5 = trader_item.augment_five; out_data->item_aug_6 = trader_item.augment_six; - out_data->item_charges = trader_item.item_charges; + out_data->item_quantity = quantity; + out_data->item_charges = charges; out_data->id = trader_item.id; out_data->trader_zone_id = trader_item.char_zone_id; out_data->trader_zone_instance_id = trader_item.char_zone_instance_id; + out_data->buyer_zone_id = GetZoneID(); + out_data->buyer_zone_instance_id = GetInstanceID(); strn0cpy(out_data->trader_buy_struct.buyer_name, GetCleanName(), sizeof(out_data->trader_buy_struct.buyer_name)); strn0cpy(out_data->trader_buy_struct.seller_name, in->seller_name, sizeof(out_data->trader_buy_struct.seller_name)); strn0cpy(out_data->trader_buy_struct.item_name, in->item_name, sizeof(out_data->trader_buy_struct.item_name)); diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index d17bfbfb7..4be8a4dbe 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -3774,62 +3774,226 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) break; } case ServerOP_BazaarPurchase: { - auto in = (BazaarPurchaseMessaging_Struct *) pack->pBuffer; - auto trader_pc = entity_list.GetClientByCharID(in->trader_buy_struct.trader_id); - if (!trader_pc) { - LogTrading("Request trader_id [{}] could not be found in zone_id [{}]", - in->trader_buy_struct.trader_id, - zone->GetZoneID() - ); - return; - } + auto in = reinterpret_cast(pack->pBuffer); + switch (in->transaction_status) { + case BazaarPurchaseBuyerCompleteSendToSeller: { + auto trader_pc = entity_list.GetClientByCharID(in->trader_buy_struct.trader_id); + if (!trader_pc) { + LogTrading( + "Request trader_id [{}] could not be found in zone_id [{}]", + in->trader_buy_struct.trader_id, + zone->GetZoneID()); + return; + } - if (trader_pc->IsThereACustomer()) { - auto customer = entity_list.GetClientByID(trader_pc->GetCustomerID()); - if (customer) { - customer->CancelTraderTradeWindow(); + auto item = trader_pc->FindTraderItemByUniqueID(in->trader_buy_struct.item_unique_id); + if (!item) { + in->transaction_status = BazaarPurchaseTraderFailed; + TraderRepository::UpdateActiveTransaction(database, in->id, false); + worldserver.SendPacket(pack); + break; + } + + //if there is a customer currently browsing, close to ensure no conflict of purchase + if (trader_pc->IsThereACustomer()) { + auto customer = entity_list.GetClientByID(trader_pc->GetCustomerID()); + if (customer) { + customer->CancelTraderTradeWindow(); + } + } + + //Update the trader's db entries + if (item->IsStackable() && in->item_quantity != in->item_charges) { + TraderRepository::UpdateQuantity(database, in->trader_buy_struct.item_unique_id, item->GetCharges() - in->item_quantity); + LogTradingDetail( + "Step 4a:Bazaar Purchase. Decreased database id {} from [{}] to [{}] charges", + in->trader_buy_struct.item_id, + in->item_charges, + in->item_charges + ); + } + else { + TraderRepository::DeleteOne(database, in->trader_buy_struct.item_id); + LogTradingDetail( + "Step 4b:Bazaar Purchase. Deleted database id [{}] because database quantity [{}] equals [{}] purchased quantity", + in->trader_buy_struct.item_id, + in->item_charges, + in->item_charges + ); + } + + //at this time, buyer checks ok, seller checks ok. + //perform actions to trader + uint64 total_cost = static_cast(in->trader_buy_struct.price) * static_cast(in->item_quantity); + if (!trader_pc->RemoveItemByItemUniqueId(in->trader_buy_struct.item_unique_id, in->item_quantity)) { + in->transaction_status = BazaarPurchaseTraderFailed; + TraderRepository::UpdateActiveTransaction(database, in->id, false); + worldserver.SendPacket(pack); + break; + } + + trader_pc->AddMoneyToPP(total_cost, true); + + //Update the trader to indicate the sale has completed + EQApplicationPacket outapp(OP_Trader, sizeof(TraderBuy_Struct)); + auto data = reinterpret_cast(outapp.pBuffer); + + memcpy(data, &in->trader_buy_struct, sizeof(TraderBuy_Struct)); + trader_pc->QueuePacket(&outapp); + + if (item && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_SELL)) { + auto e = PlayerEvent::TraderSellEvent{ + .item_id = item->GetID(), + .augment_1_id = item->GetAugmentItemID(0), + .augment_2_id = item->GetAugmentItemID(1), + .augment_3_id = item->GetAugmentItemID(2), + .augment_4_id = item->GetAugmentItemID(3), + .augment_5_id = item->GetAugmentItemID(4), + .augment_6_id = item->GetAugmentItemID(5), + .item_name = in->trader_buy_struct.item_name, + .buyer_id = in->buyer_id, + .buyer_name = in->trader_buy_struct.buyer_name, + .price = in->trader_buy_struct.price, + .quantity = in->item_quantity, + .charges = in->item_charges, + .total_cost = total_cost, + .player_money_balance = trader_pc->GetCarriedMoney(), + }; + RecordPlayerEventLogWithClient(trader_pc, PlayerEvent::TRADER_SELL, e); + } + + in->transaction_status = BazaarPurchaseSuccess; + TraderRepository::UpdateActiveTransaction(database, in->id, false); + worldserver.SendPacket(pack); + + break; + } + case BazaarPurchaseTraderFailed: { + auto buyer = entity_list.GetClientByCharID(in->buyer_id); + if (!buyer) { + LogTrading( + "Requested buyer_id [{}] could not be found in zone_id [{}] instance_id [{}]", + in->trader_buy_struct.trader_id, + zone->GetZoneID(), + zone->GetInstanceID() + ); + return; + } + + // return buyer's money including the fee + uint64 total_cost = + static_cast(in->trader_buy_struct.price) * static_cast(in->item_quantity); + uint64 fee = std::round(total_cost * RuleR(Bazaar, ParcelDeliveryCostMod)); + buyer->AddMoneyToPP(total_cost + fee, false); + buyer->SendMoneyUpdate(); + + break; + } + case BazaarPurchaseSuccess: { + auto buyer = entity_list.GetClientByCharID(in->buyer_id); + if (!buyer) { + LogTrading( + "Requested buyer_id [{}] could not be found in zone_id [{}] instance_id [{}]", + in->trader_buy_struct.trader_id, + zone->GetZoneID(), + zone->GetInstanceID() + ); + return; + } + uint64 total_cost = + static_cast(in->trader_buy_struct.price) * static_cast(in->item_quantity); + + if (player_event_logs.IsEventEnabled(PlayerEvent::TRADER_PURCHASE)) { + auto e = PlayerEvent::TraderPurchaseEvent{ + .item_id = in->trader_buy_struct.item_id, + .augment_1_id = in->item_aug_1, + .augment_2_id = in->item_aug_2, + .augment_3_id = in->item_aug_3, + .augment_4_id = in->item_aug_4, + .augment_5_id = in->item_aug_5, + .augment_6_id = in->item_aug_6, + .item_name = in->trader_buy_struct.item_name, + .trader_id = in->trader_buy_struct.trader_id, + .trader_name = in->trader_buy_struct.seller_name, + .price = in->trader_buy_struct.price, + .quantity = in->item_quantity, + .charges = in->item_charges, + .total_cost = total_cost, + .player_money_balance = buyer->GetCarriedMoney(), + }; + + RecordPlayerEventLogWithClient(buyer, PlayerEvent::TRADER_PURCHASE, e); + } + + auto item = database.GetItem(in->trader_buy_struct.item_id); + auto quantity = in->item_quantity; + if (item->MaxCharges > 0) { + quantity = in->item_charges; + } + + //Send the item via parcel + CharacterParcelsRepository::CharacterParcels parcel_out{}; + parcel_out.from_name = in->trader_buy_struct.seller_name; + parcel_out.note = "Delivered from a Bazaar Purchase"; + parcel_out.sent_date = time(nullptr); + parcel_out.quantity = quantity; + parcel_out.item_id = in->trader_buy_struct.item_id; + parcel_out.aug_slot_1 = in->item_aug_1; + parcel_out.aug_slot_2 = in->item_aug_2; + parcel_out.aug_slot_3 = in->item_aug_3; + parcel_out.aug_slot_4 = in->item_aug_4; + parcel_out.aug_slot_5 = in->item_aug_5; + parcel_out.aug_slot_6 = in->item_aug_6; + parcel_out.char_id = buyer->CharacterID(); + parcel_out.slot_id = buyer->FindNextFreeParcelSlot(buyer->CharacterID()); + parcel_out.id = 0; + + CharacterParcelsRepository::InsertOne(database, parcel_out); + + if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) { + PlayerEvent::ParcelSend e{}; + e.from_player_name = parcel_out.from_name; + e.to_player_name = buyer->GetCleanName(); + e.item_id = parcel_out.item_id; + e.augment_1_id = parcel_out.aug_slot_1; + e.augment_2_id = parcel_out.aug_slot_2; + e.augment_3_id = parcel_out.aug_slot_3; + e.augment_4_id = parcel_out.aug_slot_4; + e.augment_5_id = parcel_out.aug_slot_5; + e.augment_6_id = parcel_out.aug_slot_6; + e.quantity = in->item_quantity; + e.charges = in->item_charges; + e.sent_date = parcel_out.sent_date; + + RecordPlayerEventLogWithClient(buyer, PlayerEvent::PARCEL_SEND, e); + } + + Parcel_Struct ps{}; + ps.item_slot = parcel_out.slot_id; + strn0cpy(ps.send_to, buyer->GetCleanName(), sizeof(ps.send_to)); + buyer->SendParcelDeliveryToWorld(ps); + + LogTradingDetail( + "Step 3:Bazaar Purchase. Sent parcel to Buyer [{}] Item ID [{}] Quantity [{}] Charges [{}]", + buyer->CharacterID(), + in->trader_buy_struct.item_id, + in->item_quantity, + in->item_charges + ); + + //Update the buyer to indicate the sale has completed + EQApplicationPacket outapp(OP_Trader, sizeof(TraderBuy_Struct)); + auto data = reinterpret_cast(outapp.pBuffer); + + memcpy(data, &in->trader_buy_struct, sizeof(TraderBuy_Struct)); + buyer->ReturnTraderReq(&outapp, in->item_quantity, in->trader_buy_struct.item_id); + + break; + } + default: { } } - auto sn = std::string(in->trader_buy_struct.item_unique_id); - auto outapp = std::make_unique(OP_Trader, static_cast(sizeof(TraderBuy_Struct))); - auto data = (TraderBuy_Struct *) outapp->pBuffer; - - memcpy(data, &in->trader_buy_struct, sizeof(TraderBuy_Struct)); - - if (trader_pc->ClientVersion() < EQ::versions::ClientVersion::RoF) { - data->price = in->trader_buy_struct.price * in->trader_buy_struct.quantity; - } - - TraderRepository::UpdateActiveTransaction(database, in->id, false); - - auto item = trader_pc->FindTraderItemBySerialNumber(sn); - - if (item && PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_SELL)) { - auto e = PlayerEvent::TraderSellEvent{ - .item_id = item ? item->GetID() : 0, - .augment_1_id = item->GetAugmentItemID(0), - .augment_2_id = item->GetAugmentItemID(1), - .augment_3_id = item->GetAugmentItemID(2), - .augment_4_id = item->GetAugmentItemID(3), - .augment_5_id = item->GetAugmentItemID(4), - .augment_6_id = item->GetAugmentItemID(5), - .item_name = in->trader_buy_struct.item_name, - .buyer_id = in->buyer_id, - .buyer_name = in->trader_buy_struct.buyer_name, - .price = in->trader_buy_struct.price, - .quantity = in->trader_buy_struct.quantity, - .charges = item ? item->IsStackable() ? 1 : item->GetCharges() : 0, - .total_cost = (in->trader_buy_struct.price * in->trader_buy_struct.quantity), - .player_money_balance = trader_pc->GetCarriedMoney(), - }; - RecordPlayerEventLogWithClient(trader_pc, PlayerEvent::TRADER_SELL, e); - } - - trader_pc->RemoveItemByItemUniqueId(sn, in->trader_buy_struct.quantity); - trader_pc->AddMoneyToPP(in->trader_buy_struct.price * in->trader_buy_struct.quantity, true); - trader_pc->QueuePacket(outapp.get()); - break; } case ServerOP_BuyerMessaging: { From 898a26d4e52be168ee891fce22e39f4236af7130 Mon Sep 17 00:00:00 2001 From: neckkola <65987027+neckkola@users.noreply.github.com> Date: Sat, 19 Apr 2025 10:03:54 -0300 Subject: [PATCH 24/48] WIP --- common/eq_limits.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/eq_limits.h b/common/eq_limits.h index ab10edce3..7058c8122 100644 --- a/common/eq_limits.h +++ b/common/eq_limits.h @@ -43,7 +43,7 @@ namespace EQ int16 CharacterCreationLimit; size_t SayLinkBodySize; uint32 BazaarTraderLimit; - uint32 BazaarMaxTransaction; + uint64 BazaarMaxTransaction; LookupEntry(const LookupEntry *lookup_entry) { } LookupEntry( @@ -53,7 +53,7 @@ namespace EQ int16 CharacterCreationLimit, size_t SayLinkBodySize, uint32 BazaarTraderLimit, - uint32 BazaarMaxTransaction + uint64 BazaarMaxTransaction ) : Expansion(Expansion), ExpansionBit(ExpansionBit), From d87d1dfb1db0224bb544866f573616e54a9ad951 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sat, 19 Apr 2025 21:22:48 -0300 Subject: [PATCH 25/48] wip --- zone/trading.cpp | 17 ++++++---- zone/worldserver.cpp | 78 +++++++++++++++++++++++++++++--------------- 2 files changed, 61 insertions(+), 34 deletions(-) diff --git a/zone/trading.cpp b/zone/trading.cpp index f082d2865..17ba6c9ea 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -2860,7 +2860,6 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) charges = trader_item.item_charges; } - LogTrading("Name: [{}] Requested Quantity: [{}] Charges: [{}]", in->item_name, quantity, charges); LogTradingDetail( "Step 1:Bazaar Purchase. Buyer [{}] Seller [{}] Quantity [{}] Charges [{}] Item_Unique_ID [{}]", CharacterID(), @@ -2892,8 +2891,15 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) } Message(Chat::Red, fmt::format("You paid {} for the parcel delivery.", DetermineMoneyString(fee)).c_str()); - LogTrading("Customer [{}] Paid: [{}] to trader [{}]", CharacterID(), DetermineMoneyString(total_cost), trader_item.character_id); - LogTradingDetail("Step 2:Bazaar Purchase. Took [{}] from Buyer [{}] ", DetermineMoneyString(total_cost), CharacterID()); + SendMoneyUpdate(); + + LogTradingDetail("Step 2:Bazaar Purchase. Took [{}] from Buyer [{}] for purchase of [{}] {}{}", + DetermineMoneyString(total_cost), + CharacterID(), + quantity, + quantity > 1 ? fmt::format("{}s", in->item_name) : in->item_name, + item->MaxCharges > 0 ? fmt::format(" with charges of [{}]", charges) : std::string("") + ); auto out_server = std::make_unique(ServerOP_BazaarPurchase, sizeof(BazaarPurchaseMessaging_Struct)); auto out_data = reinterpret_cast(out_server->pBuffer); @@ -2931,7 +2937,7 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) ); worldserver.SendPacket(out_server.get()); - LogTradingDetail("Step 5:Bazaar Purchase. Send bazaar messaging data to world.\n" + LogTradingDetail("Step 3:Bazaar Purchase. Buyer checks passed, sending bazaar messaging data to trader via world.\n" "Action: {} \n" "Sub Action: {} \n" "Method: {} \n" @@ -2978,9 +2984,6 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) out_data->trader_zone_instance_id, out_data->buyer_id, out_data->trader_buy_struct.buyer_name); - - SendMoneyUpdate(); - LogTradingDetail("Step 6:Bazaar Purchase. Send money update to client {}. Buyer Actions complete.", CharacterID()); } void Client::SetBuyerWelcomeMessage(const char *welcome_message) diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 4be8a4dbe..f662c2534 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -3780,9 +3780,11 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) auto trader_pc = entity_list.GetClientByCharID(in->trader_buy_struct.trader_id); if (!trader_pc) { LogTrading( - "Request trader_id [{}] could not be found in zone_id [{}]", + "Request trader_id [{}] could not be found in zone_id [{}] instance_id [{}]", in->trader_buy_struct.trader_id, - zone->GetZoneID()); + zone->GetZoneID(), + zone->GetInstanceID() + ); return; } @@ -3806,19 +3808,19 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) if (item->IsStackable() && in->item_quantity != in->item_charges) { TraderRepository::UpdateQuantity(database, in->trader_buy_struct.item_unique_id, item->GetCharges() - in->item_quantity); LogTradingDetail( - "Step 4a:Bazaar Purchase. Decreased database id {} from [{}] to [{}] charges", + "Step 4:Bazaar Purchase. Decreased database id {} from [{}] to [{}] charges", in->trader_buy_struct.item_id, - in->item_charges, - in->item_charges + item->GetCharges(), + item->GetCharges() - in->item_quantity ); } else { TraderRepository::DeleteOne(database, in->trader_buy_struct.item_id); LogTradingDetail( - "Step 4b:Bazaar Purchase. Deleted database id [{}] because database quantity [{}] equals [{}] purchased quantity", + "Step 4:Bazaar Purchase. Deleted database id [{}] because database quantity [{}] equals [{}] purchased quantity", in->trader_buy_struct.item_id, - in->item_charges, - in->item_charges + item->GetCharges(), + item->GetCharges() - in->item_quantity ); } @@ -3826,12 +3828,28 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) //perform actions to trader uint64 total_cost = static_cast(in->trader_buy_struct.price) * static_cast(in->item_quantity); if (!trader_pc->RemoveItemByItemUniqueId(in->trader_buy_struct.item_unique_id, in->item_quantity)) { + LogTradingDetail( + "Failed to remove item {} quantity [{}] from trader [{}]", + in->trader_buy_struct.item_unique_id, + in->item_quantity, + trader_pc->CharacterID() + ); in->transaction_status = BazaarPurchaseTraderFailed; TraderRepository::UpdateActiveTransaction(database, in->id, false); worldserver.SendPacket(pack); break; } + LogTradingDetail( + "Step 5:Bazaar Purchase. Removed from inventory of Trader [{}] for sale of [{}] {}{}", + trader_pc->CharacterID(), + in->item_quantity, + in->item_quantity > 1 ? fmt::format("{}s", in->trader_buy_struct.item_name) + : in->trader_buy_struct.item_name, + item->GetItem()->MaxCharges > 0 ? fmt::format(" with charges of [{}]", in->item_charges) + : std::string("") + ); + trader_pc->AddMoneyToPP(total_cost, true); //Update the trader to indicate the sale has completed @@ -3866,6 +3884,8 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) TraderRepository::UpdateActiveTransaction(database, in->id, false); worldserver.SendPacket(pack); + LogTradingDetail("Step 6:Bazaar Purchase. Purchase checks complete for trader. Send Success to buyer via world."); + break; } case BazaarPurchaseTraderFailed: { @@ -3887,6 +3907,9 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) buyer->AddMoneyToPP(total_cost + fee, false); buyer->SendMoneyUpdate(); + buyer->Message(Chat::Red, "Bazaar purchased failed. Returning your money."); + LogTradingDetail("Bazaar Purchase Failed. Returning money [{}] to Buyer [{}]", total_cost + fee, buyer->CharacterID()); + break; } case BazaarPurchaseSuccess: { @@ -3933,20 +3956,21 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) //Send the item via parcel CharacterParcelsRepository::CharacterParcels parcel_out{}; - parcel_out.from_name = in->trader_buy_struct.seller_name; - parcel_out.note = "Delivered from a Bazaar Purchase"; - parcel_out.sent_date = time(nullptr); - parcel_out.quantity = quantity; - parcel_out.item_id = in->trader_buy_struct.item_id; - parcel_out.aug_slot_1 = in->item_aug_1; - parcel_out.aug_slot_2 = in->item_aug_2; - parcel_out.aug_slot_3 = in->item_aug_3; - parcel_out.aug_slot_4 = in->item_aug_4; - parcel_out.aug_slot_5 = in->item_aug_5; - parcel_out.aug_slot_6 = in->item_aug_6; - parcel_out.char_id = buyer->CharacterID(); - parcel_out.slot_id = buyer->FindNextFreeParcelSlot(buyer->CharacterID()); - parcel_out.id = 0; + parcel_out.from_name = in->trader_buy_struct.seller_name; + parcel_out.note = "Delivered from a Bazaar Purchase"; + parcel_out.sent_date = time(nullptr); + parcel_out.quantity = quantity; + parcel_out.item_id = in->trader_buy_struct.item_id; + parcel_out.item_unique_id = in->trader_buy_struct.item_unique_id; + parcel_out.aug_slot_1 = in->item_aug_1; + parcel_out.aug_slot_2 = in->item_aug_2; + parcel_out.aug_slot_3 = in->item_aug_3; + parcel_out.aug_slot_4 = in->item_aug_4; + parcel_out.aug_slot_5 = in->item_aug_5; + parcel_out.aug_slot_6 = in->item_aug_6; + parcel_out.char_id = buyer->CharacterID(); + parcel_out.slot_id = buyer->FindNextFreeParcelSlot(buyer->CharacterID()); + parcel_out.id = 0; CharacterParcelsRepository::InsertOne(database, parcel_out); @@ -3973,12 +3997,11 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) strn0cpy(ps.send_to, buyer->GetCleanName(), sizeof(ps.send_to)); buyer->SendParcelDeliveryToWorld(ps); - LogTradingDetail( - "Step 3:Bazaar Purchase. Sent parcel to Buyer [{}] Item ID [{}] Quantity [{}] Charges [{}]", + LogTradingDetail("Step 7:Bazaar Purchase. Sent parcel to Buyer [{}] for purchase of [{}] {}{}", buyer->CharacterID(), - in->trader_buy_struct.item_id, - in->item_quantity, - in->item_charges + quantity, + quantity > 1 ? fmt::format("{}s", in->trader_buy_struct.item_name) : in->trader_buy_struct.item_name, + item->MaxCharges > 0 ? fmt::format(" with charges of [{}]", in->item_charges) : std::string("") ); //Update the buyer to indicate the sale has completed @@ -3987,6 +4010,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) memcpy(data, &in->trader_buy_struct, sizeof(TraderBuy_Struct)); buyer->ReturnTraderReq(&outapp, in->item_quantity, in->trader_buy_struct.item_id); + LogTradingDetail("Step 8:Bazaar Purchase. Purchase complete. Sending update packet to buyer."); break; } From 3c4cc74a54432b8fe5f0820d38434271c6c6029c Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Fri, 25 Apr 2025 23:34:18 -0300 Subject: [PATCH 26/48] Move conversion to world --- common/database.cpp | 19 +++++++++++++++++++ common/database.h | 1 + common/shareddb.cpp | 2 ++ loginserver/main.cpp | 3 +++ world/world_boot.cpp | 2 ++ 5 files changed, 27 insertions(+) diff --git a/common/database.cpp b/common/database.cpp index 3052fd722..63982568b 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -2288,3 +2288,22 @@ uint64_t Database::GetNextTableId(const std::string &table_name) return 1; } + +void Database::ConvertInventoryToNewUniqueId() +{ + LogInfo("Converting inventory entries with NULL item_unique_id"); + auto results = InventoryRepository::GetWhere(*this, "`item_unique_id` IS NULL"); + + if (results.empty()) { + return; + } + + TransactionBegin(); + for (auto &r: results) { + r.item_unique_id = EQ::UniqueHashGenerator::generate(); + } + + InventoryRepository::ReplaceMany(*this, results); + TransactionCommit(); + LogInfo("Converted {} records", results.size()); +} \ No newline at end of file diff --git a/common/database.h b/common/database.h index 9b81f66a0..3a372eec9 100644 --- a/common/database.h +++ b/common/database.h @@ -278,6 +278,7 @@ public: void Decode(std::string &in); uint64_t GetNextTableId(const std::string& table_name); + void ConvertInventoryToNewUniqueId(); private: Mutex Mvarcache; diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 95ff53cdc..26f7a38f7 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -717,6 +717,8 @@ bool SharedDatabase::GetInventory(Client *c) inst->SetOrnamentationIDFile(ornament_idfile); inst->SetOrnamentHeroModel(item->HerosForgeModel); + //Mass conversion handled by world + //This remains as a backup. Should not be required. if (row.item_unique_id.empty()) { inst->CreateUniqueID(); row.item_unique_id = inst->GetUniqueID(); diff --git a/loginserver/main.cpp b/loginserver/main.cpp index 9047db8c8..a3181a0b7 100644 --- a/loginserver/main.cpp +++ b/loginserver/main.cpp @@ -15,6 +15,9 @@ #include "../common/database.h" #include "../common/events/player_event_logs.h" #include "../common/zone_store.h" +#include "../common/evolving_items.h" +#include "../common/content/world_content_service.h" + #include #include #include diff --git a/world/world_boot.cpp b/world/world_boot.cpp index 19667927c..ee13ab7a3 100644 --- a/world/world_boot.cpp +++ b/world/world_boot.cpp @@ -295,6 +295,8 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv) database.ClearBuyerDetails(); LogInfo("Clearing buyer table details"); + database.ConvertInventoryToNewUniqueId(); + if (RuleB(Bots, Enabled)) { LogInfo("Clearing [bot_pet_buffs] table of stale entries"); database.QueryDatabase( From 2174f41505fca38b63f61721b2fd9abfd2053540 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sat, 26 Apr 2025 17:21:36 -0300 Subject: [PATCH 27/48] Update database.cpp --- common/database.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/common/database.cpp b/common/database.cpp index 63982568b..28d7eafce 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -2299,11 +2299,26 @@ void Database::ConvertInventoryToNewUniqueId() } TransactionBegin(); + uint32 index = 0; + uint32 batch_size = 1000; + std::vector queue{}; + queue.reserve(batch_size); + for (auto &r: results) { r.item_unique_id = EQ::UniqueHashGenerator::generate(); + queue.push_back(r); + index++; + if (index >= batch_size) { + InventoryRepository::ReplaceMany(*this, queue); + index = 0; + queue.clear(); + } + } + + if (!queue.empty()) { + InventoryRepository::ReplaceMany(*this, queue); } - InventoryRepository::ReplaceMany(*this, results); TransactionCommit(); LogInfo("Converted {} records", results.size()); } \ No newline at end of file From 8c6a99dc180ba251946783f697e263f94cb1c18f Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 27 Apr 2025 11:19:31 -0300 Subject: [PATCH 28/48] Testing --- common/database.cpp | 4 ++-- zone/client.h | 1 + zone/trading.cpp | 50 +++++++++++++++++++++++++++++++------------- zone/worldserver.cpp | 11 +++++++--- 4 files changed, 47 insertions(+), 19 deletions(-) diff --git a/common/database.cpp b/common/database.cpp index 28d7eafce..51c0c6429 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -2300,7 +2300,7 @@ void Database::ConvertInventoryToNewUniqueId() TransactionBegin(); uint32 index = 0; - uint32 batch_size = 1000; + const uint32 batch_size = 1000; std::vector queue{}; queue.reserve(batch_size); @@ -2321,4 +2321,4 @@ void Database::ConvertInventoryToNewUniqueId() TransactionCommit(); LogInfo("Converted {} records", results.size()); -} \ No newline at end of file +} diff --git a/zone/client.h b/zone/client.h index b17922b06..6a5ab378b 100644 --- a/zone/client.h +++ b/zone/client.h @@ -382,6 +382,7 @@ public: void NukeTraderItem(uint16 slot, int16 charges, int16 quantity, Client* customer, uint16 trader_slot, const std::string &serial_number, int32 item_id = 0); void ReturnTraderReq(const EQApplicationPacket* app,int16 traderitemcharges, uint32 itemid = 0); void TradeRequestFailed(const EQApplicationPacket* app); + void TradeRequestFailed(TraderBuy_Struct &in); void BuyTraderItem(const EQApplicationPacket* app); void BuyTraderItemFromBazaarWindow(const EQApplicationPacket* app); void FinishTrade( diff --git a/zone/trading.cpp b/zone/trading.cpp index 17ba6c9ea..bf7546f1d 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1381,22 +1381,43 @@ void Client::TradeRequestFailed(const EQApplicationPacket *app) safe_delete(outapp); } -static void BazaarAuditTrail(const char *seller, const char *buyer, const char *itemName, int quantity, int totalCost, int tranType) { +void Client::TradeRequestFailed(TraderBuy_Struct &in) +{ + auto outapp = EQApplicationPacket(OP_TraderBuy, sizeof(TraderBuy_Struct)); + auto data = reinterpret_cast(outapp.pBuffer); - const std::string& query = fmt::format( - "INSERT INTO `trader_audit` " - "(`time`, `seller`, `buyer`, `itemname`, `quantity`, `totalcost`, `trantype`) " - "VALUES (NOW(), '{}', '{}', '{}', {}, {}, {})", - seller, - buyer, - Strings::Escape(itemName), - quantity, - totalCost, - tranType - ); - database.QueryDatabase(query); + data->method = in.method; + data->action = in.action; + data->sub_action = Failed; + data->already_sold = 0xFFFFFFFF; + data->item_id = in.item_id; + data->price = in.price; + data->quantity = in.quantity; + data->trader_id = 0xFFFFFFFF; + strn0cpy(data->buyer_name, in.buyer_name, sizeof(data->buyer_name)); + strn0cpy(data->item_name, in.item_name, sizeof(data->item_name)); + strn0cpy(data->item_unique_id, in.item_unique_id, sizeof(data->item_unique_id)); + strn0cpy(data->seller_name, in.seller_name, sizeof(data->seller_name)); + + QueuePacket(&outapp); } +// static void BazaarAuditTrail(const char *seller, const char *buyer, const char *itemName, int quantity, int totalCost, int tranType) { +// +// const std::string& query = fmt::format( +// "INSERT INTO `trader_audit` " +// "(`time`, `seller`, `buyer`, `itemname`, `quantity`, `totalcost`, `trantype`) " +// "VALUES (NOW(), '{}', '{}', '{}', {}, {}, {})", +// seller, +// buyer, +// Strings::Escape(itemName), +// quantity, +// totalCost, +// tranType +// ); +// database.QueryDatabase(query); +// } + void Client::BuyTraderItem(const EQApplicationPacket *app) { auto in = reinterpret_cast(app->pBuffer); @@ -2893,8 +2914,9 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) Message(Chat::Red, fmt::format("You paid {} for the parcel delivery.", DetermineMoneyString(fee)).c_str()); SendMoneyUpdate(); - LogTradingDetail("Step 2:Bazaar Purchase. Took [{}] from Buyer [{}] for purchase of [{}] {}{}", + LogTradingDetail("Step 2:Bazaar Purchase. Took [{}] {}from Buyer [{}] for purchase of [{}] {}{}", DetermineMoneyString(total_cost), + fee > 0 ? fmt::format("plus a fee of [{}] ", fee) : std::string(""), CharacterID(), quantity, quantity > 1 ? fmt::format("{}s", in->item_name) : in->item_name, diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index f662c2534..4c01e7a38 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -3815,7 +3815,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) ); } else { - TraderRepository::DeleteOne(database, in->trader_buy_struct.item_id); + TraderRepository::DeleteOne(database, in->id); LogTradingDetail( "Step 4:Bazaar Purchase. Deleted database id [{}] because database quantity [{}] equals [{}] purchased quantity", in->trader_buy_struct.item_id, @@ -3908,8 +3908,13 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) buyer->SendMoneyUpdate(); buyer->Message(Chat::Red, "Bazaar purchased failed. Returning your money."); - LogTradingDetail("Bazaar Purchase Failed. Returning money [{}] to Buyer [{}]", total_cost + fee, buyer->CharacterID()); - + LogTradingDetail( + "Bazaar Purchase Failed. Returning money [{}] + fee [{}] to Buyer [{}]", + total_cost, + fee, + buyer->CharacterID() + ); + buyer->TradeRequestFailed(in->trader_buy_struct); break; } case BazaarPurchaseSuccess: { From a81ec11ea31dce215211d43f08f32bc910399432 Mon Sep 17 00:00:00 2001 From: neckkola <65987027+neckkola@users.noreply.github.com> Date: Sun, 27 Apr 2025 13:34:37 -0300 Subject: [PATCH 29/48] WIP for world item unique id for CharSelect Screen --- common/item_instance.cpp | 25 ++++++++++++++++++++++++- common/item_instance.h | 2 ++ common/shareddb.cpp | 4 ++-- common/shareddb.h | 2 +- world/worlddb.cpp | 2 +- 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/common/item_instance.cpp b/common/item_instance.cpp index c3a246936..37c826920 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -89,6 +89,29 @@ EQ::ItemInstance::ItemInstance(const ItemData* item, int16 charges) m_SerialNumber = GetNextItemInstSerialNumber(); } +EQ::ItemInstance::ItemInstance(const ItemData *item, const std::string &item_unique_id, int16 charges) +{ + if (item) { + m_item = new ItemData(*item); + } + + m_charges = charges; + + if (m_item && m_item->IsClassCommon()) { + m_color = m_item->Color; + } + + if (m_item && IsEvolving()) { + SetTimer("evolve", RuleI(EvolvingItems, DelayUponEquipping)); + } + + m_SerialNumber = GetNextItemInstSerialNumber(); + + if (m_item && !item_unique_id.empty()) { + SetUniqueID(item_unique_id); + } +} + EQ::ItemInstance::ItemInstance(SharedDatabase *db, uint32 item_id, int16 charges) { m_item = db->GetItem(item_id); @@ -2019,4 +2042,4 @@ std::string EQ::ItemInstance::GenerateUniqueID() LogInventoryDetail("Generated an item serial number {}", unique_hash); return unique_hash; -} \ No newline at end of file +} diff --git a/common/item_instance.h b/common/item_instance.h index 22f25b6ad..501635d67 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -85,6 +85,8 @@ namespace EQ ItemInstance(SharedDatabase *db, uint32 item_id, int16 charges = 0); + ItemInstance(const ItemData *item, const std::string &item_unique_id, int16 charges = 0); + ItemInstance(ItemInstTypes use_type); ItemInstance(const ItemInstance& copy); diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 26f7a38f7..2d805cbfe 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -1413,7 +1413,7 @@ EQ::ItemInstance* SharedDatabase::CreateItem( return inst; } -EQ::ItemInstance* SharedDatabase::CreateBaseItem(const EQ::ItemData* item, int16 charges) { +EQ::ItemInstance* SharedDatabase::CreateBaseItem(const EQ::ItemData* item, int16 charges, const std::string &item_unique_id) { EQ::ItemInstance* inst = nullptr; if (item) { // if maxcharges is -1 that means it is an unlimited use item. @@ -1427,7 +1427,7 @@ EQ::ItemInstance* SharedDatabase::CreateBaseItem(const EQ::ItemData* item, int16 charges = 1; } - inst = new EQ::ItemInstance(item, charges); + inst = new EQ::ItemInstance(item, item_unique_id, charges); if (!inst) { LogError("Error: valid item data returned a null reference for EQ::ItemInstance creation in SharedDatabase::CreateBaseItem()"); diff --git a/common/shareddb.h b/common/shareddb.h index 2a78be670..6366075b3 100644 --- a/common/shareddb.h +++ b/common/shareddb.h @@ -155,7 +155,7 @@ public: uint32 ornamentidfile = 0, uint32 ornament_hero_model = 0 ); - EQ::ItemInstance *CreateBaseItem(const EQ::ItemData *item, int16 charges = 0); + EQ::ItemInstance *CreateBaseItem(const EQ::ItemData *item, int16 charges = 0, const std::string &item_unique_id = ""); void GetItemsCount(int32& item_count, uint32& max_id); void LoadItems(void *data, uint32 size, int32 items, uint32 max_item_id); diff --git a/world/worlddb.cpp b/world/worlddb.cpp index f47cdb608..f899a4ba6 100644 --- a/world/worlddb.cpp +++ b/world/worlddb.cpp @@ -875,7 +875,7 @@ bool WorldDatabase::GetCharSelInventory( continue; } - EQ::ItemInstance *inst = content_db.CreateBaseItem(item, e.charges); + EQ::ItemInstance *inst = content_db.CreateBaseItem(item, e.charges, e.item_unique_id); if (!inst) { continue; From cf3b9638c97dabda0a32f146991c0aa082005bce Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sat, 22 Mar 2025 08:39:53 -0300 Subject: [PATCH 30/48] Phase 1 Offline Trading Cleanup and testing Zone updated builds ok World updated builds ok Update guild_base.h --- common/CMakeLists.txt | 2 + common/database.cpp | 9 +- common/database.h | 2 +- common/database/database_update_manifest.cpp | 26 + common/database_schema.h | 2 + common/emu_constants.h | 5 + common/emu_oplist.h | 3 + common/eq_packet_structs.h | 14 +- common/events/player_events.h | 9 +- common/guild_base.cpp | 7 +- common/guild_base.h | 21 +- common/patches/rof2.cpp | 26 +- common/patches/rof2_structs.h | 45 +- common/repositories/account_repository.h | 39 ++ .../base/base_account_repository.h | 52 +- ...haracter_offline_transactions_repository.h | 451 ++++++++++++++++++ common/repositories/buyer_repository.h | 29 +- ...haracter_offline_transactions_repository.h | 53 ++ common/repositories/trader_repository.h | 43 ++ common/servertalk.h | 27 +- loginserver/client.cpp | 43 ++ loginserver/client.h | 1 + loginserver/login_types.h | 8 +- loginserver/login_util/login_opcodes_sod.conf | 2 + loginserver/world_server.cpp | 120 +++++ loginserver/world_server.h | 1 + loginserver/world_server_manager.cpp | 32 ++ loginserver/world_server_manager.h | 5 + utils/patches/patch_RoF2.conf | 3 + world/cliententry.cpp | 11 + world/cliententry.h | 51 +- world/clientlist.cpp | 80 +++- world/console.cpp | 2 +- world/login_server.cpp | 164 +++++-- world/login_server.h | 1 + world/zonelist.cpp | 12 + world/zonelist.h | 1 + world/zoneserver.cpp | 29 +- zone/api_service.cpp | 2 +- zone/client.cpp | 33 +- zone/client.h | 72 ++- zone/client_packet.cpp | 100 +++- zone/client_packet.h | 1 + zone/client_process.cpp | 12 +- zone/entity.cpp | 28 +- zone/guild_mgr.cpp | 40 +- zone/guild_mgr.h | 2 +- zone/mob.h | 99 ++++ zone/trading.cpp | 64 ++- zone/worldserver.cpp | 122 +++++ 50 files changed, 1777 insertions(+), 229 deletions(-) create mode 100644 common/repositories/base/base_character_offline_transactions_repository.h create mode 100644 common/repositories/character_offline_transactions_repository.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 1a9028eb7..40d06e735 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -186,6 +186,7 @@ SET(repositories repositories/base/base_character_leadership_abilities_repository.h repositories/base/base_character_material_repository.h repositories/base/base_character_memmed_spells_repository.h + repositories/base/base_character_offline_transactions_repository.h repositories/base/base_character_parcels_repository.h repositories/base/base_character_parcels_containers_repository.h repositories/base/base_character_peqzone_flags_repository.h @@ -382,6 +383,7 @@ SET(repositories repositories/character_leadership_abilities_repository.h repositories/character_material_repository.h repositories/character_memmed_spells_repository.h + repositories/character_offline_transactions_repository.h repositories/character_parcels_repository.h repositories/character_parcels_containers_repository.h repositories/character_peqzone_flags_repository.h diff --git a/common/database.cpp b/common/database.cpp index 51c0c6429..d60f3ce60 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -210,7 +210,7 @@ void Database::LoginIP(uint32 account_id, const std::string& login_ip) QueryDatabase(query); } -int16 Database::GetAccountStatus(uint32 account_id) +AccountStatus::StatusRecord Database::GetAccountStatus(uint32 account_id) { auto e = AccountRepository::FindOne(*this, account_id); @@ -222,7 +222,11 @@ int16 Database::GetAccountStatus(uint32 account_id) AccountRepository::UpdateOne(*this, e); } - return e.status; + AccountStatus::StatusRecord result{}; + result.status = e.status; + result.offline = e.offline; + + return result; } uint32 Database::CreateAccount( @@ -2265,6 +2269,7 @@ void Database::ClearGuildOnlineStatus() void Database::ClearTraderDetails() { TraderRepository::Truncate(*this); + AccountRepository::ClearAllOfflineStatus(*this); } void Database::ClearBuyerDetails() diff --git a/common/database.h b/common/database.h index 3a372eec9..accbb55ff 100644 --- a/common/database.h +++ b/common/database.h @@ -172,8 +172,8 @@ public: const std::string GetLiveChar(uint32 account_id); bool SetAccountStatus(const std::string& account_name, int16 status); bool SetLocalPassword(uint32 account_id, const std::string& password); + AccountStatus::StatusRecord GetAccountStatus(uint32 account_id); bool UpdateLiveChar(const std::string& name, uint32 account_id); - int16 GetAccountStatus(uint32 account_id); void SetAccountCRCField(uint32 account_id, const std::string& field_name, uint64 checksum); uint32 CheckLogin(const std::string& name, const std::string& password, const std::string& loginserver, int16* status = 0); uint32 CreateAccount( diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 98e8128b6..01c8d5228 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -7240,6 +7240,32 @@ ALTER TABLE `trader` )", .content_schema_update = false }, + ManifestEntry{ + .version = 9325, + .description = "2025_01_27_offline_account_status.sql", + .check = "SHOW COLUMNS FROM `account` LIKE 'offline'", + .condition = "empty", + .match = "", + .sql = R"( + ALTER TABLE `account` + ADD COLUMN `offline` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `time_creation`; + + CREATE TABLE `character_offline_transactions` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `character_id` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `type` INT(10) UNSIGNED NULL DEFAULT '0', + `item_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci', + `quantity` INT(11) NULL DEFAULT '0', + `price` BIGINT(20) UNSIGNED NULL DEFAULT '0', + `buyer_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_character_id` (`character_id`) + ) + COLLATE='latin1_swedish_ci' + ENGINE=InnoDB; + )", + .content_schema_update = false + }, // -- template; copy/paste this when you need to create a new entry // ManifestEntry{ // .version = 9228, diff --git a/common/database_schema.h b/common/database_schema.h index de6f8472f..d606a96b1 100644 --- a/common/database_schema.h +++ b/common/database_schema.h @@ -59,6 +59,7 @@ namespace DatabaseSchema { {"character_leadership_abilities", "id"}, {"character_material", "id"}, {"character_memmed_spells", "id"}, + {"character_offline_transactions", "character_id"}, {"character_parcels", "char_id"}, {"character_parcels_containers", "id"}, {"character_pet_buffs", "char_id"}, @@ -134,6 +135,7 @@ namespace DatabaseSchema { "character_leadership_abilities", "character_material", "character_memmed_spells", + "character_offline_transactions", "character_parcels", "character_parcels_containers", "character_pet_buffs", diff --git a/common/emu_constants.h b/common/emu_constants.h index 0c762ffb8..c95a8f62e 100644 --- a/common/emu_constants.h +++ b/common/emu_constants.h @@ -46,6 +46,11 @@ namespace AccountStatus { constexpr uint8 Max = 255; std::string GetName(uint8 account_status); + + struct StatusRecord { + int16 status; + uint32 offline; + }; } static std::map account_status_names = { diff --git a/common/emu_oplist.h b/common/emu_oplist.h index 268fa97cd..7e753a307 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -71,6 +71,8 @@ N(OP_BuyerItems), N(OP_CameraEffect), N(OP_Camp), N(OP_CancelSneakHide), +N(OP_CancelOfflineTrader), +N(OP_CancelOfflineTraderResponse), N(OP_CancelTask), N(OP_CancelTrade), N(OP_CashReward), @@ -381,6 +383,7 @@ N(OP_MultiLineMsg), N(OP_NewSpawn), N(OP_NewTitlesAvailable), N(OP_NewZone), +N(OP_Offline), N(OP_OnLevelMessage), N(OP_OpenContainer), N(OP_OpenDiscordMerchant), diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 71495ac37..8f2c1d08d 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -326,6 +326,7 @@ union bool buyer; bool untargetable; uint32 npc_tint_id; + bool offline; }; struct PlayerState_Struct { @@ -3913,12 +3914,12 @@ struct SimpleMessage_Struct{ }; struct GuildMemberUpdate_Struct { -/*00*/ uint32 GuildID; -/*04*/ char MemberName[64]; -/*68*/ uint16 ZoneID; -/*70*/ uint16 InstanceID; //speculated -/*72*/ uint32 LastSeen; //unix timestamp -/*76*/ + /*00*/ uint32 GuildID; + /*04*/ char MemberName[64]; + /*68*/ uint16 ZoneID; + /*72*/ uint16 InstanceID; //speculated + /*76*/ uint32 LastSeen; //unix timestamp + /*80*/ uint32 offline_mode; }; struct GuildMemberLevelUpdate_Struct { @@ -3941,6 +3942,7 @@ struct Internal_GuildMemberEntry_Struct { uint16 zoneinstance; //network byte order uint16 zone_id; //network byte order uint32 online; + uint32 offline_mode; }; struct Internal_GuildMembers_Struct { //just for display purposes, this is not actually used in the message encoding. diff --git a/common/events/player_events.h b/common/events/player_events.h index 4a3850cac..2ca03c0cc 100644 --- a/common/events/player_events.h +++ b/common/events/player_events.h @@ -1098,6 +1098,7 @@ namespace PlayerEvent { int32 charges; uint64 total_cost; uint64 player_money_balance; + bool offline_purchase; // cereal template @@ -1153,7 +1154,8 @@ namespace PlayerEvent { CEREAL_NVP(quantity), CEREAL_NVP(charges), CEREAL_NVP(total_cost), - CEREAL_NVP(player_money_balance) + CEREAL_NVP(player_money_balance), + CEREAL_NVP(offline_purchase) ); } }; @@ -1174,7 +1176,9 @@ namespace PlayerEvent { int32 charges; uint64 total_cost; uint64 player_money_balance; + bool offline_purchase; + // cereal template void serialize(Archive& ar) { @@ -1228,7 +1232,8 @@ namespace PlayerEvent { CEREAL_NVP(quantity), CEREAL_NVP(charges), CEREAL_NVP(total_cost), - CEREAL_NVP(player_money_balance) + CEREAL_NVP(player_money_balance), + CEREAL_NVP(offline_purchase) ); } }; diff --git a/common/guild_base.cpp b/common/guild_base.cpp index 3f34c3625..1e8be7228 100644 --- a/common/guild_base.cpp +++ b/common/guild_base.cpp @@ -844,8 +844,10 @@ bool BaseGuildManager::QueryWithLogging(std::string query, const char *errmsg) #define GuildMemberBaseQuery \ "SELECT c.`id`, c.`name`, c.`class`, c.`level`, c.`last_login`, c.`zone_id`," \ " g.`guild_id`, g.`rank`, g.`tribute_enable`, g.`total_tribute`, g.`last_tribute`," \ -" g.`banker`, g.`public_note`, g.`alt`, g.`online` " \ -" FROM `character_data` AS c LEFT JOIN `guild_members` AS g ON c.`id` = g.`char_id` " +" g.`banker`, g.`public_note`, g.`alt`, g.`online`, a.`offline` " \ +" FROM `character_data` AS c LEFT JOIN `guild_members` AS g ON c.`id` = g.`char_id` " \ +" LEFT JOIN `account` AS a ON a.`id` = c.`account_id` " + static void ProcessGuildMember(MySQLRequestRow row, CharGuildInfo &into) { //fields from `characer_` @@ -866,6 +868,7 @@ static void ProcessGuildMember(MySQLRequestRow row, CharGuildInfo &into) into.public_note = row[12] ? row[12] : ""; into.alt = row[13] ? (row[13][0] == '0' ? false : true) : false; into.online = row[14] ? (row[14][0] == '0' ? false : true) : false; + into.offline_mode = row[15] ? (row[15][0] == '0' ? false : true) : false; //a little sanity checking/cleanup if (into.guild_id == 0) { diff --git a/common/guild_base.h b/common/guild_base.h index 0cd4fe5f8..76887bb9c 100644 --- a/common/guild_base.h +++ b/common/guild_base.h @@ -55,16 +55,17 @@ class CharGuildInfo uint32 time_last_on; uint32 zone_id; - //fields from `guild_members` - uint32 guild_id; - uint8 rank; - bool tribute_enable; - uint32 total_tribute; - uint32 last_tribute; //timestamp - bool banker; - bool alt; - std::string public_note; - bool online; + // fields from `guild_members` + uint32 guild_id; + uint8 rank; + bool tribute_enable; + uint32 total_tribute; + uint32 last_tribute; // timestamp + bool banker; + bool alt; + std::string public_note; + bool online; + bool offline_mode; }; //this object holds guild functionality shared between world and zone. diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 407c021df..ce2891995 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -1845,7 +1845,7 @@ namespace RoF2 e->zoneinstance = 0; e->zone_id = htons(emu_e->zone_id); e->unknown_one2 = htonl(1); - e->unknown04 = 0; + e->offline_mode = htonl(emu_e->offline_mode); #undef SlideStructString #undef PutFieldN @@ -1862,14 +1862,12 @@ namespace RoF2 { SETUP_DIRECT_ENCODE(GuildMemberUpdate_Struct, structs::GuildMemberUpdate_Struct); - OUT(GuildID); - memcpy(eq->MemberName, emu->MemberName, sizeof(eq->MemberName)); - //OUT(ZoneID); - //OUT(InstanceID); - eq->InstanceID = emu->InstanceID; - eq->ZoneID = emu->ZoneID; - OUT(LastSeen); - eq->Unknown76 = 0; + eq->guild_id = emu->GuildID; + eq->last_seen = emu->LastSeen; + eq->instance_id = emu->InstanceID; + eq->zone_id = emu->ZoneID; + eq->offline_mode = emu->offline_mode; + memcpy(eq->member_name, emu->MemberName, sizeof(eq->member_name)); FINISH_ENCODE(); } @@ -4475,6 +4473,7 @@ namespace RoF2 *p = nullptr; char *InBuffer = (char *)in->pBuffer; + std::vector p_ids { 0x430, 0x420 }; WhoAllReturnStruct *wars = (WhoAllReturnStruct*)InBuffer; @@ -4500,8 +4499,9 @@ namespace RoF2 x = VARSTRUCT_DECODE_TYPE(uint32, InBuffer); VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, x); - InBuffer += 4; - VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); + x = VARSTRUCT_DECODE_TYPE(uint32, InBuffer); + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, std::ranges::find(p_ids.begin(), p_ids.end(), x) == p_ids.end() ? 0 : x); + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0xffffffff); char Name[64]; @@ -4737,6 +4737,10 @@ namespace RoF2 OtherData = OtherData | 0x01; } + if (emu->offline) { + OtherData = OtherData | 0x02; + } + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, OtherData); // float EmitterScalingRadius diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 745ecc470..6b5f85215 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -3681,22 +3681,22 @@ struct SimpleMessage_Struct{ // Size: 52 + strings // Other than the strings, all of this packet is network byte order (reverse from normal) struct GuildMemberEntry_Struct { - char name[1]; // variable length - uint32 level; - uint32 banker; // 1=yes, 0=no - uint32 class_; - uint32 rank; - uint32 time_last_on; - uint32 tribute_enable; - uint32 unknown01; // Seen 0 - uint32 total_tribute; // total guild tribute donated, network byte order - uint32 last_tribute; // unix timestamp - uint32 unknown_one; // unknown, set to 1 - char public_note[1]; // variable length. - uint16 zoneinstance; // Seen 0s or -1 in RoF2 - uint16 zone_id; // Seen 0s or -1 in RoF2 - uint32 unknown_one2; // unknown, set to 1 - uint32 unknown04; // Seen 0 + char name[1]; // variable length + uint32 level; + uint32 banker; // 1=yes, 0=no + uint32 class_; + uint32 rank; + uint32 time_last_on; + uint32 tribute_enable; + uint32 unknown01; // Seen 0 + uint32 total_tribute; // total guild tribute donated, network byte order + uint32 last_tribute; // unix timestamp + uint32 unknown_one; // unknown, set to 1 + char public_note[1]; // variable length. + uint16 zoneinstance; // Seen 0s or -1 in RoF2 + uint16 zone_id; // Seen 0s or -1 in RoF2 + uint32 unknown_one2; // unknown, set to 1 + uint32 offline_mode; // Displays OFFLINE MODE instead of Zone Name }; //just for display purposes, this is not actually used in the message encoding other than for size. @@ -3731,13 +3731,12 @@ struct GuildStatus_Struct }; struct GuildMemberUpdate_Struct { -/*00*/ uint32 GuildID; -/*04*/ char MemberName[64]; -/*68*/ uint16 ZoneID; -/*70*/ uint16 InstanceID; //speculated -/*72*/ uint32 LastSeen; //unix timestamp -/*76*/ uint32 Unknown76; -/*80*/ + /*00*/ uint32 guild_id; + /*04*/ char member_name[64]; + /*68*/ uint16 zone_id; + /*70*/ uint16 instance_id; //speculated + /*72*/ uint32 last_seen; //unix timestamp + /*76*/ uint32 offline_mode; }; struct GuildMemberLevelUpdate_Struct { diff --git a/common/repositories/account_repository.h b/common/repositories/account_repository.h index 5f2b08581..9f269abb6 100644 --- a/common/repositories/account_repository.h +++ b/common/repositories/account_repository.h @@ -107,6 +107,45 @@ public: return AccountRepository::UpdateOne(db, e); } + + static void SetOfflineStatus(Database& db, const uint32 account_id, bool offline_status) + { + auto account = FindOne(db, account_id); + if (!account.id) { + return; + } + + account.offline = offline_status; + UpdateOne(db, account); + } + + static void ClearAllOfflineStatus(Database& db) + { + auto query = fmt::format("UPDATE {} SET `offline` = '0' WHERE `offline` = '1';", + TableName() + ); + + db.QueryDatabase(query); + } + + static bool GetAllOfflineStatus(Database& db, const uint32 character_id) + { + auto query = fmt::format("SELECT a.`offline` " + "FROM `account` AS a " + "INNER JOIN character_data AS c ON c.account_id = a.id " + "WHERE c.id = '{}'", + character_id + ); + auto results = db.QueryDatabase(query); + if (!results.Success() || !results.RowCount()) { + return false; + } + + auto row = results.begin(); + bool const status = static_cast(Strings::ToInt(row[0])); + + return status; + } }; #endif //EQEMU_ACCOUNT_REPOSITORY_H diff --git a/common/repositories/base/base_account_repository.h b/common/repositories/base/base_account_repository.h index 33b198f0e..d07603f89 100644 --- a/common/repositories/base/base_account_repository.h +++ b/common/repositories/base/base_account_repository.h @@ -39,6 +39,7 @@ public: uint8_t rulesflag; time_t suspendeduntil; uint32_t time_creation; + uint8_t offline; std::string ban_reason; std::string suspend_reason; std::string crc_eqgame; @@ -74,6 +75,7 @@ public: "rulesflag", "suspendeduntil", "time_creation", + "offline", "ban_reason", "suspend_reason", "crc_eqgame", @@ -105,6 +107,7 @@ public: "rulesflag", "UNIX_TIMESTAMP(suspendeduntil)", "time_creation", + "offline", "ban_reason", "suspend_reason", "crc_eqgame", @@ -170,6 +173,7 @@ public: e.rulesflag = 0; e.suspendeduntil = 0; e.time_creation = 0; + e.offline = 0; e.ban_reason = ""; e.suspend_reason = ""; e.crc_eqgame = ""; @@ -231,11 +235,12 @@ public: e.rulesflag = row[17] ? static_cast(strtoul(row[17], nullptr, 10)) : 0; e.suspendeduntil = strtoll(row[18] ? row[18] : "-1", nullptr, 10); e.time_creation = row[19] ? static_cast(strtoul(row[19], nullptr, 10)) : 0; - e.ban_reason = row[20] ? row[20] : ""; - e.suspend_reason = row[21] ? row[21] : ""; - e.crc_eqgame = row[22] ? row[22] : ""; - e.crc_skillcaps = row[23] ? row[23] : ""; - e.crc_basedata = row[24] ? row[24] : ""; + e.offline = row[20] ? static_cast(strtoul(row[20], nullptr, 10)) : 0; + e.ban_reason = row[21] ? row[21] : ""; + e.suspend_reason = row[22] ? row[22] : ""; + e.crc_eqgame = row[23] ? row[23] : ""; + e.crc_skillcaps = row[24] ? row[24] : ""; + e.crc_basedata = row[25] ? row[25] : ""; return e; } @@ -288,11 +293,12 @@ public: v.push_back(columns[17] + " = " + std::to_string(e.rulesflag)); v.push_back(columns[18] + " = FROM_UNIXTIME(" + (e.suspendeduntil > 0 ? std::to_string(e.suspendeduntil) : "null") + ")"); v.push_back(columns[19] + " = " + std::to_string(e.time_creation)); - v.push_back(columns[20] + " = '" + Strings::Escape(e.ban_reason) + "'"); - v.push_back(columns[21] + " = '" + Strings::Escape(e.suspend_reason) + "'"); - v.push_back(columns[22] + " = '" + Strings::Escape(e.crc_eqgame) + "'"); - v.push_back(columns[23] + " = '" + Strings::Escape(e.crc_skillcaps) + "'"); - v.push_back(columns[24] + " = '" + Strings::Escape(e.crc_basedata) + "'"); + v.push_back(columns[20] + " = " + std::to_string(e.offline)); + v.push_back(columns[21] + " = '" + Strings::Escape(e.ban_reason) + "'"); + v.push_back(columns[22] + " = '" + Strings::Escape(e.suspend_reason) + "'"); + v.push_back(columns[23] + " = '" + Strings::Escape(e.crc_eqgame) + "'"); + v.push_back(columns[24] + " = '" + Strings::Escape(e.crc_skillcaps) + "'"); + v.push_back(columns[25] + " = '" + Strings::Escape(e.crc_basedata) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -334,6 +340,7 @@ public: v.push_back(std::to_string(e.rulesflag)); v.push_back("FROM_UNIXTIME(" + (e.suspendeduntil > 0 ? std::to_string(e.suspendeduntil) : "null") + ")"); v.push_back(std::to_string(e.time_creation)); + v.push_back(std::to_string(e.offline)); v.push_back("'" + Strings::Escape(e.ban_reason) + "'"); v.push_back("'" + Strings::Escape(e.suspend_reason) + "'"); v.push_back("'" + Strings::Escape(e.crc_eqgame) + "'"); @@ -388,6 +395,7 @@ public: v.push_back(std::to_string(e.rulesflag)); v.push_back("FROM_UNIXTIME(" + (e.suspendeduntil > 0 ? std::to_string(e.suspendeduntil) : "null") + ")"); v.push_back(std::to_string(e.time_creation)); + v.push_back(std::to_string(e.offline)); v.push_back("'" + Strings::Escape(e.ban_reason) + "'"); v.push_back("'" + Strings::Escape(e.suspend_reason) + "'"); v.push_back("'" + Strings::Escape(e.crc_eqgame) + "'"); @@ -446,11 +454,12 @@ public: e.rulesflag = row[17] ? static_cast(strtoul(row[17], nullptr, 10)) : 0; e.suspendeduntil = strtoll(row[18] ? row[18] : "-1", nullptr, 10); e.time_creation = row[19] ? static_cast(strtoul(row[19], nullptr, 10)) : 0; - e.ban_reason = row[20] ? row[20] : ""; - e.suspend_reason = row[21] ? row[21] : ""; - e.crc_eqgame = row[22] ? row[22] : ""; - e.crc_skillcaps = row[23] ? row[23] : ""; - e.crc_basedata = row[24] ? row[24] : ""; + e.offline = row[20] ? static_cast(strtoul(row[20], nullptr, 10)) : 0; + e.ban_reason = row[21] ? row[21] : ""; + e.suspend_reason = row[22] ? row[22] : ""; + e.crc_eqgame = row[23] ? row[23] : ""; + e.crc_skillcaps = row[24] ? row[24] : ""; + e.crc_basedata = row[25] ? row[25] : ""; all_entries.push_back(e); } @@ -495,11 +504,12 @@ public: e.rulesflag = row[17] ? static_cast(strtoul(row[17], nullptr, 10)) : 0; e.suspendeduntil = strtoll(row[18] ? row[18] : "-1", nullptr, 10); e.time_creation = row[19] ? static_cast(strtoul(row[19], nullptr, 10)) : 0; - e.ban_reason = row[20] ? row[20] : ""; - e.suspend_reason = row[21] ? row[21] : ""; - e.crc_eqgame = row[22] ? row[22] : ""; - e.crc_skillcaps = row[23] ? row[23] : ""; - e.crc_basedata = row[24] ? row[24] : ""; + e.offline = row[20] ? static_cast(strtoul(row[20], nullptr, 10)) : 0; + e.ban_reason = row[21] ? row[21] : ""; + e.suspend_reason = row[22] ? row[22] : ""; + e.crc_eqgame = row[23] ? row[23] : ""; + e.crc_skillcaps = row[24] ? row[24] : ""; + e.crc_basedata = row[25] ? row[25] : ""; all_entries.push_back(e); } @@ -594,6 +604,7 @@ public: v.push_back(std::to_string(e.rulesflag)); v.push_back("FROM_UNIXTIME(" + (e.suspendeduntil > 0 ? std::to_string(e.suspendeduntil) : "null") + ")"); v.push_back(std::to_string(e.time_creation)); + v.push_back(std::to_string(e.offline)); v.push_back("'" + Strings::Escape(e.ban_reason) + "'"); v.push_back("'" + Strings::Escape(e.suspend_reason) + "'"); v.push_back("'" + Strings::Escape(e.crc_eqgame) + "'"); @@ -641,6 +652,7 @@ public: v.push_back(std::to_string(e.rulesflag)); v.push_back("FROM_UNIXTIME(" + (e.suspendeduntil > 0 ? std::to_string(e.suspendeduntil) : "null") + ")"); v.push_back(std::to_string(e.time_creation)); + v.push_back(std::to_string(e.offline)); v.push_back("'" + Strings::Escape(e.ban_reason) + "'"); v.push_back("'" + Strings::Escape(e.suspend_reason) + "'"); v.push_back("'" + Strings::Escape(e.crc_eqgame) + "'"); diff --git a/common/repositories/base/base_character_offline_transactions_repository.h b/common/repositories/base/base_character_offline_transactions_repository.h new file mode 100644 index 000000000..64141e6d0 --- /dev/null +++ b/common/repositories/base/base_character_offline_transactions_repository.h @@ -0,0 +1,451 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_CHARACTER_OFFLINE_TRANSACTIONS_REPOSITORY_H +#define EQEMU_BASE_CHARACTER_OFFLINE_TRANSACTIONS_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BaseCharacterOfflineTransactionsRepository { +public: + struct CharacterOfflineTransactions { + uint64_t id; + uint32_t character_id; + uint32_t type; + std::string item_name; + int32_t quantity; + uint64_t price; + std::string buyer_name; + }; + + static std::string PrimaryKey() + { + return std::string("id"); + } + + static std::vector Columns() + { + return { + "id", + "character_id", + "type", + "item_name", + "quantity", + "price", + "buyer_name", + }; + } + + static std::vector SelectColumns() + { + return { + "id", + "character_id", + "type", + "item_name", + "quantity", + "price", + "buyer_name", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("character_offline_transactions"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static CharacterOfflineTransactions NewEntity() + { + CharacterOfflineTransactions e{}; + + e.id = 0; + e.character_id = 0; + e.type = 0; + e.item_name = ""; + e.quantity = 0; + e.price = 0; + e.buyer_name = ""; + + return e; + } + + static CharacterOfflineTransactions GetCharacterOfflineTransactions( + const std::vector &character_offline_transactionss, + int character_offline_transactions_id + ) + { + for (auto &character_offline_transactions : character_offline_transactionss) { + if (character_offline_transactions.id == character_offline_transactions_id) { + return character_offline_transactions; + } + } + + return NewEntity(); + } + + static CharacterOfflineTransactions FindOne( + Database& db, + int character_offline_transactions_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + character_offline_transactions_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + CharacterOfflineTransactions e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.type = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_name = row[3] ? row[3] : ""; + e.quantity = row[4] ? static_cast(atoi(row[4])) : 0; + e.price = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.buyer_name = row[6] ? row[6] : ""; + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int character_offline_transactions_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + character_offline_transactions_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const CharacterOfflineTransactions &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[1] + " = " + std::to_string(e.character_id)); + v.push_back(columns[2] + " = " + std::to_string(e.type)); + v.push_back(columns[3] + " = '" + Strings::Escape(e.item_name) + "'"); + v.push_back(columns[4] + " = " + std::to_string(e.quantity)); + v.push_back(columns[5] + " = " + std::to_string(e.price)); + v.push_back(columns[6] + " = '" + Strings::Escape(e.buyer_name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static CharacterOfflineTransactions InsertOne( + Database& db, + CharacterOfflineTransactions e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.type)); + v.push_back("'" + Strings::Escape(e.item_name) + "'"); + v.push_back(std::to_string(e.quantity)); + v.push_back(std::to_string(e.price)); + v.push_back("'" + Strings::Escape(e.buyer_name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.type)); + v.push_back("'" + Strings::Escape(e.item_name) + "'"); + v.push_back(std::to_string(e.quantity)); + v.push_back(std::to_string(e.price)); + v.push_back("'" + Strings::Escape(e.buyer_name) + "'"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CharacterOfflineTransactions e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.type = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_name = row[3] ? row[3] : ""; + e.quantity = row[4] ? static_cast(atoi(row[4])) : 0; + e.price = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.buyer_name = row[6] ? row[6] : ""; + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CharacterOfflineTransactions e{}; + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.type = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.item_name = row[3] ? row[3] : ""; + e.quantity = row[4] ? static_cast(atoi(row[4])) : 0; + e.price = row[5] ? strtoull(row[5], nullptr, 10) : 0; + e.buyer_name = row[6] ? row[6] : ""; + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const CharacterOfflineTransactions &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.type)); + v.push_back("'" + Strings::Escape(e.item_name) + "'"); + v.push_back(std::to_string(e.quantity)); + v.push_back(std::to_string(e.price)); + v.push_back("'" + Strings::Escape(e.buyer_name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.id)); + v.push_back(std::to_string(e.character_id)); + v.push_back(std::to_string(e.type)); + v.push_back("'" + Strings::Escape(e.item_name) + "'"); + v.push_back(std::to_string(e.quantity)); + v.push_back(std::to_string(e.price)); + v.push_back("'" + Strings::Escape(e.buyer_name) + "'"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_CHARACTER_OFFLINE_TRANSACTIONS_REPOSITORY_H diff --git a/common/repositories/buyer_repository.h b/common/repositories/buyer_repository.h index 2d402b0dc..579228456 100644 --- a/common/repositories/buyer_repository.h +++ b/common/repositories/buyer_repository.h @@ -106,8 +106,13 @@ public: return false; } - auto buy_lines = - BaseBuyerBuyLinesRepository::GetWhere(db, fmt::format("`buyer_id` = {}", buyer.front().id)); + auto buy_lines = BaseBuyerBuyLinesRepository::GetWhere( + db, + fmt::format("`buyer_id` = '{}'", buyer.front().id) + ); + if (buy_lines.empty()) { + return false; + } std::vector buy_line_ids{}; for (auto const &bl: buy_lines) { @@ -175,6 +180,26 @@ public: return true; } + + static bool UpdateBuyerEntityID(Database &db, uint32 char_id, uint32 old_entity_id, uint32 new_entity_id) + { + if (!char_id || !old_entity_id || !new_entity_id) { + return false; + } + + auto results = GetWhere(db, fmt::format("`char_id` = '{}' AND `char_entity_id` = '{}' LIMIT 1;", char_id, old_entity_id)); + + if (results.empty()) { + return false; + } + + for (auto &e: results) { + e.char_entity_id = new_entity_id; + } + + ReplaceMany(db, results); + return true; + } }; #endif //EQEMU_BUYER_REPOSITORY_H diff --git a/common/repositories/character_offline_transactions_repository.h b/common/repositories/character_offline_transactions_repository.h new file mode 100644 index 000000000..6802a4a4a --- /dev/null +++ b/common/repositories/character_offline_transactions_repository.h @@ -0,0 +1,53 @@ +#ifndef EQEMU_CHARACTER_OFFLINE_TRANSACTIONS_REPOSITORY_H +#define EQEMU_CHARACTER_OFFLINE_TRANSACTIONS_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_character_offline_transactions_repository.h" + +class CharacterOfflineTransactionsRepository: public BaseCharacterOfflineTransactionsRepository { +public: + +#define TRADER_TRANSACTION 1 +#define BUYER_TRANSACTION 2 + + /** + * This file was auto generated and can be modified and extended upon + * + * Base repository methods are automatically + * generated in the "base" version of this repository. The base repository + * is immutable and to be left untouched, while methods in this class + * are used as extension methods for more specific persistence-layer + * accessors or mutators. + * + * Base Methods (Subject to be expanded upon in time) + * + * Note: Not all tables are designed appropriately to fit functionality with all base methods + * + * InsertOne + * UpdateOne + * DeleteOne + * FindOne + * GetWhere(std::string where_filter) + * DeleteWhere(std::string where_filter) + * InsertMany + * All + * + * Example custom methods in a repository + * + * CharacterOfflineTransactionsRepository::GetByZoneAndVersion(int zone_id, int zone_version) + * CharacterOfflineTransactionsRepository::GetWhereNeverExpires() + * CharacterOfflineTransactionsRepository::GetWhereXAndY() + * CharacterOfflineTransactionsRepository::DeleteWhereXAndY() + * + * Most of the above could be covered by base methods, but if you as a developer + * find yourself re-using logic for other parts of the code, its best to just make a + * method that can be re-used easily elsewhere especially if it can use a base repository + * method and encapsulate filters there + */ + + // Custom extended repository methods here + +}; + +#endif //EQEMU_CHARACTER_OFFLINE_TRANSACTIONS_REPOSITORY_H diff --git a/common/repositories/trader_repository.h b/common/repositories/trader_repository.h index e3dd79a32..5afb76c33 100644 --- a/common/repositories/trader_repository.h +++ b/common/repositories/trader_repository.h @@ -395,6 +395,49 @@ public: return all_entries; } + + static Trader GetAccountZoneIdAndInstanceIdByAccountId(Database &db, uint32 account_id) + { + auto trader_query = fmt::format( + "SELECT t.id, t.char_id, t.char_zone_id, t.char_zone_instance_id " + "FROM trader AS t " + "WHERE t.char_id IN(SELECT c.id FROM character_data AS c WHERE c.account_id = '{}') " + "LIMIT 1;", + account_id + ); + + auto buyer_query = fmt::format( + "SELECT t.id, t.char_id, t.char_zone_id, t.char_zone_instance_id " + "FROM buyer AS t " + "WHERE t.char_id IN(SELECT c.id FROM character_data AS c WHERE c.account_id = '{}') " + "LIMIT 1;", + account_id + ); + + Trader e{}; + + auto trader_results = db.QueryDatabase(trader_query); + auto buyer_results = db.QueryDatabase(buyer_query); + if (trader_results.RowCount() == 0 && buyer_results.RowCount() == 0) { + return e; + } + + MySQLRequestRow row; + if (trader_results.RowCount() > 0) { + row = trader_results.begin(); + } + + if (buyer_results.RowCount() > 0) { + row = buyer_results.begin(); + } + + e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; + e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.char_zone_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; + e.char_zone_instance_id = row[3] ? static_cast(atoi(row[3])) : 0; + + return e; + } }; #endif //EQEMU_TRADER_REPOSITORY_H diff --git a/common/servertalk.h b/common/servertalk.h index 2180d9c85..791434305 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -229,10 +229,12 @@ #define ServerOP_LSPlayerJoinWorld 0x3007 #define ServerOP_LSPlayerZoneChange 0x3008 -#define ServerOP_UsertoWorldReqLeg 0xAB00 -#define ServerOP_UsertoWorldRespLeg 0xAB01 -#define ServerOP_UsertoWorldReq 0xAB02 -#define ServerOP_UsertoWorldResp 0xAB03 +#define ServerOP_UsertoWorldReqLeg 0xAB00 +#define ServerOP_UsertoWorldRespLeg 0xAB01 +#define ServerOP_UsertoWorldReq 0xAB02 +#define ServerOP_UsertoWorldResp 0xAB03 +#define ServerOP_UsertoWorldCancelOfflineRequest 0xAB04 +#define ServerOP_UsertoWorldCancelOfflineResponse 0xAB05 #define ServerOP_LauncherConnectInfo 0x3000 #define ServerOP_LauncherZoneRequest 0x3001 @@ -360,12 +362,13 @@ enum { QSG_LFGuild_PlayerMatches = 0, QSG_LFGuild_UpdatePlayerInfo, QSG_LFGuild_ enum { - UserToWorldStatusWorldUnavail = 0, - UserToWorldStatusSuccess = 1, - UserToWorldStatusSuspended = -1, - UserToWorldStatusBanned = -2, - UserToWorldStatusWorldAtCapacity = -3, - UserToWorldStatusAlreadyOnline = -4 + UserToWorldStatusWorldUnavail = 0, + UserToWorldStatusSuccess = 1, + UserToWorldStatusSuspended = -1, + UserToWorldStatusBanned = -2, + UserToWorldStatusWorldAtCapacity = -3, + UserToWorldStatusAlreadyOnline = -4, + UserToWorldStatusOffilineTraderBuyer = -5 }; enum { @@ -567,6 +570,9 @@ struct ServerClientList_Struct { uint8 LFGToLevel; bool LFGMatchFilter; char LFGComments[64]; + bool trader; + bool buyer; + bool offline; }; struct ServerClientListKeepAlive_Struct { @@ -1030,6 +1036,7 @@ struct ServerGuildMemberUpdate_Struct { char member_name[64]; uint32 zone_id; uint32 last_seen; + uint32 offline_mode; }; struct ServerGuildPermissionUpdate_Struct { diff --git a/loginserver/client.cpp b/loginserver/client.cpp index 754c83ab5..20fbca94d 100644 --- a/loginserver/client.cpp +++ b/loginserver/client.cpp @@ -71,6 +71,25 @@ bool Client::Process() SendPlayToWorld((const char *) app->pBuffer); break; } + case OP_CancelOfflineTrader: { + if (app->Size() < sizeof(CancelOfflineTrader)) { + LogError("Play received but it is too small, discarding"); + break; + } + + safe_delete_array(app->pBuffer); + auto buffer = new unsigned char[sizeof(PlayEverquestRequest)]; + auto data = (PlayEverquestRequest *) buffer; + data->base_header.sequence = GetCurrentPlaySequence(); + data->server_number = GetSelectedPlayServerID(); + app->pBuffer = buffer; + app->size = sizeof(PlayEverquestRequest); + + LogLoginserverDetail("Step 1 - Hit CancelOfflineTrader Mode Packet for."); + SendCancelOfflineStatusToWorld((const char *) app->pBuffer); + + break; + } } delete app; @@ -561,3 +580,27 @@ std::string Client::GetClientLoggingDescription() client_ip ); } + +void Client::SendCancelOfflineStatusToWorld(const char *data) +{ + if (m_client_status != cs_logged_in) { + LogError("Client sent a play request when they were not logged in, discarding"); + return; + } + + const auto *play = (const PlayEverquestRequest *) data; + auto server_id_in = (unsigned int) play->server_number; + auto sequence_in = (unsigned int) play->base_header.sequence; + + LogLoginserverDetail( + "Step 2 - Cancel Offline Status Request received from client [{}] server number [{}] sequence [{}]", + GetAccountName(), + server_id_in, + sequence_in + ); + + m_selected_play_server_id = (unsigned int) play->server_number; + m_play_sequence_id = sequence_in; + m_selected_play_server_id = server_id_in; + server.server_manager->SendUserToWorldCancelOfflineRequest(server_id_in, m_account_id, m_loginserver_name); +} diff --git a/loginserver/client.h b/loginserver/client.h index 0dc71d00c..6e09a661e 100644 --- a/loginserver/client.h +++ b/loginserver/client.h @@ -27,6 +27,7 @@ public: void SendPlayToWorld(const char *data); void SendServerListPacket(uint32 seq); void SendPlayResponse(EQApplicationPacket *outapp); + void SendCancelOfflineStatusToWorld(const char *data); void GenerateRandomLoginKey(); unsigned int GetAccountID() const { return m_account_id; } std::string GetLoginServerName() const { return m_loginserver_name; } diff --git a/loginserver/login_types.h b/loginserver/login_types.h index 127514696..2c8541826 100644 --- a/loginserver/login_types.h +++ b/loginserver/login_types.h @@ -79,6 +79,11 @@ struct PlayEverquestResponse { uint32 server_number; }; +struct CancelOfflineTrader { + LoginBaseMessage base_header; + int16_t unk; +}; + #pragma pack() enum LSClientVersion { @@ -154,11 +159,12 @@ namespace LS { constexpr static int ERROR_NONE = 101; // No Error constexpr static int ERROR_UNKNOWN = 102; // Error - Unknown Error Occurred constexpr static int ERROR_ACTIVE_CHARACTER = 111; // Error 1018: You currently have an active character on that EverQuest Server, please allow a minute for synchronization and try again. + constexpr static int ERROR_OFFLINE_TRADER = 114; // You have a character logged into a world server as an OFFLINE TRADER from this account. You may only have 1 character from a single account logged into a server at a time (even across different servers). Would you like to remove this character from the game so you may login? constexpr static int ERROR_SERVER_UNAVAILABLE = 326; // That server is currently unavailable. Please check the EverQuest webpage for current server status and try again later. constexpr static int ERROR_ACCOUNT_SUSPENDED = 337; // This account is currently suspended. Please contact customer service for more information. constexpr static int ERROR_ACCOUNT_BANNED = 338; // This account is currently banned. Please contact customer service for more information. constexpr static int ERROR_WORLD_MAX_CAPACITY = 339; // The world server is currently at maximum capacity and not allowing further logins until the number of players online decreases. Please try again later. - }; + }; } #endif diff --git a/loginserver/login_util/login_opcodes_sod.conf b/loginserver/login_util/login_opcodes_sod.conf index cdc856d2c..c91d144c5 100644 --- a/loginserver/login_util/login_opcodes_sod.conf +++ b/loginserver/login_util/login_opcodes_sod.conf @@ -11,3 +11,5 @@ OP_Poll=0x0029 OP_LoginExpansionPacketData=0x0031 OP_EnterChat=0x000f OP_PollResponse=0x0011 +OP_CancelOfflineTrader=0x0016 + OP_CancelOfflineTraderResponse=0x0030 \ No newline at end of file diff --git a/loginserver/world_server.cpp b/loginserver/world_server.cpp index 44e6049d1..507f38390 100644 --- a/loginserver/world_server.cpp +++ b/loginserver/world_server.cpp @@ -51,6 +51,12 @@ WorldServer::WorldServer(std::shared_ptr wo ServerOP_LSAccountUpdate, std::bind(&WorldServer::ProcessLSAccountUpdate, this, std::placeholders::_1, std::placeholders::_2) ); + + worldserver_connection->OnMessage( + ServerOP_UsertoWorldCancelOfflineResponse, + std::bind( + &WorldServer::ProcessUserToWorldCancelOfflineResponse, this, std::placeholders::_1, std::placeholders::_2) + ); } WorldServer::~WorldServer() = default; @@ -298,6 +304,10 @@ void WorldServer::ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Pac case UserToWorldStatusAlreadyOnline: r->base_reply.error_str_id = LS::ErrStr::ERROR_ACTIVE_CHARACTER; break; + case UserToWorldStatusOffilineTraderBuyer: + r->base_reply.success = false; + r->base_reply.error_str_id = LS::ErrStr::ERROR_OFFLINE_TRADER; + break; default: r->base_reply.error_str_id = LS::ErrStr::ERROR_UNKNOWN; break; @@ -774,3 +784,113 @@ void WorldServer::FormatWorldServerName(char *name, int8 server_list_type) strn0cpy(name, server_long_name.c_str(), 201); } + +void WorldServer::ProcessUserToWorldCancelOfflineResponse(uint16_t opcode, const EQ::Net::Packet &packet) + { + LogNetcode( + "Application packet received from server [{:#04x}] [Size: {}]\n{}", + opcode, + packet.Length(), + packet.ToString() + ); + LogLoginserverDetail("Step 8 - back in Login Server from world."); + + if (packet.Length() < sizeof(UsertoWorldResponse)) { + LogError( + "Received application packet from server that had opcode ServerOP_UsertoWorldCancelOfflineResp, " + "but was too small. Discarded to avoid buffer overrun" + ); + return; + } + + auto res = (UsertoWorldResponse *) packet.Data(); + LogDebug("Trying to find client with user id of [{}]", res->lsaccountid); + + Client *c = server.client_manager->GetClient( + res->lsaccountid, + res->login + ); + + if (c) { + LogDebug( + "Found client with user id of [{}] and account name of {}", + res->lsaccountid, + c->GetAccountName().c_str() + ); + + auto client_packet = EQApplicationPacket(OP_CancelOfflineTraderResponse, sizeof(PlayEverquestResponse)); + auto client_packet_payload = reinterpret_cast(client_packet.pBuffer); + + client_packet_payload->base_header.sequence = c->GetCurrentPlaySequence(); + client_packet_payload->server_number = c->GetSelectedPlayServerID(); + + LogLoginserverDetail( + "Step 9 - Send Play Response OPCODE 30 to remove the client message about having an offline Trader/Buyer" + ); + c->SendPlayResponse(&client_packet); + + auto outapp = new EQApplicationPacket(OP_PlayEverquestResponse, sizeof(PlayEverquestResponse)); + auto r = reinterpret_cast(outapp->pBuffer); + r->base_header.sequence = c->GetCurrentPlaySequence(); + r->server_number = c->GetSelectedPlayServerID(); + + LogDebug( + "Found sequence and play of [{}] [{}]", + c->GetCurrentPlaySequence(), + c->GetSelectedPlayServerID() + ); + + //LogDebug("[Size: [{}]] {}", outapp->size, DumpPacketToString(outapp)); + + if (res->response > 0) { + r->base_reply.success = true; + SendClientAuthToWorld(c); + } + + switch (res->response) { + case UserToWorldStatusSuccess: + r->base_reply.error_str_id = LS::ErrStr::ERROR_NONE; + break; + case UserToWorldStatusWorldUnavail: + r->base_reply.error_str_id = LS::ErrStr::ERROR_SERVER_UNAVAILABLE; + break; + case UserToWorldStatusSuspended: + r->base_reply.error_str_id = LS::ErrStr::ERROR_ACCOUNT_SUSPENDED; + break; + case UserToWorldStatusBanned: + r->base_reply.error_str_id = LS::ErrStr::ERROR_ACCOUNT_BANNED; + break; + case UserToWorldStatusWorldAtCapacity: + r->base_reply.error_str_id = LS::ErrStr::ERROR_WORLD_MAX_CAPACITY; + break; + case UserToWorldStatusAlreadyOnline: + r->base_reply.error_str_id = LS::ErrStr::ERROR_ACTIVE_CHARACTER; + break; + case UserToWorldStatusOffilineTraderBuyer: + r->base_reply.success = false; + r->base_reply.error_str_id = LS::ErrStr::ERROR_OFFLINE_TRADER; + break; + default: + r->base_reply.error_str_id = LS::ErrStr::ERROR_UNKNOWN; + break; + } + + LogDebug( + "Sending play response with following data, allowed [{}], sequence {}, server number {}, message {}", + r->base_reply.success, + r->base_header.sequence, + r->server_number, + r->base_reply.error_str_id + ); + LogLoginserverDetail("Step 10 - Send Play Response EnterWorld to client"); + + c->SendPlayResponse(outapp); + delete outapp; + } + else { + LogError( + "Received User-To-World Response for [{}] but could not find the client referenced!.", + res->lsaccountid + ); + } + } \ No newline at end of file diff --git a/loginserver/world_server.h b/loginserver/world_server.h index f5120d38e..865a3d522 100644 --- a/loginserver/world_server.h +++ b/loginserver/world_server.h @@ -60,6 +60,7 @@ private: void ProcessUserToWorldResponseLegacy(uint16_t opcode, const EQ::Net::Packet &packet); void ProcessUserToWorldResponse(uint16_t opcode, const EQ::Net::Packet &packet); void ProcessLSAccountUpdate(uint16_t opcode, const EQ::Net::Packet &packet); + void ProcessUserToWorldCancelOfflineResponse(uint16_t opcode, const EQ::Net::Packet &packet); std::shared_ptr m_connection; diff --git a/loginserver/world_server_manager.cpp b/loginserver/world_server_manager.cpp index f18b48537..f2a609b9a 100644 --- a/loginserver/world_server_manager.cpp +++ b/loginserver/world_server_manager.cpp @@ -216,3 +216,35 @@ const std::list> &WorldServerManager::GetWorldServe { return m_world_servers; } + +void WorldServerManager::SendUserToWorldCancelOfflineRequest( + unsigned int server_id, + unsigned int client_account_id, + const std::string &client_loginserver +) +{ + auto iter = std::find_if( + m_world_servers.begin(), m_world_servers.end(), + [&](const std::unique_ptr &server) { + return server->GetServerId() == server_id; + } + ); + + if (iter != m_world_servers.end()) { + EQ::Net::DynamicPacket outapp; + outapp.Resize(sizeof(UsertoWorldRequest)); + + auto *r = reinterpret_cast(outapp.Data()); + r->worldid = server_id; + r->lsaccountid = client_account_id; + strncpy(r->login, client_loginserver.c_str(), 64); + + LogLoginserverDetail("Step 3 - Sending ServerOP_UsertoWorldCancelOfflineRequest to world for client account id {}", client_account_id); + (*iter)->GetConnection()->Send(ServerOP_UsertoWorldCancelOfflineRequest, outapp); + + LogNetcode("[UsertoWorldRequest] [Size: {}]\n{}", outapp.Length(), outapp.ToString()); + } + else { + LogError("Client requested a user to world but supplied an invalid id of {}", server_id); + } +} \ No newline at end of file diff --git a/loginserver/world_server_manager.h b/loginserver/world_server_manager.h index 711bc7560..de5cb74d2 100644 --- a/loginserver/world_server_manager.h +++ b/loginserver/world_server_manager.h @@ -18,6 +18,11 @@ public: unsigned int client_account_id, const std::string &client_loginserver ); + void SendUserToWorldCancelOfflineRequest( + unsigned int server_id, + unsigned int client_account_id, + const std::string &client_loginserver + ); std::unique_ptr CreateServerListPacket(Client *client, uint32 sequence); bool DoesServerExist(const std::string &s, const std::string &server_short_name, WorldServer *ignore = nullptr); void DestroyServerByName(std::string s, std::string server_short_name, WorldServer *ignore = nullptr); diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 3533aaa87..f01d7d762 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -749,3 +749,6 @@ OP_ChangePetName=0x5dab OP_InvokeNameChangeImmediate=0x4fe2 OP_InvokeNameChangeLazy=0x2f2e + +#Offline Trading Mode + OP_Offline=0x53d3 \ No newline at end of file diff --git a/world/cliententry.cpp b/world/cliententry.cpp index d6feb68dd..cf803c2ce 100644 --- a/world/cliententry.cpp +++ b/world/cliententry.cpp @@ -202,6 +202,9 @@ void ClientListEntry::Update(ZoneServer *iZS, ServerClientList_Struct *scl, CLE_ m_lfg = scl->LFG; m_gm = scl->gm; m_client_version = scl->ClientVersion; + m_trader = scl->trader; + m_buyer = scl->buyer; + m_offline = scl->offline; // Fields from the LFG Window if ((scl->LFGFromLevel != 0) && (scl->LFGToLevel != 0)) { @@ -219,6 +222,10 @@ void ClientListEntry::LeavingZone(ZoneServer *iZS, CLE_Status iOnline) if (iZS != 0 && iZS != m_zone_server) { return; } + + m_trader = false; + m_buyer = false; + m_offline = false; SetOnline(iOnline); SharedTaskManager::Instance()->RemoveActiveInvitationByCharacterID(CharID()); @@ -260,6 +267,10 @@ void ClientListEntry::ClearVars(bool iAll) m_lfg = 0; m_gm = 0; m_client_version = 0; + m_trader = false; + m_buyer = false; + m_offline = false; + for (auto &elem: m_tell_queue) { safe_delete_array(elem); } diff --git a/world/cliententry.h b/world/cliententry.h index bf9a2662c..508f98148 100644 --- a/world/cliententry.h +++ b/world/cliententry.h @@ -14,7 +14,8 @@ typedef enum { Online, CharSelect, Zoning, - InZone + InZone, + OfflineMode } CLE_Status; static const char *CLEStatusString[] = { @@ -23,7 +24,8 @@ static const char *CLEStatusString[] = { "Online", "CharSelect", "Zoning", - "InZone" + "InZone", + "OfflineMode" }; class ZoneServer; @@ -103,6 +105,10 @@ public: inline bool GetLFGMatchFilter() const { return m_lfg_match_filter; } inline const char *GetLFGComments() const { return m_lfg_comments; } inline uint8 GetClientVersion() { return m_client_version; } + bool GetTrader() const { return m_trader; } + bool GetBuyer() const { return m_buyer; } + bool GetOfflineMode() const { return m_offline; } + void SetOfflineMode(bool status) { m_offline = status; } inline bool TellQueueFull() const { return m_tell_queue.size() >= RuleI(World, TellQueueSize); } inline bool TellQueueEmpty() const { return m_tell_queue.empty(); } @@ -135,25 +141,28 @@ private: // Character info ZoneServer *m_zone_server{}; - uint32 m_zone{}; - uint16 m_instance{}; - uint32 m_char_id{}; - char m_char_name[64]{}; - uint8 m_level{}; - uint8 m_class_{}; - uint16 m_race{}; - uint8 m_anon{}; - uint8 m_tells_off{}; - uint32 m_guild_id{}; - uint32 m_guild_rank; - bool m_guild_tribute_opt_in{}; - bool m_lfg{}; - uint8 m_gm{}; - uint8 m_client_version{}; - uint8 m_lfg_from_level{}; - uint8 m_lfg_to_level{}; - bool m_lfg_match_filter{}; - char m_lfg_comments[64]{}; + uint32 m_zone{}; + uint16 m_instance{}; + uint32 m_char_id{}; + char m_char_name[64]{}; + uint8 m_level{}; + uint8 m_class_{}; + uint16 m_race{}; + uint8 m_anon{}; + uint8 m_tells_off{}; + uint32 m_guild_id{}; + uint32 m_guild_rank; + bool m_guild_tribute_opt_in{}; + bool m_lfg{}; + uint8 m_gm{}; + uint8 m_client_version{}; + uint8 m_lfg_from_level{}; + uint8 m_lfg_to_level{}; + bool m_lfg_match_filter{}; + char m_lfg_comments[64]{}; + bool m_trader = false; + bool m_buyer = false; + bool m_offline = false; // Tell Queue -- really a vector :D std::vector m_tell_queue; diff --git a/world/clientlist.cpp b/world/clientlist.cpp index d8f1d9fbc..7b8e99d6f 100644 --- a/world/clientlist.cpp +++ b/world/clientlist.cpp @@ -427,7 +427,10 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s while (iterator.MoreElements()) { if (iterator.GetData()->GetID() == scl->wid) { cle = iterator.GetData(); - if (scl->remove == 2) { + if (scl->remove == 3) { + cle->Update(zoneserver, scl, CLE_Status::OfflineMode); + } + else if (scl->remove == 2) { cle->LeavingZone(zoneserver, CLE_Status::Offline); } else if (scl->remove == 1) { @@ -441,7 +444,11 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s } iterator.Advance(); } - if (scl->remove == 2) { + + if (scl->remove == 3) { + cle = new ClientListEntry(GetNextCLEID(), zoneserver, scl, CLE_Status::OfflineMode); + } + else if (scl->remove == 2) { cle = new ClientListEntry(GetNextCLEID(), zoneserver, scl, CLE_Status::Online); } else if (scl->remove == 1) { @@ -479,7 +486,10 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s " LFGFromLevel [{}]" " LFGToLevel [{}]" " LFGMatchFilter [{}]" - " LFGComments [{}]", + " LFGComments [{}]" + " Trader [{}]" + " Buyer [{}]" + " Offline [{}]", scl->remove, scl->wid, scl->IP, @@ -506,7 +516,10 @@ void ClientList::ClientUpdate(ZoneServer *zoneserver, ServerClientList_Struct *s scl->LFGFromLevel, scl->LFGToLevel, scl->LFGMatchFilter, - scl->LFGComments + scl->LFGComments, + scl->trader, + scl->buyer, + scl->offline ); clientlist.Insert(cle); @@ -784,7 +797,14 @@ void ClientList::SendWhoAll(uint32 fromid,const char* to, int16 admin, Who_All_S rankstring = 0; iterator.Advance(); continue; - } else if (cle->GetGM()) { + } + else if (cle->GetTrader()) { + rankstring = 12315; + } + else if (cle->GetBuyer()) { + rankstring = 6056; + } + else if (cle->GetGM()) { if (cle->Admin() >= AccountStatus::GMImpossible) { rankstring = 5021; } else if (cle->Admin() >= AccountStatus::GMMgmt) { @@ -877,6 +897,18 @@ void ClientList::SendWhoAll(uint32 fromid,const char* to, int16 admin, Who_All_S strcpy(placcount,cle->AccountName()); } + if (cle->GetOfflineMode()) { + if (cle->GetTrader()) { + pidstring = 0x0430; + rankstring = 0xFFFFFFFF; + } + + if (cle->GetBuyer()) { + pidstring = 0x0420; + rankstring = 0xFFFFFFFF; + } + } + memcpy(bufptr,&formatstring, sizeof(uint32)); bufptr+=sizeof(uint32); memcpy(bufptr,&pidstring, sizeof(uint32)); @@ -1631,25 +1663,29 @@ void ClientList::OnTick(EQ::Timer *t) outclient["Server"] = Json::Value(); } - outclient["CharID"] = cle->CharID(); - outclient["name"] = cle->name(); - outclient["zone"] = cle->zone(); - outclient["instance"] = cle->instance(); - outclient["level"] = cle->level(); - outclient["class_"] = cle->class_(); - outclient["race"] = cle->race(); - outclient["Anon"] = cle->Anon(); + outclient["CharID"] = cle->CharID(); + outclient["name"] = cle->name(); + outclient["zone"] = cle->zone(); + outclient["instance"] = cle->instance(); + outclient["level"] = cle->level(); + outclient["class_"] = cle->class_(); + outclient["race"] = cle->race(); + outclient["Anon"] = cle->Anon(); - outclient["TellsOff"] = cle->TellsOff(); - outclient["GuildID"] = cle->GuildID(); - outclient["LFG"] = cle->LFG(); - outclient["GM"] = cle->GetGM(); - outclient["LocalClient"] = cle->IsLocalClient(); - outclient["LFGFromLevel"] = cle->GetLFGFromLevel(); - outclient["LFGToLevel"] = cle->GetLFGToLevel(); + outclient["TellsOff"] = cle->TellsOff(); + outclient["GuildID"] = cle->GuildID(); + outclient["LFG"] = cle->LFG(); + outclient["GM"] = cle->GetGM(); + outclient["LocalClient"] = cle->IsLocalClient(); + outclient["LFGFromLevel"] = cle->GetLFGFromLevel(); + outclient["LFGToLevel"] = cle->GetLFGToLevel(); outclient["LFGMatchFilter"] = cle->GetLFGMatchFilter(); - outclient["LFGComments"] = cle->GetLFGComments(); - outclient["ClientVersion"] = cle->GetClientVersion(); + outclient["LFGComments"] = cle->GetLFGComments(); + outclient["ClientVersion"] = cle->GetClientVersion(); + outclient["Trader"] = cle->GetTrader(); + outclient["Buyer"] = cle->GetBuyer(); + outclient["OfflineMode"] = cle->GetOfflineMode(); + out["data"].append(outclient); Iterator.Advance(); diff --git a/world/console.cpp b/world/console.cpp index 41906239b..5f8d03aa4 100644 --- a/world/console.cpp +++ b/world/console.cpp @@ -56,7 +56,7 @@ struct EQ::Net::ConsoleLoginStatus CheckLogin(const std::string &username, const const std::string& account_name = database.GetAccountName(ret.account_id); ret.account_name = account_name; - ret.status = database.GetAccountStatus(ret.account_id); + ret.status = database.GetAccountStatus(ret.account_id).status; return ret; } diff --git a/world/login_server.cpp b/world/login_server.cpp index 9bdc0c470..00aaa0a6e 100644 --- a/world/login_server.cpp +++ b/world/login_server.cpp @@ -1,24 +1,28 @@ -#include "../common/global_define.h" -#include -#include -#include -#include -#include -#include "../common/version.h" -#include "../common/servertalk.h" -#include "../common/misc_functions.h" -#include "../common/eq_packet_structs.h" -#include "../common/packet_dump.h" -#include "../common/strings.h" -#include "../common/eqemu_logsys.h" #include "login_server.h" +#include +#include +#include +#include +#include +#include "../common/eq_packet_structs.h" +#include "../common/eqemu_logsys.h" +#include "../common/global_define.h" +#include "../common/misc_functions.h" +#include "../common/packet_dump.h" +#include "../common/repositories/account_repository.h" +#include "../common/repositories/buyer_repository.h" +#include "../common/repositories/character_data_repository.h" +#include "../common/repositories/trader_repository.h" +#include "../common/servertalk.h" +#include "../common/strings.h" +#include "../common/version.h" +#include "cliententry.h" +#include "clientlist.h" #include "login_server_list.h" -#include "zoneserver.h" +#include "world_config.h" #include "worlddb.h" #include "zonelist.h" -#include "clientlist.h" -#include "cliententry.h" -#include "world_config.h" +#include "zoneserver.h" extern uint32 numzones; extern uint32 numplayers; @@ -44,9 +48,9 @@ void LoginServer::ProcessUsertoWorldReqLeg(uint16_t opcode, EQ::Net::Packet &p) const WorldConfig *Config = WorldConfig::get(); LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode); - UsertoWorldRequestLegacy *utwr = (UsertoWorldRequestLegacy *) p.Data(); - uint32 id = database.GetAccountIDFromLSID("eqemu", utwr->lsaccountid); - int16 status = database.GetAccountStatus(id); + UsertoWorldRequestLegacy *utwr = (UsertoWorldRequestLegacy *) p.Data(); + uint32 id = database.GetAccountIDFromLSID("eqemu", utwr->lsaccountid); + int16 status = database.GetAccountStatus(id).status; LogDebug( "id [{}] status [{}] account_id [{}] world_id [{}] from_id [{}] to_id [{}] ip [{}]", @@ -124,14 +128,19 @@ void LoginServer::ProcessUsertoWorldReq(uint16_t opcode, EQ::Net::Packet &p) const WorldConfig *Config = WorldConfig::get(); LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode); - UsertoWorldRequest *utwr = (UsertoWorldRequest *) p.Data(); - uint32 id = database.GetAccountIDFromLSID(utwr->login, utwr->lsaccountid); - int16 status = database.GetAccountStatus(id); + UsertoWorldRequest *utwr = (UsertoWorldRequest *) p.Data(); + uint32 id = database.GetAccountIDFromLSID(utwr->login, utwr->lsaccountid); + auto status_record = database.GetAccountStatus(id); + auto client = client_list.FindCLEByAccountID(id); + + if (client) { + client->SetOfflineMode(status_record.offline); + } LogDebug( "id [{}] status [{}] account_id [{}] world_id [{}] from_id [{}] to_id [{}] ip [{}]", id, - status, + status_record.status, utwr->lsaccountid, utwr->worldid, utwr->FromID, @@ -153,7 +162,7 @@ void LoginServer::ProcessUsertoWorldReq(uint16_t opcode, EQ::Net::Packet &p) utwrs->response = UserToWorldStatusSuccess; if (Config->Locked == true) { - if (status < (RuleI(GM, MinStatusToBypassLockedServer))) { + if (status_record.status < (RuleI(GM, MinStatusToBypassLockedServer))) { LogDebug( "Server locked and status is not high enough for account_id [{0}]", utwr->lsaccountid @@ -165,27 +174,34 @@ void LoginServer::ProcessUsertoWorldReq(uint16_t opcode, EQ::Net::Packet &p) } int32 x = Config->MaxClients; - if ((int32) numplayers >= x && x != -1 && x != 255 && status < (RuleI(GM, MinStatusToBypassLockedServer))) { + if ((int32) numplayers >= x && x != -1 && x != 255 && status_record.status < (RuleI(GM, MinStatusToBypassLockedServer))) { LogDebug("World at capacity account_id [{0}]", utwr->lsaccountid); utwrs->response = UserToWorldStatusWorldAtCapacity; SendPacket(&outpack); return; } - if (status == -1) { + if (status_record.status == -1) { LogDebug("User suspended account_id [{0}]", utwr->lsaccountid); utwrs->response = UserToWorldStatusSuspended; SendPacket(&outpack); return; } - if (status == -2) { + if (status_record.status == -2) { LogDebug("User banned account_id [{0}]", utwr->lsaccountid); utwrs->response = UserToWorldStatusBanned; SendPacket(&outpack); return; } + if (status_record.offline) { + LogDebug("User has an offline character for account_id [{0}]", utwr->lsaccountid); + utwrs->response = UserToWorldStatusOffilineTraderBuyer; + SendPacket(&outpack); + return; + } + if (RuleB(World, EnforceCharacterLimitAtLogin)) { if (ClientList::Instance()->IsAccountInGame(utwr->lsaccountid)) { LogDebug("User already online account_id [{0}]", utwr->lsaccountid); @@ -573,6 +589,14 @@ bool LoginServer::Connect() std::placeholders::_2 ) ); + m_client->OnMessage( + ServerOP_UsertoWorldCancelOfflineRequest, + std::bind( + &LoginServer::ProcessUserToWorldCancelOfflineRequest, + this, + std::placeholders::_1, + std::placeholders::_2) + ); } return true; @@ -688,3 +712,87 @@ void LoginServer::SendAccountUpdate(ServerPacket *pack) } } +void LoginServer::ProcessUserToWorldCancelOfflineRequest(uint16_t opcode, EQ::Net::Packet &p) +{ + auto const Config = WorldConfig::get(); + LogNetcode("Received ServerPacket from LS OpCode {:#04x}", opcode); + + auto utwr = static_cast(p.Data()); + uint32 id = database.GetAccountIDFromLSID(utwr->login, utwr->lsaccountid); + auto status_record = database.GetAccountStatus(id); + + LogLoginserverDetail( + "Step 4 - World received CancelOfflineRequest for client login server account id {} offline mode {}", + id, + status_record.offline + ); + LogDebug( + "id [{}] status [{}] account_id [{}] world_id [{}] ip [{}]", + id, + status_record.status, + utwr->lsaccountid, + utwr->worldid, + utwr->IPAddr + ); + + ServerPacket server_packet; + server_packet.size = sizeof(UsertoWorldResponse); + server_packet.pBuffer = new uchar[server_packet.size]; + memset(server_packet.pBuffer, 0, server_packet.size); + + auto utwrs = reinterpret_cast(server_packet.pBuffer); + utwrs->lsaccountid = utwr->lsaccountid; + utwrs->ToID = utwr->FromID; + utwrs->worldid = utwr->worldid; + utwrs->response = UserToWorldStatusSuccess; + strn0cpy(utwrs->login, utwr->login, 64); + + if (Config->Locked == true) { + if (status_record.status < RuleI(GM, MinStatusToBypassLockedServer)) { + LogDebug("Server locked and status is not high enough for account_id [{0}]", utwr->lsaccountid); + server_packet.opcode = ServerOP_UsertoWorldCancelOfflineResponse; + utwrs->response = UserToWorldStatusWorldUnavail; + SendPacket(&server_packet); + return; + } + } + + int32 x = Config->MaxClients; + if (static_cast(numplayers) >= x && + x != -1 && + x != 255 && + status_record.status < RuleI(GM, MinStatusToBypassLockedServer) + ) { + LogDebug("World at capacity account_id [{0}]", utwr->lsaccountid); + server_packet.opcode = ServerOP_UsertoWorldCancelOfflineResponse; + utwrs->response = UserToWorldStatusWorldAtCapacity; + SendPacket(&server_packet); + return; + } + + auto trader = TraderRepository::GetAccountZoneIdAndInstanceIdByAccountId(database, id); + if (trader.id && + zoneserver_list.IsZoneBootedByZoneIdAndInstanceId(trader.char_zone_id, trader.char_zone_instance_id)) { + LogLoginserverDetail( + "Step 5a(1) - World Checked offline users zone/instance is booted. " + "Sending packet to zone id {} instance id {}", + trader.char_zone_id, + trader.char_zone_instance_id); + + server_packet.opcode = ServerOP_UsertoWorldCancelOfflineRequest; + zoneserver_list.SendPacketToBootedZones(&server_packet); + return; + } + + LogLoginserverDetail("Step 5b(1) - World determined offline users zone/instance is not booted. Ignoring zone."); + + LogLoginserverDetail("Step 5b(2) - World clearing users offline status from account table."); + AccountRepository::SetOfflineStatus(database, id, false); + + LogLoginserverDetail("Step 5b(3) - World clearing trader and buyer tablese."); + TraderRepository::DeleteWhere(database, fmt::format("`char_id` = '{}'", trader.id)); + BuyerRepository::DeleteBuyer(database, trader.id); + + server_packet.opcode = ServerOP_UsertoWorldCancelOfflineResponse; + SendPacket(&server_packet); +} diff --git a/world/login_server.h b/world/login_server.h index 0a5e6a107..729f7a0b2 100644 --- a/world/login_server.h +++ b/world/login_server.h @@ -49,6 +49,7 @@ private: void ProcessSystemwideMessage(uint16_t opcode, EQ::Net::Packet &p); void ProcessLSRemoteAddr(uint16_t opcode, EQ::Net::Packet &p); void ProcessLSAccountUpdate(uint16_t opcode, EQ::Net::Packet &p); + void ProcessUserToWorldCancelOfflineRequest(uint16_t opcode, EQ::Net::Packet &p); std::unique_ptr m_keepalive; diff --git a/world/zonelist.cpp b/world/zonelist.cpp index f108031dc..111748f97 100644 --- a/world/zonelist.cpp +++ b/world/zonelist.cpp @@ -1018,3 +1018,15 @@ void ZSList::QueueServerReload(ServerReload::Type &type) m_queued_reloads.emplace_back(type); m_queued_reloads_mutex.unlock(); } + +bool ZSList::IsZoneBootedByZoneIdAndInstanceId(uint32 zone_id, uint32 instance_id) const +{ + for (auto const& z : zone_server_list) { + auto r = z.get(); + if (r && r->GetZoneID() == zone_id && r->GetInstanceID() == instance_id) { + return true; + } + } + + return false; +} diff --git a/world/zonelist.h b/world/zonelist.h index 43dd52b44..757802eaf 100644 --- a/world/zonelist.h +++ b/world/zonelist.h @@ -34,6 +34,7 @@ public: bool SendPacketToZonesWithGMs(ServerPacket *pack); bool SendPacketToBootedZones(ServerPacket* pack); bool SetLockedZone(uint16 iZoneID, bool iLock); + bool IsZoneBootedByZoneIdAndInstanceId(uint32 zone_id, uint32 instance_id) const; EQTime worldclock; diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 4a372cb79..29afe2caf 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1726,9 +1726,34 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - default: - return; + default: { + break; + } } + break; + } + case ServerOP_UsertoWorldCancelOfflineResponse: { + auto utwr = reinterpret_cast(pack->pBuffer); + + ServerPacket server_packet; + server_packet.opcode = ServerOP_UsertoWorldCancelOfflineResponse; + server_packet.size = sizeof(UsertoWorldResponse); + server_packet.pBuffer = new uchar[server_packet.size]; + memset(server_packet.pBuffer, 0, server_packet.size); + + auto utwrs = reinterpret_cast(server_packet.pBuffer); + utwrs->lsaccountid = utwr->lsaccountid; + utwrs->ToID = utwr->FromID; + utwrs->worldid = utwr->worldid; + utwrs->response = UserToWorldStatusSuccess; + strn0cpy(utwrs->login, utwr->login, 64); + + LogLoginserverDetail( + "Step 7a - World received ServerOP_UsertoWorldCancelOfflineResponse back to login with success." + ); + + loginserverlist.SendPacket(&server_packet); + break; } default: { LogInfo("Unknown ServerOPcode from zone {:#04x}, size [{}]", pack->opcode, pack->size); diff --git a/zone/api_service.cpp b/zone/api_service.cpp index 607ab64c6..d4f91918f 100644 --- a/zone/api_service.cpp +++ b/zone/api_service.cpp @@ -59,7 +59,7 @@ EQ::Net::WebsocketLoginStatus CheckLogin( ret.account_name = database.GetAccountName(static_cast(ret.account_id)); ret.logged_in = true; - ret.status = database.GetAccountStatus(ret.account_id); + ret.status = database.GetAccountStatus(ret.account_id).status; return ret; } diff --git a/zone/client.cpp b/zone/client.cpp index 5b990635a..46c5db9f1 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -510,10 +510,10 @@ Client::Client(EQStreamInterface *ieqs) : Mob( client_data_loaded = false; berserk = false; dead = false; - eqs = ieqs; - ip = eqs->GetRemoteIP(); - port = ntohs(eqs->GetRemotePort()); - client_state = CLIENT_CONNECTING; + eqs = ieqs ? ieqs : nullptr; + ip = eqs ? eqs->GetRemoteIP() : 0; + port = eqs ? ntohs(eqs->GetRemotePort()) : 0; + client_state = eqs ? CLIENT_CONNECTING : CLIENT_CONNECTED; SetTrader(false); Haste = 0; SetCustomerID(0); @@ -700,6 +700,7 @@ Client::Client(EQStreamInterface *ieqs) : Mob( m_parcels.clear(); m_buyer_id = 0; + m_offline = false; SetBotPulling(false); SetBotPrecombat(false); @@ -728,10 +729,12 @@ Client::~Client() { zone->ClearEXPModifier(this); } - if (!IsZoning()) { - if(IsInAGuild()) { - guild_mgr.UpdateDbMemberOnline(CharacterID(), false); - guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr)); + if (!IsZoning() && IsInAGuild()) { + guild_mgr.UpdateDbMemberOnline(CharacterID(), false); + if (IsOffline()) { + guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), GetZoneID(), time(nullptr), 1); + } else { + guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr), 0); } } @@ -743,11 +746,11 @@ Client::~Client() { if (merc) merc->Depop(); - if(IsTrader()) { + if(IsTrader() && !IsOffline()) { TraderEndTrader(); } - if(IsBuyer()) { + if(IsBuyer() && !IsOffline()) { ToggleBuyerMode(false); } @@ -779,7 +782,9 @@ Client::~Client() { if(isgrouped && !bZoning && is_zone_loaded) LeaveGroup(); - UpdateWho(2); + if (!IsOffline() && !IsTrader()) { + UpdateWho(2); + } if(IsHoveringForRespawn()) { @@ -2155,6 +2160,9 @@ void Client::UpdateWho(uint8 remove) s->race = GetRace(); s->class_ = GetClass(); s->level = GetLevel(); + s->trader = IsTrader(); + s->buyer = IsBuyer(); + s->offline = IsOffline(); if (m_pp.anon == 0) { s->anon = 0; @@ -2223,7 +2231,7 @@ void Client::FriendsWho(char *FriendsString) { void Client::UpdateAdmin(bool from_database) { int16 tmp = admin; if (from_database) { - admin = database.GetAccountStatus(account_id); + admin = database.GetAccountStatus(account_id).status; } if (tmp == admin && from_database) { @@ -2539,6 +2547,7 @@ void Client::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) ns->spawn.guildID = GuildID(); ns->spawn.trader = IsTrader(); ns->spawn.buyer = IsBuyer(); + ns->spawn.offline = IsOffline(); // ns->spawn.linkdead = IsLD() ? 1 : 0; // ns->spawn.pvp = GetPVP(false) ? 1 : 0; ns->spawn.show_name = true; diff --git a/zone/client.h b/zone/client.h index 6a5ab378b..6d97b9031 100644 --- a/zone/client.h +++ b/zone/client.h @@ -416,6 +416,8 @@ public: int32 FindNextFreeParcelSlot(uint32 char_id); int32 FindNextFreeParcelSlotUsingMemory(); void SendParcelIconStatus(); + bool IsOffline() { return m_offline; } + void SetOffline(bool status) { m_offline = status; } void SendBecomeTraderToWorld(Client *trader, BazaarTraderBarterActions action); void SendBecomeTrader(BazaarTraderBarterActions action, uint32 trader_id); @@ -508,7 +510,12 @@ public: inline bool ClientDataLoaded() const { return client_data_loaded; } inline bool Connected() const { return (client_state == CLIENT_CONNECTED); } inline bool InZone() const { return (client_state == CLIENT_CONNECTED || client_state == CLIENT_LINKDEAD); } - inline void Disconnect() { eqs->Close(); client_state = DISCONNECTED; } + inline void Disconnect() { + if (eqs) { + eqs->Close(); + client_state = DISCONNECTED; + } + } inline bool IsLD() const { return (bool) (client_state == CLIENT_LINKDEAD); } void Kick(const std::string &reason); void WorldKick(); @@ -2117,6 +2124,7 @@ private: uint32 m_trader_count{}; std::map> m_trader_merchant_list{}; // itemid, qty, item_unique_id uint32 m_buyer_id; + bool m_offline; uint32 m_barter_time; int32 m_parcel_platinum; int32 m_parcel_gold; @@ -2446,6 +2454,68 @@ public: bool IsFilteredAFKPacket(const EQApplicationPacket *p); void CheckAutoIdleAFK(PlayerPositionUpdateClient_Struct *p); void SyncWorldPositionsToClient(bool ignore_idle = false); + + + Mob* GetMob() { + return Mob::GetMob(); + } + + void Clone(Client& in) + { + WID = in.WID; + admin = in.admin; + guild_id = in.guild_id; + guildrank = in.guildrank; + LFG = in.LFG; + AFK = in.AFK; + trader_id = in.trader_id; + m_buyer_id = in.m_buyer_id; + race = in.race; + class_ = in.class_; + size = in.size; + deity = in.deity; + texture = in.texture; + m_ClientVersion = in.m_ClientVersion; + m_ClientVersionBit = in.m_ClientVersionBit; + character_id = in.character_id; + account_id = in.account_id; + lsaccountid = in.lsaccountid; + + m_pp.platinum = in.m_pp.platinum; + m_pp.gold = in.m_pp.gold; + m_pp.silver = in.m_pp.silver; + m_pp.copper = in.m_pp.copper; + m_pp.platinum_bank = in.m_pp.platinum_bank; + m_pp.gold_bank = in.m_pp.gold_bank; + m_pp.silver_bank = in.m_pp.silver_bank; + m_pp.copper_bank = in.m_pp.copper_bank; + m_pp.platinum_cursor = in.m_pp.platinum_cursor; + m_pp.gold_cursor = in.m_pp.gold_cursor; + m_pp.silver_cursor = in.m_pp.silver_cursor; + m_pp.copper_cursor = in.m_pp.copper_cursor; + m_pp.currentRadCrystals = in.m_pp.currentRadCrystals; + m_pp.careerRadCrystals = in.m_pp.careerRadCrystals; + m_pp.currentEbonCrystals = in.m_pp.currentEbonCrystals; + m_pp.careerEbonCrystals = in.m_pp.careerEbonCrystals; + m_pp.gm = in.m_pp.gm; + + m_inv.SetInventoryVersion(in.m_ClientVersion); + SetBodyType(in.GetBodyType(), false); + + for (auto [slot, item] : in.m_inv.GetPersonal()) { + if (item) { + m_inv.GetPersonal()[slot] = item->Clone(); + } + } + + for (auto [slot, item] : in.m_inv.GetWorn()) { + if (item) { + m_inv.GetWorn()[slot] = item->Clone(); + } + } + + CloneMob(*in.GetMob()); + } }; #endif diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 89b0d1d07..b51b0ee38 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -19,6 +19,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/eqemu_logsys.h" #include "../common/opcodemgr.h" #include "../common/raid.h" +#include "../common/repositories/character_offline_transactions_repository.h" #include #include @@ -323,6 +324,7 @@ void MapOpcodes() ConnectedOpcodes[OP_MoveCoin] = &Client::Handle_OP_MoveCoin; ConnectedOpcodes[OP_MoveItem] = &Client::Handle_OP_MoveItem; ConnectedOpcodes[OP_MoveMultipleItems] = &Client::Handle_OP_MoveMultipleItems; + ConnectedOpcodes[OP_Offline] = &Client::Handle_OP_Offline; ConnectedOpcodes[OP_OpenContainer] = &Client::Handle_OP_OpenContainer; ConnectedOpcodes[OP_OpenGuildTributeMaster] = &Client::Handle_OP_OpenGuildTributeMaster; ConnectedOpcodes[OP_OpenInventory] = &Client::Handle_OP_OpenInventory; @@ -857,6 +859,54 @@ void Client::CompleteConnect() } } + auto offline_transactions_trader = CharacterOfflineTransactionsRepository::GetWhere( + database, fmt::format("`character_id` = '{}' AND `type` = '{}'", CharacterID(), TRADER_TRANSACTION) + ); + if (offline_transactions_trader.size() > 0) { + Message(Chat::Yellow, "You sold the following items while in offline trader mode:"); + + for (auto const &t: offline_transactions_trader) { + Message( + Chat::Yellow, + fmt::format( + "You sold {} {}{} to {} for {}.", + t.quantity, + t.item_name, + t.quantity > 1 ? "s" : "", + t.buyer_name, + DetermineMoneyString(t.price)) + .c_str()); + } + + CharacterOfflineTransactionsRepository::DeleteWhere( + database, fmt::format("`character_id` = '{}' AND `type` = '{}'", CharacterID(), TRADER_TRANSACTION) + ); + } + + auto offline_transactions_buyer = CharacterOfflineTransactionsRepository::GetWhere( + database, fmt::format("`character_id` = '{}' AND `type` = '{}'", CharacterID(), BUYER_TRANSACTION) + ); + if (offline_transactions_buyer.size() > 0) { + Message(Chat::Yellow, "You bought the following items while in offline buyer mode:"); + + for (auto const &t: offline_transactions_buyer) { + Message( + Chat::Yellow, + fmt::format( + "You bought {} {}{} from {} for {}.", + t.quantity, + t.item_name, + t.quantity > 1 ? "s" : "", + t.buyer_name, + DetermineMoneyString(t.price)) + .c_str()); + } + + CharacterOfflineTransactionsRepository::DeleteWhere( + database, fmt::format("`character_id` = '{}' AND `type` = '{}'", CharacterID(), BUYER_TRANSACTION) + ); + } + if(ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) { SendParcelStatus(); } @@ -900,7 +950,7 @@ void Client::CompleteConnect() SendGuildMembersList(); } - guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), zone->GetZoneID(), time(nullptr)); + guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), zone->GetZoneID(), time(nullptr), 0); SendGuildList(); if (GetGuildListDirty()) { @@ -17360,3 +17410,51 @@ void Client::SyncWorldPositionsToClient(bool ignore_idle) m_is_idle = false; } } + + +void Client::Handle_OP_Offline(const EQApplicationPacket *app) +{ + if (IsThereACustomer()) { + auto customer = entity_list.GetClientByID(GetCustomerID()); + if (customer) { + auto end_session = new EQApplicationPacket(OP_ShopEnd); + customer->FastQueuePacket(&end_session); + } + } + + AccountRepository::SetOfflineStatus(database, AccountID(), true); + SetOffline(true); + + EQStreamInterface *eqsi = nullptr; + auto offline_client = new Client(eqsi); + + database.LoadCharacterData(CharacterID(), &offline_client->GetPP(), &offline_client->GetEPP()); + offline_client->Clone(*this); + offline_client->GetInv().SetGMInventory(true); + offline_client->SetPosition(GetX(), GetY(), GetZ()); + offline_client->SetHeading(GetHeading()); + offline_client->SetSpawned(); + offline_client->SetBecomeNPC(false); + offline_client->SetOffline(true); + entity_list.AddClient(offline_client); + + if (IsBuyer()) { + offline_client->SetBuyerID(offline_client->CharacterID()); + if (!BuyerRepository::UpdateBuyerEntityID(database, CharacterID(), GetID(), offline_client->GetID())) { + entity_list.RemoveMob(offline_client->CastToMob()->GetID()); + return; + } + } + else { + offline_client->SetTrader(true); + } + + OnDisconnect(true); + + auto outapp = new EQApplicationPacket(); + offline_client->CreateSpawnPacket(outapp); + entity_list.QueueClients(nullptr, outapp, false); + safe_delete(outapp); + + offline_client->UpdateWho(3); +} diff --git a/zone/client_packet.h b/zone/client_packet.h index 0840f5edc..8c606d1ee 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -226,6 +226,7 @@ void Handle_OP_MoveCoin(const EQApplicationPacket *app); void Handle_OP_MoveItem(const EQApplicationPacket *app); void Handle_OP_MoveMultipleItems(const EQApplicationPacket *app); + void Handle_OP_Offline(const EQApplicationPacket *app); void Handle_OP_OpenContainer(const EQApplicationPacket *app); void Handle_OP_OpenGuildTributeMaster(const EQApplicationPacket *app); void Handle_OP_OpenInventory(const EQApplicationPacket *app); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index e14985763..ee7d8272b 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -178,7 +178,7 @@ bool Client::Process() { } if (IsInAGuild()) { guild_mgr.UpdateDbMemberOnline(CharacterID(), false); - guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr)); + guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr), 0); } SetDynamicZoneMemberStatus(DynamicZoneMemberStatus::Offline); @@ -207,7 +207,7 @@ bool Client::Process() { Save(); if (IsInAGuild()) { guild_mgr.UpdateDbMemberOnline(CharacterID(), false); - guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr)); + guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr), 0); } if (GetMerc()) @@ -588,7 +588,7 @@ bool Client::Process() { return false; } - if (client_state != CLIENT_LINKDEAD && !eqs->CheckState(ESTABLISHED)) { + if (eqs && client_state != CLIENT_LINKDEAD && !eqs->CheckState(ESTABLISHED)) { OnDisconnect(true); LogInfo("Client linkdead: {}", name); @@ -599,7 +599,7 @@ bool Client::Process() { } if (IsInAGuild()) { guild_mgr.UpdateDbMemberOnline(CharacterID(), false); - guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr)); + guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr), 0); } return false; @@ -617,7 +617,7 @@ bool Client::Process() { /************ Get all packets from packet manager out queue and process them ************/ EQApplicationPacket *app = nullptr; - if (!eqs->CheckState(CLOSING)) + if (eqs && !eqs->CheckState(CLOSING)) { while (app = eqs->PopPacket()) { HandlePacket(app); @@ -627,7 +627,7 @@ bool Client::Process() { ClientToNpcAggroProcess(); - if (client_state != CLIENT_LINKDEAD && (client_state == CLIENT_ERROR || client_state == DISCONNECTED || client_state == CLIENT_KICKED || !eqs->CheckState(ESTABLISHED))) + if (eqs && client_state != CLIENT_LINKDEAD && (client_state == CLIENT_ERROR || client_state == DISCONNECTED || client_state == CLIENT_KICKED || !eqs->CheckState(ESTABLISHED))) { //client logged out or errored out //ResetTrade(); diff --git a/zone/entity.cpp b/zone/entity.cpp index 37fa9b4ad..ccd9c921e 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -5006,15 +5006,31 @@ void EntityList::ZoneWho(Client *c, Who_All_Struct *Who) strcpy(WAPP1->Name, ClientEntry->GetName()); Buffer += sizeof(WhoAllPlayerPart1) + strlen(WAPP1->Name); WhoAllPlayerPart2* WAPP2 = (WhoAllPlayerPart2*)Buffer; + WAPP2->RankMSGID = 0xFFFFFFFF; - if (ClientEntry->IsTrader()) - WAPP2->RankMSGID = 12315; - else if (ClientEntry->IsBuyer()) - WAPP2->RankMSGID = 6056; - else if (ClientEntry->Admin() >= AccountStatus::Steward && ClientEntry->GetGM()) + if (ClientEntry->IsOffline()) { + if (ClientEntry->IsTrader()) { + WAPP1->PIDMSGID = 0x0430; + } + if (ClientEntry->IsBuyer()) { + WAPP1->PIDMSGID = 0x0420; + } + } + else { + if (ClientEntry->IsTrader()) { + WAPP2->RankMSGID = 12315; + } + else if (ClientEntry->IsBuyer()) { + WAPP2->RankMSGID = 6056; + } + } + + if (ClientEntry->Admin() >= AccountStatus::Steward && ClientEntry->GetGM()) { WAPP2->RankMSGID = 12312; - else + } + else { WAPP2->RankMSGID = 0xFFFFFFFF; + } strcpy(WAPP2->Guild, GuildName.c_str()); Buffer += sizeof(WhoAllPlayerPart2) + strlen(WAPP2->Guild); diff --git a/zone/guild_mgr.cpp b/zone/guild_mgr.cpp index aa2d9fd70..4f167cc45 100644 --- a/zone/guild_mgr.cpp +++ b/zone/guild_mgr.cpp @@ -426,10 +426,11 @@ void ZoneGuildManager::ProcessWorldPacket(ServerPacket *pack) auto outapp = new EQApplicationPacket(OP_GuildMemberUpdate, sizeof(GuildMemberUpdate_Struct)); auto gmus = (GuildMemberUpdate_Struct *) outapp->pBuffer; - gmus->GuildID = sgmus->guild_id; - gmus->ZoneID = sgmus->zone_id; - gmus->InstanceID = 0; - gmus->LastSeen = sgmus->last_seen; + gmus->GuildID = sgmus->guild_id; + gmus->ZoneID = sgmus->zone_id; + gmus->InstanceID = 0; + gmus->LastSeen = sgmus->last_seen; + gmus->offline_mode = sgmus->offline_mode; strn0cpy(gmus->MemberName, sgmus->member_name, sizeof(gmus->MemberName)); entity_list.QueueClientsGuild(outapp, sgmus->guild_id); @@ -652,17 +653,24 @@ void ZoneGuildManager::ProcessWorldPacket(ServerPacket *pack) } } -void ZoneGuildManager::SendGuildMemberUpdateToWorld(const char *MemberName, uint32 GuildID, uint16 ZoneID, uint32 LastSeen) +void ZoneGuildManager::SendGuildMemberUpdateToWorld( + const char *MemberName, + uint32 GuildID, + uint16 ZoneID, + uint32 LastSeen, + uint32 offline_mode +) { auto pack = new ServerPacket(ServerOP_GuildMemberUpdate, sizeof(ServerGuildMemberUpdate_Struct)); - ServerGuildMemberUpdate_Struct *sgmus = (ServerGuildMemberUpdate_Struct*)pack->pBuffer; - sgmus->guild_id = GuildID; + auto sgmus = (ServerGuildMemberUpdate_Struct *) pack->pBuffer; + sgmus->guild_id = GuildID; + sgmus->zone_id = ZoneID; + sgmus->last_seen = LastSeen; + sgmus->offline_mode = offline_mode; strn0cpy(sgmus->member_name, MemberName, sizeof(sgmus->member_name)); - sgmus->zone_id = ZoneID; - sgmus->last_seen = LastSeen; - worldserver.SendPacket(pack); + worldserver.SendPacket(pack); safe_delete(pack); } @@ -1517,14 +1525,12 @@ uint8* ZoneGuildManager::MakeGuildMembers(uint32 guild_id, const char* prefix_na PutField(total_tribute); PutField(last_tribute); SlideStructString(note_buf, ci->public_note); - //e->zoneinstance = 0; - if (ci->online) { - e->zone_id = ci->zone_id; //This routine, if there is a zone_id, will update the entire guild window (roster, notes, tribute) for online characters. + e->zone_id = 0; //If zone_id is 0 and we rely on the current world routine, the notes/tribute tabs are not updated for online characters. + e->offline_mode = 0; + if (ci->online || ci->offline_mode) { + e->zone_id = ci->zone_id; //This routine, if there is a zone_id, will update the entire guild window (roster, notes, tribute) for online characters. + e->offline_mode = ci->offline_mode; } - else { - e->zone_id = 0; //If zone_id is 0 and we rely on the current world routine, the notes/tribute tabs are not updated for online characters. - } - #undef SlideStructString #undef PutFieldN diff --git a/zone/guild_mgr.h b/zone/guild_mgr.h index 0b59bd1f6..57874a7f4 100644 --- a/zone/guild_mgr.h +++ b/zone/guild_mgr.h @@ -89,7 +89,7 @@ public: void RecordInvite(uint32 char_id, uint32 guild_id, uint8 rank); bool VerifyAndClearInvite(uint32 char_id, uint32 guild_id, uint8 rank); - void SendGuildMemberUpdateToWorld(const char *MemberName, uint32 GuildID, uint16 ZoneID, uint32 LastSeen); + void SendGuildMemberUpdateToWorld(const char *MemberName, uint32 GuildID, uint16 ZoneID, uint32 LastSeen, uint32 offline_mode); void RequestOnlineGuildMembers(uint32 FromID, uint32 GuildID); void UpdateRankPermission(uint32 gid, uint32 charid, uint32 fid, uint32 rank, uint32 value); void SendPermissionUpdate(uint32 guild_id, uint32 rank, uint32 function_id, uint32 value); diff --git a/zone/mob.h b/zone/mob.h index ca0abe268..80bdf9ba5 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -1952,6 +1952,105 @@ private: void DoSpellInterrupt(uint16 spell_id, int32 mana_cost, int my_curmana); void HandleDoorOpen(); + + public: + Mob* GetMob() { return this; } + + void CloneMob(Mob& in) { + strn0cpy(name, in.name, 64); + strn0cpy(orig_name, in.orig_name, 64); + strn0cpy(lastname, in.lastname, 64); + current_hp = in.current_hp; + max_hp = in.max_hp; + base_hp = in.base_hp; + gender = in.gender; + race = in.race; + base_gender = in.base_gender; + base_race = in.race; + use_model = in.use_model; + class_ = in.class_; + bodytype = in.bodytype; + orig_bodytype = in.orig_bodytype; + deity = in.deity; + level = in.level; + orig_level = in.orig_level; + npctype_id = in.npctype_id; + size = in.size; + base_size = in.base_size; + runspeed = in.runspeed; + texture = in.texture; + helmtexture = in.helmtexture; + armtexture = in.armtexture; + bracertexture = in.bracertexture; + handtexture = in.handtexture; + legtexture = in.legtexture; + feettexture = in.feettexture; + multitexture = in.multitexture; + haircolor = in.haircolor; + beardcolor = in.beardcolor; + eyecolor1 = in.eyecolor1; + eyecolor2 = in.eyecolor2; + hairstyle = in.hairstyle; + luclinface = in.luclinface; + beard = in.beard; + drakkin_heritage = in.drakkin_heritage; + drakkin_tattoo = in.drakkin_tattoo; + drakkin_details = in.drakkin_details; + attack_speed = in.attack_speed; + attack_delay = in.attack_delay; + slow_mitigation = in.slow_mitigation; + findable = in.findable; + trackable = in.trackable; + has_shield_equipped = in.has_shield_equipped; + has_two_hand_blunt_equipped = in.has_two_hand_blunt_equipped; + has_two_hander_equipped = in.has_two_hander_equipped; + has_dual_weapons_equipped = in.has_dual_weapons_equipped; + can_facestab = in.can_facestab; + has_numhits = in.has_numhits; + has_MGB = in.has_MGB; + has_ProjectIllusion = in.has_ProjectIllusion; + SpellPowerDistanceMod = in.SpellPowerDistanceMod; + last_los_check = in.last_los_check; + aa_title = in.aa_title; + AC = in.AC; + ATK = in.ATK; + STR = in.STR; + STA = in.STA; + DEX = in.DEX; + AGI = in.AGI; + INT = in.INT; + WIS = in.WIS; + CHA = in.CHA; + MR = in.MR; + extra_haste = in.extra_haste; + bEnraged = in.bEnraged; + current_mana = in.current_mana; + max_mana = in.max_mana; + hp_regen = in.hp_regen; + hp_regen_per_second = in.hp_regen_per_second; + mana_regen = in.mana_regen; + ooc_regen = in.ooc_regen; + maxlevel = in.maxlevel; + scalerate = in.scalerate; + invisible = in.invisible; + invisible_undead = in.invisible_undead; + invisible_animals = in.invisible_animals; + sneaking = in.sneaking; + hidden = in.hidden; + improved_hidden = in.improved_hidden; + invulnerable = in.invulnerable; + qglobal = in.qglobal; + spawned = in.spawned; + rare_spawn = in.rare_spawn; + always_aggro = in.always_aggro; + heroic_strikethrough = in.heroic_strikethrough; + keeps_sold_items = in.keeps_sold_items; + + for (int i = 0; i < MAX_APPEARANCE_EFFECTS; i++) { + appearance_effects_id[i] = in.appearance_effects_id[i]; + appearance_effects_slot[i] = in.appearance_effects_slot[i]; + } + } }; #endif diff --git a/zone/trading.cpp b/zone/trading.cpp index bf7546f1d..409211669 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -26,6 +26,8 @@ #include "../common/repositories/trader_repository.h" #include "../common/repositories/buyer_repository.h" #include "../common/repositories/buyer_buy_lines_repository.h" +#include "../common/repositories/character_offline_transactions_repository.h" +#include "../common/repositories/account_repository.h" #include "client.h" #include "entity.h" @@ -907,6 +909,7 @@ void Client::TraderStartTrader(const EQApplicationPacket *app) SetTrader(true); SendTraderMode(TraderOn); SendBecomeTraderToWorld(this, TraderOn); + UpdateWho(); LogTrading("Trader Mode ON for Player [{}] with client version {}.", GetCleanName(), (uint32) ClientVersion()); } @@ -927,6 +930,7 @@ void Client::TraderEndTrader() WithCustomer(0); SetTrader(false); + UpdateWho(); } void Client::SendTraderItem(uint32 ItemID, uint16 Quantity, TraderRepository::Trader &t) { @@ -1402,22 +1406,6 @@ void Client::TradeRequestFailed(TraderBuy_Struct &in) QueuePacket(&outapp); } -// static void BazaarAuditTrail(const char *seller, const char *buyer, const char *itemName, int quantity, int totalCost, int tranType) { -// -// const std::string& query = fmt::format( -// "INSERT INTO `trader_audit` " -// "(`time`, `seller`, `buyer`, `itemname`, `quantity`, `totalcost`, `trantype`) " -// "VALUES (NOW(), '{}', '{}', '{}', {}, {}, {})", -// seller, -// buyer, -// Strings::Escape(itemName), -// quantity, -// totalCost, -// tranType -// ); -// database.QueryDatabase(query); -// } - void Client::BuyTraderItem(const EQApplicationPacket *app) { auto in = reinterpret_cast(app->pBuffer); @@ -1594,6 +1582,7 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) .charges = buy_inst->GetCharges(), .total_cost = total_cost, .player_money_balance = GetCarriedMoney(), + .offline_purchase = trader->IsOffline(), }; RecordPlayerEventLog(PlayerEvent::TRADER_PURCHASE, e); @@ -1616,9 +1605,22 @@ void Client::BuyTraderItem(const EQApplicationPacket *app) .charges = buy_inst->GetCharges(), .total_cost = total_cost, .player_money_balance = trader->GetCarriedMoney(), + .offline_purchase = trader->IsOffline(), }; RecordPlayerEventLogWithClient(trader, PlayerEvent::TRADER_SELL, e); + + if (trader->IsOffline()) { + auto e = CharacterOfflineTransactionsRepository::NewEntity(); + e.character_id = trader->CharacterID(); + e.item_name = buy_inst->GetItem()->Name; + e.price = total_cost; + e.quantity = quantity; + e.type = TRADER_TRANSACTION; + e.buyer_name = GetCleanName(); + + CharacterOfflineTransactionsRepository::InsertOne(database, e); + } } } @@ -1972,12 +1974,18 @@ void Client::SellToBuyer(const EQApplicationPacket *app) } if (!DoBarterBuyerChecks(sell_line)) { + SendBarterBuyerClientMessage( + sell_line, Barter_SellerTransactionComplete, Barter_Failure, Barter_Failure + ); return; - }; + } if (!DoBarterSellerChecks(sell_line)) { + SendBarterBuyerClientMessage( + sell_line, Barter_SellerTransactionComplete, Barter_Failure, Barter_Failure + ); return; - }; + } BuyerRepository::UpdateTransactionDate(database, sell_line.buyer_id, time(nullptr)); @@ -2078,6 +2086,18 @@ void Client::SellToBuyer(const EQApplicationPacket *app) RecordPlayerEventLog(PlayerEvent::BARTER_TRANSACTION, e); } + if (buyer->IsOffline()) { + auto e = CharacterOfflineTransactionsRepository::NewEntity(); + e.character_id = buyer->CharacterID(); + e.item_name = sell_line.item_name; + e.price = total_cost; + e.quantity = sell_line.seller_quantity; + e.type = BUYER_TRANSACTION; + e.buyer_name = GetCleanName(); + + CharacterOfflineTransactionsRepository::InsertOne(database, e); + } + SendWindowUpdatesToSellerAndBuyer(sell_line); SendBarterBuyerClientMessage( sell_line, @@ -2220,6 +2240,7 @@ void Client::ToggleBuyerMode(bool status) SetCustomerID(0); SendBuyerMode(true); SendBuyerToBarterWindow(this, Barter_AddToBarterWindow); + UpdateWho(); Message(Chat::Yellow, "Barter Mode ON."); } else { @@ -2232,6 +2253,8 @@ void Client::ToggleBuyerMode(bool status) if (!IsInBuyerSpace()) { Message(Chat::Red, "You must be in a Barter Stall to start Barter Mode."); } + + UpdateWho(); Message(Chat::Yellow, fmt::format("Barter Mode OFF. Buy lines deactivated.").c_str()); } @@ -2793,8 +2816,9 @@ std::string Client::DetermineMoneyString(uint64 cp) void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) { - auto in = reinterpret_cast(app->pBuffer); - auto trader_item = TraderRepository::GetItemByItemUniqueNumber(database, in->item_unique_id); + auto in = reinterpret_cast(app->pBuffer); + auto trader_item = TraderRepository::GetItemByItemUniqueNumber(database, in->item_unique_id); + auto offline = AccountRepository::GetAllOfflineStatus(database, trader_item.char_id); LogTradingDetail( "Packet details: \n" diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 4c01e7a38..1c03b0f21 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -62,6 +62,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/skill_caps.h" #include "../common/server_reload_types.h" #include "queryserv.h" +#include "../common/repositories/account_repository.h" +#include "../common/repositories/character_offline_transactions_repository.h" extern EntityList entity_list; extern Zone *zone; @@ -3876,10 +3878,23 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) .charges = in->item_charges, .total_cost = total_cost, .player_money_balance = trader_pc->GetCarriedMoney(), + .offline_purchase = trader_pc->IsOffline(), }; RecordPlayerEventLogWithClient(trader_pc, PlayerEvent::TRADER_SELL, e); } + if (trader_pc->IsOffline()) { + auto e = CharacterOfflineTransactionsRepository::NewEntity(); + e.character_id = trader_pc->CharacterID(); + e.item_name = in->trader_buy_struct.item_name; + e.price = in->trader_buy_struct.price * in->trader_buy_struct.quantity; + e.quantity = in->trader_buy_struct.quantity; + e.type = TRADER_TRANSACTION; + e.buyer_name = in->trader_buy_struct.buyer_name; + + CharacterOfflineTransactionsRepository::InsertOne(database, e); + } + in->transaction_status = BazaarPurchaseSuccess; TraderRepository::UpdateActiveTransaction(database, in->id, false); worldserver.SendPacket(pack); @@ -4274,6 +4289,18 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) RecordPlayerEventLogWithClient(buyer, PlayerEvent::BARTER_TRANSACTION, e); } + if (buyer->IsOffline()) { + auto e = CharacterOfflineTransactionsRepository::NewEntity(); + e.character_id = buyer->CharacterID(); + e.item_name = sell_line.item_name; + e.price = (uint64) sell_line.item_cost * (uint64) in->seller_quantity; + e.quantity = sell_line.seller_quantity; + e.type = BUYER_TRANSACTION; + e.buyer_name = sell_line.seller_name; + + CharacterOfflineTransactionsRepository::InsertOne(database, e); + } + in->action = Barter_BuyerTransactionComplete; worldserver.SendPacket(pack); @@ -4334,8 +4361,103 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) break; } + break; } + break; } + case ServerOP_UsertoWorldCancelOfflineRequest: { + auto in = reinterpret_cast(pack->pBuffer); + auto client = entity_list.GetClientByLSID(in->lsaccountid); + if (!client) { + LogLoginserverDetail("Step 6a(1) - Zone received ServerOP_UsertoWorldCancelOfflineRequest though could " + "not find client." + ); + + auto e = AccountRepository::GetWhere(database, fmt::format("`lsaccount_id` = '{}'", in->lsaccountid)); + if (!e.empty()) { + auto r = e.front(); + r.offline = 0; + AccountRepository::UpdateOne(database, r); + LogLoginserverDetail( + "Step 6a(2) - Zone cleared offline status in account table for user id {} / {}", + r.lsaccount_id, + r.charname + ); + } + + + auto sp = new ServerPacket(ServerOP_UsertoWorldCancelOfflineResponse, pack->size); + auto out = reinterpret_cast(sp->pBuffer); + sp->opcode = ServerOP_UsertoWorldCancelOfflineResponse; + out->FromID = in->FromID; + out->lsaccountid = in->lsaccountid; + out->response = in->response; + out->ToID = in->ToID; + out->worldid = in->worldid; + strn0cpy(out->login, in->login, 64); + + LogLoginserverDetail("Step 6a(3) - Zone sending ServerOP_UsertoWorldCancelOfflineResponse back to world"); + worldserver.SendPacket(sp); + safe_delete(sp); + break; + } + + LogLoginserverDetail( + "Step 6b(1) - Zone received ServerOP_UsertoWorldCancelOfflineRequest and found client {}", + client->GetCleanName() + ); + LogLoginserverDetail( + "Step 6b(2) - Zone cleared offline status in account table for user id {} / {}", + client->CharacterID(), + client->GetCleanName() + ); + AccountRepository::SetOfflineStatus(database, client->AccountID(), false); + + if (client->IsThereACustomer()) { + auto customer = entity_list.GetClientByID(client->GetCustomerID()); + if (customer) { + auto end_session = new EQApplicationPacket(OP_ShopEnd); + customer->FastQueuePacket(&end_session); + } + } + + if (client->IsTrader()) { + LogLoginserverDetail("Step 6b(3) - Zone ending trader mode for client {}", client->GetCleanName()); + client->TraderEndTrader(); + } + + if (client->IsBuyer()) { + LogLoginserverDetail("Step 6b(4) - Zone ending buyer mode for client {}", client->GetCleanName()); + client->ToggleBuyerMode(false); + } + + LogLoginserverDetail("Step 6b(5) - Zone updating UpdateWho(2) for client {}", client->GetCleanName()); + client->UpdateWho(2); + + auto outapp = new EQApplicationPacket(); + LogLoginserverDetail("Step 6b(6) - Zone sending despawn packet for client {}", client->GetCleanName()); + client->CreateDespawnPacket(outapp, false); + entity_list.QueueClients(nullptr, outapp, false); + safe_delete(outapp); + + LogLoginserverDetail("Step 6b(7) - Zone removing client from entity_list"); + entity_list.RemoveMob(client->CastToMob()->GetID()); + + auto sp = new ServerPacket(ServerOP_UsertoWorldCancelOfflineResponse, pack->size); + auto out = reinterpret_cast(sp->pBuffer); + sp->opcode = ServerOP_UsertoWorldCancelOfflineResponse; + out->FromID = in->FromID; + out->lsaccountid = in->lsaccountid; + out->response = in->response; + out->ToID = in->ToID; + out->worldid = in->worldid; + strn0cpy(out->login, in->login, 64); + + LogLoginserverDetail("Step 6b(8) - Zone sending ServerOP_UsertoWorldCancelOfflineResponse back to world"); + worldserver.SendPacket(sp); + safe_delete(sp); + break; + } default: { LogInfo("Unknown ZS Opcode [{}] size [{}]", (int) pack->opcode, pack->size); break; From 3617b24e1053d9e41f3f45372a089a9f3e68b11f Mon Sep 17 00:00:00 2001 From: neckkola <65987027+neckkola@users.noreply.github.com> Date: Sun, 30 Mar 2025 13:20:28 -0300 Subject: [PATCH 31/48] Fix a type in patches_rof2 in utils Fix a few compiler warnings --- utils/patches/patch_RoF2.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index f01d7d762..b14f9d230 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -751,4 +751,4 @@ OP_InvokeNameChangeImmediate=0x4fe2 OP_InvokeNameChangeLazy=0x2f2e #Offline Trading Mode - OP_Offline=0x53d3 \ No newline at end of file +OP_Offline=0x53d3 From b38d30e8ffe3d04370198a0ce53278de86397ea4 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 4 May 2025 19:00:45 -0300 Subject: [PATCH 32/48] Fix Merge issues --- common/repositories/trader_repository.h | 6 +++--- common/servertalk.h | 1 + world/login_server.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/common/repositories/trader_repository.h b/common/repositories/trader_repository.h index 5afb76c33..783166eb4 100644 --- a/common/repositories/trader_repository.h +++ b/common/repositories/trader_repository.h @@ -399,9 +399,9 @@ public: static Trader GetAccountZoneIdAndInstanceIdByAccountId(Database &db, uint32 account_id) { auto trader_query = fmt::format( - "SELECT t.id, t.char_id, t.char_zone_id, t.char_zone_instance_id " + "SELECT t.id, t.character_id, t.char_zone_id, t.char_zone_instance_id " "FROM trader AS t " - "WHERE t.char_id IN(SELECT c.id FROM character_data AS c WHERE c.account_id = '{}') " + "WHERE t.character_id IN(SELECT c.id FROM character_data AS c WHERE c.account_id = '{}') " "LIMIT 1;", account_id ); @@ -432,7 +432,7 @@ public: } e.id = row[0] ? strtoull(row[0], nullptr, 10) : 0; - e.char_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; + e.character_id = row[1] ? static_cast(strtoul(row[1], nullptr, 10)) : 0; e.char_zone_id = row[2] ? static_cast(strtoul(row[2], nullptr, 10)) : 0; e.char_zone_instance_id = row[3] ? static_cast(atoi(row[3])) : 0; diff --git a/common/servertalk.h b/common/servertalk.h index 791434305..0e3beffc2 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -1799,6 +1799,7 @@ struct BazaarPurchaseMessaging_Struct { uint32 buyer_zone_id; uint32 buyer_zone_instance_id; uint32 transaction_status; + bool offline_purchase; }; diff --git a/world/login_server.cpp b/world/login_server.cpp index 00aaa0a6e..5d94394b2 100644 --- a/world/login_server.cpp +++ b/world/login_server.cpp @@ -790,7 +790,7 @@ void LoginServer::ProcessUserToWorldCancelOfflineRequest(uint16_t opcode, EQ::Ne AccountRepository::SetOfflineStatus(database, id, false); LogLoginserverDetail("Step 5b(3) - World clearing trader and buyer tablese."); - TraderRepository::DeleteWhere(database, fmt::format("`char_id` = '{}'", trader.id)); + TraderRepository::DeleteWhere(database, fmt::format("`character_id` = '{}'", trader.id)); BuyerRepository::DeleteBuyer(database, trader.id); server_packet.opcode = ServerOP_UsertoWorldCancelOfflineResponse; From cb79d805c44948de710f8084e398d19b8e416cf8 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 4 May 2025 19:46:44 -0300 Subject: [PATCH 33/48] Fix db mainifest --- common/database/database_update_manifest.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 01c8d5228..57c9750d3 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -7217,7 +7217,11 @@ ALTER TABLE `inventory_snapshots` CHANGE COLUMN `ornamenticon` `ornament_icon` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `custom_data`, CHANGE COLUMN `ornamentidfile` `ornament_idfile` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `ornament_icon`, ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `ornament_hero_model`; - DROP PRIMARY KEY, + +ALTER TABLE `inventory_snapshots` + DROP PRIMARY KEY; + +ALTER TABLE `inventory_snapshots` ADD PRIMARY KEY (`time_index`, `character_id`, `slot_id`) USING BTREE; ALTER TABLE `trader` @@ -7231,9 +7235,8 @@ ALTER TABLE `trader` CHANGE COLUMN `aug_slot_6` `augment_six` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `augment_five`, DROP COLUMN `item_sn`, DROP INDEX `idx_trader_item_sn`, - DROP INDEX `charid_slotid`, - ADD INDEX `charid_slotid` (`character_id`, `slot_id`) USING BTREE, DROP INDEX `idx_trader_char`, + ADD INDEX `charid_slotid` (`character_id`, `slot_id`) USING BTREE, ADD INDEX `idx_trader_char` (`character_id`, `char_zone_id`, `char_zone_instance_id`) USING BTREE, ADD UNIQUE INDEX `idx_item_unique_id` (`item_unique_id`); From 2a76c60f6364651482fa4cd9053f862bc4bb37c1 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 4 May 2025 20:37:43 -0300 Subject: [PATCH 34/48] Fix error messages in zone tests --- common/item_instance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/item_instance.cpp b/common/item_instance.cpp index 37c826920..7bc382fc8 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -182,7 +182,7 @@ EQ::ItemInstance::ItemInstance(const ItemInstance& copy) m_timers = copy.m_timers; if (copy.GetUniqueID().empty()) { - LogError("Creating unique item ID as part of clone process for item id {}", copy.GetID()); + LogInfo("Creating unique item ID as part of clone process for item id {}", copy.GetID()); copy.CreateUniqueID(); } m_unique_id = copy.m_unique_id; From 609beec23a6e814cbedb0f7da0d529aa69104eb1 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 4 May 2025 21:03:08 -0300 Subject: [PATCH 35/48] Fix error messages in zone tests --- .../base/base_inventory_repository.h | 68 +++++-------------- .../inventory_snapshots_repository.h | 2 - 2 files changed, 16 insertions(+), 54 deletions(-) diff --git a/common/repositories/base/base_inventory_repository.h b/common/repositories/base/base_inventory_repository.h index b02648483..7900fcfae 100644 --- a/common/repositories/base/base_inventory_repository.h +++ b/common/repositories/base/base_inventory_repository.h @@ -32,13 +32,10 @@ public: uint32_t augment_six; uint8_t instnodrop; std::string custom_data; - uint32_t ornamenticon; - uint32_t ornamentidfile; uint32_t ornament_icon; uint32_t ornament_idfile; int32_t ornament_hero_model; std::string item_unique_id; - uint32_t guid; }; static std::string PrimaryKey() @@ -62,13 +59,10 @@ public: "augment_six", "instnodrop", "custom_data", - "ornamenticon", - "ornamentidfile", "ornament_icon", "ornament_idfile", "ornament_hero_model", "item_unique_id", - "guid", }; } @@ -88,13 +82,10 @@ public: "augment_six", "instnodrop", "custom_data", - "ornamenticon", - "ornamentidfile", "ornament_icon", "ornament_idfile", "ornament_hero_model", "item_unique_id", - "guid", }; } @@ -148,13 +139,10 @@ public: e.augment_six = 0; e.instnodrop = 0; e.custom_data = ""; - e.ornamenticon = 0; - e.ornamentidfile = 0; e.ornament_icon = 0; e.ornament_idfile = 0; e.ornament_hero_model = 0; e.item_unique_id = ""; - e.guid = 0; return e; } @@ -204,13 +192,10 @@ public: e.augment_six = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; e.instnodrop = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.custom_data = row[12] ? row[12] : ""; - 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_icon = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; - e.ornament_idfile = row[16] ? static_cast(strtoul(row[16], nullptr, 10)) : 0; - e.ornament_hero_model = row[17] ? static_cast(atoi(row[17])) : 0; - e.item_unique_id = row[18] ? row[18] : ""; - e.guid = row[19] ? static_cast(strtoul(row[19], nullptr, 10)) : 0; + e.ornament_icon = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.ornament_idfile = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.ornament_hero_model = row[15] ? static_cast(atoi(row[15])) : 0; + e.item_unique_id = row[16] ? row[16] : ""; return e; } @@ -257,13 +242,10 @@ public: v.push_back(columns[10] + " = " + std::to_string(e.augment_six)); v.push_back(columns[11] + " = " + std::to_string(e.instnodrop)); v.push_back(columns[12] + " = '" + Strings::Escape(e.custom_data) + "'"); - 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_icon)); - v.push_back(columns[16] + " = " + std::to_string(e.ornament_idfile)); - v.push_back(columns[17] + " = " + std::to_string(e.ornament_hero_model)); - v.push_back(columns[18] + " = '" + Strings::Escape(e.item_unique_id) + "'"); - v.push_back(columns[19] + " = " + std::to_string(e.guid)); + v.push_back(columns[13] + " = " + std::to_string(e.ornament_icon)); + v.push_back(columns[14] + " = " + std::to_string(e.ornament_idfile)); + v.push_back(columns[15] + " = " + std::to_string(e.ornament_hero_model)); + v.push_back(columns[16] + " = '" + Strings::Escape(e.item_unique_id) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -298,13 +280,10 @@ public: v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.instnodrop)); v.push_back("'" + Strings::Escape(e.custom_data) + "'"); - v.push_back(std::to_string(e.ornamenticon)); - v.push_back(std::to_string(e.ornamentidfile)); v.push_back(std::to_string(e.ornament_icon)); v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); - v.push_back(std::to_string(e.guid)); auto results = db.QueryDatabase( fmt::format( @@ -347,13 +326,10 @@ public: v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.instnodrop)); v.push_back("'" + Strings::Escape(e.custom_data) + "'"); - v.push_back(std::to_string(e.ornamenticon)); - v.push_back(std::to_string(e.ornamentidfile)); v.push_back(std::to_string(e.ornament_icon)); v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); - v.push_back(std::to_string(e.guid)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -400,13 +376,10 @@ public: e.augment_six = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; e.instnodrop = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.custom_data = row[12] ? row[12] : ""; - 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_icon = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; - e.ornament_idfile = row[16] ? static_cast(strtoul(row[16], nullptr, 10)) : 0; - e.ornament_hero_model = row[17] ? static_cast(atoi(row[17])) : 0; - e.item_unique_id = row[18] ? row[18] : ""; - e.guid = row[19] ? static_cast(strtoul(row[19], nullptr, 10)) : 0; + e.ornament_icon = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.ornament_idfile = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.ornament_hero_model = row[15] ? static_cast(atoi(row[15])) : 0; + e.item_unique_id = row[16] ? row[16] : ""; all_entries.push_back(e); } @@ -444,13 +417,10 @@ public: e.augment_six = row[10] ? static_cast(strtoul(row[10], nullptr, 10)) : 0; e.instnodrop = row[11] ? static_cast(strtoul(row[11], nullptr, 10)) : 0; e.custom_data = row[12] ? row[12] : ""; - 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_icon = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; - e.ornament_idfile = row[16] ? static_cast(strtoul(row[16], nullptr, 10)) : 0; - e.ornament_hero_model = row[17] ? static_cast(atoi(row[17])) : 0; - e.item_unique_id = row[18] ? row[18] : ""; - e.guid = row[19] ? static_cast(strtoul(row[19], nullptr, 10)) : 0; + e.ornament_icon = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; + e.ornament_idfile = row[14] ? static_cast(strtoul(row[14], nullptr, 10)) : 0; + e.ornament_hero_model = row[15] ? static_cast(atoi(row[15])) : 0; + e.item_unique_id = row[16] ? row[16] : ""; all_entries.push_back(e); } @@ -538,13 +508,10 @@ public: v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.instnodrop)); v.push_back("'" + Strings::Escape(e.custom_data) + "'"); - v.push_back(std::to_string(e.ornamenticon)); - v.push_back(std::to_string(e.ornamentidfile)); v.push_back(std::to_string(e.ornament_icon)); v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); - v.push_back(std::to_string(e.guid)); auto results = db.QueryDatabase( fmt::format( @@ -580,13 +547,10 @@ public: v.push_back(std::to_string(e.augment_six)); v.push_back(std::to_string(e.instnodrop)); v.push_back("'" + Strings::Escape(e.custom_data) + "'"); - v.push_back(std::to_string(e.ornamenticon)); - v.push_back(std::to_string(e.ornamentidfile)); v.push_back(std::to_string(e.ornament_icon)); v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); - v.push_back(std::to_string(e.guid)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/repositories/inventory_snapshots_repository.h b/common/repositories/inventory_snapshots_repository.h index 6acf9ea8d..731dced62 100644 --- a/common/repositories/inventory_snapshots_repository.h +++ b/common/repositories/inventory_snapshots_repository.h @@ -243,7 +243,6 @@ public: snapshot.ornament_hero_model = i.ornament_hero_model; snapshot.ornament_icon = i.ornament_icon; snapshot.ornament_idfile = i.ornament_idfile; - snapshot.guid = i.guid; snapshot.slot_id = i.slot_id; snapshot.time_index = time_index; queue.push_back(snapshot); @@ -295,7 +294,6 @@ public: inventory_entry.ornament_hero_model = i.ornament_hero_model; inventory_entry.ornament_icon = i.ornament_icon; inventory_entry.ornament_idfile = i.ornament_idfile; - inventory_entry.guid = i.guid; inventory_entry.slot_id = i.slot_id; queue.push_back(inventory_entry); } From c86ddca0b9c04a26c0abacdf38de9b8ea7256f77 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Fri, 16 May 2025 21:05:57 -0300 Subject: [PATCH 36/48] Fix rebase --- zone/trading.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/zone/trading.cpp b/zone/trading.cpp index 409211669..c88c0710b 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -2818,7 +2818,6 @@ void Client::BuyTraderItemFromBazaarWindow(const EQApplicationPacket *app) { auto in = reinterpret_cast(app->pBuffer); auto trader_item = TraderRepository::GetItemByItemUniqueNumber(database, in->item_unique_id); - auto offline = AccountRepository::GetAllOfflineStatus(database, trader_item.char_id); LogTradingDetail( "Packet details: \n" From af500b14f6cdc1d138bdc6ffe7e0c09e364c74fd Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Fri, 6 Jun 2025 20:55:00 -0300 Subject: [PATCH 37/48] Update trader db calls for character_id --- zone/client_packet.cpp | 2 +- zone/trading.cpp | 8 +++++++- zone/zonedb.cpp | 32 ++++++++++++++++---------------- zone/zonedb.h | 2 +- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index b51b0ee38..34e1ea6d1 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -835,7 +835,7 @@ void Client::CompleteConnect() if (is_first_login) { e.first_login = time(nullptr); - TraderRepository::DeleteWhere(database, fmt::format("`char_id` = '{}'", CharacterID())); + TraderRepository::DeleteWhere(database, fmt::format("`character_id` = '{}'", CharacterID())); BuyerRepository::DeleteBuyer(database, CharacterID()); LogTradingDetail( "Removed trader abd buyer entries for Character ID {} on first logon to ensure table consistency.", diff --git a/zone/trading.cpp b/zone/trading.cpp index c88c0710b..18705fe46 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1783,7 +1783,13 @@ static void UpdateTraderCustomerPriceChanged( LogTrading("Sending price updates to customer [{}]", customer->GetName()); - auto it = std::find_if(trader_items.begin(), trader_items.end(), [&](TraderRepository::Trader x){ return x.item_id == item->ID;}); + auto it = std::find_if( + trader_items.begin(), + trader_items.end(), + [&](TraderRepository::Trader x) { + return x.item_id == item->ID; + } + ); std::unique_ptr inst( database.CreateItem( it->item_id, diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 20b62386c..8d8e5ee17 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -309,19 +309,19 @@ void ZoneDatabase::DeleteWorldContainer(uint32 parent_id, uint32 zone_id) ); } -std::unique_ptr ZoneDatabase::LoadSingleTraderItem(uint32 char_id, const std::string &serial_number) +std::unique_ptr ZoneDatabase::LoadSingleTraderItem(uint32 character_id, const std::string &unique_item_id) { auto results = TraderRepository::GetWhere( database, fmt::format( - "`char_id` = '{}' AND `item_sn` = '{}' ORDER BY slot_id", - char_id, - serial_number + "`character_id` = '{}' AND `item_unique_id` = '{}' ORDER BY slot_id", + character_id, + unique_item_id ) ); if (results.empty()) { - LogTrading("Could not find item serial number {} for character id {}", serial_number, char_id); + LogTrading("Could not find item serial number {} for character id {}", unique_item_id, character_id); return nullptr; } @@ -357,7 +357,7 @@ std::unique_ptr ZoneDatabase::LoadSingleTraderItem(uint32 char } inst->SetCharges(charges); - inst->SetUniqueID(serial_number); + inst->SetUniqueID(unique_item_id); //FIX inst->SetMerchantSlot(serial_number); inst->SetPrice(cost); @@ -368,9 +368,9 @@ std::unique_ptr ZoneDatabase::LoadSingleTraderItem(uint32 char return std::move(inst); } -void ZoneDatabase::UpdateTraderItemPrice(int char_id, uint32 item_id, uint32 charges, uint32 new_price) { +void ZoneDatabase::UpdateTraderItemPrice(int character_id, uint32 item_id, uint32 charges, uint32 new_price) { - LogTrading("ZoneDatabase::UpdateTraderPrice([{}], [{}], [{}], [{}])", char_id, item_id, charges, new_price); + LogTrading("ZoneDatabase::UpdateTraderPrice([{}], [{}], [{}], [{}])", character_id, item_id, charges, new_price); const EQ::ItemData *item = database.GetItem(item_id); if(!item) { @@ -378,20 +378,20 @@ void ZoneDatabase::UpdateTraderItemPrice(int char_id, uint32 item_id, uint32 cha } if (new_price == 0) { - LogTrading("Removing Trader items from the DB for char_id [{}], item_id [{}]", char_id, item_id); + LogTrading("Removing Trader items from the DB for char_id [{}], item_id [{}]", character_id, item_id); auto results = TraderRepository::DeleteWhere( database, fmt::format( - "`char_id` = '{}' AND `item_id` = {}", - char_id, + "`character_id` = '{}' AND `item_id` = {}", + character_id, item_id ) ); if (!results) { LogDebug("[CLIENT] Failed to remove trader item(s): [{}] for char_id: [{}]", item_id, - char_id + character_id ); } @@ -399,23 +399,23 @@ void ZoneDatabase::UpdateTraderItemPrice(int char_id, uint32 item_id, uint32 cha } if (!item->Stackable) { - auto results = TraderRepository::UpdateItem(database, char_id, new_price, item_id, charges); + auto results = TraderRepository::UpdateItem(database, character_id, new_price, item_id, charges); if (!results) { LogTrading( "Failed to update price for trader item [{}] for char_id: [{}]", item_id, - char_id + character_id ); } return; } - auto results = TraderRepository::UpdateItem(database, char_id, new_price, item_id, 0); + auto results = TraderRepository::UpdateItem(database, character_id, new_price, item_id, 0); if (!results) { LogTrading( "Failed to update price for trader item [{}] for char_id: [{}]", item_id, - char_id + character_id ); } } diff --git a/zone/zonedb.h b/zone/zonedb.h index 6e54108e8..03072efdd 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -390,7 +390,7 @@ public: /* Traders */ void SaveTraderItem(uint32 char_id,uint32 itemid,uint32 uniqueid, int32 charges,uint32 itemcost,uint8 slot); void UpdateTraderItemCharges(int char_id, uint32 ItemInstID, int32 charges); - void UpdateTraderItemPrice(int char_id, uint32 item_id, uint32 charges, uint32 new_price); + void UpdateTraderItemPrice(int character_id, uint32 item_id, uint32 charges, uint32 new_price); void DeleteTraderItem(uint32 char_id); void DeleteTraderItem(uint32 char_id,uint16 slot_id); From f08cf6246af20e086c953639583ca624c5c545ff Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Fri, 6 Jun 2025 21:06:59 -0300 Subject: [PATCH 38/48] Fix rebase --- zone/client.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zone/client.h b/zone/client.h index 6d97b9031..ef9d65dd6 100644 --- a/zone/client.h +++ b/zone/client.h @@ -2467,7 +2467,9 @@ public: guild_id = in.guild_id; guildrank = in.guildrank; LFG = in.LFG; - AFK = in.AFK; + m_is_afk = in.m_is_afk; + m_is_idle = in.m_is_idle; + m_is_manual_afk = in.m_is_manual_afk; trader_id = in.trader_id; m_buyer_id = in.m_buyer_id; race = in.race; From 3676f16dc31e7420eca3ba58e5bae872fd20b94b Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 8 Jun 2025 07:44:30 -0300 Subject: [PATCH 39/48] Test db migration --- common/database/database_update_manifest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 57c9750d3..e8341994c 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -7244,7 +7244,7 @@ ALTER TABLE `trader` .content_schema_update = false }, ManifestEntry{ - .version = 9325, + .version = 9327, .description = "2025_01_27_offline_account_status.sql", .check = "SHOW COLUMNS FROM `account` LIKE 'offline'", .condition = "empty", From a0f701686bb2f0fcdc65fd9eec1878e73d16e2e0 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Tue, 1 Jul 2025 21:22:59 -0300 Subject: [PATCH 40/48] Comment_Review Updates based on comment review. --- common/eq_packet_structs.h | 2 +- common/item_instance.h | 1 - common/patches/rof2.cpp | 8 ---- common/patches/titanium.cpp | 1 - common/patches/uf.cpp | 1 - common/repositories/buyer_repository.h | 2 +- common/repositories/trader_repository.h | 41 ++++--------------- loginserver/login_util/login_opcodes_sod.conf | 2 +- world/clientlist.cpp | 5 ++- zone/client.h | 3 +- zone/client_packet.cpp | 7 ++-- zone/inventory.cpp | 1 - zone/string_ids.h | 2 + zone/trading.cpp | 24 ++++------- zone/zonedb.cpp | 5 +-- 15 files changed, 30 insertions(+), 75 deletions(-) diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 8f2c1d08d..767c2273b 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -3813,7 +3813,7 @@ struct ClickTraderNew_Struct { } }; -struct GetItems2_Struct { +struct GetBazaarItems_Struct { uint64 items[EQ::invtype::BAZAAR_SIZE]; std::string serial_number[EQ::invtype::BAZAAR_SIZE]; uint32 charges[EQ::invtype::BAZAAR_SIZE]; diff --git a/common/item_instance.h b/common/item_instance.h index 501635d67..907f927d7 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -247,7 +247,6 @@ namespace EQ void SetSerialNumber(int32 id) { m_SerialNumber = id; } const std::string &GetSerialNumber2() const { return m_unique_id; } const std::string &GetUniqueID() const { return m_unique_id; } - //std::string &GetSerialNumber2() const { return m_serial_number2; } void SetUniqueID(std::string sn) { m_unique_id = std::move(sn); } void CreateUniqueID() const { m_unique_id = GenerateUniqueID(); } diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index ce2891995..9074f8e01 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -6354,14 +6354,12 @@ namespace RoF2 IN_str(buyer_name); IN_str(seller_name); IN_str(item_name); - //IN_str(serial_number); strn0cpy(emu->item_unique_id, eq->item_unique_id, sizeof(emu->item_unique_id)); FINISH_DIRECT_DECODE(); break; } default: { - //LogTradingDetail("(RoF2) Unhandled action [{}]", action); } return; } @@ -6475,12 +6473,6 @@ namespace RoF2 hdr.unknown000[16] = '\0'; } - // strn0cpy( - // hdr.unknown000, - // inst->GetSerialNumber2().empty() ? "0000000000000000" : inst->GetSerialNumber2().c_str(), - // sizeof(hdr.unknown000) - // ); - //hdr.unknown000[16] = '\0'; hdr.stacksize = 1; diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index 1320e6a2e..3d3b8ebe1 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -228,7 +228,6 @@ namespace Titanium VARSTRUCT_ENCODE_TYPE(uint32, bufptr, row->trader_entity_id); bufptr += 4; VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->item_id); - //FIX VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->serial_number); bufptr += 4; if (row->stackable) { strn0cpy( diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 90fecd6fd..057e98ad3 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -338,7 +338,6 @@ namespace UF bufptr += 64; VARSTRUCT_ENCODE_TYPE(uint32, bufptr, 1); VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->item_id); - //FIX VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->serial_number); bufptr += 4; if (row->stackable) { strn0cpy( diff --git a/common/repositories/buyer_repository.h b/common/repositories/buyer_repository.h index 579228456..fe4e23fda 100644 --- a/common/repositories/buyer_repository.h +++ b/common/repositories/buyer_repository.h @@ -187,7 +187,7 @@ public: return false; } - auto results = GetWhere(db, fmt::format("`char_id` = '{}' AND `char_entity_id` = '{}' LIMIT 1;", char_id, old_entity_id)); + auto results = GetWhere(db, fmt::format("`char_id` = {} AND `char_entity_id` = {} LIMIT 1;", char_id, old_entity_id)); if (results.empty()) { return false; diff --git a/common/repositories/trader_repository.h b/common/repositories/trader_repository.h index 783166eb4..d1cf39394 100644 --- a/common/repositories/trader_repository.h +++ b/common/repositories/trader_repository.h @@ -125,7 +125,7 @@ public: items = GetWhere( db, fmt::format( - "character_id = '{}' AND item_id = '{}'", + "character_id = {} AND item_id = {}", character_id, item_id ) @@ -135,7 +135,7 @@ public: items = GetWhere( db, fmt::format( - "character_id = '{}' AND item_id = '{}' AND item_charges = '{}'", + "character_id = {} AND item_id = {} AND item_charges = {}", character_id, item_id, item_charges @@ -161,7 +161,7 @@ public: auto query = fmt::format( "SELECT t.character_id, t.item_id, t.item_unique.id, t.charges, t.item_cost, t.slot_id, t.entity_id FROM trader AS t " - "WHERE t.entity_id = '{}' AND t.item_id = '{}' AND t.item_cost = '{}' " + "WHERE t.entity_id = {} AND t.item_id = {} AND t.item_cost = {} " "LIMIT 1;", trader_id, item_id, @@ -207,7 +207,7 @@ public: std::vector all_entries{}; const auto query = fmt::format( - "UPDATE trader t1 SET t1.`item_cost` = '{}', t1.`listing_date` = FROM_UNIXTIME({}) WHERE t1.`item_id` = " + "UPDATE trader t1 SET t1.`item_cost` = {}, t1.`listing_date` = FROM_UNIXTIME({}) WHERE t1.`item_id` = " "(SELECT t2.`item_id` FROM trader t2 WHERE t2.`item_unique_id` = '{}')", price, time(nullptr), @@ -324,28 +324,6 @@ public: { std::vector all_entries{}; - // auto query_2 = fmt::format( - // "WITH ranked_trader_items AS (" - // "SELECT trader.id, trader.character_id, trader.item_id, trader.item_unique_id, trader.augment_one, " - // "trader.augment_two, trader.augment_three, trader.augment_four, trader.augment_five, trader.augment_six, " - // "trader.item_charges, trader.item_cost, trader.slot_id, trader.char_entity_id, trader.char_zone_id, " - // "trader.char_zone_instance_id, trader.active_transaction, c.`name`, " - // "items.name AS n1, items.stackable, items.icon, {}, " - // "ROW_NUMBER() OVER (PARTITION BY trader.character_id) AS row_num " - // "FROM trader " - // "INNER JOIN character_data AS c ON trader.character_id = c.id " - // "JOIN peq642024_content.items AS items ON trader.item_id = items.id " - // "WHERE items.`name` LIKE '%{}%' AND {} AND {}" - // ") " - // "SELECT * FROM ranked_trader_items " - // "WHERE row_num <= '{}';", - // field_criteria_items, - // Strings::Escape(name), - // where_criteria_items, - // search_criteria_trader, - // max_results - // ); - auto query = fmt::format( "SELECT trader.id, trader.character_id, trader.item_id, trader.item_unique_id, trader.augment_one, " "trader.augment_two, trader.augment_three, trader.augment_four, trader.augment_five, trader.augment_six, " @@ -385,10 +363,6 @@ public: e.trader.char_zone_instance_id = row[15] ? static_cast(atoi(row[15])) : 0; e.trader.active_transaction = row[16] ? static_cast(strtoul(row[16], nullptr, 10)) : 0; e.trader_name = row[17] ? row[17] : std::string(""); - // e.name = row[18] ? row[18] : ""; - // e.stackable = atoi(row[19]) ? true : false; - // e.icon = row[20] ? static_cast(atoi(row[20])) : 0; - // e.stats = row[21] ? static_cast(atoi(row[21])) : 0; all_entries.push_back(e); } @@ -401,7 +375,7 @@ public: auto trader_query = fmt::format( "SELECT t.id, t.character_id, t.char_zone_id, t.char_zone_instance_id " "FROM trader AS t " - "WHERE t.character_id IN(SELECT c.id FROM character_data AS c WHERE c.account_id = '{}') " + "WHERE t.character_id IN(SELECT c.id FROM character_data AS c WHERE c.account_id = {}) " "LIMIT 1;", account_id ); @@ -409,7 +383,7 @@ public: auto buyer_query = fmt::format( "SELECT t.id, t.char_id, t.char_zone_id, t.char_zone_instance_id " "FROM buyer AS t " - "WHERE t.char_id IN(SELECT c.id FROM character_data AS c WHERE c.account_id = '{}') " + "WHERE t.char_id IN(SELECT c.id FROM character_data AS c WHERE c.account_id = {}) " "LIMIT 1;", account_id ); @@ -440,4 +414,5 @@ public: } }; -#endif //EQEMU_TRADER_REPOSITORY_H +#endif +//EQEMU_TRADER_REPOSITORY_H diff --git a/loginserver/login_util/login_opcodes_sod.conf b/loginserver/login_util/login_opcodes_sod.conf index c91d144c5..9a21a8dcc 100644 --- a/loginserver/login_util/login_opcodes_sod.conf +++ b/loginserver/login_util/login_opcodes_sod.conf @@ -12,4 +12,4 @@ OP_LoginExpansionPacketData=0x0031 OP_EnterChat=0x000f OP_PollResponse=0x0011 OP_CancelOfflineTrader=0x0016 - OP_CancelOfflineTraderResponse=0x0030 \ No newline at end of file +OP_CancelOfflineTraderResponse=0x0030 diff --git a/world/clientlist.cpp b/world/clientlist.cpp index 7b8e99d6f..4b1b420c4 100644 --- a/world/clientlist.cpp +++ b/world/clientlist.cpp @@ -35,6 +35,7 @@ #include "wguild_mgr.h" #include "../common/zone_store.h" #include +#include "../zone/string_ids.h" uint32 numplayers = 0; //this really wants to be a member variable of ClientList... @@ -799,10 +800,10 @@ void ClientList::SendWhoAll(uint32 fromid,const char* to, int16 admin, Who_All_S continue; } else if (cle->GetTrader()) { - rankstring = 12315; + rankstring = TRADER; } else if (cle->GetBuyer()) { - rankstring = 6056; + rankstring = BUYER; } else if (cle->GetGM()) { if (cle->Admin() >= AccountStatus::GMImpossible) { diff --git a/zone/client.h b/zone/client.h index ef9d65dd6..c9ee0d2ae 100644 --- a/zone/client.h +++ b/zone/client.h @@ -314,7 +314,6 @@ public: void Trader_CustomerBrowsing(Client *Customer); void TraderEndTrader(); - //void TraderPriceUpdate(const EQApplicationPacket *app); void TraderUpdateItem(const EQApplicationPacket *app); void SendBazaarDone(uint32 trader_id); void SendBulkBazaarTraders(); @@ -351,7 +350,7 @@ public: void SendTraderPacket(Client* trader, uint32 Unknown72 = 51); void SendBuyerPacket(Client* Buyer); void SendBuyerToBarterWindow(Client* buyer, uint32 action); - GetItems2_Struct* GetTraderItems(); + GetBazaarItems_Struct* GetTraderItems(); void SendBazaarWelcome(); void SendBarterWelcome(); void DyeArmor(EQ::TintProfile* dye); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 34e1ea6d1..49cc75d1f 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -860,7 +860,7 @@ void Client::CompleteConnect() } auto offline_transactions_trader = CharacterOfflineTransactionsRepository::GetWhere( - database, fmt::format("`character_id` = '{}' AND `type` = '{}'", CharacterID(), TRADER_TRANSACTION) + database, fmt::format("`character_id` = {} AND `type` = {}", CharacterID(), TRADER_TRANSACTION) ); if (offline_transactions_trader.size() > 0) { Message(Chat::Yellow, "You sold the following items while in offline trader mode:"); @@ -884,7 +884,7 @@ void Client::CompleteConnect() } auto offline_transactions_buyer = CharacterOfflineTransactionsRepository::GetWhere( - database, fmt::format("`character_id` = '{}' AND `type` = '{}'", CharacterID(), BUYER_TRANSACTION) + database, fmt::format("`character_id` = {} AND `type` = {}", CharacterID(), BUYER_TRANSACTION) ); if (offline_transactions_buyer.size() > 0) { Message(Chat::Yellow, "You bought the following items while in offline buyer mode:"); @@ -903,7 +903,7 @@ void Client::CompleteConnect() } CharacterOfflineTransactionsRepository::DeleteWhere( - database, fmt::format("`character_id` = '{}' AND `type` = '{}'", CharacterID(), BUYER_TRANSACTION) + database, fmt::format("`character_id` = {} AND `type` = {}", CharacterID(), BUYER_TRANSACTION) ); } @@ -15506,7 +15506,6 @@ void Client::Handle_OP_Trader(const EQApplicationPacket *app) break; } default: { - //LogTradingDetail("Unknown size for OP_Trader: [{}]", app->size); } } } diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 3a8eb0260..5543e4ef2 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -651,7 +651,6 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, // put item into inventory if (to_slot == EQ::invslot::slotCursor) { PushItemOnCursor(*inst, true); - //SendItemPacket(EQ::invslot::slotCursor, inst, ItemPacketLimbo); } else { PutItemInInventory(to_slot, *inst, true); } diff --git a/zone/string_ids.h b/zone/string_ids.h index 51e981476..fdec85c12 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -416,6 +416,7 @@ #define MAX_ACTIVE_TASKS 6010 //Sorry %3, you already have the maximum number of active tasks. #define TASK_REQUEST_COOLDOWN_TIMER 6011 //Sorry, %3, but you can't request another task for %4 minutes and %5 seconds. #define FORAGE_MASTERY 6012 //Your forage mastery has enabled you to find something else! +#define BUYER 6056 //BUYER #define BUYER_WELCOME 6065 //There are %1 Buyers waiting to purchase your loot. Type /barter to search for them, or use /buyer to set up your own Buy Lines. #define BUYER_GREETING 6070 //%1 greets you, '%2' #define GUILD_BANK_CANNOT_DEPOSIT 6097 // Cannot deposit this item. Containers must be empty, and only one of each LORE and no NO TRADE or TEMPORARY items may be deposited. @@ -543,6 +544,7 @@ #define GROUP_INVITEE_NOT_FOUND 12268 //You must target a player or use /invite to invite someone to your group. #define GROUP_INVITEE_SELF 12270 //12270 You cannot invite yourself. #define ALREADY_IN_PARTY 12272 //That person is already in your party. +#define TRADER 12315 //TRADER #define TALKING_TO_SELF 12323 //Talking to yourself again? #define SPLIT_NO_GROUP 12328 //You are not in a group! Keep it all. #define NO_LONGER_HIDDEN 12337 //You are no longer hidden. diff --git a/zone/trading.cpp b/zone/trading.cpp index 18705fe46..acbcb0cde 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1085,10 +1085,8 @@ EQ::ItemInstance *Client::FindTraderItemByUniqueID(std::string &unique_id) // we already have the parent bag and a contents iterator..why not just iterate the bag!?? slot_id = EQ::InventoryProfile::CalcSlotId(i, x); item = GetInv().GetItem(slot_id); - if (item) { - if (item->GetUniqueID().compare(unique_id) == 0) { - return item; - } + if (item && item->GetUniqueID().compare(unique_id) == 0) { + return item; } } } @@ -1136,10 +1134,8 @@ std::vector Client::FindTraderItemsByUniqueID(const char* un // we already have the parent bag and a contents iterator..why not just iterate the bag!?? slot_id = EQ::InventoryProfile::CalcSlotId(i, x); item = GetInv().GetItem(slot_id); - if (item) { - if (item->GetUniqueID().compare(unique_id) == 0) { - items.push_back(item); - } + if (item && item->GetUniqueID().compare(unique_id) == 0) { + items.push_back(item); } } } @@ -1149,11 +1145,11 @@ std::vector Client::FindTraderItemsByUniqueID(const char* un return items; } -GetItems2_Struct *Client::GetTraderItems() +GetBazaarItems_Struct *Client::GetTraderItems() { const EQ::ItemInstance *item = nullptr; int16 slot_id = INVALID_INDEX; - auto gis = new GetItems2_Struct{0}; + auto gis = new GetBazaarItems_Struct{0}; uint8 ndx = 0; for (int16 i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) { @@ -1309,7 +1305,7 @@ void Client::FindAndNukeTraderItem(std::string &item_unique_id, int16 quantity, std::vector delete_queue{}; for (int i = 0; i < item_limit; i++) { - if (test_slot && trader_items.at(i).item_unique_id.compare(item_unique_id) == 0) { + if (test_slot && i < trader_items.size() && trader_items.at(i).item_unique_id.compare(item_unique_id) == 0) { delete_queue.push_back(trader_items.at(i)); NukeTraderItem( slot_id, @@ -1322,7 +1318,7 @@ void Client::FindAndNukeTraderItem(std::string &item_unique_id, int16 quantity, ); test_slot = false; } - else if (trader_items.at(i).item_id > 0) { + else if (i < trader_items.size() && trader_items.at(i).item_id > 0) { count++; } } @@ -1766,10 +1762,6 @@ static void UpdateTraderCustomerPriceChanged( // RoF+ use Item IDs for now tdis->item_id = trader_items.at(i).item_id; } - //FIX else { - // tdis->item_id = trader_items.at(i).item_sn; - // } - //tdis->item_id = trader_items.at(i).item_sn; LogTrading("Telling customer to remove item [{}] with [{}] charges and S/N [{}]", item_id, charges, trader_items.at(i).item_unique_id); diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 8d8e5ee17..6ed2ced80 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -314,7 +314,7 @@ std::unique_ptr ZoneDatabase::LoadSingleTraderItem(uint32 char auto results = TraderRepository::GetWhere( database, fmt::format( - "`character_id` = '{}' AND `item_unique_id` = '{}' ORDER BY slot_id", + "`character_id` = {} AND `item_unique_id` = '{}' ORDER BY slot_id", character_id, unique_item_id ) @@ -358,7 +358,6 @@ std::unique_ptr ZoneDatabase::LoadSingleTraderItem(uint32 char inst->SetCharges(charges); inst->SetUniqueID(unique_item_id); - //FIX inst->SetMerchantSlot(serial_number); inst->SetPrice(cost); if (inst->IsStackable()) { @@ -383,7 +382,7 @@ void ZoneDatabase::UpdateTraderItemPrice(int character_id, uint32 item_id, uint3 auto results = TraderRepository::DeleteWhere( database, fmt::format( - "`character_id` = '{}' AND `item_id` = {}", + "`character_id` = {} AND `item_id` = {}", character_id, item_id ) From addc1a865d3ca15d7f623883e541000a5eae1dae Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Tue, 1 Jul 2025 22:16:28 -0300 Subject: [PATCH 41/48] Rebase Rebase updates --- world/login_server.cpp | 6 +++--- world/zoneserver.cpp | 6 +++--- zone/worldserver.cpp | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/world/login_server.cpp b/world/login_server.cpp index 5d94394b2..afc272c09 100644 --- a/world/login_server.cpp +++ b/world/login_server.cpp @@ -131,7 +131,7 @@ void LoginServer::ProcessUsertoWorldReq(uint16_t opcode, EQ::Net::Packet &p) UsertoWorldRequest *utwr = (UsertoWorldRequest *) p.Data(); uint32 id = database.GetAccountIDFromLSID(utwr->login, utwr->lsaccountid); auto status_record = database.GetAccountStatus(id); - auto client = client_list.FindCLEByAccountID(id); + auto client = ClientList::Instance()->FindCLEByAccountID(id); if (client) { client->SetOfflineMode(status_record.offline); @@ -772,7 +772,7 @@ void LoginServer::ProcessUserToWorldCancelOfflineRequest(uint16_t opcode, EQ::Ne auto trader = TraderRepository::GetAccountZoneIdAndInstanceIdByAccountId(database, id); if (trader.id && - zoneserver_list.IsZoneBootedByZoneIdAndInstanceId(trader.char_zone_id, trader.char_zone_instance_id)) { + ZSList::Instance()->IsZoneBootedByZoneIdAndInstanceId(trader.char_zone_id, trader.char_zone_instance_id)) { LogLoginserverDetail( "Step 5a(1) - World Checked offline users zone/instance is booted. " "Sending packet to zone id {} instance id {}", @@ -780,7 +780,7 @@ void LoginServer::ProcessUserToWorldCancelOfflineRequest(uint16_t opcode, EQ::Ne trader.char_zone_instance_id); server_packet.opcode = ServerOP_UsertoWorldCancelOfflineRequest; - zoneserver_list.SendPacketToBootedZones(&server_packet); + ZSList::Instance()->SendPacketToBootedZones(&server_packet); return; } diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 29afe2caf..cb6caf7ce 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1677,12 +1677,12 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { auto in = reinterpret_cast(pack->pBuffer); switch (in->transaction_status) { case BazaarPurchaseBuyerCompleteSendToSeller: { - zoneserver_list.SendPacket(in->trader_zone_id, in->trader_zone_instance_id, pack); + ZSList::Instance()->SendPacket(in->trader_zone_id, in->trader_zone_instance_id, pack); break; } case BazaarPurchaseTraderFailed: case BazaarPurchaseSuccess: { - zoneserver_list.SendPacket(in->buyer_zone_id, in->buyer_zone_instance_id, pack); + ZSList::Instance()->SendPacket(in->buyer_zone_id, in->buyer_zone_instance_id, pack); break; } default: { @@ -1752,7 +1752,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { "Step 7a - World received ServerOP_UsertoWorldCancelOfflineResponse back to login with success." ); - loginserverlist.SendPacket(&server_packet); + LoginServerList::Instance()->SendPacket(&server_packet); break; } default: { diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 1c03b0f21..2cfcdca18 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -3946,7 +3946,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) uint64 total_cost = static_cast(in->trader_buy_struct.price) * static_cast(in->item_quantity); - if (player_event_logs.IsEventEnabled(PlayerEvent::TRADER_PURCHASE)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::TRADER_PURCHASE)) { auto e = PlayerEvent::TraderPurchaseEvent{ .item_id = in->trader_buy_struct.item_id, .augment_1_id = in->item_aug_1, @@ -3994,7 +3994,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) CharacterParcelsRepository::InsertOne(database, parcel_out); - if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) { + if (PlayerEventLogs::Instance()->IsEventEnabled(PlayerEvent::PARCEL_SEND)) { PlayerEvent::ParcelSend e{}; e.from_player_name = parcel_out.from_name; e.to_player_name = buyer->GetCleanName(); From efda9975dea227c8a54bee4612066b700a0bb957 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:09:26 -0700 Subject: [PATCH 42/48] Rebase updates --- common/database/database_update_manifest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index e8341994c..9bc78afdd 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -7244,7 +7244,7 @@ ALTER TABLE `trader` .content_schema_update = false }, ManifestEntry{ - .version = 9327, + .version = 9328, .description = "2025_01_27_offline_account_status.sql", .check = "SHOW COLUMNS FROM `account` LIKE 'offline'", .condition = "empty", From 4ded4d6b587459ecf042617850baba8e39b0c7fa Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Mon, 11 Aug 2025 16:03:32 -0700 Subject: [PATCH 43/48] Updates based on comments. --- common/bazaar.cpp | 35 ------ common/inventory_profile.cpp | 3 - common/patches/rof2.cpp | 9 -- common/patches/titanium.cpp | 1 - common/patches/uf.cpp | 1 - common/repositories/account_repository.h | 4 +- .../inventory_snapshots_repository.h | 104 +++++++++--------- 7 files changed, 54 insertions(+), 103 deletions(-) diff --git a/common/bazaar.cpp b/common/bazaar.cpp index d670b0880..c8ae40850 100644 --- a/common/bazaar.cpp +++ b/common/bazaar.cpp @@ -264,7 +264,6 @@ Bazaar::GetSearchResults( std::vector all_entries; std::unordered_set trader_items_ids{}; -// auto const trader_results = TraderRepository::GetBazaarTraderDetails(db, search_criteria_trader, search.max_results); auto const trader_results = TraderRepository::GetBazaarTraderDetails( db, search_criteria_trader, @@ -281,7 +280,6 @@ Bazaar::GetSearchResults( for (auto const &i: trader_results) { trader_items_ids.emplace(std::to_string(i.trader.item_id)); - //trader_items_ids.push_back(std::to_string(i.trader.item_id)); } auto const item_results = ItemsRepository::GetItemsForBazaarSearch( @@ -333,39 +331,6 @@ Bazaar::GetSearchResults( all_entries.push_back(r); } - // for (auto const& t:item_results) { - // BazaarSearchResultsFromDB_Struct r{}; - // r.count = 1; - // r.trader_id = t.trader.character_id; - // r.item_unique_id = t.trader.item_unique_id; - // r.cost = t.trader.item_cost; - // r.slot_id = t.trader.slot_id; - // r.charges = t.trader.item_charges; - // r.stackable = t.stackable; - // r.icon_id = t.icon; - // r.trader_zone_id = t.trader.char_zone_id; - // r.trader_zone_instance_id = t.trader.char_zone_instance_id; - // r.trader_entity_id = t.trader.char_entity_id; - // r.item_name = fmt::format("{:.63}\0", t.name); - // r.trader_name = fmt::format("{:.63}\0", t.trader_name); - // r.item_stat = t.stats; - // - // if (RuleB(Bazaar, UseAlternateBazaarSearch)) { - // if (convert || - // char_zone_id != Zones::BAZAAR || - // (char_zone_id == Zones::BAZAAR && r.trader_zone_instance_id != char_zone_instance_id) - // ) { - // r.trader_id = TraderRepository::TRADER_CONVERT_ID + r.trader_zone_instance_id; - // } - // } - // - // all_entries.push_back(r); - // } - - // if (all_entries.size() > search.max_results) { - // all_entries.resize(search.max_results); - // } - LogTrading("Returning [{}] items from search results", all_entries.size()); return all_entries; diff --git a/common/inventory_profile.cpp b/common/inventory_profile.cpp index 158ac4dab..d9d06bc50 100644 --- a/common/inventory_profile.cpp +++ b/common/inventory_profile.cpp @@ -252,9 +252,6 @@ int16 EQ::InventoryProfile::PutItem(int16 slot_id, const ItemInstance& inst) return slot_id; } - // if (inst.GetSerialNumber2().empty()) { - // inst.GenerateUniqueSerialNumber(); - // } // Delegate to internal method return _PutItem(slot_id, inst.Clone()); } diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 9074f8e01..ffb33bfd2 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -439,7 +439,6 @@ namespace RoF2 break; } default: { - //LogTradingDetail("Unhandled action [{}]", sub_action); dest->FastQueuePacket(&in); } } @@ -533,7 +532,6 @@ namespace RoF2 break; } default: { - //LogTradingDetail("(RoF2) Unhandled action [{}]", action); dest->FastQueuePacket(&in, ack_req); } } @@ -622,10 +620,6 @@ namespace RoF2 break; } default: { - // LogTradingDetail( - // "(RoF2) Unhandled action [{}]", - // in->action - // ); dest->QueuePacket(inapp); } } @@ -4337,7 +4331,6 @@ namespace RoF2 break; } default: { - // LogTradingDetail("(RoF2) Unhandled action [{}]", action); EQApplicationPacket *in = *p; *p = nullptr; @@ -5111,7 +5104,6 @@ namespace RoF2 } default: { auto emu = (BuyerGeneric_Struct *) __packet->pBuffer; - LogTradingDetail("(RoF2) Pass thru OP_Barter packet action [{}]", emu->action); } } } @@ -6228,7 +6220,6 @@ namespace RoF2 break; } default: { - //LogTradingDetail("(RoF2) Unhandled action [{}]", action); } } } diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index 3d3b8ebe1..a31e7aea2 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -2528,7 +2528,6 @@ namespace Titanium IN(action); memcpy(emu->player_name, eq->player_name, sizeof(emu->player_name)); - //FIXIN(serial_number); FINISH_DIRECT_DECODE(); break; diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 057e98ad3..46a576e7c 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -3617,7 +3617,6 @@ namespace UF IN(action); memcpy(emu->player_name, eq->player_name, sizeof(emu->player_name)); - //FIXIN(serial_number); FINISH_DIRECT_DECODE(); break; diff --git a/common/repositories/account_repository.h b/common/repositories/account_repository.h index 9f269abb6..950c975d8 100644 --- a/common/repositories/account_repository.h +++ b/common/repositories/account_repository.h @@ -121,7 +121,7 @@ public: static void ClearAllOfflineStatus(Database& db) { - auto query = fmt::format("UPDATE {} SET `offline` = '0' WHERE `offline` = '1';", + auto query = fmt::format("UPDATE {} SET `offline` = 0 WHERE `offline` = 1;", TableName() ); @@ -133,7 +133,7 @@ public: auto query = fmt::format("SELECT a.`offline` " "FROM `account` AS a " "INNER JOIN character_data AS c ON c.account_id = a.id " - "WHERE c.id = '{}'", + "WHERE c.id = {}", character_id ); auto results = db.QueryDatabase(query); diff --git a/common/repositories/inventory_snapshots_repository.h b/common/repositories/inventory_snapshots_repository.h index 731dced62..618ffd2fa 100644 --- a/common/repositories/inventory_snapshots_repository.h +++ b/common/repositories/inventory_snapshots_repository.h @@ -73,7 +73,7 @@ public: { const std::string &query = fmt::format( "SELECT COUNT(*) FROM (SELECT * FROM `inventory_snapshots` a WHERE " - "`character_id` = '{}' GROUP BY `time_index`) b", + "`character_id` = {} GROUP BY `time_index`) b", character_id ); @@ -103,14 +103,14 @@ public: del_time -= RuleI(Character, InvSnapshotHistoryD) * 86400; } - DeleteWhere(db, fmt::format("`character_id` = '{}' AND `time_index` <= '{}'", character_id, del_time)); + DeleteWhere(db, fmt::format("`character_id` = {} AND `time_index` <= {}", character_id, del_time)); } static void ListCharacterInvSnapshots(Database &db, uint32 character_id, std::list> &is_list) { const std::string &query = fmt::format( "SELECT `time_index`, COUNT(*) FROM `inventory_snapshots` WHERE " - "`character_id` = '{}' GROUP BY `time_index` ORDER BY `time_index` DESC", + "`character_id` = {} GROUP BY `time_index` ORDER BY `time_index` DESC", character_id ); auto results = db.QueryDatabase(query); @@ -130,8 +130,8 @@ public: } const std::string &query = fmt::format( - "SELECT * FROM `inventory_snapshots` WHERE `character_id` = '{}' " - "AND `time_index` = '{}' LIMIT 1", + "SELECT * FROM `inventory_snapshots` WHERE `character_id` = {} " + "AND `time_index` = {} LIMIT 1", character_id, timestamp ); @@ -152,7 +152,7 @@ public: { const std::string &query = fmt::format( "SELECT `slot_id`, `item_id` FROM `inventory_snapshots` " - "WHERE `character_id` = '{}' AND `time_index` = '{}' ORDER BY `slot_id`", + "WHERE `character_id` = {} AND `time_index` = {} ORDER BY `slot_id`", character_id, timestamp ); @@ -175,9 +175,9 @@ public: { const std::string &query = fmt::format( "SELECT slot_id, item_id FROM `inventory_snapshots` " - "WHERE `time_index` = '{0}' AND `character_id` = '{1}' AND `slot_id` NOT IN (" + "WHERE `time_index` = {0} AND `character_id` = {1} AND `slot_id` NOT IN (" "SELECT a.`slot_id` FROM `inventory_snapshots` a JOIN `inventory` b USING (`slot_id`, `item_id`) " - "WHERE a.`time_index` = '{0}' AND a.`character_id` = '{1}' AND b.`character_id` = '{1}')", + "WHERE a.`time_index` = {0} AND a.`character_id` = {1} AND b.`character_id` = {1})", timestamp, character_id ); @@ -197,9 +197,9 @@ public: { const std::string &query = fmt::format( "SELECT `slot_id`, `item_id` FROM `inventory` WHERE " - "`character_id` = '{0}' AND `slot_id` NOT IN (" + "`character_id` = {0} AND `slot_id` NOT IN (" "SELECT a.`slot_id` FROM `inventory` a JOIN `inventory_snapshots` b USING (`slot_id`, `item_id`) " - "WHERE b.`time_index` = '{1}' AND b.`character_id` = '{0}' AND a.`character_id` = '{0}')", + "WHERE b.`time_index` = {1} AND b.`character_id` = {0} AND a.`character_id` = {0})", character_id, timestamp ); @@ -219,33 +219,33 @@ public: uint32 time_index = time(nullptr); std::vector queue{}; - auto inventory = InventoryRepository::GetWhere(db, fmt::format("`character_id` = '{}'", character_id)); + auto inventory = InventoryRepository::GetWhere(db, fmt::format("`character_id` = {}", character_id)); if (inventory.empty()) { LogError("Character ID [{}] inventory is empty. Snapshot not created", character_id); return false; } for (auto const &i: inventory) { - auto snapshot = NewEntity(); - snapshot.character_id = i.character_id; - snapshot.item_id = i.item_id; - snapshot.item_unique_id = i.item_unique_id; - snapshot.augment_one = i.augment_one; - snapshot.augment_two = i.augment_two; - snapshot.augment_three = i.augment_three; - snapshot.augment_four = i.augment_four; - snapshot.augment_five = i.augment_five; - snapshot.augment_six = i.augment_six; - snapshot.charges = i.charges; - snapshot.color = i.color; - snapshot.custom_data = i.custom_data; - snapshot.instnodrop = i.instnodrop; - snapshot.ornament_hero_model = i.ornament_hero_model; - snapshot.ornament_icon = i.ornament_icon; - snapshot.ornament_idfile = i.ornament_idfile; - snapshot.slot_id = i.slot_id; - snapshot.time_index = time_index; - queue.push_back(snapshot); + auto s = NewEntity(); + s.character_id = i.character_id; + s.item_id = i.item_id; + s.item_unique_id = i.item_unique_id; + s.augment_one = i.augment_one; + s.augment_two = i.augment_two; + s.augment_three = i.augment_three; + s.augment_four = i.augment_four; + s.augment_five = i.augment_five; + s.augment_six = i.augment_six; + s.charges = i.charges; + s.color = i.color; + s.custom_data = i.custom_data; + s.instnodrop = i.instnodrop; + s.ornament_hero_model = i.ornament_hero_model; + s.ornament_icon = i.ornament_icon; + s.ornament_idfile = i.ornament_idfile; + s.slot_id = i.slot_id; + s.time_index = time_index; + queue.push_back(s); } if (queue.empty()) { @@ -264,9 +264,9 @@ public: static bool RestoreCharacterInvSnapshot(Database &db, uint32 character_id, uint32 timestamp) { - InventoryRepository::DeleteWhere(db, fmt::format("`character_id` = '{}'", character_id)); + InventoryRepository::DeleteWhere(db, fmt::format("`character_id` = {}", character_id)); - auto snapshot = GetWhere(db, fmt::format("`character_id` = '{}' AND `time_index` = '{}'", character_id, timestamp)); + auto snapshot = GetWhere(db, fmt::format("`character_id` = {} AND `time_index` = {}", character_id, timestamp)); if (snapshot.empty()) { LogError("The snapshot requested could not be found. Restore failed for character id [{}] @ [{}] failed", character_id, @@ -277,25 +277,25 @@ public: std::vector queue{}; for (auto const &i: snapshot) { - auto inventory_entry = InventoryRepository::NewEntity(); - inventory_entry.character_id = i.character_id; - inventory_entry.item_id = i.item_id; - inventory_entry.item_unique_id = i.item_unique_id; - inventory_entry.augment_one = i.augment_one; - inventory_entry.augment_two = i.augment_two; - inventory_entry.augment_three = i.augment_three; - inventory_entry.augment_four = i.augment_four; - inventory_entry.augment_five = i.augment_five; - inventory_entry.augment_six = i.augment_six; - inventory_entry.charges = i.charges; - inventory_entry.color = i.color; - inventory_entry.custom_data = i.custom_data; - inventory_entry.instnodrop = i.instnodrop; - inventory_entry.ornament_hero_model = i.ornament_hero_model; - inventory_entry.ornament_icon = i.ornament_icon; - inventory_entry.ornament_idfile = i.ornament_idfile; - inventory_entry.slot_id = i.slot_id; - queue.push_back(inventory_entry); + auto e = InventoryRepository::NewEntity(); + e.character_id = i.character_id; + e.item_id = i.item_id; + e.item_unique_id = i.item_unique_id; + e.augment_one = i.augment_one; + e.augment_two = i.augment_two; + e.augment_three = i.augment_three; + e.augment_four = i.augment_four; + e.augment_five = i.augment_five; + e.augment_six = i.augment_six; + e.charges = i.charges; + e.color = i.color; + e.custom_data = i.custom_data; + e.instnodrop = i.instnodrop; + e.ornament_hero_model = i.ornament_hero_model; + e.ornament_icon = i.ornament_icon; + e.ornament_idfile = i.ornament_idfile; + e.slot_id = i.slot_id; + queue.push_back(e); } if (queue.empty()) { From 345d452a7e13efea52b468f6d1d890717baf72d8 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Mon, 11 Aug 2025 16:57:41 -0700 Subject: [PATCH 44/48] Remove FindTraderItemSerialNumber and FIndTraderItemBySerialNumber as they are no longer used. Updated sharedbank to store unique_item_id instead of guid. --- common/database/database_update_manifest.cpp | 5 ++ common/item_instance.h | 1 - .../base/base_sharedbank_repository.h | 24 ++++----- common/shareddb.cpp | 2 +- zone/client.h | 2 - zone/trading.cpp | 49 ------------------- 6 files changed, 18 insertions(+), 65 deletions(-) diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 9bc78afdd..904c67052 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -7240,6 +7240,11 @@ ALTER TABLE `trader` ADD INDEX `idx_trader_char` (`character_id`, `char_zone_id`, `char_zone_instance_id`) USING BTREE, ADD UNIQUE INDEX `idx_item_unique_id` (`item_unique_id`); +ALTER TABLE `sharedbank` + DROP COLUMN `guid`, + ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `ornament_hero_model`, + ADD UNIQUE INDEX `idx_item_unique_id` (`item_unique_id`); + )", .content_schema_update = false }, diff --git a/common/item_instance.h b/common/item_instance.h index 907f927d7..36404df05 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -245,7 +245,6 @@ namespace EQ int32 GetSerialNumber() const { return m_SerialNumber; } void SetSerialNumber(int32 id) { m_SerialNumber = id; } - const std::string &GetSerialNumber2() const { return m_unique_id; } const std::string &GetUniqueID() const { return m_unique_id; } void SetUniqueID(std::string sn) { m_unique_id = std::move(sn); } void CreateUniqueID() const { m_unique_id = GenerateUniqueID(); } diff --git a/common/repositories/base/base_sharedbank_repository.h b/common/repositories/base/base_sharedbank_repository.h index d208251da..262f54fe0 100644 --- a/common/repositories/base/base_sharedbank_repository.h +++ b/common/repositories/base/base_sharedbank_repository.h @@ -34,7 +34,7 @@ public: uint32_t ornament_icon; uint32_t ornament_idfile; int32_t ornament_hero_model; - uint64_t guid; + std::string item_unique_id; }; static std::string PrimaryKey() @@ -60,7 +60,7 @@ public: "ornament_icon", "ornament_idfile", "ornament_hero_model", - "guid", + "item_unique_id", }; } @@ -82,7 +82,7 @@ public: "ornament_icon", "ornament_idfile", "ornament_hero_model", - "guid", + "item_unique_id", }; } @@ -138,7 +138,7 @@ public: e.ornament_icon = 0; e.ornament_idfile = 0; e.ornament_hero_model = 0; - e.guid = 0; + e.item_unique_id = ""; return e; } @@ -190,7 +190,7 @@ public: e.ornament_icon = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; e.ornament_idfile = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; e.ornament_hero_model = row[14] ? static_cast(atoi(row[14])) : 0; - e.guid = row[15] ? strtoull(row[15], nullptr, 10) : 0; + e.item_unique_id = row[15] ? row[15] : ""; return e; } @@ -239,7 +239,7 @@ public: v.push_back(columns[12] + " = " + std::to_string(e.ornament_icon)); v.push_back(columns[13] + " = " + std::to_string(e.ornament_idfile)); v.push_back(columns[14] + " = " + std::to_string(e.ornament_hero_model)); - v.push_back(columns[15] + " = " + std::to_string(e.guid)); + v.push_back(columns[15] + " = '" + Strings::Escape(e.item_unique_id) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -276,7 +276,7 @@ public: v.push_back(std::to_string(e.ornament_icon)); v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); - v.push_back(std::to_string(e.guid)); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -321,7 +321,7 @@ public: v.push_back(std::to_string(e.ornament_icon)); v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); - v.push_back(std::to_string(e.guid)); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -370,7 +370,7 @@ public: e.ornament_icon = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; e.ornament_idfile = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; e.ornament_hero_model = row[14] ? static_cast(atoi(row[14])) : 0; - e.guid = row[15] ? strtoull(row[15], nullptr, 10) : 0; + e.item_unique_id = row[15] ? row[15] : ""; all_entries.push_back(e); } @@ -410,7 +410,7 @@ public: e.ornament_icon = row[12] ? static_cast(strtoul(row[12], nullptr, 10)) : 0; e.ornament_idfile = row[13] ? static_cast(strtoul(row[13], nullptr, 10)) : 0; e.ornament_hero_model = row[14] ? static_cast(atoi(row[14])) : 0; - e.guid = row[15] ? strtoull(row[15], nullptr, 10) : 0; + e.item_unique_id = row[15] ? row[15] : ""; all_entries.push_back(e); } @@ -500,7 +500,7 @@ public: v.push_back(std::to_string(e.ornament_icon)); v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); - v.push_back(std::to_string(e.guid)); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); auto results = db.QueryDatabase( fmt::format( @@ -538,7 +538,7 @@ public: v.push_back(std::to_string(e.ornament_icon)); v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); - v.push_back(std::to_string(e.guid)); + v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 2d805cbfe..9148caa78 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -335,7 +335,7 @@ bool SharedDatabase::UpdateSharedBankSlot(uint32 char_id, const EQ::ItemInstance e.ornament_icon = inst->GetOrnamentationIcon(); e.ornament_idfile = inst->GetOrnamentationIDFile(); e.ornament_hero_model = inst->GetOrnamentHeroModel(); - e.guid = inst->GetSerialNumber(); + e.item_unique_id = inst->GetUniqueID(); const int replaced = SharedbankRepository::ReplaceOne(*this, e); diff --git a/zone/client.h b/zone/client.h index c9ee0d2ae..bd0d76709 100644 --- a/zone/client.h +++ b/zone/client.h @@ -372,8 +372,6 @@ public: void SendTraderItem(uint32 item_id,uint16 quantity, TraderRepository::Trader &trader); void DoBazaarSearch(BazaarSearchCriteria_Struct search_criteria); uint16 FindTraderItem(std::string &SerialNumber,uint16 Quantity); - uint32 FindTraderItemSerialNumber(int32 ItemID); - EQ::ItemInstance* FindTraderItemBySerialNumber(std::string &serial_number); EQ::ItemInstance* FindTraderItemByUniqueID(std::string &unique_id); EQ::ItemInstance* FindTraderItemByUniqueID(const char* unique_id); std::vector FindTraderItemsByUniqueID(const char* unique_id); diff --git a/zone/trading.cpp b/zone/trading.cpp index acbcb0cde..44b4155cb 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -1024,55 +1024,6 @@ void Client::BulkSendTraderInventory(uint32 character_id) } } -uint32 Client::FindTraderItemSerialNumber(int32 ItemID) { - - EQ::ItemInstance* item = nullptr; - uint16 SlotID = 0; - for (int i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++){ - item = GetInv().GetItem(i); - if (item && item->GetItem()->BagType == EQ::item::BagTypeTradersSatchel){ - for (int x = EQ::invbag::SLOT_BEGIN; x <= EQ::invbag::SLOT_END; x++) { - // we already have the parent bag and a contents iterator..why not just iterate the bag!?? - SlotID = EQ::InventoryProfile::CalcSlotId(i, x); - item = GetInv().GetItem(SlotID); - if (item) { - if (item->GetID() == ItemID) - return item->GetSerialNumber(); - } - } - } - } - LogTrading("Client::FindTraderItemSerialNumber Couldn't find item! Item ID [{}]", ItemID); - - return 0; -} - -EQ::ItemInstance *Client::FindTraderItemBySerialNumber(std::string &unique_id) -{ - EQ::ItemInstance *item = nullptr; - int16 slot_id = 0; - - for (int16 i = EQ::invslot::GENERAL_BEGIN; i <= EQ::invslot::GENERAL_END; i++) { - item = GetInv().GetItem(i); - if (item && item->GetItem()->BagType == EQ::item::BagTypeTradersSatchel) { - for (int16 x = EQ::invbag::SLOT_BEGIN; x <= EQ::invbag::SLOT_END; x++) { - // we already have the parent bag and a contents iterator..why not just iterate the bag!?? - slot_id = EQ::InventoryProfile::CalcSlotId(i, x); - item = GetInv().GetItem(slot_id); - if (item) { - if (item->GetUniqueID().compare(unique_id) == 0) { - return item; - } - } - } - } - } - - LogTrading("Couldn't find item! Serial No. was [{}]", unique_id); - - return nullptr; -} - EQ::ItemInstance *Client::FindTraderItemByUniqueID(std::string &unique_id) { EQ::ItemInstance *item = nullptr; From a2310b6d5e4e67e6b93aa30e3a9d54f246d87a04 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:06:33 -0700 Subject: [PATCH 45/48] Updated inventory_snapshots to correct a test case. Working as intended now. Added sharedbank conversion to create unique item ids on world load. --- common/database.cpp | 35 +++++++++++++++++++ common/database.h | 1 + common/database/database_update_manifest.cpp | 4 ++- .../base_inventory_snapshots_repository.h | 12 ------- .../inventory_snapshots_repository.h | 6 ++-- world/world_boot.cpp | 1 + 6 files changed, 44 insertions(+), 15 deletions(-) diff --git a/common/database.cpp b/common/database.cpp index d60f3ce60..7e909ffa8 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -52,6 +52,7 @@ #include "../common/repositories/variables_repository.h" #include "../common/repositories/character_pet_name_repository.h" #include "../common/events/player_event_logs.h" +#include "../common/repositories/sharedbank_repository.h" // Disgrace: for windows compile #ifdef _WINDOWS @@ -2327,3 +2328,37 @@ void Database::ConvertInventoryToNewUniqueId() TransactionCommit(); LogInfo("Converted {} records", results.size()); } + +void Database::ConvertSharedbankToNewUniqueId() +{ + LogInfo("Converting shared bank entries with NULL item_unique_id"); + auto results = SharedbankRepository::GetWhere(*this, "`item_unique_id` IS NULL"); + + if (results.empty()) { + return; + } + + TransactionBegin(); + uint32 index = 0; + const uint32 batch_size = 1000; + std::vector queue{}; + queue.reserve(batch_size); + + for (auto &r: results) { + r.item_unique_id = EQ::UniqueHashGenerator::generate(); + queue.push_back(r); + index++; + if (index >= batch_size) { + SharedbankRepository::ReplaceMany(*this, queue); + index = 0; + queue.clear(); + } + } + + if (!queue.empty()) { + SharedbankRepository::ReplaceMany(*this, queue); + } + + TransactionCommit(); + LogInfo("Converted {} records", results.size()); +} \ No newline at end of file diff --git a/common/database.h b/common/database.h index accbb55ff..ee5e2c8a7 100644 --- a/common/database.h +++ b/common/database.h @@ -279,6 +279,7 @@ public: uint64_t GetNextTableId(const std::string& table_name); void ConvertInventoryToNewUniqueId(); + void ConvertSharedbankToNewUniqueId(); private: Mutex Mvarcache; diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 904c67052..8a6b9a20b 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -7219,7 +7219,9 @@ ALTER TABLE `inventory_snapshots` ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `ornament_hero_model`; ALTER TABLE `inventory_snapshots` - DROP PRIMARY KEY; + DROP PRIMARY KEY, + DROP COLUMN `guid`, + ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `ornament_hero_model`; ALTER TABLE `inventory_snapshots` ADD PRIMARY KEY (`time_index`, `character_id`, `slot_id`) USING BTREE; diff --git a/common/repositories/base/base_inventory_snapshots_repository.h b/common/repositories/base/base_inventory_snapshots_repository.h index 0088a8784..a700c39f0 100644 --- a/common/repositories/base/base_inventory_snapshots_repository.h +++ b/common/repositories/base/base_inventory_snapshots_repository.h @@ -37,7 +37,6 @@ public: uint32_t ornament_idfile; int32_t ornament_hero_model; std::string item_unique_id; - uint64_t guid; }; static std::string PrimaryKey() @@ -66,7 +65,6 @@ public: "ornament_idfile", "ornament_hero_model", "item_unique_id", - "guid", }; } @@ -91,7 +89,6 @@ public: "ornament_idfile", "ornament_hero_model", "item_unique_id", - "guid", }; } @@ -150,7 +147,6 @@ public: e.ornament_idfile = 0; e.ornament_hero_model = 0; e.item_unique_id = ""; - e.guid = 0; return e; } @@ -205,7 +201,6 @@ public: e.ornament_idfile = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; e.ornament_hero_model = row[16] ? static_cast(atoi(row[16])) : 0; e.item_unique_id = row[17] ? row[17] : ""; - e.guid = row[18] ? strtoull(row[18], nullptr, 10) : 0; return e; } @@ -257,7 +252,6 @@ public: v.push_back(columns[15] + " = " + std::to_string(e.ornament_idfile)); v.push_back(columns[16] + " = " + std::to_string(e.ornament_hero_model)); v.push_back(columns[17] + " = '" + Strings::Escape(e.item_unique_id) + "'"); - v.push_back(columns[18] + " = " + std::to_string(e.guid)); auto results = db.QueryDatabase( fmt::format( @@ -297,7 +291,6 @@ public: v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); - v.push_back(std::to_string(e.guid)); auto results = db.QueryDatabase( fmt::format( @@ -345,7 +338,6 @@ public: v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); - v.push_back(std::to_string(e.guid)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } @@ -397,7 +389,6 @@ public: e.ornament_idfile = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; e.ornament_hero_model = row[16] ? static_cast(atoi(row[16])) : 0; e.item_unique_id = row[17] ? row[17] : ""; - e.guid = row[18] ? strtoull(row[18], nullptr, 10) : 0; all_entries.push_back(e); } @@ -440,7 +431,6 @@ public: e.ornament_idfile = row[15] ? static_cast(strtoul(row[15], nullptr, 10)) : 0; e.ornament_hero_model = row[16] ? static_cast(atoi(row[16])) : 0; e.item_unique_id = row[17] ? row[17] : ""; - e.guid = row[18] ? strtoull(row[18], nullptr, 10) : 0; all_entries.push_back(e); } @@ -533,7 +523,6 @@ public: v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); - v.push_back(std::to_string(e.guid)); auto results = db.QueryDatabase( fmt::format( @@ -574,7 +563,6 @@ public: v.push_back(std::to_string(e.ornament_idfile)); v.push_back(std::to_string(e.ornament_hero_model)); v.push_back("'" + Strings::Escape(e.item_unique_id) + "'"); - v.push_back(std::to_string(e.guid)); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); } diff --git a/common/repositories/inventory_snapshots_repository.h b/common/repositories/inventory_snapshots_repository.h index 618ffd2fa..c05ac98bc 100644 --- a/common/repositories/inventory_snapshots_repository.h +++ b/common/repositories/inventory_snapshots_repository.h @@ -245,6 +245,7 @@ public: s.ornament_idfile = i.ornament_idfile; s.slot_id = i.slot_id; s.time_index = time_index; + s.item_unique_id = i.item_unique_id; queue.push_back(s); } @@ -264,8 +265,6 @@ public: static bool RestoreCharacterInvSnapshot(Database &db, uint32 character_id, uint32 timestamp) { - InventoryRepository::DeleteWhere(db, fmt::format("`character_id` = {}", character_id)); - auto snapshot = GetWhere(db, fmt::format("`character_id` = {} AND `time_index` = {}", character_id, timestamp)); if (snapshot.empty()) { LogError("The snapshot requested could not be found. Restore failed for character id [{}] @ [{}] failed", @@ -295,6 +294,7 @@ public: e.ornament_icon = i.ornament_icon; e.ornament_idfile = i.ornament_idfile; e.slot_id = i.slot_id; + e.item_unique_id = i.item_unique_id; queue.push_back(e); } @@ -303,6 +303,8 @@ public: return false; } + InventoryRepository::DeleteWhere(db, fmt::format("`character_id` = {}", character_id)); + if (!InventoryRepository::InsertMany(db, queue)) { LogError("A database error occurred. Restore failed for character id [{}] @ [{}] failed", character_id, timestamp); return false; diff --git a/world/world_boot.cpp b/world/world_boot.cpp index ee13ab7a3..3fc5c1ea7 100644 --- a/world/world_boot.cpp +++ b/world/world_boot.cpp @@ -296,6 +296,7 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv) LogInfo("Clearing buyer table details"); database.ConvertInventoryToNewUniqueId(); + database.ConvertSharedbankToNewUniqueId(); if (RuleB(Bots, Enabled)) { LogInfo("Clearing [bot_pet_buffs] table of stale entries"); From a012e01730c5b7a118b22998957c99e50dec0f23 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Mon, 11 Aug 2025 20:19:02 -0700 Subject: [PATCH 46/48] Fix for db manifest typo --- common/database/database_update_manifest.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 8a6b9a20b..9b750e583 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -7205,6 +7205,7 @@ ALTER TABLE `character_parcels_containers` ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `item_id`; ALTER TABLE `inventory_snapshots` + DROP PRIMARY KEY, CHANGE COLUMN `charid` `character_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `time_index`, CHANGE COLUMN `slotid` `slot_id` MEDIUMINT(7) UNSIGNED NOT NULL DEFAULT '0' AFTER `character_id`, CHANGE COLUMN `itemid` `item_id` INT(11) UNSIGNED NULL DEFAULT '0' AFTER `slot_id`, @@ -7216,12 +7217,8 @@ ALTER TABLE `inventory_snapshots` CHANGE COLUMN `augslot6` `augment_six` MEDIUMINT(7) NOT NULL DEFAULT '0' AFTER `augment_five`, CHANGE COLUMN `ornamenticon` `ornament_icon` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `custom_data`, CHANGE COLUMN `ornamentidfile` `ornament_idfile` INT(11) UNSIGNED NOT NULL DEFAULT '0' AFTER `ornament_icon`, - ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `ornament_hero_model`; - -ALTER TABLE `inventory_snapshots` - DROP PRIMARY KEY, - DROP COLUMN `guid`, - ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `ornament_hero_model`; + ADD COLUMN `item_unique_id` VARCHAR(16) NULL DEFAULT NULL AFTER `ornament_hero_model`, + DROP COLUMN `guid`; ALTER TABLE `inventory_snapshots` ADD PRIMARY KEY (`time_index`, `character_id`, `slot_id`) USING BTREE; From c30ae140b043a46ce9c20aca852a8e75649ace62 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Wed, 3 Sep 2025 19:59:06 -0300 Subject: [PATCH 47/48] Fix after rebase --- common/database/database_update_manifest.cpp | 2 +- common/version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 9b750e583..31c37ad61 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -7248,7 +7248,7 @@ ALTER TABLE `sharedbank` .content_schema_update = false }, ManifestEntry{ - .version = 9328, + .version = 9330, .description = "2025_01_27_offline_account_status.sql", .check = "SHOW COLUMNS FROM `account` LIKE 'offline'", .condition = "empty", diff --git a/common/version.h b/common/version.h index c119346d4..2375bc9f3 100644 --- a/common/version.h +++ b/common/version.h @@ -42,7 +42,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9328 +#define CURRENT_BINARY_DATABASE_VERSION 9330 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054 #define CUSTOM_BINARY_DATABASE_VERSION 0 From 28d0e75a5ec7d4452cadb64fe8235092b352aa05 Mon Sep 17 00:00:00 2001 From: Mitch Freeman <65987027+neckkola@users.noreply.github.com> Date: Sun, 21 Sep 2025 19:36:21 -0300 Subject: [PATCH 48/48] Fix after rebase --- common/database/database_update_manifest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 31c37ad61..08e2c88f1 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -7272,7 +7272,7 @@ ALTER TABLE `sharedbank` ENGINE=InnoDB; )", .content_schema_update = false - }, + }, // -- template; copy/paste this when you need to create a new entry // ManifestEntry{ // .version = 9228,