Files
CypherCore/Source/Game/Loot/LootManager.cs
T
2018-05-16 19:57:48 -04:00

1096 lines
44 KiB
C#

/*
* Copyright (C) 2012-2018 CypherCore <http://github.com/CypherCore>
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Framework.Constants;
using Framework.Database;
using Game.Conditions;
using Game.DataStorage;
using Game.Entities;
using System;
using System.Collections.Generic;
namespace Game.Loots
{
using LootStoreItemList = List<LootStoreItem>;
using LootTemplateMap = Dictionary<uint, LootTemplate>;
public class LootManager : LootStorage
{
static void Initialize()
{
Creature = new LootStore("creature_loot_template", "creature entry");
Disenchant = new LootStore("disenchant_loot_template", "item disenchant id");
Fishing = new LootStore("fishing_loot_template", "area id");
Gameobject = new LootStore("gameobject_loot_template", "gameobject entry");
Items = new LootStore("item_loot_template", "item entry");
Mail = new LootStore("mail_loot_template", "mail template id", false);
Milling = new LootStore("milling_loot_template", "item entry (herb)");
Pickpocketing = new LootStore("pickpocketing_loot_template", "creature pickpocket lootid");
Prospecting = new LootStore("prospecting_loot_template", "item entry (ore)");
Reference = new LootStore("reference_loot_template", "reference id", false);
Skinning = new LootStore("skinning_loot_template", "creature skinning id");
Spell = new LootStore("spell_loot_template", "spell id (random item creating)", false);
}
public static void LoadLootTables()
{
Initialize();
LoadLootTemplates_Creature();
LoadLootTemplates_Fishing();
LoadLootTemplates_Gameobject();
LoadLootTemplates_Item();
LoadLootTemplates_Mail();
LoadLootTemplates_Milling();
LoadLootTemplates_Pickpocketing();
LoadLootTemplates_Skinning();
LoadLootTemplates_Disenchant();
LoadLootTemplates_Prospecting();
LoadLootTemplates_Spell();
LoadLootTemplates_Reference();
}
public static void LoadLootTemplates_Creature()
{
Log.outInfo(LogFilter.ServerLoading, "Loading creature loot templates...");
uint oldMSTime = Time.GetMSTime();
List<uint> lootIdSet, lootIdSetUsed = new List<uint>();
uint count = Creature.LoadAndCollectLootIds(out lootIdSet);
// Remove real entries and check loot existence
var ctc = Global.ObjectMgr.GetCreatureTemplates();
foreach (var pair in ctc)
{
uint lootid = pair.Value.LootId;
if (lootid != 0)
{
if (!lootIdSet.Contains(lootid))
Creature.ReportNonExistingId(lootid, pair.Value.Entry);
else
lootIdSetUsed.Add(lootid);
}
}
foreach (var id in lootIdSetUsed)
lootIdSet.Remove(id);
// output error for any still listed (not referenced from appropriate table) ids
Creature.ReportUnusedIds(lootIdSet);
if (count != 0)
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} creature loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
else
Log.outError(LogFilter.ServerLoading, "Loaded 0 creature loot templates. DB table `creature_loot_template` is empty");
}
public static void LoadLootTemplates_Disenchant()
{
Log.outInfo(LogFilter.ServerLoading, "Loading disenchanting loot templates...");
uint oldMSTime = Time.GetMSTime();
List<uint> lootIdSet, lootIdSetUsed = new List<uint>();
uint count = Disenchant.LoadAndCollectLootIds(out lootIdSet);
foreach (var disenchant in CliDB.ItemDisenchantLootStorage.Values)
{
uint lootid = disenchant.Id;
if (!lootIdSet.Contains(lootid))
Disenchant.ReportNonExistingId(lootid, disenchant.Id);
else
lootIdSetUsed.Add(lootid);
}
foreach (var id in lootIdSetUsed)
lootIdSet.Remove(id);
// output error for any still listed (not referenced from appropriate table) ids
Disenchant.ReportUnusedIds(lootIdSet);
if (count != 0)
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} disenchanting loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
else
Log.outError(LogFilter.ServerLoading, "Loaded 0 disenchanting loot templates. DB table `disenchant_loot_template` is empty");
}
public static void LoadLootTemplates_Fishing()
{
Log.outInfo(LogFilter.ServerLoading, "Loading fishing loot templates...");
uint oldMSTime = Time.GetMSTime();
List<uint> lootIdSet;
uint count = Fishing.LoadAndCollectLootIds(out lootIdSet);
// remove real entries and check existence loot
foreach (var areaEntry in CliDB.AreaTableStorage.Values)
if (lootIdSet.Contains(areaEntry.Id))
lootIdSet.Remove(areaEntry.Id);
// output error for any still listed (not referenced from appropriate table) ids
Fishing.ReportUnusedIds(lootIdSet);
if (count != 0)
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} fishing loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
else
Log.outError(LogFilter.ServerLoading, "Loaded 0 fishing loot templates. DB table `fishing_loot_template` is empty");
}
public static void LoadLootTemplates_Gameobject()
{
Log.outInfo(LogFilter.ServerLoading, "Loading gameobject loot templates...");
uint oldMSTime = Time.GetMSTime();
List<uint> lootIdSet, lootIdSetUsed = new List<uint>();
uint count = Gameobject.LoadAndCollectLootIds(out lootIdSet);
// remove real entries and check existence loot
var gotc = Global.ObjectMgr.GetGameObjectTemplates();
foreach (var go in gotc)
{
uint lootid = go.Value.GetLootId();
if (lootid != 0)
{
if (!lootIdSet.Contains(lootid))
Gameobject.ReportNonExistingId(lootid, go.Value.entry);
else
lootIdSetUsed.Add(lootid);
}
}
foreach (var id in lootIdSetUsed)
lootIdSet.Remove(id);
// output error for any still listed (not referenced from appropriate table) ids
Gameobject.ReportUnusedIds(lootIdSet);
if (count != 0)
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} gameobject loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
else
Log.outError(LogFilter.ServerLoading, "Loaded 0 gameobject loot templates. DB table `gameobject_loot_template` is empty");
}
public static void LoadLootTemplates_Item()
{
Log.outInfo(LogFilter.ServerLoading, "Loading item loot templates...");
uint oldMSTime = Time.GetMSTime();
List<uint> lootIdSet;
uint count = Items.LoadAndCollectLootIds(out lootIdSet);
// remove real entries and check existence loot
var its = Global.ObjectMgr.GetItemTemplates();
foreach (var pair in its)
if (lootIdSet.Contains(pair.Value.GetId()) && pair.Value.GetFlags().HasAnyFlag(ItemFlags.HasLoot))
lootIdSet.Remove(pair.Value.GetId());
// output error for any still listed (not referenced from appropriate table) ids
Items.ReportUnusedIds(lootIdSet);
if (count != 0)
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} item loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
else
Log.outError(LogFilter.ServerLoading, "Loaded 0 item loot templates. DB table `item_loot_template` is empty");
}
public static void LoadLootTemplates_Milling()
{
Log.outInfo(LogFilter.ServerLoading, "Loading milling loot templates...");
uint oldMSTime = Time.GetMSTime();
List<uint> lootIdSet;
uint count = Milling.LoadAndCollectLootIds(out lootIdSet);
// remove real entries and check existence loot
var its = Global.ObjectMgr.GetItemTemplates();
foreach (var pair in its)
{
if (!pair.Value.GetFlags().HasAnyFlag(ItemFlags.IsMillable))
continue;
if (lootIdSet.Contains(pair.Value.GetId()))
lootIdSet.Remove(pair.Value.GetId());
}
// output error for any still listed (not referenced from appropriate table) ids
Milling.ReportUnusedIds(lootIdSet);
if (count != 0)
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} milling loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
else
Log.outError(LogFilter.ServerLoading, "Loaded 0 milling loot templates. DB table `milling_loot_template` is empty");
}
public static void LoadLootTemplates_Pickpocketing()
{
Log.outInfo(LogFilter.ServerLoading, "Loading pickpocketing loot templates...");
uint oldMSTime = Time.GetMSTime();
List<uint> lootIdSet;
List<uint> lootIdSetUsed = new List<uint>();
uint count = Pickpocketing.LoadAndCollectLootIds(out lootIdSet);
// Remove real entries and check loot existence
var ctc = Global.ObjectMgr.GetCreatureTemplates();
foreach (var pair in ctc)
{
uint lootid = pair.Value.PickPocketId;
if (lootid != 0)
{
if (!lootIdSet.Contains(lootid))
Pickpocketing.ReportNonExistingId(lootid, pair.Value.Entry);
else
lootIdSetUsed.Add(lootid);
}
}
foreach (var id in lootIdSetUsed)
lootIdSet.Remove(id);
// output error for any still listed (not referenced from appropriate table) ids
Pickpocketing.ReportUnusedIds(lootIdSet);
if (count != 0)
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} pickpocketing loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
else
Log.outError(LogFilter.ServerLoading, "Loaded 0 pickpocketing loot templates. DB table `pickpocketing_loot_template` is empty");
}
public static void LoadLootTemplates_Prospecting()
{
Log.outInfo(LogFilter.ServerLoading, "Loading prospecting loot templates...");
uint oldMSTime = Time.GetMSTime();
List<uint> lootIdSet;
uint count = Prospecting.LoadAndCollectLootIds(out lootIdSet);
// remove real entries and check existence loot
var its = Global.ObjectMgr.GetItemTemplates();
foreach (var pair in its)
{
if (!pair.Value.GetFlags().HasAnyFlag(ItemFlags.IsProspectable))
continue;
if (lootIdSet.Contains(pair.Value.GetId()))
lootIdSet.Remove(pair.Value.GetId());
}
// output error for any still listed (not referenced from appropriate table) ids
Prospecting.ReportUnusedIds(lootIdSet);
if (count != 0)
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} prospecting loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
else
Log.outError(LogFilter.ServerLoading, "Loaded 0 prospecting loot templates. DB table `prospecting_loot_template` is empty");
}
public static void LoadLootTemplates_Mail()
{
Log.outInfo(LogFilter.ServerLoading, "Loading mail loot templates...");
uint oldMSTime = Time.GetMSTime();
List<uint> lootIdSet;
uint count = Mail.LoadAndCollectLootIds(out lootIdSet);
// remove real entries and check existence loot
foreach (var mail in CliDB.MailTemplateStorage.Values)
if (lootIdSet.Contains(mail.Id))
lootIdSet.Remove(mail.Id);
// output error for any still listed (not referenced from appropriate table) ids
Mail.ReportUnusedIds(lootIdSet);
if (count != 0)
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} mail loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
else
Log.outError(LogFilter.ServerLoading, "Loaded 0 mail loot templates. DB table `mail_loot_template` is empty");
}
public static void LoadLootTemplates_Skinning()
{
Log.outInfo(LogFilter.ServerLoading, "Loading skinning loot templates...");
uint oldMSTime = Time.GetMSTime();
List<uint> lootIdSet;
List<uint> lootIdSetUsed = new List<uint>();
uint count = Skinning.LoadAndCollectLootIds(out lootIdSet);
// remove real entries and check existence loot
var ctc = Global.ObjectMgr.GetCreatureTemplates();
foreach (var pair in ctc)
{
uint lootid = pair.Value.SkinLootId;
if (lootid != 0)
{
if (!lootIdSet.Contains(lootid))
Skinning.ReportNonExistingId(lootid, pair.Value.Entry);
else
lootIdSetUsed.Add(lootid);
}
}
foreach (var id in lootIdSetUsed)
lootIdSet.Remove(id);
// output error for any still listed (not referenced from appropriate table) ids
Skinning.ReportUnusedIds(lootIdSet);
if (count != 0)
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} skinning loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
else
Log.outError(LogFilter.ServerLoading, "Loaded 0 skinning loot templates. DB table `skinning_loot_template` is empty");
}
public static void LoadLootTemplates_Spell()
{
// TODO: change this to use MiscValue from spell effect as id instead of spell id
Log.outInfo(LogFilter.ServerLoading, "Loading spell loot templates...");
uint oldMSTime = Time.GetMSTime();
List<uint> lootIdSet;
uint count = Spell.LoadAndCollectLootIds(out lootIdSet);
// remove real entries and check existence loot
foreach (var spellInfo in Global.SpellMgr.GetSpellInfoStorage().Values)
{
// possible cases
if (!spellInfo.IsLootCrafting())
continue;
if (!lootIdSet.Contains(spellInfo.Id))
{
// not report about not trainable spells (optionally supported by DB)
// ignore 61756 (Northrend Inscription Research (FAST QA VERSION) for example
if (!spellInfo.HasAttribute(SpellAttr0.NotShapeshift) || spellInfo.HasAttribute(SpellAttr0.Tradespell))
{
Spell.ReportNonExistingId(spellInfo.Id, spellInfo.Id);
}
}
else
lootIdSet.Remove(spellInfo.Id);
}
// output error for any still listed (not referenced from appropriate table) ids
Spell.ReportUnusedIds(lootIdSet);
if (count != 0)
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell loot templates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
else
Log.outError(LogFilter.ServerLoading, "Loaded 0 spell loot templates. DB table `spell_loot_template` is empty");
}
public static void LoadLootTemplates_Reference()
{
Log.outInfo(LogFilter.ServerLoading, "Loading reference loot templates...");
uint oldMSTime = Time.GetMSTime();
List<uint> lootIdSet;
Reference.LoadAndCollectLootIds(out lootIdSet);
// check references and remove used
Creature.CheckLootRefs(lootIdSet);
Fishing.CheckLootRefs(lootIdSet);
Gameobject.CheckLootRefs(lootIdSet);
Items.CheckLootRefs(lootIdSet);
Milling.CheckLootRefs(lootIdSet);
Pickpocketing.CheckLootRefs(lootIdSet);
Skinning.CheckLootRefs(lootIdSet);
Disenchant.CheckLootRefs(lootIdSet);
Prospecting.CheckLootRefs(lootIdSet);
Mail.CheckLootRefs(lootIdSet);
Reference.CheckLootRefs(lootIdSet);
// output error for any still listed ids (not referenced from any loot table)
Reference.ReportUnusedIds(lootIdSet);
Log.outInfo(LogFilter.ServerLoading, "Loaded refence loot templates in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime));
}
}
public class LootStoreItem
{
public uint itemid; // id of the item
public uint reference; // referenced TemplateleId
public float chance; // chance to drop for both quest and non-quest items, chance to be used for refs
public ushort lootmode;
public bool needs_quest; // quest drop (negative ChanceOrQuestChance in DB)
public byte groupid;
public byte mincount; // mincount for drop items
public byte maxcount; // max drop count for the item mincount or Ref multiplicator
public List<Condition> conditions; // additional loot condition
public LootStoreItem(uint _itemid, uint _reference, float _chance, bool _needs_quest, ushort _lootmode, byte _groupid, byte _mincount, byte _maxcount)
{
itemid = _itemid;
reference = _reference;
chance = _chance;
lootmode = _lootmode;
needs_quest = _needs_quest;
groupid = _groupid;
mincount = _mincount;
maxcount = _maxcount;
conditions = new List<Condition>();
}
public bool Roll(bool rate)
{
if (chance >= 100.0f)
return true;
if (reference > 0) // reference case
return RandomHelper.randChance(chance * (rate ? WorldConfig.GetFloatValue(WorldCfg.RateDropItemReferenced) : 1.0f));
ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(itemid);
float qualityModifier = pProto != null && rate ? WorldConfig.GetFloatValue(qualityToRate[(int)pProto.GetQuality()]) : 1.0f;
return RandomHelper.randChance(chance * qualityModifier);
}
public bool IsValid(LootStore store, uint entry)
{
if (mincount == 0)
{
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: wrong mincount ({3}) - skipped", store.GetName(), entry, itemid, reference);
return false;
}
if (reference == 0) // item (quest or non-quest) entry, maybe grouped
{
ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(itemid);
if (proto == null)
{
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: item entry not listed in `item_template` - skipped", store.GetName(), entry, itemid);
return false;
}
if (chance == 0 && groupid == 0) // Zero chance is allowed for grouped entries only
{
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: 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
{
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: low chance ({3}) - skipped",
store.GetName(), entry, itemid, chance);
return false;
}
if (maxcount < mincount) // wrong max count
{
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: max count ({3}) less that min count ({4}) - skipped", store.GetName(), entry, itemid, maxcount, reference);
return false;
}
}
else // mincountOrRef < 0
{
if (needs_quest)
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: quest chance will be treated as non-quest chance", store.GetName(), entry, itemid);
else if (chance == 0) // no chance for the reference
{
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: 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
}
public static WorldCfg[] qualityToRate = new WorldCfg[7]
{
WorldCfg.RateDropItemPoor, // ITEM_QUALITY_POOR
WorldCfg.RateDropItemNormal, // ITEM_QUALITY_NORMAL
WorldCfg.RateDropItemUncommon, // ITEM_QUALITY_UNCOMMON
WorldCfg.RateDropItemRare, // ITEM_QUALITY_RARE
WorldCfg.RateDropItemEpic, // ITEM_QUALITY_EPIC
WorldCfg.RateDropItemLegendary, // ITEM_QUALITY_LEGENDARY
WorldCfg.RateDropItemArtifact, // ITEM_QUALITY_ARTIFACT
};
}
public class LootStore
{
public LootStore(string name, string entryName, bool ratesAllowed = true)
{
m_name = name;
m_entryName = entryName;
m_ratesAllowed = ratesAllowed;
}
void Verify()
{
foreach (var i in m_LootTemplates)
i.Value.Verify(this, i.Key);
}
public uint LoadAndCollectLootIds(out List<uint> lootIdSet)
{
uint count = LoadLootTable();
lootIdSet = new List<uint>();
foreach (var tab in m_LootTemplates)
lootIdSet.Add(tab.Key);
return count;
}
public void CheckLootRefs(List<uint> ref_set = null)
{
foreach (var pair in m_LootTemplates)
pair.Value.CheckLootRefs(m_LootTemplates, ref_set);
}
public void ReportUnusedIds(List<uint> lootIdSet)
{
// all still listed ids isn't referenced
foreach (var id in lootIdSet)
Log.outError( LogFilter.Sql, "Table '{0}' entry {1} isn't {2} and not referenced from loot, and then useless.", GetName(), id, GetEntryName());
}
public void ReportNonExistingId(uint lootId, uint ownerId)
{
Log.outError( LogFilter.Sql, "Table '{0}' Entry {1} does not exist but it is used by {2} {3}", GetName(), lootId, GetEntryName(), ownerId);
}
public bool HaveLootFor(uint loot_id) { return m_LootTemplates.LookupByKey(loot_id) != null; }
public bool HaveQuestLootFor(uint loot_id)
{
var lootTemplate = m_LootTemplates.LookupByKey(loot_id);
if (lootTemplate == null)
return false;
// scan loot for quest items
return lootTemplate.HasQuestDrop(m_LootTemplates);
}
public bool HaveQuestLootForPlayer(uint loot_id, Player player)
{
var tab = m_LootTemplates.LookupByKey(loot_id);
if (tab != null)
if (tab.HasQuestDropForPlayer(m_LootTemplates, player))
return true;
return false;
}
public LootTemplate GetLootFor(uint loot_id)
{
var tab = m_LootTemplates.LookupByKey(loot_id);
if (tab == null)
return null;
return tab;
}
public void ResetConditions()
{
foreach (var pair in m_LootTemplates)
{
List<Condition> empty = new List<Condition>();
pair.Value.CopyConditions(empty);
}
}
public LootTemplate GetLootForConditionFill(uint loot_id)
{
var tab = m_LootTemplates.LookupByKey(loot_id);
if (tab == null)
return null;
return tab;
}
public string GetName() { return m_name; }
string GetEntryName() { return m_entryName; }
public bool IsRatesAllowed() { return m_ratesAllowed; }
uint LoadLootTable()
{
// Clearing store (for reloading case)
Clear();
// 0 1 2 3 4 5 6 7 8
SQLResult result = DB.World.Query("SELECT Entry, Item, Reference, Chance, QuestRequired, LootMode, GroupId, MinCount, MaxCount FROM {0}", GetName());
if (result.IsEmpty())
return 0;
uint count = 0;
do
{
uint entry = result.Read<uint>(0);
uint item = result.Read<uint>(1);
uint reference = result.Read<uint>(2);
float chance = result.Read<float>(3);
bool needsquest = result.Read<bool>(4);
ushort lootmode = result.Read<ushort>(5);
byte groupid = result.Read<byte>(6);
byte mincount = result.Read<byte>(7);
byte maxcount = result.Read<byte>(8);
if (groupid >= 1 << 7) // it stored in 7 bit field
{
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} item {2}: group ({3}) must be less {4} - skipped", GetName(), entry, item, groupid, 1 << 7);
return 0;
}
LootStoreItem storeitem = new LootStoreItem(item, reference, chance, needsquest, lootmode, groupid, mincount, maxcount);
if (!storeitem.IsValid(this, entry)) // Validity checks
continue;
// Looking for the template of the entry
// often entries are put together
if (m_LootTemplates.Empty() || !m_LootTemplates.ContainsKey(entry))
m_LootTemplates.Add(entry, new LootTemplate());
// Adds current row to the template
m_LootTemplates[entry].AddEntry(storeitem);
++count;
}
while (result.NextRow());
Verify(); // Checks validity of the loot store
return count;
}
void Clear()
{
m_LootTemplates.Clear();
}
LootTemplateMap m_LootTemplates = new LootTemplateMap();
string m_name;
string m_entryName;
bool m_ratesAllowed;
}
public class LootTemplate
{
public void AddEntry(LootStoreItem item)
{
if (item.groupid > 0 && item.reference == 0) // Group
{
if (!Groups.ContainsKey(item.groupid - 1))
Groups[item.groupid - 1] = new LootGroup();
Groups[item.groupid - 1].AddEntry(item); // Adds new entry to the group
}
else // Non-grouped entries and references are stored together
Entries.Add(item);
}
public void Process(Loot loot, bool rate, ushort lootMode, byte groupId = 0)
{
if (groupId != 0) // Group reference uses own processing of the group
{
if (groupId > Groups.Count)
return; // Error message already printed at loading stage
if (Groups[groupId - 1] == null)
return;
Groups[groupId - 1].Process(loot, lootMode);
return;
}
// Rolling non-grouped items
foreach (var item in Entries)
{
if (!Convert.ToBoolean(item.lootmode & lootMode)) // Do not add if mode mismatch
continue;
if (!item.Roll(rate))
continue; // Bad luck for the entry
if (item.reference > 0) // References processing
{
LootTemplate Referenced = LootManager.Reference.GetLootFor(item.reference);
if (Referenced == null)
continue; // Error message already printed at loading stage
uint maxcount = (uint)(item.maxcount * WorldConfig.GetFloatValue(WorldCfg.RateDropItemReferencedAmount));
for (uint loop = 0; loop < maxcount; ++loop) // Ref multiplicator
Referenced.Process(loot, rate, lootMode, item.groupid);
}
else // Plain entries (not a reference, not grouped)
loot.AddItem(item); // Chance is already checked, just add
}
// Now processing groups
foreach (var group in Groups.Values)
{
if (group != null)
group.Process(loot, lootMode);
}
}
public void CopyConditions(List<Condition> conditions)
{
foreach (var i in Entries)
i.conditions.Clear();
foreach (var group in Groups.Values)
group.CopyConditions(conditions);
}
public void CopyConditions(LootItem li)
{
// Copies the conditions list from a template item to a LootItem
foreach (var item in Entries)
{
if (item.itemid != li.itemid)
continue;
li.conditions = item.conditions;
break;
}
}
public bool HasQuestDrop(LootTemplateMap store, byte groupId = 0)
{
if (groupId != 0) // Group reference
{
if (groupId > Groups.Count)
return false; // Error message [should be] already printed at loading stage
if (Groups[groupId - 1] == null)
return false;
return Groups[groupId - 1].HasQuestDrop();
}
foreach (var item in Entries)
{
if (item.reference > 0) // References
{
var Referenced = store.LookupByKey(item.reference);
if (Referenced == null)
continue; // Error message [should be] already printed at loading stage
if (Referenced.HasQuestDrop(store, item.groupid))
return true;
}
else if (item.needs_quest)
return true; // quest drop found
}
// Now processing groups
foreach (var group in Groups.Values)
if (group.HasQuestDrop())
return true;
return false;
}
public bool HasQuestDropForPlayer(LootTemplateMap store, Player player, byte groupId = 0)
{
if (groupId != 0) // Group reference
{
if (groupId > Groups.Count)
return false; // Error message already printed at loading stage
if (Groups[groupId - 1] == null)
return false;
return Groups[groupId - 1].HasQuestDropForPlayer(player);
}
// Checking non-grouped entries
foreach (var item in Entries)
{
if (item.reference > 0) // References processing
{
var Referenced = store.LookupByKey(item.reference);
if (Referenced == null)
continue; // Error message already printed at loading stage
if (Referenced.HasQuestDropForPlayer(store, player, item.groupid))
return true;
}
else if (player.HasQuestForItem(item.itemid))
return true; // active quest drop found
}
// Now checking groups
foreach (var group in Groups.Values)
if (group.HasQuestDropForPlayer(player))
return true;
return false;
}
public void Verify(LootStore lootstore, uint id)
{
// Checking group chances
foreach (var group in Groups)
group.Value.Verify(lootstore, id, (byte)(group.Key + 1));
// @todo References validity checks
}
public void CheckLootRefs(LootTemplateMap store, List<uint> ref_set)
{
foreach (var item in Entries)
{
if (item.reference > 0)
{
if (LootManager.Reference.GetLootFor(item.reference) == null)
LootManager.Reference.ReportNonExistingId(item.reference, item.itemid);
else if (ref_set != null)
ref_set.Remove(item.reference);
}
}
foreach (var group in Groups.Values)
group.CheckLootRefs(store, ref_set);
}
public bool addConditionItem(Condition cond)
{
if (cond == null || !cond.isLoaded())//should never happen, checked at loading
{
Log.outError(LogFilter.Loot, "LootTemplate.addConditionItem: condition is null");
return false;
}
if (!Entries.Empty())
{
foreach (var i in Entries)
{
if (i.itemid == cond.SourceEntry)
{
i.conditions.Add(cond);
return true;
}
}
}
if (!Groups.Empty())
{
foreach (var group in Groups.Values)
{
if (group == null)
continue;
LootStoreItemList itemList = group.GetExplicitlyChancedItemList();
if (!itemList.Empty())
{
foreach (var i in itemList)
{
if (i.itemid == cond.SourceEntry)
{
i.conditions.Add(cond);
return true;
}
}
}
itemList = group.GetEqualChancedItemList();
if (!itemList.Empty())
{
foreach (var i in itemList)
{
if (i.itemid == cond.SourceEntry)
{
i.conditions.Add(cond);
return true;
}
}
}
}
}
return false;
}
public bool isReference(uint id)
{
foreach (var storeItem in Entries)
if (storeItem.itemid == id && storeItem.reference > 0)
return true;
return false;//not found or not reference
}
LootStoreItemList Entries = new LootStoreItemList(); // not grouped only
Dictionary<int, LootGroup> Groups = new Dictionary<int,LootGroup>(); // groups have own (optimised) processing, grouped entries go there
public class LootGroup // A set of loot definitions for items (refs are not allowed)
{
public void AddEntry(LootStoreItem item)
{
if (item.chance != 0)
ExplicitlyChanced.Add(item);
else
EqualChanced.Add(item);
}
public bool HasQuestDrop()
{
foreach (var i in ExplicitlyChanced)
if (i.needs_quest)
return true;
foreach (var i in EqualChanced)
if (i.needs_quest)
return true;
return false;
}
public bool HasQuestDropForPlayer(Player player)
{
foreach (var i in ExplicitlyChanced)
if (player.HasQuestForItem(i.itemid))
return true;
foreach (var i in EqualChanced)
if (player.HasQuestForItem(i.itemid))
return true;
return false;
}
public void Process(Loot loot, ushort lootMode)
{
LootStoreItem item = Roll(loot, lootMode);
if (item != null)
loot.AddItem(item);
}
float RawTotalChance()
{
float result = 0;
foreach (var i in ExplicitlyChanced)
if (!i.needs_quest)
result += i.chance;
return result;
}
float TotalChance()
{
float result = RawTotalChance();
if (!EqualChanced.Empty() && result < 100.0f)
return 100.0f;
return result;
}
public void Verify(LootStore lootstore, uint id, byte group_id = 0)
{
float chance = RawTotalChance();
if (chance > 101.0f) // @todo replace with 100% when DBs will be ready
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} group {2} has total chance > 100% ({3})", lootstore.GetName(), id, group_id, chance);
if (chance >= 100.0f && !EqualChanced.Empty())
Log.outError(LogFilter.Sql, "Table '{0}' entry {1} group {2} has items with chance=0% but group total chance >= 100% ({3})", lootstore.GetName(), id, group_id, chance);
}
public void CheckLootRefs(LootTemplateMap store, List<uint> ref_set)
{
foreach (var item in ExplicitlyChanced)
{
if (item.reference > 0)
{
if (LootManager.Reference.GetLootFor(item.reference) == null)
LootManager.Reference.ReportNonExistingId(item.reference, item.itemid);
else if (ref_set != null)
ref_set.Remove(item.reference);
}
}
foreach (var item in EqualChanced)
{
if (item.reference > 0)
{
if (LootManager.Reference.GetLootFor(item.reference) == null)
LootManager.Reference.ReportNonExistingId(item.reference, item.itemid);
else if (ref_set != null)
ref_set.Remove(item.reference);
}
}
}
public LootStoreItemList GetExplicitlyChancedItemList() { return ExplicitlyChanced; }
public LootStoreItemList GetEqualChancedItemList() { return EqualChanced; }
public void CopyConditions(List<Condition> conditions)
{
foreach (var i in ExplicitlyChanced)
i.conditions.Clear();
foreach (var i in EqualChanced)
i.conditions.Clear();
}
LootStoreItemList ExplicitlyChanced = new LootStoreItemList(); // Entries with chances defined in DB
LootStoreItemList EqualChanced = new LootStoreItemList(); // Zero chances - every entry takes the same chance
LootStoreItem Roll(Loot loot, ushort lootMode)
{
LootStoreItemList possibleLoot = ExplicitlyChanced;
possibleLoot.RemoveAll(new LootGroupInvalidSelector(loot, lootMode).Check);
if (!possibleLoot.Empty()) // First explicitly chanced entries are checked
{
float roll = (float)RandomHelper.randChance();
foreach (var item in possibleLoot) // check each explicitly chanced entry in the template and modify its chance based on quality.
{
if (item.chance >= 100.0f)
return item;
roll -= item.chance;
if (roll < 0)
return item;
}
}
possibleLoot = EqualChanced;
possibleLoot.RemoveAll(new LootGroupInvalidSelector(loot, lootMode).Check);
if (!possibleLoot.Empty()) // If nothing selected yet - an item is taken from equal-chanced part
return possibleLoot.SelectRandom();
return null; // Empty drop from the group
}
}
}
public struct LootGroupInvalidSelector
{
public LootGroupInvalidSelector(Loot loot, ushort lootMode)
{
_loot = loot;
_lootMode = lootMode;
}
public bool Check(LootStoreItem item)
{
if (!Convert.ToBoolean(item.lootmode & _lootMode))
return true;
byte foundDuplicates = 0;
foreach (var lootItem in _loot.items)
if (lootItem.itemid == item.itemid)
if (++foundDuplicates == _loot.maxDuplicates)
return true;
return false;
}
Loot _loot;
ushort _lootMode;
}
}