mirror of
https://github.com/mangosfour/server.git
synced 2025-12-12 10:37:03 +00:00
1732 lines
59 KiB
C++
1732 lines
59 KiB
C++
/**
|
|
* MaNGOS is a full featured server for World of Warcraft, supporting
|
|
* the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8
|
|
*
|
|
* Copyright (C) 2005-2020 MaNGOS <https://getmangos.eu>
|
|
*
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* World of Warcraft, and all World of Warcraft or Warcraft art, images,
|
|
* and lore are copyrighted by Blizzard Entertainment, Inc.
|
|
*/
|
|
|
|
#include "LootMgr.h"
|
|
#include "Log.h"
|
|
#include "ObjectMgr.h"
|
|
#include "ProgressBar.h"
|
|
#include "World.h"
|
|
#include "Util.h"
|
|
#include "SharedDefines.h"
|
|
#include "SpellMgr.h"
|
|
#include "DBCStores.h"
|
|
#include "SQLStorages.h"
|
|
|
|
static eConfigFloatValues const qualityToRate[MAX_ITEM_QUALITY] =
|
|
{
|
|
CONFIG_FLOAT_RATE_DROP_ITEM_POOR, // ITEM_QUALITY_POOR
|
|
CONFIG_FLOAT_RATE_DROP_ITEM_NORMAL, // ITEM_QUALITY_NORMAL
|
|
CONFIG_FLOAT_RATE_DROP_ITEM_UNCOMMON, // ITEM_QUALITY_UNCOMMON
|
|
CONFIG_FLOAT_RATE_DROP_ITEM_RARE, // ITEM_QUALITY_RARE
|
|
CONFIG_FLOAT_RATE_DROP_ITEM_EPIC, // ITEM_QUALITY_EPIC
|
|
CONFIG_FLOAT_RATE_DROP_ITEM_LEGENDARY, // ITEM_QUALITY_LEGENDARY
|
|
CONFIG_FLOAT_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 Lootid", true);
|
|
LootStore LootTemplates_Item("item_loot_template", "item entry with ITEM_FLAG_LOOTABLE", 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);
|
|
|
|
class LootTemplate::LootGroup // A set of loot definitions for items (refs are not allowed)
|
|
{
|
|
public:
|
|
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
|
|
// The same for active quests of the player
|
|
void Process(Loot& loot) 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, uint32 group_id) const;
|
|
void CollectLootIds(LootIdSet& set) const;
|
|
void CheckLootRefs(LootIdSet* ref_set) const;
|
|
private:
|
|
LootStoreItemList ExplicitlyChanced; // Entries with chances defined in DB
|
|
LootStoreItemList EqualChanced; // Zero chances - every entry takes the same chance
|
|
|
|
LootStoreItem const* Roll() const; // Rolls an item from the group, returns NULL if all miss their chances
|
|
};
|
|
|
|
// 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
|
|
void LootStore::LoadLootTable()
|
|
{
|
|
LootTemplateMap::const_iterator tab;
|
|
uint32 count = 0;
|
|
|
|
// Clearing store (for reloading case)
|
|
Clear();
|
|
|
|
// 0 1 2 3 4 5 6
|
|
QueryResult* result = WorldDatabase.PQuery("SELECT entry, item, ChanceOrQuestChance, groupid, mincountOrRef, maxcount, condition_id FROM %s", GetName());
|
|
|
|
if (result)
|
|
{
|
|
BarGoLink bar(result->GetRowCount());
|
|
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
bar.step();
|
|
|
|
uint32 entry = fields[0].GetUInt32();
|
|
uint32 item = abs(fields[1].GetInt32());
|
|
uint8 type = fields[1].GetInt32() >= 0 ? LOOT_ITEM_TYPE_ITEM : LOOT_ITEM_TYPE_CURRENCY;
|
|
float chanceOrQuestChance = fields[2].GetFloat();
|
|
uint8 group = fields[3].GetUInt8();
|
|
int32 mincountOrRef = fields[4].GetInt32();
|
|
uint32 maxcount = fields[5].GetUInt32();
|
|
uint16 conditionId = fields[6].GetUInt16();
|
|
|
|
if (type == LOOT_ITEM_TYPE_ITEM && maxcount > std::numeric_limits<uint8>::max())
|
|
{
|
|
sLog.outErrorDb("Table '%s' entry %u item %u: maxcount value (%u) to large. must be less than %u - skipped", GetName(), entry, item, maxcount, uint32(std::numeric_limits<uint8>::max()));
|
|
continue; // error already printed to log/console.
|
|
}
|
|
|
|
if (conditionId)
|
|
{
|
|
const PlayerCondition* condition = sConditionStorage.LookupEntry<PlayerCondition>(conditionId);
|
|
if (!condition)
|
|
{
|
|
sLog.outErrorDb("Table `%s` for entry %u, item %u has condition_id %u that does not exist in `conditions`, ignoring", GetName(), entry, item, uint32(conditionId));
|
|
continue;
|
|
}
|
|
|
|
if (mincountOrRef < 0 && !PlayerCondition::CanBeUsedWithoutPlayer(conditionId))
|
|
{
|
|
sLog.outErrorDb("Table '%s' entry %u mincountOrRef %i < 0 and has condition %u that requires a player and is not supported, skipped", GetName(), entry, mincountOrRef, uint32(conditionId));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
LootStoreItem storeitem = LootStoreItem(item, type, chanceOrQuestChance, group, conditionId, mincountOrRef, maxcount);
|
|
|
|
if (!storeitem.IsValid(*this, entry)) // Validity checks
|
|
{
|
|
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 existing or just created <entry, LootTemplate>
|
|
|
|
// Adds current row to the template
|
|
tab->second->AddEntry(storeitem);
|
|
++count;
|
|
}
|
|
while (result->NextRow());
|
|
|
|
delete result;
|
|
|
|
Verify(); // Checks validity of the loot store
|
|
|
|
sLog.outString(">> Loaded %u loot definitions (" SIZEFMTD " templates) from table %s", count, m_LootTemplates.size(), GetName());
|
|
sLog.outString();
|
|
}
|
|
else
|
|
{
|
|
sLog.outString();
|
|
sLog.outErrorDb(">> Loaded 0 loot definitions. DB table `%s` is empty.", GetName());
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void LootStore::LoadAndCollectLootIds(LootIdSet& ids_set)
|
|
{
|
|
LoadLootTable();
|
|
|
|
for (LootTemplateMap::const_iterator tab = m_LootTemplates.begin(); tab != m_LootTemplates.end(); ++tab)
|
|
{ ids_set.insert(tab->first); }
|
|
}
|
|
|
|
void LootStore::CheckLootRefs(LootIdSet* ref_set) const
|
|
{
|
|
for (LootTemplateMap::const_iterator ltItr = m_LootTemplates.begin(); ltItr != m_LootTemplates.end(); ++ltItr)
|
|
{ ltItr->second->CheckLootRefs(ref_set); }
|
|
}
|
|
|
|
void LootStore::ReportUnusedIds(LootIdSet const& ids_set) const
|
|
{
|
|
// all still listed ids isn't referenced
|
|
if (!ids_set.empty())
|
|
{
|
|
for (LootIdSet::const_iterator itr = ids_set.begin(); itr != ids_set.end(); ++itr)
|
|
sLog.outErrorDb("Table '%s' entry %d isn't %s and not referenced from loot, and then useless.", GetName(), *itr, GetEntryName());
|
|
sLog.outString();
|
|
}
|
|
}
|
|
|
|
void LootStore::ReportNotExistedId(uint32 id) const
|
|
{
|
|
sLog.outErrorDb("Table '%s' entry %d (%s) not exist but used as loot id in DB.", GetName(), id, GetEntryName());
|
|
}
|
|
|
|
//
|
|
// --------- 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 (mincountOrRef < 0) // reference case
|
|
{
|
|
return roll_chance_f(chance * (rate ? sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_ITEM_REFERENCED) : 1.0f));
|
|
}
|
|
|
|
if (type == LOOTITEM_TYPE_CURRENCY)
|
|
{
|
|
return roll_chance_f(chance * (rate ? sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_CURRENCY) : 1.0f));
|
|
}
|
|
else
|
|
{
|
|
if (needs_quest)
|
|
{
|
|
return roll_chance_f(chance * (rate ? sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_ITEM_QUEST) : 1.0f));
|
|
}
|
|
|
|
ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(itemid);
|
|
|
|
float qualityModifier = pProto && rate ? sWorld.getConfig(qualityToRate[pProto->Quality]) : 1.0f;
|
|
|
|
return roll_chance_f(chance * qualityModifier);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Checks correctness of values
|
|
bool LootStoreItem::IsValid(LootStore const& store, uint32 entry) const
|
|
{
|
|
if (group >= 1 << 7) // it stored in 7 bit field
|
|
{
|
|
sLog.outErrorDb("Table '%s' entry %d item %d: group (%u) must be less %u - skipped", store.GetName(), entry, itemid, group, 1 << 7);
|
|
return false;
|
|
}
|
|
|
|
if (group && type == LOOT_ITEM_TYPE_CURRENCY)
|
|
{
|
|
sLog.outErrorDb("Table '%s' entry %d currency %d: has group %u, but currencies must not have group - skipped", store.GetName(), entry, itemid, group);
|
|
return false;
|
|
}
|
|
|
|
if (mincountOrRef == 0)
|
|
{
|
|
sLog.outErrorDb("Table '%s' entry %d item %d: wrong mincountOrRef (%d) - skipped", store.GetName(), entry, itemid, mincountOrRef);
|
|
return false;
|
|
}
|
|
|
|
if (mincountOrRef > 0) // item (quest or non-quest) entry, maybe grouped
|
|
{
|
|
if (type == LOOT_ITEM_TYPE_ITEM)
|
|
{
|
|
ItemPrototype const* proto = ObjectMgr::GetItemPrototype(itemid);
|
|
if (!proto)
|
|
{
|
|
sLog.outErrorDb("Table '%s' entry %d item %d: item entry not listed in `item_template` - skipped", store.GetName(), entry, itemid);
|
|
return false;
|
|
}
|
|
}
|
|
else if (type == LOOT_ITEM_TYPE_CURRENCY)
|
|
{
|
|
CurrencyTypesEntry const* currency = sCurrencyTypesStore.LookupEntry(itemid);
|
|
if (!currency)
|
|
{
|
|
sLog.outErrorDb("Table '%s' entry %d: currency entry %u not exists - skipped", store.GetName(), entry, itemid);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sLog.outErrorDb("Table '%s' entry %d: has unknown item %u with type %u - skipped", store.GetName(), entry, itemid, type);
|
|
return false;
|
|
}
|
|
|
|
if (chance == 0 && group == 0) // Zero chance is allowed for grouped entries only
|
|
{
|
|
sLog.outErrorDb("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
|
|
{
|
|
sLog.outErrorDb("Table '%s' entry %d item %d: low chance (%f) - skipped", store.GetName(), entry, itemid, chance);
|
|
return false;
|
|
}
|
|
|
|
if (maxcount < (uint32)mincountOrRef) // wrong max count
|
|
{
|
|
sLog.outErrorDb("Table '%s' entry %d item %d: max count (%u) less that min count (%i) - skipped", store.GetName(), entry, itemid, uint32(maxcount), mincountOrRef);
|
|
return false;
|
|
}
|
|
}
|
|
else // mincountOrRef < 0
|
|
{
|
|
if (needs_quest)
|
|
{
|
|
sLog.outErrorDb("Table '%s' entry %d item %d: negative chance is given for a reference, skipped", store.GetName(), entry, itemid);
|
|
return false;
|
|
}
|
|
else if (chance == 0) // no chance for the reference
|
|
{
|
|
sLog.outErrorDb("Table '%s' entry %d item %d: zero chance is given for a reference, reference will never be used, 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;
|
|
type = li.type;
|
|
conditionId = li.conditionId;
|
|
currency = type == LOOT_ITEM_TYPE_CURRENCY;
|
|
count = urand(li.mincountOrRef, li.maxcount); // constructor called for mincountOrRef > 0 only
|
|
|
|
is_looted = 0;
|
|
is_blocked = 0;
|
|
is_underthreshold = 0;
|
|
is_counted = 0;
|
|
|
|
if (currency)
|
|
{
|
|
freeforall = false;
|
|
needs_quest = false;
|
|
randomSuffix = 0;
|
|
randomPropertyId = 0;
|
|
count = uint32(count * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_CURRENCY_AMOUNT));
|
|
}
|
|
else
|
|
{
|
|
ItemPrototype const* proto = ObjectMgr::GetItemPrototype(itemid);
|
|
freeforall = proto && (proto->Flags & ITEM_FLAG_PARTY_LOOT);
|
|
needs_quest = li.needs_quest;
|
|
randomSuffix = GenerateEnchSuffixFactor(itemid);
|
|
randomPropertyId = Item::GenerateItemRandomPropertyId(itemid);
|
|
}
|
|
}
|
|
|
|
LootItem::LootItem(uint32 itemid_, uint8 type_, uint32 count_, uint32 randomSuffix_, int32 randomPropertyId_)
|
|
{
|
|
itemid = itemid_;
|
|
type = type_;
|
|
conditionId = 0;
|
|
count = count_;
|
|
randomSuffix = randomSuffix_;
|
|
randomPropertyId = randomPropertyId_;
|
|
is_looted = 0;
|
|
is_blocked = 0;
|
|
is_underthreshold = 0;
|
|
is_counted = 0;
|
|
currency = type == LOOT_ITEM_TYPE_CURRENCY;
|
|
needs_quest = false;
|
|
|
|
if (currency)
|
|
freeforall = false;
|
|
else
|
|
{
|
|
ItemPrototype const* proto = ObjectMgr::GetItemPrototype(itemid);
|
|
freeforall = proto && (proto->Flags & ITEM_FLAG_PARTY_LOOT);
|
|
}
|
|
}
|
|
|
|
// Basic checks for player/item compatibility - if false no chance to see the item in the loot
|
|
bool LootItem::AllowedForPlayer(Player const* player, WorldObject const* lootTarget) const
|
|
{
|
|
// DB conditions check
|
|
if (conditionId && !sObjectMgr.IsPlayerMeetToCondition(conditionId, player, player->GetMap(), lootTarget, CONDITION_FROM_LOOT))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (type == LOOT_ITEM_TYPE_ITEM)
|
|
{
|
|
ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(itemid);
|
|
if (!pProto)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// not show loot for not own team
|
|
if ((pProto->Flags2 & ITEM_FLAG2_HORDE_ONLY) && player->GetTeam() != HORDE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ((pProto->Flags2 & ITEM_FLAG2_ALLIANCE_ONLY) && player->GetTeam() != ALLIANCE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (needs_quest)
|
|
{
|
|
// Checking quests for quest-only drop (check only quests requirements in this case)
|
|
if (!player->HasQuestForItem(itemid))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Not quest only drop (check quest starting items for already accepted non-repeatable quests)
|
|
if (pProto->StartQuest && player->GetQuestStatus(pProto->StartQuest) != QUEST_STATUS_NONE && !player->HasQuestForItem(itemid))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if (type == LOOT_ITEM_TYPE_CURRENCY)
|
|
{
|
|
CurrencyTypesEntry const * currency = sCurrencyTypesStore.LookupEntry(itemid);
|
|
if (!itemid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!player->isGameMaster())
|
|
{
|
|
if (currency->Category == CURRENCY_CATEGORY_META)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (currency->Category == CURRENCY_CATEGORY_ARCHAEOLOGY && !player->HasSkill(SKILL_ARCHAEOLOGY))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
LootSlotType LootItem::GetSlotTypeForSharedLoot(PermissionTypes permission, Player* viewer, WorldObject const* lootTarget, bool condition_ok /*= false*/) const
|
|
{
|
|
// ignore currencies, looted items, FFA (each player get own copy) and not allowed items
|
|
if (currency || is_looted || freeforall || (conditionId && !condition_ok) || !AllowedForPlayer(viewer, lootTarget))
|
|
{
|
|
return MAX_LOOT_SLOT_TYPE;
|
|
}
|
|
|
|
switch (permission)
|
|
{
|
|
case ALL_PERMISSION:
|
|
return LOOT_SLOT_NORMAL;
|
|
case GROUP_PERMISSION:
|
|
return (is_blocked || is_underthreshold) ? LOOT_SLOT_NORMAL : LOOT_SLOT_VIEW;
|
|
case MASTER_PERMISSION:
|
|
return !is_underthreshold ? LOOT_SLOT_MASTER : LOOT_SLOT_NORMAL;
|
|
case OWNER_PERMISSION:
|
|
return LOOT_SLOT_OWNER;
|
|
default:
|
|
return MAX_LOOT_SLOT_TYPE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// --------- Loot ---------
|
|
//
|
|
|
|
// Inserts the item into the loot (called by LootTemplate processors)
|
|
void Loot::AddItem(LootStoreItem const& item)
|
|
{
|
|
if (item.needs_quest) // Quest drop
|
|
{
|
|
if (m_questItems.size() < MAX_NR_QUEST_ITEMS)
|
|
{
|
|
m_questItems.push_back(LootItem(item));
|
|
}
|
|
}
|
|
else if (items.size() < MAX_NR_LOOT_ITEMS) // Non-quest drop
|
|
{
|
|
items.push_back(LootItem(item));
|
|
|
|
// non-conditional one-player only items are counted here,
|
|
// currencies are counter in FillCurrencyLoot,
|
|
// free for all items are counted in FillFFALoot(),
|
|
// non-ffa conditionals are counted in FillNonQuestNonFFANonCurrencyConditionalLoot()
|
|
if (!item.conditionId && item.type == LOOT_ITEM_TYPE_ITEM)
|
|
{
|
|
ItemPrototype const* proto = ObjectMgr::GetItemPrototype(item.itemid);
|
|
if (!proto || !(proto->Flags & ITEM_FLAG_PARTY_LOOT))
|
|
{
|
|
++unlootedCount;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calls processor of corresponding LootTemplate (which handles everything including references)
|
|
bool Loot::FillLoot(uint32 loot_id, LootStore const& store, Player* loot_owner, bool personal, bool noEmptyError)
|
|
{
|
|
// Must be provided
|
|
if (!loot_owner)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
LootTemplate const* tab = store.GetLootFor(loot_id);
|
|
|
|
if (!tab)
|
|
{
|
|
if (!noEmptyError)
|
|
{
|
|
sLog.outErrorDb("Table '%s' loot id #%u used but it doesn't have records.", store.GetName(), loot_id);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
items.reserve(MAX_NR_LOOT_ITEMS);
|
|
m_questItems.reserve(MAX_NR_QUEST_ITEMS);
|
|
|
|
tab->Process(*this, store, store.IsRatesAllowed()); // Processing is done there, callback via Loot::AddItem()
|
|
|
|
// Setting access rights for group loot case
|
|
Group* pGroup = loot_owner->GetGroup();
|
|
if (!personal && pGroup)
|
|
{
|
|
for (GroupReference* itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next())
|
|
if (Player* pl = itr->getSource())
|
|
{
|
|
FillNotNormalLootFor(pl);
|
|
}
|
|
}
|
|
// ... for personal loot
|
|
else
|
|
{
|
|
FillNotNormalLootFor(loot_owner);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Loot::FillNotNormalLootFor(Player* pl)
|
|
{
|
|
uint32 plguid = pl->GetGUIDLow();
|
|
|
|
QuestItemMap::const_iterator qmapitr = m_playerCurrencies.find(plguid);
|
|
if (qmapitr == m_playerCurrencies.end())
|
|
FillCurrencyLoot(pl);
|
|
|
|
qmapitr = m_playerQuestItems.find(plguid);
|
|
if (qmapitr == m_playerQuestItems.end())
|
|
{
|
|
FillQuestLoot(pl);
|
|
}
|
|
|
|
qmapitr = m_playerFFAItems.find(plguid);
|
|
if (qmapitr == m_playerFFAItems.end())
|
|
{
|
|
FillFFALoot(pl);
|
|
}
|
|
|
|
qmapitr = m_playerNonQuestNonFFANonCurrencyConditionalItems.find(plguid);
|
|
if (qmapitr == m_playerNonQuestNonFFANonCurrencyConditionalItems.end())
|
|
FillNonQuestNonFFANonCurrencyConditionalLoot(pl);
|
|
}
|
|
|
|
QuestItemList* Loot::FillCurrencyLoot(Player* player)
|
|
{
|
|
QuestItemList* ql = new QuestItemList();
|
|
|
|
for (uint8 i = 0; i < items.size(); ++i)
|
|
{
|
|
LootItem& item = items[i];
|
|
if (!item.is_looted && item.currency && item.AllowedForPlayer(player, m_lootTarget))
|
|
{
|
|
ql->push_back(QuestItem(i));
|
|
++unlootedCount;
|
|
}
|
|
}
|
|
if (ql->empty())
|
|
{
|
|
delete ql;
|
|
return NULL;
|
|
}
|
|
|
|
m_playerCurrencies[player->GetGUIDLow()] = ql;
|
|
return ql;
|
|
}
|
|
|
|
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, m_lootTarget))
|
|
{
|
|
ql->push_back(QuestItem(i));
|
|
++unlootedCount;
|
|
}
|
|
}
|
|
if (ql->empty())
|
|
{
|
|
delete ql;
|
|
return NULL;
|
|
}
|
|
|
|
m_playerFFAItems[player->GetGUIDLow()] = 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 < m_questItems.size(); ++i)
|
|
{
|
|
LootItem& item = m_questItems[i];
|
|
if (!item.is_looted && item.AllowedForPlayer(player, m_lootTarget))
|
|
{
|
|
ql->push_back(QuestItem(i));
|
|
|
|
// questitems get blocked when they first apper 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;
|
|
}
|
|
|
|
item.is_blocked = true;
|
|
|
|
if (items.size() + ql->size() == MAX_NR_LOOT_ITEMS)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (ql->empty())
|
|
{
|
|
delete ql;
|
|
return NULL;
|
|
}
|
|
|
|
m_playerQuestItems[player->GetGUIDLow()] = ql;
|
|
return ql;
|
|
}
|
|
|
|
QuestItemList* Loot::FillNonQuestNonFFANonCurrencyConditionalLoot(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.currency && item.conditionId && item.AllowedForPlayer(player, m_lootTarget))
|
|
{
|
|
ql->push_back(QuestItem(i));
|
|
if (!item.is_counted)
|
|
{
|
|
++unlootedCount;
|
|
item.is_counted = true;
|
|
}
|
|
}
|
|
}
|
|
if (ql->empty())
|
|
{
|
|
delete ql;
|
|
return NULL;
|
|
}
|
|
|
|
m_playerNonQuestNonFFANonCurrencyConditionalItems[player->GetGUIDLow()] = 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 = m_playersLooting.begin(); i != m_playersLooting.end(); i = i_next)
|
|
{
|
|
i_next = i;
|
|
++i_next;
|
|
if (Player* pl = ObjectAccessor::FindPlayer(*i))
|
|
{
|
|
pl->SendNotifyLootItemRemoved(lootIndex);
|
|
}
|
|
else
|
|
{
|
|
m_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 = m_playersLooting.begin(); i != m_playersLooting.end(); i = i_next)
|
|
{
|
|
i_next = i;
|
|
++i_next;
|
|
if (Player* pl = ObjectAccessor::FindPlayer(*i))
|
|
{
|
|
pl->SendNotifyLootMoneyRemoved();
|
|
}
|
|
else
|
|
{
|
|
m_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 isnt called often
|
|
|
|
GuidSet::iterator i_next;
|
|
for (GuidSet::iterator i = m_playersLooting.begin(); i != m_playersLooting.end(); i = i_next)
|
|
{
|
|
i_next = i;
|
|
++i_next;
|
|
if (Player* pl = ObjectAccessor::FindPlayer(*i))
|
|
{
|
|
QuestItemMap::const_iterator pq = m_playerQuestItems.find(pl->GetGUIDLow());
|
|
if (pq != m_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())
|
|
{
|
|
pl->SendNotifyLootItemRemoved(items.size() + j);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_playersLooting.erase(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Loot::generateMoneyLoot(uint32 minAmount, uint32 maxAmount)
|
|
{
|
|
if (maxAmount > 0)
|
|
{
|
|
if (maxAmount <= minAmount)
|
|
{
|
|
gold = uint32(maxAmount * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY));
|
|
}
|
|
else if ((maxAmount - minAmount) < 32700)
|
|
{
|
|
gold = uint32(urand(minAmount, maxAmount) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY));
|
|
}
|
|
else
|
|
{
|
|
gold = uint32(urand(minAmount >> 8, maxAmount >> 8) * sWorld.getConfig(CONFIG_FLOAT_RATE_DROP_MONEY)) << 8;
|
|
}
|
|
}
|
|
}
|
|
|
|
LootItem* Loot::LootItemInSlot(uint32 lootSlot, Player* player, QuestItem** qitem, QuestItem** ffaitem, QuestItem** conditem, QuestItem** currency)
|
|
{
|
|
LootItem* item = NULL;
|
|
bool is_looted = true;
|
|
if (lootSlot >= items.size())
|
|
{
|
|
uint32 questSlot = lootSlot - items.size();
|
|
QuestItemMap::const_iterator itr = m_playerQuestItems.find(player->GetGUIDLow());
|
|
if (itr != m_playerQuestItems.end() && questSlot < itr->second->size())
|
|
{
|
|
QuestItem* qitem2 = &itr->second->at(questSlot);
|
|
if (qitem)
|
|
{
|
|
*qitem = qitem2;
|
|
}
|
|
item = &m_questItems[qitem2->index];
|
|
is_looted = qitem2->is_looted;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
item = &items[lootSlot];
|
|
is_looted = item->is_looted;
|
|
if (item->currency)
|
|
{
|
|
QuestItemMap::const_iterator itr = m_playerCurrencies.find(player->GetGUIDLow());
|
|
if (itr != m_playerCurrencies.end())
|
|
{
|
|
for (QuestItemList::const_iterator iter = itr->second->begin(); iter != itr->second->end(); ++iter)
|
|
{
|
|
if (iter->index == lootSlot)
|
|
{
|
|
QuestItem* currency2 = (QuestItem*) & (*iter);
|
|
if (currency)
|
|
*currency = currency2;
|
|
is_looted = currency2->is_looted;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (item->freeforall)
|
|
{
|
|
QuestItemMap::const_iterator itr = m_playerFFAItems.find(player->GetGUIDLow());
|
|
if (itr != m_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->conditionId)
|
|
{
|
|
QuestItemMap::const_iterator itr = m_playerNonQuestNonFFANonCurrencyConditionalItems.find(player->GetGUIDLow());
|
|
if (itr != m_playerNonQuestNonFFANonCurrencyConditionalItems.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 = m_playerQuestItems.find(player->GetGUIDLow());
|
|
return items.size() + (itr != m_playerQuestItems.end() ? itr->second->size() : 0);
|
|
}
|
|
|
|
ByteBuffer& operator<<(ByteBuffer& b, LootItem const& li)
|
|
{
|
|
if (li.type == LOOT_ITEM_TYPE_ITEM)
|
|
{
|
|
b << uint32(li.itemid);
|
|
b << uint32(li.count); // nr of items of this type
|
|
b << uint32(ObjectMgr::GetItemPrototype(li.itemid)->DisplayInfoID);
|
|
b << uint32(li.randomSuffix);
|
|
b << uint32(li.randomPropertyId);
|
|
}
|
|
else if (li.type == LOOT_ITEM_TYPE_CURRENCY)
|
|
{
|
|
b << uint32(li.itemid);
|
|
b << uint32(li.count);
|
|
}
|
|
// b << uint8(0); // slot type - will send after this function call
|
|
return b;
|
|
}
|
|
|
|
ByteBuffer& operator<<(ByteBuffer& b, LootView const& lv)
|
|
{
|
|
if (lv.permission == NONE_PERMISSION)
|
|
{
|
|
b << uint32(0); // gold
|
|
b << uint8(0); // item count
|
|
b << uint8(0); // currency count
|
|
return b;
|
|
}
|
|
|
|
Loot& l = lv.loot;
|
|
|
|
uint8 itemsShown = 0;
|
|
uint8 currenciesShown = 0;
|
|
|
|
// gold
|
|
b << uint32(l.gold);
|
|
|
|
size_t count_pos = b.wpos(); // pos of item count byte
|
|
b << uint8(0); // item count placeholder
|
|
size_t currency_count_pos = b.wpos(); // pos of currency count byte
|
|
b << uint8(0); // currency count placeholder
|
|
|
|
for (uint8 i = 0; i < l.items.size(); ++i)
|
|
{
|
|
LootSlotType slot_type = l.items[i].GetSlotTypeForSharedLoot(lv.permission, lv.viewer, l.GetLootTarget());
|
|
if (slot_type >= MAX_LOOT_SLOT_TYPE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
b << uint8(i) << l.items[i];
|
|
b << uint8(slot_type); // 0 - get 1 - look only 2 - master selection
|
|
++itemsShown;
|
|
}
|
|
|
|
QuestItemMap const& lootPlayerNonQuestNonFFAConditionalItems = l.GetPlayerNonQuestNonFFANonCurrencyConditionalItems();
|
|
QuestItemMap::const_iterator nn_itr = lootPlayerNonQuestNonFFAConditionalItems.find(lv.viewer->GetGUIDLow());
|
|
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& item = l.items[ci->index];
|
|
|
|
LootSlotType slot_type = item.GetSlotTypeForSharedLoot(lv.permission, lv.viewer, l.GetLootTarget(), !ci->is_looted);
|
|
if (slot_type >= MAX_LOOT_SLOT_TYPE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
b << uint8(ci->index) << item;
|
|
b << uint8(slot_type); // allow loot
|
|
++itemsShown;
|
|
}
|
|
}
|
|
|
|
// in next cases used same slot type for all items
|
|
LootSlotType slot_type = lv.permission == OWNER_PERMISSION ? LOOT_SLOT_OWNER : LOOT_SLOT_NORMAL;
|
|
|
|
QuestItemMap const& lootPlayerQuestItems = l.GetPlayerQuestItems();
|
|
QuestItemMap::const_iterator q_itr = lootPlayerQuestItems.find(lv.viewer->GetGUIDLow());
|
|
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& item = l.m_questItems[qi->index];
|
|
if (!qi->is_looted && !item.is_looted)
|
|
{
|
|
b << uint8(l.items.size() + (qi - q_list->begin()));
|
|
b << item;
|
|
b << uint8(slot_type); // allow loot
|
|
++itemsShown;
|
|
}
|
|
}
|
|
}
|
|
|
|
QuestItemMap const& lootPlayerFFAItems = l.GetPlayerFFAItems();
|
|
QuestItemMap::const_iterator ffa_itr = lootPlayerFFAItems.find(lv.viewer->GetGUIDLow());
|
|
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& item = l.items[fi->index];
|
|
if (!fi->is_looted && !item.is_looted)
|
|
{
|
|
b << uint8(fi->index) << item;
|
|
b << uint8(slot_type); // allow loot
|
|
++itemsShown;
|
|
}
|
|
}
|
|
}
|
|
|
|
QuestItemMap const& lootPlayerCurrencies = l.GetPlayerCurrencies();
|
|
QuestItemMap::const_iterator currency_itr = lootPlayerCurrencies.find(lv.viewer->GetGUIDLow());
|
|
if (currency_itr != lootPlayerCurrencies.end())
|
|
{
|
|
QuestItemList* currency_list = currency_itr->second;
|
|
for (QuestItemList::const_iterator ci = currency_list->begin() ; ci != currency_list->end(); ++ci)
|
|
{
|
|
LootItem& item = l.items[ci->index];
|
|
if (!ci->is_looted && !item.is_looted)
|
|
{
|
|
b << uint8(ci->index) << item;
|
|
++currenciesShown;
|
|
}
|
|
}
|
|
}
|
|
|
|
// update number of items and currencies shown
|
|
b.put<uint8>(count_pos, itemsShown);
|
|
b.put<uint8>(currency_count_pos, currenciesShown);
|
|
|
|
return b;
|
|
}
|
|
|
|
//
|
|
// --------- LootTemplate::LootGroup ---------
|
|
//
|
|
|
|
// 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() const
|
|
{
|
|
if (!ExplicitlyChanced.empty()) // First explicitly chanced entries are checked
|
|
{
|
|
float Roll = rand_chance_f();
|
|
|
|
for (uint32 i = 0; i < ExplicitlyChanced.size(); ++i) // check each explicitly chanced entry in the template and modify its chance based on quality.
|
|
{
|
|
if (ExplicitlyChanced[i].chance >= 100.0f)
|
|
{
|
|
return &ExplicitlyChanced[i];
|
|
}
|
|
|
|
Roll -= ExplicitlyChanced[i].chance;
|
|
if (Roll < 0)
|
|
{
|
|
return &ExplicitlyChanced[i];
|
|
}
|
|
}
|
|
}
|
|
if (!EqualChanced.empty()) // If nothing selected yet - an item is taken from equal-chanced part
|
|
{
|
|
return &EqualChanced[irand(0, EqualChanced.size() - 1)];
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Rolls an item from the group (if any takes its chance) and adds the item to the loot
|
|
void LootTemplate::LootGroup::Process(Loot& loot) const
|
|
{
|
|
LootStoreItem const* item = Roll();
|
|
if (item != NULL)
|
|
{
|
|
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, uint32 group_id) const
|
|
{
|
|
float chance = RawTotalChance();
|
|
if (chance > 101.0f) // TODO: replace with 100% when DBs will be ready
|
|
{
|
|
sLog.outErrorDb("Table '%s' entry %u group %d has total chance > 100%% (%f)", lootstore.GetName(), id, group_id, chance);
|
|
}
|
|
|
|
if (chance >= 100.0f && !EqualChanced.empty())
|
|
{
|
|
sLog.outErrorDb("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(LootIdSet* ref_set) const
|
|
{
|
|
for (LootStoreItemList::const_iterator ieItr = ExplicitlyChanced.begin(); ieItr != ExplicitlyChanced.end(); ++ieItr)
|
|
{
|
|
if (ieItr->mincountOrRef < 0)
|
|
{
|
|
if (!LootTemplates_Reference.GetLootFor(-ieItr->mincountOrRef))
|
|
{
|
|
LootTemplates_Reference.ReportNotExistedId(-ieItr->mincountOrRef);
|
|
}
|
|
else if (ref_set)
|
|
{
|
|
ref_set->erase(-ieItr->mincountOrRef);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (LootStoreItemList::const_iterator ieItr = EqualChanced.begin(); ieItr != EqualChanced.end(); ++ieItr)
|
|
{
|
|
if (ieItr->mincountOrRef < 0)
|
|
{
|
|
if (!LootTemplates_Reference.GetLootFor(-ieItr->mincountOrRef))
|
|
{
|
|
LootTemplates_Reference.ReportNotExistedId(-ieItr->mincountOrRef);
|
|
}
|
|
else if (ref_set)
|
|
{
|
|
ref_set->erase(-ieItr->mincountOrRef);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// --------- LootTemplate ---------
|
|
//
|
|
|
|
// Adds an entry to the group (at loading stage)
|
|
void LootTemplate::AddEntry(LootStoreItem& item)
|
|
{
|
|
if (item.group > 0 && item.mincountOrRef > 0) // Group
|
|
{
|
|
if (item.group >= Groups.size())
|
|
{ Groups.resize(item.group); } // Adds new group the the loot template if needed
|
|
Groups[item.group - 1].AddEntry(item); // Adds new entry to the group
|
|
}
|
|
else // Non-grouped entries and references are stored together
|
|
{
|
|
Entries.push_back(item);
|
|
}
|
|
}
|
|
|
|
// Rolls for every item in the template and adds the rolled items the the loot
|
|
void LootTemplate::Process(Loot& loot, LootStore const& store, bool rate, 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
|
|
|
|
Groups[groupId - 1].Process(loot);
|
|
return;
|
|
}
|
|
|
|
// Rolling non-grouped items
|
|
for (LootStoreItemList::const_iterator i = Entries.begin() ; i != Entries.end() ; ++i)
|
|
{
|
|
if (!i->Roll(rate))
|
|
{ continue; } // Bad luck for the entry
|
|
|
|
if (i->mincountOrRef < 0 && i->type == LOOT_ITEM_TYPE_ITEM) // References processing
|
|
{
|
|
LootTemplate const* Referenced = LootTemplates_Reference.GetLootFor(-i->mincountOrRef);
|
|
|
|
if (!Referenced)
|
|
{ continue; } // Error message already printed at loading stage
|
|
|
|
// Check condition
|
|
if (i->conditionId && !sObjectMgr.IsPlayerMeetToCondition(i->conditionId, NULL, NULL, loot.GetLootTarget(), CONDITION_FROM_REFERING_LOOT))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (uint32 loop = 0; loop < i->maxcount; ++loop) // Ref multiplicator
|
|
{ Referenced->Process(loot, store, rate, i->group); }
|
|
}
|
|
else // Plain entries (not a reference, not grouped)
|
|
{ loot.AddItem(*i); } // Chance is already checked, just add
|
|
}
|
|
|
|
// Now processing groups
|
|
for (LootGroups::const_iterator i = Groups.begin() ; i != Groups.end() ; ++i)
|
|
{ i->Process(loot); }
|
|
}
|
|
|
|
// 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
|
|
return Groups[groupId - 1].HasQuestDrop();
|
|
}
|
|
|
|
for (LootStoreItemList::const_iterator i = Entries.begin(); i != Entries.end(); ++i)
|
|
{
|
|
if (i->mincountOrRef < 0) // References
|
|
{
|
|
LootTemplateMap::const_iterator Referenced = store.find(-i->mincountOrRef);
|
|
if (Referenced == store.end())
|
|
{ continue; } // Error message [should be] already printed at loading stage
|
|
if (Referenced->second->HasQuestDrop(store, i->group))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else if (i->needs_quest)
|
|
{ return true; } // quest drop found
|
|
}
|
|
|
|
// Now processing groups
|
|
for (LootGroups::const_iterator i = Groups.begin() ; i != Groups.end() ; ++i)
|
|
if (i->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
|
|
return Groups[groupId - 1].HasQuestDropForPlayer(player);
|
|
}
|
|
|
|
// Checking non-grouped entries
|
|
for (LootStoreItemList::const_iterator i = Entries.begin() ; i != Entries.end() ; ++i)
|
|
{
|
|
if (i->mincountOrRef < 0) // References processing
|
|
{
|
|
LootTemplateMap::const_iterator Referenced = store.find(-i->mincountOrRef);
|
|
if (Referenced == store.end())
|
|
{ continue; } // Error message already printed at loading stage
|
|
if (Referenced->second->HasQuestDropForPlayer(store, player, i->group))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else if (player->HasQuestForItem(i->itemid))
|
|
{ return true; } // active quest drop found
|
|
}
|
|
|
|
// Now checking groups
|
|
for (LootGroups::const_iterator i = Groups.begin(); i != Groups.end(); ++i)
|
|
if (i->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)
|
|
{ Groups[i].Verify(lootstore, id, i + 1); }
|
|
|
|
// TODO: References validity checks
|
|
}
|
|
|
|
void LootTemplate::CheckLootRefs(LootIdSet* ref_set) const
|
|
{
|
|
for (LootStoreItemList::const_iterator ieItr = Entries.begin(); ieItr != Entries.end(); ++ieItr)
|
|
{
|
|
if (ieItr->mincountOrRef < 0)
|
|
{
|
|
if (!LootTemplates_Reference.GetLootFor(-ieItr->mincountOrRef))
|
|
{
|
|
LootTemplates_Reference.ReportNotExistedId(-ieItr->mincountOrRef);
|
|
}
|
|
else if (ref_set)
|
|
{
|
|
ref_set->erase(-ieItr->mincountOrRef);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (LootGroups::const_iterator grItr = Groups.begin(); grItr != Groups.end(); ++grItr)
|
|
{ grItr->CheckLootRefs(ref_set); }
|
|
}
|
|
|
|
void LoadLootTemplates_Creature()
|
|
{
|
|
LootIdSet ids_set, ids_setUsed;
|
|
LootTemplates_Creature.LoadAndCollectLootIds(ids_set);
|
|
|
|
// remove real entries and check existence loot
|
|
for (uint32 i = 1; i < sCreatureStorage.GetMaxEntry(); ++i)
|
|
{
|
|
if (CreatureInfo const* cInfo = sCreatureStorage.LookupEntry<CreatureInfo>(i))
|
|
{
|
|
if (uint32 lootid = cInfo->LootId)
|
|
{
|
|
if (ids_set.find(lootid) == ids_set.end())
|
|
{
|
|
LootTemplates_Creature.ReportNotExistedId(lootid);
|
|
}
|
|
else
|
|
{
|
|
ids_setUsed.insert(lootid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (LootIdSet::const_iterator itr = ids_setUsed.begin(); itr != ids_setUsed.end(); ++itr)
|
|
{ ids_set.erase(*itr); }
|
|
|
|
// for alterac valley we've defined Player-loot inside creature_loot_template id=0
|
|
// this hack is used, so that we won't need to create an extra table player_loot_template for just one case
|
|
ids_set.erase(0);
|
|
|
|
// output error for any still listed (not referenced from appropriate table) ids
|
|
LootTemplates_Creature.ReportUnusedIds(ids_set);
|
|
}
|
|
|
|
void LoadLootTemplates_Disenchant()
|
|
{
|
|
LootIdSet ids_set, ids_setUsed;
|
|
LootTemplates_Disenchant.LoadAndCollectLootIds(ids_set);
|
|
|
|
// remove real entries and check existence loot
|
|
for (uint32 i = 1; i < sItemStorage.GetMaxEntry(); ++i)
|
|
{
|
|
if (ItemPrototype const* proto = sItemStorage.LookupEntry<ItemPrototype>(i))
|
|
{
|
|
if (uint32 lootid = proto->DisenchantID)
|
|
{
|
|
if (ids_set.find(lootid) == ids_set.end())
|
|
{
|
|
LootTemplates_Disenchant.ReportNotExistedId(lootid);
|
|
}
|
|
else
|
|
{
|
|
ids_setUsed.insert(lootid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (LootIdSet::const_iterator itr = ids_setUsed.begin(); itr != ids_setUsed.end(); ++itr)
|
|
{ ids_set.erase(*itr); }
|
|
// output error for any still listed (not referenced from appropriate table) ids
|
|
LootTemplates_Disenchant.ReportUnusedIds(ids_set);
|
|
}
|
|
|
|
void LoadLootTemplates_Fishing()
|
|
{
|
|
LootIdSet ids_set;
|
|
LootTemplates_Fishing.LoadAndCollectLootIds(ids_set);
|
|
|
|
// remove real entries and check existence loot
|
|
for (uint32 i = 1; i < sAreaStore.GetNumRows(); ++i)
|
|
{
|
|
if (AreaTableEntry const* areaEntry = sAreaStore.LookupEntry(i))
|
|
if (ids_set.find(areaEntry->ID) != ids_set.end())
|
|
{
|
|
ids_set.erase(areaEntry->ID);
|
|
}
|
|
}
|
|
|
|
// by default (look config options) fishing at fail provide junk loot, entry 0 use for store this loot
|
|
ids_set.erase(0);
|
|
|
|
// output error for any still listed (not referenced from appropriate table) ids
|
|
LootTemplates_Fishing.ReportUnusedIds(ids_set);
|
|
}
|
|
|
|
void LoadLootTemplates_Gameobject()
|
|
{
|
|
LootIdSet ids_set, ids_setUsed;
|
|
LootTemplates_Gameobject.LoadAndCollectLootIds(ids_set);
|
|
|
|
// remove real entries and check existence loot
|
|
for (SQLStorageBase::SQLSIterator<GameObjectInfo> itr = sGOStorage.getDataBegin<GameObjectInfo>(); itr < sGOStorage.getDataEnd<GameObjectInfo>(); ++itr)
|
|
{
|
|
if (uint32 lootid = itr->GetLootId())
|
|
{
|
|
if (ids_set.find(lootid) == ids_set.end())
|
|
{
|
|
LootTemplates_Gameobject.ReportNotExistedId(lootid);
|
|
}
|
|
else
|
|
{
|
|
ids_setUsed.insert(lootid);
|
|
}
|
|
}
|
|
}
|
|
for (LootIdSet::const_iterator itr = ids_setUsed.begin(); itr != ids_setUsed.end(); ++itr)
|
|
{ ids_set.erase(*itr); }
|
|
|
|
// output error for any still listed (not referenced from appropriate table) ids
|
|
LootTemplates_Gameobject.ReportUnusedIds(ids_set);
|
|
}
|
|
|
|
void LoadLootTemplates_Item()
|
|
{
|
|
LootIdSet ids_set;
|
|
LootTemplates_Item.LoadAndCollectLootIds(ids_set);
|
|
|
|
// remove real entries and check existence loot
|
|
for (uint32 i = 1; i < sItemStorage.GetMaxEntry(); ++i)
|
|
{
|
|
if (ItemPrototype const* proto = sItemStorage.LookupEntry<ItemPrototype>(i))
|
|
{
|
|
if (!(proto->Flags & ITEM_FLAG_LOOTABLE))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (ids_set.find(proto->ItemId) != ids_set.end() || proto->MaxMoneyLoot > 0)
|
|
{
|
|
ids_set.erase(proto->ItemId);
|
|
}
|
|
// wdb have wrong data cases, so skip by default
|
|
else if (!sLog.HasLogFilter(LOG_FILTER_DB_STRICTED_CHECK))
|
|
{
|
|
LootTemplates_Item.ReportNotExistedId(proto->ItemId);
|
|
}
|
|
}
|
|
}
|
|
|
|
// output error for any still listed (not referenced from appropriate table) ids
|
|
LootTemplates_Item.ReportUnusedIds(ids_set);
|
|
}
|
|
|
|
void LoadLootTemplates_Milling()
|
|
{
|
|
LootIdSet ids_set;
|
|
LootTemplates_Milling.LoadAndCollectLootIds(ids_set);
|
|
|
|
// remove real entries and check existence loot
|
|
for (uint32 i = 1; i < sItemStorage.GetMaxEntry(); ++i)
|
|
{
|
|
ItemPrototype const* proto = sItemStorage.LookupEntry<ItemPrototype>(i);
|
|
if (!proto)
|
|
continue;
|
|
|
|
if (!(proto->Flags & ITEM_FLAG_MILLABLE))
|
|
continue;
|
|
|
|
if (ids_set.find(proto->ItemId) != ids_set.end())
|
|
ids_set.erase(proto->ItemId);
|
|
else
|
|
LootTemplates_Milling.ReportNotExistedId(proto->ItemId);
|
|
}
|
|
|
|
// output error for any still listed (not referenced from appropriate table) ids
|
|
LootTemplates_Milling.ReportUnusedIds(ids_set);
|
|
}
|
|
|
|
void LoadLootTemplates_Pickpocketing()
|
|
{
|
|
LootIdSet ids_set, ids_setUsed;
|
|
LootTemplates_Pickpocketing.LoadAndCollectLootIds(ids_set);
|
|
|
|
// remove real entries and check existence loot
|
|
for (uint32 i = 1; i < sCreatureStorage.GetMaxEntry(); ++i)
|
|
{
|
|
if (CreatureInfo const* cInfo = sCreatureStorage.LookupEntry<CreatureInfo>(i))
|
|
{
|
|
if (uint32 lootid = cInfo->PickpocketLootId)
|
|
{
|
|
if (ids_set.find(lootid) == ids_set.end())
|
|
{
|
|
LootTemplates_Pickpocketing.ReportNotExistedId(lootid);
|
|
}
|
|
else
|
|
{
|
|
ids_setUsed.insert(lootid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (LootIdSet::const_iterator itr = ids_setUsed.begin(); itr != ids_setUsed.end(); ++itr)
|
|
{ ids_set.erase(*itr); }
|
|
|
|
// output error for any still listed (not referenced from appropriate table) ids
|
|
LootTemplates_Pickpocketing.ReportUnusedIds(ids_set);
|
|
}
|
|
|
|
void LoadLootTemplates_Prospecting()
|
|
{
|
|
LootIdSet ids_set;
|
|
LootTemplates_Prospecting.LoadAndCollectLootIds(ids_set);
|
|
|
|
// remove real entries and check existence loot
|
|
for (uint32 i = 1; i < sItemStorage.GetMaxEntry(); ++i)
|
|
{
|
|
ItemPrototype const* proto = sItemStorage.LookupEntry<ItemPrototype>(i);
|
|
if (!proto)
|
|
continue;
|
|
|
|
if (!(proto->Flags & ITEM_FLAG_PROSPECTABLE))
|
|
continue;
|
|
|
|
if (ids_set.find(proto->ItemId) != ids_set.end())
|
|
ids_set.erase(proto->ItemId);
|
|
// else -- exist some cases that possible can be prospected but not expected have any result loot
|
|
// LootTemplates_Prospecting.ReportNotExistedId(proto->ItemId);
|
|
}
|
|
|
|
// output error for any still listed (not referenced from appropriate table) ids
|
|
LootTemplates_Prospecting.ReportUnusedIds(ids_set);
|
|
}
|
|
|
|
void LoadLootTemplates_Mail()
|
|
{
|
|
LootIdSet ids_set;
|
|
LootTemplates_Mail.LoadAndCollectLootIds(ids_set);
|
|
|
|
// remove real entries and check existence loot
|
|
for (uint32 i = 1; i < sMailTemplateStore.GetNumRows(); ++i)
|
|
if (sMailTemplateStore.LookupEntry(i))
|
|
if (ids_set.find(i) != ids_set.end())
|
|
{
|
|
ids_set.erase(i);
|
|
}
|
|
|
|
// output error for any still listed (not referenced from appropriate table) ids
|
|
LootTemplates_Mail.ReportUnusedIds(ids_set);
|
|
}
|
|
|
|
void LoadLootTemplates_Skinning()
|
|
{
|
|
LootIdSet ids_set, ids_setUsed;
|
|
LootTemplates_Skinning.LoadAndCollectLootIds(ids_set);
|
|
|
|
// remove real entries and check existence loot
|
|
for (uint32 i = 1; i < sCreatureStorage.GetMaxEntry(); ++i)
|
|
{
|
|
if (CreatureInfo const* cInfo = sCreatureStorage.LookupEntry<CreatureInfo>(i))
|
|
{
|
|
if (uint32 lootid = cInfo->SkinningLootId)
|
|
{
|
|
if (ids_set.find(lootid) == ids_set.end())
|
|
{
|
|
LootTemplates_Skinning.ReportNotExistedId(lootid);
|
|
}
|
|
else
|
|
{
|
|
ids_setUsed.insert(lootid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (LootIdSet::const_iterator itr = ids_setUsed.begin(); itr != ids_setUsed.end(); ++itr)
|
|
{ ids_set.erase(*itr); }
|
|
|
|
// output error for any still listed (not referenced from appropriate table) ids
|
|
LootTemplates_Skinning.ReportUnusedIds(ids_set);
|
|
}
|
|
|
|
void LoadLootTemplates_Spell()
|
|
{
|
|
LootIdSet ids_set;
|
|
LootTemplates_Spell.LoadAndCollectLootIds(ids_set);
|
|
|
|
// remove real entries and check existence loot
|
|
for (uint32 spell_id = 1; spell_id < sSpellStore.GetNumRows(); ++spell_id)
|
|
{
|
|
SpellEntry const* spellInfo = sSpellStore.LookupEntry(spell_id);
|
|
if (!spellInfo)
|
|
continue;
|
|
|
|
// possible cases
|
|
if (!IsLootCraftingSpell(spellInfo))
|
|
continue;
|
|
|
|
if (ids_set.find(spell_id) == ids_set.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_ATTR_NOT_SHAPESHIFT) || spellInfo->HasAttribute(SPELL_ATTR_TRADESPELL))
|
|
{
|
|
LootTemplates_Spell.ReportNotExistedId(spell_id);
|
|
}
|
|
}
|
|
else
|
|
ids_set.erase(spell_id);
|
|
}
|
|
|
|
// output error for any still listed (not referenced from appropriate table) ids
|
|
LootTemplates_Spell.ReportUnusedIds(ids_set);
|
|
}
|
|
|
|
void LoadLootTemplates_Reference()
|
|
{
|
|
LootIdSet ids_set;
|
|
LootTemplates_Reference.LoadAndCollectLootIds(ids_set);
|
|
|
|
// check references and remove used
|
|
LootTemplates_Creature.CheckLootRefs(&ids_set);
|
|
LootTemplates_Fishing.CheckLootRefs(&ids_set);
|
|
LootTemplates_Gameobject.CheckLootRefs(&ids_set);
|
|
LootTemplates_Item.CheckLootRefs(&ids_set);
|
|
LootTemplates_Milling.CheckLootRefs(&ids_set);
|
|
LootTemplates_Pickpocketing.CheckLootRefs(&ids_set);
|
|
LootTemplates_Skinning.CheckLootRefs(&ids_set);
|
|
LootTemplates_Disenchant.CheckLootRefs(&ids_set);
|
|
LootTemplates_Prospecting.CheckLootRefs(&ids_set);
|
|
LootTemplates_Mail.CheckLootRefs(&ids_set);
|
|
LootTemplates_Reference.CheckLootRefs(&ids_set);
|
|
LootTemplates_Spell.CheckLootRefs(&ids_set);
|
|
|
|
// output error for any still listed ids (not referenced from any loot table)
|
|
LootTemplates_Reference.ReportUnusedIds(ids_set);
|
|
}
|