aboutsummaryrefslogtreecommitdiff
path: root/src/game/LootMgr.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/game/LootMgr.cpp')
-rw-r--r--src/game/LootMgr.cpp1239
1 files changed, 1239 insertions, 0 deletions
diff --git a/src/game/LootMgr.cpp b/src/game/LootMgr.cpp
new file mode 100644
index 00000000000..e7848ff4094
--- /dev/null
+++ b/src/game/LootMgr.cpp
@@ -0,0 +1,1239 @@
+/*
+ * Copyright (C) 2005-2008 MaNGOS <http://www.mangosproject.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"
+
+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");
+LootStore LootTemplates_Disenchant( "disenchant_loot_template", "item disenchant id");
+LootStore LootTemplates_Fishing( "fishing_loot_template", "area id");
+LootStore LootTemplates_Gameobject( "gameobject_loot_template", "gameobject entry");
+LootStore LootTemplates_Item( "item_loot_template", "item entry");
+LootStore LootTemplates_Pickpocketing("pickpocketing_loot_template","creature pickpocket lootid");
+LootStore LootTemplates_Prospecting( "prospecting_loot_template", "item entry");
+LootStore LootTemplates_QuestMail( "quest_mail_loot_template", "quest id");
+LootStore LootTemplates_Reference( "reference_loot_template", "reference id");
+LootStore LootTemplates_Skinning( "skinning_loot_template", "creature skinning id");
+
+
+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::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();
+ uint8 maxcount = fields[5].GetUInt8();
+ ConditionType condition = (ConditionType)fields[6].GetUInt8();
+ uint32 cond_value1 = fields[7].GetUInt32();
+ uint32 cond_value2 = fields[8].GetUInt32();
+
+ 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 (%d templates)", count, 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() const
+{
+ if(chance>=100.f)
+ return true;
+
+ if(mincountOrRef < 0) // reference case
+ return roll_chance_f(chance*sWorld.getRate(RATE_DROP_ITEM_REFERENCED));
+
+ ItemPrototype const *pProto = objmgr.GetItemPrototype(itemid);
+
+ float qualityModifier = pProto ? 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 (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 (%d) - skipped", store.GetName(), entry, itemid, chance);
+ 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)
+{
+ 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); // Processing is done there, callback via Loot::AddItem()
+
+ // Setting access rights fow group-looting case
+ if(!loot_owner)
+ return;
+ Group * pGroup=loot_owner->GetGroup();
+ if(!pGroup)
+ return;
+ for(GroupReference *itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next())
+ {
+ //fill the quest item map for every player in the recipient's group
+ Player* pl = itr->getSource();
+ if(!pl)
+ continue;
+ uint32 plguid = pl->GetGUIDLow();
+ QuestItemMap::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 isnt 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::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::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::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;
+}
+
+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)
+{
+ Loot &l = lv.loot;
+
+ uint8 itemsShown = 0;
+
+ //gold
+ b << uint32(lv.permission!=NONE_PERMISSION ? l.gold : 0);
+
+ 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;
+ }
+ case NONE_PERMISSION:
+ default:
+ return b; // nothing output more
+ }
+
+ if (lv.qlist)
+ {
+ for (QuestItemList::iterator qi = lv.qlist->begin() ; qi != lv.qlist->end(); ++qi)
+ {
+ LootItem &item = l.quest_items[qi->index];
+ if (!qi->is_looted && !item.is_looted)
+ {
+ b << uint8(l.items.size() + (qi - lv.qlist->begin()));
+ b << item;
+ b << uint8(0); // allow loot
+ ++itemsShown;
+ }
+ }
+ }
+
+ if (lv.ffalist)
+ {
+ for (QuestItemList::iterator fi = lv.ffalist->begin() ; fi != lv.ffalist->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;
+ }
+ }
+ }
+
+ if (lv.conditionallist)
+ {
+ for (QuestItemList::iterator ci = lv.conditionallist->begin() ; ci != lv.conditionallist->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.f)
+ return &ExplicitlyChanced[i];
+
+ ItemPrototype const *pProto = objmgr.GetItemPrototype(ExplicitlyChanced[i].itemid);
+ float qualityMultiplier = pProto ? sWorld.getRate(qualityToRate[pProto->Quality]) : 1.0f;
+ Roll -= ExplicitlyChanced[i].chance * qualityMultiplier;
+ 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, 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() )
+ 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, i->group); // Ref processing
+ }
+ 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->HasQuestDrop())
+ 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_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 )
+ 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_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(ids_set.count(itr->first))
+ ids_set.erase(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_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_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);
+}