/*
 * Copyright (C) 2012-2017 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 Framework.Dynamic;
using Game.BattleFields;
using Game.BattleGrounds;
using Game.DataStorage;
using Game.Movement;
using Game.Spells;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Game.Entities
{
    public sealed class SpellManager : Singleton<SpellManager>
    {
        SpellManager()
        {
            Assembly currentAsm = Assembly.GetExecutingAssembly();
            foreach (var type in currentAsm.GetTypes())
            {
                foreach (var methodInfo in type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic))
                {
                    foreach (var auraEffect in methodInfo.GetCustomAttributes<AuraEffectHandlerAttribute>())
                    {
                        if (auraEffect == null)
                            continue;

                        var parameters = methodInfo.GetParameters();
                        if (parameters.Length < 3)
                        {
                            Log.outError(LogFilter.ServerLoading, "Method: {0} has wrong parameter count: {1} Should be 3. Can't load AuraEffect.", methodInfo.Name, parameters.Length);
                            continue;
                        }

                        if (parameters[0].ParameterType != typeof(AuraApplication) || parameters[1].ParameterType != typeof(AuraEffectHandleModes) || parameters[2].ParameterType != typeof(bool))
                        {
                            Log.outError(LogFilter.ServerLoading, "Method: {0} has wrong parameter Types: ({1}, {2}, {3}) Should be (AuraApplication, AuraEffectHandleModes, Bool). Can't load AuraEffect.",
                                methodInfo.Name, parameters[0].ParameterType, parameters[1].ParameterType, parameters[2].ParameterType);
                            continue;
                        }

                        if (AuraEffectHandlers.ContainsKey(auraEffect.auraType))
                        {
                            Log.outError(LogFilter.ServerLoading, "Tried to override AuraEffectHandler of {0} with {1} (AuraType {2}).", AuraEffectHandlers[auraEffect.auraType].ToString(), methodInfo.Name, auraEffect.auraType);
                            continue;
                        }

                        AuraEffectHandlers.Add(auraEffect.auraType, (AuraEffectHandler)methodInfo.CreateDelegate(typeof(AuraEffectHandler)));

                    }

                    foreach (var spellEffect in methodInfo.GetCustomAttributes<SpellEffectHandlerAttribute>())
                    {
                        if (spellEffect == null)
                            continue;

                        var parameters = methodInfo.GetParameters();
                        if (parameters.Length < 1)
                        {
                            Log.outError(LogFilter.ServerLoading, "Method: {0} has wrong parameter count: {1} Should be 1. Can't load SpellEffect.", methodInfo.Name, parameters.Length);
                            continue;
                        }

                        if (parameters[0].ParameterType != typeof(uint))
                        {
                            Log.outError(LogFilter.ServerLoading, "Method: {0} has wrong parameter Types: ({1}) Should be (uint). Can't load SpellEffect.", methodInfo.Name, parameters[0].ParameterType);
                            continue;
                        }

                        if (SpellEffectsHandlers.ContainsKey(spellEffect.effectName))
                        {
                            Log.outError(LogFilter.ServerLoading, "Tried to override SpellEffectsHandler of {0} with {1} (EffectName {2}).", SpellEffectsHandlers[spellEffect.effectName].ToString(), methodInfo.Name, spellEffect.effectName);
                            continue;
                        }

                        SpellEffectsHandlers.Add(spellEffect.effectName, (SpellEffectHandler)methodInfo.CreateDelegate(typeof(SpellEffectHandler)));
                    }
                }
            }
        }

        public bool IsSpellValid(SpellInfo spellInfo, Player player = null, bool msg = true)
        {
            // not exist
            if (spellInfo == null)
                return false;

            bool need_check_reagents = false;

            // check effects
            foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None))
            {
                if (effect == null)
                    continue;

                switch (effect.Effect)
                {
                    case 0:
                        continue;

                    // craft spell for crafting non-existed item (break client recipes list show)
                    case SpellEffectName.CreateItem:
                    case SpellEffectName.CreateLoot:
                        {
                            if (effect.ItemType == 0)
                            {
                                // skip auto-loot crafting spells, its not need explicit item info (but have special fake items sometime)
                                if (!spellInfo.IsLootCrafting())
                                {
                                    if (msg)
                                    {
                                        if (player)
                                            player.SendSysMessage("Craft spell {0} not have create item entry.", spellInfo.Id);
                                        else
                                            Log.outError(LogFilter.Spells, "Craft spell {0} not have create item entry.", spellInfo.Id);
                                    }
                                    return false;
                                }

                            }
                            // also possible IsLootCrafting case but fake item must exist anyway
                            else if (Global.ObjectMgr.GetItemTemplate(effect.ItemType) == null)
                            {
                                if (msg)
                                {
                                    if (player)
                                        player.SendSysMessage("Craft spell {0} create not-exist in DB item (Entry: {1}) and then...", spellInfo.Id, effect.ItemType);
                                    else
                                        Log.outError(LogFilter.Spells, "Craft spell {0} create not-exist in DB item (Entry: {1}) and then...", spellInfo.Id, effect.ItemType);
                                }
                                return false;
                            }

                            need_check_reagents = true;
                            break;
                        }
                    case SpellEffectName.LearnSpell:
                        {
                            SpellInfo spellInfo2 = Global.SpellMgr.GetSpellInfo(effect.TriggerSpell);
                            if (!IsSpellValid(spellInfo2, player, msg))
                            {
                                if (msg)
                                {
                                    if (player != null)
                                        player.SendSysMessage("Spell {0} learn to broken spell {1}, and then...", spellInfo.Id, effect.TriggerSpell);
                                    else
                                        Log.outError(LogFilter.Spells, "Spell {0} learn to invalid spell {1}, and then...", spellInfo.Id, effect.TriggerSpell);
                                }
                                return false;
                            }
                            break;
                        }
                }
            }

            if (need_check_reagents)
            {
                for (int j = 0; j < SpellConst.MaxReagents; ++j)
                {
                    if (spellInfo.Reagent[j] > 0 && Global.ObjectMgr.GetItemTemplate((uint)spellInfo.Reagent[j]) == null)
                    {
                        if (msg)
                        {
                            if (player != null)
                                player.SendSysMessage("Craft spell {0} have not-exist reagent in DB item (Entry: {1}) and then...", spellInfo.Id, spellInfo.Reagent[j]);
                            else
                                Log.outError(LogFilter.Spells, "Craft spell {0} have not-exist reagent in DB item (Entry: {1}) and then...", spellInfo.Id, spellInfo.Reagent[j]);
                        }
                        return false;
                    }
                }
            }

            return true;
        }

        public SpellChainNode GetSpellChainNode(uint spell_id)
        {
            return mSpellChains.LookupByKey(spell_id);
        }

        public uint GetFirstSpellInChain(uint spell_id)
        {
            var node = GetSpellChainNode(spell_id);
            if (node != null)
                return node.first.Id;

            return spell_id;
        }

        public uint GetLastSpellInChain(uint spell_id)
        {
            var node = GetSpellChainNode(spell_id);
            if (node != null)
                return node.last.Id;

            return spell_id;
        }

        public uint GetNextSpellInChain(uint spell_id)
        {
            var node = GetSpellChainNode(spell_id);
            if (node != null)
                if (node.next != null)
                    return node.next.Id;

            return 0;
        }

        public uint GetPrevSpellInChain(uint spell_id)
        {
            var node = GetSpellChainNode(spell_id);
            if (node != null)
                if (node.prev != null)
                    return node.prev.Id;

            return 0;
        }

        public byte GetSpellRank(uint spell_id)
        {
            var node = GetSpellChainNode(spell_id);
            if (node != null)
                return node.rank;

            return 0;
        }

        public uint GetSpellWithRank(uint spell_id, uint rank, bool strict = false)
        {
            var node = GetSpellChainNode(spell_id);
            if (node != null)
            {
                if (rank != node.rank)
                    return GetSpellWithRank(node.rank < rank ? node.next.Id : node.prev.Id, rank, strict);
            }
            else if (strict && rank > 1)
                return 0;

            return spell_id;
        }

        public List<uint> GetSpellsRequiredForSpellBounds(uint spell_id)
        {
            return mSpellReq.LookupByKey(spell_id);
        }

        public List<uint> GetSpellsRequiringSpellBounds(uint spell_id)
        {
            return mSpellsReqSpell.LookupByKey(spell_id);
        }

        public bool IsSpellRequiringSpell(uint spellid, uint req_spellid)
        {
            var spellsRequiringSpell = GetSpellsRequiringSpellBounds(req_spellid);

            foreach (var spell in spellsRequiringSpell)
            {
                if (spell == spellid)
                    return true;
            }
            return false;
        }

        public SpellLearnSkillNode GetSpellLearnSkill(uint spell_id)
        {
            return mSpellLearnSkills.LookupByKey(spell_id);
        }

        public List<SpellLearnSpellNode> GetSpellLearnSpellMapBounds(uint spell_id)
        {
            return mSpellLearnSpells.LookupByKey(spell_id);
        }

        bool IsSpellLearnSpell(uint spell_id)
        {
            return mSpellLearnSpells.ContainsKey(spell_id);
        }

        public bool IsSpellLearnToSpell(uint spell_id1, uint spell_id2)
        {
            var bounds = GetSpellLearnSpellMapBounds(spell_id1);
            foreach (var bound in bounds)
                if (bound.Spell == spell_id2)
                    return true;
            return false;
        }

        public SpellTargetPosition GetSpellTargetPosition(uint spell_id, uint effIndex)
        {
            return mSpellTargetPositions.LookupByKey(new KeyValuePair<uint, uint>(spell_id, effIndex));
        }

        public List<SpellGroup> GetSpellSpellGroupMapBounds(uint spell_id)
        {
            return mSpellSpellGroup.LookupByKey(GetFirstSpellInChain(spell_id));
        }

        public bool IsSpellMemberOfSpellGroup(uint spellid, SpellGroup groupid)
        {
            var spellGroup = GetSpellSpellGroupMapBounds(spellid);
            foreach (var group in spellGroup)
            {
                if (group == groupid)
                    return true;
            }
            return false;
        }

        List<int> GetSpellGroupSpellMapBounds(SpellGroup group_id)
        {
            return mSpellGroupSpell.LookupByKey(group_id);
        }

        public void GetSetOfSpellsInSpellGroup(SpellGroup group_id, out List<int> foundSpells)
        {
            List<SpellGroup> usedGroups = new List<SpellGroup>();
            GetSetOfSpellsInSpellGroup(group_id, out foundSpells, ref usedGroups);
        }

        void GetSetOfSpellsInSpellGroup(SpellGroup group_id, out List<int> foundSpells, ref List<SpellGroup> usedGroups)
        {
            foundSpells = new List<int>();
            if (usedGroups.Find(p => p == group_id) == 0)
                return;

            usedGroups.Add(group_id);

            var groupSpell = GetSpellGroupSpellMapBounds(group_id);
            foreach (var group in groupSpell)
            {
                if (group < 0)
                {
                    SpellGroup currGroup = (SpellGroup)Math.Abs(group);
                    GetSetOfSpellsInSpellGroup(currGroup, out foundSpells, ref usedGroups);
                }
                else
                {
                    foundSpells.Add(group);
                }
            }
        }

        public bool AddSameEffectStackRuleSpellGroups(SpellInfo spellInfo, int amount, out Dictionary<SpellGroup, int> groups)
        {
            groups = new Dictionary<SpellGroup, int>();
            uint spellId = spellInfo.GetFirstRankSpell().Id;
            var spellGroup = GetSpellSpellGroupMapBounds(spellId);
            // Find group with SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT if it belongs to one
            foreach (var group in spellGroup)
            {
                var found = mSpellGroupStack.FirstOrDefault(p => p.Key == group);
                if (found.Key != SpellGroup.None)
                {
                    if (found.Value == SpellGroupStackRule.ExclusiveSameEffect)
                    {
                        // Put the highest amount in the map
                        if (groups.FirstOrDefault(p => p.Key == group).Key == SpellGroup.None)
                            groups.Add(group, amount);
                        else
                        {
                            int curr_amount = groups[group];
                            // Take absolute value because this also counts for the highest negative aura
                            if (Math.Abs(curr_amount) < Math.Abs(amount))
                                groups[group] = amount;
                        }
                        // return because a spell should be in only one SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT group
                        return true;
                    }
                }
            }
            // Not in a SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT group, so return false
            return false;
        }

        public SpellGroupStackRule CheckSpellGroupStackRules(SpellInfo spellInfo1, SpellInfo spellInfo2)
        {
            uint spellid_1 = spellInfo1.GetFirstRankSpell().Id;
            uint spellid_2 = spellInfo2.GetFirstRankSpell().Id;
            if (spellid_1 == spellid_2)
                return SpellGroupStackRule.Default;
            // find SpellGroups which are common for both spells
            var spellGroup1 = GetSpellSpellGroupMapBounds(spellid_1);
            List<SpellGroup> groups = new List<SpellGroup>();
            foreach (var group in spellGroup1)
            {
                if (IsSpellMemberOfSpellGroup(spellid_2, group))
                {
                    bool add = true;
                    var groupSpell = GetSpellGroupSpellMapBounds(group);
                    foreach (var group2 in groupSpell)
                    {
                        if (group2 < 0)
                        {
                            SpellGroup currGroup = (SpellGroup)Math.Abs(group2);
                            if (IsSpellMemberOfSpellGroup(spellid_1, currGroup) && IsSpellMemberOfSpellGroup(spellid_2, currGroup))
                            {
                                add = false;
                                break;
                            }
                        }
                    }
                    if (add)
                        groups.Add(group);
                }
            }

            SpellGroupStackRule rule = SpellGroupStackRule.Default;

            foreach (var group in groups)
            {
                var found = mSpellGroupStack.LookupByKey(group);
                if (found != 0)
                    rule = found;
                if (rule != 0)
                    break;
            }
            return rule;
        }

        public SpellGroupStackRule GetSpellGroupStackRule(SpellGroup group)
        {
            if (mSpellGroupStack.ContainsKey(group))
                return mSpellGroupStack.LookupByKey(group);

            return SpellGroupStackRule.Default;
        }

        public SpellProcEventEntry GetSpellProcEvent(uint spellId)
        {
            return mSpellProcEventMap.LookupByKey(spellId);
        }

        public bool IsSpellProcEventCanTriggeredBy(SpellInfo spellProto, SpellProcEventEntry spellProcEvent, ProcFlags EventProcFlag, SpellInfo procSpell, ProcFlags procFlags, ProcFlagsExLegacy procExtra, bool active)
        {
            // No extra req need
            ProcFlagsExLegacy procEvent_procEx = ProcFlagsExLegacy.None;

            // check prockFlags for condition
            if (!procFlags.HasAnyFlag(EventProcFlag))
                return false;

            bool hasFamilyMask = false;

            // Quick Check - If PROC_FLAG_TAKEN_DAMAGE is set for aura and procSpell dealt damage, proc no matter what kind of spell that deals the damage.
            if (procFlags.HasAnyFlag(ProcFlags.TakenDamage) && EventProcFlag.HasAnyFlag(ProcFlags.TakenDamage))
                return true;

            if (procFlags.HasAnyFlag(ProcFlags.DonePeriodic) && EventProcFlag.HasAnyFlag(ProcFlags.DonePeriodic))
            {
                if (procExtra.HasAnyFlag(ProcFlagsExLegacy.InternalHot))
                {
                    if (EventProcFlag == ProcFlags.DonePeriodic)
                    {
                        // no aura with only PROC_FLAG_DONE_PERIODIC and spellFamilyName == 0 can proc from a HOT.
                        if (spellProto.SpellFamilyName == 0)
                            return false;
                    }
                    // Aura must have positive procflags for a HOT to proc
                    else if (!EventProcFlag.HasAnyFlag(ProcFlags.DoneSpellMagicDmgClassPos | ProcFlags.DoneSpellNoneDmgClassPos))
                        return false;
                }
                // Aura must have negative or neutral(PROC_FLAG_DONE_PERIODIC only) procflags for a DOT to proc
                else if (EventProcFlag != ProcFlags.DonePeriodic)
                    if (!EventProcFlag.HasAnyFlag(ProcFlags.DoneSpellMagicDmgClassNeg | ProcFlags.DoneSpellNoneDmgClassNeg))
                        return false;
            }

            if (procFlags.HasAnyFlag(ProcFlags.TakenPeriodic) && EventProcFlag.HasAnyFlag(ProcFlags.TakenPeriodic))
            {
                if (procExtra.HasAnyFlag(ProcFlagsExLegacy.InternalHot))
                {
                    // No aura that only has PROC_FLAG_TAKEN_PERIODIC can proc from a HOT.
                    if (EventProcFlag == ProcFlags.TakenPeriodic)
                        return false;
                    // Aura must have positive procflags for a HOT to proc
                    if (!EventProcFlag.HasAnyFlag(ProcFlags.TakenSpellMagicDmgClassPos | ProcFlags.TakenSpellNoneDmgClassPos))
                        return false;
                }
                // Aura must have negative or neutral(PROC_FLAG_TAKEN_PERIODIC only) procflags for a DOT to proc
                else if (EventProcFlag != ProcFlags.TakenPeriodic)
                    if (!EventProcFlag.HasAnyFlag(ProcFlags.TakenSpellMagicDmgClassNeg | ProcFlags.TakenSpellNoneDmgClassNeg))
                        return false;
            }
            // Trap casts are active by default
            if (procFlags.HasAnyFlag(ProcFlags.DoneTrapActivation))
                active = true;

            // Always trigger for this
            if (procFlags.HasAnyFlag(ProcFlags.Killed | ProcFlags.Kill | ProcFlags.Death))
                return true;

            if (spellProcEvent != null)     // Exist event data
            {
                // Store extra req
                procEvent_procEx = (ProcFlagsExLegacy)spellProcEvent.procEx;

                // For melee triggers
                if (procSpell == null)
                {
                    // Check (if set) for school (melee attack have Normal school)
                    if (spellProcEvent.schoolMask != 0 && (spellProcEvent.schoolMask & SpellSchoolMask.Normal) == 0)
                        return false;
                }
                else // For spells need check school/spell family/family mask
                {
                    // Check (if set) for school
                    if (spellProcEvent.schoolMask != 0 && (spellProcEvent.schoolMask & procSpell.SchoolMask) == 0)
                        return false;

                    // Check (if set) for spellFamilyName
                    if (spellProcEvent.spellFamilyName != 0 && (spellProcEvent.spellFamilyName != procSpell.SpellFamilyName))
                        return false;

                    // spellFamilyName is Ok need check for spellFamilyMask if present
                    if (spellProcEvent.spellFamilyMask != null)
                    {
                        if (!(spellProcEvent.spellFamilyMask & procSpell.SpellFamilyFlags))
                            return false;
                        hasFamilyMask = true;
                        // Some spells are not considered as active even with have spellfamilyflags
                        if (!procEvent_procEx.HasAnyFlag(ProcFlagsExLegacy.OnlyActiveSpell))
                            active = true;
                    }
                }
            }

            if (procExtra.HasAnyFlag(ProcFlagsExLegacy.InternalReqFamily))
            {
                if (!hasFamilyMask)
                    return false;
            }

            // Check for extra req (if none) and hit/crit
            if (procEvent_procEx == ProcFlagsExLegacy.None)
            {
                // No extra req, so can trigger only for hit/crit - spell has to be active
                if (procExtra.HasAnyFlag(ProcFlagsExLegacy.NormalHit | ProcFlagsExLegacy.CriticalHit) && active)
                    return true;
            }
            else // Passive spells hits here only if resist/reflect/immune/evade
            {
                if (procExtra.HasAnyFlag(ProcFlagsExLegacy.AuraProcMask))
                {
                    // if spell marked as procing only from not active spells
                    if (active && procEvent_procEx.HasAnyFlag(ProcFlagsExLegacy.NotActiveSpell))
                        return false;
                    // if spell marked as procing only from active spells
                    if (!active && procEvent_procEx.HasAnyFlag(ProcFlagsExLegacy.OnlyActiveSpell))
                        return false;
                    // Exist req for PROC_EX_EX_TRIGGER_ALWAYS
                    if (procEvent_procEx.HasAnyFlag(ProcFlagsExLegacy.ExTriggerAlways))
                        return true;
                    // PROC_EX_NOT_ACTIVE_SPELL and PROC_EX_ONLY_ACTIVE_SPELL flags handle: if passed checks before
                    if ((procExtra.HasAnyFlag(ProcFlagsExLegacy.NormalHit | ProcFlagsExLegacy.CriticalHit) && (procEvent_procEx & ProcFlagsExLegacy.AuraProcMask) == 0))
                        return true;
                }
                // Check Extra Requirement like (hit/crit/miss/resist/parry/dodge/block/immune/reflect/absorb and other)
                if (procEvent_procEx.HasAnyFlag(procExtra))
                    return true;
            }
            return false;
        }

        public SpellProcEntry GetSpellProcEntry(uint spellId)
        {
            return mSpellProcMap.LookupByKey(spellId);
        }

        public bool CanSpellTriggerProcOnEvent(SpellProcEntry procEntry, ProcEventInfo eventInfo)
        {
            // proc type doesn't match
            if (!Convert.ToBoolean(eventInfo.GetTypeMask() & procEntry.ProcFlags))
                return false;

            // check XP or honor target requirement
            if ((procEntry.AttributesMask & 0x0000010) != 0)
            {
                Player actor = eventInfo.GetActor().ToPlayer();
                if (actor)
                    if (eventInfo.GetActionTarget() && !actor.isHonorOrXPTarget(eventInfo.GetActionTarget()))
                        return false;
            }

            // always trigger for these types
            if ((eventInfo.GetTypeMask() & (ProcFlags.Killed | ProcFlags.Kill | ProcFlags.Death)) != 0)
                return true;

            // check school mask (if set) for other trigger types
            if (procEntry.SchoolMask != 0 && !Convert.ToBoolean(eventInfo.GetSchoolMask() & procEntry.SchoolMask))
                return false;

            // check spell family name/flags (if set) for spells
            if (eventInfo.GetTypeMask().HasAnyFlag(ProcFlags.PeriodicMask | ProcFlags.SpellMask | ProcFlags.DoneTrapActivation))
            {
                SpellInfo eventSpellInfo = eventInfo.GetSpellInfo();

                if (procEntry.SpellFamilyName != 0 && eventSpellInfo != null && (procEntry.SpellFamilyName != eventSpellInfo.SpellFamilyName))
                    return false;

                if (procEntry.SpellFamilyMask != null && eventSpellInfo != null && !(procEntry.SpellFamilyMask & eventSpellInfo.SpellFamilyFlags))
                    return false;
            }

            // check spell type mask (if set)
            if ((eventInfo.GetTypeMask() & (ProcFlags.SpellMask | ProcFlags.PeriodicMask)) != 0)
            {
                if (procEntry.SpellTypeMask != 0 && !Convert.ToBoolean(eventInfo.GetSpellTypeMask() & procEntry.SpellTypeMask))
                    return false;
            }

            // check spell phase mask
            if ((eventInfo.GetTypeMask() & ProcFlags.ReqSpellPhaseMask) != 0)
            {
                if (!Convert.ToBoolean(eventInfo.GetSpellPhaseMask() & procEntry.SpellPhaseMask))
                    return false;
            }

            // check hit mask (on taken hit or on done hit, but not on spell cast phase)
            if ((eventInfo.GetTypeMask() & ProcFlags.TakenHitMask) != 0 || ((eventInfo.GetTypeMask() & ProcFlags.DoneHitMask) != 0
                && !Convert.ToBoolean(eventInfo.GetSpellPhaseMask() & ProcFlagsSpellPhase.Cast)))
            {
                ProcFlagsHit hitMask = procEntry.HitMask;
                // get default values if hit mask not set
                if (hitMask == 0)
                {
                    // for taken procs allow normal + critical hits by default
                    if ((eventInfo.GetTypeMask() & ProcFlags.TakenHitMask) != 0)
                        hitMask |= ProcFlagsHit.Normal | ProcFlagsHit.Critical;
                    // for done procs allow normal + critical + absorbs by default
                    else
                        hitMask |= ProcFlagsHit.Normal | ProcFlagsHit.Critical | ProcFlagsHit.Absorb;
                }
                if (!Convert.ToBoolean(eventInfo.GetHitMask() & hitMask))
                    return false;
            }

            return true;
        }

        public SpellThreatEntry GetSpellThreatEntry(uint spellID)
        {
            var spellthreat = mSpellThreatMap.LookupByKey(spellID);
            if (spellthreat != null)
                return spellthreat;
            else
            {
                uint firstSpell = GetFirstSpellInChain(spellID);
                return mSpellThreatMap.LookupByKey(firstSpell);
            }
        }

        public List<SkillLineAbilityRecord> GetSkillLineAbilityMapBounds(uint spell_id)
        {
            return mSkillLineAbilityMap.LookupByKey(spell_id);
        }

        public PetAura GetPetAura(uint spell_id, byte eff)
        {
            return mSpellPetAuraMap.LookupByKey((spell_id << 8) + eff);
        }

        public SpellEnchantProcEntry GetSpellEnchantProcEvent(uint enchId)
        {
            return mSpellEnchantProcEventMap.LookupByKey(enchId);
        }

        public bool IsArenaAllowedEnchancment(uint ench_id)
        {
            return mEnchantCustomAttr.LookupByKey((int)ench_id);
        }

        public List<int> GetSpellLinked(int spell_id)
        {
            return mSpellLinkedMap.LookupByKey(spell_id);
        }

        public MultiMap<uint, uint> GetPetLevelupSpellList(CreatureFamily petFamily)
        {
            return mPetLevelupSpellMap.LookupByKey(petFamily);
        }

        public PetDefaultSpellsEntry GetPetDefaultSpellsEntry(int id)
        {
            return mPetDefaultSpellsMap.LookupByKey(id);
        }

        public List<SpellArea> GetSpellAreaMapBounds(uint spell_id)
        {
            return mSpellAreaMap.LookupByKey(spell_id);
        }

        public List<SpellArea> GetSpellAreaForQuestMapBounds(uint quest_id)
        {
            return mSpellAreaForQuestMap.LookupByKey(quest_id);
        }

        public List<SpellArea> GetSpellAreaForQuestEndMapBounds(uint quest_id)
        {
            return mSpellAreaForQuestEndMap.LookupByKey(quest_id);
        }

        public List<SpellArea> GetSpellAreaForAuraMapBounds(uint spell_id)
        {
            return mSpellAreaForAuraMap.LookupByKey(spell_id);
        }

        public List<SpellArea> GetSpellAreaForAreaMapBounds(uint area_id)
        {
            return mSpellAreaForAreaMap.LookupByKey(area_id);
        }

        public List<SpellArea> GetSpellAreaForQuestAreaMapBounds(uint area_id, uint quest_id)
        {
            return mSpellAreaForQuestAreaMap.LookupByKey(Tuple.Create(area_id, quest_id));
        }

        public DiminishingGroup GetDiminishingReturnsGroupForSpell(SpellInfo spellproto)
        {
            if (spellproto.IsPositive())
                return DiminishingGroup.None;

            if (spellproto.HasAura(Difficulty.None, AuraType.ModTaunt))
                return DiminishingGroup.Taunt;

            switch (spellproto.Id)
            {
                case 64803:     // Entrapment
                case 135373:    // Entrapment
                    return DiminishingGroup.Root;
                case 24394:     // Intimidation
                    return DiminishingGroup.Stun;
                case 118345:    // Pulverize (Primal Earth Elemental)
                    return DiminishingGroup.Stun;
                case 118905:    // Static Charge (Capacitor Totem)
                    return DiminishingGroup.Stun;
                case 108199:    // Gorefiend's Grasp
                    return DiminishingGroup.AOEKnockback;
                default:
                    break;
            }

            // Explicit Diminishing Groups
            switch (spellproto.SpellFamilyName)
            {
                case SpellFamilyNames.Generic:
                        break;
                case SpellFamilyNames.Mage:
                    {
                        // Frostjaw -- 102051
                        if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x40000u))
                            return DiminishingGroup.Silence;

                        // Frost Nova -- 122
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x40u))
                            return DiminishingGroup.Root;
                        // Ice Ward -- 111340
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x80000u) && spellproto.SpellFamilyFlags[2].HasAnyFlag(0x2000u))
                            return DiminishingGroup.Root;
                        // Freeze (Water Elemental) -- 33395
                        if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x200u))
                            return DiminishingGroup.Root;

                        // Deep Freeze -- 44572
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x100000u))
                            return DiminishingGroup.Stun;

                        // Dragon's Breath -- 31661
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x800000u))
                            return DiminishingGroup.Incapacitate;
                        // Polymorph -- 118
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x1000000u))
                            return DiminishingGroup.Incapacitate;
                        // Ring of Frost -- 82691
                        if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x40u))
                            return DiminishingGroup.Incapacitate;
                        // Ice Nova -- 157997
                        if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x800000u))
                            return DiminishingGroup.Incapacitate;
                        break;
                    }
                case SpellFamilyNames.Warrior:
                    {
                        // Shockwave -- 132168
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x8000u))
                            return DiminishingGroup.Stun;
                        // Storm Bolt -- 132169
                        if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x1000u))
                            return DiminishingGroup.Stun;

                        // Intimidating Shout -- 5246
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x40000u))
                            return DiminishingGroup.Disorient;

                        // Hamstring -- 1715, 8 seconds in PvP (6.0)
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x2u))
                            return DiminishingGroup.LimitOnly;
                        break;
                    }
                case SpellFamilyNames.Warlock:
                    {
                        // Mortal Coil -- 6789
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x80000u))
                            return DiminishingGroup.Incapacitate;
                        // Banish -- 710
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x8000000u))
                            return DiminishingGroup.Incapacitate;

                        // Fear -- 118699
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x400u))
                            return DiminishingGroup.Disorient;
                        // Howl of Terror -- 5484
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x8u))
                            return DiminishingGroup.Disorient;

                        // Shadowfury -- 30283
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x1000u))
                            return DiminishingGroup.Stun;
                        // Summon Infernal -- 22703
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x1000u))
                            return DiminishingGroup.Stun;
                        break;
                    }
                case SpellFamilyNames.WarlockPet:
                    {
                        // Fellash -- 115770
                        // Whiplash -- 6360
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x8000000u))
                            return DiminishingGroup.AOEKnockback;

                        // Mesmerize (Shivarra pet) -- 115268
                        // Seduction (Succubus pet) -- 6358
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x2000000u))
                            return DiminishingGroup.Disorient;

                        // Axe Toss (Felguard pet) -- 89766
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x4u))
                            return DiminishingGroup.Stun;
                        break;
                    }
                case SpellFamilyNames.Druid:
                    {
                        // Maim -- 22570
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x80u))
                            return DiminishingGroup.Stun;
                        // Mighty Bash -- 5211
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x2000u))
                            return DiminishingGroup.Stun;
                        // Rake -- 163505 -- no flags on the stun
                        if (spellproto.Id == 163505)
                            return DiminishingGroup.Stun;

                        // Incapacitating Roar -- 99, no flags on the stun, 14
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x1u))
                            return DiminishingGroup.Incapacitate;

                        // Cyclone -- 33786
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x20u))
                            return DiminishingGroup.Disorient;

                        // Typhoon -- 61391
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x1000000u))
                            return DiminishingGroup.AOEKnockback;
                        // Ursol's Vortex -- 118283, no family flags
                        if (spellproto.Id == 118283)
                            return DiminishingGroup.AOEKnockback;

                        // Entangling Roots -- 339
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x200u))
                            return DiminishingGroup.Root;
                        // Mass Entanglement -- 102359
                        if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x4u))
                            return DiminishingGroup.Root;

                        // Faerie Fire -- 770, 20 seconds in PvP (6.0)
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x400u))
                            return DiminishingGroup.LimitOnly;
                        break;
                    }
                case SpellFamilyNames.Rogue:
                    {
                        // Cheap Shot -- 1833
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x400u))
                            return DiminishingGroup.Stun;
                        // Kidney Shot -- 408
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x200000u))
                            return DiminishingGroup.Stun;

                        // Gouge -- 1776
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x8u))
                            return DiminishingGroup.Incapacitate;
                        // Sap -- 6770
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x80u))
                            return DiminishingGroup.Incapacitate;

                        // Blind -- 2094
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x1000000u))
                            return DiminishingGroup.Disorient;

                        // Garrote -- 1330
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x20000000u))
                            return DiminishingGroup.Silence;
                        break;
                    }
                case SpellFamilyNames.Hunter:
                    {
                        // Charge (Tenacity pet) -- 53148, no flags
                        if (spellproto.Id == 53148)
                            return DiminishingGroup.Root;
                        // Narrow Escape -- 136634, no flags
                        if (spellproto.Id == 136634)
                            return DiminishingGroup.Root;

                        // Binding Shot -- 117526, no flags
                        if (spellproto.Id == 117526)
                            return DiminishingGroup.Stun;

                        // Freezing Trap -- 3355
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x8u))
                            return DiminishingGroup.Incapacitate;
                        // Wyvern Sting -- 19386
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x1000u))
                            return DiminishingGroup.Incapacitate;
                        break;
                    }
                case SpellFamilyNames.Paladin:
                    {
                        // Repentance -- 20066
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x4u))
                            return DiminishingGroup.Incapacitate;

                        // Turn Evil -- 10326
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x800000u))
                            return DiminishingGroup.Disorient;

                        // Avenger's Shield -- 31935
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x4000u))
                            return DiminishingGroup.Silence;

                        // Fist of Justice -- 105593
                        // Hammer of Justice -- 853
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x800u))
                            return DiminishingGroup.Stun;
                        // Holy Wrath -- 119072
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x200000u))
                            return DiminishingGroup.Stun;
                        break;
                    }
                case SpellFamilyNames.Shaman:
                    {
                        // Hex -- 51514
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x8000u))
                            return DiminishingGroup.Incapacitate;

                        // Thunderstorm -- 51490
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x2000u))
                            return DiminishingGroup.AOEKnockback;
                        // Earthgrab Totem -- 64695
                        if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x4000u))
                            return DiminishingGroup.Root;
                        break;
                    }
                case SpellFamilyNames.Deathknight:
                    {
                        // Strangulate -- 47476
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x200u))
                            return DiminishingGroup.Silence;

                        // Asphyxiate -- 108194
                        if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x100000u))
                            return DiminishingGroup.Stun;
                        // Gnaw (Ghoul) -- 91800, no flags
                        if (spellproto.Id == 91800)
                            return DiminishingGroup.Stun;
                        // Monstrous Blow (Ghoul w/ Dark Transformation active) -- 91797
                        if (spellproto.Id == 91797)
                            return DiminishingGroup.Stun;
                        break;
                    }
                case SpellFamilyNames.Priest:
                    {
                        // Dominate Mind -- 605
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x20000u) && spellproto.GetSpellVisual() == 39068)
                            return DiminishingGroup.Incapacitate;
                        // Holy Word: Chastise -- 88625
                        if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x20u))
                            return DiminishingGroup.Incapacitate;
                        // Psychic Horror -- 64044
                        if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x2000u))
                            return DiminishingGroup.Incapacitate;

                        // Psychic Scream -- 8122
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x10000u))
                            return DiminishingGroup.Disorient;

                        // Silence -- 15487
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x200000u) && spellproto.SchoolMask == (SpellSchoolMask)32)
                            return DiminishingGroup.Silence;
                        break;
                    }
                case SpellFamilyNames.Monk:
                    {
                        // Disable -- 116706, no flags
                        if (spellproto.Id == 116706)
                            return DiminishingGroup.Root;

                        // Charging Ox Wave -- 119392
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x10000u))
                            return DiminishingGroup.Stun;
                        // Fists of Fury -- 120086
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x800000u) && !spellproto.SpellFamilyFlags[2].HasAnyFlag(0x8u))
                            return DiminishingGroup.Stun;
                        // Leg Sweep -- 119381
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x200u))
                            return DiminishingGroup.Stun;

                        // Incendiary Breath (honor talent) -- 202274, no flags
                        if (spellproto.Id == 202274)
                            return DiminishingGroup.Incapacitate;
                        // Paralysis -- 115078
                        if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x800000u))
                            return DiminishingGroup.Incapacitate;
                        break;
                    }
                default:
                    break;
            }

            return DiminishingGroup.None;
        }

        public DiminishingReturnsType GetDiminishingReturnsGroupType(DiminishingGroup group)
        {
            switch (group)
            {
                case DiminishingGroup.Taunt:
                case DiminishingGroup.Stun:
                    return DiminishingReturnsType.All;
                case DiminishingGroup.LimitOnly:
                case DiminishingGroup.None:
                    return DiminishingReturnsType.None;
                default:
                    return DiminishingReturnsType.Player;
            }
        }

        public DiminishingLevels GetDiminishingReturnsMaxLevel(DiminishingGroup group)
        {
            switch (group)
            {
                case DiminishingGroup.Taunt:
                    return DiminishingLevels.TauntImmune;
                case DiminishingGroup.AOEKnockback:
                    return DiminishingLevels.Level2;
                default:
                    return DiminishingLevels.Immune;
            }
        }

        public int GetDiminishingReturnsLimitDuration(SpellInfo spellproto)
        {
            // Explicit diminishing duration
            switch (spellproto.SpellFamilyName)
            {
                case SpellFamilyNames.Druid:
                    {
                        // Faerie Fire - 20 seconds in PvP (6.0)
                        if (spellproto.SpellFamilyFlags[0].HasAnyFlag(0x400u))
                            return 20 * Time.InMilliseconds;
                        break;
                    }
                case SpellFamilyNames.Hunter:
                    {
                        // Binding Shot - 3 seconds in PvP (6.0)
                        if (spellproto.Id == 117526)
                            return 3 * Time.InMilliseconds;
                        // Wyvern Sting - 6 seconds in PvP (6.0)
                        if (spellproto.SpellFamilyFlags[1].HasAnyFlag(0x1000u))
                            return 6 * Time.InMilliseconds;
                        break;
                    }
                case SpellFamilyNames.Monk:
                    {
                        // Paralysis - 4 seconds in PvP regardless of if they are facing you (6.0)
                        if (spellproto.SpellFamilyFlags[2].HasAnyFlag(0x800000u))
                            return 4 * Time.InMilliseconds;
                        break;
                    }
                default:
                    break;
            }

            return 8 * Time.InMilliseconds;
        }

        void UnloadSpellInfoChains()
        {
            foreach (var chain in mSpellChains)
                mSpellInfoMap[chain.Key].ChainEntry = null;

            mSpellChains.Clear();
        }

        #region Loads
        public void LoadSpellRanks()
        {
            uint oldMSTime = Time.GetMSTime();

            Dictionary<uint /*spell*/, uint /*next*/> chains = new Dictionary<uint, uint>();
            List<uint> hasPrev = new List<uint>();
            foreach (SkillLineAbilityRecord skillAbility in CliDB.SkillLineAbilityStorage.Values)
            {
                if (skillAbility.SupercedesSpell == 0)
                    continue;

                if (!HasSpellInfo(skillAbility.SupercedesSpell) || !HasSpellInfo(skillAbility.SpellID))
                    continue;

                chains[skillAbility.SupercedesSpell] = skillAbility.SpellID;
                hasPrev.Add(skillAbility.SpellID);
            }

            // each key in chains that isn't present in hasPrev is a first rank
            foreach (var pair in chains)
            {
                if (hasPrev.Contains(pair.Key))
                    continue;

                SpellInfo first = GetSpellInfo(pair.Key);
                SpellInfo next = GetSpellInfo(pair.Value);

                if (first == null || next != null)
                    continue;

                mSpellChains[pair.Key].first = first;
                mSpellChains[pair.Key].prev = null;
                mSpellChains[pair.Key].next = next;
                mSpellChains[pair.Key].last = next;
                mSpellChains[pair.Key].rank = 1;
                mSpellInfoMap[pair.Key].ChainEntry = mSpellChains[pair.Key];

                mSpellChains[pair.Value].first = first;
                mSpellChains[pair.Value].prev = first;
                mSpellChains[pair.Value].next = null;
                mSpellChains[pair.Value].last = next;
                mSpellChains[pair.Value].rank = 2;
                mSpellInfoMap[pair.Value].ChainEntry = mSpellChains[pair.Value];

                byte rank = 3;
                var nextPair = chains.Find(pair.Value);
                while (nextPair.Key != 0)
                {
                    SpellInfo prev = GetSpellInfo(nextPair.Key); // already checked in previous iteration (or above, in case this is the first one)
                    SpellInfo last = GetSpellInfo(nextPair.Value);
                    if (last == null)
                        break;

                    mSpellChains[nextPair.Key].next = last;

                    mSpellChains[nextPair.Value].first = first;
                    mSpellChains[nextPair.Value].prev = prev;
                    mSpellChains[nextPair.Value].next = null;
                    mSpellChains[nextPair.Value].last = last;
                    mSpellChains[nextPair.Value].rank = rank++;
                    mSpellInfoMap[nextPair.Value].ChainEntry = mSpellChains[nextPair.Value];

                    // fill 'last'
                    do
                    {
                        mSpellChains[prev.Id].last = last;
                        prev = mSpellChains[prev.Id].prev;
                    } while (prev != null);

                    nextPair = chains.Find(nextPair.Value);
                }
            }

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell rank records in {1}ms", mSpellChains.Count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void LoadSpellRequired()
        {
            uint oldMSTime = Time.GetMSTime();

            mSpellsReqSpell.Clear();                                   // need for reload case
            mSpellReq.Clear();                                         // need for reload case

            //                                                   0        1
            SQLResult result = DB.World.Query("SELECT spell_id, req_spell from spell_required");

            if (result.IsEmpty())
            {
                Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell required records. DB table `spell_required` is empty.");

                return;
            }

            uint count = 0;
            do
            {
                uint spell_id = result.Read<uint>(0);
                uint spell_req = result.Read<uint>(1);

                // check if chain is made with valid first spell
                SpellInfo spell = GetSpellInfo(spell_id);
                if (spell == null)
                {
                    Log.outError(LogFilter.Sql, "spell_id {0} in `spell_required` table is not found in dbcs, skipped", spell_id);
                    continue;
                }

                SpellInfo req_spell = GetSpellInfo(spell_req);
                if (req_spell == null)
                {
                    Log.outError(LogFilter.Sql, "req_spell {0} in `spell_required` table is not found in dbcs, skipped", spell_req);
                    continue;
                }

                if (spell.IsRankOf(req_spell))
                {
                    Log.outError(LogFilter.Sql, "req_spell {0} and spell_id {1} in `spell_required` table are ranks of the same spell, entry not needed, skipped", spell_req, spell_id);
                    continue;
                }

                if (IsSpellRequiringSpell(spell_id, spell_req))
                {
                    Log.outError(LogFilter.Sql, "duplicated entry of req_spell {0} and spell_id {1} in `spell_required`, skipped", spell_req, spell_id);
                    continue;
                }

                mSpellReq.Add(spell_id, spell_req);
                mSpellsReqSpell.Add(spell_req, spell_id);
                ++count;
            } while (result.NextRow());

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell required records in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));

        }

        public void LoadSpellLearnSkills()
        {
            mSpellLearnSkills.Clear();

            // search auto-learned skills and add its to map also for use in unlearn spells/talents
            uint dbc_count = 0;
            foreach (var spell in mSpellInfoMap.Values)
            {
                foreach (SpellEffectInfo effect in spell.GetEffectsForDifficulty(Difficulty.None))
                {
                    if (effect == null)
                        continue;

                    SpellLearnSkillNode dbc_node = new SpellLearnSkillNode();
                    switch (effect.Effect)
                    {
                        case SpellEffectName.Skill:
                            dbc_node.skill = (SkillType)effect.MiscValue;
                            dbc_node.step = (ushort)effect.CalcValue();
                            if (dbc_node.skill != SkillType.Riding)
                                dbc_node.value = 1;
                            else
                                dbc_node.value = (ushort)(dbc_node.step * 75);
                            dbc_node.maxvalue = (ushort)(dbc_node.step * 75);
                            break;
                        case SpellEffectName.DualWield:
                            dbc_node.skill = SkillType.DualWield;
                            dbc_node.step = 1;
                            dbc_node.value = 1;
                            dbc_node.maxvalue = 1;
                            break;
                        default:
                            continue;
                    }

                    mSpellLearnSkills.Add(spell.Id, dbc_node);
                    ++dbc_count;
                    break;
                }
            }
            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} Spell Learn Skills from DBC", dbc_count);
        }

        public void LoadSpellLearnSpells()
        {
            uint oldMSTime = Time.GetMSTime();

            mSpellLearnSpells.Clear();

            //                                         0      1        2
            SQLResult result = DB.World.Query("SELECT entry, SpellID, Active FROM spell_learn_spell");
            if (result.IsEmpty())
            {
                Log.outError(LogFilter.ServerLoading, "Loaded 0 spell learn spells. DB table `spell_learn_spell` is empty.");
                return;
            }
            uint count = 0;
            do
            {
                uint spell_id = result.Read<uint>(0);

                var node = new SpellLearnSpellNode();
                node.Spell = result.Read<uint>(1);
                node.OverridesSpell = 0;
                node.Active = result.Read<bool>(2);
                node.AutoLearned = false;

                SpellInfo spellInfo = GetSpellInfo(spell_id);
                if (spellInfo == null)
                {
                    Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_learn_spell` does not exist", spell_id);
                    continue;
                }

                if (GetSpellInfo(node.Spell) == null)
                {
                    Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_learn_spell` learning not existed spell {1}", spell_id, node.Spell);
                    continue;
                }

                if (spellInfo.HasAttribute(SpellCustomAttributes.IsTalent))
                {
                    Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_learn_spell` attempt learning talent spell {1}, skipped", spell_id, node.Spell);
                    continue;
                }

                mSpellLearnSpells.Add(spell_id, node);
                ++count;
            } while (result.NextRow());

            // search auto-learned spells and add its to map also for use in unlearn spells/talents
            uint dbc_count = 0;
            foreach (var entry in mSpellInfoMap.Values)
            {
                foreach (SpellEffectInfo effect in entry.GetEffectsForDifficulty(Difficulty.None))
                {
                    if (effect != null && effect.Effect == SpellEffectName.LearnSpell)
                    {
                        var dbc_node = new SpellLearnSpellNode();
                        dbc_node.Spell = effect.TriggerSpell;
                        dbc_node.Active = true;                     // all dbc based learned spells is active (show in spell book or hide by client itself)
                        dbc_node.OverridesSpell = 0;

                        // ignore learning not existed spells (broken/outdated/or generic learnig spell 483
                        if (GetSpellInfo(dbc_node.Spell) == null)
                            continue;

                        // talent or passive spells or skill-step spells auto-cast and not need dependent learning,
                        // pet teaching spells must not be dependent learning (cast)
                        // other required explicit dependent learning
                        dbc_node.AutoLearned = effect.TargetA.GetTarget() == Targets.UnitPet || entry.HasAttribute(SpellCustomAttributes.IsTalent) || entry.IsPassive() || entry.HasEffect(SpellEffectName.SkillStep);

                        var db_node_bounds = GetSpellLearnSpellMapBounds(entry.Id);

                        bool found = false;
                        foreach (var bound in db_node_bounds)
                        {
                            if (bound.Spell == dbc_node.Spell)
                            {
                                Log.outError(LogFilter.Sql, "Spell {0} auto-learn spell {1} in spell.dbc then the record in `spell_learn_spell` is redundant, please fix DB.",
                                    entry.SpellName, dbc_node.Spell);
                                found = true;
                                break;
                            }
                        }

                        if (!found)                                  // add new spell-spell pair if not found
                        {
                            mSpellLearnSpells.Add(entry.Id, dbc_node);
                            ++dbc_count;
                        }
                    }
                }
            }

            foreach (var spellLearnSpell in CliDB.SpellLearnSpellStorage.Values)
            {
                if (GetSpellInfo(spellLearnSpell.SpellID) == null)
                    continue;

                var db_node_bounds = mSpellLearnSpells.LookupByKey(spellLearnSpell.LearnSpellID);
                bool found = false;
                foreach (var spellNode in db_node_bounds)
                {
                    if (spellNode.Spell == spellLearnSpell.SpellID)
                    {
                        Log.outError(LogFilter.Sql, "Found redundant record (entry: {0}, SpellID: {1}) in `spell_learn_spell`, spell added automatically from SpellLearnSpell.db2", spellLearnSpell.LearnSpellID, spellLearnSpell.SpellID);
                        found = true;
                        break;
                    }
                }

                if (found)
                    continue;

                // Check if it is already found in Spell.dbc, ignore silently if yes
                var dbc_node_bounds = GetSpellLearnSpellMapBounds(spellLearnSpell.LearnSpellID);
                found = false;
                foreach (var spellNode in dbc_node_bounds)
                {
                    if (spellNode.Spell == spellLearnSpell.SpellID)
                    {
                        found = true;
                        break;
                    }
                }

                if (found)
                    continue;

                SpellLearnSpellNode dbcLearnNode = new SpellLearnSpellNode();
                dbcLearnNode.Spell = spellLearnSpell.SpellID;
                dbcLearnNode.OverridesSpell = spellLearnSpell.OverridesSpellID;
                dbcLearnNode.Active = true;
                dbcLearnNode.AutoLearned = false;

                mSpellLearnSpells.Add(spellLearnSpell.LearnSpellID, dbcLearnNode);
                ++dbc_count;
            }

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell learn spells, {1} found in Spell.dbc in {2} ms", count, dbc_count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void LoadSpellTargetPositions()
        {
            uint oldMSTime = Time.GetMSTime();

            mSpellTargetPositions.Clear();                                // need for reload case

            //                                         0   1         2           3                  4                  5
            SQLResult result = DB.World.Query("SELECT ID, EffectIndex, MapID, PositionX, PositionY, PositionZ FROM spell_target_position");
            if (result.IsEmpty())
            {
                Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell target coordinates. DB table `spell_target_position` is empty.");
                return;
            }

            uint count = 0;
            do
            {
                uint spellId = result.Read<uint>(0);
                uint effIndex = result.Read<byte>(1);

                SpellTargetPosition st = new SpellTargetPosition();
                st.target_mapId = result.Read<uint>(2);
                st.target_X = result.Read<float>(3);
                st.target_Y = result.Read<float>(4);
                st.target_Z = result.Read<float>(5);

                var mapEntry = CliDB.MapStorage.LookupByKey(st.target_mapId);
                if (mapEntry == null)
                {
                    Log.outError(LogFilter.Sql, "Spell (ID: {0}, EffectIndex: {1}) is using a non-existant MapID (ID: {2})", spellId, effIndex, st.target_mapId);
                    continue;
                }

                if (st.target_X == 0 && st.target_Y == 0 && st.target_Z == 0)
                {
                    Log.outError(LogFilter.Sql, "Spell (ID: {0}, EffectIndex: {1}) target coordinates not provided.", spellId, effIndex);
                    continue;
                }

                SpellInfo spellInfo = GetSpellInfo(spellId);
                if (spellInfo == null)
                {
                    Log.outError(LogFilter.Sql, "Spell (ID: {0}) listed in `spell_target_position` does not exist.", spellId);
                    continue;
                }

                SpellEffectInfo effect = spellInfo.GetEffect(effIndex);
                if (effect == null)
                {
                    Log.outError(LogFilter.Sql, "Spell (Id: {0}, effIndex: {1}) listed in `spell_target_position` does not have an effect at index {2}.", spellId, effIndex, effIndex);
                    continue;
                }

                if (effect.TargetA.GetTarget() == Targets.DestDb || effect.TargetB.GetTarget() == Targets.DestDb)
                {
                    var key = new KeyValuePair<uint, uint>(spellId, effIndex);
                    mSpellTargetPositions[key] = st;
                    ++count;
                }
                else
                {
                    Log.outError(LogFilter.Sql, "Spell (Id: {0}, effIndex: {1}) listed in `spell_target_position` does not have target TARGET_DEST_DB (17).", spellId, effIndex);
                    continue;
                }

            }
            while (result.NextRow());

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell teleport coordinates in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void LoadSpellGroups()
        {
            uint oldMSTime = Time.GetMSTime();

            mSpellSpellGroup.Clear();                                  // need for reload case
            mSpellGroupSpell.Clear();

            //                                                0     1
            SQLResult result = DB.World.Query("SELECT id, spell_id FROM spell_group");
            if (result.IsEmpty())
            {
                Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell group definitions. DB table `spell_group` is empty.");
                return;
            }

            List<uint> groups = new List<uint>();
            uint count = 0;
            do
            {
                uint group_id = result.Read<uint>(0);
                if (group_id <= 1000 && group_id >= (uint)SpellGroup.CoreRangeMax)
                {
                    Log.outError(LogFilter.Sql, "SpellGroup id {0} listed in `spell_group` is in core range, but is not defined in core!", group_id);
                    continue;
                }
                int spell_id = result.Read<int>(1);

                groups.Add(group_id);
                mSpellGroupSpell.Add((SpellGroup)group_id, spell_id);

            } while (result.NextRow());

            foreach (var group in mSpellGroupSpell.KeyValueList)
            {
                if (group.Value < 0)
                {
                    if (!groups.Contains((uint)Math.Abs(group.Value)))
                    {
                        Log.outError(LogFilter.Sql, "SpellGroup id {0} listed in `spell_group` does not exist", Math.Abs(group.Value));
                        mSpellGroupSpell.Remove(group.Key);
                    }
                }
                else
                {
                    SpellInfo spellInfo = GetSpellInfo((uint)group.Value);

                    if (spellInfo == null)
                    {
                        Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_group` does not exist", group.Value);
                        mSpellGroupSpell.Remove(group.Key);
                    }
                    else if (spellInfo.GetRank() > 1)
                    {
                        Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_group` is not first rank of spell", group.Value);
                        mSpellGroupSpell.Remove(group.Key);
                    }
                }
            }

            foreach (var group in groups)
            {
                List<int> spells;
                GetSetOfSpellsInSpellGroup((SpellGroup)group, out spells);

                foreach (var spell in spells)
                {
                    ++count;
                    mSpellSpellGroup.Add((uint)spell, (SpellGroup)group);
                }
            }

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell group definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void LoadSpellGroupStackRules()
        {
            uint oldMSTime = Time.GetMSTime();

            mSpellGroupStack.Clear();                                  // need for reload case

            //                                                       0         1
            SQLResult result = DB.World.Query("SELECT group_id, stack_rule FROM spell_group_stack_rules");
            if (result.IsEmpty())
            {
                Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell group stack rules. DB table `spell_group_stack_rules` is empty.");
                return;
            }

            uint count = 0;
            do
            {
                uint group_id = result.Read<uint>(0);
                byte stack_rule = result.Read<byte>(1);
                if (stack_rule >= (byte)SpellGroupStackRule.Max)
                {
                    Log.outError(LogFilter.Sql, "SpellGroupStackRule {0} listed in `spell_group_stack_rules` does not exist", stack_rule);
                    continue;
                }

                var spellGroup = GetSpellGroupSpellMapBounds((SpellGroup)group_id);

                if (spellGroup == null)
                {
                    Log.outError(LogFilter.Sql, "SpellGroup id {0} listed in `spell_group_stack_rules` does not exist", group_id);
                    continue;
                }

                mSpellGroupStack[(SpellGroup)group_id] = (SpellGroupStackRule)stack_rule;

                ++count;
            } while (result.NextRow());

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell group stack rules in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void LoadSpellProcEvents()
        {
            uint oldMSTime = Time.GetMSTime();

            mSpellProcEventMap.Clear();                             // need for reload case

            //                                            0      1           2                3                 4                 5                 6                 7          8       9        10            11
            SQLResult result = DB.World.Query("SELECT entry, SchoolMask, SpellFamilyName, SpellFamilyMask0, SpellFamilyMask1, SpellFamilyMask2, SpellFamilyMask3, procFlags, procEx, ppmRate, CustomChance, Cooldown FROM spell_proc_event");
            if (result.IsEmpty())
            {
                Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell proc event conditions. DB table `spell_proc_event` is empty.");
                return;
            }

            uint count = 0;
            do
            {
                int spellId = result.Read<int>(0);

                bool allRanks = false;
                if (spellId < 0)
                {
                    allRanks = true;
                    spellId = -spellId;
                }

                SpellInfo spellInfo = GetSpellInfo((uint)spellId);
                if (spellInfo == null)
                {
                    Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc_event` does not exist", spellId);
                    continue;
                }

                if (allRanks)
                {
                    if (!spellInfo.IsRanked())
                        Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc_event` with all ranks, but spell has no ranks.", spellId);

                    if (spellInfo.GetFirstRankSpell().Id != spellId)
                    {
                        Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc_event` is not first rank of spell.", spellId);
                        continue;
                    }
                }

                SpellProcEventEntry spellProcEvent = new SpellProcEventEntry();

                spellProcEvent.schoolMask = (SpellSchoolMask)result.Read<uint>(1);
                spellProcEvent.spellFamilyName = (SpellFamilyNames)result.Read<uint>(2);
                spellProcEvent.spellFamilyMask = new FlagArray128(result.Read<uint>(3), result.Read<uint>(4), result.Read<uint>(5), result.Read<uint>(6));
                spellProcEvent.procFlags = result.Read<uint>(7);
                spellProcEvent.procEx = result.Read<uint>(8);
                spellProcEvent.ppmRate = result.Read<float>(9);
                spellProcEvent.customChance = result.Read<float>(10);
                spellProcEvent.cooldown = result.Read<uint>(11);

                while (spellInfo != null)
                {
                    if (mSpellProcEventMap.ContainsKey(spellInfo.Id))
                    {
                        Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc_event` already has its first rank in table.", spellInfo.Id);
                        break;
                    }

                    if (spellInfo.ProcFlags == 0 && spellProcEvent.procFlags == 0)
                        Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc_event` probably not triggered spell", spellInfo.Id);

                    mSpellProcEventMap[spellInfo.Id] = spellProcEvent;

                    if (allRanks)
                        spellInfo = spellInfo.GetNextRankSpell();
                    else
                        break;
                }

                ++count;
            } while (result.NextRow());

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} extra spell proc event conditions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void LoadSpellProcs()
        {
            uint oldMSTime = Time.GetMSTime();

            mSpellProcMap.Clear();                             // need for reload case

            //                                            0        1           2                3                 4                 5                 6
            SQLResult result = DB.World.Query("SELECT SpellId, SchoolMask, SpellFamilyName, SpellFamilyMask0, SpellFamilyMask1, SpellFamilyMask2, SpellFamilyMask3, " +
            //           7              8               9       10              11              12      13        14      15
                "ProcFlags, SpellTypeMask, SpellPhaseMask, HitMask, AttributesMask, ProcsPerMinute, Chance, Cooldown, Charges FROM spell_proc");
            if (result.IsEmpty())
            {
                Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell proc conditions and data. DB table `spell_proc` is empty.");
                return;
            }

            uint count = 0;
            do
            {
                int spellId = result.Read<int>(0);

                bool allRanks = false;
                if (spellId < 0)
                {
                    allRanks = true;
                    spellId = -spellId;
                }

                SpellInfo spellInfo = GetSpellInfo((uint)spellId);
                if (spellInfo == null)
                {
                    Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc` does not exist", spellId);
                    continue;
                }

                if (allRanks)
                {
                    if (spellInfo.GetFirstRankSpell().Id != (uint)spellId)
                    {
                        Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc` is not first rank of spell.", spellId);
                        continue;
                    }
                }

                SpellProcEntry baseProcEntry = new SpellProcEntry();

                baseProcEntry.SchoolMask = (SpellSchoolMask)result.Read<uint>(1);
                baseProcEntry.SpellFamilyName = (SpellFamilyNames)result.Read<uint>(2);
                baseProcEntry.SpellFamilyMask = new FlagArray128(result.Read<uint>(3), result.Read<uint>(4), result.Read<uint>(5), result.Read<uint>(6));
                baseProcEntry.ProcFlags = (ProcFlags)result.Read<uint>(7);
                baseProcEntry.SpellTypeMask = (ProcFlagsSpellType)result.Read<uint>(8);
                baseProcEntry.SpellPhaseMask = (ProcFlagsSpellPhase)result.Read<uint>(9);
                baseProcEntry.HitMask = (ProcFlagsHit)result.Read<uint>(10);
                baseProcEntry.AttributesMask = result.Read<uint>(11);
                baseProcEntry.ProcsPerMinute = result.Read<float>(12);
                baseProcEntry.Chance = result.Read<float>(13);
                baseProcEntry.Cooldown = result.Read<uint>(14);
                baseProcEntry.Charges = result.Read<uint>(15);

                while (spellInfo != null)
                {
                    if (mSpellProcMap.ContainsKey(spellInfo.Id))
                    {
                        Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc` has duplicate entry in the table", spellInfo.Id);
                        break;
                    }
                    SpellProcEntry procEntry = baseProcEntry;

                    // take defaults from dbcs
                    if (procEntry.ProcFlags == 0)
                        procEntry.ProcFlags = spellInfo.ProcFlags;
                    if (procEntry.Charges == 0)
                        procEntry.Charges = spellInfo.ProcCharges;
                    if (procEntry.Chance == 0 && procEntry.ProcsPerMinute == 0)
                        procEntry.Chance = spellInfo.ProcChance;

                    // validate data
                    if (Convert.ToBoolean(procEntry.SchoolMask & ~SpellSchoolMask.All))
                        Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `SchoolMask` set: {1}", spellInfo.Id, procEntry.SchoolMask);
                    if (procEntry.SpellFamilyName != 0 && ((int)procEntry.SpellFamilyName < 3 || (int)procEntry.SpellFamilyName > 17 || (int)procEntry.SpellFamilyName == 14 || (int)procEntry.SpellFamilyName == 16))
                        Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `SpellFamilyName` set: {1}", spellInfo.Id, procEntry.SpellFamilyName);
                    if (procEntry.Chance < 0)
                    {
                        Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has negative value in `Chance` field", spellInfo.Id);
                        procEntry.Chance = 0;
                    }
                    if (procEntry.ProcsPerMinute < 0)
                    {
                        Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has negative value in `ProcsPerMinute` field", spellInfo.Id);
                        procEntry.ProcsPerMinute = 0;
                    }
                    if (procEntry.Chance == 0 && procEntry.ProcsPerMinute == 0)
                        Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} doesn't have `Chance` and `ProcsPerMinute` values defined, proc will not be triggered", spellInfo.Id);
                    if (procEntry.ProcFlags == 0)
                        Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} doesn't have `ProcFlags` value defined, proc will not be triggered", spellInfo.Id);
                    if (Convert.ToBoolean(procEntry.SpellTypeMask & ~ProcFlagsSpellType.MaskAll))
                        Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `SpellTypeMask` set: {1}", spellInfo.Id, procEntry.SpellTypeMask);
                    if (procEntry.SpellTypeMask != 0 && !Convert.ToBoolean(procEntry.ProcFlags & (ProcFlags.SpellMask | ProcFlags.PeriodicMask)))
                        Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has `SpellTypeMask` value defined, but it won't be used for defined `ProcFlags` value", spellInfo.Id);
                    if (procEntry.SpellPhaseMask == 0 && Convert.ToBoolean(procEntry.ProcFlags & ProcFlags.ReqSpellPhaseMask))
                        Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} doesn't have `SpellPhaseMask` value defined, but it's required for defined `ProcFlags` value, proc will not be triggered", spellInfo.Id);
                    if (Convert.ToBoolean(procEntry.SpellPhaseMask & ~ProcFlagsSpellPhase.MaskAll))
                        Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `SpellPhaseMask` set: {1}", spellInfo.Id, procEntry.SpellPhaseMask);
                    if (procEntry.SpellPhaseMask != 0 && !Convert.ToBoolean(procEntry.ProcFlags & ProcFlags.ReqSpellPhaseMask))
                        Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has `SpellPhaseMask` value defined, but it won't be used for defined `ProcFlags` value", spellInfo.Id);
                    if (Convert.ToBoolean(procEntry.HitMask & ~ProcFlagsHit.MaskAll))
                        Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `HitMask` set: {1}", spellInfo.Id, procEntry.HitMask);
                    if (procEntry.HitMask != 0 && !(Convert.ToBoolean(procEntry.ProcFlags & ProcFlags.TakenHitMask) || (Convert.ToBoolean(procEntry.ProcFlags & ProcFlags.DoneHitMask) && (procEntry.SpellPhaseMask == 0 || Convert.ToBoolean(procEntry.SpellPhaseMask & (ProcFlagsSpellPhase.Hit | ProcFlagsSpellPhase.Finish))))))
                        Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has `HitMask` value defined, but it won't be used for defined `ProcFlags` and `SpellPhaseMask` values", spellInfo.Id);

                    mSpellProcMap.Add(spellInfo.Id, procEntry);
                    ++count;

                    if (allRanks)
                        spellInfo = spellInfo.GetNextRankSpell();
                    else
                        break;
                }
            } while (result.NextRow());

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell proc conditions and data in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void LoadSpellThreats()
        {
            uint oldMSTime = Time.GetMSTime();

            mSpellThreatMap.Clear();                                // need for reload case

            //                                           0      1        2       3
            SQLResult result = DB.World.Query("SELECT entry, flatMod, pctMod, apPctMod FROM spell_threat");
            if (result.IsEmpty())
            {
                Log.outInfo(LogFilter.ServerLoading, "Loaded 0 aggro generating spells. DB table `spell_threat` is empty.");
                return;
            }
            uint count = 0;
            do
            {
                uint entry = result.Read<uint>(0);

                if (GetSpellInfo(entry) == null)
                {
                    Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_threat` does not exist", entry);
                    continue;
                }

                SpellThreatEntry ste = new SpellThreatEntry();
                ste.flatMod = result.Read<int>(1);
                ste.pctMod = result.Read<float>(2);
                ste.apPctMod = result.Read<float>(3);

                mSpellThreatMap[entry] = ste;
                count++;
            } while (result.NextRow());

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} SpellThreatEntries in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void LoadSkillLineAbilityMap()
        {
            uint oldMSTime = Time.GetMSTime();

            mSkillLineAbilityMap.Clear();

            foreach (var skill in CliDB.SkillLineAbilityStorage.Values)
                mSkillLineAbilityMap.Add(skill.SpellID, skill);

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} SkillLineAbility MultiMap Data in {1} ms", mSkillLineAbilityMap.Count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void LoadSpellPetAuras()
        {
            uint oldMSTime = Time.GetMSTime();

            mSpellPetAuraMap.Clear();                                  // need for reload case

            //                                                  0       1       2    3
            SQLResult result = DB.World.Query("SELECT spell, effectId, pet, aura FROM spell_pet_auras");
            if (result.IsEmpty())
            {
                Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell pet auras. DB table `spell_pet_auras` is empty.");
                return;
            }

            uint count = 0;
            do
            {
                uint spell = result.Read<uint>(0);
                byte eff = result.Read<byte>(1);
                uint pet = result.Read<uint>(2);
                uint aura = result.Read<uint>(3);

                var petAura = mSpellPetAuraMap.LookupByKey((spell << 8) + eff);
                if (petAura != null)
                    petAura.AddAura(pet, aura);
                else
                {
                    SpellInfo spellInfo = GetSpellInfo(spell);
                    if (spellInfo == null)
                    {
                        Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_pet_auras` does not exist", spell);
                        continue;
                    }
                    SpellEffectInfo effect = spellInfo.GetEffect(eff);
                    if (effect == null)
                    {
                        Log.outError(LogFilter.Spells, "Spell {0} listed in `spell_pet_auras` does not have effect at index {1}", spell, eff);
                        continue;
                    }

                    if (effect.Effect != SpellEffectName.Dummy && (effect.Effect != SpellEffectName.ApplyAura || effect.ApplyAuraName != AuraType.Dummy))
                    {
                        Log.outError(LogFilter.Spells, "Spell {0} listed in `spell_pet_auras` does not have dummy aura or dummy effect", spell);
                        continue;
                    }

                    SpellInfo spellInfo2 = GetSpellInfo(aura);
                    if (spellInfo2 == null)
                    {
                        Log.outError(LogFilter.Sql, "Aura {0} listed in `spell_pet_auras` does not exist", aura);
                        continue;
                    }

                    PetAura pa = new PetAura(pet, aura, effect.TargetA.GetTarget() == Targets.UnitPet, effect.CalcValue());
                    mSpellPetAuraMap[(spell << 8) + eff] = pa;
                }
                ++count;
            } while (result.NextRow());

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell pet auras in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        // Fill custom data about enchancments
        public void LoadEnchantCustomAttr()
        {
            uint oldMSTime = Time.GetMSTime();

            uint count = 0;
            foreach (var spellInfo in mSpellInfoMap.Values)
            {
                // @todo find a better check
                if (!spellInfo.HasAttribute(SpellAttr2.PreserveEnchantInArena) || !spellInfo.HasAttribute(SpellAttr0.NotShapeshift))
                    continue;

                foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None))
                {
                    if (effect != null && effect.Effect == SpellEffectName.EnchantItemTemporary)
                    {
                        int enchId = effect.MiscValue;
                        var ench = CliDB.SpellItemEnchantmentStorage.LookupByKey((uint)enchId);
                        if (ench == null)
                            continue;
                        mEnchantCustomAttr[enchId] = true;
                        ++count;
                        break;
                    }
                }
            }

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} custom enchant attributes in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void LoadSpellEnchantProcData()
        {
            uint oldMSTime = Time.GetMSTime();

            mSpellEnchantProcEventMap.Clear();                             // need for reload case

            //                                                  0         1           2         3
            SQLResult result = DB.World.Query("SELECT entry, customChance, PPMChance, procEx FROM spell_enchant_proc_data");
            if (result.IsEmpty())
            {
                Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell enchant proc event conditions. DB table `spell_enchant_proc_data` is empty.");
                return;
            }

            uint count = 0;
            do
            {
                uint enchantId = result.Read<uint>(0);

                var ench = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchantId);
                if (ench == null)
                {
                    Log.outError(LogFilter.Sql, "Enchancment {0} listed in `spell_enchant_proc_data` does not exist", enchantId);
                    continue;
                }

                SpellEnchantProcEntry spe = new SpellEnchantProcEntry();
                spe.customChance = result.Read<uint>(1);
                spe.PPMChance = result.Read<float>(2);
                spe.procEx = (ProcFlagsExLegacy)result.Read<uint>(3);

                mSpellEnchantProcEventMap[enchantId] = spe;

                ++count;
            } while (result.NextRow());

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} enchant proc data definitions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void LoadSpellLinked()
        {
            uint oldMSTime = Time.GetMSTime();

            mSpellLinkedMap.Clear();    // need for reload case

            //                                                0              1             2
            SQLResult result = DB.World.Query("SELECT spell_trigger, spell_effect, type FROM spell_linked_spell");
            if (result.IsEmpty())
            {
                Log.outInfo(LogFilter.ServerLoading, "Loaded 0 linked spells. DB table `spell_linked_spell` is empty.");
                return;
            }

            uint count = 0;
            do
            {
                int trigger = result.Read<int>(0);
                int effect = result.Read<int>(1);
                int type = result.Read<int>(2);

                SpellInfo spellInfo = GetSpellInfo((uint)Math.Abs(trigger));
                if (spellInfo == null)
                {
                    Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_linked_spell` does not exist", Math.Abs(trigger));
                    continue;
                }
                spellInfo = GetSpellInfo((uint)Math.Abs(effect));
                if (spellInfo == null)
                {
                    Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_linked_spell` does not exist", Math.Abs(effect));
                    continue;
                }

                if (type != 0) //we will find a better way when more types are needed
                {
                    if (trigger > 0)
                        trigger += 200000 * type;
                    else
                        trigger -= 200000 * type;
                }
                mSpellLinkedMap.Add(trigger, effect);

                ++count;
            } while (result.NextRow());

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} linked spells in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void LoadPetLevelupSpellMap()
        {
            uint oldMSTime = Time.GetMSTime();

            mPetLevelupSpellMap.Clear();                                   // need for reload case

            uint count = 0;
            uint family_count = 0;

            foreach (var creatureFamily in CliDB.CreatureFamilyStorage.Values)
            {
                for (byte j = 0; j < 2; ++j)
                {
                    if (creatureFamily.SkillLine[j] == 0)
                        continue;

                    foreach (var skillLine in CliDB.SkillLineAbilityStorage.Values)
                    {
                        if (skillLine.SkillLine != creatureFamily.SkillLine[j])
                            continue;

                        if (skillLine.AcquireMethod != AbilytyLearnType.OnSkillLearn)
                            continue;

                        SpellInfo spell = GetSpellInfo(skillLine.SpellID);
                        if (spell == null) // not exist or triggered or talent
                            continue;

                        if (spell.SpellLevel == 0)
                            continue;

                        if (!mPetLevelupSpellMap.ContainsKey(creatureFamily.Id))
                            mPetLevelupSpellMap.Add(creatureFamily.Id, new MultiMap<uint, uint>());

                        var spellSet = mPetLevelupSpellMap.LookupByKey(creatureFamily.Id);
                        if (spellSet.Count == 0)
                            ++family_count;

                        spellSet.Add(spell.SpellLevel, spell.Id);
                        ++count;
                    }
                }
            }

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} pet levelup and default spells for {1} families in {2} ms", count, family_count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void LoadPetDefaultSpells()
        {
            uint oldMSTime = Time.GetMSTime();

            mPetDefaultSpellsMap.Clear();

            uint countCreature = 0;

            Log.outInfo(LogFilter.ServerLoading, "Loading summonable creature templates...");
            oldMSTime = Time.GetMSTime();

            // different summon spells
            foreach (var spellEntry in mSpellInfoMap.Values)
            {
                foreach (SpellEffectInfo effect in spellEntry.GetEffectsForDifficulty(Difficulty.None))
                {
                    if (effect != null && (effect.Effect == SpellEffectName.Summon || effect.Effect == SpellEffectName.SummonPet))
                    {
                        int creature_id = effect.MiscValue;
                        CreatureTemplate cInfo = Global.ObjectMgr.GetCreatureTemplate((uint)creature_id);
                        if (cInfo == null)
                            continue;

                        // get default pet spells from creature_template
                        uint petSpellsId = cInfo.Entry;
                        if (mPetDefaultSpellsMap.LookupByKey(cInfo.Entry) != null)
                            continue;

                        PetDefaultSpellsEntry petDefSpells = new PetDefaultSpellsEntry();
                        for (byte j = 0; j < SharedConst.MaxCreatureSpellDataSlots; ++j)
                            petDefSpells.spellid[j] = cInfo.Spells[j];

                        if (LoadPetDefaultSpells_helper(cInfo, petDefSpells))
                        {
                            mPetDefaultSpellsMap[petSpellsId] = petDefSpells;
                            ++countCreature;
                        }
                    }
                }
            }

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} summonable creature templates in {1} ms", countCreature, Time.GetMSTimeDiffToNow(oldMSTime));
        }
        bool LoadPetDefaultSpells_helper(CreatureTemplate cInfo, PetDefaultSpellsEntry petDefSpells)
        {
            // skip empty list;
            bool have_spell = false;
            for (byte j = 0; j < SharedConst.MaxCreatureSpellDataSlots; ++j)
            {
                if (petDefSpells.spellid[j] != 0)
                {
                    have_spell = true;
                    break;
                }
            }
            if (!have_spell)
                return false;

            // remove duplicates with levelupSpells if any
            var levelupSpells = cInfo.Family != 0 ? GetPetLevelupSpellList(cInfo.Family) : null;
            if (levelupSpells != null)
            {
                for (byte j = 0; j < SharedConst.MaxCreatureSpellDataSlots; ++j)
                {
                    if (petDefSpells.spellid[j] == 0)
                        continue;

                    foreach (var pair in levelupSpells)
                    {
                        if (pair.Value == petDefSpells.spellid[j])
                        {
                            petDefSpells.spellid[j] = 0;
                            break;
                        }
                    }
                }
            }

            // skip empty list;
            have_spell = false;
            for (byte j = 0; j < SharedConst.MaxCreatureSpellDataSlots; ++j)
            {
                if (petDefSpells.spellid[j] != 0)
                {
                    have_spell = true;
                    break;
                }
            }

            return have_spell;
        }

        public void LoadSpellAreas()
        {
            uint oldMSTime = Time.GetMSTime();

            mSpellAreaMap.Clear();                                  // need for reload case
            mSpellAreaForQuestMap.Clear();
            mSpellAreaForQuestEndMap.Clear();
            mSpellAreaForAuraMap.Clear();

            //                                            0     1         2              3               4                 5          6          7       8         9
            SQLResult result = DB.World.Query("SELECT spell, area, quest_start, quest_start_status, quest_end_status, quest_end, aura_spell, racemask, gender, autocast FROM spell_area");
            if (result.IsEmpty())
            {
                Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell area requirements. DB table `spell_area` is empty.");

                return;
            }

            uint count = 0;
            do
            {
                uint spell = result.Read<uint>(0);

                SpellArea spellArea = new SpellArea();
                spellArea.spellId = spell;
                spellArea.areaId = result.Read<uint>(1);
                spellArea.questStart = result.Read<uint>(2);
                spellArea.questStartStatus = result.Read<uint>(3);
                spellArea.questEndStatus = result.Read<uint>(4);
                spellArea.questEnd = result.Read<uint>(5);
                spellArea.auraSpell = result.Read<int>(6);
                spellArea.raceMask = result.Read<uint>(7);
                spellArea.gender = (Gender)result.Read<uint>(8);
                spellArea.autocast = result.Read<bool>(9);

                SpellInfo spellInfo = GetSpellInfo(spell);
                if (spellInfo != null)
                {
                    if (spellArea.autocast)
                        spellInfo.Attributes |= SpellAttr0.CantCancel;
                }
                else
                {
                    Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` does not exist", spell);
                    continue;
                }

                {
                    bool ok = true;
                    var sa_bounds = GetSpellAreaMapBounds(spellArea.spellId);
                    foreach (var bound in sa_bounds)
                    {
                        if (spellArea.spellId != bound.spellId)
                            continue;
                        if (spellArea.areaId != bound.areaId)
                            continue;
                        if (spellArea.questStart != bound.questStart)
                            continue;
                        if (spellArea.auraSpell != bound.auraSpell)
                            continue;
                        if ((spellArea.raceMask & bound.raceMask) == 0)
                            continue;
                        if (spellArea.gender != bound.gender)
                            continue;

                        // duplicate by requirements
                        ok = false;
                        break;
                    }

                    if (!ok)
                    {
                        Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` already listed with similar requirements.", spell);
                        continue;
                    }
                }

                if (spellArea.areaId != 0 && !CliDB.AreaTableStorage.ContainsKey(spellArea.areaId))
                {
                    Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have wrong area ({1}) requirement", spell, spellArea.areaId);
                    continue;
                }

                if (spellArea.questStart != 0 && Global.ObjectMgr.GetQuestTemplate(spellArea.questStart) == null)
                {
                    Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have wrong start quest ({1}) requirement", spell, spellArea.questStart);
                    continue;
                }

                if (spellArea.questEnd != 0)
                {
                    if (Global.ObjectMgr.GetQuestTemplate(spellArea.questEnd) == null)
                    {
                        Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have wrong end quest ({1}) requirement", spell, spellArea.questEnd);
                        continue;
                    }
                }

                if (spellArea.auraSpell != 0)
                {
                    SpellInfo info = GetSpellInfo((uint)Math.Abs(spellArea.auraSpell));
                    if (info == null)
                    {
                        Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have wrong aura spell ({1}) requirement", spell, Math.Abs(spellArea.auraSpell));
                        continue;
                    }

                    if (Math.Abs(spellArea.auraSpell) == spellArea.spellId)
                    {
                        Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have aura spell ({1}) requirement for itself", spell, Math.Abs(spellArea.auraSpell));
                        continue;
                    }

                    // not allow autocast chains by auraSpell field (but allow use as alternative if not present)
                    if (spellArea.autocast && spellArea.auraSpell > 0)
                    {
                        bool chain = false;
                        var saBound = GetSpellAreaForAuraMapBounds(spellArea.spellId);
                        foreach (var bound in saBound)
                        {
                            if (bound.autocast && bound.auraSpell > 0)
                            {
                                chain = true;
                                break;
                            }
                        }

                        if (chain)
                        {
                            Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have aura spell ({1}) requirement that itself autocast from aura", spell, spellArea.auraSpell);
                            continue;
                        }

                        var saBound2 = GetSpellAreaMapBounds((uint)spellArea.auraSpell);
                        foreach (var bound in saBound2)
                        {
                            if (bound.autocast && bound.auraSpell > 0)
                            {
                                chain = true;
                                break;
                            }
                        }

                        if (chain)
                        {
                            Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have aura spell ({1}) requirement that itself autocast from aura", spell, spellArea.auraSpell);
                            continue;
                        }
                    }
                }

                if (spellArea.raceMask != 0 && (spellArea.raceMask & (uint)Race.RaceMaskAllPlayable) == 0)
                {
                    Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have wrong race mask ({1}) requirement", spell, spellArea.raceMask);
                    continue;
                }

                if (spellArea.gender != Gender.None && spellArea.gender != Gender.Female && spellArea.gender != Gender.Male)
                {
                    Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_area` have wrong gender ({1}) requirement", spell, spellArea.gender);
                    continue;
                }
                mSpellAreaMap.Add(spell, spellArea);
                var sa = mSpellAreaMap[spell];

                // for search by current zone/subzone at zone/subzone change
                if (spellArea.areaId != 0)
                    mSpellAreaForAreaMap.AddRange(spellArea.areaId, sa);

                // for search at quest start/reward
                if (spellArea.questStart != 0)
                    mSpellAreaForQuestMap.AddRange(spellArea.questStart, sa);

                // for search at quest start/reward
                if (spellArea.questEnd != 0)
                    mSpellAreaForQuestEndMap.AddRange(spellArea.questEnd, sa);

                // for search at aura apply
                if (spellArea.auraSpell != 0)
                    mSpellAreaForAuraMap.AddRange((uint)Math.Abs(spellArea.auraSpell), sa);

                if (spellArea.areaId != 0 && spellArea.questStart != 0)
                    mSpellAreaForQuestAreaMap.AddRange(Tuple.Create(spellArea.areaId, spellArea.questStart), sa);

                if (spellArea.areaId != 0 && spellArea.questEnd != 0)
                    mSpellAreaForQuestAreaMap.AddRange(Tuple.Create(spellArea.areaId, spellArea.questEnd), sa);

                ++count;
            } while (result.NextRow());

            Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell area requirements in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void LoadSpellInfoStore()
        {
            uint oldMSTime = Time.GetMSTime();

            mSpellInfoMap.Clear();
            Dictionary<uint, SpellInfoLoadHelper> loadData = new Dictionary<uint, SpellInfoLoadHelper>();

            Dictionary<uint, Dictionary<uint, SpellEffectRecord[]>> effectsBySpell = new Dictionary<uint, Dictionary<uint, SpellEffectRecord[]>>();
            Dictionary<uint, MultiMap<uint, SpellXSpellVisualRecord>> visualsBySpell = new Dictionary<uint, MultiMap<uint, SpellXSpellVisualRecord>>();
            Dictionary<uint, SpellEffectScalingRecord> spellEffectScallingByEffectId = new Dictionary<uint, SpellEffectScalingRecord>();
            foreach (var effect in CliDB.SpellEffectStorage.Values)
            {
                /*Contract.Assert(effect.EffectIndex < MAX_SPELL_EFFECTS, "MAX_SPELL_EFFECTS must be at least {0}", effect.EffectIndex);
                Contract.Assert(effect.Effect < TOTAL_SPELL_EFFECTS, "TOTAL_SPELL_EFFECTS must be at least {0}", effect.Effect);
                Contract.Assert(effect.EffectAura < TOTAL_AURAS, "TOTAL_AURAS must be at least {0}", effect.EffectAura);
                Contract.Assert(effect.ImplicitTarget[0] < TOTAL_SPELL_TARGETS, "TOTAL_SPELL_TARGETS must be at least {0}", effect.ImplicitTarget[0]);
                Contract.Assert(effect.ImplicitTarget[1] < TOTAL_SPELL_TARGETS, "TOTAL_SPELL_TARGETS must be at least {0}", effect.ImplicitTarget[1]);*/

                if (!effectsBySpell.ContainsKey(effect.SpellID))
                    effectsBySpell[effect.SpellID] = new Dictionary<uint, SpellEffectRecord[]>();

                if(effect.DifficultyID > (int)Difficulty.Max)
                {

                }

                if (!effectsBySpell[effect.SpellID].ContainsKey(effect.DifficultyID))
                    effectsBySpell[effect.SpellID][effect.DifficultyID] = new SpellEffectRecord[SpellConst.MaxEffects];

                effectsBySpell[effect.SpellID][effect.DifficultyID][effect.EffectIndex] = effect;
            }

            foreach (var spell in CliDB.SpellStorage.Values)
                loadData[spell.Id] = new SpellInfoLoadHelper();

            foreach (SpellAuraOptionsRecord auraOptions in CliDB.SpellAuraOptionsStorage.Values)
                if (auraOptions.DifficultyID == 0)    // TODO: implement
                    loadData[auraOptions.SpellID].AuraOptions = auraOptions;

            foreach (SpellAuraRestrictionsRecord auraRestrictions in CliDB.SpellAuraRestrictionsStorage.Values)
                if (auraRestrictions.DifficultyID == 0)    // TODO: implement
                    loadData[auraRestrictions.SpellID].AuraRestrictions = auraRestrictions;

            foreach (SpellCastingRequirementsRecord castingRequirements in CliDB.SpellCastingRequirementsStorage.Values)
                loadData[castingRequirements.SpellID].CastingRequirements = castingRequirements;

            foreach (SpellCategoriesRecord categories in CliDB.SpellCategoriesStorage.Values)
                if (categories.DifficultyID == 0)  // TODO: implement
                    loadData[categories.SpellID].Categories = categories;

            foreach (SpellClassOptionsRecord classOptions in CliDB.SpellClassOptionsStorage.Values)
                loadData[classOptions.SpellID].ClassOptions = classOptions;

            foreach (SpellCooldownsRecord cooldowns in CliDB.SpellCooldownsStorage.Values)
                if (cooldowns.DifficultyID == 0)   // TODO: implement
                    loadData[cooldowns.SpellID].Cooldowns = cooldowns;

            foreach (SpellEffectScalingRecord spellEffectScaling in CliDB.SpellEffectScalingStorage.Values)
                spellEffectScallingByEffectId[spellEffectScaling.SpellEffectID] = spellEffectScaling;

            foreach (SpellEquippedItemsRecord equippedItems in CliDB.SpellEquippedItemsStorage.Values)
                loadData[equippedItems.SpellID].EquippedItems = equippedItems;

            foreach (SpellInterruptsRecord interrupts in CliDB.SpellInterruptsStorage.Values)
                if (interrupts.DifficultyID == 0)  // TODO: implement
                    loadData[interrupts.SpellID].Interrupts = interrupts;

            foreach (SpellLevelsRecord levels in CliDB.SpellLevelsStorage.Values)
                if (levels.DifficultyID == 0)  // TODO: implement
                    loadData[levels.SpellID].Levels = levels;

            foreach (SpellReagentsRecord reagents in CliDB.SpellReagentsStorage.Values)
                loadData[reagents.SpellID].Reagents = reagents;

            foreach (SpellScalingRecord scaling in CliDB.SpellScalingStorage.Values)
                loadData[scaling.SpellID].Scaling = scaling;

            foreach (SpellShapeshiftRecord shapeshift in CliDB.SpellShapeshiftStorage.Values)
                loadData[shapeshift.SpellID].Shapeshift = shapeshift;

            foreach (SpellTargetRestrictionsRecord targetRestrictions in CliDB.SpellTargetRestrictionsStorage.Values)
                if (targetRestrictions.DifficultyID == 0)  // TODO: implement
                    loadData[targetRestrictions.SpellID].TargetRestrictions = targetRestrictions;

            foreach (SpellTotemsRecord totems in CliDB.SpellTotemsStorage.Values)
                loadData[totems.SpellID].Totems = totems;

            foreach (var visual in CliDB.SpellXSpellVisualStorage.Values)
            {
                if (!visualsBySpell.ContainsKey(visual.SpellID))
                    visualsBySpell[visual.SpellID] = new MultiMap<uint, SpellXSpellVisualRecord>();

                visualsBySpell[visual.SpellID].Add(visual.DifficultyID, visual);
            }

            foreach (var spellEntry in CliDB.SpellStorage.Values)
            {
                loadData[spellEntry.Id].Entry = spellEntry;
                loadData[spellEntry.Id].Misc = CliDB.SpellMiscStorage.LookupByKey(spellEntry.MiscID);
                mSpellInfoMap[spellEntry.Id] = new SpellInfo(loadData[spellEntry.Id], effectsBySpell.LookupByKey(spellEntry.Id), visualsBySpell.LookupByKey(spellEntry.Id), spellEffectScallingByEffectId);
            }

            Log.outInfo(LogFilter.ServerLoading, "Loaded SpellInfo store in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void UnloadSpellInfoImplicitTargetConditionLists()
        {
            foreach (var spell in mSpellInfoMap.Values)
                spell._UnloadImplicitTargetConditionLists();

        }

        public void LoadSpellInfoCustomAttributes()
        {
            uint oldMSTime = Time.GetMSTime();
            uint oldMSTime2 = oldMSTime;

            SQLResult result = DB.World.Query("SELECT entry, attributes FROM spell_custom_attr");
            if (result.IsEmpty())
                Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell custom attributes from DB. DB table `spell_custom_attr` is empty.");
            else
            {
                uint count = 0;
                do
                {
                    uint spellId = result.Read<uint>(0);
                    uint attributes = result.Read<uint>(1);

                    SpellInfo spellInfo = GetSpellInfo(spellId);
                    if (spellInfo == null)
                    {
                        Log.outError(LogFilter.Sql, "Table `spell_custom_attr` has wrong spell (entry: {0}), ignored.", spellId);
                        continue;
                    }

                    // TODO: validate attributes
                    if (attributes.HasAnyFlag((uint)SpellCustomAttributes.ShareDamage))
                    {
                        if (!spellInfo.HasEffect(SpellEffectName.SchoolDamage))
                        {
                            Log.outError(LogFilter.Sql, "Spell {0} listed in table `spell_custom_attr` with SPELL_ATTR0_CU_SHARE_DAMAGE has no SPELL_EFFECT_SCHOOL_DAMAGE, ignored.", spellId);
                            continue;
                        }
                    }

                    spellInfo.AttributesCu |= (SpellCustomAttributes)attributes;
                    ++count;
                } while (result.NextRow());

                Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell custom attributes from DB in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime2));
            }

            List<uint> talentSpells = new List<uint>();
            foreach (var talentInfo in CliDB.TalentStorage.Values)
                talentSpells.Add(talentInfo.SpellID);

            foreach (var spellInfo in mSpellInfoMap.Values)
            {
                foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None))
                {
                    if (effect == null)
                        continue;

                    switch (effect.ApplyAuraName)
                    {
                        case AuraType.PeriodicHeal:
                        case AuraType.PeriodicDamage:
                        case AuraType.PeriodicDamagePercent:
                        case AuraType.PeriodicLeech:
                        case AuraType.PeriodicManaLeech:
                        case AuraType.PeriodicHealthFunnel:
                        case AuraType.PeriodicEnergize:
                        case AuraType.ObsModHealth:
                        case AuraType.ObsModPower:
                        case AuraType.PowerBurn:
                            spellInfo.AttributesCu |= SpellCustomAttributes.NoInitialThreat;
                            break;
                    }

                    switch (effect.Effect)
                    {
                        case SpellEffectName.SchoolDamage:
                        case SpellEffectName.WeaponDamage:
                        case SpellEffectName.WeaponDamageNoschool:
                        case SpellEffectName.NormalizedWeaponDmg:
                        case SpellEffectName.WeaponPercentDamage:
                        case SpellEffectName.Heal:
                            spellInfo.AttributesCu |= SpellCustomAttributes.DirectDamage;
                            break;
                        case SpellEffectName.PowerDrain:
                        case SpellEffectName.PowerBurn:
                        case SpellEffectName.HealMaxHealth:
                        case SpellEffectName.HealthLeech:
                        case SpellEffectName.HealPct:
                        case SpellEffectName.EnergizePct:
                        case SpellEffectName.Energize:
                        case SpellEffectName.HealMechanical:
                            spellInfo.AttributesCu |= SpellCustomAttributes.NoInitialThreat;
                            break;
                        case SpellEffectName.Charge:
                        case SpellEffectName.ChargeDest:
                        case SpellEffectName.Jump:
                        case SpellEffectName.JumpDest:
                        case SpellEffectName.LeapBack:
                            spellInfo.AttributesCu |= SpellCustomAttributes.Charge;
                            break;
                        case SpellEffectName.Pickpocket:
                            spellInfo.AttributesCu |= SpellCustomAttributes.PickPocket;
                            break;
                        case SpellEffectName.EnchantItem:
                        case SpellEffectName.EnchantItemTemporary:
                        case SpellEffectName.EnchantItemPrismatic:
                        case SpellEffectName.EnchantHeldItem:
                            {
                                // only enchanting profession enchantments procs can stack
                                if (IsPartOfSkillLine(SkillType.Enchanting, spellInfo.Id))
                                {
                                    uint enchantId = (uint)effect.MiscValue;
                                    var enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchantId);
                                    for (var s = 0; s < ItemConst.MaxItemEnchantmentEffects; ++s)
                                    {
                                        if (enchant.Effect[s] != ItemEnchantmentType.CombatSpell)
                                            continue;

                                        SpellInfo procInfo = GetSpellInfo(enchant.EffectSpellID[s]);
                                        if (procInfo == null)
                                            continue;

                                        // if proced directly from enchantment, not via proc aura
                                        // NOTE: Enchant Weapon - Blade Ward also has proc aura spell and is proced directly
                                        // however its not expected to stack so this check is good
                                        if (procInfo.HasAura(Difficulty.None, AuraType.ProcTriggerSpell))
                                            continue;

                                        procInfo.AttributesCu |= SpellCustomAttributes.EnchantProc;
                                    }
                                }
                                break;
                            }
                    }
                }

                if (!spellInfo._IsPositiveEffect(0, false))
                    spellInfo.AttributesCu |= SpellCustomAttributes.NegativeEff0;

                if (!spellInfo._IsPositiveEffect(1, false))
                    spellInfo.AttributesCu |= SpellCustomAttributes.NegativeEff1;

                if (!spellInfo._IsPositiveEffect(2, false))
                    spellInfo.AttributesCu |= SpellCustomAttributes.NegativeEff2;

                if (spellInfo.GetSpellVisual() == 3879)
                    spellInfo.AttributesCu |= SpellCustomAttributes.ConeBack;

                if (talentSpells.Contains(spellInfo.Id))
                    spellInfo.AttributesCu |= SpellCustomAttributes.IsTalent;

                spellInfo._InitializeExplicitTargetMask();
            }

            Log.outInfo(LogFilter.ServerLoading, "Loaded spell custom attributes in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void LoadSpellInfoCorrections()
        {
            uint oldMSTime = Time.GetMSTime();

            foreach (var spellInfo in mSpellInfoMap.Values)
            {
                foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None))
                {
                    if (effect == null)
                        continue;

                    switch (effect.Effect)
                    {
                        case SpellEffectName.Charge:
                        case SpellEffectName.ChargeDest:
                        case SpellEffectName.Jump:
                        case SpellEffectName.JumpDest:
                        case SpellEffectName.LeapBack:
                            if (spellInfo.Speed == 0 && spellInfo.SpellFamilyName == 0)
                                spellInfo.Speed = MotionMaster.SPEED_CHARGE;
                            break;
                    }
                }

                if (spellInfo.ActiveIconFileDataId == 135754)  // flight
                    spellInfo.Attributes |= SpellAttr0.Passive;

                switch (spellInfo.Id)
                {
                    case 63026: // Summon Aspirant Test NPC (HACK: Target shouldn't be changed)
                    case 63137: // Summon Valiant Test (HACK: Target shouldn't be changed; summon position should be untied from spell destination)
                        spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.DestDb);
                        break;
                    case 42436: // Drink! (Brewfest)
                        spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitAny);
                        break;
                    case 52611: // Summon Skeletons
                    case 52612: // Summon Skeletons
                        spellInfo.GetEffect(0).MiscValueB = 64;
                        break;
                    case 40244: // Simon Game Visual
                    case 40245: // Simon Game Visual
                    case 40246: // Simon Game Visual
                    case 40247: // Simon Game Visual
                    case 42835: // Spout, remove damage effect, only anim is needed
                        spellInfo.GetEffect(0).Effect = 0;
                        break;
                    case 30657: // Quake
                        spellInfo.GetEffect(0).TriggerSpell = 30571;
                        break;
                    case 30541: // Blaze (needs conditions entry)
                        spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitEnemy);
                        spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo();
                        break;
                    case 63665: // Charge (Argent Tournament emote on riders)
                    case 31298: // Sleep (needs target selection script)
                    case 51904: // Summon Ghouls On Scarlet Crusade (this should use conditions table, script for this spell needs to be fixed)
                    case 68933: // Wrath of Air Totem rank 2 (Aura)
                    case 29200: // Purify Helboar Meat
                        spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitCaster);
                        spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo();
                        break;
                    case 31344: // Howl of Azgalor
                        spellInfo.GetEffect(0).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards100); // 100yards instead of 50000?!
                        break;
                    case 42818: // Headless Horseman - Wisp Flight Port
                    case 42821: // Headless Horseman - Wisp Flight Missile
                        spellInfo.RangeEntry = CliDB.SpellRangeStorage.LookupByKey(6); // 100 yards
                        break;
                    case 36350: //They Must Burn Bomb Aura (self)
                        spellInfo.GetEffect(0).TriggerSpell = 36325; // They Must Burn Bomb Drop (DND)
                        break;
                    case 61407: // Energize Cores
                    case 62136: // Energize Cores
                    case 54069: // Energize Cores
                    case 56251: // Energize Cores
                        spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitSrcAreaEntry);
                        break;
                    case 50785: // Energize Cores
                    case 59372: // Energize Cores
                        spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitSrcAreaEnemy);
                        break;
                    case 63320: // Glyph of Life Tap
                        // Entries were not updated after spell effect change, we have to do that manually :/
                        spellInfo.AttributesEx3 |= SpellAttr3.CanProcWithTriggered;
                        break;
                    case 5308: // Execute
                        spellInfo.AttributesEx3 |= SpellAttr3.CantTriggerProc;
                        break;
                    case 31347: // Doom
                    case 36327: // Shoot Arcane Explosion Arrow
                    case 39365: // Thundering Storm
                    case 41071: // Raise Dead (HACK)
                    case 42442: // Vengeance Landing Cannonfire
                    case 42611: // Shoot
                    case 44978: // Wild Magic
                    case 45001: // Wild Magic
                    case 45002: // Wild Magic
                    case 45004: // Wild Magic
                    case 45006: // Wild Magic
                    case 45010: // Wild Magic
                    case 45761: // Shoot Gun
                    case 45863: // Cosmetic - Incinerate to Random Target
                    case 48246: // Ball of Flame
                    case 41635: // Prayer of Mending
                    case 44869: // Spectral Blast
                    case 45027: // Revitalize
                    case 45976: // Muru Portal Channel
                    case 52124: // Sky Darkener Assault
                    case 52479: // Gift of the Harvester
                    case 61588: // Blazing Harpoon
                    case 55479: // Force Obedience
                    case 28560: // Summon Blizzard (Sapphiron)
                    case 53096: // Quetz'lun's Judgment
                    case 70743: // AoD Special
                    case 70614: // AoD Special - Vegard
                    case 4020: // Safirdrang's Chill
                    case 52438: // Summon Skittering Swarmer (Force Cast)
                    case 52449: // Summon Skittering Infector (Force Cast)
                    case 53609: // Summon Anub'ar Assassin (Force Cast)
                    case 53457: // Summon Impale Trigger (AoE)
                        spellInfo.MaxAffectedTargets = 1;
                        break;
                    case 36384: // Skartax Purple Beam
                        spellInfo.MaxAffectedTargets = 2;
                        break;
                    case 28542: // Life Drain - Sapphiron
                    case 29213: // Curse of the Plaguebringer - Noth
                    case 29576: // Multi-Shot
                    case 37790: // Spread Shot
                    case 39992: // Needle Spine
                    case 40816: // Saber Lash
                    case 41303: // Soul Drain
                    case 41376: // Spite
                    case 45248: // Shadow Blades
                    case 46771: // Flame Sear
                    case 66588: // Flaming Spear
                        spellInfo.MaxAffectedTargets = 3;
                        break;
                    case 38310: // Multi-Shot
                    case 53385: // Divine Storm (Damage)
                        spellInfo.MaxAffectedTargets = 4;
                        break;
                    case 42005: // Bloodboil
                    case 38296: // Spitfire Totem
                    case 37676: // Insidious Whisper
                    case 46008: // Negative Energy
                    case 45641: // Fire Bloom
                    case 55665: // Life Drain - Sapphiron (H)
                    case 28796: // Poison Bolt Volly - Faerlina
                        spellInfo.MaxAffectedTargets = 5;
                        break;
                    case 54835: // Curse of the Plaguebringer - Noth (H)
                        spellInfo.MaxAffectedTargets = 8;
                        break;
                    case 40827: // Sinful Beam
                    case 40859: // Sinister Beam
                    case 40860: // Vile Beam
                    case 40861: // Wicked Beam
                    case 54098: // Poison Bolt Volly - Faerlina (H)
                        spellInfo.MaxAffectedTargets = 10;
                        break;
                    case 50312: // Unholy Frenzy
                        spellInfo.MaxAffectedTargets = 15;
                        break;
                    case 33711: // Murmur's Touch
                    case 38794:
                        spellInfo.MaxAffectedTargets = 1;
                        spellInfo.GetEffect(0).TriggerSpell = 33760;
                        break;
                    case 17941: // Shadow Trance
                    case 22008: // Netherwind Focus
                    case 34477: // Misdirection
                    case 48108: // Hot Streak
                    case 51124: // Killing Machine
                    case 57761: // Fireball!
                    case 64823: // Item - Druid T8 Balance 4P Bonus
                    case 88819: // Daybreak
                        spellInfo.ProcCharges = 1;
                        break;
                    case 44544: // Fingers of Frost
                        spellInfo.GetEffect(0).SpellClassMask = new FlagArray128(685904631, 1151048, 0, 0);
                        break;
                    case 53257: // Cobra Strikes
                        spellInfo.ProcCharges = 2;
                        spellInfo.StackAmount = 0;
                        break;
                    case 28200: // Ascendance (Talisman of Ascendance trinket)
                        spellInfo.ProcCharges = 6;
                        break;
                    case 37408: // Oscillation Field
                        spellInfo.AttributesEx3 |= SpellAttr3.StackForDiffCasters;
                        break;
                    case 51852: // The Eye of Acherus (no spawn in phase 2 in db)
                        spellInfo.GetEffect(0).MiscValue |= 1;
                        break;
                    case 51912: // Crafty's Ultra-Advanced Proto-Typical Shortening Blaster
                        spellInfo.GetEffect(0).ApplyAuraPeriod = 3000;
                        break;
                    case 30421: // Nether Portal - Perseverence
                        spellInfo.GetEffect(2).BasePoints += 30000;
                        break;
                    case 41913: // Parasitic Shadowfiend Passive
                        spellInfo.GetEffect(0).ApplyAuraName = AuraType.Dummy; // proc debuff, and summon infinite fiends
                        break;
                    case 27892: // To Anchor 1
                    case 27928: // To Anchor 1
                    case 27935: // To Anchor 1
                    case 27915: // Anchor to Skulls
                    case 27931: // Anchor to Skulls
                    case 27937: // Anchor to Skulls
                        spellInfo.RangeEntry = CliDB.SpellRangeStorage.LookupByKey(13);
                        break;
                    // target allys instead of enemies, target A is src_caster, spells with effect like that have ally target
                    // this is the only known exception, probably just wrong data
                    case 29214: // Wrath of the Plaguebringer
                    case 54836: // Wrath of the Plaguebringer
                        spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo(Targets.UnitSrcAreaAlly);
                        spellInfo.GetEffect(1).TargetB = new SpellImplicitTargetInfo(Targets.UnitSrcAreaAlly);
                        break;
                    case 15290: // Vampiric Embrace
                        spellInfo.AttributesEx3 |= SpellAttr3.NoInitialAggro;
                        break;
                    case 8145: // Tremor Totem (instant pulse)
                        spellInfo.AttributesEx2 |= SpellAttr2.CanTargetNotInLos;
                        spellInfo.AttributesEx5 |= SpellAttr5.StartPeriodicAtApply;
                        break;
                    case 6474: // Earthbind Totem (instant pulse)
                        spellInfo.AttributesEx5 |= SpellAttr5.StartPeriodicAtApply;
                        break;
                    case 70728: // Exploit Weakness (needs target selection script)
                    case 70840: // Devious Minds (needs target selection script)
                        spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitCaster);
                        spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo(Targets.UnitPet);
                        break;
                    case 45602: // Ride Carpet
                        spellInfo.GetEffect(0).BasePoints = 0;// force seat 0, vehicle doesn't have the required seat flags for "no seat specified (-1)"
                        break;
                    case 61719: // Easter Lay Noblegarden Egg Aura - Interrupt flags copied from aura which this aura is linked with
                        spellInfo.AuraInterruptFlags = SpellAuraInterruptFlags.Hitbyspell | SpellAuraInterruptFlags.TakeDamage;
                        break;
                    case 71838: // Drain Life - Bryntroll Normal
                    case 71839: // Drain Life - Bryntroll Heroic
                        spellInfo.AttributesEx2 |= SpellAttr2.CantCrit;
                        break;
                    case 56606: // Ride Jokkum
                    case 61791: // Ride Vehicle (Yogg-Saron)
                                /// @todo: remove this when basepoints of all Ride Vehicle auras are calculated correctly
                        spellInfo.GetEffect(0).BasePoints = 1;
                        break;
                    case 59630: // Black Magic
                        spellInfo.Attributes |= SpellAttr0.Passive;
                        break;
                    case 48278: // Paralyze
                        spellInfo.AttributesEx3 |= SpellAttr3.StackForDiffCasters;
                        break;
                    case 51798: // Brewfest - Relay Race - Intro - Quest Complete
                    case 47134: // Quest Complete
                                //! HACK: This spell break quest complete for alliance and on retail not used °_O
                        spellInfo.GetEffect(0).Effect = 0;
                        break;
                    case 85123: // Siege Cannon (Tol Barad)
                        spellInfo.GetEffect(0).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards200);
                        spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitSrcAreaEntry);
                        break;
                    case 198300: // Gathering Storms
                        spellInfo.ProcCharges = 1; // override proc charges, has 0 (unlimited) in db2
                        break;
                    case 42490: // Energized!
                    case 42492: // Cast Energized
                    case 43115: // Plague Vial
                        spellInfo.AttributesEx |= SpellAttr1.NoThreat;
                        break;
                    case 29726: // Test Ribbon Pole Channel
                        spellInfo.InterruptFlags &= ~SpellInterruptFlags.Interrupt;//AURA_INTERRUPT_FLAG_CAST
                        break;
                    case 42767: // Sic'em
                        spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitNearbyEntry);
                        break;
                    // VIOLET HOLD SPELLS
                    //
                    case 54258: // Water Globule (Ichoron)
                    case 54264: // Water Globule (Ichoron)
                    case 54265: // Water Globule (Ichoron)
                    case 54266: // Water Globule (Ichoron)
                    case 54267: // Water Globule (Ichoron)
                                // in 3.3.5 there is only one radius in dbc which is 0 yards in this case
                                // use max radius from 4.3.4
                        spellInfo.GetEffect(0).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards25);
                        break;
                    // ENDOF VIOLET HOLD
                    //
                    // ULDUAR SPELLS
                    //
                    case 62374: // Pursued (Flame Leviathan)
                        spellInfo.GetEffect(0).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards50000);   // 50000yd
                        break;
                    case 63342: // Focused Eyebeam Summon Trigger (Kologarn)
                        spellInfo.MaxAffectedTargets = 1;
                        break;
                    case 65584: // Growth of Nature (Freya)
                    case 64381: // Strength of the Pack (Auriaya)
                        spellInfo.AttributesEx3 |= SpellAttr3.StackForDiffCasters;
                        break;
                    case 63018: // Searing Light (XT-002)
                    case 65121: // Searing Light (25m) (XT-002)
                    case 63024: // Gravity Bomb (XT-002)
                    case 64234: // Gravity Bomb (25m) (XT-002)
                        spellInfo.MaxAffectedTargets = 1;
                        break;
                    case 62834: // Boom (XT-002)
                                // This hack is here because we suspect our implementation of spell effect execution on targets
                                // is done in the wrong order. We suspect that 0 needs to be applied on all targets,
                                // then 1, etc - instead of applying each effect on target1, then target2, etc.
                                // The above situation causes the visual for this spell to be bugged, so we remove the instakill
                                // effect and implement a script hack for that.
                        spellInfo.GetEffect(1).Effect = 0;
                        break;
                    case 64386: // Terrifying Screech (Auriaya)
                    case 64389: // Sentinel Blast (Auriaya)
                    case 64678: // Sentinel Blast (Auriaya)
                        spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(28); // 5 seconds, wrong DBC data?
                        break;
                    case 64321: // Potent Pheromones (Freya)
                                // spell should dispel area aura, but doesn't have the attribute
                                // may be db data bug, or blizz may keep reapplying area auras every update with checking immunity
                                // that will be clear if we get more spells with problem like this
                        spellInfo.AttributesEx |= SpellAttr1.DispelAurasOnImmunity;
                        break;
                    case 63414: // Spinning Up (Mimiron)
                        spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo(Targets.UnitCaster);
                        spellInfo.ChannelInterruptFlags = 0;
                        break;
                    case 63036: // Rocket Strike (Mimiron)
                        spellInfo.Speed = 0;
                        break;
                    case 64668: // Magnetic Field (Mimiron)
                        spellInfo.Mechanic = Mechanics.None;
                        break;
                    case 64468: // Empowering Shadows (Yogg-Saron)
                    case 64486: // Empowering Shadows (Yogg-Saron)
                        spellInfo.MaxAffectedTargets = 3;  // same for both modes?
                        break;
                    case 62301: // Cosmic Smash (Algalon the Observer)
                        spellInfo.MaxAffectedTargets = 1;
                        break;
                    case 64598: // Cosmic Smash (Algalon the Observer)
                        spellInfo.MaxAffectedTargets = 3;
                        break;
                    case 62293: // Cosmic Smash (Algalon the Observer)
                        spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo(Targets.DestCaster);
                        break;
                    case 62311: // Cosmic Smash (Algalon the Observer)
                    case 64596: // Cosmic Smash (Algalon the Observer)
                        spellInfo.RangeEntry = CliDB.SpellRangeStorage.LookupByKey(6);  // 100yd
                        break;
                    case 64014: // Expedition Base Camp Teleport
                    case 64024: // Conservatory Teleport
                    case 64025: // Halls of Invention Teleport
                    case 64028: // Colossal Forge Teleport
                    case 64029: // Shattered Walkway Teleport
                    case 64030: // Antechamber Teleport
                    case 64031: // Scrapyard Teleport
                    case 64032: // Formation Grounds Teleport
                    case 65042: // Prison of Yogg-Saron Teleport
                        spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.DestDb);
                        break;
                    // ENDOF ULDUAR SPELLS
                    //
                    // TRIAL OF THE CRUSADER SPELLS
                    //
                    case 66258: // Infernal Eruption
                        // increase duration from 15 to 18 seconds because caster is already
                        // unsummoned when spell missile hits the ground so nothing happen in result
                        spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(85);
                        break;
                    // ENDOF TRIAL OF THE CRUSADER SPELLS
                    //
                    // ICECROWN CITADEL SPELLS
                    //
                    // THESE SPELLS ARE WORKING CORRECTLY EVEN WITHOUT THIS HACK
                    // THE ONLY REASON ITS HERE IS THAT CURRENT GRID SYSTEM
                    // DOES NOT ALLOW FAR OBJECT SELECTION (dist > 333)
                    case 70781: // Light's Hammer Teleport
                    case 70856: // Oratory of the Damned Teleport
                    case 70857: // Rampart of Skulls Teleport
                    case 70858: // Deathbringer's Rise Teleport
                    case 70859: // Upper Spire Teleport
                    case 70860: // Frozen Throne Teleport
                    case 70861: // Sindragosa's Lair Teleport
                        spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.DestDb);
                        break;
                    case 71169: // Shadow's Fate
                        spellInfo.AttributesEx3 |= SpellAttr3.StackForDiffCasters;
                        break;
                    case 72347: // Lock Players and Tap Chest
                        spellInfo.AttributesEx3 &= ~SpellAttr3.NoInitialAggro;
                        break;
                    case 72723: // Resistant Skin (Deathbringer Saurfang adds)
                        // this spell initially granted Shadow damage immunity, however it was removed but the data was left in client
                        spellInfo.GetEffect(2).Effect = 0;
                        break;
                    case 70460: // Coldflame Jets (Traps after Saurfang)
                        spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(1); // 10 seconds
                        break;
                    case 71412: // Green Ooze Summon (Professor Putricide)
                    case 71415: // Orange Ooze Summon (Professor Putricide)
                        spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitAny);
                        break;
                    case 71159: // Awaken Plagued Zombies
                        spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(21);
                        break;
                    case 70530: // Volatile Ooze Beam Protection (Professor Putricide)
                        spellInfo.GetEffect(0).Effect = SpellEffectName.ApplyAura; // for an unknown reason this was SPELL_EFFECT_APPLY_AREA_AURA_RAID
                        break;
                    // THIS IS HERE BECAUSE COOLDOWN ON CREATURE PROCS IS NOT IMPLEMENTED
                    case 71604: // Mutated Strength (Professor Putricide)
                        spellInfo.GetEffect(1).Effect = 0;
                        break;
                    case 70911: // Unbound Plague (Professor Putricide) (needs target selection script)
                        spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo(Targets.UnitEnemy);
                        break;
                    case 71708: // Empowered Flare (Blood Prince Council)
                        spellInfo.AttributesEx3 |= SpellAttr3.NoDoneBonus;
                        break;
                    case 71266: // Swarming Shadows
                        spellInfo.RequiredAreasID = 0; // originally, these require area 4522, which is... outside of Icecrown Citadel
                        break;
                    case 70602: // Corruption
                        spellInfo.AttributesEx3 |= SpellAttr3.StackForDiffCasters;
                        break;
                    case 70715: // Column of Frost (visual marker)
                        spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(32); // 6 seconds (missing)
                        break;
                    case 71085: // Mana Void (periodic aura)
                        spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(9); // 30 seconds (missing)
                        break;
                    case 70936: // Summon Suppressor (needs target selection script)
                        spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitAny);
                        spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo();
                        break;
                    case 70598: // Sindragosa's Fury
                        spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.DestDest);
                        break;
                    case 69846: // Frost Bomb
                        spellInfo.Speed = 0.0f;    // This spell's summon happens instantly
                        break;
                    case 71614: // Ice Lock
                        spellInfo.Mechanic = Mechanics.Stun;
                        break;
                    case 72762: // Defile
                        spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(559); // 53 seconds
                        break;
                    case 72743: // Defile
                        spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(22); // 45 seconds
                        break;
                    case 72754: // Defile
                        spellInfo.GetEffect(0).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards200); // 200yd
                        spellInfo.GetEffect(1).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards200); // 200yd
                        break;
                    case 69198: // Raging Spirit Visual
                        spellInfo.RangeEntry = CliDB.SpellRangeStorage.LookupByKey(13); // 50000yd
                        break;
                    case 73655: // Harvest Soul
                        spellInfo.AttributesEx3 |= SpellAttr3.NoDoneBonus;
                        break;
                    case 73540: // Summon Shadow Trap
                        spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(23); // 90 seconds
                        break;
                    case 73530: // Shadow Trap (visual)
                        spellInfo.DurationEntry = CliDB.SpellDurationStorage.LookupByKey(28); // 5 seconds
                        break;
                    case 74302: // Summon Spirit Bomb
                        spellInfo.MaxAffectedTargets = 2;
                        break;
                    case 73579: // Summon Spirit Bomb
                        spellInfo.GetEffect(0).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards25); // 25yd
                        break;
                    case 72376: // Raise Dead
                        spellInfo.MaxAffectedTargets = 3;
                        break;
                    case 71809: // Jump
                        spellInfo.RangeEntry = CliDB.SpellRangeStorage.LookupByKey(3); // 20yd
                        spellInfo.GetEffect(0).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards25); // 25yd
                        break;
                    case 72405: // Broken Frostmourne
                        spellInfo.GetEffect(1).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards200); // 200yd
                        break;
                    // ENDOF ICECROWN CITADEL SPELLS
                    //
                    // RUBY SANCTUM SPELLS
                    //
                    case 74799: // Soul Consumption
                        spellInfo.GetEffect(1).RadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards12);
                        break;
                    case 75509: // Twilight Mending
                        spellInfo.AttributesEx6 |= SpellAttr6.CanTargetInvisible;
                        spellInfo.AttributesEx2 |= SpellAttr2.CanTargetNotInLos;
                        break;
                    case 75888: // Awaken Flames
                        spellInfo.AttributesEx |= SpellAttr1.CantTargetSelf;
                        break;
                    // ENDOF RUBY SANCTUM SPELLS
                    //
                    // EYE OF ETERNITY SPELLS
                    // All spells below work even without these changes. The LOS attribute is due to problem
                    // from collision between maps & gos with active destroyed state.
                    case 57473: // Arcane Storm bonus explicit visual spell
                    case 57431: // Summon Static Field
                    case 56091: // Flame Spike (Wyrmrest Skytalon)
                    case 56092: // Engulf in Flames (Wyrmrest Skytalon)
                    case 57090: // Revivify (Wyrmrest Skytalon)
                    case 57143: // Life Burst (Wyrmrest Skytalon)
                        spellInfo.AttributesEx2 |= SpellAttr2.CanTargetNotInLos;
                        break;
                    // This would never crit on retail and it has attribute for SPELL_ATTR3_NO_DONE_BONUS because is handled from player,
                    // until someone figures how to make scions not critting without hack and without making them main casters this should stay here.
                    case 63934: // Arcane Barrage (cast by players and NONMELEEDAMAGELOG with caster Scion of Eternity (original caster)).
                        spellInfo.AttributesEx2 |= SpellAttr2.CantCrit;
                        break;
                    // ENDOF EYE OF ETERNITY SPELLS
                    //
                    case 40055: // Introspection
                    case 40165: // Introspection
                    case 40166: // Introspection
                    case 40167: // Introspection
                        spellInfo.Attributes |= SpellAttr0.Negative1;
                        break;
                    // Stonecore spells
                    case 95284: // Teleport (from entrance to Slabhide)
                    case 95285: // Teleport (from Slabhide to entrance)
                        spellInfo.GetEffect(0).TargetB = new SpellImplicitTargetInfo(Targets.DestDb);
                        break;
                    // Halls Of Origination spells
                    // Temple Guardian Anhuur
                    case 76606: // Disable Beacon Beams L
                    case 76608: // Disable Beacon Beams R
                                // Little hack, Increase the radius so it can hit the Cave In Stalkers in the platform.
                        spellInfo.GetEffect(0).MaxRadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards45);
                        break;
                    case 75323: // Reverberating Hymn
                                // Aura is refreshed at 3 seconds, and the tick should happen at the fourth.
                        spellInfo.AttributesEx8 |= SpellAttr8.DontResetPeriodicTimer;
                        break;
                    case 24314: // Threatening Gaze
                        spellInfo.AuraInterruptFlags |= SpellAuraInterruptFlags.Cast | SpellAuraInterruptFlags.Move | SpellAuraInterruptFlags.Jump;
                        break;
                    case 5420: // Tree of Life (Passive)
                        spellInfo.Stances = 1 << ((int)ShapeShiftForm.TreeOfLife - 1);
                        break;
                    case 49376: // Feral Charge (Cat Form)
                        spellInfo.AttributesEx3 &= ~SpellAttr3.CantTriggerProc;
                        break;
                    case 96942:  // Gaze of Occu'thar
                        spellInfo.AttributesEx &= ~SpellAttr1.Channeled1;
                        break;
                    case 75610: // Evolution
                        spellInfo.MaxAffectedTargets = 1;
                        break;
                    case 75697: // Evolution
                        spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.UnitSrcAreaEntry);
                        break;
                    // ISLE OF CONQUEST SPELLS
                    //
                    case 66551: // Teleport
                        spellInfo.RangeEntry = CliDB.SpellRangeStorage.LookupByKey(13); // 50000yd
                        break;
                    // ENDOF ISLE OF CONQUEST SPELLS
                    //
                    case 102445: // Summon Master Li Fei
                        spellInfo.GetEffect(0).TargetA = new SpellImplicitTargetInfo(Targets.DestDb);
                        break;
                }
            }

            SummonPropertiesRecord properties = CliDB.SummonPropertiesStorage.LookupByKey(121);
            if (properties != null)
                properties.Type = SummonType.Totem;
            properties = CliDB.SummonPropertiesStorage.LookupByKey(647); // 52893
            if (properties != null)
                properties.Type = SummonType.Totem;
            properties = CliDB.SummonPropertiesStorage.LookupByKey(628);
            if (properties != null) // Hungry Plaguehound
                properties.Category = SummonCategory.Pet;

            Log.outInfo(LogFilter.ServerLoading, "Loaded SpellInfo corrections in {0} ms", Time.GetMSTimeDiffToNow(oldMSTime));
        }

        public void LoadPetFamilySpellsStore()
        {
            Dictionary<uint, SpellLevelsRecord> levelsBySpell = new Dictionary<uint, SpellLevelsRecord>();
            foreach (SpellLevelsRecord levels in CliDB.SpellLevelsStorage.Values)
                if (levels.DifficultyID == 0)
                    levelsBySpell[levels.SpellID] = levels;

            foreach (var skillLine in CliDB.SkillLineAbilityStorage.Values)
            {
                SpellInfo spellInfo = GetSpellInfo(skillLine.SpellID);
                if (spellInfo == null)
                    continue;

                var levels = levelsBySpell.LookupByKey(skillLine.SpellID);
                if (levels != null && levels.SpellLevel != 0)
                    continue;

                if (spellInfo.IsPassive())
                {
                    foreach (CreatureFamilyRecord cFamily in CliDB.CreatureFamilyStorage.Values)
                    {
                        if (skillLine.SkillLine != cFamily.SkillLine[0] && skillLine.SkillLine != cFamily.SkillLine[1])
                            continue;

                        if (skillLine.AcquireMethod != AbilytyLearnType.OnSkillLearn)
                            continue;

                        Global.SpellMgr.PetFamilySpellsStorage.Add(cFamily.Id, spellInfo.Id);
                    }
                }
            }
        }
        #endregion

        // SpellInfo object management
        public SpellInfo GetSpellInfo(uint spellId)
        {
            return mSpellInfoMap.LookupByKey(spellId);
        }
        public bool HasSpellInfo(uint spellId)
        {
            return mSpellInfoMap.ContainsKey(spellId);
        }
        public Dictionary<uint, SpellInfo> GetSpellInfoStorage()
        {
            return mSpellInfoMap;
        }

        //Extra Shit
        public SpellEffectHandler GetSpellEffectHandler(SpellEffectName eff)
        {
            if (!SpellEffectsHandlers.ContainsKey(eff))
            {
                Log.outError(LogFilter.Spells, "No defined handler for SpellEffect {0}", eff);
                return SpellEffectsHandlers[SpellEffectName.Null];
            }

            return SpellEffectsHandlers[eff];
        }

        public AuraEffectHandler GetAuraEffectHandler(AuraType type)
        {
            if (!AuraEffectHandlers.ContainsKey(type))
            {
                Log.outError(LogFilter.Spells, "No defined handler for AuraEffect {0}", type);
                return AuraEffectHandlers[AuraType.None];
            }

            return AuraEffectHandlers[type];
        }

        public SkillRangeType GetSkillRangeType(SkillRaceClassInfoRecord rcEntry)
        {
            SkillLineRecord skill = CliDB.SkillLineStorage.LookupByKey(rcEntry.SkillID);
            if (skill == null)
                return SkillRangeType.None;

            if (Global.ObjectMgr.GetSkillTier(rcEntry.SkillTierID) != null)
                return SkillRangeType.Rank;

            if (rcEntry.SkillID == (uint)SkillType.Runeforging)
                return SkillRangeType.Mono;

            switch (skill.CategoryID)
            {
                case SkillCategory.Armor:
                    return SkillRangeType.Mono;
                case SkillCategory.Languages:
                    return SkillRangeType.Language;
            }
            return SkillRangeType.Level;
        }

        public bool IsPrimaryProfessionSkill(uint skill)
        {
            SkillLineRecord pSkill = CliDB.SkillLineStorage.LookupByKey(skill);
            return pSkill != null && pSkill.CategoryID == SkillCategory.Profession;
        }

        public bool IsWeaponSkill(uint skill)
        {
            var pSkill = CliDB.SkillLineStorage.LookupByKey(skill);
            return pSkill != null && pSkill.CategoryID == SkillCategory.Weapon;
        }

        public bool IsProfessionOrRidingSkill(uint skill)
        {
            return IsProfessionSkill(skill) || skill == (uint)SkillType.Riding;
        }

        public bool IsProfessionSkill(uint skill)
        {
            return IsPrimaryProfessionSkill(skill) || skill == (uint)SkillType.Fishing || skill == (uint)SkillType.Cooking || skill == (uint)SkillType.FirstAid;
        }

        public bool IsPartOfSkillLine(SkillType skillId, uint spellId)
        {
            var skillBounds = GetSkillLineAbilityMapBounds(spellId);
            if (skillBounds != null)
            {
                foreach (var skill in skillBounds)
                    if (skill.SkillLine == (uint)skillId)
                        return true;
            }

            return false;
        }

        public SpellSchools GetFirstSchoolInMask(SpellSchoolMask mask)
        {
            for (int i = 0; i < (int)SpellSchools.Max; ++i)
                if (Convert.ToBoolean((int)mask & (1 << i)))
                    return (SpellSchools)i;

            return SpellSchools.Normal;
        }

        #region Fields
        //private:
        Dictionary<uint, SpellChainNode> mSpellChains = new Dictionary<uint, SpellChainNode>();
        MultiMap<uint, uint> mSpellsReqSpell = new MultiMap<uint, uint>();
        MultiMap<uint, uint> mSpellReq = new MultiMap<uint, uint>();
        Dictionary<uint, SpellLearnSkillNode> mSpellLearnSkills = new Dictionary<uint, SpellLearnSkillNode>();
        MultiMap<uint, SpellLearnSpellNode> mSpellLearnSpells = new MultiMap<uint, SpellLearnSpellNode>();
        Dictionary<KeyValuePair<uint, uint>, SpellTargetPosition> mSpellTargetPositions = new Dictionary<KeyValuePair<uint, uint>, SpellTargetPosition>();
        MultiMap<uint, SpellGroup> mSpellSpellGroup = new MultiMap<uint, SpellGroup>();
        MultiMap<SpellGroup, int> mSpellGroupSpell = new MultiMap<SpellGroup, int>();
        Dictionary<SpellGroup, SpellGroupStackRule> mSpellGroupStack = new Dictionary<SpellGroup, SpellGroupStackRule>();
        Dictionary<uint, SpellProcEventEntry> mSpellProcEventMap = new Dictionary<uint, SpellProcEventEntry>();
        Dictionary<uint, SpellProcEntry> mSpellProcMap = new Dictionary<uint, SpellProcEntry>();
        Dictionary<uint, SpellThreatEntry> mSpellThreatMap = new Dictionary<uint, SpellThreatEntry>();
        Dictionary<uint, PetAura> mSpellPetAuraMap = new Dictionary<uint, PetAura>();
        MultiMap<int, int> mSpellLinkedMap = new MultiMap<int, int>();
        Dictionary<uint, SpellEnchantProcEntry> mSpellEnchantProcEventMap = new Dictionary<uint, SpellEnchantProcEntry>();
        Dictionary<int, bool> mEnchantCustomAttr = new Dictionary<int, bool>();
        MultiMap<uint, SpellArea> mSpellAreaMap = new MultiMap<uint, SpellArea>();
        MultiMap<uint, SpellArea> mSpellAreaForQuestMap = new MultiMap<uint, SpellArea>();
        MultiMap<uint, SpellArea> mSpellAreaForQuestEndMap = new MultiMap<uint, SpellArea>();
        MultiMap<uint, SpellArea> mSpellAreaForAuraMap = new MultiMap<uint, SpellArea>();
        MultiMap<uint, SpellArea> mSpellAreaForAreaMap = new MultiMap<uint, SpellArea>();
        MultiMap<Tuple<uint, uint>, SpellArea> mSpellAreaForQuestAreaMap = new MultiMap<Tuple<uint, uint>, SpellArea>();
        MultiMap<uint, SkillLineAbilityRecord> mSkillLineAbilityMap = new MultiMap<uint, SkillLineAbilityRecord>();
        Dictionary<uint, MultiMap<uint, uint>> mPetLevelupSpellMap = new Dictionary<uint, MultiMap<uint, uint>>();
        Dictionary<uint, PetDefaultSpellsEntry> mPetDefaultSpellsMap = new Dictionary<uint, PetDefaultSpellsEntry>();           // only spells not listed in related mPetLevelupSpellMap entry
        Dictionary<uint, SpellInfo> mSpellInfoMap = new Dictionary<uint, SpellInfo>();

        public delegate void AuraEffectHandler(AuraEffect effect, AuraApplication aurApp, AuraEffectHandleModes mode, bool apply);
        Dictionary<AuraType, AuraEffectHandler> AuraEffectHandlers = new Dictionary<AuraType, AuraEffectHandler>();
        public delegate void SpellEffectHandler(Spell spell, uint effectIndex);
        Dictionary<SpellEffectName, SpellEffectHandler> SpellEffectsHandlers = new Dictionary<SpellEffectName, SpellEffectHandler>();

        public MultiMap<uint, uint> PetFamilySpellsStorage = new MultiMap<uint, uint>();
        #endregion
    }

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public class AuraEffectHandlerAttribute : Attribute
    {
        public AuraEffectHandlerAttribute(AuraType type)
        {
            auraType = type;
        }

        public AuraType auraType { get; set; }
    }

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public class SpellEffectHandlerAttribute : Attribute
    {
        public SpellEffectHandlerAttribute(SpellEffectName effectName)
        {
            this.effectName = effectName;
        }

        public SpellEffectName effectName { get; set; }
    }

    public class SpellInfoLoadHelper
    {
        public SpellRecord Entry;

        public SpellAuraOptionsRecord AuraOptions;
        public SpellAuraRestrictionsRecord AuraRestrictions;
        public SpellCastingRequirementsRecord CastingRequirements;
        public SpellCategoriesRecord Categories;
        public SpellClassOptionsRecord ClassOptions;
        public SpellCooldownsRecord Cooldowns;
        public SpellEquippedItemsRecord EquippedItems;
        public SpellInterruptsRecord Interrupts;
        public SpellLevelsRecord Levels;
        public SpellMiscRecord Misc;
        public SpellReagentsRecord Reagents;
        public SpellScalingRecord Scaling;
        public SpellShapeshiftRecord Shapeshift;
        public SpellTargetRestrictionsRecord TargetRestrictions;
        public SpellTotemsRecord Totems;
    }

    public class SpellThreatEntry
    {
        public int flatMod;                                    // flat threat-value for this Spell  - default: 0
        public float pctMod;                                     // threat-multiplier for this Spell  - default: 1.0f
        public float apPctMod;                                   // Pct of AP that is added as Threat - default: 0.0f
    }

    public class SpellProcEntry
    {
        public SpellSchoolMask SchoolMask { get; set; }                                 // if nonzero - bitmask for matching proc condition based on spell's school
        public SpellFamilyNames SpellFamilyName { get; set; }                            // if nonzero - for matching proc condition based on candidate spell's SpellFamilyName
        public FlagArray128 SpellFamilyMask { get; set; }                            // if nonzero - bitmask for matching proc condition based on candidate spell's SpellFamilyFlags
        public ProcFlags ProcFlags { get; set; }                                   // if nonzero - owerwrite procFlags field for given Spell.dbc entry, bitmask for matching proc condition, see enum ProcFlags
        public ProcFlagsSpellType SpellTypeMask { get; set; }                              // if nonzero - bitmask for matching proc condition based on candidate spell's damage/heal effects, see enum ProcFlagsSpellType
        public ProcFlagsSpellPhase SpellPhaseMask { get; set; }                             // if nonzero - bitmask for matching phase of a spellcast on which proc occurs, see enum ProcFlagsSpellPhase
        public ProcFlagsHit HitMask { get; set; }                                    // if nonzero - bitmask for matching proc condition based on hit result, see enum ProcFlagsHit
        public uint AttributesMask { get; set; }                             // bitmask, see ProcAttributes
        public float ProcsPerMinute { get; set; }                              // if nonzero - chance to proc is equal to value * aura caster's weapon speed / 60
        public float Chance { get; set; }                                     // if nonzero - owerwrite procChance field for given Spell.dbc entry, defines chance of proc to occur, not used if ProcsPerMinute set
        public uint Cooldown { get; set; }                                   // if nonzero - cooldown in secs for aura proc, applied to aura
        public uint Charges { get; set; }                                   // if nonzero - owerwrite procCharges field for given Spell.dbc entry, defines how many times proc can occur before aura remove, 0 - infinite
    }

    public class SpellProcEventEntry
    {
        public SpellSchoolMask schoolMask;                                 // if nonzero - bit mask for matching proc condition based on spell candidate's school: Fire=2, Mask=1<<(2-1)=2
        public SpellFamilyNames spellFamilyName;                            // if nonzero - for matching proc condition based on candidate spell's SpellFamilyNamer value
        public FlagArray128 spellFamilyMask;                            // if nonzero - for matching proc condition based on candidate spell's SpellFamilyFlags  (like auras 107 and 108 do)
        public uint procFlags;                                  // bitmask for matching proc event
        public uint procEx;                                     // proc Extend info (see ProcFlagsEx)
        public float ppmRate;                                    // for melee (ranged?) damage spells - proc rate per minute. if zero, falls back to flat chance from Spell.dbc
        public float customChance;                               // Owerride chance (in most cases for debug only)
        public uint cooldown;                                   // hidden cooldown used for some spell proc events, applied to _triggered_spell_
    }

    public class PetDefaultSpellsEntry
    {
        public uint[] spellid = new uint[4];
    }

    public class SpellArea
    {
        public uint spellId;
        public uint areaId;                                          // zone/subzone/or 0 is not limited to zone
        public uint questStart;                                      // quest start (quest must be active or rewarded for spell apply)
        public uint questEnd;                                        // quest end (quest must not be rewarded for spell apply)
        public int auraSpell;                                       // spell aura must be applied for spell apply)if possitive) and it must not be applied in other case
        public uint raceMask;                                        // can be applied only to races
        public Gender gender;                                          // can be applied only to gender
        public uint questStartStatus;                                // QuestStatus that quest_start must have in order to keep the spell
        public uint questEndStatus;                                  // QuestStatus that the quest_end must have in order to keep the spell (if the quest_end's status is different than this, the spell will be dropped)
        public bool autocast;                                          // if true then auto applied at area enter, in other case just allowed to cast

        // helpers
        public bool IsFitToRequirements(Player player, uint newZone, uint newArea)
        {
            if (gender != Gender.None)                   // not in expected gender
                if (player == null || gender != player.GetGender())
                    return false;

            if (raceMask != 0)                                // not in expected race
                if (player == null || !Convert.ToBoolean(raceMask & player.getRaceMask()))
                    return false;

            if (areaId != 0)                                  // not in expected zone
                if (newZone != areaId && newArea != areaId)
                    return false;

            if (questStart != 0)                              // not in expected required quest state
                if (player == null || (((1 << (int)player.GetQuestStatus(questStart)) & questStartStatus) == 0))
                    return false;

             if (questEnd != 0)                                // not in expected forbidden quest state
                 if (player == null || (((1 << (int)player.GetQuestStatus(questEnd)) & questEndStatus) == 0))
                     return false;

            if (auraSpell != 0)                               // not have expected aura
                if (player == null || (auraSpell > 0 && !player.HasAura((uint)auraSpell)) || (auraSpell < 0 && player.HasAura((uint)-auraSpell)))
                    return false;

            if (player)
            {
                Battleground bg = player.GetBattleground();
                if (bg)
                    return bg.IsSpellAllowed(spellId, player);
            }

            // Extra conditions -- leaving the possibility add extra conditions...
            switch (spellId)
            {
                case 91604: // No fly Zone - Wintergrasp
                    {
                        if (!player)
                            return false;

                        BattleField Bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(player.GetZoneId());
                        if (Bf == null || Bf.CanFlyIn() || (!player.HasAuraType(AuraType.ModIncreaseMountedFlightSpeed) && !player.HasAuraType(AuraType.Fly)))
                            return false;
                        break;
                    }
                case 56618: // Horde Controls Factory Phase Shift
                case 56617: // Alliance Controls Factory Phase Shift
                    {
                        if (!player)
                            return false;

                        BattleField bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(player.GetZoneId());

                        if (bf == null || bf.GetTypeId() != (int)BattleFieldTypes.WinterGrasp)
                            return false;

                        // team that controls the workshop in the specified area
                        uint team = bf.GetData(newArea);

                        if (team == TeamId.Horde)
                            return spellId == 56618;
                        else if (team == TeamId.Alliance)
                            return spellId == 56617;
                        break;
                    }
                case 57940: // Essence of Wintergrasp - Northrend
                case 58045: // Essence of Wintergrasp - Wintergrasp
                    {
                        if (!player)
                            return false;
                        
                        BattleField battlefieldWG = Global.BattleFieldMgr.GetBattlefieldByBattleId(1);
                        if (battlefieldWG != null)
                            return battlefieldWG.IsEnabled() && (player.GetTeamId() == battlefieldWG.GetDefenderTeam()) && !battlefieldWG.IsWarTime();
                        break;
                    }
                case 74411: // Battleground- Dampening
                    {
                        if (!player)
                            return false;
                        
                        BattleField bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(player.GetZoneId());
                        if (bf != null)
                            return bf.IsWarTime();
                        break;
                    }
            }
            return true;
        }
    }

    public class PetAura
    {
        public PetAura()
        {
            removeOnChangePet = false;
            damage = 0;
        }

        public PetAura(uint petEntry, uint aura, bool _removeOnChangePet, int _damage)
        {
            removeOnChangePet = _removeOnChangePet;
            damage = _damage;

            auras[petEntry] = aura;
        }

        public uint GetAura(uint petEntry)
        {
            var auraId = auras.LookupByKey(petEntry);
            if (auraId != 0)
                return auraId;

            auraId = auras.LookupByKey(0);
            if (auraId != 0)
                return auraId;

            return 0;
        }

        public void AddAura(uint petEntry, uint aura)
        {
            auras[petEntry] = aura;
        }

        public bool IsRemovedOnChangePet()
        {
            return removeOnChangePet;
        }

        public int GetDamage()
        {
            return damage;
        }

        Dictionary<uint, uint> auras = new Dictionary<uint, uint>();
        bool removeOnChangePet;
        int damage;
    }

    public class SpellEnchantProcEntry
    {
        public uint customChance;
        public float PPMChance;
        public ProcFlagsExLegacy procEx;
    }

    public class SpellTargetPosition
    {
        public uint target_mapId;
        public float target_X;
        public float target_Y;
        public float target_Z;
        public float target_Orientation;
    }
}
