From 90d54c9e0527a603d6dcdc02b3f5aae928ab3fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo?= Date: Tue, 9 Dec 2025 17:43:43 -0300 Subject: [PATCH 1/3] improve: Player Binary Save Change player saving to Binary Save to achieve better loading/saving times up to 40x Thanks to @SaiyansKing --- data/libs/functions/player.lua | 57 ++- data/migrations/60.lua | 9 + src/io/functions/iologindata_load_player.cpp | 399 +++++++++++++------ src/io/functions/iologindata_save_player.cpp | 366 +++++++---------- src/io/functions/iologindata_save_player.hpp | 2 +- 5 files changed, 475 insertions(+), 358 deletions(-) create mode 100644 data/migrations/60.lua diff --git a/data/libs/functions/player.lua b/data/libs/functions/player.lua index e9fb3183e..283c69371 100644 --- a/data/libs/functions/player.lua +++ b/data/libs/functions/player.lua @@ -276,15 +276,56 @@ function Player.getAccountStorage(self, key, forceUpdate) if ACCOUNT_STORAGES[accountId] and not forceUpdate then return ACCOUNT_STORAGES[accountId] end - - local query = db.storeQuery("SELECT `key`, MAX(`value`) as value FROM `player_storage` WHERE `player_id` IN (SELECT `id` FROM `players` WHERE `account_id` = " .. accountId .. ") AND `key` = " .. key .. " GROUP BY `key` LIMIT 1;") - if query ~= false then - local value = Result.getNumber(query, "value") - ACCOUNT_STORAGES[accountId] = value - Result.free(query) - return value + local kvFlag = self:kv():scoped("storages"):get("use-blob") + if kvFlag then + local result = db.storeQuery("SELECT `id`, `storages` FROM `players` WHERE `account_id` = " .. accountId) + if result ~= false then + local maxValue + repeat + local stream, length = Result.getStream(result, "storages") + if stream and length and length > 0 then + local pos = 1 + local function read_u32_le(s, p) + local b1, b2, b3, b4 = s:byte(p, p + 3) + return b1 + b2 * 256 + b3 * 65536 + b4 * 16777216, p + 4 + end + local function read_i32_le(s, p) + local u, np = read_u32_le(s, p) + if u >= 2147483648 then + u = u - 4294967296 + end + return u, np + end + while pos + 7 <= length do + local k + k, pos = read_u32_le(stream, pos) + local v + v, pos = read_i32_le(stream, pos) + if k == key then + if maxValue == nil or v > maxValue then + maxValue = v + end + end + end + end + until not Result.next(result) + Result.free(result) + if maxValue ~= nil then + ACCOUNT_STORAGES[accountId] = maxValue + return maxValue + end + end + return false + else + local query = db.storeQuery("SELECT `key`, MAX(`value`) as value FROM `player_storage` WHERE `player_id` IN (SELECT `id` FROM `players` WHERE `account_id` = " .. accountId .. ") AND `key` = " .. key .. " GROUP BY `key` LIMIT 1;") + if query ~= false then + local value = Result.getNumber(query, "value") + ACCOUNT_STORAGES[accountId] = value + Result.free(query) + return value + end + return false end - return false end function Player:getUpdatedAccountStorage(bucket) diff --git a/data/migrations/60.lua b/data/migrations/60.lua new file mode 100644 index 000000000..c805f0f1b --- /dev/null +++ b/data/migrations/60.lua @@ -0,0 +1,9 @@ +function onUpdateDatabase() + logger.info("Updating database to version 60 (player binary save/load)") + db.query("ALTER TABLE `players` ADD `spells` blob DEFAULT NULL") + db.query("ALTER TABLE `players` ADD `storages` mediumblob DEFAULT NULL") + db.query("ALTER TABLE `players` ADD `items` longblob DEFAULT NULL") + db.query("ALTER TABLE `players` ADD `depotitems` longblob DEFAULT NULL") + db.query("ALTER TABLE `players` ADD `inboxitems` longblob DEFAULT NULL") + db.query("ALTER TABLE `players` ADD `stashitems` longblob DEFAULT NULL") +end diff --git a/src/io/functions/iologindata_load_player.cpp b/src/io/functions/iologindata_load_player.cpp index cba1a0cba..1df1e9567 100644 --- a/src/io/functions/iologindata_load_player.cpp +++ b/src/io/functions/iologindata_load_player.cpp @@ -42,6 +42,7 @@ #include "items/containers/rewards/rewardchest.hpp" #include "creatures/players/player.hpp" #include "utils/tools.hpp" +#include "kv/kv.hpp" void IOLoginDataLoad::loadItems(ItemsMap &itemsMap, const DBResult_ptr &result, const std::shared_ptr &player) { try { @@ -480,14 +481,33 @@ void IOLoginDataLoad::loadPlayerStashItems(const std::shared_ptr &player g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); return; } - - Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `item_count`, `item_id` FROM `player_stash` WHERE `player_id` = " << player->getGUID(); - if ((result = db.storeQuery(query.str()))) { - do { - player->addItemOnStash(result->getNumber("item_id"), result->getNumber("item_count")); - } while (result->next()); + const auto kvFlag = player->kv()->scoped("stashitems")->get("use-blob"); + if (kvFlag && kvFlag->get()) { + Database &db = Database::getInstance(); + std::ostringstream query; + query << "SELECT `stashitems` FROM `players` WHERE `id` = " << player->getGUID(); + if ((result = db.storeQuery(query.str()))) { + unsigned long size; + const char* blob = result->getStream("stashitems", size); + if (blob && size > 0) { + PropStream ps; + ps.init(blob, size); + while (true) { + uint16_t itemId; uint32_t itemCount; + if (!ps.read(itemId) || !ps.read(itemCount)) { break; } + player->addItemOnStash(itemId, itemCount); + } + } + } + } else { + Database &db = Database::getInstance(); + std::ostringstream query; + query << "SELECT `item_count`, `item_id` FROM `player_stash` WHERE `player_id` = " << player->getGUID(); + if ((result = db.storeQuery(query.str()))) { + do { + player->addItemOnStash(result->getNumber("item_id"), result->getNumber("item_count")); + } while (result->next()); + } } } @@ -553,12 +573,32 @@ void IOLoginDataLoad::loadPlayerInstantSpellList(const std::shared_ptr & } Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `player_id`, `name` FROM `player_spells` WHERE `player_id` = " << player->getGUID(); - if ((result = db.storeQuery(query.str()))) { - do { - player->learnedInstantSpellList.emplace_back(result->getString("name")); - } while (result->next()); + + const auto kvFlag = player->kv()->scoped("spells")->get("use-blob"); + if (kvFlag && kvFlag->get()) { + std::ostringstream query; + query << "SELECT `spells` FROM `players` WHERE `id` = " << player->getGUID(); + if ((result = db.storeQuery(query.str()))) { + unsigned long size; + const char* blob = result->getStream("spells", size); + + if ((blob && size > 0)) { + PropStream ps; + ps.init(blob, size); + std::string spellName; + while (ps.readString(spellName)) { + player->learnedInstantSpellList.emplace_back(spellName); + } + } + } + } else { + std::ostringstream query; + query << "SELECT `player_id`, `name` FROM `player_spells` WHERE `player_id` = " << player->getGUID(); + if ((result = db.storeQuery(query.str()))) { + do { + player->learnedInstantSpellList.emplace_back(result->getString("name")); + } while (result->next()); + } } } @@ -568,51 +608,69 @@ void IOLoginDataLoad::loadPlayerInventoryItems(const std::shared_ptr &pl return; } - auto query = fmt::format("SELECT pid, sid, itemtype, count, attributes FROM player_items WHERE player_id = {} ORDER BY sid DESC", player->getGUID()); - ItemsMap inventoryItems; std::vector> itemsToStartDecaying; try { - if ((result = g_database().storeQuery(query))) { - loadItems(inventoryItems, result, player); - - for (auto it = inventoryItems.rbegin(), end = inventoryItems.rend(); it != end; ++it) { - const std::pair, int32_t> &pair = it->second; - const auto &item = pair.first; - if (!item) { - continue; - } - - int32_t pid = pair.second; - if (pid >= CONST_SLOT_FIRST && pid <= CONST_SLOT_LAST) { - player->internalAddThing(pid, item); - item->startDecaying(); - } else { - ItemsMap::const_iterator it2 = inventoryItems.find(pid); - if (it2 == inventoryItems.end()) { - continue; + const auto kvFlag = player->kv()->scoped("items")->get("use-blob"); + if (kvFlag && kvFlag->get()) { + Database &db = Database::getInstance(); + std::ostringstream q; + q << "SELECT `items` FROM `players` WHERE `id` = " << player->getGUID(); + if ((result = db.storeQuery(q.str()))) { + unsigned long size; + const char* blob = result->getStream("items", size); + if (blob && size > 0) { + PropStream ps; + ps.init(blob, size); + while (true) { + uint32_t pid, sid; uint16_t type, count; + if (!ps.read(pid) || !ps.read(sid) || !ps.read(type) || !ps.read(count)) { break; } + std::string attrStr; if (!ps.readString(attrStr)) { break; } + PropStream attrStream; attrStream.init(attrStr.c_str(), attrStr.size()); + try { + const auto &item = Item::CreateItem(type, count); + if (item && item->unserializeAttr(attrStream)) { + inventoryItems[sid] = std::make_pair(item, static_cast(pid)); + } + } catch (...) { } } + } + } + } else { + auto query = fmt::format("SELECT pid, sid, itemtype, count, attributes FROM player_items WHERE player_id = {} ORDER BY sid DESC", player->getGUID()); + if ((result = g_database().storeQuery(query))) { + loadItems(inventoryItems, result, player); + } + } - const std::shared_ptr &container = it2->second.first->getContainer(); - if (container) { - container->internalAddThing(item); - // Here, the sub-containers do not yet have a parent, since the main backpack has not yet been added to the player, so we need to postpone - itemsToStartDecaying.emplace_back(item); - } + for (auto it = inventoryItems.rbegin(), end = inventoryItems.rend(); it != end; ++it) { + const std::pair, int32_t> &pair = it->second; + const auto &item = pair.first; + if (!item) { continue; } + int32_t pid = pair.second; + if (pid >= CONST_SLOT_FIRST && pid <= CONST_SLOT_LAST) { + player->internalAddThing(pid, item); + item->startDecaying(); + } else { + ItemsMap::const_iterator it2 = inventoryItems.find(pid); + if (it2 == inventoryItems.end()) { continue; } + const std::shared_ptr &container = it2->second.first->getContainer(); + if (container) { + container->internalAddThing(item); + itemsToStartDecaying.emplace_back(item); } + } - const std::shared_ptr &itemContainer = item->getContainer(); - if (itemContainer) { - for (const bool isLootContainer : { true, false }) { - const auto checkAttribute = isLootContainer ? ItemAttribute_t::QUICKLOOTCONTAINER : ItemAttribute_t::OBTAINCONTAINER; - if (item->hasAttribute(checkAttribute)) { - const auto flags = item->getAttribute(checkAttribute); - - for (uint8_t category = OBJECTCATEGORY_FIRST; category <= OBJECTCATEGORY_LAST; category++) { - if (hasBitSet(1 << category, flags)) { - player->refreshManagedContainer(static_cast(category), itemContainer, isLootContainer, true); - } + const std::shared_ptr &itemContainer = item->getContainer(); + if (itemContainer) { + for (const bool isLootContainer : { true, false }) { + const auto checkAttribute = isLootContainer ? ItemAttribute_t::QUICKLOOTCONTAINER : ItemAttribute_t::OBTAINCONTAINER; + if (item->hasAttribute(checkAttribute)) { + const auto flags = item->getAttribute(checkAttribute); + for (uint8_t category = OBJECTCATEGORY_FIRST; category <= OBJECTCATEGORY_LAST; category++) { + if (hasBitSet(1 << category, flags)) { + player->refreshManagedContainer(static_cast(category), itemContainer, isLootContainer, true); } } } @@ -620,7 +678,6 @@ void IOLoginDataLoad::loadPlayerInventoryItems(const std::shared_ptr &pl } } - // Now that all items and containers have been added and parent chain is established, start decay for (const auto &item : itemsToStartDecaying) { item->startDecaying(); } @@ -664,94 +721,178 @@ void IOLoginDataLoad::loadPlayerDepotItems(const std::shared_ptr &player return; } - ItemsMap depotItems; - std::vector> itemsToStartDecaying; - auto query = fmt::format("SELECT pid, sid, itemtype, count, attributes FROM player_depotitems WHERE player_id = {} ORDER BY sid DESC", player->getGUID()); - if ((result = g_database().storeQuery(query))) { - loadItems(depotItems, result, player); - for (auto it = depotItems.rbegin(), end = depotItems.rend(); it != end; ++it) { - const std::pair, int32_t> &pair = it->second; - const auto &item = pair.first; - if (!item) { - continue; + const auto kvFlag = player->kv()->scoped("depotitems")->get("use-blob"); + if (kvFlag && kvFlag->get()) { + ItemsMap depotItemsKV; + std::vector> itemsToStartDecayingKV; + Database &db = Database::getInstance(); + std::ostringstream query; + query << "SELECT `depotitems` FROM `players` WHERE `id` = " << player->getGUID(); + if ((result = db.storeQuery(query.str()))) { + unsigned long size; const char* blob = result->getStream("depotitems", size); + if (blob && size > 0) { + PropStream ps; ps.init(blob, size); + while (true) { + uint32_t pid, sid; uint16_t type, count; + if (!ps.read(pid) || !ps.read(sid) || !ps.read(type) || !ps.read(count)) { break; } + std::string attrStr; if (!ps.readString(attrStr)) { break; } + PropStream attrStream; attrStream.init(attrStr.c_str(), attrStr.size()); + try { const auto &item = Item::CreateItem(type, count); if (item && item->unserializeAttr(attrStream)) { depotItemsKV[sid] = std::make_pair(item, static_cast(pid)); } } catch (...) { } + } + } } - int32_t pid = pair.second; - if (pid >= 0 && pid < 100) { - const std::shared_ptr &depotChest = player->getDepotChest(pid, true); - if (depotChest) { - depotChest->internalAddThing(item); - item->startDecaying(); + for (auto it = depotItemsKV.rbegin(), end = depotItemsKV.rend(); it != end; ++it) { + const std::pair, int32_t> &pair = it->second; + const auto &item = pair.first; + if (!item) { continue; } + int32_t pid = pair.second; + if (pid >= 0 && pid < 100) { + const std::shared_ptr &depotChest = player->getDepotChest(pid, true); + if (depotChest) { depotChest->internalAddThing(item); item->startDecaying(); } + } else { + auto depotIt = depotItemsKV.find(pid); + if (depotIt == depotItemsKV.end()) { continue; } + const std::shared_ptr &container = depotIt->second.first->getContainer(); + if (container) { container->internalAddThing(item); itemsToStartDecayingKV.emplace_back(item); } } - } else { - auto depotIt = depotItems.find(pid); - if (depotIt == depotItems.end()) { + } + + // Now that all items and containers have been added and parent chain is established, start decay + for (const auto &item : itemsToStartDecayingKV) { + item->startDecaying(); + } + } else { + ItemsMap depotItems; + std::vector> itemsToStartDecaying; + auto query = fmt::format("SELECT pid, sid, itemtype, count, attributes FROM player_depotitems WHERE player_id = {} ORDER BY sid DESC", player->getGUID()); + if ((result = g_database().storeQuery(query))) { + loadItems(depotItems, result, player); + for (auto it = depotItems.rbegin(), end = depotItems.rend(); it != end; ++it) { + const std::pair, int32_t> &pair = it->second; + const auto &item = pair.first; + if (!item) { continue; } - const std::shared_ptr &container = depotIt->second.first->getContainer(); - if (container) { - container->internalAddThing(item); - // Here, the sub-containers do not yet have a parent, since the main backpack has not yet been added to the player, so we need to postpone - itemsToStartDecaying.emplace_back(item); + int32_t pid = pair.second; + if (pid >= 0 && pid < 100) { + const std::shared_ptr &depotChest = player->getDepotChest(pid, true); + if (depotChest) { + depotChest->internalAddThing(item); + item->startDecaying(); + } + } else { + auto depotIt = depotItems.find(pid); + if (depotIt == depotItems.end()) { + continue; + } + + const std::shared_ptr &container = depotIt->second.first->getContainer(); + if (container) { + container->internalAddThing(item); + // Here, the sub-containers do not yet have a parent, since the main backpack has not yet been added to the player, so we need to postpone + itemsToStartDecaying.emplace_back(item); + } } } } - } - // Now that all items and containers have been added and parent chain is established, start decay - for (const auto &item : itemsToStartDecaying) { - item->startDecaying(); + // Now that all items and containers have been added and parent chain is established, start decay + for (const auto &item : itemsToStartDecaying) { + item->startDecaying(); + } } } void IOLoginDataLoad::loadPlayerInboxItems(const std::shared_ptr &player, DBResult_ptr result) { - if (!result || !player) { - g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); - return; - } - - std::vector> itemsToStartDecaying; - auto query = fmt::format("SELECT pid, sid, itemtype, count, attributes FROM player_inboxitems WHERE player_id = {} ORDER BY sid DESC", player->getGUID()); - if ((result = g_database().storeQuery(query))) { - ItemsMap inboxItems; - loadItems(inboxItems, result, player); + if (!result || !player) { + g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); + return; + } + + const auto kvFlag = player->kv()->scoped("inboxitems")->get("use-blob"); + if (kvFlag && kvFlag->get()) { + ItemsMap inboxItemsKV; + std::vector> itemsToStartDecayingKV; + Database &db = Database::getInstance(); + std::ostringstream query; + query << "SELECT `inboxitems` FROM `players` WHERE `id` = " << player->getGUID(); + if ((result = db.storeQuery(query.str()))) { + unsigned long size; const char* blob = result->getStream("inboxitems", size); + if (blob && size > 0) { + PropStream ps; ps.init(blob, size); + while (true) { + uint32_t pid, sid; uint16_t type, count; + if (!ps.read(pid) || !ps.read(sid) || !ps.read(type) || !ps.read(count)) { break; } + std::string attrStr; if (!ps.readString(attrStr)) { break; } + PropStream attrStream; attrStream.init(attrStr.c_str(), attrStr.size()); + try { const auto &item = Item::CreateItem(type, count); if (item && item->unserializeAttr(attrStream)) { inboxItemsKV[sid] = std::make_pair(item, static_cast(pid)); } } catch (...) { } + } + } + } const auto &playerInbox = player->getInbox(); - if (!playerInbox) { - g_logger().warn("[{}] - Player inbox nullptr", __FUNCTION__); - return; - } + if (!playerInbox) { g_logger().warn("[{}] - Player inbox nullptr", __FUNCTION__); return; } - for (const auto &it : std::ranges::reverse_view(inboxItems)) { - const std::pair, int32_t> &pair = it.second; + for (auto it = inboxItemsKV.rbegin(), end = inboxItemsKV.rend(); it != end; ++it) { + const std::pair, int32_t> &pair = it->second; const auto &item = pair.first; - if (!item) { - continue; + if (!item) { continue; } + int32_t pid = pair.second; + if (pid >= 0 && pid < 100) { playerInbox->internalAddThing(item); item->startDecaying(); } + else { + auto inboxIt = inboxItemsKV.find(pid); + if (inboxIt == inboxItemsKV.end()) { continue; } + const std::shared_ptr &container = inboxIt->second.first->getContainer(); + if (container) { container->internalAddThing(item); itemsToStartDecayingKV.emplace_back(item); } } + } - int32_t pid = pair.second; - if (pid >= 0 && pid < 100) { - playerInbox->internalAddThing(item); - item->startDecaying(); - } else { - auto inboxIt = inboxItems.find(pid); - if (inboxIt == inboxItems.end()) { + for (const auto &item : itemsToStartDecayingKV) { item->startDecaying(); } + } else { + std::vector> itemsToStartDecaying; + auto query = fmt::format("SELECT pid, sid, itemtype, count, attributes FROM player_inboxitems WHERE player_id = {} ORDER BY sid DESC", player->getGUID()); + if ((result = g_database().storeQuery(query))) { + ItemsMap inboxItems; + loadItems(inboxItems, result, player); + + const auto &playerInbox = player->getInbox(); + if (!playerInbox) { + g_logger().warn("[{}] - Player inbox nullptr", __FUNCTION__); + return; + } + + for (const auto &it : std::ranges::reverse_view(inboxItems)) { + const std::pair, int32_t> &pair = it.second; + const auto &item = pair.first; + if (!item) { continue; } - const std::shared_ptr &container = inboxIt->second.first->getContainer(); - if (container) { - container->internalAddThing(item); - itemsToStartDecaying.emplace_back(item); + int32_t pid = pair.second; + if (pid >= 0 && pid < 100) { + playerInbox->internalAddThing(item); + item->startDecaying(); + } else { + auto inboxIt = inboxItems.find(pid); + if (inboxIt == inboxItems.end()) { + continue; + } + + const std::shared_ptr &container = inboxIt->second.first->getContainer(); + if (container) { + container->internalAddThing(item); + itemsToStartDecaying.emplace_back(item); + } } } } - } - // Now that all items and containers have been added and parent chain is established, start decay - for (const auto &item : itemsToStartDecaying) { - item->startDecaying(); + // Now that all items and containers have been added and parent chain is established, start decay + for (const auto &item : itemsToStartDecaying) { + item->startDecaying(); + } } } @@ -762,12 +903,32 @@ void IOLoginDataLoad::loadPlayerStorageMap(const std::shared_ptr &player } Database &db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = " << player->getGUID(); - if ((result = db.storeQuery(query.str()))) { - do { - player->addStorageValue(result->getNumber("key"), result->getNumber("value"), true); - } while (result->next()); + + const auto kvFlag = player->kv()->scoped("storages")->get("use-blob"); + if (kvFlag && kvFlag->get()) { + std::ostringstream query; + query << "SELECT `storages` FROM `players` WHERE `id` = " << player->getGUID(); + if ((result = db.storeQuery(query.str()))) { + unsigned long size; + const char* blob = result->getStream("storages", size); + if (blob && size > 0) { + PropStream ps; + ps.init(blob, size); + uint32_t key; + int32_t value; + while (ps.read(key) && ps.read(value)) { + player->addStorageValue(key, value, true); + } + } + } + } else { + std::ostringstream query; + query << "SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = " << player->getGUID(); + if ((result = db.storeQuery(query.str()))) { + do { + player->addStorageValue(result->getNumber("key"), result->getNumber("value"), true); + } while (result->next()); + } } } diff --git a/src/io/functions/iologindata_save_player.cpp b/src/io/functions/iologindata_save_player.cpp index bcceb82c9..ff5fb3448 100644 --- a/src/io/functions/iologindata_save_player.cpp +++ b/src/io/functions/iologindata_save_player.cpp @@ -28,137 +28,114 @@ #include "items/containers/inbox/inbox.hpp" #include "items/containers/rewards/reward.hpp" #include "creatures/players/player.hpp" +#include "kv/kv.hpp" -bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const ItemBlockList &itemList, DBInsert &query_insert, PropWriteStream &propWriteStream) { - if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); - return false; - } +bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const ItemBlockList &itemList, PropWriteStream &propWriteStream, std::ostringstream &query, DBInsert *query_insert, const std::string &blobColumn) { + if (!player) { + g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); + return false; + } - const Database &db = Database::getInstance(); - std::ostringstream ss; - - // Initialize variables + const auto &openContainers = player->getOpenContainers(); + int32_t runningId = 100; using ContainerBlock = std::pair, int32_t>; std::vector containers; - containers.reserve(32); + containers.reserve(64); - int32_t runningId = 100; + Database &db = Database::getInstance(); + PropWriteStream blobStream; + std::ostringstream ss; + + const bool toBlob = !blobColumn.empty(); + if (toBlob) { + const auto scope = player->kv()->scoped(blobColumn); + const auto kvFlag = scope->get("use-blob"); + if (!(kvFlag && kvFlag->get())) { scope->set("use-blob", true); } + + if (blobColumn == "stashitems") { + // Special-case: stash stores only (itemId, itemCount) pairs + for (const auto &[itemId, itemCount] : player->getStashItems()) { + blobStream.write(static_cast(itemId)); + blobStream.write(static_cast(itemCount)); + } + + size_t blobSize; const char* data = blobStream.getStream(blobSize); + query.str(""); query << "UPDATE `players` SET `" << blobColumn << "` = " << db.escapeBlob(data, static_cast(blobSize)) << " WHERE `id` = " << player->getGUID(); + return db.executeQuery(query.str()); + } + } - // Loop through each item in itemList - const auto &openContainers = player->getOpenContainers(); for (const auto &it : itemList) { const auto &item = it.second; - if (!item) { - continue; - } - + if (!item) { continue; } int32_t pid = it.first; - ++runningId; - // Update container attributes if necessary if (const std::shared_ptr &container = item->getContainer()) { - if (!container) { - continue; - } - if (container->getAttribute(ItemAttribute_t::OPENCONTAINER) > 0) { container->setAttribute(ItemAttribute_t::OPENCONTAINER, 0); } - if (!openContainers.empty()) { for (const auto &its : openContainers) { - auto openContainer = its.second; - auto opcontainer = openContainer.container; - - if (opcontainer == container) { - container->setAttribute(ItemAttribute_t::OPENCONTAINER, ((int)its.first) + 1); - break; - } + auto openContainer = its.second; auto opcontainer = openContainer.container; + if (opcontainer == container) { container->setAttribute(ItemAttribute_t::OPENCONTAINER, ((int)its.first) + 1); break; } } } - - // Add container to queue containers.emplace_back(container, runningId); } - // Serialize item attributes - propWriteStream.clear(); - item->serializeAttr(propWriteStream); - - size_t attributesSize; - const char* attributes = propWriteStream.getStream(attributesSize); - - // Build query string and add row - ss << player->getGUID() << ',' << pid << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, static_cast(attributesSize)); - if (!query_insert.addRow(ss)) { - g_logger().error("Error adding row to query."); - return false; + propWriteStream.clear(); item->serializeAttr(propWriteStream); + size_t attributesSize; const char* attributes = propWriteStream.getStream(attributesSize); + + if (toBlob) { + blobStream.write(pid); + blobStream.write(runningId); + blobStream.write(item->getID()); + blobStream.write(item->getSubType()); + blobStream.writeString(std::string(attributes, attributesSize)); + } else { + ss << player->getGUID() << ',' << pid << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, static_cast(attributesSize)); + if (!query_insert || !query_insert->addRow(ss)) { g_logger().error("Error adding row to query."); return false; } } } - // Loop through containers in queue - for (size_t i = 0; i < containers.size(); i++) { - const ContainerBlock &cb = containers[i]; - const std::shared_ptr &container = cb.first; - if (!container) { - continue; - } - + for (size_t i = 0; i < containers.size(); ++i) { + const auto &cb = containers[i]; const std::shared_ptr &container = cb.first; if (!container) { continue; } int32_t parentId = cb.second; - - // Loop through items in container - for (auto &item : container->getItemList()) { - if (!item) { - continue; - } - + for (auto &subItem : container->getItemList()) { + if (!subItem) { continue; } ++runningId; - - // Update sub-container attributes if necessary - const auto &subContainer = item->getContainer(); + const auto &subContainer = subItem->getContainer(); if (subContainer) { containers.emplace_back(subContainer, runningId); - if (subContainer->getAttribute(ItemAttribute_t::OPENCONTAINER) > 0) { - subContainer->setAttribute(ItemAttribute_t::OPENCONTAINER, 0); - } - + if (subContainer->getAttribute(ItemAttribute_t::OPENCONTAINER) > 0) { subContainer->setAttribute(ItemAttribute_t::OPENCONTAINER, 0); } if (!openContainers.empty()) { - for (const auto &it : openContainers) { - auto openContainer = it.second; - auto opcontainer = openContainer.container; - - if (opcontainer == subContainer) { - subContainer->setAttribute(ItemAttribute_t::OPENCONTAINER, (it.first) + 1); - break; - } - } + for (const auto &it : openContainers) { auto openContainer = it.second; auto opcontainer = openContainer.container; if (opcontainer == subContainer) { subContainer->setAttribute(ItemAttribute_t::OPENCONTAINER, (it.first) + 1); break; } } } } - - // Serialize item attributes - propWriteStream.clear(); - item->serializeAttr(propWriteStream); - - size_t attributesSize; - const char* attributes = propWriteStream.getStream(attributesSize); - - // Build query string and add row - ss << player->getGUID() << ',' << parentId << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, static_cast(attributesSize)); - if (!query_insert.addRow(ss)) { - g_logger().error("Error adding row to query for container item."); - return false; + propWriteStream.clear(); subItem->serializeAttr(propWriteStream); + size_t attributesSize; const char* attributes = propWriteStream.getStream(attributesSize); + + if (toBlob) { + blobStream.write(parentId); + blobStream.write(runningId); + blobStream.write(subItem->getID()); + blobStream.write(subItem->getSubType()); + blobStream.writeString(std::string(attributes, attributesSize)); + } else { + ss << player->getGUID() << ',' << parentId << ',' << runningId << ',' << subItem->getID() << ',' << subItem->getSubType() << ',' << db.escapeBlob(attributes, static_cast(attributesSize)); + if (!query_insert || !query_insert->addRow(ss)) { g_logger().error("Error adding row to query for container item."); return false; } } } } - // Execute query - if (!query_insert.execute()) { - g_logger().error("Error executing query."); - return false; + if (toBlob) { + size_t blobSize; const char* data = blobStream.getStream(blobSize); + query.str(""); query << "UPDATE `players` SET `" << blobColumn << "` = " << db.escapeBlob(data, static_cast(blobSize)) << " WHERE `id` = " << player->getGUID(); + return db.executeQuery(query.str()); } + if (!query_insert->execute()) { g_logger().error("Error executing query."); return false; } return true; } @@ -362,32 +339,13 @@ bool IOLoginDataSave::savePlayerFirst(const std::shared_ptr &player) { } bool IOLoginDataSave::savePlayerStash(const std::shared_ptr &player) { - if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); - return false; - } - - Database &db = Database::getInstance(); - std::ostringstream query; - query << "DELETE FROM `player_stash` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { - return false; - } - - query.str(""); - - DBInsert stashQuery("INSERT INTO `player_stash` (`player_id`,`item_id`,`item_count`) VALUES "); - for (const auto &[itemId, itemCount] : player->getStashItems()) { - query << player->getGUID() << ',' << itemId << ',' << itemCount; - if (!stashQuery.addRow(query)) { - return false; - } - } - - if (!stashQuery.execute()) { - return false; - } - return true; + if (!player) { + g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); + return false; + } + ItemBlockList itemList; + std::ostringstream query; PropWriteStream propWriteStream; propWriteStream.clear(); + return saveItems(player, itemList, propWriteStream, query, nullptr, "stashitems"); } bool IOLoginDataSave::savePlayerSpells(const std::shared_ptr &player) { @@ -398,25 +356,22 @@ bool IOLoginDataSave::savePlayerSpells(const std::shared_ptr &player) { Database &db = Database::getInstance(); std::ostringstream query; - query << "DELETE FROM `player_spells` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { - return false; + const auto spellsScope = player->kv()->scoped("spells"); + const auto kvFlag = spellsScope->get("use-blob"); + if (!(kvFlag && kvFlag->get())) { + spellsScope->set("use-blob", true); } - - query.str(""); - - DBInsert spellsQuery("INSERT INTO `player_spells` (`player_id`, `name` ) VALUES "); - for (const std::string &spellName : player->learnedInstantSpellList) { - query << player->getGUID() << ',' << db.escapeString(spellName); - if (!spellsQuery.addRow(query)) { - return false; - } + PropWriteStream stream; + for (const auto &spellName : player->learnedInstantSpellList) { + stream.writeString(spellName); } - if (!spellsQuery.execute()) { - return false; - } - return true; + size_t size; + const char* data = stream.getStream(size); + query.str(""); + query << "UPDATE `players` SET `spells` = " << db.escapeBlob(data, static_cast(size)) + << " WHERE `id` = " << player->getGUID(); + return db.executeQuery(query.str()); } bool IOLoginDataSave::savePlayerKills(const std::shared_ptr &player) { @@ -494,35 +449,17 @@ bool IOLoginDataSave::savePlayerBestiarySystem(const std::shared_ptr &pl } bool IOLoginDataSave::savePlayerItem(const std::shared_ptr &player) { - if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); - return false; - } - - Database &db = Database::getInstance(); - PropWriteStream propWriteStream; - std::ostringstream query; - query << "DELETE FROM `player_items` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { - g_logger().warn("[IOLoginData::savePlayer] - Error delete query 'player_items' from player: {}", player->getName()); - return false; - } - - DBInsert itemsQuery("INSERT INTO `player_items` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); - - ItemBlockList itemList; - for (int32_t slotId = CONST_SLOT_FIRST; slotId <= CONST_SLOT_LAST; ++slotId) { - const auto &item = player->inventory[slotId]; - if (item) { - itemList.emplace_back(slotId, item); - } - } - - if (!saveItems(player, itemList, itemsQuery, propWriteStream)) { - g_logger().warn("[IOLoginData::savePlayer] - Failed for save items from player: {}", player->getName()); - return false; - } - return true; + if (!player) { + g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); + return false; + } + ItemBlockList itemList; + for (int32_t slotId = CONST_SLOT_FIRST; slotId <= CONST_SLOT_LAST; ++slotId) { + const auto &item = player->inventory[slotId]; + if (item) { itemList.emplace_back(slotId, item); } + } + std::ostringstream query; PropWriteStream propWriteStream; propWriteStream.clear(); + return saveItems(player, itemList, propWriteStream, query, nullptr, "items"); } bool IOLoginDataSave::savePlayerDepotItems(const std::shared_ptr &player) { @@ -531,33 +468,15 @@ bool IOLoginDataSave::savePlayerDepotItems(const std::shared_ptr &player return false; } - Database &db = Database::getInstance(); - PropWriteStream propWriteStream; - ItemDepotList depotList; - if (player->lastDepotId != -1) { - std::ostringstream query; - query << "DELETE FROM `player_depotitems` WHERE `player_id` = " << player->getGUID(); - - if (!db.executeQuery(query.str())) { - return false; - } - - query.str(""); - - DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); - - for (const auto &[pid, depotChest] : player->depotChests) { - for (const std::shared_ptr &item : depotChest->getItemList()) { - depotList.emplace_back(pid, item); - } - } - - if (!saveItems(player, depotList, depotQuery, propWriteStream)) { - return false; - } - return true; - } - return true; + if (player->lastDepotId == -1) { return true; } + ItemBlockList itemList; + for (const auto &[pid, depotChest] : player->depotChests) { + for (const std::shared_ptr &item : depotChest->getItemList()) { + itemList.emplace_back(pid, item); + } + } + std::ostringstream query; PropWriteStream propWriteStream; propWriteStream.clear(); + return saveItems(player, itemList, propWriteStream, query, nullptr, "depotitems"); } bool IOLoginDataSave::saveRewardItems(const std::shared_ptr &player) { @@ -585,11 +504,11 @@ bool IOLoginDataSave::saveRewardItems(const std::shared_ptr &player) { } } - DBInsert rewardQuery("INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); - PropWriteStream propWriteStream; - if (!saveItems(player, rewardListItems, rewardQuery, propWriteStream)) { - return false; - } + DBInsert rewardQuery("INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + PropWriteStream propWriteStream; std::ostringstream queryTmp; + if (!saveItems(player, rewardListItems, propWriteStream, queryTmp, &rewardQuery, std::string())) { + return false; + } } return true; } @@ -600,26 +519,12 @@ bool IOLoginDataSave::savePlayerInbox(const std::shared_ptr &player) { return false; } - Database &db = Database::getInstance(); - PropWriteStream propWriteStream; - ItemInboxList inboxList; - std::ostringstream query; - query << "DELETE FROM `player_inboxitems` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { - return false; - } - - query.str(""); - DBInsert inboxQuery("INSERT INTO `player_inboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); - - for (const auto &item : player->getInbox()->getItemList()) { - inboxList.emplace_back(0, item); - } - - if (!saveItems(player, inboxList, inboxQuery, propWriteStream)) { - return false; - } - return true; + ItemBlockList itemList; + for (auto it = player->getInbox()->getReversedItems(), end = player->getInbox()->getReversedEnd(); it != end; ++it) { + itemList.emplace_back(0, *it); + } + std::ostringstream query; PropWriteStream propWriteStream; propWriteStream.clear(); + return saveItems(player, itemList, propWriteStream, query, nullptr, "inboxitems"); } bool IOLoginDataSave::savePlayerPreyClass(const std::shared_ptr &player) { @@ -814,27 +719,28 @@ bool IOLoginDataSave::savePlayerStorage(const std::shared_ptr &player) { Database &db = Database::getInstance(); std::ostringstream query; - query << "DELETE FROM `player_storage` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { - return false; - } - query.str(""); + const auto storagesScope = player->kv()->scoped("storages"); + const auto kvFlag = storagesScope->get("use-blob"); + if (!(kvFlag && kvFlag->get())) { + storagesScope->set("use-blob", true); + } - DBInsert storageQuery("INSERT INTO `player_storage` (`player_id`, `key`, `value`) VALUES "); player->genReservedStorageRange(); + PropWriteStream stream; for (const auto &[key, value] : player->storageMap) { - query << player->getGUID() << ',' << key << ',' << value; - if (!storageQuery.addRow(query)) { - return false; - } + stream.write(key); + stream.write(value); } - if (!storageQuery.execute()) { - return false; - } - return true; + size_t size; + const char* data = stream.getStream(size); + + query.str(""); + query << "UPDATE `players` SET `storages` = " << db.escapeBlob(data, static_cast(size)) + << " WHERE `id` = " << player->getGUID(); + return db.executeQuery(query.str()); } bool IOLoginDataSave::savePlayerStatement(const std::shared_ptr &player, const std::string &receiver, uint16_t channelId, const std::string &text, uint32_t &statementId) { diff --git a/src/io/functions/iologindata_save_player.hpp b/src/io/functions/iologindata_save_player.hpp index 7743e7d11..6fc27a69e 100644 --- a/src/io/functions/iologindata_save_player.hpp +++ b/src/io/functions/iologindata_save_player.hpp @@ -49,5 +49,5 @@ class IOLoginDataSave : public IOLoginData { using ItemRewardList = std::list>>; using ItemInboxList = std::list>>; - static bool saveItems(const std::shared_ptr &player, const ItemBlockList &itemList, DBInsert &query_insert, PropWriteStream &stream); + static bool saveItems(const std::shared_ptr &player, const ItemBlockList &itemList, PropWriteStream &stream, std::ostringstream &query, DBInsert *query_insert, const std::string &blobColumn); }; From 3e74183212959bdb4e0a43f06597fb1c9a8fa1be Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 9 Dec 2025 20:44:40 +0000 Subject: [PATCH 2/3] Code format - (Clang-format) --- src/io/functions/iologindata_load_player.cpp | 160 +++++++++----- src/io/functions/iologindata_save_player.cpp | 211 ++++++++++++------- src/io/functions/iologindata_save_player.hpp | 2 +- 3 files changed, 246 insertions(+), 127 deletions(-) diff --git a/src/io/functions/iologindata_load_player.cpp b/src/io/functions/iologindata_load_player.cpp index 1df1e9567..0b5aa5835 100644 --- a/src/io/functions/iologindata_load_player.cpp +++ b/src/io/functions/iologindata_load_player.cpp @@ -493,8 +493,11 @@ void IOLoginDataLoad::loadPlayerStashItems(const std::shared_ptr &player PropStream ps; ps.init(blob, size); while (true) { - uint16_t itemId; uint32_t itemCount; - if (!ps.read(itemId) || !ps.read(itemCount)) { break; } + uint16_t itemId; + uint32_t itemCount; + if (!ps.read(itemId) || !ps.read(itemCount)) { + break; + } player->addItemOnStash(itemId, itemCount); } } @@ -624,10 +627,17 @@ void IOLoginDataLoad::loadPlayerInventoryItems(const std::shared_ptr &pl PropStream ps; ps.init(blob, size); while (true) { - uint32_t pid, sid; uint16_t type, count; - if (!ps.read(pid) || !ps.read(sid) || !ps.read(type) || !ps.read(count)) { break; } - std::string attrStr; if (!ps.readString(attrStr)) { break; } - PropStream attrStream; attrStream.init(attrStr.c_str(), attrStr.size()); + uint32_t pid, sid; + uint16_t type, count; + if (!ps.read(pid) || !ps.read(sid) || !ps.read(type) || !ps.read(count)) { + break; + } + std::string attrStr; + if (!ps.readString(attrStr)) { + break; + } + PropStream attrStream; + attrStream.init(attrStr.c_str(), attrStr.size()); try { const auto &item = Item::CreateItem(type, count); if (item && item->unserializeAttr(attrStream)) { @@ -647,14 +657,18 @@ void IOLoginDataLoad::loadPlayerInventoryItems(const std::shared_ptr &pl for (auto it = inventoryItems.rbegin(), end = inventoryItems.rend(); it != end; ++it) { const std::pair, int32_t> &pair = it->second; const auto &item = pair.first; - if (!item) { continue; } + if (!item) { + continue; + } int32_t pid = pair.second; if (pid >= CONST_SLOT_FIRST && pid <= CONST_SLOT_LAST) { player->internalAddThing(pid, item); item->startDecaying(); } else { ItemsMap::const_iterator it2 = inventoryItems.find(pid); - if (it2 == inventoryItems.end()) { continue; } + if (it2 == inventoryItems.end()) { + continue; + } const std::shared_ptr &container = it2->second.first->getContainer(); if (container) { container->internalAddThing(item); @@ -729,34 +743,58 @@ void IOLoginDataLoad::loadPlayerDepotItems(const std::shared_ptr &player std::ostringstream query; query << "SELECT `depotitems` FROM `players` WHERE `id` = " << player->getGUID(); if ((result = db.storeQuery(query.str()))) { - unsigned long size; const char* blob = result->getStream("depotitems", size); + unsigned long size; + const char* blob = result->getStream("depotitems", size); if (blob && size > 0) { - PropStream ps; ps.init(blob, size); + PropStream ps; + ps.init(blob, size); while (true) { - uint32_t pid, sid; uint16_t type, count; - if (!ps.read(pid) || !ps.read(sid) || !ps.read(type) || !ps.read(count)) { break; } - std::string attrStr; if (!ps.readString(attrStr)) { break; } - PropStream attrStream; attrStream.init(attrStr.c_str(), attrStr.size()); - try { const auto &item = Item::CreateItem(type, count); if (item && item->unserializeAttr(attrStream)) { depotItemsKV[sid] = std::make_pair(item, static_cast(pid)); } } catch (...) { } + uint32_t pid, sid; + uint16_t type, count; + if (!ps.read(pid) || !ps.read(sid) || !ps.read(type) || !ps.read(count)) { + break; + } + std::string attrStr; + if (!ps.readString(attrStr)) { + break; } + PropStream attrStream; + attrStream.init(attrStr.c_str(), attrStr.size()); + try { + const auto &item = Item::CreateItem(type, count); + if (item && item->unserializeAttr(attrStream)) { + depotItemsKV[sid] = std::make_pair(item, static_cast(pid)); + } + } catch (...) { } } } + } - for (auto it = depotItemsKV.rbegin(), end = depotItemsKV.rend(); it != end; ++it) { - const std::pair, int32_t> &pair = it->second; - const auto &item = pair.first; - if (!item) { continue; } - int32_t pid = pair.second; - if (pid >= 0 && pid < 100) { - const std::shared_ptr &depotChest = player->getDepotChest(pid, true); - if (depotChest) { depotChest->internalAddThing(item); item->startDecaying(); } - } else { - auto depotIt = depotItemsKV.find(pid); - if (depotIt == depotItemsKV.end()) { continue; } - const std::shared_ptr &container = depotIt->second.first->getContainer(); - if (container) { container->internalAddThing(item); itemsToStartDecayingKV.emplace_back(item); } + for (auto it = depotItemsKV.rbegin(), end = depotItemsKV.rend(); it != end; ++it) { + const std::pair, int32_t> &pair = it->second; + const auto &item = pair.first; + if (!item) { + continue; + } + int32_t pid = pair.second; + if (pid >= 0 && pid < 100) { + const std::shared_ptr &depotChest = player->getDepotChest(pid, true); + if (depotChest) { + depotChest->internalAddThing(item); + item->startDecaying(); + } + } else { + auto depotIt = depotItemsKV.find(pid); + if (depotIt == depotItemsKV.end()) { + continue; + } + const std::shared_ptr &container = depotIt->second.first->getContainer(); + if (container) { + container->internalAddThing(item); + itemsToStartDecayingKV.emplace_back(item); } } + } // Now that all items and containers have been added and parent chain is established, start decay for (const auto &item : itemsToStartDecayingKV) { @@ -806,12 +844,12 @@ void IOLoginDataLoad::loadPlayerDepotItems(const std::shared_ptr &player } void IOLoginDataLoad::loadPlayerInboxItems(const std::shared_ptr &player, DBResult_ptr result) { - if (!result || !player) { - g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); - return; - } + if (!result || !player) { + g_logger().warn("[{}] - Player or Result nullptr", __FUNCTION__); + return; + } - const auto kvFlag = player->kv()->scoped("inboxitems")->get("use-blob"); + const auto kvFlag = player->kv()->scoped("inboxitems")->get("use-blob"); if (kvFlag && kvFlag->get()) { ItemsMap inboxItemsKV; std::vector> itemsToStartDecayingKV; @@ -819,37 +857,65 @@ void IOLoginDataLoad::loadPlayerInboxItems(const std::shared_ptr &player std::ostringstream query; query << "SELECT `inboxitems` FROM `players` WHERE `id` = " << player->getGUID(); if ((result = db.storeQuery(query.str()))) { - unsigned long size; const char* blob = result->getStream("inboxitems", size); + unsigned long size; + const char* blob = result->getStream("inboxitems", size); if (blob && size > 0) { - PropStream ps; ps.init(blob, size); + PropStream ps; + ps.init(blob, size); while (true) { - uint32_t pid, sid; uint16_t type, count; - if (!ps.read(pid) || !ps.read(sid) || !ps.read(type) || !ps.read(count)) { break; } - std::string attrStr; if (!ps.readString(attrStr)) { break; } - PropStream attrStream; attrStream.init(attrStr.c_str(), attrStr.size()); - try { const auto &item = Item::CreateItem(type, count); if (item && item->unserializeAttr(attrStream)) { inboxItemsKV[sid] = std::make_pair(item, static_cast(pid)); } } catch (...) { } + uint32_t pid, sid; + uint16_t type, count; + if (!ps.read(pid) || !ps.read(sid) || !ps.read(type) || !ps.read(count)) { + break; + } + std::string attrStr; + if (!ps.readString(attrStr)) { + break; + } + PropStream attrStream; + attrStream.init(attrStr.c_str(), attrStr.size()); + try { + const auto &item = Item::CreateItem(type, count); + if (item && item->unserializeAttr(attrStream)) { + inboxItemsKV[sid] = std::make_pair(item, static_cast(pid)); + } + } catch (...) { } } } } const auto &playerInbox = player->getInbox(); - if (!playerInbox) { g_logger().warn("[{}] - Player inbox nullptr", __FUNCTION__); return; } + if (!playerInbox) { + g_logger().warn("[{}] - Player inbox nullptr", __FUNCTION__); + return; + } for (auto it = inboxItemsKV.rbegin(), end = inboxItemsKV.rend(); it != end; ++it) { const std::pair, int32_t> &pair = it->second; const auto &item = pair.first; - if (!item) { continue; } + if (!item) { + continue; + } int32_t pid = pair.second; - if (pid >= 0 && pid < 100) { playerInbox->internalAddThing(item); item->startDecaying(); } - else { + if (pid >= 0 && pid < 100) { + playerInbox->internalAddThing(item); + item->startDecaying(); + } else { auto inboxIt = inboxItemsKV.find(pid); - if (inboxIt == inboxItemsKV.end()) { continue; } + if (inboxIt == inboxItemsKV.end()) { + continue; + } const std::shared_ptr &container = inboxIt->second.first->getContainer(); - if (container) { container->internalAddThing(item); itemsToStartDecayingKV.emplace_back(item); } + if (container) { + container->internalAddThing(item); + itemsToStartDecayingKV.emplace_back(item); + } } } - for (const auto &item : itemsToStartDecayingKV) { item->startDecaying(); } + for (const auto &item : itemsToStartDecayingKV) { + item->startDecaying(); + } } else { std::vector> itemsToStartDecaying; auto query = fmt::format("SELECT pid, sid, itemtype, count, attributes FROM player_inboxitems WHERE player_id = {} ORDER BY sid DESC", player->getGUID()); diff --git a/src/io/functions/iologindata_save_player.cpp b/src/io/functions/iologindata_save_player.cpp index ff5fb3448..c72e5855f 100644 --- a/src/io/functions/iologindata_save_player.cpp +++ b/src/io/functions/iologindata_save_player.cpp @@ -30,11 +30,11 @@ #include "creatures/players/player.hpp" #include "kv/kv.hpp" -bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const ItemBlockList &itemList, PropWriteStream &propWriteStream, std::ostringstream &query, DBInsert *query_insert, const std::string &blobColumn) { - if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); - return false; - } +bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const ItemBlockList &itemList, PropWriteStream &propWriteStream, std::ostringstream &query, DBInsert* query_insert, const std::string &blobColumn) { + if (!player) { + g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); + return false; + } const auto &openContainers = player->getOpenContainers(); int32_t runningId = 100; @@ -46,28 +46,34 @@ bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const Ite PropWriteStream blobStream; std::ostringstream ss; - const bool toBlob = !blobColumn.empty(); - if (toBlob) { - const auto scope = player->kv()->scoped(blobColumn); - const auto kvFlag = scope->get("use-blob"); - if (!(kvFlag && kvFlag->get())) { scope->set("use-blob", true); } - - if (blobColumn == "stashitems") { - // Special-case: stash stores only (itemId, itemCount) pairs - for (const auto &[itemId, itemCount] : player->getStashItems()) { - blobStream.write(static_cast(itemId)); - blobStream.write(static_cast(itemCount)); - } - - size_t blobSize; const char* data = blobStream.getStream(blobSize); - query.str(""); query << "UPDATE `players` SET `" << blobColumn << "` = " << db.escapeBlob(data, static_cast(blobSize)) << " WHERE `id` = " << player->getGUID(); - return db.executeQuery(query.str()); - } - } + const bool toBlob = !blobColumn.empty(); + if (toBlob) { + const auto scope = player->kv()->scoped(blobColumn); + const auto kvFlag = scope->get("use-blob"); + if (!(kvFlag && kvFlag->get())) { + scope->set("use-blob", true); + } + + if (blobColumn == "stashitems") { + // Special-case: stash stores only (itemId, itemCount) pairs + for (const auto &[itemId, itemCount] : player->getStashItems()) { + blobStream.write(static_cast(itemId)); + blobStream.write(static_cast(itemCount)); + } + + size_t blobSize; + const char* data = blobStream.getStream(blobSize); + query.str(""); + query << "UPDATE `players` SET `" << blobColumn << "` = " << db.escapeBlob(data, static_cast(blobSize)) << " WHERE `id` = " << player->getGUID(); + return db.executeQuery(query.str()); + } + } for (const auto &it : itemList) { const auto &item = it.second; - if (!item) { continue; } + if (!item) { + continue; + } int32_t pid = it.first; ++runningId; @@ -77,15 +83,21 @@ bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const Ite } if (!openContainers.empty()) { for (const auto &its : openContainers) { - auto openContainer = its.second; auto opcontainer = openContainer.container; - if (opcontainer == container) { container->setAttribute(ItemAttribute_t::OPENCONTAINER, ((int)its.first) + 1); break; } + auto openContainer = its.second; + auto opcontainer = openContainer.container; + if (opcontainer == container) { + container->setAttribute(ItemAttribute_t::OPENCONTAINER, ((int)its.first) + 1); + break; + } } } containers.emplace_back(container, runningId); } - propWriteStream.clear(); item->serializeAttr(propWriteStream); - size_t attributesSize; const char* attributes = propWriteStream.getStream(attributesSize); + propWriteStream.clear(); + item->serializeAttr(propWriteStream); + size_t attributesSize; + const char* attributes = propWriteStream.getStream(attributesSize); if (toBlob) { blobStream.write(pid); @@ -95,26 +107,46 @@ bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const Ite blobStream.writeString(std::string(attributes, attributesSize)); } else { ss << player->getGUID() << ',' << pid << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, static_cast(attributesSize)); - if (!query_insert || !query_insert->addRow(ss)) { g_logger().error("Error adding row to query."); return false; } + if (!query_insert || !query_insert->addRow(ss)) { + g_logger().error("Error adding row to query."); + return false; + } } } for (size_t i = 0; i < containers.size(); ++i) { - const auto &cb = containers[i]; const std::shared_ptr &container = cb.first; if (!container) { continue; } + const auto &cb = containers[i]; + const std::shared_ptr &container = cb.first; + if (!container) { + continue; + } int32_t parentId = cb.second; for (auto &subItem : container->getItemList()) { - if (!subItem) { continue; } + if (!subItem) { + continue; + } ++runningId; const auto &subContainer = subItem->getContainer(); if (subContainer) { containers.emplace_back(subContainer, runningId); - if (subContainer->getAttribute(ItemAttribute_t::OPENCONTAINER) > 0) { subContainer->setAttribute(ItemAttribute_t::OPENCONTAINER, 0); } + if (subContainer->getAttribute(ItemAttribute_t::OPENCONTAINER) > 0) { + subContainer->setAttribute(ItemAttribute_t::OPENCONTAINER, 0); + } if (!openContainers.empty()) { - for (const auto &it : openContainers) { auto openContainer = it.second; auto opcontainer = openContainer.container; if (opcontainer == subContainer) { subContainer->setAttribute(ItemAttribute_t::OPENCONTAINER, (it.first) + 1); break; } } + for (const auto &it : openContainers) { + auto openContainer = it.second; + auto opcontainer = openContainer.container; + if (opcontainer == subContainer) { + subContainer->setAttribute(ItemAttribute_t::OPENCONTAINER, (it.first) + 1); + break; + } + } } } - propWriteStream.clear(); subItem->serializeAttr(propWriteStream); - size_t attributesSize; const char* attributes = propWriteStream.getStream(attributesSize); + propWriteStream.clear(); + subItem->serializeAttr(propWriteStream); + size_t attributesSize; + const char* attributes = propWriteStream.getStream(attributesSize); if (toBlob) { blobStream.write(parentId); @@ -124,18 +156,26 @@ bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const Ite blobStream.writeString(std::string(attributes, attributesSize)); } else { ss << player->getGUID() << ',' << parentId << ',' << runningId << ',' << subItem->getID() << ',' << subItem->getSubType() << ',' << db.escapeBlob(attributes, static_cast(attributesSize)); - if (!query_insert || !query_insert->addRow(ss)) { g_logger().error("Error adding row to query for container item."); return false; } + if (!query_insert || !query_insert->addRow(ss)) { + g_logger().error("Error adding row to query for container item."); + return false; + } } } } if (toBlob) { - size_t blobSize; const char* data = blobStream.getStream(blobSize); - query.str(""); query << "UPDATE `players` SET `" << blobColumn << "` = " << db.escapeBlob(data, static_cast(blobSize)) << " WHERE `id` = " << player->getGUID(); + size_t blobSize; + const char* data = blobStream.getStream(blobSize); + query.str(""); + query << "UPDATE `players` SET `" << blobColumn << "` = " << db.escapeBlob(data, static_cast(blobSize)) << " WHERE `id` = " << player->getGUID(); return db.executeQuery(query.str()); } - if (!query_insert->execute()) { g_logger().error("Error executing query."); return false; } + if (!query_insert->execute()) { + g_logger().error("Error executing query."); + return false; + } return true; } @@ -339,13 +379,15 @@ bool IOLoginDataSave::savePlayerFirst(const std::shared_ptr &player) { } bool IOLoginDataSave::savePlayerStash(const std::shared_ptr &player) { - if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); - return false; - } - ItemBlockList itemList; - std::ostringstream query; PropWriteStream propWriteStream; propWriteStream.clear(); - return saveItems(player, itemList, propWriteStream, query, nullptr, "stashitems"); + if (!player) { + g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); + return false; + } + ItemBlockList itemList; + std::ostringstream query; + PropWriteStream propWriteStream; + propWriteStream.clear(); + return saveItems(player, itemList, propWriteStream, query, nullptr, "stashitems"); } bool IOLoginDataSave::savePlayerSpells(const std::shared_ptr &player) { @@ -370,7 +412,7 @@ bool IOLoginDataSave::savePlayerSpells(const std::shared_ptr &player) { const char* data = stream.getStream(size); query.str(""); query << "UPDATE `players` SET `spells` = " << db.escapeBlob(data, static_cast(size)) - << " WHERE `id` = " << player->getGUID(); + << " WHERE `id` = " << player->getGUID(); return db.executeQuery(query.str()); } @@ -449,17 +491,21 @@ bool IOLoginDataSave::savePlayerBestiarySystem(const std::shared_ptr &pl } bool IOLoginDataSave::savePlayerItem(const std::shared_ptr &player) { - if (!player) { - g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); - return false; - } - ItemBlockList itemList; - for (int32_t slotId = CONST_SLOT_FIRST; slotId <= CONST_SLOT_LAST; ++slotId) { - const auto &item = player->inventory[slotId]; - if (item) { itemList.emplace_back(slotId, item); } - } - std::ostringstream query; PropWriteStream propWriteStream; propWriteStream.clear(); - return saveItems(player, itemList, propWriteStream, query, nullptr, "items"); + if (!player) { + g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); + return false; + } + ItemBlockList itemList; + for (int32_t slotId = CONST_SLOT_FIRST; slotId <= CONST_SLOT_LAST; ++slotId) { + const auto &item = player->inventory[slotId]; + if (item) { + itemList.emplace_back(slotId, item); + } + } + std::ostringstream query; + PropWriteStream propWriteStream; + propWriteStream.clear(); + return saveItems(player, itemList, propWriteStream, query, nullptr, "items"); } bool IOLoginDataSave::savePlayerDepotItems(const std::shared_ptr &player) { @@ -468,15 +514,19 @@ bool IOLoginDataSave::savePlayerDepotItems(const std::shared_ptr &player return false; } - if (player->lastDepotId == -1) { return true; } - ItemBlockList itemList; - for (const auto &[pid, depotChest] : player->depotChests) { - for (const std::shared_ptr &item : depotChest->getItemList()) { - itemList.emplace_back(pid, item); - } - } - std::ostringstream query; PropWriteStream propWriteStream; propWriteStream.clear(); - return saveItems(player, itemList, propWriteStream, query, nullptr, "depotitems"); + if (player->lastDepotId == -1) { + return true; + } + ItemBlockList itemList; + for (const auto &[pid, depotChest] : player->depotChests) { + for (const std::shared_ptr &item : depotChest->getItemList()) { + itemList.emplace_back(pid, item); + } + } + std::ostringstream query; + PropWriteStream propWriteStream; + propWriteStream.clear(); + return saveItems(player, itemList, propWriteStream, query, nullptr, "depotitems"); } bool IOLoginDataSave::saveRewardItems(const std::shared_ptr &player) { @@ -504,11 +554,12 @@ bool IOLoginDataSave::saveRewardItems(const std::shared_ptr &player) { } } - DBInsert rewardQuery("INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); - PropWriteStream propWriteStream; std::ostringstream queryTmp; - if (!saveItems(player, rewardListItems, propWriteStream, queryTmp, &rewardQuery, std::string())) { - return false; - } + DBInsert rewardQuery("INSERT INTO `player_rewards` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + PropWriteStream propWriteStream; + std::ostringstream queryTmp; + if (!saveItems(player, rewardListItems, propWriteStream, queryTmp, &rewardQuery, std::string())) { + return false; + } } return true; } @@ -519,12 +570,14 @@ bool IOLoginDataSave::savePlayerInbox(const std::shared_ptr &player) { return false; } - ItemBlockList itemList; - for (auto it = player->getInbox()->getReversedItems(), end = player->getInbox()->getReversedEnd(); it != end; ++it) { - itemList.emplace_back(0, *it); - } - std::ostringstream query; PropWriteStream propWriteStream; propWriteStream.clear(); - return saveItems(player, itemList, propWriteStream, query, nullptr, "inboxitems"); + ItemBlockList itemList; + for (auto it = player->getInbox()->getReversedItems(), end = player->getInbox()->getReversedEnd(); it != end; ++it) { + itemList.emplace_back(0, *it); + } + std::ostringstream query; + PropWriteStream propWriteStream; + propWriteStream.clear(); + return saveItems(player, itemList, propWriteStream, query, nullptr, "inboxitems"); } bool IOLoginDataSave::savePlayerPreyClass(const std::shared_ptr &player) { @@ -739,7 +792,7 @@ bool IOLoginDataSave::savePlayerStorage(const std::shared_ptr &player) { query.str(""); query << "UPDATE `players` SET `storages` = " << db.escapeBlob(data, static_cast(size)) - << " WHERE `id` = " << player->getGUID(); + << " WHERE `id` = " << player->getGUID(); return db.executeQuery(query.str()); } diff --git a/src/io/functions/iologindata_save_player.hpp b/src/io/functions/iologindata_save_player.hpp index 6fc27a69e..6b7dbdb48 100644 --- a/src/io/functions/iologindata_save_player.hpp +++ b/src/io/functions/iologindata_save_player.hpp @@ -49,5 +49,5 @@ class IOLoginDataSave : public IOLoginData { using ItemRewardList = std::list>>; using ItemInboxList = std::list>>; - static bool saveItems(const std::shared_ptr &player, const ItemBlockList &itemList, PropWriteStream &stream, std::ostringstream &query, DBInsert *query_insert, const std::string &blobColumn); + static bool saveItems(const std::shared_ptr &player, const ItemBlockList &itemList, PropWriteStream &stream, std::ostringstream &query, DBInsert* query_insert, const std::string &blobColumn); }; From 1a90e7b74cc489a3198b4dbc2b258d6b6d0f21c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo?= Date: Sun, 15 Mar 2026 10:33:36 -0300 Subject: [PATCH 3/3] Create 61.lua --- data/migrations/61.lua | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 data/migrations/61.lua diff --git a/data/migrations/61.lua b/data/migrations/61.lua new file mode 100644 index 000000000..c63b5274a --- /dev/null +++ b/data/migrations/61.lua @@ -0,0 +1,9 @@ +function onUpdateDatabase() + logger.info("Updating database to version 61 (player binary save/load)") + db.query("ALTER TABLE `players` ADD `spells` blob DEFAULT NULL") + db.query("ALTER TABLE `players` ADD `storages` mediumblob DEFAULT NULL") + db.query("ALTER TABLE `players` ADD `items` longblob DEFAULT NULL") + db.query("ALTER TABLE `players` ADD `depotitems` longblob DEFAULT NULL") + db.query("ALTER TABLE `players` ADD `inboxitems` longblob DEFAULT NULL") + db.query("ALTER TABLE `players` ADD `stashitems` longblob DEFAULT NULL") +end