/*
* 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 .
*/
#include "DatabaseEnv.h"
#include "Item.h"
#include "ItemTemplate.h"
#include "Log.h"
#include "Loot.h"
#include "LootItemStorage.h"
#include "LootMgr.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "StringConvert.h"
#include "Util.h"
#include
#include
namespace
{
std::unordered_map _lootItemStore;
}
StoredLootItem::StoredLootItem(LootItem const& lootItem) : ItemId(lootItem.itemid), Count(lootItem.count), ItemIndex(lootItem.LootListId), 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;
}
std::shared_mutex* LootItemStorage::GetLock()
{
static std::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();
StoredLootContainer& storedContainer = _lootItemStore.try_emplace(key, key).first->second;
LootItem lootItem;
lootItem.type = static_cast(fields[1].GetInt8());
lootItem.itemid = fields[2].GetUInt32();
lootItem.count = fields[3].GetUInt32();
lootItem.LootListId = fields[4].GetUInt32();
lootItem.follow_loot_rules = fields[5].GetBool();
lootItem.freeforall = fields[6].GetBool();
lootItem.is_blocked = fields[7].GetBool();
lootItem.is_counted = fields[8].GetBool();
lootItem.is_underthreshold = fields[9].GetBool();
lootItem.needs_quest = fields[10].GetBool();
lootItem.randomBonusListId = fields[11].GetUInt32();
lootItem.context = ItemContext(fields[12].GetUInt8());
for (std::string_view bonusList : Trinity::Tokenize(fields[13].GetStringView(), ' ', false))
if (Optional bonusListID = Trinity::StringTo(bonusList))
lootItem.BonusListIDs.push_back(*bonusListID);
storedContainer.AddLootItem(lootItem, trans);
++count;
} while (result->NextRow());
TC_LOG_INFO("server.loading", ">> Loaded {} stored item loots in {} 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();
StoredLootContainer& storedContainer = _lootItemStore.try_emplace(key, key).first->second;
storedContainer.AddMoney(fields[1].GetUInt32(), trans);
++count;
} while (result->NextRow());
TC_LOG_INFO("server.loading", ">> Loaded {} stored item money in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
}
else
TC_LOG_INFO("server.loading", ">> Loaded 0 stored item money");
}
bool LootItemStorage::LoadStoredLoot(Item* item, Player* player)
{
StoredLootContainer const* container = nullptr;
// read
{
std::shared_lock lock(*GetLock());
auto itr = _lootItemStore.find(item->GetGUID().GetCounter());
if (itr == _lootItemStore.end())
return false;
container = &itr->second;
}
// container is never null at this point
Loot* loot = new Loot(player->GetMap(), item->GetGUID(), LOOT_ITEM, nullptr);
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.LootListId = storedItemPair.second.ItemIndex;
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;
}
}
if (!loot->items.empty())
{
std::sort(loot->items.begin(), loot->items.end(), [](LootItem const& left, LootItem const& right) { return left.LootListId < right.LootListId; });
uint32 lootListId = 0;
// add dummy loot items to ensure items are indexable by their LootListId
while (loot->items.size() <= loot->items.back().LootListId)
{
if (loot->items[lootListId].LootListId != lootListId)
{
auto li = loot->items.emplace(loot->items.begin() + lootListId);
li->LootListId = lootListId;
li->is_looted = true;
}
++lootListId;
}
}
// Mark the item if it has loot so it won't be generated again on open
item->m_loot.reset(loot);
item->m_lootGenerated = true;
return true;
}
void LootItemStorage::RemoveStoredMoneyForContainer(uint64 containerId)
{
// write
std::unique_lock lock(*GetLock());
auto itr = _lootItemStore.find(containerId);
if (itr == _lootItemStore.end())
return;
itr->second.RemoveMoney();
}
void LootItemStorage::RemoveStoredLootForContainer(uint64 containerId)
{
// write
{
std::unique_lock 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, LootItemType type, uint32 itemId, uint32 count, uint32 itemIndex)
{
// write
std::unique_lock lock(*GetLock());
auto itr = _lootItemStore.find(containerId);
if (itr == _lootItemStore.end())
return;
itr->second.RemoveItem(type, itemId, count, itemIndex);
}
void LootItemStorage::AddNewStoredLoot(uint64 containerId, 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
{
std::shared_lock lock(*GetLock());
auto itr = _lootItemStore.find(containerId);
if (itr != _lootItemStore.end())
{
TC_LOG_ERROR("misc", "Trying to store item loot by player: {} for container id: {} that is already in storage!", player->GetGUID().ToString(), containerId);
return;
}
}
StoredLootContainer container(containerId);
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
if (loot->gold)
container.AddMoney(loot->gold, trans);
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_ITEMS);
stmt->setUInt64(0, 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, loot))
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
{
std::unique_lock lock(*GetLock());
_lootItemStore.emplace(containerId, 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_type, item_id, item_count, item_index, follow_rules, ffa, blocked, counted, under_threshold, needs_quest, rnd_prop, rnd_suffix
stmt->setUInt64(0, _containerId);
stmt->setInt8(1, AsUnderlyingType(lootItem.type));
stmt->setUInt32(2, lootItem.itemid);
stmt->setUInt32(3, lootItem.count);
stmt->setUInt32(4, lootItem.LootListId);
stmt->setBool(5, lootItem.follow_loot_rules);
stmt->setBool(6, lootItem.freeforall);
stmt->setBool(7, lootItem.is_blocked);
stmt->setBool(8, lootItem.is_counted);
stmt->setBool(9, lootItem.is_underthreshold);
stmt->setBool(10, lootItem.needs_quest);
stmt->setInt32(11, lootItem.randomBonusListId);
stmt->setUInt8(13, AsUnderlyingType(lootItem.context));
std::ostringstream bonusListIDs;
for (int32 bonusListID : lootItem.BonusListIDs)
bonusListIDs << bonusListID << ' ';
stmt->setString(13, 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(LootItemType type, uint32 itemId, uint32 count, uint32 itemIndex)
{
auto bounds = _lootItems.equal_range(itemId);
for (auto itr = bounds.first; itr != bounds.second; ++itr)
{
if (itr->second.ItemIndex == itemIndex)
{
_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->setInt8(1, AsUnderlyingType(type));
stmt->setUInt32(2, itemId);
stmt->setUInt32(3, count);
stmt->setUInt32(4, itemIndex);
CharacterDatabase.Execute(stmt);
}