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/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 diff --git a/src/io/functions/iologindata_load_player.cpp b/src/io/functions/iologindata_load_player.cpp index 15a7a548b..eb06a3021 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,36 @@ 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 +576,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(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 +611,80 @@ 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); + 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); + } + } - 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) { + 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; } - - 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); - // 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); - } + 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 +692,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,18 +735,47 @@ 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 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 (...) { } + } + } + } + + 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); @@ -684,24 +784,62 @@ void IOLoginDataLoad::loadPlayerDepotItems(const std::shared_ptr &player item->startDecaying(); } } else { - auto depotIt = depotItems.find(pid); - if (depotIt == depotItems.end()) { + auto depotIt = depotItemsKV.find(pid); + if (depotIt == depotItemsKV.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); + itemsToStartDecayingKV.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 : 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; + } + + 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(); + } } } @@ -711,11 +849,40 @@ void IOLoginDataLoad::loadPlayerInboxItems(const std::shared_ptr &player 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); + 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) { @@ -723,35 +890,75 @@ void IOLoginDataLoad::loadPlayerInboxItems(const std::shared_ptr &player 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; } - 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()) { + auto inboxIt = inboxItemsKV.find(pid); + if (inboxIt == inboxItemsKV.end()) { continue; } - const std::shared_ptr &container = inboxIt->second.first->getContainer(); if (container) { container->internalAddThing(item); - itemsToStartDecaying.emplace_back(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 : itemsToStartDecaying) { - 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()); + 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; + } + + 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(); + } } } @@ -762,12 +969,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 e6c2918a3..c29ebbb5a 100644 --- a/src/io/functions/iologindata_save_player.cpp +++ b/src/io/functions/iologindata_save_player.cpp @@ -28,107 +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) { +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; } - 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; } } } - - // 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; + 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]; + 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) { + 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 (!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; @@ -136,29 +143,39 @@ bool IOLoginDataSave::saveItems(const std::shared_ptr &player, const Ite } } } - - // Serialize item attributes propWriteStream.clear(); - item->serializeAttr(propWriteStream); - + subItem->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; + 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()) { + 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; } @@ -366,28 +383,11 @@ bool IOLoginDataSave::savePlayerStash(const std::shared_ptr &player) { g_logger().warn("[IOLoginData::savePlayer] - Player nullptr: {}", __FUNCTION__); return false; } - - Database &db = Database::getInstance(); + ItemBlockList itemList; 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; + PropWriteStream propWriteStream; + propWriteStream.clear(); + return saveItems(player, itemList, propWriteStream, query, nullptr, "stashitems"); } bool IOLoginDataSave::savePlayerSpells(const std::shared_ptr &player) { @@ -398,25 +398,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) { @@ -498,18 +495,6 @@ bool IOLoginDataSave::savePlayerItem(const std::shared_ptr &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]; @@ -517,12 +502,10 @@ bool IOLoginDataSave::savePlayerItem(const std::shared_ptr &player) { 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; + 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 +514,19 @@ 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; - } + if (player->lastDepotId == -1) { return true; } - 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) { @@ -587,7 +556,8 @@ 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)) { + std::ostringstream queryTmp; + if (!saveItems(player, rewardListItems, propWriteStream, queryTmp, &rewardQuery, std::string())) { return false; } } @@ -600,26 +570,14 @@ 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; + ItemBlockList itemList; + for (auto it = player->getInbox()->getReversedItems(), end = player->getInbox()->getReversedEnd(); it != end; ++it) { + itemList.emplace_back(0, *it); } - return true; + 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 +772,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..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, 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); };