Files
TCPlayerbotCore/src/game/LootMgr.cpp
T
megamage d9ec2d2196 [8072] First step to get rid of data blob.
Adds new fields gender, level, xp, money, playerBytes, playerBytes2 and playerFlags to characters table.

    The update will not work if your database contains characters with an old data field (not fitting to the actual client version).

    It's recommended to backup your character database before applying this patch.

--HG--
branch : trunk
2009-06-27 17:39:51 -05:00

1363 lines
49 KiB
C++

/*
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
*
* Copyright (C) 2008-2009 Trinity <http://www.trinitycore.org/>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "LootMgr.h"
#include "Log.h"
#include "ObjectMgr.h"
#include "ProgressBar.h"
#include "World.h"
#include "Util.h"
#include "SharedDefines.h"
#include "SpellMgr.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_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_QuestMail( "quest_mail_loot_template", "quest id (with mail template)",false);
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
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(LootTemplateMap const& store, 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();
sLog.outString( "%s :", GetName());
// 0 1 2 3 4 5 6 7 8
QueryResult *result = WorldDatabase.PQuery("SELECT entry, item, ChanceOrQuestChance, groupid, mincountOrRef, maxcount, lootcondition, condition_value1, condition_value2 FROM %s",GetName());
if (result)
{
barGoLink bar(result->GetRowCount());
do
{
Field *fields = result->Fetch();
bar.step();
uint32 entry = fields[0].GetUInt32();
uint32 item = fields[1].GetUInt32();
float chanceOrQuestChance = fields[2].GetFloat();
uint8 group = fields[3].GetUInt8();
int32 mincountOrRef = fields[4].GetInt32();
uint32 maxcount = fields[5].GetUInt32();
ConditionType condition = (ConditionType)fields[6].GetUInt8();
uint32 cond_value1 = fields[7].GetUInt32();
uint32 cond_value2 = fields[8].GetUInt32();
if(maxcount > std::numeric_limits<uint8>::max())
{
sLog.outErrorDb("Table '%s' entry %d item %d: maxcount value (%u) to large. must be less %u - skipped", GetName(), entry, item, maxcount,std::numeric_limits<uint8>::max());
continue; // error already printed to log/console.
}
if(!PlayerCondition::IsValid(condition,cond_value1, cond_value2))
{
sLog.outErrorDb("... in table '%s' entry %u item %u", GetName(), entry, item);
continue; // error already printed to log/console.
}
// (condition + cond_value1/2) are converted into single conditionId
uint16 conditionId = objmgr.GetConditionId(condition, cond_value1, cond_value2);
LootStoreItem storeitem = LootStoreItem(item, 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 existed 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();
sLog.outString( ">> Loaded %u loot definitions (%lu templates)", count, (unsigned long)m_LootTemplates.size());
}
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(m_LootTemplates,ref_set);
}
void LootStore::ReportUnusedIds(LootIdSet const& ids_set) const
{
// all still listed ids isn't referenced
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());
}
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.getRate(RATE_DROP_ITEM_REFERENCED) : 1.0f));
ItemPrototype const *pProto = objmgr.GetItemPrototype(itemid);
float qualityModifier = pProto && rate ? sWorld.getRate(qualityToRate[pProto->Quality]) : 1.0f;
return roll_chance_f(chance*qualityModifier);
}
// 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 (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
{
ItemPrototype const *proto = objmgr.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;
}
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 < 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: quest chance will be treated as non-quest chance", store.GetName(), entry, itemid);
else if( chance == 0 ) // no chance for the reference
{
sLog.outErrorDb("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;
conditionId = li.conditionId;
ItemPrototype const* proto = objmgr.GetItemPrototype(itemid);
freeforall = proto && (proto->Flags & ITEM_FLAGS_PARTY_LOOT);
needs_quest = li.needs_quest;
count = urand(li.mincountOrRef, li.maxcount); // constructor called for mincountOrRef > 0 only
randomSuffix = GenerateEnchSuffixFactor(itemid);
randomPropertyId = Item::GenerateItemRandomPropertyId(itemid);
is_looted = 0;
is_blocked = 0;
is_underthreshold = 0;
is_counted = 0;
}
// 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 ( !objmgr.IsPlayerMeetToCondition(player,conditionId) )
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)
ItemPrototype const *pProto = objmgr.GetItemPrototype(itemid);
if (pProto && pProto->StartQuest && player->GetQuestStatus(pProto->StartQuest) != QUEST_STATUS_NONE && !player->HasQuestForItem(itemid))
return false;
}
return true;
}
//
// --------- Loot ---------
//
// Inserts the item into the loot (called by LootTemplate processors)
void Loot::AddItem(LootStoreItem const & item)
{
if (item.needs_quest) // Quest drop
{
if (quest_items.size() < MAX_NR_QUEST_ITEMS)
quest_items.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,
// free for all items are counted in FillFFALoot(),
// non-ffa conditionals are counted in FillNonQuestNonFFAConditionalLoot()
if( !item.conditionId )
{
ItemPrototype const* proto = objmgr.GetItemPrototype(item.itemid);
if( !proto || (proto->Flags & ITEM_FLAGS_PARTY_LOOT)==0 )
++unlootedCount;
}
}
}
// Calls processor of corresponding LootTemplate (which handles everything including references)
void Loot::FillLoot(uint32 loot_id, LootStore const& store, Player* loot_owner, bool personal)
{
// Must be provided
if(!loot_owner)
return;
LootTemplate const* tab = store.GetLootFor(loot_id);
if (!tab)
{
sLog.outErrorDb("Table '%s' loot id #%u used but it doesn't have records.",store.GetName(),loot_id);
return;
}
items.reserve(MAX_NR_LOOT_ITEMS);
quest_items.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);
}
void Loot::FillNotNormalLootFor(Player* pl)
{
uint32 plguid = pl->GetGUIDLow();
QuestItemMap::const_iterator qmapitr = PlayerQuestItems.find(plguid);
if (qmapitr == PlayerQuestItems.end())
FillQuestLoot(pl);
qmapitr = PlayerFFAItems.find(plguid);
if (qmapitr == PlayerFFAItems.end())
FillFFALoot(pl);
qmapitr = PlayerNonQuestNonFFAConditionalItems.find(plguid);
if (qmapitr == PlayerNonQuestNonFFAConditionalItems.end())
FillNonQuestNonFFAConditionalLoot(pl);
}
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->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 < quest_items.size(); ++i)
{
LootItem &item = quest_items[i];
if(!item.is_looted && item.AllowedForPlayer(player) )
{
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;
}
PlayerQuestItems[player->GetGUIDLow()] = ql;
return ql;
}
QuestItemList* Loot::FillNonQuestNonFFAConditionalLoot(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.conditionId && item.AllowedForPlayer(player))
{
ql->push_back(QuestItem(i));
if(!item.is_counted)
{
++unlootedCount;
item.is_counted=true;
}
}
}
if (ql->empty())
{
delete ql;
return NULL;
}
PlayerNonQuestNonFFAConditionalItems[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
std::set<uint64>::iterator i_next;
for(std::set<uint64>::iterator i = PlayersLooting.begin(); i != PlayersLooting.end(); i = i_next)
{
i_next = i;
++i_next;
if(Player* pl = ObjectAccessor::FindPlayer(*i))
pl->SendNotifyLootItemRemoved(lootIndex);
else
PlayersLooting.erase(i);
}
}
void Loot::NotifyMoneyRemoved()
{
// notify all players that are looting this that the money was removed
std::set<uint64>::iterator i_next;
for(std::set<uint64>::iterator i = PlayersLooting.begin(); i != PlayersLooting.end(); i = i_next)
{
i_next = i;
++i_next;
if(Player* pl = ObjectAccessor::FindPlayer(*i))
pl->SendNotifyLootMoneyRemoved();
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
std::set<uint64>::iterator i_next;
for(std::set<uint64>::iterator i = PlayersLooting.begin(); i != PlayersLooting.end(); i = i_next)
{
i_next = i;
++i_next;
if(Player* pl = ObjectAccessor::FindPlayer(*i))
{
QuestItemMap::const_iterator pq = PlayerQuestItems.find(pl->GetGUIDLow());
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())
pl->SendNotifyLootItemRemoved(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;
}
}
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->GetGUIDLow());
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->GetGUIDLow());
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->conditionId)
{
QuestItemMap::const_iterator itr = PlayerNonQuestNonFFAConditionalItems.find(player->GetGUIDLow());
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->GetGUIDLow());
return items.size() + (itr != PlayerQuestItems.end() ? itr->second->size() : 0);
}
ByteBuffer& operator<<(ByteBuffer& b, LootItem const& li)
{
b << uint32(li.itemid);
b << uint32(li.count); // nr of items of this type
b << uint32(objmgr.GetItemPrototype(li.itemid)->DisplayInfoID);
b << uint32(li.randomSuffix);
b << uint32(li.randomPropertyId);
//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
return b; // nothing output more
}
Loot &l = lv.loot;
uint8 itemsShown = 0;
//gold
b << uint32(l.gold);
size_t count_pos = b.wpos(); // pos of item count byte
b << uint8(0); // item count placeholder
switch (lv.permission)
{
case GROUP_PERMISSION:
{
// You are not the items proprietary, so you can only see
// blocked rolled items and quest items, and !ffa items
for (uint8 i = 0; i < l.items.size(); ++i)
{
if (!l.items[i].is_looted && !l.items[i].freeforall && !l.items[i].conditionId && l.items[i].AllowedForPlayer(lv.viewer))
{
uint8 slot_type = (l.items[i].is_blocked || l.items[i].is_underthreshold) ? 0 : 1;
b << uint8(i) << l.items[i]; //send the index and the item if it's not looted, and blocked or under threshold, free for all items will be sent later, only one-player loots here
b << uint8(slot_type); // 0 - get 1 - look only
++itemsShown;
}
}
break;
}
case ALL_PERMISSION:
case MASTER_PERMISSION:
{
uint8 slot_type = (lv.permission==MASTER_PERMISSION) ? 2 : 0;
for (uint8 i = 0; i < l.items.size(); ++i)
{
if (!l.items[i].is_looted && !l.items[i].freeforall && !l.items[i].conditionId && l.items[i].AllowedForPlayer(lv.viewer))
{
b << uint8(i) << l.items[i]; //only send one-player loot items now, free for all will be sent later
b << uint8(slot_type); // 0 - get 2 - master selection
++itemsShown;
}
}
break;
}
default:
return b; // nothing output more
}
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.quest_items[qi->index];
if (!qi->is_looted && !item.is_looted)
{
b << uint8(l.items.size() + (qi - q_list->begin()));
b << item;
b << uint8(0); // 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(0); // allow loot
++itemsShown;
}
}
}
QuestItemMap const& lootPlayerNonQuestNonFFAConditionalItems = l.GetPlayerNonQuestNonFFAConditionalItems();
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];
if (!ci->is_looted && !item.is_looted)
{
b << uint8(ci->index) << item;
b << uint8(0); // allow loot
++itemsShown;
}
}
}
//update number of items shown
b.put<uint8>(count_pos,itemsShown);
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();
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(LootTemplateMap const& store, 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) // References processing
{
LootTemplate const* Referenced = LootTemplates_Reference.GetLootFor(-i->mincountOrRef);
if(!Referenced)
continue; // Error message already printed at loading stage
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(LootTemplateMap const& store, 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(store,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.MaxEntry; ++i )
{
if(CreatureInfo const* cInfo = sCreatureStorage.LookupEntry<CreatureInfo>(i))
{
if(uint32 lootid = cInfo->lootid)
{
if(!ids_set.count(lootid))
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);
// 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.MaxEntry; ++i )
{
if(ItemPrototype const* proto = sItemStorage.LookupEntry<ItemPrototype>(i))
{
if(uint32 lootid = proto->DisenchantID)
{
if(!ids_set.count(lootid))
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.count(areaEntry->ID))
ids_set.erase(areaEntry->ID);
}
// 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(uint32 i = 1; i < sGOStorage.MaxEntry; ++i )
{
if(GameObjectInfo const* gInfo = sGOStorage.LookupEntry<GameObjectInfo>(i))
{
if(uint32 lootid = GameObject::GetLootId(gInfo))
{
if(!ids_set.count(lootid))
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.MaxEntry; ++i )
if(ItemPrototype const* proto = sItemStorage.LookupEntry<ItemPrototype>(i))
if(ids_set.count(proto->ItemId))
ids_set.erase(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.MaxEntry; ++i )
{
ItemPrototype const* proto = sItemStorage.LookupEntry<ItemPrototype>(i);
if(!proto)
continue;
if((proto->BagFamily & BAG_FAMILY_MASK_HERBS)==0)
continue;
if(ids_set.count(proto->ItemId))
ids_set.erase(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.MaxEntry; ++i )
{
if(CreatureInfo const* cInfo = sCreatureStorage.LookupEntry<CreatureInfo>(i))
{
if(uint32 lootid = cInfo->pickpocketLootId)
{
if(!ids_set.count(lootid))
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.MaxEntry; ++i )
{
ItemPrototype const* proto = sItemStorage.LookupEntry<ItemPrototype>(i);
if(!proto)
continue;
if((proto->BagFamily & BAG_FAMILY_MASK_MINING_SUPP)==0)
continue;
if(ids_set.count(proto->ItemId))
ids_set.erase(proto->ItemId);
}
// output error for any still listed (not referenced from appropriate table) ids
LootTemplates_Prospecting.ReportUnusedIds(ids_set);
}
void LoadLootTemplates_QuestMail()
{
LootIdSet ids_set;
LootTemplates_QuestMail.LoadAndCollectLootIds(ids_set);
// remove real entries and check existence loot
ObjectMgr::QuestMap const& questMap = objmgr.GetQuestTemplates();
for(ObjectMgr::QuestMap::const_iterator itr = questMap.begin(); itr != questMap.end(); ++itr )
{
if(!itr->second->GetRewMailTemplateId())
continue;
if(ids_set.count(itr->first))
ids_set.erase(itr->first);
/* disabled reporting: some quest mails not include items
else
LootTemplates_QuestMail.ReportNotExistedId(itr->first);
*/
}
// output error for any still listed (not referenced from appropriate table) ids
LootTemplates_QuestMail.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.MaxEntry; ++i )
{
if(CreatureInfo const* cInfo = sCreatureStorage.LookupEntry<CreatureInfo>(i))
{
if(uint32 lootid = cInfo->SkinLootId)
{
if(!ids_set.count(lootid))
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.count(spell_id))
{
// not report about not trainable spells (optionally supported by DB)
// ignore 61756 (Northrend Inscription Research (FAST QA VERSION) for example
if (!(spellInfo->Attributes & SPELL_ATTR_NOT_SHAPESHIFT) || (spellInfo->Attributes & SPELL_ATTR_UNK5))
{
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_QuestMail.CheckLootRefs(&ids_set);
LootTemplates_Reference.CheckLootRefs(&ids_set);
// output error for any still listed ids (not referenced from any loot table)
LootTemplates_Reference.ReportUnusedIds(ids_set);
}