aboutsummaryrefslogtreecommitdiff
path: root/src/server/game
diff options
context:
space:
mode:
authorxinef1 <w.szyszko2@gmail.com>2017-04-12 03:22:50 +0200
committerfunjoker <funjoker109@gmail.com>2020-04-27 12:25:52 +0200
commit090fd8304a7a6e2f7c233ac39c94ccc67cc816f8 (patch)
tree2c32dcab82f2befd0a9638543726c21ec9de228d /src/server/game
parent4e03d2717a7f971263f17639eb496c0b3b248018 (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.cpp190
-rw-r--r--src/server/game/Entities/Item/Item.h8
-rw-r--r--src/server/game/Entities/Player/Player.cpp44
-rw-r--r--src/server/game/Handlers/LootHandler.cpp3
-rw-r--r--src/server/game/Loot/Loot.cpp28
-rw-r--r--src/server/game/Loot/Loot.h8
-rw-r--r--src/server/game/Loot/LootItemStorage.cpp365
-rw-r--r--src/server/game/Loot/LootItemStorage.h96
-rw-r--r--src/server/game/World/World.cpp4
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);