/*
* Copyright (C) 2008-2017 TrinityCore
* Copyright (C) 2005-2009 MaNGOS
*
* 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 "LootMgr.h"
#include "Log.h"
#include "ObjectMgr.h"
#include "World.h"
#include "Util.h"
#include "SharedDefines.h"
#include "SpellMgr.h"
#include "SpellInfo.h"
#include "Group.h"
#include "Player.h"
#include "Containers.h"
#include "LootPackets.h"
static Rates const qualityToRate[MAX_ITEM_QUALITY] =
{
RATE_DROP_ITEM_POOR, // ITEM_QUALITY_POOR
RATE_DROP_ITEM_NORMAL, // ITEM_QUALITY_NORMAL
RATE_DROP_ITEM_UNCOMMON, // ITEM_QUALITY_UNCOMMON
RATE_DROP_ITEM_RARE, // ITEM_QUALITY_RARE
RATE_DROP_ITEM_EPIC, // ITEM_QUALITY_EPIC
RATE_DROP_ITEM_LEGENDARY, // ITEM_QUALITY_LEGENDARY
RATE_DROP_ITEM_ARTIFACT, // ITEM_QUALITY_ARTIFACT
};
LootStore LootTemplates_Creature("creature_loot_template", "creature entry", true);
LootStore LootTemplates_Disenchant("disenchant_loot_template", "item disenchant id", true);
LootStore LootTemplates_Fishing("fishing_loot_template", "area id", true);
LootStore LootTemplates_Gameobject("gameobject_loot_template", "gameobject entry", true);
LootStore LootTemplates_Item("item_loot_template", "item entry", true);
LootStore LootTemplates_Mail("mail_loot_template", "mail template id", false);
LootStore LootTemplates_Milling("milling_loot_template", "item entry (herb)", true);
LootStore LootTemplates_Pickpocketing("pickpocketing_loot_template", "creature pickpocket lootid", true);
LootStore LootTemplates_Prospecting("prospecting_loot_template", "item entry (ore)", true);
LootStore LootTemplates_Reference("reference_loot_template", "reference id", false);
LootStore LootTemplates_Skinning("skinning_loot_template", "creature skinning id", true);
LootStore LootTemplates_Spell("spell_loot_template", "spell id (random item creating)", false);
// Selects invalid loot items to be removed from group possible entries (before rolling)
struct LootGroupInvalidSelector : public std::unary_function
{
explicit LootGroupInvalidSelector(Loot const& loot, uint16 lootMode) : _loot(loot), _lootMode(lootMode) { }
bool operator()(LootStoreItem* item) const
{
if (!(item->lootmode & _lootMode))
return true;
uint8 foundDuplicates = 0;
for (std::vector::const_iterator itr = _loot.items.begin(); itr != _loot.items.end(); ++itr)
if (itr->itemid == item->itemid)
if (++foundDuplicates == _loot.maxDuplicates)
return true;
return false;
}
private:
Loot const& _loot;
uint16 _lootMode;
};
class LootTemplate::LootGroup // A set of loot definitions for items (refs are not allowed)
{
public:
LootGroup() { }
~LootGroup();
void AddEntry(LootStoreItem* item); // Adds an entry to the group (at loading stage)
bool HasQuestDrop() const; // True if group includes at least 1 quest drop entry
bool HasQuestDropForPlayer(Player const* player) const;
// The same for active quests of the player
void Process(Loot& loot, uint16 lootMode) const; // Rolls an item from the group (if any) and adds the item to the loot
float RawTotalChance() const; // Overall chance for the group (without equal chanced items)
float TotalChance() const; // Overall chance for the group
void Verify(LootStore const& lootstore, uint32 id, uint8 group_id) const;
void CollectLootIds(LootIdSet& set) const;
void CheckLootRefs(LootTemplateMap const& store, LootIdSet* ref_set) const;
LootStoreItemList* GetExplicitlyChancedItemList() { return &ExplicitlyChanced; }
LootStoreItemList* GetEqualChancedItemList() { return &EqualChanced; }
void CopyConditions(ConditionContainer conditions);
private:
LootStoreItemList ExplicitlyChanced; // Entries with chances defined in DB
LootStoreItemList EqualChanced; // Zero chances - every entry takes the same chance
LootStoreItem const* Roll(Loot& loot, uint16 lootMode) const; // Rolls an item from the group, returns NULL if all miss their chances
// This class must never be copied - storing pointers
LootGroup(LootGroup const&);
LootGroup& operator=(LootGroup const&);
};
//Remove all data and free all memory
void LootStore::Clear()
{
for (LootTemplateMap::const_iterator itr=m_LootTemplates.begin(); itr != m_LootTemplates.end(); ++itr)
delete itr->second;
m_LootTemplates.clear();
}
// Checks validity of the loot store
// Actual checks are done within LootTemplate::Verify() which is called for every template
void LootStore::Verify() const
{
for (LootTemplateMap::const_iterator i = m_LootTemplates.begin(); i != m_LootTemplates.end(); ++i)
i->second->Verify(*this, i->first);
}
// Loads a *_loot_template DB table into loot store
// All checks of the loaded template are called from here, no error reports at loot generation required
uint32 LootStore::LoadLootTable()
{
LootTemplateMap::const_iterator tab;
// Clearing store (for reloading case)
Clear();
// 0 1 2 3 4 5 6
QueryResult result = WorldDatabase.PQuery("SELECT Entry, Item, Reference, Chance, QuestRequired, LootMode, GroupId, MinCount, MaxCount FROM %s", GetName());
if (!result)
return 0;
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint32 entry = fields[0].GetUInt32();
uint32 item = fields[1].GetUInt32();
uint32 reference = fields[2].GetUInt32();
float chance = fields[3].GetFloat();
bool needsquest = fields[4].GetBool();
uint16 lootmode = fields[5].GetUInt16();
uint8 groupid = fields[6].GetUInt8();
uint8 mincount = fields[7].GetUInt8();
uint8 maxcount = fields[8].GetUInt8();
if (groupid >= 1 << 7) // it stored in 7 bit field
{
TC_LOG_ERROR("sql.sql", "Table '%s' Entry %d Item %d: GroupId (%u) must be less %u - skipped", GetName(), entry, item, groupid, 1 << 7);
return 0;
}
LootStoreItem* storeitem = new LootStoreItem(item, reference, chance, needsquest, lootmode, groupid, mincount, maxcount);
if (!storeitem->IsValid(*this, entry)) // Validity checks
{
delete storeitem;
continue;
}
// Looking for the template of the entry
// often entries are put together
if (m_LootTemplates.empty() || tab->first != entry)
{
// Searching the template (in case template Id changed)
tab = m_LootTemplates.find(entry);
if (tab == m_LootTemplates.end())
{
std::pair< LootTemplateMap::iterator, bool > pr = m_LootTemplates.insert(LootTemplateMap::value_type(entry, new LootTemplate()));
tab = pr.first;
}
}
// else is empty - template Id and iter are the same
// finally iter refers to already existed or just created
// Adds current row to the template
tab->second->AddEntry(storeitem);
++count;
}
while (result->NextRow());
Verify(); // Checks validity of the loot store
return count;
}
bool LootStore::HaveQuestLootFor(uint32 loot_id) const
{
LootTemplateMap::const_iterator itr = m_LootTemplates.find(loot_id);
if (itr == m_LootTemplates.end())
return false;
// scan loot for quest items
return itr->second->HasQuestDrop(m_LootTemplates);
}
bool LootStore::HaveQuestLootForPlayer(uint32 loot_id, Player* player) const
{
LootTemplateMap::const_iterator tab = m_LootTemplates.find(loot_id);
if (tab != m_LootTemplates.end())
if (tab->second->HasQuestDropForPlayer(m_LootTemplates, player))
return true;
return false;
}
void LootStore::ResetConditions()
{
for (LootTemplateMap::iterator itr = m_LootTemplates.begin(); itr != m_LootTemplates.end(); ++itr)
{
ConditionContainer empty;
itr->second->CopyConditions(empty);
}
}
LootTemplate const* LootStore::GetLootFor(uint32 loot_id) const
{
LootTemplateMap::const_iterator tab = m_LootTemplates.find(loot_id);
if (tab == m_LootTemplates.end())
return NULL;
return tab->second;
}
LootTemplate* LootStore::GetLootForConditionFill(uint32 loot_id)
{
LootTemplateMap::iterator tab = m_LootTemplates.find(loot_id);
if (tab == m_LootTemplates.end())
return NULL;
return tab->second;
}
uint32 LootStore::LoadAndCollectLootIds(LootIdSet& lootIdSet)
{
uint32 count = LoadLootTable();
for (LootTemplateMap::const_iterator tab = m_LootTemplates.begin(); tab != m_LootTemplates.end(); ++tab)
lootIdSet.insert(tab->first);
return count;
}
void LootStore::CheckLootRefs(LootIdSet* ref_set) const
{
for (LootTemplateMap::const_iterator ltItr = m_LootTemplates.begin(); ltItr != m_LootTemplates.end(); ++ltItr)
ltItr->second->CheckLootRefs(m_LootTemplates, ref_set);
}
void LootStore::ReportUnusedIds(LootIdSet const& lootIdSet) const
{
// all still listed ids isn't referenced
for (LootIdSet::const_iterator itr = lootIdSet.begin(); itr != lootIdSet.end(); ++itr)
TC_LOG_ERROR("sql.sql", "Table '%s' Entry %d isn't %s and not referenced from loot, and thus useless.", GetName(), *itr, GetEntryName());
}
void LootStore::ReportNonExistingId(uint32 lootId) const
{
TC_LOG_ERROR("sql.sql", "Table '%s' Entry %d does not exist", GetName(), lootId);
}
void LootStore::ReportNonExistingId(uint32 lootId, const char* ownerType, uint32 ownerId) const
{
TC_LOG_ERROR("sql.sql", "Table '%s' Entry %d does not exist but it is used by %s %d", GetName(), lootId, ownerType, ownerId);
}
//
// --------- LootStoreItem ---------
//
// Checks if the entry (quest, non-quest, reference) takes it's chance (at loot generation)
// RATE_DROP_ITEMS is no longer used for all types of entries
bool LootStoreItem::Roll(bool rate) const
{
if (chance >= 100.0f)
return true;
if (reference > 0) // reference case
return roll_chance_f(chance* (rate ? sWorld->getRate(RATE_DROP_ITEM_REFERENCED) : 1.0f));
ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(itemid);
float qualityModifier = pProto && rate ? sWorld->getRate(qualityToRate[pProto->GetQuality()]) : 1.0f;
return roll_chance_f(chance * qualityModifier);
}
// Checks correctness of values
bool LootStoreItem::IsValid(LootStore const& store, uint32 entry) const
{
if (mincount == 0)
{
TC_LOG_ERROR("sql.sql", "Table '%s' Entry %d Item %d: wrong MinCount (%d) - skipped", store.GetName(), entry, itemid, mincount);
return false;
}
if (reference == 0) // item (quest or non-quest) entry, maybe grouped
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemid);
if (!proto)
{
TC_LOG_ERROR("sql.sql", "Table '%s' Entry %d Item %d: item entry not listed in `item_template` - skipped", store.GetName(), entry, itemid);
return false;
}
if (chance == 0 && groupid == 0) // Zero chance is allowed for grouped entries only
{
TC_LOG_ERROR("sql.sql", "Table '%s' Entry %d Item %d: equal-chanced grouped entry, but group not defined - skipped", store.GetName(), entry, itemid);
return false;
}
if (chance != 0 && chance < 0.000001f) // loot with low chance
{
TC_LOG_ERROR("sql.sql", "Table '%s' Entry %d Item %d: low chance (%f) - skipped",
store.GetName(), entry, itemid, chance);
return false;
}
if (maxcount < mincount) // wrong max count
{
TC_LOG_ERROR("sql.sql", "Table '%s' Entry %d Item %d: MaxCount (%u) less that MinCount (%i) - skipped", store.GetName(), entry, itemid, int32(maxcount), mincount);
return false;
}
}
else // if reference loot
{
if (needs_quest)
TC_LOG_ERROR("sql.sql", "Table '%s' Entry %d Item %d: quest required will be ignored", store.GetName(), entry, itemid);
else if (chance == 0) // no chance for the reference
{
TC_LOG_ERROR("sql.sql", "Table '%s' Entry %d Item %d: zero chance is specified for a reference, skipped", store.GetName(), entry, itemid);
return false;
}
}
return true; // Referenced template existence is checked at whole store level
}
//
// --------- LootItem ---------
//
// Constructor, copies most fields from LootStoreItem and generates random count
LootItem::LootItem(LootStoreItem const& li)
{
itemid = li.itemid;
conditions = li.conditions;
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemid);
freeforall = proto && (proto->GetFlags() & ITEM_FLAG_MULTI_DROP);
follow_loot_rules = proto && (proto->FlagsCu & ITEM_FLAGS_CU_FOLLOW_LOOT_RULES);
needs_quest = li.needs_quest;
randomSuffix = GenerateEnchSuffixFactor(itemid);
randomPropertyId = Item::GenerateItemRandomPropertyId(itemid);
upgradeId = sDB2Manager.GetRulesetItemUpgrade(itemid);
count = 0;
is_looted = 0;
is_blocked = 0;
is_underthreshold = 0;
is_counted = 0;
canSave = true;
}
// Basic checks for player/item compatibility - if false no chance to see the item in the loot
bool LootItem::AllowedForPlayer(Player const* player) const
{
// DB conditions check
if (!sConditionMgr->IsObjectMeetToConditions(const_cast(player), conditions))
return false;
ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(itemid);
if (!pProto)
return false;
// not show loot for players without profession or those who already know the recipe
if ((pProto->GetFlags() & ITEM_FLAG_HIDE_UNUSABLE_RECIPE) && (!player->HasSkill(pProto->GetRequiredSkill()) || player->HasSpell(pProto->Effects[1]->SpellID)))
return false;
// not show loot for not own team
if ((pProto->GetFlags2() & ITEM_FLAG2_FACTION_HORDE) && player->GetTeam() != HORDE)
return false;
if ((pProto->GetFlags2() & ITEM_FLAG2_FACTION_ALLIANCE) && player->GetTeam() != ALLIANCE)
return false;
// check quest requirements
if (!(pProto->FlagsCu & ITEM_FLAGS_CU_IGNORE_QUEST_STATUS) && ((needs_quest || (pProto->GetStartQuest() && player->GetQuestStatus(pProto->GetStartQuest()) != QUEST_STATUS_NONE)) && !player->HasQuestForItem(itemid)))
return false;
// Don't show bind-when-picked-up unique items if player already has the maximum allowed quantity.
if (pProto->GetBonding() == BIND_ON_ACQUIRE && pProto->GetMaxCount() && player->GetItemCount(itemid, true) >= pProto->GetMaxCount())
return false;
return true;
}
void LootItem::AddAllowedLooter(const Player* player)
{
allowedGUIDs.insert(player->GetGUID());
}
//
// --------- Loot ---------
//
// Inserts the item into the loot (called by LootTemplate processors)
void Loot::AddItem(LootStoreItem const& item)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item.itemid);
if (!proto)
return;
uint32 count = urand(item.mincount, item.maxcount);
uint32 stacks = count / proto->GetMaxStackSize() + ((count % proto->GetMaxStackSize()) ? 1 : 0);
std::vector& lootItems = item.needs_quest ? quest_items : items;
uint32 limit = item.needs_quest ? MAX_NR_QUEST_ITEMS : MAX_NR_LOOT_ITEMS;
for (uint32 i = 0; i < stacks && lootItems.size() < limit; ++i)
{
LootItem generatedLoot(item);
generatedLoot.count = std::min(count, proto->GetMaxStackSize());
if (_difficultyBonusTreeMod)
{
std::set bonusListIDs = sDB2Manager.GetItemBonusTree(generatedLoot.itemid, _difficultyBonusTreeMod);
generatedLoot.BonusListIDs.insert(generatedLoot.BonusListIDs.end(), bonusListIDs.begin(), bonusListIDs.end());
}
lootItems.push_back(generatedLoot);
count -= proto->GetMaxStackSize();
// non-conditional one-player only items are counted here,
// free for all items are counted in FillFFALoot(),
// non-ffa conditionals are counted in FillNonQuestNonFFAConditionalLoot()
if (!item.needs_quest && item.conditions.empty() && !(proto->GetFlags() & ITEM_FLAG_MULTI_DROP))
++unlootedCount;
}
}
LootItem const* Loot::GetItemInSlot(uint32 lootSlot) const
{
if (lootSlot < items.size())
return &items[lootSlot];
lootSlot -= uint32(items.size());
if (lootSlot < quest_items.size())
return &quest_items[lootSlot];
return nullptr;
}
// Calls processor of corresponding LootTemplate (which handles everything including references)
bool Loot::FillLoot(uint32 lootId, LootStore const& store, Player* lootOwner, bool personal, bool noEmptyError, uint16 lootMode /*= LOOT_MODE_DEFAULT*/)
{
// Must be provided
if (!lootOwner)
return false;
LootTemplate const* tab = store.GetLootFor(lootId);
if (!tab)
{
if (!noEmptyError)
TC_LOG_ERROR("sql.sql", "Table '%s' loot id #%u used but it doesn't have records.", store.GetName(), lootId);
return false;
}
_difficultyBonusTreeMod = lootOwner->GetMap()->GetDifficultyLootBonusTreeMod();
items.reserve(MAX_NR_LOOT_ITEMS);
quest_items.reserve(MAX_NR_QUEST_ITEMS);
tab->Process(*this, store.IsRatesAllowed(), lootMode); // Processing is done there, callback via Loot::AddItem()
// Setting access rights for group loot case
Group* group = lootOwner->GetGroup();
if (!personal && group)
{
roundRobinPlayer = lootOwner->GetGUID();
for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next())
if (Player* player = itr->GetSource()) // should actually be looted object instead of lootOwner but looter has to be really close so doesnt really matter
FillNotNormalLootFor(player, player->IsAtGroupRewardDistance(lootOwner));
for (uint8 i = 0; i < items.size(); ++i)
{
if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(items[i].itemid))
if (proto->GetQuality() < uint32(group->GetLootThreshold()))
items[i].is_underthreshold = true;
}
}
// ... for personal loot
else
FillNotNormalLootFor(lootOwner, true);
return true;
}
void Loot::FillNotNormalLootFor(Player* player, bool presentAtLooting)
{
ObjectGuid::LowType plguid = player->GetGUID().GetCounter();
QuestItemMap::const_iterator qmapitr = PlayerQuestItems.find(plguid);
if (qmapitr == PlayerQuestItems.end())
FillQuestLoot(player);
qmapitr = PlayerFFAItems.find(plguid);
if (qmapitr == PlayerFFAItems.end())
FillFFALoot(player);
qmapitr = PlayerNonQuestNonFFAConditionalItems.find(plguid);
if (qmapitr == PlayerNonQuestNonFFAConditionalItems.end())
FillNonQuestNonFFAConditionalLoot(player, presentAtLooting);
// if not auto-processed player will have to come and pick it up manually
if (!presentAtLooting)
return;
// Process currency items
uint32 max_slot = GetMaxSlotInLootFor(player);
LootItem const* item = NULL;
uint32 itemsSize = uint32(items.size());
for (uint32 i = 0; i < max_slot; ++i)
{
if (i < items.size())
item = &items[i];
else
item = &quest_items[i-itemsSize];
if (!item->is_looted && item->freeforall && item->AllowedForPlayer(player))
if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(item->itemid))
if (proto->IsCurrencyToken())
player->StoreLootItem(i, this);
}
}
QuestItemList* Loot::FillFFALoot(Player* player)
{
QuestItemList* ql = new QuestItemList();
for (uint8 i = 0; i < items.size(); ++i)
{
LootItem &item = items[i];
if (!item.is_looted && item.freeforall && item.AllowedForPlayer(player))
{
ql->push_back(QuestItem(i));
++unlootedCount;
}
}
if (ql->empty())
{
delete ql;
return NULL;
}
PlayerFFAItems[player->GetGUID().GetCounter()] = ql;
return ql;
}
QuestItemList* Loot::FillQuestLoot(Player* player)
{
if (items.size() == MAX_NR_LOOT_ITEMS)
return NULL;
QuestItemList* ql = new QuestItemList();
for (uint8 i = 0; i < quest_items.size(); ++i)
{
LootItem &item = quest_items[i];
if (!item.is_looted && (item.AllowedForPlayer(player) || (item.follow_loot_rules && player->GetGroup() && ((player->GetGroup()->GetLootMethod() == MASTER_LOOT && player->GetGroup()->GetMasterLooterGuid() == player->GetGUID()) || player->GetGroup()->GetLootMethod() != MASTER_LOOT))))
{
ql->push_back(QuestItem(i));
// quest items get blocked when they first appear in a
// player's quest vector
//
// increase once if one looter only, looter-times if free for all
if (item.freeforall || !item.is_blocked)
++unlootedCount;
if (!player->GetGroup() || (player->GetGroup()->GetLootMethod() != GROUP_LOOT))
item.is_blocked = true;
if (items.size() + ql->size() == MAX_NR_LOOT_ITEMS)
break;
}
}
if (ql->empty())
{
delete ql;
return NULL;
}
PlayerQuestItems[player->GetGUID().GetCounter()] = ql;
return ql;
}
QuestItemList* Loot::FillNonQuestNonFFAConditionalLoot(Player* player, bool presentAtLooting)
{
QuestItemList* ql = new QuestItemList();
for (uint8 i = 0; i < items.size(); ++i)
{
LootItem &item = items[i];
if (!item.is_looted && !item.freeforall && (item.AllowedForPlayer(player) || (item.follow_loot_rules && player->GetGroup() && ((player->GetGroup()->GetLootMethod() == MASTER_LOOT && player->GetGroup()->GetMasterLooterGuid() == player->GetGUID()) || player->GetGroup()->GetLootMethod() != MASTER_LOOT))))
{
if (presentAtLooting)
item.AddAllowedLooter(player);
if (!item.conditions.empty())
{
ql->push_back(QuestItem(i));
if (!item.is_counted)
{
++unlootedCount;
item.is_counted = true;
}
}
}
}
if (ql->empty())
{
delete ql;
return NULL;
}
PlayerNonQuestNonFFAConditionalItems[player->GetGUID().GetCounter()] = ql;
return ql;
}
//===================================================
void Loot::NotifyItemRemoved(uint8 lootIndex)
{
// notify all players that are looting this that the item was removed
// convert the index to the slot the player sees
GuidSet::iterator i_next;
for (GuidSet::iterator i = PlayersLooting.begin(); i != PlayersLooting.end(); i = i_next)
{
i_next = i;
++i_next;
if (Player* player = ObjectAccessor::FindPlayer(*i))
player->SendNotifyLootItemRemoved(GetGUID(), lootIndex);
else
PlayersLooting.erase(i);
}
}
void Loot::NotifyMoneyRemoved()
{
// notify all players that are looting this that the money was removed
GuidSet::iterator i_next;
for (GuidSet::iterator i = PlayersLooting.begin(); i != PlayersLooting.end(); i = i_next)
{
i_next = i;
++i_next;
if (Player* player = ObjectAccessor::FindPlayer(*i))
player->SendNotifyLootMoneyRemoved(GetGUID());
else
PlayersLooting.erase(i);
}
}
void Loot::NotifyQuestItemRemoved(uint8 questIndex)
{
// when a free for all questitem is looted
// all players will get notified of it being removed
// (other questitems can be looted by each group member)
// bit inefficient but isn't called often
GuidSet::iterator i_next;
for (GuidSet::iterator i = PlayersLooting.begin(); i != PlayersLooting.end(); i = i_next)
{
i_next = i;
++i_next;
if (Player* player = ObjectAccessor::FindPlayer(*i))
{
QuestItemMap::const_iterator pq = PlayerQuestItems.find(player->GetGUID().GetCounter());
if (pq != PlayerQuestItems.end() && pq->second)
{
// find where/if the player has the given item in it's vector
QuestItemList& pql = *pq->second;
uint8 j;
for (j = 0; j < pql.size(); ++j)
if (pql[j].index == questIndex)
break;
if (j < pql.size())
player->SendNotifyLootItemRemoved(GetGUID(), items.size()+j);
}
}
else
PlayersLooting.erase(i);
}
}
void Loot::generateMoneyLoot(uint32 minAmount, uint32 maxAmount)
{
if (maxAmount > 0)
{
if (maxAmount <= minAmount)
gold = uint32(maxAmount * sWorld->getRate(RATE_DROP_MONEY));
else if ((maxAmount - minAmount) < 32700)
gold = uint32(urand(minAmount, maxAmount) * sWorld->getRate(RATE_DROP_MONEY));
else
gold = uint32(urand(minAmount >> 8, maxAmount >> 8) * sWorld->getRate(RATE_DROP_MONEY)) << 8;
}
}
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->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
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEMCONTAINER_MONEY);
stmt->setUInt64(0, containerID.GetCounter());
CharacterDatabase.Execute(stmt);
}
LootItem* Loot::LootItemInSlot(uint32 lootSlot, Player* player, QuestItem* *qitem, QuestItem* *ffaitem, QuestItem* *conditem)
{
LootItem* item = NULL;
bool is_looted = true;
if (lootSlot >= items.size())
{
uint32 questSlot = lootSlot - items.size();
QuestItemMap::const_iterator itr = PlayerQuestItems.find(player->GetGUID().GetCounter());
if (itr != PlayerQuestItems.end() && questSlot < itr->second->size())
{
QuestItem* qitem2 = &itr->second->at(questSlot);
if (qitem)
*qitem = qitem2;
item = &quest_items[qitem2->index];
is_looted = qitem2->is_looted;
}
}
else
{
item = &items[lootSlot];
is_looted = item->is_looted;
if (item->freeforall)
{
QuestItemMap::const_iterator itr = PlayerFFAItems.find(player->GetGUID().GetCounter());
if (itr != PlayerFFAItems.end())
{
for (QuestItemList::const_iterator iter=itr->second->begin(); iter!= itr->second->end(); ++iter)
if (iter->index == lootSlot)
{
QuestItem* ffaitem2 = (QuestItem*)&(*iter);
if (ffaitem)
*ffaitem = ffaitem2;
is_looted = ffaitem2->is_looted;
break;
}
}
}
else if (!item->conditions.empty())
{
QuestItemMap::const_iterator itr = PlayerNonQuestNonFFAConditionalItems.find(player->GetGUID().GetCounter());
if (itr != PlayerNonQuestNonFFAConditionalItems.end())
{
for (QuestItemList::const_iterator iter=itr->second->begin(); iter!= itr->second->end(); ++iter)
{
if (iter->index == lootSlot)
{
QuestItem* conditem2 = (QuestItem*)&(*iter);
if (conditem)
*conditem = conditem2;
is_looted = conditem2->is_looted;
break;
}
}
}
}
}
if (is_looted)
return NULL;
return item;
}
uint32 Loot::GetMaxSlotInLootFor(Player* player) const
{
QuestItemMap::const_iterator itr = PlayerQuestItems.find(player->GetGUID().GetCounter());
return items.size() + (itr != PlayerQuestItems.end() ? itr->second->size() : 0);
}
// return true if there is any item that is lootable for any player (not quest item, FFA or conditional)
bool Loot::hasItemForAll() const
{
// Gold is always lootable
if (gold)
return true;
for (LootItem const& item : items)
if (!item.is_looted && !item.freeforall && item.conditions.empty())
return true;
return false;
}
// return true if there is any FFA, quest or conditional item for the player.
bool Loot::hasItemFor(Player* player) const
{
QuestItemMap const& lootPlayerQuestItems = GetPlayerQuestItems();
QuestItemMap::const_iterator q_itr = lootPlayerQuestItems.find(player->GetGUID().GetCounter());
if (q_itr != lootPlayerQuestItems.end())
{
QuestItemList* q_list = q_itr->second;
for (QuestItemList::const_iterator qi = q_list->begin(); qi != q_list->end(); ++qi)
{
const LootItem &item = quest_items[qi->index];
if (!qi->is_looted && !item.is_looted)
return true;
}
}
QuestItemMap const& lootPlayerFFAItems = GetPlayerFFAItems();
QuestItemMap::const_iterator ffa_itr = lootPlayerFFAItems.find(player->GetGUID().GetCounter());
if (ffa_itr != lootPlayerFFAItems.end())
{
QuestItemList* ffa_list = ffa_itr->second;
for (QuestItemList::const_iterator fi = ffa_list->begin(); fi != ffa_list->end(); ++fi)
{
const LootItem &item = items[fi->index];
if (!fi->is_looted && !item.is_looted)
return true;
}
}
QuestItemMap const& lootPlayerNonQuestNonFFAConditionalItems = GetPlayerNonQuestNonFFAConditionalItems();
QuestItemMap::const_iterator nn_itr = lootPlayerNonQuestNonFFAConditionalItems.find(player->GetGUID().GetCounter());
if (nn_itr != lootPlayerNonQuestNonFFAConditionalItems.end())
{
QuestItemList* conditional_list = nn_itr->second;
for (QuestItemList::const_iterator ci = conditional_list->begin(); ci != conditional_list->end(); ++ci)
{
const LootItem &item = items[ci->index];
if (!ci->is_looted && !item.is_looted)
return true;
}
}
return false;
}
// return true if there is any item over the group threshold (i.e. not underthreshold).
bool Loot::hasOverThresholdItem() const
{
for (uint8 i = 0; i < items.size(); ++i)
{
if (!items[i].is_looted && !items[i].is_underthreshold && !items[i].freeforall)
return true;
}
return false;
}
void Loot::BuildLootResponse(WorldPackets::Loot::LootResponse& packet, Player* viewer, PermissionTypes permission) const
{
if (permission == NONE_PERMISSION)
return;
packet.Coins = gold;
switch (permission)
{
case GROUP_PERMISSION:
case MASTER_PERMISSION:
case RESTRICTED_PERMISSION:
{
// if you are not the round-robin group looter, you can only see
// blocked rolled items and quest items, and !ffa items
for (uint8 i = 0; i < items.size(); ++i)
{
if (!items[i].is_looted && !items[i].freeforall && items[i].conditions.empty() && items[i].AllowedForPlayer(viewer))
{
uint8 slot_type;
if (items[i].is_blocked) // for ML & restricted is_blocked = !is_underthreshold
{
switch (permission)
{
case GROUP_PERMISSION:
slot_type = LOOT_SLOT_TYPE_ROLL_ONGOING;
break;
case MASTER_PERMISSION:
{
if (viewer->GetGroup() && viewer->GetGroup()->GetMasterLooterGuid() == viewer->GetGUID())
slot_type = LOOT_SLOT_TYPE_MASTER;
else
slot_type = LOOT_SLOT_TYPE_LOCKED;
break;
}
case RESTRICTED_PERMISSION:
slot_type = LOOT_SLOT_TYPE_LOCKED;
break;
default:
continue;
}
}
else if (roundRobinPlayer.IsEmpty() || viewer->GetGUID() == roundRobinPlayer || !items[i].is_underthreshold)
{
// no round robin owner or he has released the loot
// or it IS the round robin group owner
// => item is lootable
slot_type = LOOT_SLOT_TYPE_ALLOW_LOOT;
}
else
// item shall not be displayed.
continue;
WorldPackets::Loot::LootItemData lootItem;
lootItem.LootListID = i + 1;
lootItem.UIType = slot_type;
lootItem.Quantity = items[i].count;
lootItem.Loot.Initialize(items[i]);
packet.Items.push_back(lootItem);
}
}
break;
}
case ALL_PERMISSION:
case OWNER_PERMISSION:
{
for (uint8 i = 0; i < items.size(); ++i)
{
if (!items[i].is_looted && !items[i].freeforall && items[i].conditions.empty() && items[i].AllowedForPlayer(viewer))
{
WorldPackets::Loot::LootItemData lootItem;
lootItem.LootListID = i + 1;
lootItem.UIType = permission == OWNER_PERMISSION ? LOOT_SLOT_TYPE_OWNER : LOOT_SLOT_TYPE_ALLOW_LOOT;
lootItem.Quantity = items[i].count;
lootItem.Loot.Initialize(items[i]);
packet.Items.push_back(lootItem);
}
}
break;
}
default:
return;
}
LootSlotType slotType = permission == OWNER_PERMISSION ? LOOT_SLOT_TYPE_OWNER : LOOT_SLOT_TYPE_ALLOW_LOOT;
QuestItemMap const& lootPlayerQuestItems = GetPlayerQuestItems();
QuestItemMap::const_iterator q_itr = lootPlayerQuestItems.find(viewer->GetGUID().GetCounter());
if (q_itr != lootPlayerQuestItems.end())
{
QuestItemList* q_list = q_itr->second;
for (QuestItemList::const_iterator qi = q_list->begin(); qi != q_list->end(); ++qi)
{
LootItem const& item = quest_items[qi->index];
if (!qi->is_looted && !item.is_looted)
{
WorldPackets::Loot::LootItemData lootItem;
lootItem.LootListID = items.size() + qi->index + 1;
lootItem.Quantity = item.count;
lootItem.Loot.Initialize(item);
if (item.follow_loot_rules)
{
switch (permission)
{
case MASTER_PERMISSION:
lootItem.UIType = LOOT_SLOT_TYPE_MASTER;
break;
case RESTRICTED_PERMISSION:
lootItem.UIType = item.is_blocked ? LOOT_SLOT_TYPE_LOCKED : LOOT_SLOT_TYPE_ALLOW_LOOT;
break;
case GROUP_PERMISSION:
if (!item.is_blocked)
lootItem.UIType = LOOT_SLOT_TYPE_ALLOW_LOOT;
else
lootItem.UIType = LOOT_SLOT_TYPE_ROLL_ONGOING;
break;
default:
lootItem.UIType = slotType;
break;
}
}
else
lootItem.UIType = slotType;
packet.Items.push_back(lootItem);
}
}
}
QuestItemMap const& lootPlayerFFAItems = GetPlayerFFAItems();
QuestItemMap::const_iterator ffa_itr = lootPlayerFFAItems.find(viewer->GetGUID().GetCounter());
if (ffa_itr != lootPlayerFFAItems.end())
{
QuestItemList* ffa_list = ffa_itr->second;
for (QuestItemList::const_iterator fi = ffa_list->begin(); fi != ffa_list->end(); ++fi)
{
LootItem const& item = items[fi->index];
if (!fi->is_looted && !item.is_looted)
{
WorldPackets::Loot::LootItemData lootItem;
lootItem.LootListID = items.size() + fi->index + 1;
lootItem.UIType = slotType;
lootItem.Quantity = item.count;
lootItem.Loot.Initialize(item);
packet.Items.push_back(lootItem);
}
}
}
QuestItemMap const& lootPlayerNonQuestNonFFAConditionalItems = GetPlayerNonQuestNonFFAConditionalItems();
QuestItemMap::const_iterator nn_itr = lootPlayerNonQuestNonFFAConditionalItems.find(viewer->GetGUID().GetCounter());
if (nn_itr != lootPlayerNonQuestNonFFAConditionalItems.end())
{
QuestItemList* conditional_list = nn_itr->second;
for (QuestItemList::const_iterator ci = conditional_list->begin(); ci != conditional_list->end(); ++ci)
{
LootItem const& item = items[ci->index];
if (!ci->is_looted && !item.is_looted)
{
WorldPackets::Loot::LootItemData lootItem;
lootItem.LootListID = items.size() + ci->index + 1;
lootItem.Quantity = item.count;
lootItem.Loot.Initialize(item);
if (item.follow_loot_rules)
{
switch (permission)
{
case MASTER_PERMISSION:
lootItem.UIType = LOOT_SLOT_TYPE_MASTER;
break;
case RESTRICTED_PERMISSION:
lootItem.UIType = item.is_blocked ? LOOT_SLOT_TYPE_LOCKED : LOOT_SLOT_TYPE_ALLOW_LOOT;
break;
case GROUP_PERMISSION:
if (!item.is_blocked)
lootItem.UIType = LOOT_SLOT_TYPE_ALLOW_LOOT;
else
lootItem.UIType = LOOT_SLOT_TYPE_ROLL_ONGOING;
break;
default:
lootItem.UIType = slotType;
break;
}
}
else
lootItem.UIType = slotType;
packet.Items.push_back(lootItem);
}
}
}
}
//
// --------- LootTemplate::LootGroup ---------
//
LootTemplate::LootGroup::~LootGroup()
{
while (!ExplicitlyChanced.empty())
{
delete ExplicitlyChanced.back();
ExplicitlyChanced.pop_back();
}
while (!EqualChanced.empty())
{
delete EqualChanced.back();
EqualChanced.pop_back();
}
}
// Adds an entry to the group (at loading stage)
void LootTemplate::LootGroup::AddEntry(LootStoreItem* item)
{
if (item->chance != 0)
ExplicitlyChanced.push_back(item);
else
EqualChanced.push_back(item);
}
// Rolls an item from the group, returns NULL if all miss their chances
LootStoreItem const* LootTemplate::LootGroup::Roll(Loot& loot, uint16 lootMode) const
{
LootStoreItemList possibleLoot = ExplicitlyChanced;
possibleLoot.remove_if(LootGroupInvalidSelector(loot, lootMode));
if (!possibleLoot.empty()) // First explicitly chanced entries are checked
{
float roll = (float)rand_chance();
for (LootStoreItemList::const_iterator itr = possibleLoot.begin(); itr != possibleLoot.end(); ++itr) // check each explicitly chanced entry in the template and modify its chance based on quality.
{
LootStoreItem* item = *itr;
if (item->chance >= 100.0f)
return item;
roll -= item->chance;
if (roll < 0)
return item;
}
}
possibleLoot = EqualChanced;
possibleLoot.remove_if(LootGroupInvalidSelector(loot, lootMode));
if (!possibleLoot.empty()) // If nothing selected yet - an item is taken from equal-chanced part
return Trinity::Containers::SelectRandomContainerElement(possibleLoot);
return NULL; // Empty drop from the group
}
// True if group includes at least 1 quest drop entry
bool LootTemplate::LootGroup::HasQuestDrop() const
{
for (LootStoreItemList::const_iterator i = ExplicitlyChanced.begin(); i != ExplicitlyChanced.end(); ++i)
if ((*i)->needs_quest)
return true;
for (LootStoreItemList::const_iterator i = EqualChanced.begin(); i != EqualChanced.end(); ++i)
if ((*i)->needs_quest)
return true;
return false;
}
// True if group includes at least 1 quest drop entry for active quests of the player
bool LootTemplate::LootGroup::HasQuestDropForPlayer(Player const* player) const
{
for (LootStoreItemList::const_iterator i = ExplicitlyChanced.begin(); i != ExplicitlyChanced.end(); ++i)
if (player->HasQuestForItem((*i)->itemid))
return true;
for (LootStoreItemList::const_iterator i = EqualChanced.begin(); i != EqualChanced.end(); ++i)
if (player->HasQuestForItem((*i)->itemid))
return true;
return false;
}
void LootTemplate::LootGroup::CopyConditions(ConditionContainer /*conditions*/)
{
for (LootStoreItemList::iterator i = ExplicitlyChanced.begin(); i != ExplicitlyChanced.end(); ++i)
(*i)->conditions.clear();
for (LootStoreItemList::iterator i = EqualChanced.begin(); i != EqualChanced.end(); ++i)
(*i)->conditions.clear();
}
// Rolls an item from the group (if any takes its chance) and adds the item to the loot
void LootTemplate::LootGroup::Process(Loot& loot, uint16 lootMode) const
{
if (LootStoreItem const* item = Roll(loot, lootMode))
loot.AddItem(*item);
}
// Overall chance for the group without equal chanced items
float LootTemplate::LootGroup::RawTotalChance() const
{
float result = 0;
for (LootStoreItemList::const_iterator i=ExplicitlyChanced.begin(); i != ExplicitlyChanced.end(); ++i)
if (!(*i)->needs_quest)
result += (*i)->chance;
return result;
}
// Overall chance for the group
float LootTemplate::LootGroup::TotalChance() const
{
float result = RawTotalChance();
if (!EqualChanced.empty() && result < 100.0f)
return 100.0f;
return result;
}
void LootTemplate::LootGroup::Verify(LootStore const& lootstore, uint32 id, uint8 group_id) const
{
float chance = RawTotalChance();
if (chance > 101.0f) /// @todo replace with 100% when DBs will be ready
TC_LOG_ERROR("sql.sql", "Table '%s' entry %u group %d has total chance > 100%% (%f)", lootstore.GetName(), id, group_id, chance);
if (chance >= 100.0f && !EqualChanced.empty())
TC_LOG_ERROR("sql.sql", "Table '%s' entry %u group %d has items with chance=0%% but group total chance >= 100%% (%f)", lootstore.GetName(), id, group_id, chance);
}
void LootTemplate::LootGroup::CheckLootRefs(LootTemplateMap const& /*store*/, LootIdSet* ref_set) const
{
for (LootStoreItemList::const_iterator ieItr = ExplicitlyChanced.begin(); ieItr != ExplicitlyChanced.end(); ++ieItr)
{
LootStoreItem* item = *ieItr;
if (item->reference > 0)
{
if (!LootTemplates_Reference.GetLootFor(item->reference))
LootTemplates_Reference.ReportNonExistingId(item->reference, "Reference", item->itemid);
else if (ref_set)
ref_set->erase(item->reference);
}
}
for (LootStoreItemList::const_iterator ieItr = EqualChanced.begin(); ieItr != EqualChanced.end(); ++ieItr)
{
LootStoreItem* item = *ieItr;
if (item->reference > 0)
{
if (!LootTemplates_Reference.GetLootFor(item->reference))
LootTemplates_Reference.ReportNonExistingId(item->reference, "Reference", item->itemid);
else if (ref_set)
ref_set->erase(item->reference);
}
}
}
//
// --------- LootTemplate ---------
//
LootTemplate::~LootTemplate()
{
for (LootStoreItemList::iterator i = Entries.begin(); i != Entries.end(); ++i)
delete *i;
for (size_t i = 0; i < Groups.size(); ++i)
delete Groups[i];
}
// Adds an entry to the group (at loading stage)
void LootTemplate::AddEntry(LootStoreItem* item)
{
if (item->groupid > 0 && item->reference == 0) // Group
{
if (item->groupid >= Groups.size())
Groups.resize(item->groupid, NULL); // Adds new group the the loot template if needed
if (!Groups[item->groupid - 1])
Groups[item->groupid - 1] = new LootGroup();
Groups[item->groupid - 1]->AddEntry(item); // Adds new entry to the group
}
else // Non-grouped entries and references are stored together
Entries.push_back(item);
}
void LootTemplate::CopyConditions(const ConditionContainer& conditions)
{
for (LootStoreItemList::iterator i = Entries.begin(); i != Entries.end(); ++i)
(*i)->conditions.clear();
for (LootGroups::iterator i = Groups.begin(); i != Groups.end(); ++i)
if (LootGroup* group = *i)
group->CopyConditions(conditions);
}
void LootTemplate::CopyConditions(LootItem* li) const
{
// Copies the conditions list from a template item to a LootItemData
for (LootStoreItemList::const_iterator _iter = Entries.begin(); _iter != Entries.end(); ++_iter)
{
LootStoreItem* item = *_iter;
if (item->itemid != li->itemid)
continue;
li->conditions = item->conditions;
break;
}
}
// Rolls for every item in the template and adds the rolled items the the loot
void LootTemplate::Process(Loot& loot, bool rate, uint16 lootMode, uint8 groupId) const
{
if (groupId) // Group reference uses own processing of the group
{
if (groupId > Groups.size())
return; // Error message already printed at loading stage
if (!Groups[groupId - 1])
return;
Groups[groupId - 1]->Process(loot, lootMode);
return;
}
// Rolling non-grouped items
for (LootStoreItemList::const_iterator i = Entries.begin(); i != Entries.end(); ++i)
{
LootStoreItem* item = *i;
if (!(item->lootmode & lootMode)) // Do not add if mode mismatch
continue;
if (!item->Roll(rate))
continue; // Bad luck for the entry
if (item->reference > 0) // References processing
{
LootTemplate const* Referenced = LootTemplates_Reference.GetLootFor(item->reference);
if (!Referenced)
continue; // Error message already printed at loading stage
uint32 maxcount = uint32(float(item->maxcount) * sWorld->getRate(RATE_DROP_ITEM_REFERENCED_AMOUNT));
for (uint32 loop = 0; loop < maxcount; ++loop) // Ref multiplicator
Referenced->Process(loot, rate, lootMode, item->groupid);
}
else // Plain entries (not a reference, not grouped)
loot.AddItem(*item); // Chance is already checked, just add
}
// Now processing groups
for (LootGroups::const_iterator i = Groups.begin(); i != Groups.end(); ++i)
if (LootGroup* group = *i)
group->Process(loot, lootMode);
}
// True if template includes at least 1 quest drop entry
bool LootTemplate::HasQuestDrop(LootTemplateMap const& store, uint8 groupId) const
{
if (groupId) // Group reference
{
if (groupId > Groups.size())
return false; // Error message [should be] already printed at loading stage
if (!Groups[groupId - 1])
return false;
return Groups[groupId-1]->HasQuestDrop();
}
for (LootStoreItemList::const_iterator i = Entries.begin(); i != Entries.end(); ++i)
{
LootStoreItem* item = *i;
if (item->reference > 0) // References
{
LootTemplateMap::const_iterator Referenced = store.find(item->reference);
if (Referenced == store.end())
continue; // Error message [should be] already printed at loading stage
if (Referenced->second->HasQuestDrop(store, item->groupid))
return true;
}
else if (item->needs_quest)
return true; // quest drop found
}
// Now processing groups
for (LootGroups::const_iterator i = Groups.begin(); i != Groups.end(); ++i)
if (LootGroup* group = *i)
if (group->HasQuestDrop())
return true;
return false;
}
// True if template includes at least 1 quest drop for an active quest of the player
bool LootTemplate::HasQuestDropForPlayer(LootTemplateMap const& store, Player const* player, uint8 groupId) const
{
if (groupId) // Group reference
{
if (groupId > Groups.size())
return false; // Error message already printed at loading stage
if (!Groups[groupId - 1])
return false;
return Groups[groupId - 1]->HasQuestDropForPlayer(player);
}
// Checking non-grouped entries
for (LootStoreItemList::const_iterator i = Entries.begin(); i != Entries.end(); ++i)
{
LootStoreItem* item = *i;
if (item->reference > 0) // References processing
{
LootTemplateMap::const_iterator Referenced = store.find(item->reference);
if (Referenced == store.end())
continue; // Error message already printed at loading stage
if (Referenced->second->HasQuestDropForPlayer(store, player, item->groupid))
return true;
}
else if (player->HasQuestForItem(item->itemid))
return true; // active quest drop found
}
// Now checking groups
for (LootGroups::const_iterator i = Groups.begin(); i != Groups.end(); ++i)
if (LootGroup* group = *i)
if (group->HasQuestDropForPlayer(player))
return true;
return false;
}
// Checks integrity of the template
void LootTemplate::Verify(LootStore const& lootstore, uint32 id) const
{
// Checking group chances
for (uint32 i = 0; i < Groups.size(); ++i)
if (Groups[i])
Groups[i]->Verify(lootstore, id, i + 1);
/// @todo References validity checks
}
void LootTemplate::CheckLootRefs(LootTemplateMap const& store, LootIdSet* ref_set) const
{
for (LootStoreItemList::const_iterator ieItr = Entries.begin(); ieItr != Entries.end(); ++ieItr)
{
LootStoreItem* item = *ieItr;
if (item->reference > 0)
{
if (!LootTemplates_Reference.GetLootFor(item->reference))
LootTemplates_Reference.ReportNonExistingId(item->reference, "Reference", item->itemid);
else if (ref_set)
ref_set->erase(item->reference);
}
}
for (LootGroups::const_iterator grItr = Groups.begin(); grItr != Groups.end(); ++grItr)
if (LootGroup* group = *grItr)
group->CheckLootRefs(store, ref_set);
}
bool LootTemplate::addConditionItem(Condition* cond)
{
if (!cond || !cond->isLoaded())//should never happen, checked at loading
{
TC_LOG_ERROR("loot", "LootTemplate::addConditionItem: condition is null");
return false;
}
if (!Entries.empty())
{
for (LootStoreItemList::iterator i = Entries.begin(); i != Entries.end(); ++i)
{
if ((*i)->itemid == uint32(cond->SourceEntry))
{
(*i)->conditions.push_back(cond);
return true;
}
}
}
if (!Groups.empty())
{
for (LootGroups::iterator groupItr = Groups.begin(); groupItr != Groups.end(); ++groupItr)
{
LootGroup* group = *groupItr;
if (!group)
continue;
LootStoreItemList* itemList = group->GetExplicitlyChancedItemList();
if (!itemList->empty())
{
for (LootStoreItemList::iterator i = itemList->begin(); i != itemList->end(); ++i)
{
if ((*i)->itemid == uint32(cond->SourceEntry))
{
(*i)->conditions.push_back(cond);
return true;
}
}
}
itemList = group->GetEqualChancedItemList();
if (!itemList->empty())
{
for (LootStoreItemList::iterator i = itemList->begin(); i != itemList->end(); ++i)
{
if ((*i)->itemid == uint32(cond->SourceEntry))
{
(*i)->conditions.push_back(cond);
return true;
}
}
}
}
}
return false;
}
bool LootTemplate::isReference(uint32 id)
{
for (LootStoreItemList::const_iterator ieItr = Entries.begin(); ieItr != Entries.end(); ++ieItr)
if ((*ieItr)->itemid == id && (*ieItr)->reference > 0)
return true;
return false;//not found or not reference
}
void LoadLootTemplates_Creature()
{
TC_LOG_INFO("server.loading", "Loading creature loot templates...");
uint32 oldMSTime = getMSTime();
LootIdSet lootIdSet, lootIdSetUsed;
uint32 count = LootTemplates_Creature.LoadAndCollectLootIds(lootIdSet);
// Remove real entries and check loot existence
CreatureTemplateContainer const* ctc = sObjectMgr->GetCreatureTemplates();
for (CreatureTemplateContainer::const_iterator itr = ctc->begin(); itr != ctc->end(); ++itr)
{
if (uint32 lootid = itr->second.lootid)
{
if (lootIdSet.find(lootid) == lootIdSet.end())
LootTemplates_Creature.ReportNonExistingId(lootid, "Creature", itr->second.Entry);
else
lootIdSetUsed.insert(lootid);
}
}
for (LootIdSet::const_iterator itr = lootIdSetUsed.begin(); itr != lootIdSetUsed.end(); ++itr)
lootIdSet.erase(*itr);
// output error for any still listed (not referenced from appropriate table) ids
LootTemplates_Creature.ReportUnusedIds(lootIdSet);
if (count)
TC_LOG_INFO("server.loading", ">> Loaded %u creature loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
else
TC_LOG_ERROR("server.loading", ">> Loaded 0 creature loot templates. DB table `creature_loot_template` is empty");
}
void LoadLootTemplates_Disenchant()
{
TC_LOG_INFO("server.loading", "Loading disenchanting loot templates...");
uint32 oldMSTime = getMSTime();
LootIdSet lootIdSet, lootIdSetUsed;
uint32 count = LootTemplates_Disenchant.LoadAndCollectLootIds(lootIdSet);
for (uint32 i = 0; i < sItemDisenchantLootStore.GetNumRows(); ++i)
{
ItemDisenchantLootEntry const* disenchant = sItemDisenchantLootStore.LookupEntry(i);
if (!disenchant)
continue;
uint32 lootid = i;
if (lootIdSet.find(lootid) == lootIdSet.end())
LootTemplates_Disenchant.ReportNonExistingId(lootid);
else
lootIdSetUsed.insert(lootid);
}
for (LootIdSet::const_iterator itr = lootIdSetUsed.begin(); itr != lootIdSetUsed.end(); ++itr)
lootIdSet.erase(*itr);
// output error for any still listed (not referenced from appropriate table) ids
LootTemplates_Disenchant.ReportUnusedIds(lootIdSet);
if (count)
TC_LOG_INFO("server.loading", ">> Loaded %u disenchanting loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
else
TC_LOG_ERROR("server.loading", ">> Loaded 0 disenchanting loot templates. DB table `disenchant_loot_template` is empty");
}
void LoadLootTemplates_Fishing()
{
TC_LOG_INFO("server.loading", "Loading fishing loot templates...");
uint32 oldMSTime = getMSTime();
LootIdSet lootIdSet;
uint32 count = LootTemplates_Fishing.LoadAndCollectLootIds(lootIdSet);
// remove real entries and check existence loot
for (AreaTableEntry const* areaTable : sAreaTableStore)
if (lootIdSet.find(areaTable->ID) != lootIdSet.end())
lootIdSet.erase(areaTable->ID);
// output error for any still listed (not referenced from appropriate table) ids
LootTemplates_Fishing.ReportUnusedIds(lootIdSet);
if (count)
TC_LOG_INFO("server.loading", ">> Loaded %u fishing loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
else
TC_LOG_ERROR("server.loading", ">> Loaded 0 fishing loot templates. DB table `fishing_loot_template` is empty");
}
void LoadLootTemplates_Gameobject()
{
TC_LOG_INFO("server.loading", "Loading gameobject loot templates...");
uint32 oldMSTime = getMSTime();
LootIdSet lootIdSet, lootIdSetUsed;
uint32 count = LootTemplates_Gameobject.LoadAndCollectLootIds(lootIdSet);
// remove real entries and check existence loot
GameObjectTemplateContainer const* gotc = sObjectMgr->GetGameObjectTemplates();
for (GameObjectTemplateContainer::const_iterator itr = gotc->begin(); itr != gotc->end(); ++itr)
{
if (uint32 lootid = itr->second.GetLootId())
{
if (lootIdSet.find(lootid) == lootIdSet.end())
LootTemplates_Gameobject.ReportNonExistingId(lootid, "Gameobject", itr->second.entry);
else
lootIdSetUsed.insert(lootid);
}
}
for (LootIdSet::const_iterator itr = lootIdSetUsed.begin(); itr != lootIdSetUsed.end(); ++itr)
lootIdSet.erase(*itr);
// output error for any still listed (not referenced from appropriate table) ids
LootTemplates_Gameobject.ReportUnusedIds(lootIdSet);
if (count)
TC_LOG_INFO("server.loading", ">> Loaded %u gameobject loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
else
TC_LOG_ERROR("server.loading", ">> Loaded 0 gameobject loot templates. DB table `gameobject_loot_template` is empty");
}
void LoadLootTemplates_Item()
{
TC_LOG_INFO("server.loading", "Loading item loot templates...");
uint32 oldMSTime = getMSTime();
LootIdSet lootIdSet;
uint32 count = LootTemplates_Item.LoadAndCollectLootIds(lootIdSet);
// remove real entries and check existence loot
ItemTemplateContainer const* its = sObjectMgr->GetItemTemplateStore();
for (ItemTemplateContainer::const_iterator itr = its->begin(); itr != its->end(); ++itr)
if (lootIdSet.find(itr->second.GetId()) != lootIdSet.end() && itr->second.GetFlags() & ITEM_FLAG_HAS_LOOT)
lootIdSet.erase(itr->second.GetId());
// output error for any still listed (not referenced from appropriate table) ids
LootTemplates_Item.ReportUnusedIds(lootIdSet);
if (count)
TC_LOG_INFO("server.loading", ">> Loaded %u item loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
else
TC_LOG_ERROR("server.loading", ">> Loaded 0 item loot templates. DB table `item_loot_template` is empty");
}
void LoadLootTemplates_Milling()
{
TC_LOG_INFO("server.loading", "Loading milling loot templates...");
uint32 oldMSTime = getMSTime();
LootIdSet lootIdSet;
uint32 count = LootTemplates_Milling.LoadAndCollectLootIds(lootIdSet);
// remove real entries and check existence loot
ItemTemplateContainer const* its = sObjectMgr->GetItemTemplateStore();
for (ItemTemplateContainer::const_iterator itr = its->begin(); itr != its->end(); ++itr)
{
if (!(itr->second.GetFlags() & ITEM_FLAG_IS_MILLABLE))
continue;
if (lootIdSet.find(itr->second.GetId()) != lootIdSet.end())
lootIdSet.erase(itr->second.GetId());
}
// output error for any still listed (not referenced from appropriate table) ids
LootTemplates_Milling.ReportUnusedIds(lootIdSet);
if (count)
TC_LOG_INFO("server.loading", ">> Loaded %u milling loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
else
TC_LOG_ERROR("server.loading", ">> Loaded 0 milling loot templates. DB table `milling_loot_template` is empty");
}
void LoadLootTemplates_Pickpocketing()
{
TC_LOG_INFO("server.loading", "Loading pickpocketing loot templates...");
uint32 oldMSTime = getMSTime();
LootIdSet lootIdSet, lootIdSetUsed;
uint32 count = LootTemplates_Pickpocketing.LoadAndCollectLootIds(lootIdSet);
// Remove real entries and check loot existence
CreatureTemplateContainer const* ctc = sObjectMgr->GetCreatureTemplates();
for (CreatureTemplateContainer::const_iterator itr = ctc->begin(); itr != ctc->end(); ++itr)
{
if (uint32 lootid = itr->second.pickpocketLootId)
{
if (lootIdSet.find(lootid) == lootIdSet.end())
LootTemplates_Pickpocketing.ReportNonExistingId(lootid, "Creature", itr->second.Entry);
else
lootIdSetUsed.insert(lootid);
}
}
for (LootIdSet::const_iterator itr = lootIdSetUsed.begin(); itr != lootIdSetUsed.end(); ++itr)
lootIdSet.erase(*itr);
// output error for any still listed (not referenced from appropriate table) ids
LootTemplates_Pickpocketing.ReportUnusedIds(lootIdSet);
if (count)
TC_LOG_INFO("server.loading", ">> Loaded %u pickpocketing loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
else
TC_LOG_ERROR("server.loading", ">> Loaded 0 pickpocketing loot templates. DB table `pickpocketing_loot_template` is empty");
}
void LoadLootTemplates_Prospecting()
{
TC_LOG_INFO("server.loading", "Loading prospecting loot templates...");
uint32 oldMSTime = getMSTime();
LootIdSet lootIdSet;
uint32 count = LootTemplates_Prospecting.LoadAndCollectLootIds(lootIdSet);
// remove real entries and check existence loot
ItemTemplateContainer const* its = sObjectMgr->GetItemTemplateStore();
for (ItemTemplateContainer::const_iterator itr = its->begin(); itr != its->end(); ++itr)
{
if (!(itr->second.GetFlags() & ITEM_FLAG_IS_PROSPECTABLE))
continue;
if (lootIdSet.find(itr->second.GetId()) != lootIdSet.end())
lootIdSet.erase(itr->second.GetId());
}
// output error for any still listed (not referenced from appropriate table) ids
LootTemplates_Prospecting.ReportUnusedIds(lootIdSet);
if (count)
TC_LOG_INFO("server.loading", ">> Loaded %u prospecting loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
else
TC_LOG_ERROR("server.loading", ">> Loaded 0 prospecting loot templates. DB table `prospecting_loot_template` is empty");
}
void LoadLootTemplates_Mail()
{
TC_LOG_INFO("server.loading", "Loading mail loot templates...");
uint32 oldMSTime = getMSTime();
LootIdSet lootIdSet;
uint32 count = LootTemplates_Mail.LoadAndCollectLootIds(lootIdSet);
// remove real entries and check existence loot
for (uint32 i = 1; i < sMailTemplateStore.GetNumRows(); ++i)
if (sMailTemplateStore.LookupEntry(i))
if (lootIdSet.find(i) != lootIdSet.end())
lootIdSet.erase(i);
// output error for any still listed (not referenced from appropriate table) ids
LootTemplates_Mail.ReportUnusedIds(lootIdSet);
if (count)
TC_LOG_INFO("server.loading", ">> Loaded %u mail loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
else
TC_LOG_ERROR("server.loading", ">> Loaded 0 mail loot templates. DB table `mail_loot_template` is empty");
}
void LoadLootTemplates_Skinning()
{
TC_LOG_INFO("server.loading", "Loading skinning loot templates...");
uint32 oldMSTime = getMSTime();
LootIdSet lootIdSet, lootIdSetUsed;
uint32 count = LootTemplates_Skinning.LoadAndCollectLootIds(lootIdSet);
// remove real entries and check existence loot
CreatureTemplateContainer const* ctc = sObjectMgr->GetCreatureTemplates();
for (CreatureTemplateContainer::const_iterator itr = ctc->begin(); itr != ctc->end(); ++itr)
{
if (uint32 lootid = itr->second.SkinLootId)
{
if (lootIdSet.find(lootid) == lootIdSet.end())
LootTemplates_Skinning.ReportNonExistingId(lootid, "Creature", itr->second.Entry);
else
lootIdSetUsed.insert(lootid);
}
}
for (LootIdSet::const_iterator itr = lootIdSetUsed.begin(); itr != lootIdSetUsed.end(); ++itr)
lootIdSet.erase(*itr);
// output error for any still listed (not referenced from appropriate table) ids
LootTemplates_Skinning.ReportUnusedIds(lootIdSet);
if (count)
TC_LOG_INFO("server.loading", ">> Loaded %u skinning loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
else
TC_LOG_ERROR("server.loading", ">> Loaded 0 skinning loot templates. DB table `skinning_loot_template` is empty");
}
void LoadLootTemplates_Spell()
{
// TODO: change this to use MiscValue from spell effect as id instead of spell id
TC_LOG_INFO("server.loading", "Loading spell loot templates...");
uint32 oldMSTime = getMSTime();
LootIdSet lootIdSet;
uint32 count = LootTemplates_Spell.LoadAndCollectLootIds(lootIdSet);
// remove real entries and check existence loot
for (uint32 spell_id = 1; spell_id < sSpellMgr->GetSpellInfoStoreSize(); ++spell_id)
{
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell_id);
if (!spellInfo)
continue;
// possible cases
if (!spellInfo->IsLootCrafting())
continue;
if (lootIdSet.find(spell_id) == lootIdSet.end())
{
// not report about not trainable spells (optionally supported by DB)
// ignore 61756 (Northrend Inscription Research (FAST QA VERSION) for example
if (!spellInfo->HasAttribute(SPELL_ATTR0_NOT_SHAPESHIFT) || (spellInfo->HasAttribute(SPELL_ATTR0_TRADESPELL)))
{
LootTemplates_Spell.ReportNonExistingId(spell_id, "Spell", spellInfo->Id);
}
}
else
lootIdSet.erase(spell_id);
}
// output error for any still listed (not referenced from appropriate table) ids
LootTemplates_Spell.ReportUnusedIds(lootIdSet);
if (count)
TC_LOG_INFO("server.loading", ">> Loaded %u spell loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
else
TC_LOG_ERROR("server.loading", ">> Loaded 0 spell loot templates. DB table `spell_loot_template` is empty");
}
void LoadLootTemplates_Reference()
{
TC_LOG_INFO("server.loading", "Loading reference loot templates...");
uint32 oldMSTime = getMSTime();
LootIdSet lootIdSet;
LootTemplates_Reference.LoadAndCollectLootIds(lootIdSet);
// check references and remove used
LootTemplates_Creature.CheckLootRefs(&lootIdSet);
LootTemplates_Fishing.CheckLootRefs(&lootIdSet);
LootTemplates_Gameobject.CheckLootRefs(&lootIdSet);
LootTemplates_Item.CheckLootRefs(&lootIdSet);
LootTemplates_Milling.CheckLootRefs(&lootIdSet);
LootTemplates_Pickpocketing.CheckLootRefs(&lootIdSet);
LootTemplates_Skinning.CheckLootRefs(&lootIdSet);
LootTemplates_Disenchant.CheckLootRefs(&lootIdSet);
LootTemplates_Prospecting.CheckLootRefs(&lootIdSet);
LootTemplates_Mail.CheckLootRefs(&lootIdSet);
LootTemplates_Reference.CheckLootRefs(&lootIdSet);
// output error for any still listed ids (not referenced from any loot table)
LootTemplates_Reference.ReportUnusedIds(lootIdSet);
TC_LOG_INFO("server.loading", ">> Loaded refence loot templates in %u ms", GetMSTimeDiffToNow(oldMSTime));
}
void LoadLootTables()
{
LoadLootTemplates_Creature();
LoadLootTemplates_Fishing();
LoadLootTemplates_Gameobject();
LoadLootTemplates_Item();
LoadLootTemplates_Mail();
LoadLootTemplates_Milling();
LoadLootTemplates_Pickpocketing();
LoadLootTemplates_Skinning();
LoadLootTemplates_Disenchant();
LoadLootTemplates_Prospecting();
LoadLootTemplates_Spell();
LoadLootTemplates_Reference();
}
void AELootResult::Add(Item* item, uint8 count, LootType lootType)
{
auto itr = _byItem.find(item);
if (itr != _byItem.end())
_byOrder[itr->second].count += count;
else
{
_byItem[item] = _byOrder.size();
ResultValue value;
value.item = item;
value.count = count;
value.lootType = lootType;
_byOrder.push_back(value);
}
}
AELootResult::OrderedStorage::const_iterator AELootResult::begin() const
{
return _byOrder.begin();
}
AELootResult::OrderedStorage::const_iterator AELootResult::end() const
{
return _byOrder.end();
}