diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/server/database/Database/Implementation/CharacterDatabase.cpp | 6 | ||||
-rw-r--r-- | src/server/game/Entities/Item/Item.cpp | 193 | ||||
-rw-r--r-- | src/server/game/Entities/Item/Item.h | 8 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 43 | ||||
-rw-r--r-- | src/server/game/Handlers/LootHandler.cpp | 3 | ||||
-rw-r--r-- | src/server/game/Loot/LootItemStorage.cpp | 353 | ||||
-rw-r--r-- | src/server/game/Loot/LootItemStorage.h | 93 | ||||
-rw-r--r-- | src/server/game/Loot/LootMgr.cpp | 28 | ||||
-rw-r--r-- | src/server/game/Loot/LootMgr.h | 9 | ||||
-rw-r--r-- | src/server/game/World/World.cpp | 4 |
10 files changed, 492 insertions, 248 deletions
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 80be3a59bee..819ff8ecc62 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -543,11 +543,11 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_DEL_CHAR_FISHINGSTEPS, "DELETE FROM character_fishingsteps WHERE guid = ?", 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_prop, rnd_suffix 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_prop, rnd_suffix 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_prop, rnd_suffix) 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); diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp index e19f8ddc881..6ea1665ce35 100644 --- a/src/server/game/Entities/Item/Item.cpp +++ b/src/server/game/Entities/Item/Item.cpp @@ -22,6 +22,7 @@ #include "WorldPacket.h" #include "DatabaseEnv.h" #include "ItemEnchantmentMgr.h" +#include "LootItemStorage.h" #include "SpellMgr.h" #include "SpellInfo.h" #include "ScriptMgr.h" @@ -384,7 +385,7 @@ void Item::SaveToDB(SQLTransaction& trans) // Delete the items if this is a container if (!loot.isLooted()) - ItemContainerDeleteLootMoneyAndLootItemsFromDB(); + sLootItemStorage->RemoveStoredLootForContainer(GetGUID().GetCounter()); delete this; return; @@ -495,7 +496,7 @@ void Item::DeleteFromDB(SQLTransaction& trans) // Delete the items if this is a container if (!loot.isLooted()) - ItemContainerDeleteLootMoneyAndLootItemsFromDB(); + sLootItemStorage->RemoveStoredLootForContainer(GetGUID().GetCounter()); } /*static*/ @@ -1220,194 +1221,6 @@ bool Item::CheckSoulboundTradeExpire() return false; } -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; - - ObjectGuid::LowType container_id = GetGUID().GetCounter(); - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - - loot.containerID = container_id; // Save this for when a LootItem is removed - - // Save money - if (loot.gold > 0) - { - PreparedStatement* stmt_money = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY); - stmt_money->setUInt32(0, container_id); - trans->Append(stmt_money); - - stmt_money = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEMCONTAINER_MONEY); - stmt_money->setUInt32(0, container_id); - stmt_money->setUInt32(1, loot.gold); - trans->Append(stmt_money); - } - - // Save items - if (!loot.isLooted()) - { - PreparedStatement* stmt_items = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEMS); - stmt_items->setUInt32(0, container_id); - 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, rnd_suffix - stmt_items->setUInt32(0, container_id); - 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->setInt32(9, _li->randomPropertyId); - stmt_items->setUInt32(10, _li->randomSuffix); - 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; - - ObjectGuid::LowType container_id = GetGUID().GetCounter(); - - // Save this for later use - loot.containerID = container_id; - - // First, see if there was any money loot. This gets added directly to the container. - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEMCONTAINER_MONEY); - stmt->setUInt32(0, container_id); - 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->setUInt32(0, container_id); - 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, rnd_suffix - 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.randomPropertyId = fields[8].GetInt32(); - loot_item.randomSuffix = fields[9].GetUInt32(); - - // 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 - ObjectGuid::LowType containerId = GetGUID().GetCounter(); - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEMS); - stmt->setUInt32(0, containerId); - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - trans->Append(stmt); - CharacterDatabase.CommitTransaction(trans); -} - -void Item::ItemContainerDeleteLootItemFromDB(uint32 itemID) -{ - // Deletes a single item associated with an openable item from the DB - ObjectGuid::LowType containerId = GetGUID().GetCounter(); - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEM); - stmt->setUInt32(0, containerId); - stmt->setUInt32(1, itemID); - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - trans->Append(stmt); - CharacterDatabase.CommitTransaction(trans); -} - -void Item::ItemContainerDeleteLootMoneyFromDB() -{ - // Deletes the money loot associated with an openable item from the DB - ObjectGuid::LowType containerId = GetGUID().GetCounter(); - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY); - stmt->setUInt32(0, containerId); - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - trans->Append(stmt); - CharacterDatabase.CommitTransaction(trans); -} - -void Item::ItemContainerDeleteLootMoneyAndLootItemsFromDB() -{ - // Deletes money and items associated with an openable item from the DB - ItemContainerDeleteLootMoneyFromDB(); - ItemContainerDeleteLootItemsFromDB(); -} - void Item::SetCount(uint32 value) { SetUInt32Value(ITEM_FIELD_STACK_COUNT, value); diff --git a/src/server/game/Entities/Item/Item.h b/src/server/game/Entities/Item/Item.h index d501ceb6299..421d13b9aa1 100644 --- a/src/server/game/Entities/Item/Item.h +++ b/src/server/game/Entities/Item/Item.h @@ -230,14 +230,6 @@ class TC_GAME_API Item : public Object virtual void DeleteFromDB(SQLTransaction& trans); static void DeleteFromInventoryDB(SQLTransaction& 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(SQLTransaction& trans); void SaveRefundDataToDB(); void DeleteRefundDataFromDB(SQLTransaction* trans); diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 2eb6e1cbe5a..6f2ba8dcdd8 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -54,6 +54,7 @@ #include "LFGMgr.h" #include "Language.h" #include "Log.h" +#include "LootItemStorage.h" #include "MapManager.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" @@ -8624,9 +8625,12 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type) loot = &item->loot; + // Store container id + loot->containerID = item->GetGUID().GetCounter(); + // 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(); @@ -8649,7 +8653,7 @@ void Player::SendLoot(ObjectGuid guid, LootType loot_type) // 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; } @@ -12492,6 +12496,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) { SetGuidValue(PLAYER_FIELD_INV_SLOT_HEAD + (slot * 2), ObjectGuid::Empty); @@ -12499,7 +12504,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->ItemSet) @@ -12537,9 +12541,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->Flags & ITEM_FLAG_HAS_LOOT) - pItem->ItemContainerDeleteLootMoneyAndLootItemsFromDB(); + if (pProto->Flags & ITEM_FLAG_HAS_LOOT) + sLootItemStorage->RemoveStoredLootForContainer(pItem->GetGUID().GetCounter()); if (IsInWorld() && update) { @@ -13291,7 +13294,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 @@ -13308,7 +13311,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; @@ -13396,7 +13399,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->Flags & ITEM_FLAG_HAS_LOOT) + sLootItemStorage->RemoveStoredLootForContainer(pItem->GetGUID().GetCounter()); + } } m_items[slot] = nullptr; @@ -19662,8 +19670,17 @@ void Player::_SaveInventory(SQLTransaction& 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->Flags & ITEM_FLAG_HAS_LOOT) + sLootItemStorage->RemoveStoredLootForContainer(item->GetGUID().GetCounter()); + + continue; + } stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM); stmt->setUInt32(0, item->GetGUID().GetCounter()); @@ -19673,6 +19690,10 @@ void Player::_SaveInventory(SQLTransaction& trans) stmt->setUInt32(0, item->GetGUID().GetCounter()); trans->Append(stmt); m_items[i]->FSetState(ITEM_NEW); + + if (ItemTemplate const* itemTemplate = item->GetTemplate()) + if (itemTemplate->Flags & 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, @@ -24720,7 +24741,7 @@ void Player::StoreLootItem(uint8 lootSlot, Loot* loot) // LootItem is being removed (looted) from the container, delete it from the DB. if (loot->containerID > 0) - loot->DeleteLootItemFromContainerItemDB(item->itemid); + sLootItemStorage->RemoveStoredLootItemForContainer(loot->containerID, item->itemid, item->count); } else diff --git a/src/server/game/Handlers/LootHandler.cpp b/src/server/game/Handlers/LootHandler.cpp index 1e740b33bb9..4a0013b71c1 100644 --- a/src/server/game/Handlers/LootHandler.cpp +++ b/src/server/game/Handlers/LootHandler.cpp @@ -22,6 +22,7 @@ #include "Creature.h" #include "GameObject.h" #include "Group.h" +#include "LootItemStorage.h" #include "LootMgr.h" #include "ObjectAccessor.h" #include "Object.h" @@ -206,7 +207,7 @@ void WorldSession::HandleLootMoneyOpcode(WorldPacket& /*recvData*/) // Delete the money loot record from the DB if (loot->containerID > 0) - loot->DeleteLootMoneyFromContainerItemDB(); + sLootItemStorage->RemoveStoredMoneyForContainer(loot->containerID); // Delete container if empty if (loot->isLooted() && guid.IsItem()) diff --git a/src/server/game/Loot/LootItemStorage.cpp b/src/server/game/Loot/LootItemStorage.cpp new file mode 100644 index 00000000000..45f1027932b --- /dev/null +++ b/src/server/game/Loot/LootItemStorage.cpp @@ -0,0 +1,353 @@ +/* +* Copyright (C) 2008-2017 TrinityCore <http://www.trinitycore.org/> +* +* 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 "LootItemStorage.h" +#include "ItemTemplate.h" +#include "Log.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<uint32, 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), RandomPropertyId(lootItem.randomPropertyId), RandomSuffix(lootItem.randomSuffix) +{ +} + +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; + + SQLTransaction trans = SQLTransaction(nullptr); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEMCONTAINER_ITEMS); + PreparedQueryResult result = CharacterDatabase.Query(stmt); + if (result) + { + do + { + Field* fields = result->Fetch(); + + uint32 key = fields[0].GetUInt32(); + 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.randomPropertyId = fields[9].GetInt32(); + lootItem.randomSuffix = fields[10].GetUInt32(); + + 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(); + + uint32 key = fields[0].GetUInt32(); + 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); + 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.randomPropertyId = storedItemPair.second.RandomPropertyId; + li.randomSuffix = storedItemPair.second.RandomSuffix; + + // 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(uint32 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(uint32 containerId) +{ + // write + { + boost::unique_lock<boost::shared_mutex> lock(*GetLock()); + _lootItemStore.erase(containerId); + } + + SQLTransaction trans = CharacterDatabase.BeginTransaction(); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEMS); + stmt->setUInt32(0, containerId); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY); + stmt->setUInt32(0, containerId); + trans->Append(stmt); + + CharacterDatabase.CommitTransaction(trans); +} + +void LootItemStorage::RemoveStoredLootItemForContainer(uint32 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); + if (itr != _lootItemStore.end()) + { + TC_LOG_ERROR("misc", "Trying to store item loot by player: %s for container id: %u that is already in storage!", player->GetGUID().ToString().c_str(), loot->containerID); + return; + } + } + + StoredLootContainer container(loot->containerID); + + SQLTransaction trans = CharacterDatabase.BeginTransaction(); + if (loot->gold) + container.AddMoney(loot->gold, trans); + + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEMS); + stmt->setUInt32(0, loot->containerID); + 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, std::move(container)); + } +} + +void StoredLootContainer::AddLootItem(LootItem const& lootItem, SQLTransaction& trans) +{ + _lootItems.emplace(std::piecewise_construct, std::forward_as_tuple(lootItem.itemid), std::forward_as_tuple(lootItem)); + if (!trans) + return; + + PreparedStatement* 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->setUInt32(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.randomPropertyId); + stmt->setUInt32(10, lootItem.randomSuffix); + trans->Append(stmt); +} + +void StoredLootContainer::AddMoney(uint32 money, SQLTransaction& trans) +{ + _money = money; + if (!trans) + return; + + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY); + stmt->setUInt32(0, _containerId); + trans->Append(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEMCONTAINER_MONEY); + stmt->setUInt32(0, _containerId); + stmt->setUInt32(1, _money); + trans->Append(stmt); +} + +void StoredLootContainer::RemoveMoney() +{ + _money = 0; + + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY); + stmt->setUInt32(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 + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEM); + stmt->setUInt32(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..871300e7fcd --- /dev/null +++ b/src/server/game/Loot/LootItemStorage.h @@ -0,0 +1,93 @@ +/* +* Copyright (C) 2008-2017 TrinityCore <http://www.trinitycore.org/> +* +* 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 <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; + int32 RandomPropertyId; + uint32 RandomSuffix; +}; + +class StoredLootContainer +{ + public: + typedef std::unordered_multimap<uint32 /*itemId*/, StoredLootItem> StoredLootItemContainer; + + explicit StoredLootContainer(uint32 containerId) : _containerId(containerId), _money(0) { } + + void AddLootItem(LootItem const& lootItem, SQLTransaction& trans); + void AddMoney(uint32 money, SQLTransaction& 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; + uint32 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(uint32 containerId); + void RemoveStoredLootForContainer(uint32 containerId); + void RemoveStoredLootItemForContainer(uint32 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/Loot/LootMgr.cpp b/src/server/game/Loot/LootMgr.cpp index a301426f556..34712308070 100644 --- a/src/server/game/Loot/LootMgr.cpp +++ b/src/server/game/Loot/LootMgr.cpp @@ -366,7 +366,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 @@ -692,33 +691,6 @@ void Loot::generateMoneyLoot(uint32 minAmount, uint32 maxAmount) } } -void Loot::DeleteLootItemFromContainerItemDB(uint32 itemID) -{ - // Deletes a single item associated with an openable item from the DB - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEM); - stmt->setUInt32(0, containerID); - 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 - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY); - stmt->setUInt32(0, containerID); - CharacterDatabase.Execute(stmt); -} - LootItem* Loot::LootItemInSlot(uint32 lootSlot, Player* player, NotNormalLootItem* *qitem, NotNormalLootItem* *ffaitem, NotNormalLootItem* *conditem) { LootItem* item = NULL; diff --git a/src/server/game/Loot/LootMgr.h b/src/server/game/Loot/LootMgr.h index 0aa85bb0186..4069d5731b5 100644 --- a/src/server/game/Loot/LootMgr.h +++ b/src/server/game/Loot/LootMgr.h @@ -164,7 +164,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) @@ -172,8 +171,8 @@ struct TC_GAME_API LootItem // Empty constructor for creating an empty LootItem to be filled in with DB data LootItem() : itemid(0), randomSuffix(0), randomPropertyId(0), 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; @@ -328,10 +327,6 @@ struct TC_GAME_API Loot Loot(uint32 _gold = 0) : gold(_gold), unlootedCount(0), roundRobinPlayer(), loot_type(LOOT_NONE), maxDuplicates(1), containerID(0) { } ~Loot() { clear(); } - // 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/World/World.cpp b/src/server/game/World/World.cpp index 38015c6bdab..6cd5960ab9e 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -47,6 +47,7 @@ #include "InstanceSaveMgr.h" #include "Language.h" #include "LFGMgr.h" +#include "LootItemStorage.h" #include "MapManager.h" #include "Memory.h" #include "MMapFactory.h" @@ -1841,6 +1842,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); |