diff options
author | xinef1 <w.szyszko2@gmail.com> | 2017-04-12 03:22:50 +0200 |
---|---|---|
committer | funjoker <funjoker109@gmail.com> | 2020-04-27 12:25:52 +0200 |
commit | 090fd8304a7a6e2f7c233ac39c94ccc67cc816f8 (patch) | |
tree | 2c32dcab82f2befd0a9638543726c21ec9de228d /src/server/game | |
parent | 4e03d2717a7f971263f17639eb496c0b3b248018 (diff) |
Core/Loot: implement Loot Item Storage (#19018)
* Created Item Loot Storage, no more synchronous DB selects
* Fixed buyback case, where stored loot was not removed from db
* Added Primary key, and changed field types to be unsigned for table item_loot_money
(cherry picked from commit 9dc3de10f0044c35a95e9b72e7b874f4b5b8e867)
Diffstat (limited to 'src/server/game')
-rw-r--r-- | src/server/game/Entities/Item/Item.cpp | 190 | ||||
-rw-r--r-- | src/server/game/Entities/Item/Item.h | 8 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 44 | ||||
-rw-r--r-- | src/server/game/Handlers/LootHandler.cpp | 3 | ||||
-rw-r--r-- | src/server/game/Loot/Loot.cpp | 28 | ||||
-rw-r--r-- | src/server/game/Loot/Loot.h | 8 | ||||
-rw-r--r-- | src/server/game/Loot/LootItemStorage.cpp | 365 | ||||
-rw-r--r-- | src/server/game/Loot/LootItemStorage.h | 96 | ||||
-rw-r--r-- | src/server/game/World/World.cpp | 4 |
9 files changed, 503 insertions, 243 deletions
diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp index 99235c38da3..56eabab98d1 100644 --- a/src/server/game/Entities/Item/Item.cpp +++ b/src/server/game/Entities/Item/Item.cpp @@ -30,6 +30,7 @@ #include "ItemEnchantmentMgr.h" #include "ItemPackets.h" #include "Log.h" +#include "LootItemStorage.h" #include "LootMgr.h" #include "Map.h" #include "ObjectAccessor.h" @@ -763,7 +764,7 @@ void Item::SaveToDB(CharacterDatabaseTransaction& trans) // Delete the items if this is a container if (!loot.isLooted()) - ItemContainerDeleteLootMoneyAndLootItemsFromDB(); + sLootItemStorage->RemoveStoredLootForContainer(GetGUID().GetCounter()); delete this; return; @@ -1058,7 +1059,7 @@ void Item::DeleteFromDB(CharacterDatabaseTransaction& trans) // Delete the items if this is a container if (!loot.isLooted()) - ItemContainerDeleteLootMoneyAndLootItemsFromDB(); + sLootItemStorage->RemoveStoredLootForContainer(GetGUID().GetCounter()); } /*static*/ @@ -2141,191 +2142,6 @@ uint32 Item::GetSellPrice(ItemTemplate const* proto, uint32 quality, uint32 item return 0; } -void Item::ItemContainerSaveLootToDB() -{ - // Saves the money and item loot associated with an openable item to the DB - if (loot.isLooted()) // no money and no loot - return; - - CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - - loot.containerID = GetGUID(); // Save this for when a LootItem is removed - - // Save money - if (loot.gold > 0) - { - CharacterDatabasePreparedStatement* stmt_money = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY); - stmt_money->setUInt64(0, loot.containerID.GetCounter()); - trans->Append(stmt_money); - - stmt_money = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEMCONTAINER_MONEY); - stmt_money->setUInt64(0, loot.containerID.GetCounter()); - stmt_money->setUInt32(1, loot.gold); - trans->Append(stmt_money); - } - - // Save items - if (!loot.isLooted()) - { - CharacterDatabasePreparedStatement* stmt_items = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEMS); - stmt_items->setUInt64(0, loot.containerID.GetCounter()); - trans->Append(stmt_items); - - // Now insert the items - for (LootItemList::const_iterator _li = loot.items.begin(); _li != loot.items.end(); ++_li) - { - // When an item is looted, it doesn't get removed from the items collection - // but we don't want to resave it. - if (!_li->canSave) - continue; - // Conditions are not checked when loot is generated, it is checked when loot is sent to a player. - // For items that are lootable, loot is saved to the DB immediately, that means that loot can be - // saved to the DB that the player never should have gotten. This check prevents that, so that only - // items that the player should get in loot are in the DB. - // IE: Horde items are not saved to the DB for Ally players. - Player* const guid = GetOwner(); - if (!_li->AllowedForPlayer(guid)) - continue; - - stmt_items = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEMCONTAINER_ITEMS); - - // container_id, item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, context, bonus_list_ids - stmt_items->setUInt64(0, loot.containerID.GetCounter()); - stmt_items->setUInt32(1, _li->itemid); - stmt_items->setUInt32(2, _li->count); - stmt_items->setBool(3, _li->follow_loot_rules); - stmt_items->setBool(4, _li->freeforall); - stmt_items->setBool(5, _li->is_blocked); - stmt_items->setBool(6, _li->is_counted); - stmt_items->setBool(7, _li->is_underthreshold); - stmt_items->setBool(8, _li->needs_quest); - stmt_items->setUInt32(9, _li->randomBonusListId); - stmt_items->setUInt8(10, AsUnderlyingType(_li->context)); - std::ostringstream bonusListIDs; - for (int32 bonusListID : _li->BonusListIDs) - bonusListIDs << bonusListID << ' '; - stmt_items->setString(11, bonusListIDs.str()); - trans->Append(stmt_items); - } - } - - CharacterDatabase.CommitTransaction(trans); -} - -bool Item::ItemContainerLoadLootFromDB() -{ - // Loads the money and item loot associated with an openable item from the DB - // Default. If there are no records for this item then it will be rolled for in Player::SendLoot() - m_lootGenerated = false; - - // Save this for later use - loot.containerID = GetGUID(); - - // First, see if there was any money loot. This gets added directly to the container. - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEMCONTAINER_MONEY); - stmt->setUInt64(0, loot.containerID.GetCounter()); - PreparedQueryResult money_result = CharacterDatabase.Query(stmt); - - if (money_result) - { - Field* fields = money_result->Fetch(); - loot.gold = fields[0].GetUInt32(); - } - - // Next, load any items that were saved - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEMCONTAINER_ITEMS); - stmt->setUInt64(0, loot.containerID.GetCounter()); - PreparedQueryResult item_result = CharacterDatabase.Query(stmt); - - if (item_result) - { - // Get a LootTemplate for the container item. This is where - // the saved loot was originally rolled from, we will copy conditions from it - LootTemplate const* lt = LootTemplates_Item.GetLootFor(GetEntry()); - if (lt) - { - do - { - // Create an empty LootItem - LootItem loot_item = LootItem(); - - // Fill in the rest of the LootItem from the DB - Field* fields = item_result->Fetch(); - - // item_id, itm_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, context, bonus_list_ids - loot_item.itemid = fields[0].GetUInt32(); - loot_item.count = fields[1].GetUInt32(); - loot_item.follow_loot_rules = fields[2].GetBool(); - loot_item.freeforall = fields[3].GetBool(); - loot_item.is_blocked = fields[4].GetBool(); - loot_item.is_counted = fields[5].GetBool(); - loot_item.canSave = true; - loot_item.is_underthreshold = fields[6].GetBool(); - loot_item.needs_quest = fields[7].GetBool(); - loot_item.randomBonusListId = fields[8].GetUInt32(); - loot_item.context = ItemContext(fields[9].GetUInt8()); - Tokenizer bonusLists(fields[10].GetString(), ' '); - std::transform(bonusLists.begin(), bonusLists.end(), std::back_inserter(loot_item.BonusListIDs), [](char const* token) - { - return int32(strtol(token, NULL, 10)); - }); - - // Copy the extra loot conditions from the item in the loot template - lt->CopyConditions(&loot_item); - - // If container item is in a bag, add that player as an allowed looter - if (GetBagSlot()) - loot_item.AddAllowedLooter(GetOwner()); - - // Finally add the LootItem to the container - loot.items.push_back(loot_item); - - // Increment unlooted count - loot.unlootedCount++; - - } - while (item_result->NextRow()); - } - } - - // Mark the item if it has loot so it won't be generated again on open - m_lootGenerated = !loot.isLooted(); - - return m_lootGenerated; -} - -void Item::ItemContainerDeleteLootItemsFromDB() -{ - // Deletes items associated with an openable item from the DB - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEMS); - stmt->setUInt64(0, GetGUID().GetCounter()); - CharacterDatabase.Execute(stmt); -} - -void Item::ItemContainerDeleteLootItemFromDB(uint32 itemID) -{ - // Deletes a single item associated with an openable item from the DB - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEM); - stmt->setUInt64(0, GetGUID().GetCounter()); - stmt->setUInt32(1, itemID); - CharacterDatabase.Execute(stmt); -} - -void Item::ItemContainerDeleteLootMoneyFromDB() -{ - // Deletes the money loot associated with an openable item from the DB - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY); - stmt->setUInt64(0, GetGUID().GetCounter()); - CharacterDatabase.Execute(stmt); -} - -void Item::ItemContainerDeleteLootMoneyAndLootItemsFromDB() -{ - // Deletes money and items associated with an openable item from the DB - ItemContainerDeleteLootMoneyFromDB(); - ItemContainerDeleteLootItemsFromDB(); -} - uint32 Item::GetItemLevel(Player const* owner) const { ItemTemplate const* itemTemplate = GetTemplate(); diff --git a/src/server/game/Entities/Item/Item.h b/src/server/game/Entities/Item/Item.h index c4c5ee62d5a..007b9ef46c5 100644 --- a/src/server/game/Entities/Item/Item.h +++ b/src/server/game/Entities/Item/Item.h @@ -231,14 +231,6 @@ class TC_GAME_API Item : public Object virtual void DeleteFromDB(CharacterDatabaseTransaction& trans); static void DeleteFromInventoryDB(CharacterDatabaseTransaction& trans, ObjectGuid::LowType itemGuid); - // Lootable items and their contents - void ItemContainerSaveLootToDB(); - bool ItemContainerLoadLootFromDB(); - void ItemContainerDeleteLootItemsFromDB(); - void ItemContainerDeleteLootItemFromDB(uint32 itemID); - void ItemContainerDeleteLootMoneyFromDB(); - void ItemContainerDeleteLootMoneyAndLootItemsFromDB(); - void DeleteFromInventoryDB(CharacterDatabaseTransaction& trans); void SaveRefundDataToDB(); void DeleteRefundDataFromDB(CharacterDatabaseTransaction* trans); diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index be4dfa08e24..abad9678510 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -75,6 +75,7 @@ #include "LFGMgr.h" #include "Language.h" #include "Log.h" +#include "LootItemStorage.h" #include "LootMgr.h" #include "LootPackets.h" #include "Mail.h" @@ -8838,9 +8839,12 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type, bool aeLooting/* = fa loot = &item->loot; + // Store container id + loot->containerID = item->GetGUID(); + // If item doesn't already have loot, attempt to load it. If that - // fails then this is first time opening, generate loot - if (!item->m_lootGenerated && !item->ItemContainerLoadLootFromDB()) + // fails then this is first time opening, generate loot + if (!item->m_lootGenerated && !sLootItemStorage->LoadStoredLoot(item, this)) { item->m_lootGenerated = true; loot->clear(); @@ -8863,7 +8867,7 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type, bool aeLooting/* = fa // Force save the loot and money items that were just rolled // Also saves the container item ID in Loot struct (not to DB) if (loot->gold > 0 || loot->unlootedCount > 0) - item->ItemContainerSaveLootToDB(); + sLootItemStorage->AddNewStoredLoot(loot, this); break; } @@ -12803,6 +12807,7 @@ void Player::DestroyItem(uint8 bag, uint8 slot, bool update) ItemRemovedQuestCheck(pItem->GetEntry(), pItem->GetCount()); sScriptMgr->OnItemRemove(this, pItem); + ItemTemplate const* pProto = pItem->GetTemplate(); if (bag == INVENTORY_SLOT_BAG_0) { SetInvSlot(slot, ObjectGuid::Empty); @@ -12810,8 +12815,6 @@ void Player::DestroyItem(uint8 bag, uint8 slot, bool update) // equipment and equipped bags can have applied bonuses if (slot < INVENTORY_SLOT_BAG_END) { - ItemTemplate const* pProto = pItem->GetTemplate(); - // item set bonuses applied only at equip and removed at unequip, and still active for broken items if (pProto && pProto->GetItemSet()) RemoveItemsSetItem(this, pProto); @@ -12847,9 +12850,8 @@ void Player::DestroyItem(uint8 bag, uint8 slot, bool update) // Delete rolled money / loot from db. // MUST be done before RemoveFromWorld() or GetTemplate() fails - if (ItemTemplate const* pTmp = pItem->GetTemplate()) - if (pTmp->GetFlags() & ITEM_FLAG_HAS_LOOT) - pItem->ItemContainerDeleteLootMoneyAndLootItemsFromDB(); + if (pProto->GetFlags() & ITEM_FLAG_HAS_LOOT) + sLootItemStorage->RemoveStoredLootForContainer(pItem->GetGUID().GetCounter()); if (IsInWorld() && update) { @@ -13766,7 +13768,7 @@ void Player::SwapItem(uint16 src, uint16 dst) { if (Item* bagItem = bag->GetItemByPos(i)) { - if (bagItem->m_lootGenerated) + if (bagItem->GetGUID() == GetLootGUID()) { m_session->DoLootRelease(GetLootGUID()); released = true; // so we don't need to look at dstBag @@ -13783,7 +13785,7 @@ void Player::SwapItem(uint16 src, uint16 dst) { if (Item* bagItem = bag->GetItemByPos(i)) { - if (bagItem->m_lootGenerated) + if (bagItem->GetGUID() == GetLootGUID()) { m_session->DoLootRelease(GetLootGUID()); break; @@ -13872,7 +13874,12 @@ void Player::RemoveItemFromBuyBackSlot(uint32 slot, bool del) { pItem->RemoveFromWorld(); if (del) + { pItem->SetState(ITEM_REMOVED, this); + if (ItemTemplate const* itemTemplate = pItem->GetTemplate()) + if (itemTemplate->GetFlags() & ITEM_FLAG_HAS_LOOT) + sLootItemStorage->RemoveStoredLootForContainer(pItem->GetGUID().GetCounter()); + } } m_items[slot] = nullptr; @@ -20846,12 +20853,25 @@ void Player::_SaveInventory(CharacterDatabaseTransaction& trans) for (uint8 i = BUYBACK_SLOT_START; i < BUYBACK_SLOT_END; ++i) { Item* item = m_items[i]; - if (!item || item->GetState() == ITEM_NEW) + if (!item) continue; + if (item->GetState() == ITEM_NEW) + { + if (ItemTemplate const* itemTemplate = item->GetTemplate()) + if (itemTemplate->GetFlags() & ITEM_FLAG_HAS_LOOT) + sLootItemStorage->RemoveStoredLootForContainer(item->GetGUID().GetCounter()); + + continue; + } + item->DeleteFromInventoryDB(trans); item->DeleteFromDB(trans); m_items[i]->FSetState(ITEM_NEW); + + if (ItemTemplate const* itemTemplate = item->GetTemplate()) + if (itemTemplate->GetFlags() & ITEM_FLAG_HAS_LOOT) + sLootItemStorage->RemoveStoredLootForContainer(item->GetGUID().GetCounter()); } // Updated played time for refundable items. We don't do this in Player::Update because there's simply no need for it, @@ -26197,7 +26217,7 @@ void Player::StoreLootItem(uint8 lootSlot, Loot* loot, AELootResult* aeResult/* // LootItem is being removed (looted) from the container, delete it from the DB. if (!loot->containerID.IsEmpty()) - loot->DeleteLootItemFromContainerItemDB(item->itemid); + sLootItemStorage->RemoveStoredLootItemForContainer(loot->containerID.GetCounter(), item->itemid, item->count); } else SendEquipError(msg, nullptr, nullptr, item->itemid); diff --git a/src/server/game/Handlers/LootHandler.cpp b/src/server/game/Handlers/LootHandler.cpp index 7335b5f3676..68911435766 100644 --- a/src/server/game/Handlers/LootHandler.cpp +++ b/src/server/game/Handlers/LootHandler.cpp @@ -28,6 +28,7 @@ #include "GuildMgr.h" #include "Item.h" #include "Log.h" +#include "LootItemStorage.h" #include "LootMgr.h" #include "LootPackets.h" #include "Object.h" @@ -255,7 +256,7 @@ void WorldSession::HandleLootMoneyOpcode(WorldPackets::Loot::LootMoney& /*packet // Delete the money loot record from the DB if (!loot->containerID.IsEmpty()) - loot->DeleteLootMoneyFromContainerItemDB(); + sLootItemStorage->RemoveStoredMoneyForContainer(loot->containerID.GetCounter()); // Delete container if empty if (loot->isLooted() && guid.IsItem()) diff --git a/src/server/game/Loot/Loot.cpp b/src/server/game/Loot/Loot.cpp index 88365d6641f..9e4dc90cab9 100644 --- a/src/server/game/Loot/Loot.cpp +++ b/src/server/game/Loot/Loot.cpp @@ -54,7 +54,6 @@ LootItem::LootItem(LootStoreItem const& li) is_underthreshold = 0; is_counted = 0; rollWinnerGUID = ObjectGuid::Empty; - canSave = true; } // Basic checks for player/item compatibility - if false no chance to see the item in the loot @@ -104,33 +103,6 @@ Loot::~Loot() clear(); } -void Loot::DeleteLootItemFromContainerItemDB(uint32 itemID) -{ - // Deletes a single item associated with an openable item from the DB - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEM); - stmt->setUInt64(0, containerID.GetCounter()); - stmt->setUInt32(1, itemID); - CharacterDatabase.Execute(stmt); - - // Mark the item looted to prevent resaving - for (LootItemList::iterator _itr = items.begin(); _itr != items.end(); ++_itr) - { - if (_itr->itemid != itemID) - continue; - - _itr->canSave = false; - break; - } -} - -void Loot::DeleteLootMoneyFromContainerItemDB() -{ - // Deletes money loot associated with an openable item from the DB - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY); - stmt->setUInt64(0, containerID.GetCounter()); - CharacterDatabase.Execute(stmt); -} - void Loot::clear() { for (NotNormalLootItemMap::const_iterator itr = PlayerQuestItems.begin(); itr != PlayerQuestItems.end(); ++itr) diff --git a/src/server/game/Loot/Loot.h b/src/server/game/Loot/Loot.h index 27f9c89f040..1aaed3f3cf7 100644 --- a/src/server/game/Loot/Loot.h +++ b/src/server/game/Loot/Loot.h @@ -148,7 +148,6 @@ struct TC_GAME_API LootItem bool is_counted : 1; bool needs_quest : 1; // quest drop bool follow_loot_rules : 1; - bool canSave; // Constructor, copies most fields from LootStoreItem, generates random count and random suffixes/properties // Should be called for non-reference LootStoreItem entries only (reference = 0) @@ -156,8 +155,7 @@ struct TC_GAME_API LootItem // Empty constructor for creating an empty LootItem to be filled in with DB data LootItem() : itemid(0), randomBonusListId(0), context(ItemContext::NONE), count(0), is_looted(false), is_blocked(false), - freeforall(false), is_underthreshold(false), is_counted(false), needs_quest(false), follow_loot_rules(false), - canSave(true){ }; + freeforall(false), is_underthreshold(false), is_counted(false), needs_quest(false), follow_loot_rules(false) { }; // Basic checks for player/item compatibility - if false no chance to see the item in the loot bool AllowedForPlayer(Player const* player) const; @@ -230,10 +228,6 @@ struct TC_GAME_API Loot ObjectGuid const& GetGUID() const { return _GUID; } void SetGUID(ObjectGuid const& guid) { _GUID = guid; } - // For deleting items at loot removal since there is no backward interface to the Item() - void DeleteLootItemFromContainerItemDB(uint32 itemID); - void DeleteLootMoneyFromContainerItemDB(); - // if loot becomes invalid this reference is used to inform the listener void addLootValidatorRef(LootValidatorRef* pLootValidatorRef) { diff --git a/src/server/game/Loot/LootItemStorage.cpp b/src/server/game/Loot/LootItemStorage.cpp new file mode 100644 index 00000000000..4e3464eb31c --- /dev/null +++ b/src/server/game/Loot/LootItemStorage.cpp @@ -0,0 +1,365 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "DatabaseEnv.h" +#include "Item.h" +#include "ItemTemplate.h" +#include "Log.h" +#include "LootItemStorage.h" +#include "LootMgr.h" +#include "ObjectMgr.h" +#include "Player.h" + +#include <boost/thread/shared_mutex.hpp> +#include <boost/thread/locks.hpp> + +#include <unordered_map> + +namespace +{ + std::unordered_map<uint64, StoredLootContainer> _lootItemStore; +} + +StoredLootItem::StoredLootItem(LootItem const& lootItem) : ItemId(lootItem.itemid), Count(lootItem.count), FollowRules(lootItem.follow_loot_rules), +FFA(lootItem.freeforall), Blocked(lootItem.is_blocked), Counted(lootItem.is_counted), UnderThreshold(lootItem.is_underthreshold), +NeedsQuest(lootItem.needs_quest), RandomBonusListId(lootItem.randomBonusListId), Context(lootItem.context), BonusListIDs(lootItem.BonusListIDs) +{ +} + +LootItemStorage* LootItemStorage::instance() +{ + static LootItemStorage instance; + return &instance; +} + +boost::shared_mutex* LootItemStorage::GetLock() +{ + static boost::shared_mutex _lock; + return &_lock; +} + +void LootItemStorage::LoadStorageFromDB() +{ + uint32 oldMSTime = getMSTime(); + _lootItemStore.clear(); + uint32 count = 0; + + CharacterDatabaseTransaction trans = CharacterDatabaseTransaction(nullptr); + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEMCONTAINER_ITEMS); + PreparedQueryResult result = CharacterDatabase.Query(stmt); + if (result) + { + do + { + Field* fields = result->Fetch(); + + uint64 key = fields[0].GetUInt64(); + auto itr = _lootItemStore.find(key); + if (itr == _lootItemStore.end()) + { + bool added; + std::tie(itr, added) = _lootItemStore.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(key)); + + ASSERT(added); + } + + StoredLootContainer& storedContainer = itr->second; + + LootItem lootItem; + lootItem.itemid = fields[1].GetUInt32(); + lootItem.count = fields[2].GetUInt32(); + lootItem.follow_loot_rules = fields[3].GetBool(); + lootItem.freeforall = fields[4].GetBool(); + lootItem.is_blocked = fields[5].GetBool(); + lootItem.is_counted = fields[6].GetBool(); + lootItem.is_underthreshold = fields[7].GetBool(); + lootItem.needs_quest = fields[8].GetBool(); + lootItem.randomBonusListId = fields[9].GetUInt32(); + lootItem.context = ItemContext(fields[10].GetUInt8()); + Tokenizer bonusLists(fields[11].GetString(), ' '); + std::transform(bonusLists.begin(), bonusLists.end(), std::back_inserter(lootItem.BonusListIDs), [](char const* token) + { + return int32(strtol(token, NULL, 10)); + }); + + storedContainer.AddLootItem(lootItem, trans); + + ++count; + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded %u stored item loots in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); + } + else + TC_LOG_INFO("server.loading", ">> Loaded 0 stored item loots"); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEMCONTAINER_MONEY); + result = CharacterDatabase.Query(stmt); + if (result) + { + count = 0; + do + { + Field* fields = result->Fetch(); + + uint64 key = fields[0].GetUInt64(); + auto itr = _lootItemStore.find(key); + if (itr == _lootItemStore.end()) + { + bool added; + std::tie(itr, added) = _lootItemStore.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(key)); + + ASSERT(added); + } + + StoredLootContainer& storedContainer = itr->second; + storedContainer.AddMoney(fields[1].GetUInt32(), trans); + + ++count; + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded %u stored item money in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); + } + else + TC_LOG_INFO("server.loading", ">> Loaded 0 stored item money"); +} + +bool LootItemStorage::LoadStoredLoot(Item* item, Player* player) +{ + Loot* loot = &item->loot; + StoredLootContainer const* container = nullptr; + + // read + { + boost::shared_lock<boost::shared_mutex> lock(*GetLock()); + + auto itr = _lootItemStore.find(loot->containerID.GetCounter()); + if (itr == _lootItemStore.end()) + return false; + + container = &itr->second; + } + + // container is never null at this point + loot->gold = container->GetMoney(); + + if (LootTemplate const* lt = LootTemplates_Item.GetLootFor(item->GetEntry())) + { + for (auto const& storedItemPair : container->GetLootItems()) + { + LootItem li; + li.itemid = storedItemPair.first; + li.count = storedItemPair.second.Count; + li.follow_loot_rules = storedItemPair.second.FollowRules; + li.freeforall = storedItemPair.second.FFA; + li.is_blocked = storedItemPair.second.Blocked; + li.is_counted = storedItemPair.second.Counted; + li.is_underthreshold = storedItemPair.second.UnderThreshold; + li.needs_quest = storedItemPair.second.NeedsQuest; + li.randomBonusListId = storedItemPair.second.RandomBonusListId; + li.context = storedItemPair.second.Context; + li.BonusListIDs = storedItemPair.second.BonusListIDs; + + // Copy the extra loot conditions from the item in the loot template + lt->CopyConditions(&li); + + // If container item is in a bag, add that player as an allowed looter + if (item->GetBagSlot()) + li.AddAllowedLooter(player); + + // Finally add the LootItem to the container + loot->items.push_back(li); + + // Increment unlooted count + ++loot->unlootedCount; + } + } + + // Mark the item if it has loot so it won't be generated again on open + item->m_lootGenerated = true; + return true; +} + +void LootItemStorage::RemoveStoredMoneyForContainer(uint64 containerId) +{ + // write + boost::unique_lock<boost::shared_mutex> lock(*GetLock()); + + auto itr = _lootItemStore.find(containerId); + if (itr == _lootItemStore.end()) + return; + + itr->second.RemoveMoney(); +} + +void LootItemStorage::RemoveStoredLootForContainer(uint64 containerId) +{ + // write + { + boost::unique_lock<boost::shared_mutex> lock(*GetLock()); + _lootItemStore.erase(containerId); + } + + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEMS); + stmt->setUInt64(0, containerId); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY); + stmt->setUInt64(0, containerId); + trans->Append(stmt); + + CharacterDatabase.CommitTransaction(trans); +} + +void LootItemStorage::RemoveStoredLootItemForContainer(uint64 containerId, uint32 itemId, uint32 count) +{ + // write + boost::unique_lock<boost::shared_mutex> lock(*GetLock()); + + auto itr = _lootItemStore.find(containerId); + if (itr == _lootItemStore.end()) + return; + + itr->second.RemoveItem(itemId, count); +} + +void LootItemStorage::AddNewStoredLoot(Loot* loot, Player* player) +{ + // Saves the money and item loot associated with an openable item to the DB + if (loot->isLooted()) // no money and no loot + return; + + // read + { + boost::shared_lock<boost::shared_mutex> lock(*GetLock()); + + auto itr = _lootItemStore.find(loot->containerID.GetCounter()); + if (itr != _lootItemStore.end()) + { + TC_LOG_ERROR("misc", "Trying to store item loot by player: %s for container id: %lu that is already in storage!", player->GetGUID().ToString().c_str(), loot->containerID.GetCounter()); + return; + } + } + + StoredLootContainer container(loot->containerID.GetCounter()); + + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + if (loot->gold) + container.AddMoney(loot->gold, trans); + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEMS); + stmt->setUInt64(0, loot->containerID.GetCounter()); + trans->Append(stmt); + + for (LootItem const& li : loot->items) + { + // Conditions are not checked when loot is generated, it is checked when loot is sent to a player. + // For items that are lootable, loot is saved to the DB immediately, that means that loot can be + // saved to the DB that the player never should have gotten. This check prevents that, so that only + // items that the player should get in loot are in the DB. + // IE: Horde items are not saved to the DB for Ally players. + if (!li.AllowedForPlayer(player)) + continue; + + // Don't save currency tokens + ItemTemplate const* itemTemplate = sObjectMgr->GetItemTemplate(li.itemid); + if (!itemTemplate || itemTemplate->IsCurrencyToken()) + continue; + + container.AddLootItem(li, trans); + } + + CharacterDatabase.CommitTransaction(trans); + + // write + { + boost::unique_lock<boost::shared_mutex> lock(*GetLock()); + _lootItemStore.emplace(loot->containerID.GetCounter(), std::move(container)); + } +} + +void StoredLootContainer::AddLootItem(LootItem const& lootItem, CharacterDatabaseTransaction& trans) +{ + _lootItems.emplace(std::piecewise_construct, std::forward_as_tuple(lootItem.itemid), std::forward_as_tuple(lootItem)); + if (!trans) + return; + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEMCONTAINER_ITEMS); + + // container_id, item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix + stmt->setUInt64(0, _containerId); + stmt->setUInt32(1, lootItem.itemid); + stmt->setUInt32(2, lootItem.count); + stmt->setBool(3, lootItem.follow_loot_rules); + stmt->setBool(4, lootItem.freeforall); + stmt->setBool(5, lootItem.is_blocked); + stmt->setBool(6, lootItem.is_counted); + stmt->setBool(7, lootItem.is_underthreshold); + stmt->setBool(8, lootItem.needs_quest); + stmt->setInt32(9, lootItem.randomBonusListId); + stmt->setUInt8(10, AsUnderlyingType(lootItem.context)); + std::ostringstream bonusListIDs; + for (int32 bonusListID : lootItem.BonusListIDs) + bonusListIDs << bonusListID << ' '; + stmt->setString(11, bonusListIDs.str()); + trans->Append(stmt); +} + +void StoredLootContainer::AddMoney(uint32 money, CharacterDatabaseTransaction& trans) +{ + _money = money; + if (!trans) + return; + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY); + stmt->setUInt64(0, _containerId); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEMCONTAINER_MONEY); + stmt->setUInt64(0, _containerId); + stmt->setUInt32(1, _money); + trans->Append(stmt); +} + +void StoredLootContainer::RemoveMoney() +{ + _money = 0; + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY); + stmt->setUInt64(0, _containerId); + CharacterDatabase.Execute(stmt); +} + +void StoredLootContainer::RemoveItem(uint32 itemId, uint32 count) +{ + auto bounds = _lootItems.equal_range(itemId); + for (auto itr = bounds.first; itr != bounds.second; ++itr) + { + if (itr->second.Count == count) + { + _lootItems.erase(itr); + break; + } + } + + // Deletes a single item associated with an openable item from the DB + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEM); + stmt->setUInt64(0, _containerId); + stmt->setUInt32(1, itemId); + stmt->setUInt32(2, count); + CharacterDatabase.Execute(stmt); +} diff --git a/src/server/game/Loot/LootItemStorage.h b/src/server/game/Loot/LootItemStorage.h new file mode 100644 index 00000000000..7d221eaefc0 --- /dev/null +++ b/src/server/game/Loot/LootItemStorage.h @@ -0,0 +1,96 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __LOOTITEMSTORAGE_H +#define __LOOTITEMSTORAGE_H + +#include "Define.h" +#include "DBCEnums.h" +#include "ItemEnchantmentMgr.h" + +#include <vector> + +class Item; +class Player; +struct Loot; +struct LootItem; +namespace boost +{ + class shared_mutex; +} + +struct StoredLootItem +{ + explicit StoredLootItem(LootItem const& lootItem); + + uint32 ItemId; + uint32 Count; + bool FollowRules; + bool FFA; + bool Blocked; + bool Counted; + bool UnderThreshold; + bool NeedsQuest; + ItemRandomBonusListId RandomBonusListId; + ItemContext Context; + std::vector<int32> BonusListIDs; +}; + +class StoredLootContainer +{ + public: + typedef std::unordered_multimap<uint32 /*itemId*/, StoredLootItem> StoredLootItemContainer; + + explicit StoredLootContainer(uint64 containerId) : _containerId(containerId), _money(0) { } + + void AddLootItem(LootItem const& lootItem, CharacterDatabaseTransaction& trans); + void AddMoney(uint32 money, CharacterDatabaseTransaction& trans); + + void RemoveMoney(); + void RemoveItem(uint32 itemId, uint32 count); + + uint32 GetContainer() const { return _containerId; } + uint32 GetMoney() const { return _money; } + StoredLootItemContainer const& GetLootItems() const { return _lootItems; } + + private: + StoredLootItemContainer _lootItems; + uint64 const _containerId; + uint32 _money; +}; + +class LootItemStorage +{ + public: + static LootItemStorage* instance(); + static boost::shared_mutex* GetLock(); + + void LoadStorageFromDB(); + bool LoadStoredLoot(Item* item, Player* player); + void RemoveStoredMoneyForContainer(uint64 containerId); + void RemoveStoredLootForContainer(uint64 containerId); + void RemoveStoredLootItemForContainer(uint64 containerId, uint32 itemId, uint32 count); + void AddNewStoredLoot(Loot* loot, Player* player); + + private: + LootItemStorage() { } + ~LootItemStorage() { } +}; + +#define sLootItemStorage LootItemStorage::instance() + +#endif diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index e1e71ce01e2..a94e06034ba 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -60,6 +60,7 @@ #include "IPLocation.h" #include "Language.h" #include "LFGMgr.h" +#include "LootItemStorage.h" #include "LootMgr.h" #include "M2Stores.h" #include "MapManager.h" @@ -2073,6 +2074,9 @@ void World::SetInitialWorldSettings() TC_LOG_INFO("server.loading", "Loading Calendar data..."); sCalendarMgr->LoadFromDB(); + TC_LOG_INFO("server.loading", "Loading Item loot..."); + sLootItemStorage->LoadStorageFromDB(); + TC_LOG_INFO("server.loading", "Initialize query data..."); sObjectMgr->InitializeQueriesData(QUERY_DATA_ALL); |