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 9dc3de10f0)
This commit is contained in:
xinef1
2017-04-12 03:22:50 +02:00
committed by funjoker
parent 4e03d2717a
commit 090fd8304a
12 changed files with 512 additions and 248 deletions

View File

@@ -3181,7 +3181,7 @@ DROP TABLE IF EXISTS `item_loot_money`;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `item_loot_money` (
`container_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'guid of container (item_instance.guid)',
`money` int(10) NOT NULL DEFAULT '0' COMMENT 'money loot (in copper)',
`money` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'money loot (in copper)',
PRIMARY KEY (`container_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
@@ -3772,7 +3772,9 @@ INSERT INTO `updates` VALUES
('2020_02_17_00_characters.sql','E1519A81D35F19B48B3C75A83A270CB4BA0B84F2','RELEASED','2020-02-17 21:55:17',0),
('2020_04_20_00_characters.sql','977B5E0C894E0A7E80B2A9626F17CA636A69BD22','RELEASED','2020-04-20 19:08:18',0),
('2020_04_24_00_characters.sql','85E2E0395A9457A53D73A9E0A7BB39B7E4C429BF','RELEASED','2020-04-24 22:04:59',0),
('2020_04_25_00_characters_2017_04_03_00_characters.sql','00FA3EFADAF807AC96619A3FE47216E21C3FCB19','RELEASED','2020-04-25 00:00:00',0);
('2020_04_25_00_characters_2017_04_03_00_characters.sql','00FA3EFADAF807AC96619A3FE47216E21C3FCB19','RELEASED','2020-04-25 00:00:00',0),
('2020_04_26_00_characters_2017_04_12_00_characters.sql','86AA94DA9B1EA283101100886C10F648C0CE6494','RELEASED','2020-04-26 00:00:00',0);
/*!40000 ALTER TABLE `updates` ENABLE KEYS */;
UNLOCK TABLES;

View File

@@ -0,0 +1,2 @@
ALTER TABLE `item_loot_money`
CHANGE `money` `money` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'money loot (in copper)';

View File

@@ -689,11 +689,11 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_DEL_GUILD_FINDER_GUILD_SETTINGS, "DELETE FROM guild_finder_guild_settings WHERE guildId = ?", CONNECTION_ASYNC);
// Items that hold loot or money
PrepareStatement(CHAR_SEL_ITEMCONTAINER_ITEMS, "SELECT item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids FROM item_loot_items WHERE container_id = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_ITEMCONTAINER_ITEMS, "SELECT container_id, item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids FROM item_loot_items", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_ITEMCONTAINER_ITEMS, "DELETE FROM item_loot_items WHERE container_id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_ITEMCONTAINER_ITEM, "DELETE FROM item_loot_items WHERE container_id = ? AND item_id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_ITEMCONTAINER_ITEM, "DELETE FROM item_loot_items WHERE container_id = ? AND item_id = ? AND item_count = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_ITEMCONTAINER_ITEMS, "INSERT INTO item_loot_items (container_id, item_id, item_count, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_bonus, context, bonus_list_ids) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_ITEMCONTAINER_MONEY, "SELECT money FROM item_loot_money WHERE container_id = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_ITEMCONTAINER_MONEY, "SELECT container_id, money FROM item_loot_money", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_ITEMCONTAINER_MONEY, "DELETE FROM item_loot_money WHERE container_id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_ITEMCONTAINER_MONEY, "INSERT INTO item_loot_money (container_id, money) VALUES (?, ?)", CONNECTION_ASYNC);

View File

@@ -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();

View File

@@ -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);

View File

@@ -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);

View File

@@ -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())

View File

@@ -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)

View File

@@ -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)
{

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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);