/*
 * 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.Dynamic;
using Game.BattleFields;
using Game.BattleGrounds;
using Game.Conditions;
using Game.DataStorage;
using Game.Entities;
using Game.Loots;
using Game.Maps;
using Game.Movement;
using Game.Network.Packets;
using Game.Scripting;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Runtime.InteropServices;

namespace Game.Spells
{
    public partial class Spell : IDisposable
    {
        public Spell(Unit caster, SpellInfo info, TriggerCastFlags triggerFlags, ObjectGuid originalCasterGUID = default(ObjectGuid), bool skipcheck = false)
        {
            m_spellInfo = info;
            m_caster = (info.HasAttribute(SpellAttr6.CastByCharmer) && caster.GetCharmerOrOwner() != null ? caster.GetCharmerOrOwner() : caster);
            m_spellValue = new SpellValue(caster.GetMap().GetDifficultyID(), m_spellInfo);
            m_preGeneratedPath = new PathGenerator(m_caster);
            m_castItemLevel = -1;
            _effects = info.GetEffectsForDifficulty(caster.GetMap().GetDifficultyID());
            m_castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, m_caster.GetMapId(), m_spellInfo.Id, m_caster.GetMap().GenerateLowGuid(HighGuid.Cast));
            m_SpellVisual = caster.GetCastSpellXSpellVisualId(m_spellInfo);

            m_customError = SpellCustomErrors.None;
            m_skipCheck = skipcheck;
            m_fromClient = false;
            m_needComboPoints = m_spellInfo.NeedsComboPoints();

            // Get data for type of attack
            switch (m_spellInfo.DmgClass)
            {
                case SpellDmgClass.Melee:
                    if (m_spellInfo.HasAttribute(SpellAttr3.ReqOffhand))
                        m_attackType = WeaponAttackType.OffAttack;
                    else
                        m_attackType = WeaponAttackType.BaseAttack;
                    break;
                case SpellDmgClass.Ranged:
                    m_attackType = m_spellInfo.IsRangedWeaponSpell() ? WeaponAttackType.RangedAttack : WeaponAttackType.BaseAttack;
                    break;
                default:
                    // Wands
                    if (m_spellInfo.HasAttribute(SpellAttr2.AutorepeatFlag))
                        m_attackType = WeaponAttackType.RangedAttack;
                    else
                        m_attackType = WeaponAttackType.BaseAttack;
                    break;
            }

            m_spellSchoolMask = m_spellInfo.GetSchoolMask();           // Can be override for some spell (wand shoot for example)

            if (m_attackType == WeaponAttackType.RangedAttack)
            {
                if ((m_caster.getClassMask() & (uint)Class.ClassMaskWandUsers) != 0 && m_caster.IsTypeId(TypeId.Player))
                {
                    Item pItem = m_caster.ToPlayer().GetWeaponForAttack(WeaponAttackType.RangedAttack);
                    if (pItem != null)
                        m_spellSchoolMask = (SpellSchoolMask)(1 << (int)pItem.GetTemplate().GetDamageType());
                }
            }

            if (!originalCasterGUID.IsEmpty())
                m_originalCasterGUID = originalCasterGUID;
            else
                m_originalCasterGUID = m_caster.GetGUID();

            if (m_originalCasterGUID == m_caster.GetGUID())
                m_originalCaster = m_caster;
            else
            {
                m_originalCaster = Global.ObjAccessor.GetUnit(m_caster, m_originalCasterGUID);
                if (m_originalCaster != null && !m_originalCaster.IsInWorld)
                    m_originalCaster = null;
            }

            m_spellState = SpellState.None;
            _triggeredCastFlags = triggerFlags;
            if (m_spellInfo.HasAttribute(SpellAttr4.CanCastWhileCasting))
                _triggeredCastFlags = _triggeredCastFlags | TriggerCastFlags.IgnoreCastInProgress | TriggerCastFlags.CastDirectly;

            effectHandleMode = SpellEffectHandleMode.Launch;
            m_diminishLevel = DiminishingLevels.Level1;
            m_diminishGroup = DiminishingGroup.None;

            //Auto Shot & Shoot (wand)
            m_autoRepeat = m_spellInfo.IsAutoRepeatRangedSpell();

            // Determine if spell can be reflected back to the caster
            // Patch 1.2 notes: Spell Reflection no longer reflects abilities
            m_canReflect = m_spellInfo.DmgClass == SpellDmgClass.Magic && !m_spellInfo.HasAttribute(SpellAttr0.Ability)
                && !m_spellInfo.HasAttribute(SpellAttr1.CantBeReflected) && !m_spellInfo.HasAttribute(SpellAttr0.UnaffectedByInvulnerability)
                && !m_spellInfo.IsPassive() && !m_spellInfo.IsPositive();

            CleanupTargetList();

            for (var i = 0; i < SpellConst.MaxEffects; ++i)
                m_destTargets[i] = new SpellDestination(m_caster);

            //not sure needed.
            m_targets = new SpellCastTargets();
            m_appliedMods = new List<Aura>();
        }

        public virtual void Dispose()
        {
            // unload scripts
            foreach (var script in m_loadedScripts)
                script._Unload();

            if (m_referencedFromCurrentSpell && m_selfContainer && m_selfContainer == this)
            {
                // Clean the reference to avoid later crash.
                // If this error is repeating, we may have to add an ASSERT to better track down how we get into this case.
                Log.outError(LogFilter.Spells, "SPELL: deleting spell for spell ID {0}. However, spell still referenced.", m_spellInfo.Id);
                m_selfContainer = null;
            }

            if (m_caster && m_caster.GetTypeId() == TypeId.Player)
                Contract.Assert(m_caster.ToPlayer().m_spellModTakingSpell != this);
        }

        void InitExplicitTargets(SpellCastTargets targets)
        {
            m_targets = targets;
            m_targets.SetOrigUnitTarget(m_targets.GetUnitTarget());
            // this function tries to correct spell explicit targets for spell
            // client doesn't send explicit targets correctly sometimes - we need to fix such spells serverside
            // this also makes sure that we correctly send explicit targets to client (removes redundant data)
            SpellCastTargetFlags neededTargets = m_spellInfo.GetExplicitTargetMask();

            WorldObject target = m_targets.GetObjectTarget();
            if (target != null)
            {
                // check if object target is valid with needed target flags
                // for unit case allow corpse target mask because player with not released corpse is a unit target
                if ((target.ToUnit() && !Convert.ToBoolean(neededTargets & (SpellCastTargetFlags.UnitMask | SpellCastTargetFlags.CorpseMask)))
                    || (target.IsTypeId(TypeId.GameObject) && !Convert.ToBoolean(neededTargets & SpellCastTargetFlags.GameobjectMask))
                    || (target.IsTypeId(TypeId.Corpse) && !Convert.ToBoolean(neededTargets & SpellCastTargetFlags.CorpseMask)))
                    m_targets.RemoveObjectTarget();
            }
            else
            {
                // try to select correct unit target if not provided by client or by serverside cast
                if (Convert.ToBoolean(neededTargets & SpellCastTargetFlags.UnitMask))
                {
                    Unit unit = null;
                    // try to use player selection as a target
                    Player playerCaster = m_caster.ToPlayer();
                    if (playerCaster != null)
                    {
                        // selection has to be found and to be valid target for the spell
                        Unit selectedUnit = Global.ObjAccessor.GetUnit(m_caster, playerCaster.GetTarget());
                        if (selectedUnit != null)
                            if (m_spellInfo.CheckExplicitTarget(m_caster, selectedUnit) == SpellCastResult.SpellCastOk)
                                unit = selectedUnit;
                    }
                    // try to use attacked unit as a target
                    else if ((m_caster.IsTypeId(TypeId.Unit)) && Convert.ToBoolean(neededTargets & (SpellCastTargetFlags.UnitEnemy | SpellCastTargetFlags.Unit)))
                        unit = m_caster.GetVictim();

                    // didn't find anything - let's use self as target
                    if (unit == null && Convert.ToBoolean(neededTargets & (SpellCastTargetFlags.UnitRaid | SpellCastTargetFlags.UnitParty | SpellCastTargetFlags.UnitAlly)))
                        unit = m_caster;

                    m_targets.SetUnitTarget(unit);
                }
            }

            // check if spell needs dst target
            if (Convert.ToBoolean(neededTargets & SpellCastTargetFlags.DestLocation))
            {
                // and target isn't set
                if (!m_targets.HasDst())
                {
                    // try to use unit target if provided
                    WorldObject targett = targets.GetObjectTarget();
                    if (targett != null)
                        m_targets.SetDst(targett);
                    // or use self if not available
                    else
                        m_targets.SetDst(m_caster);
                }
            }
            else
                m_targets.RemoveDst();

            if (Convert.ToBoolean(neededTargets & SpellCastTargetFlags.SourceLocation))
            {
                if (!targets.HasSrc())
                    m_targets.SetSrc(m_caster);
            }
            else
                m_targets.RemoveSrc();
        }

        void SelectExplicitTargets()
        {
            // here go all explicit target changes made to explicit targets after spell prepare phase is finished
            Unit target = m_targets.GetUnitTarget();
            if (target != null)
            {
                // check for explicit target redirection, for Grounding Totem for example
                if (m_spellInfo.GetExplicitTargetMask().HasAnyFlag(SpellCastTargetFlags.UnitEnemy)
                    || (m_spellInfo.GetExplicitTargetMask().HasAnyFlag(SpellCastTargetFlags.Unit) && !m_caster.IsFriendlyTo(target)))
                {
                    Unit redirect;
                    switch (m_spellInfo.DmgClass)
                    {
                        case SpellDmgClass.Magic:
                            redirect = m_caster.GetMagicHitRedirectTarget(target, m_spellInfo);
                            break;
                        case SpellDmgClass.Melee:
                        case SpellDmgClass.Ranged:
                            redirect = m_caster.GetMeleeHitRedirectTarget(target, m_spellInfo);
                            break;
                        default:
                            redirect = null;
                            break;
                    }
                    if (redirect != null && (redirect != target))
                        m_targets.SetUnitTarget(redirect);
                }
            }
        }

        public void SelectSpellTargets()
        {
            // select targets for cast phase
            SelectExplicitTargets();

            uint processedAreaEffectsMask = 0;
            foreach (SpellEffectInfo effect in GetEffects())
            {
                if (effect == null)
                    continue;

                // not call for empty effect.
                // Also some spells use not used effect targets for store targets for dummy effect in triggered spells
                if (!effect.IsEffect())
                    continue;

                // set expected type of implicit targets to be sent to client
                SpellCastTargetFlags implicitTargetMask = SpellInfo.GetTargetFlagMask(effect.TargetA.GetObjectType()) | SpellInfo.GetTargetFlagMask(effect.TargetB.GetObjectType());
                if (Convert.ToBoolean(implicitTargetMask & SpellCastTargetFlags.Unit))
                    m_targets.SetTargetFlag(SpellCastTargetFlags.Unit);
                if (Convert.ToBoolean(implicitTargetMask & (SpellCastTargetFlags.Gameobject | SpellCastTargetFlags.GameobjectItem)))
                    m_targets.SetTargetFlag(SpellCastTargetFlags.Gameobject);

                SelectEffectImplicitTargets(effect.EffectIndex, effect.TargetA, processedAreaEffectsMask);
                SelectEffectImplicitTargets(effect.EffectIndex, effect.TargetB, processedAreaEffectsMask);

                // Select targets of effect based on effect type
                // those are used when no valid target could be added for spell effect based on spell target type
                // some spell effects use explicit target as a default target added to target map (like SPELL_EFFECT_LEARN_SPELL)
                // some spell effects add target to target map only when target type specified (like SPELL_EFFECT_WEAPON)
                // some spell effects don't add anything to target map (confirmed with sniffs) (like SPELL_EFFECT_DESTROY_ALL_TOTEMS)
                SelectEffectTypeImplicitTargets(effect.EffectIndex);

                if (m_targets.HasDst())
                    AddDestTarget(m_targets.GetDst(), effect.EffectIndex);

                if (m_spellInfo.IsChanneled())
                {
                    uint mask = (1u << (int)effect.EffectIndex);
                    foreach (var ihit in m_UniqueTargetInfo)
                    {
                        if (Convert.ToBoolean(ihit.effectMask & mask))
                        {
                            m_channelTargetEffectMask |= mask;
                            break;
                        }
                    }
                }
                else if (m_auraScaleMask != 0)
                {
                    bool checkLvl = !m_UniqueTargetInfo.Empty();
                    foreach (var ihit in m_UniqueTargetInfo)
                    {
                        // remove targets which did not pass min level check
                        if (m_auraScaleMask != 0 && ihit.effectMask == m_auraScaleMask)
                        {
                            // Do not check for selfcast
                            if (!ihit.scaleAura && ihit.targetGUID != m_caster.GetGUID())
                            {
                                m_UniqueTargetInfo.Remove(ihit);
                                continue;
                            }
                        }
                    }
                    if (checkLvl && m_UniqueTargetInfo.Empty())
                    {
                        SendCastResult(SpellCastResult.Lowlevel);
                        finish(false);
                    }
                }
            }

            if (m_targets.HasDst())
            {
                if (m_targets.HasTraj())
                {
                    float speed = m_targets.GetSpeedXY();
                    if (speed > 0.0f)
                        m_delayMoment = (ulong)Math.Floor(m_targets.GetDist2d() / speed * 1000.0f);
                }
                else if (m_spellInfo.Speed > 0.0f)
                {
                    float dist = m_caster.GetDistance(m_targets.GetDstPos());
                    if (!m_spellInfo.HasAttribute(SpellAttr9.SpecialDelayCalculation))
                        m_delayMoment = (ulong)Math.Floor(dist / m_spellInfo.Speed * 1000.0f);
                    else
                        m_delayMoment = (ulong)(m_spellInfo.Speed * 1000.0f);
                }
            }
        }

        void SelectEffectImplicitTargets(uint effIndex, SpellImplicitTargetInfo targetType, uint processedEffectMask)
        {
            if (targetType.GetTarget() == 0)
                return;

            uint effectMask = (uint)(1 << (int)effIndex);
            // set the same target list for all effects
            // some spells appear to need this, however this requires more research
            switch (targetType.GetSelectionCategory())
            {
                case SpellTargetSelectionCategories.Nearby:
                case SpellTargetSelectionCategories.Cone:
                case SpellTargetSelectionCategories.Area:
                    // targets for effect already selected
                    if (Convert.ToBoolean(effectMask & processedEffectMask))
                        return;
                    SpellEffectInfo _effect = GetEffect(effIndex);
                    if (_effect != null)
                    {
                        // choose which targets we can select at once
                        foreach (SpellEffectInfo effect in GetEffects())
                        {
                            if (effect == null || effect.EffectIndex <= effIndex)
                                continue;

                            if (effect.IsEffect() &&
                                _effect.TargetA.GetTarget() == effect.TargetA.GetTarget() &&
                                _effect.TargetB.GetTarget() == effect.TargetB.GetTarget() &&
                                _effect.ImplicitTargetConditions == effect.ImplicitTargetConditions &&
                                _effect.CalcRadius(m_caster) == effect.CalcRadius(m_caster) &&
                                CheckScriptEffectImplicitTargets(effIndex, effect.EffectIndex))
                            {
                                effectMask |= (uint)(1 << (int)effect.EffectIndex);
                            }
                        }
                    }
                    processedEffectMask |= effectMask;
                    break;
                default:
                    break;
            }

            switch (targetType.GetSelectionCategory())
            {
                case SpellTargetSelectionCategories.Channel:
                    SelectImplicitChannelTargets(effIndex, targetType);
                    break;
                case SpellTargetSelectionCategories.Nearby:
                    SelectImplicitNearbyTargets(effIndex, targetType, effectMask);
                    break;
                case SpellTargetSelectionCategories.Cone:
                    SelectImplicitConeTargets(effIndex, targetType, effectMask);
                    break;
                case SpellTargetSelectionCategories.Area:
                    SelectImplicitAreaTargets(effIndex, targetType, effectMask);
                    break;
                case SpellTargetSelectionCategories.Default:
                    switch (targetType.GetObjectType())
                    {
                        case SpellTargetObjectTypes.Src:
                            switch (targetType.GetReferenceType())
                            {
                                case SpellTargetReferenceTypes.Caster:
                                    m_targets.SetSrc(m_caster);
                                    break;
                                default:
                                    Contract.Assert(false, "Spell.SelectEffectImplicitTargets: received not implemented select target reference type for TARGET_TYPE_OBJECT_SRC");
                                    break;
                            }
                            break;
                        case SpellTargetObjectTypes.Dest:
                            switch (targetType.GetReferenceType())
                            {
                                case SpellTargetReferenceTypes.Caster:
                                    SelectImplicitCasterDestTargets(effIndex, targetType);
                                    break;
                                case SpellTargetReferenceTypes.Target:
                                    SelectImplicitTargetDestTargets(effIndex, targetType);
                                    break;
                                case SpellTargetReferenceTypes.Dest:
                                    SelectImplicitDestDestTargets(effIndex, targetType);
                                    break;
                                default:
                                    Contract.Assert(false, "Spell.SelectEffectImplicitTargets: received not implemented select target reference type for TARGET_TYPE_OBJECT_DEST");
                                    break;
                            }
                            break;
                        default:
                            switch (targetType.GetReferenceType())
                            {
                                case SpellTargetReferenceTypes.Caster:
                                    SelectImplicitCasterObjectTargets(effIndex, targetType);
                                    break;
                                case SpellTargetReferenceTypes.Target:
                                    SelectImplicitTargetObjectTargets(effIndex, targetType);
                                    break;
                                default:
                                    Contract.Assert(false, "Spell.SelectEffectImplicitTargets: received not implemented select target reference type for TARGET_TYPE_OBJECT");
                                    break;
                            }
                            break;
                    }
                    break;
                case SpellTargetSelectionCategories.Nyi:
                    Log.outDebug(LogFilter.Spells, "SPELL: target type {0}, found in spellID {1}, effect {2} is not implemented yet!", m_spellInfo.Id, effIndex, targetType.GetTarget());
                    break;
                default:
                    Contract.Assert(false, "Spell.SelectEffectImplicitTargets: received not implemented select target category");
                    break;
            }
        }

        void SelectImplicitChannelTargets(uint effIndex, SpellImplicitTargetInfo targetType)
        {
            if (targetType.GetReferenceType() != SpellTargetReferenceTypes.Caster)
            {
                Contract.Assert(false, "Spell.SelectImplicitChannelTargets: received not implemented target reference type");
                return;
            }

            Spell channeledSpell = m_originalCaster.GetCurrentSpell(CurrentSpellTypes.Channeled);
            if (channeledSpell == null)
            {
                Log.outDebug(LogFilter.Spells, "Spell.SelectImplicitChannelTargets: cannot find channel spell for spell ID {0}, effect {1}", m_spellInfo.Id, effIndex);
                return;

            }

            switch (targetType.GetTarget())
            {
                case Targets.UnitChannelTarget:
                    {
                        foreach (ObjectGuid channelTarget in m_originalCaster.GetChannelObjects())
                        {
                            WorldObject target = Global.ObjAccessor.GetUnit(m_caster, channelTarget);
                            CallScriptObjectTargetSelectHandlers(ref target, effIndex, targetType);
                            // unit target may be no longer avalible - teleported out of map for example
                            if (target && target.IsTypeId(TypeId.Unit))
                                AddUnitTarget(target.ToUnit(), 1u << (int)effIndex);
                            else
                                Log.outDebug(LogFilter.Spells, "SPELL: cannot find channel spell target for spell ID {0}, effect {1}", m_spellInfo.Id, effIndex);
                        }
                        break;
                    }
                case Targets.DestChannelTarget:
                    {
                        if (channeledSpell.m_targets.HasDst())
                            m_targets.SetDst(channeledSpell.m_targets);
                        else
                        {
                            var channelObjects = m_originalCaster.GetChannelObjects();
                            WorldObject target = channelObjects.Count > 0 ? Global.ObjAccessor.GetWorldObject(m_caster, channelObjects[0]) : null;
                            if (target != null)
                            {
                                CallScriptObjectTargetSelectHandlers(ref target, effIndex, targetType);
                                if (target)
                                {
                                    SpellDestination dest = new SpellDestination(target);
                                    CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, targetType);
                                    m_targets.SetDst(dest);
                                }
                            }
                            else
                                Log.outDebug(LogFilter.Spells, "SPELL: cannot find channel spell destination for spell ID {0}, effect {1}", m_spellInfo.Id, effIndex);
                        }
                        break;
                    }
                case Targets.DestChannelCaster:
                    {
                        SpellDestination dest = new SpellDestination(channeledSpell.GetCaster());
                        CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, targetType);
                        m_targets.SetDst(dest);
                        break;
                    }
                default:
                    Contract.Assert(false, "Spell.SelectImplicitChannelTargets: received not implemented target type");
                    break;
            }
        }

        void SelectImplicitNearbyTargets(uint effIndex, SpellImplicitTargetInfo targetType, uint effMask)
        {
            if (targetType.GetReferenceType() != SpellTargetReferenceTypes.Caster)
            {
                Contract.Assert(false, "Spell.SelectImplicitNearbyTargets: received not implemented target reference type");
                return;
            }

            SpellEffectInfo effect = GetEffect(effIndex);
            if (effect == null)
                return;

            float range = 0.0f;
            switch (targetType.GetCheckType())
            {
                case SpellTargetCheckTypes.Enemy:
                    range = m_spellInfo.GetMaxRange(false, m_caster, this);
                    break;
                case SpellTargetCheckTypes.Ally:
                case SpellTargetCheckTypes.Party:
                case SpellTargetCheckTypes.Raid:
                case SpellTargetCheckTypes.RaidClass:
                    range = m_spellInfo.GetMaxRange(true, m_caster, this);
                    break;
                case SpellTargetCheckTypes.Entry:
                case SpellTargetCheckTypes.Default:
                    range = m_spellInfo.GetMaxRange(m_spellInfo.IsPositive(), m_caster, this);
                    break;
                default:
                    Contract.Assert(false, "Spell.SelectImplicitNearbyTargets: received not implemented selection check type");
                    break;
            }

            List<Condition> condList = effect.ImplicitTargetConditions;

            // handle emergency case - try to use other provided targets if no conditions provided
            if (targetType.GetCheckType() == SpellTargetCheckTypes.Entry && (condList == null || condList.Empty()))
            {
                Log.outDebug(LogFilter.Spells, "Spell.SelectImplicitNearbyTargets: no conditions entry for target with TARGET_CHECK_ENTRY of spell ID {0}, effect {1} - selecting default targets", m_spellInfo.Id, effIndex);
                switch (targetType.GetObjectType())
                {
                    case SpellTargetObjectTypes.Gobj:
                        if (m_spellInfo.RequiresSpellFocus != 0)
                        {
                            if (focusObject != null)
                                AddGOTarget(focusObject, effMask);
                            return;
                        }
                        break;
                    case SpellTargetObjectTypes.Dest:
                        if (m_spellInfo.RequiresSpellFocus != 0)
                        {
                            if (focusObject != null)
                            {
                                SpellDestination dest = new SpellDestination(focusObject);
                                CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, targetType);
                                m_targets.SetDst(dest);
                            }
                            return;
                        }
                        break;
                    default:
                        break;
                }
            }

            WorldObject target = SearchNearbyTarget(range, targetType.GetObjectType(), targetType.GetCheckType(), condList);
            if (target == null)
            {
                Log.outDebug(LogFilter.Spells, "Spell.SelectImplicitNearbyTargets: cannot find nearby target for spell ID {0}, effect {1}", m_spellInfo.Id, effIndex);
                return;
            }

            CallScriptObjectTargetSelectHandlers(ref target, effIndex, targetType);

            switch (targetType.GetObjectType())
            {
                case SpellTargetObjectTypes.Unit:
                    Unit unitTarget = target.ToUnit();
                    if (unitTarget != null)
                        AddUnitTarget(unitTarget, effMask, true, false);
                    break;
                case SpellTargetObjectTypes.Gobj:
                    GameObject gobjTarget = target.ToGameObject();
                    if (gobjTarget != null)
                        AddGOTarget(gobjTarget, effMask);
                    break;
                case SpellTargetObjectTypes.Dest:
                    SpellDestination dest = new SpellDestination(target);
                    CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, targetType);
                    m_targets.SetDst(dest);
                    break;
                default:
                    Contract.Assert(false, "Spell.SelectImplicitNearbyTargets: received not implemented target object type");
                    break;
            }

            SelectImplicitChainTargets(effIndex, targetType, target, effMask);
        }

        void SelectImplicitConeTargets(uint effIndex, SpellImplicitTargetInfo targetType, uint effMask)
        {
            if (targetType.GetReferenceType() != SpellTargetReferenceTypes.Caster)
            {
                Contract.Assert(false, "Spell.SelectImplicitConeTargets: received not implemented target reference type");
                return;
            }
            List<WorldObject> targets = new List<WorldObject>();
            SpellTargetObjectTypes objectType = targetType.GetObjectType();
            SpellTargetCheckTypes selectionType = targetType.GetCheckType();
            SpellEffectInfo effect = GetEffect(effIndex);
            if (effect == null)
                return;

            var condList = effect.ImplicitTargetConditions;
            float coneAngle = MathFunctions.PiOver2;
            float radius = effect.CalcRadius(m_caster) * m_spellValue.RadiusMod;

            GridMapTypeMask containerTypeMask = GetSearcherTypeMask(objectType, condList);
            if (containerTypeMask != 0)
            {
                var spellCone = new WorldObjectSpellConeTargetCheck(coneAngle, radius, m_caster, m_spellInfo, selectionType, condList);
                var searcher = new WorldObjectListSearcher(m_caster, targets, spellCone, containerTypeMask);
                SearchTargets(searcher, containerTypeMask, m_caster, m_caster.GetPosition(), radius);

                CallScriptObjectAreaTargetSelectHandlers(targets, effIndex, targetType);

                if (!targets.Empty())
                {
                    // Other special target selection goes here
                    uint maxTargets = m_spellValue.MaxAffectedTargets;
                    if (maxTargets != 0)
                        targets.RandomResize(maxTargets);

                    foreach (var obj in targets)
                    {
                        Unit unitTarget = obj.ToUnit();
                        GameObject gObjTarget = obj.ToGameObject();
                        if (unitTarget)
                            AddUnitTarget(unitTarget, effMask, false);
                        else if (gObjTarget)
                            AddGOTarget(gObjTarget, effMask);
                    }
                }
            }
        }

        void SelectImplicitAreaTargets(uint effIndex, SpellImplicitTargetInfo targetType, uint effMask)
        {
            Unit referer = null;
            switch (targetType.GetReferenceType())
            {
                case SpellTargetReferenceTypes.Src:
                case SpellTargetReferenceTypes.Dest:
                case SpellTargetReferenceTypes.Caster:
                    referer = m_caster;
                    break;
                case SpellTargetReferenceTypes.Target:
                    referer = m_targets.GetUnitTarget();
                    break;
                case SpellTargetReferenceTypes.Last:
                    {
                        // find last added target for this effect
                        foreach (var target in m_UniqueTargetInfo)
                        {
                            if (Convert.ToBoolean(target.effectMask & (1 << (int)effIndex)))
                            {
                                referer = Global.ObjAccessor.GetUnit(m_caster, target.targetGUID);
                                break;
                            }
                        }
                        break;
                    }
                default:
                    Contract.Assert(false, "Spell.SelectImplicitAreaTargets: received not implemented target reference type");
                    return;
            }
            if (referer == null)
                return;

            Position center = null;
            switch (targetType.GetReferenceType())
            {
                case SpellTargetReferenceTypes.Src:
                    center = m_targets.GetSrcPos();
                    break;
                case SpellTargetReferenceTypes.Dest:
                    center = m_targets.GetDstPos();
                    break;
                case SpellTargetReferenceTypes.Caster:
                case SpellTargetReferenceTypes.Target:
                case SpellTargetReferenceTypes.Last:
                    center = referer.GetPosition();
                    break;
                default:
                    Contract.Assert(false, "Spell.SelectImplicitAreaTargets: received not implemented target reference type");
                    return;
            }
            List<WorldObject> targets = new List<WorldObject>();
            SpellEffectInfo effect = GetEffect(effIndex);
            if (effect == null)
                return;
            float radius = effect.CalcRadius(m_caster) * m_spellValue.RadiusMod;
            SearchAreaTargets(targets, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), effect.ImplicitTargetConditions);

            CallScriptObjectAreaTargetSelectHandlers(targets, effIndex, targetType);

            if (!targets.Empty())
            {
                // Other special target selection goes here
                uint maxTargets = m_spellValue.MaxAffectedTargets;
                if (maxTargets != 0)
                    targets.RandomResize(maxTargets);

                foreach (var obj in targets)
                {
                    Unit unitTarget = obj.ToUnit();
                    GameObject gObjTarget = obj.ToGameObject();
                    if (unitTarget)
                        AddUnitTarget(unitTarget, effMask, false, true, center);
                    else if (gObjTarget)
                        AddGOTarget(gObjTarget, effMask);
                }
            }
        }

        void SelectImplicitCasterDestTargets(uint effIndex, SpellImplicitTargetInfo targetType)
        {
            SpellDestination dest = new SpellDestination(m_caster);

            switch (targetType.GetTarget())
            {
                case Targets.DestCaster:
                    break;
                case Targets.DestHome:
                    Player playerCaster = m_caster.ToPlayer();
                    if (playerCaster != null)
                        dest = new SpellDestination(playerCaster.GetHomebind().posX, playerCaster.GetHomebind().posY, playerCaster.GetHomebind().posZ, playerCaster.GetOrientation(), playerCaster.GetHomebind().GetMapId());
                    break;
                case Targets.DestDb:
                    SpellTargetPosition st = Global.SpellMgr.GetSpellTargetPosition(m_spellInfo.Id, effIndex);
                    if (st != null)
                    {
                        /// @todo fix this check
                        if (m_spellInfo.HasEffect(SpellEffectName.TeleportUnitsOld) || m_spellInfo.HasEffect(SpellEffectName.Bind))
                            dest = new SpellDestination(st.target_X, st.target_Y, st.target_Z, st.target_Orientation, st.target_mapId);
                        else if (st.target_mapId == m_caster.GetMapId())
                            dest = new SpellDestination(st.target_X, st.target_Y, st.target_Z, st.target_Orientation);
                    }
                    else
                    {
                        Log.outDebug(LogFilter.Spells, "SPELL: unknown target coordinates for spell ID {0}", m_spellInfo.Id);
                        WorldObject target = m_targets.GetObjectTarget();
                        if (target)
                            dest = new SpellDestination(target);
                    }
                    break;
                case Targets.DestCasterFishing:
                    {
                        float min_dis = m_spellInfo.GetMinRange(true);
                        float max_dis = m_spellInfo.GetMaxRange(true);
                        float dis = (float)RandomHelper.NextDouble() * (max_dis - min_dis) + min_dis;
                        float x, y, z;
                        float angle = (float)RandomHelper.NextDouble() * (MathFunctions.PI * 35.0f / 180.0f) - (float)(Math.PI * 17.5f / 180.0f);
                        m_caster.GetClosePoint(out x, out y, out z, SharedConst.DefaultWorldObjectSize, dis, angle);

                        float ground = z;
                        float liquidLevel = m_caster.GetMap().GetWaterOrGroundLevel(m_caster.GetPhases(), x, y, z, ref ground);
                        if (liquidLevel <= ground) // When there is no liquid Map.GetWaterOrGroundLevel returns ground level
                        {
                            SendCastResult(SpellCastResult.NotHere);
                            SendChannelUpdate(0);
                            finish(false);
                            return;
                        }

                        if (ground + 0.75 > liquidLevel)
                        {
                            SendCastResult(SpellCastResult.TooShallow);
                            SendChannelUpdate(0);
                            finish(false);
                            return;
                        }

                        dest = new SpellDestination(x, y, liquidLevel, m_caster.GetOrientation());
                        break;
                    }
                default:
                    SpellEffectInfo effect = GetEffect(effIndex);
                    if (effect != null)
                    {
                        float dist = effect.CalcRadius(m_caster);
                        float angl = targetType.CalcDirectionAngle();
                        float objSize = m_caster.GetObjectSize();

                        switch (targetType.GetTarget())
                        {
                            case Targets.DestCasterSummon:
                                dist = SharedConst.PetFollowDist;
                                break;
                            case Targets.DestCasterRandom:
                                if (dist > objSize)
                                    dist = objSize + (dist - objSize) * (float)RandomHelper.NextDouble();
                                break;
                            case Targets.DestCasterFrontLeft:
                            case Targets.DestCasterBackLeft:
                            case Targets.DestCasterFrontRight:
                            case Targets.DestCasterBackRight:
                                {
                                    float DefaultTotemDistance = 3.0f;
                                    if (!effect.HasRadius() && !effect.HasMaxRadius())
                                        dist = DefaultTotemDistance;
                                    break;
                                }
                            default:
                                break;
                        }

                        if (dist < objSize)
                            dist = objSize;

                        Position pos = dest.Position;
                        m_caster.MovePositionToFirstCollision(ref pos, dist, angl);

                        dest.Relocate(pos);
                    }
                    break;
            }
            CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, targetType);
            m_targets.SetDst(dest);
        }

        void SelectImplicitTargetDestTargets(uint effIndex, SpellImplicitTargetInfo targetType)
        {
            WorldObject target = m_targets.GetObjectTarget();

            SpellDestination dest = new SpellDestination(target);

            switch (targetType.GetTarget())
            {
                case Targets.DestEnemy:
                case Targets.DestAny:
                    break;
                default:
                    SpellEffectInfo effect = GetEffect(effIndex);
                    if (effect != null)
                    {
                        float angle = targetType.CalcDirectionAngle();
                        float objSize = target.GetObjectSize();
                        float dist = effect.CalcRadius(m_caster);
                        if (dist < objSize)
                            dist = objSize;
                        else if (targetType.GetTarget() == Targets.DestRandom)
                            dist = objSize + (dist - objSize) * (float)RandomHelper.NextDouble();

                        Position pos = dest.Position;
                        target.MovePositionToFirstCollision(ref pos, dist, angle);

                        dest.Relocate(pos);
                    }
                    break;
            }

            CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, targetType);
            m_targets.SetDst(dest);
        }

        void SelectImplicitDestDestTargets(uint effIndex, SpellImplicitTargetInfo targetType)
        {
            // set destination to caster if no dest provided
            // can only happen if previous destination target could not be set for some reason
            // (not found nearby target, or channel target for example
            // maybe we should abort the spell in such case?
            CheckDst();

            SpellDestination dest = m_targets.GetDst();

            switch (targetType.GetTarget())
            {
                case Targets.DestDynobjEnemy:
                case Targets.DestDynobjAlly:
                case Targets.DestDynobjNone:
                case Targets.DestDest:
                    return;
                case Targets.DestTraj:
                    SelectImplicitTrajTargets(effIndex);
                    return;
                default:
                    SpellEffectInfo effect = GetEffect(effIndex);
                    if (effect != null)
                    {
                        float angle = targetType.CalcDirectionAngle();
                        float dist = effect.CalcRadius(m_caster);
                        if (targetType.GetTarget() == Targets.DestRandom)
                            dist *= (float)RandomHelper.NextDouble();

                        Position pos = m_targets.GetDstPos();
                        m_caster.MovePositionToFirstCollision(ref pos, dist, angle);

                        dest.Relocate(pos);
                    }
                    break;
            }

            CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, targetType);
            m_targets.ModDst(dest);
        }

        void SelectImplicitCasterObjectTargets(uint effIndex, SpellImplicitTargetInfo targetType)
        {
            WorldObject target = null;
            bool checkIfValid = true;

            switch (targetType.GetTarget())
            {
                case Targets.UnitCaster:
                    target = m_caster;
                    checkIfValid = false;
                    break;
                case Targets.UnitMaster:
                    target = m_caster.GetCharmerOrOwner();
                    break;
                case Targets.UnitPet:
                    target = m_caster.GetGuardianPet();
                    break;
                case Targets.UnitSummoner:
                    if (m_caster.IsSummon())
                        target = m_caster.ToTempSummon().GetSummoner();
                    break;
                case Targets.UnitVehicle:
                    target = m_caster.GetVehicleBase();
                    break;
                case Targets.UnitPassenger0:
                case Targets.UnitPassenger1:
                case Targets.UnitPassenger2:
                case Targets.UnitPassenger3:
                case Targets.UnitPassenger4:
                case Targets.UnitPassenger5:
                case Targets.UnitPassenger6:
                case Targets.UnitPassenger7:
                    if (m_caster.IsTypeId(TypeId.Unit) && m_caster.ToCreature().IsVehicle())
                        target = m_caster.GetVehicleKit().GetPassenger((sbyte)(targetType.GetTarget() - Targets.UnitPassenger0));
                    break;
                default:
                    break;
            }

            CallScriptObjectTargetSelectHandlers(ref target, effIndex, targetType);

            if (target != null && target.ToUnit())
                AddUnitTarget(target.ToUnit(), (uint)(1 << (int)effIndex), checkIfValid);
        }

        void SelectImplicitTargetObjectTargets(uint effIndex, SpellImplicitTargetInfo targetType)
        {
            WorldObject target = m_targets.GetObjectTarget();

            CallScriptObjectTargetSelectHandlers(ref target, effIndex, targetType);

            Item item = m_targets.GetItemTarget();
            if (target != null)
            {
                if (target.ToUnit())
                    AddUnitTarget(target.ToUnit(), (uint)(1 << (int)effIndex), true, false);
                else if (target.IsTypeId(TypeId.GameObject))
                    AddGOTarget(target.ToGameObject(), (uint)(1 << (int)effIndex));

                SelectImplicitChainTargets(effIndex, targetType, target, (uint)(1 << (int)effIndex));
            }
            // Script hook can remove object target and we would wrongly land here
            else if (item != null)
                AddItemTarget(item, (uint)(1 << (int)effIndex));
        }

        void SelectImplicitChainTargets(uint effIndex, SpellImplicitTargetInfo targetType, WorldObject target, uint effMask)
        {
            SpellEffectInfo effect = GetEffect(effIndex);
            if (effect == null)
                return;

            uint maxTargets = effect.ChainTargets;
            Player modOwner = m_caster.GetSpellModOwner();
            if (modOwner)
                modOwner.ApplySpellMod(m_spellInfo.Id, SpellModOp.JumpTargets, ref maxTargets, this);

            if (maxTargets > 1)
            {
                // mark damage multipliers as used
                foreach (SpellEffectInfo eff in GetEffects())
                    if (eff != null && Convert.ToBoolean(effMask & (1 << (int)eff.EffectIndex)))
                        m_damageMultipliers[eff.EffectIndex] = 1.0f;
                m_applyMultiplierMask |= (byte)effMask;

                List<WorldObject> targets = new List<WorldObject>();
                SearchChainTargets(targets, maxTargets - 1, target, targetType.GetObjectType(), targetType.GetCheckType(), effect.ImplicitTargetConditions, targetType.GetTarget() == Targets.UnitChainhealAlly);

                // Chain primary target is added earlier
                CallScriptObjectAreaTargetSelectHandlers(targets, effIndex, targetType);

                foreach (var obj in targets)
                {
                    Unit unitTarget = obj.ToUnit();
                    if (unitTarget)
                        AddUnitTarget(unitTarget, effMask, false);
                }
            }
        }

        float tangent(float x)
        {
            x = (float)Math.Tan(x);
            if (x < 100000.0f && x > -100000.0f) return x;
            if (x >= 100000.0f) return 100000.0f;
            if (x <= 100000.0f) return -100000.0f;
            return 0.0f;
        }

        void SelectImplicitTrajTargets(uint effIndex)
        {
            if (!m_targets.HasTraj())
                return;

            float dist2d = m_targets.GetDist2d();
            if (dist2d == 0)
                return;

            float srcToDestDelta = m_targets.GetDstPos().posZ - m_targets.GetSrcPos().posZ;

            List<WorldObject> targets = new List<WorldObject>();
            var spellTraj = new WorldObjectSpellTrajTargetCheck(dist2d, m_targets.GetSrcPos(), m_caster, m_spellInfo);
            var searcher = new WorldObjectListSearcher(m_caster, targets, spellTraj);
            SearchTargets(searcher, GridMapTypeMask.All, m_caster, m_targets.GetSrcPos(), dist2d);
            if (targets.Empty())
                return;

            targets.Sort(new ObjectDistanceOrderPred(m_caster));

            float b = tangent(m_targets.GetPitch());
            float a = (srcToDestDelta - dist2d * b) / (dist2d * dist2d);
            if (a > -0.0001f)
                a = 0;
            Log.outError(LogFilter.Spells, "Spell.SelectTrajTargets: a {0} b {1}", a, b);

            float bestDist = m_spellInfo.GetMaxRange(false);

            foreach (var obj in targets)
            {
                if (!m_caster.HasInLine(obj, 5.0f))
                    continue;

                if (m_spellInfo.CheckTarget(m_caster, obj, true) != SpellCastResult.SpellCastOk)
                    continue;

                Unit unitTarget = obj.ToUnit();
                if (unitTarget)
                {
                    if (m_caster == obj || m_caster.IsOnVehicle(unitTarget) || unitTarget.GetVehicle())
                        continue;

                    Creature creatureTarget = unitTarget.ToCreature();
                    if (creatureTarget)
                    {
                        if (!creatureTarget.GetCreatureTemplate().TypeFlags.HasAnyFlag(CreatureTypeFlags.CanCollideWithMissiles))
                            continue;
                    }
                }

                float size = Math.Max(obj.GetObjectSize() * 0.7f, 1.0f); // 1/sqrt(3)
                // @todo all calculation should be based on src instead of m_caster
                float objDist2d = (float)(m_targets.GetSrcPos().GetExactDist2d(obj) * Math.Cos(m_targets.GetSrcPos().GetRelativeAngle(obj)));
                float dz = obj.GetPositionZ() - m_targets.GetSrcPos().posZ;

                Log.outError(LogFilter.Spells, "Spell.SelectTrajTargets: check {0}, dist between {1} {2}, height between {3} {4}.", obj.GetEntry(), objDist2d - size, objDist2d + size, dz - size, dz + size);

                float dist = objDist2d - size;
                float height = dist * (a * dist + b);
                Log.outError(LogFilter.Spells, "Spell.SelectTrajTargets: dist {0}, height {1}.", dist, height);
                if (dist < bestDist && height < dz + size && height > dz - size)
                {
                    bestDist = dist > 0 ? dist : 0;
                    break;
                }

                if (a == 0)
                {
                    height = dz - size;
                    dist = height / b;

                    if (dist > bestDist)
                        continue;
                    if (dist < objDist2d + size && dist > objDist2d - size)
                    {
                        bestDist = dist;
                        break;
                    }

                    height = dz + size;
                    dist = height / b;

                    if (dist > bestDist)
                        continue;
                    if (dist < objDist2d + size && dist > objDist2d - size)
                    {
                        bestDist = dist;
                        break;
                    }

                    continue;
                }

                height = dz - size;
                float sqrt1 = b * b + 4 * a * height;
                if (sqrt1 > 0)
                {
                    sqrt1 = (float)Math.Sqrt(sqrt1);
                    dist = (sqrt1 - b) / (2 * a);

                    if (dist > bestDist)
                        continue;
                    if (dist < objDist2d + size && dist > objDist2d - size)
                    {
                        bestDist = dist;
                        break;
                    }
                }

                height = dz + size;
                float sqrt2 = b * b + 4 * a * height;
                if (sqrt2 > 0)
                {
                    sqrt2 = (float)Math.Sqrt(sqrt2);
                    dist = (sqrt2 - b) / (2 * a);

                    if (dist > bestDist)
                        continue;
                    if (dist < objDist2d + size && dist > objDist2d - size)
                    {
                        bestDist = dist;
                        break;
                    }

                    dist = (-sqrt2 - b) / (2 * a);

                    if (dist > bestDist)
                        continue;
                    if (dist < objDist2d + size && dist > objDist2d - size)
                    {
                        bestDist = dist;
                        break;
                    }
                }

                if (sqrt1 > 0)
                {
                    dist = (-sqrt1 - b) / (2 * a);

                    if (dist > bestDist)
                        continue;
                    if (dist < objDist2d + size && dist > objDist2d - size)
                    {
                        bestDist = dist;
                        break;
                    }
                }
            }

            if (m_targets.GetSrcPos().GetExactDist2d(m_targets.GetDstPos()) > bestDist)
            {
                float x = (float)(m_targets.GetSrcPos().posX + Math.Cos(m_caster.GetOrientation()) * bestDist);
                float y = (float)(m_targets.GetSrcPos().posY + Math.Sin(m_caster.GetOrientation()) * bestDist);
                float z = m_targets.GetSrcPos().posZ + bestDist * (a * bestDist + b);
                var obj = targets[0];
                if (obj != null)
                {
                    float distSq = obj.GetExactDistSq(x, y, z);
                    float sizeSq = obj.GetObjectSize();
                    sizeSq *= sizeSq;
                    if (distSq > sizeSq)
                    {
                        float factor = 1 - (float)Math.Sqrt(sizeSq / distSq);
                        x += factor * (obj.GetPositionX() - x);
                        y += factor * (obj.GetPositionY() - y);
                        z += factor * (obj.GetPositionZ() - z);

                        distSq = obj.GetExactDistSq(x, y, z);
                    }
                }

                Position trajDst = new Position();
                trajDst.Relocate(x, y, z, m_caster.GetOrientation());
                SpellDestination dest = m_targets.GetDst();
                dest.Relocate(trajDst);

                CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, new SpellImplicitTargetInfo(Targets.DestTraj));
                m_targets.ModDst(dest);
            }

            Vehicle veh = m_caster.GetVehicleKit();
            if (veh != null)
                veh.SetLastShootPos(m_targets.GetDstPos());
        }

        void SelectEffectTypeImplicitTargets(uint effIndex)
        {
            // special case for SPELL_EFFECT_SUMMON_RAF_FRIEND and SPELL_EFFECT_SUMMON_PLAYER
            /// @todo this is a workaround - target shouldn't be stored in target map for those spells
            SpellEffectInfo effect = GetEffect(effIndex);
            if (effect == null)
                return;
            switch (effect.Effect)
            {
                case SpellEffectName.SummonRafFriend:
                case SpellEffectName.SummonPlayer:
                    if (m_caster.IsTypeId(TypeId.Player) && !m_caster.GetTarget().IsEmpty())
                    {
                        WorldObject target1 = Global.ObjAccessor.FindPlayer(m_caster.GetTarget());

                        CallScriptObjectTargetSelectHandlers(ref target1, effIndex, new SpellImplicitTargetInfo());

                        if (target1 != null && target1.IsTypeId(TypeId.Player))
                            AddUnitTarget(target1.ToUnit(), (uint)(1 << (int)effIndex), false);
                    }
                    return;
                default:
                    break;
            }

            // select spell implicit targets based on effect type
            if (effect.GetImplicitTargetType() == 0)
                return;

            SpellCastTargetFlags targetMask = effect.GetMissingTargetMask();

            if (targetMask == 0)
                return;

            WorldObject target = null;

            switch (effect.GetImplicitTargetType())
            {
                // add explicit object target or self to the target map
                case SpellEffectImplicitTargetTypes.Explicit:
                    // player which not released his spirit is Unit, but target flag for it is TARGET_FLAG_CORPSE_MASK
                    if (Convert.ToBoolean(targetMask & (SpellCastTargetFlags.UnitMask | SpellCastTargetFlags.CorpseMask)))
                    {
                        Unit unitTarget = m_targets.GetUnitTarget();
                        if (unitTarget != null)
                            target = unitTarget;
                        else if (Convert.ToBoolean(targetMask & SpellCastTargetFlags.CorpseMask))
                        {
                            Corpse corpseTarget = m_targets.GetCorpseTarget();
                            if (corpseTarget != null)
                            {
                                // @todo this is a workaround - corpses should be added to spell target map too, but we can't do that so we add owner instead
                                Player owner = Global.ObjAccessor.FindPlayer(corpseTarget.GetOwnerGUID());
                                if (owner != null)
                                    target = owner;
                            }
                        }
                        else //if (targetMask & TARGET_FLAG_UNIT_MASK)
                            target = m_caster;
                    }
                    if (Convert.ToBoolean(targetMask & SpellCastTargetFlags.ItemMask))
                    {
                        Item itemTarget = m_targets.GetItemTarget();
                        if (itemTarget != null)
                            AddItemTarget(itemTarget, (uint)(1 << (int)effIndex));
                        return;
                    }
                    if (Convert.ToBoolean(targetMask & SpellCastTargetFlags.GameobjectMask))
                        target = m_targets.GetGOTarget();
                    break;
                // add self to the target map
                case SpellEffectImplicitTargetTypes.Caster:
                    if (Convert.ToBoolean(targetMask & SpellCastTargetFlags.UnitMask))
                        target = m_caster;
                    break;
                default:
                    break;
            }

            CallScriptObjectTargetSelectHandlers(ref target, effIndex, new SpellImplicitTargetInfo());

            if (target != null)
            {
                if (target.ToUnit())
                    AddUnitTarget(target.ToUnit(), (uint)(1 << (int)effIndex), false);
                else if (target.IsTypeId(TypeId.GameObject))
                    AddGOTarget(target.ToGameObject(), (uint)(1 << (int)effIndex));
            }
        }

        public GridMapTypeMask GetSearcherTypeMask(SpellTargetObjectTypes objType, List<Condition> condList)
        {
            // this function selects which containers need to be searched for spell target
            GridMapTypeMask retMask = GridMapTypeMask.All;

            // filter searchers based on searched object type
            switch (objType)
            {
                case SpellTargetObjectTypes.Unit:
                case SpellTargetObjectTypes.UnitAndDest:
                case SpellTargetObjectTypes.Corpse:
                case SpellTargetObjectTypes.CorpseEnemy:
                case SpellTargetObjectTypes.CorpseAlly:
                    retMask &= GridMapTypeMask.Player | GridMapTypeMask.Corpse | GridMapTypeMask.Creature;
                    break;
                case SpellTargetObjectTypes.Gobj:
                case SpellTargetObjectTypes.GobjItem:
                    retMask &= GridMapTypeMask.GameObject;
                    break;
                default:
                    break;
            }
            if (!m_spellInfo.HasAttribute(SpellAttr2.CanTargetDead))
                retMask &= ~GridMapTypeMask.Corpse;
            if (m_spellInfo.HasAttribute(SpellAttr3.OnlyTargetPlayers))
                retMask &= GridMapTypeMask.Corpse | GridMapTypeMask.Player;
            if (m_spellInfo.HasAttribute(SpellAttr3.OnlyTargetGhosts))
                retMask &= GridMapTypeMask.Player;

            if (condList != null)
                retMask &= Global.ConditionMgr.GetSearcherTypeMaskForConditionList(condList);
            return retMask;
        }

        void SearchTargets(Notifier notifier, GridMapTypeMask containerMask, Unit referer, Position pos, float radius)
        {
            if (containerMask == 0)
                return;

            bool searchInGrid = containerMask.HasAnyFlag(GridMapTypeMask.Creature | GridMapTypeMask.GameObject);
            bool searchInWorld = containerMask.HasAnyFlag(GridMapTypeMask.Creature | GridMapTypeMask.Player | GridMapTypeMask.Corpse);
            if (searchInGrid || searchInWorld)
            {
                float x, y;
                x = pos.GetPositionX();
                y = pos.GetPositionY();

                CellCoord p = GridDefines.ComputeCellCoord(x, y);
                Cell cell = new Cell(p);
                cell.SetNoCreate();

                Map map = referer.GetMap();

                if (searchInWorld)
                    Cell.VisitWorldObjects(x, y, map, notifier, radius);

                if (searchInGrid)
                    Cell.VisitGridObjects(x, y, map, notifier, radius);
            }
        }

        WorldObject SearchNearbyTarget(float range, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, List<Condition> condList)
        {
            GridMapTypeMask containerTypeMask = GetSearcherTypeMask(objectType, condList);
            if (containerTypeMask == 0)
                return null;
            var check = new WorldObjectSpellNearbyTargetCheck(range, m_caster, m_spellInfo, selectionType, condList);
            var searcher = new WorldObjectLastSearcher(m_caster, check, containerTypeMask);
            SearchTargets(searcher, containerTypeMask, m_caster, m_caster.GetPosition(), range);
            return searcher.GetTarget();
        }

        void SearchAreaTargets(List<WorldObject> targets, float range, Position position, Unit referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, List<Condition> condList)
        {
            var containerTypeMask = GetSearcherTypeMask(objectType, condList);
            if (containerTypeMask == 0)
                return;
            var check = new WorldObjectSpellAreaTargetCheck(range, position, m_caster, referer, m_spellInfo, selectionType, condList);
            var searcher = new WorldObjectListSearcher(m_caster, targets, check, containerTypeMask);
            SearchTargets(searcher, containerTypeMask, m_caster, position, range);
        }

        void SearchChainTargets(List<WorldObject> targets, uint chainTargets, WorldObject target, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectType, List<Condition> condList, bool isChainHeal)
        {
            // max dist for jump target selection
            float jumpRadius = 0.0f;
            switch (m_spellInfo.DmgClass)
            {
                case SpellDmgClass.Ranged:
                    // 7.5y for multi shot
                    jumpRadius = 7.5f;
                    break;
                case SpellDmgClass.Melee:
                    // 5y for swipe, cleave and similar
                    jumpRadius = 5.0f;
                    break;
                case SpellDmgClass.None:
                case SpellDmgClass.Magic:
                    // 12.5y for chain heal spell since 3.2 patch
                    if (isChainHeal)
                        jumpRadius = 12.5f;
                    // 10y as default for magic chain spells
                    else
                        jumpRadius = 10.0f;
                    break;
            }

            Player modOwner = m_caster.GetSpellModOwner();
            if (modOwner)
                modOwner.ApplySpellMod(m_spellInfo.Id, SpellModOp.JumpDistance, ref jumpRadius, this);

            // chain lightning/heal spells and similar - allow to jump at larger distance and go out of los
            bool isBouncingFar = (m_spellInfo.HasAttribute(SpellAttr4.AreaTargetChain)
                || m_spellInfo.DmgClass == SpellDmgClass.None
                || m_spellInfo.DmgClass == SpellDmgClass.Magic);

            // max dist which spell can reach
            float searchRadius = jumpRadius;
            if (isBouncingFar)
                searchRadius *= chainTargets;

            List<WorldObject> tempTargets = new List<WorldObject>();
            SearchAreaTargets(tempTargets, searchRadius, target.GetPosition(), m_caster, objectType, selectType, condList);
            tempTargets.Remove(target);

            // remove targets which are always invalid for chain spells
            // for some spells allow only chain targets in front of caster (swipe for example)
            if (!isBouncingFar)
            {
                foreach (var obj in tempTargets.ToList())
                {
                    if (!m_caster.HasInArc(MathFunctions.PI, obj))
                        tempTargets.Remove(obj);
                }
            }

            while (chainTargets != 0)
            {
                // try to get unit for next chain jump
                var found = tempTargets.LastOrDefault();
                // get unit with highest hp deficit in dist
                if (isChainHeal)
                {
                    uint maxHPDeficit = 0;
                    foreach (var obj in tempTargets)
                    {
                        Unit unitTarget = obj.ToUnit();
                        if (unitTarget != null)
                        {
                            uint deficit = (uint)(unitTarget.GetMaxHealth() - unitTarget.GetHealth());
                            if ((deficit > maxHPDeficit || found == null) && target.IsWithinDist(unitTarget, jumpRadius) && target.IsWithinLOSInMap(unitTarget))
                            {
                                found = obj;
                                maxHPDeficit = deficit;
                            }
                        }
                    }
                }
                // get closest object
                else
                {
                    foreach (var obj in tempTargets)
                    {
                        if (found == null)
                        {
                            if ((!isBouncingFar || target.IsWithinDist(obj, jumpRadius)) && target.IsWithinLOSInMap(obj))
                                found = obj;
                        }
                        else if (target.GetDistanceOrder(obj, found) && target.IsWithinLOSInMap(obj))
                            found = obj;
                    }
                }
                // not found any valid target - chain ends
                if (found == null)
                    break;
                target = found;
                tempTargets.Remove(found);
                targets.Add(target);
                --chainTargets;
            }
        }

        GameObject SearchSpellFocus()
        {
            var check = new GameObjectFocusCheck(m_caster, m_spellInfo.RequiresSpellFocus);
            var searcher = new GameObjectSearcher(m_caster, check);
            SearchTargets(searcher, GridMapTypeMask.GameObject, m_caster, m_caster, m_caster.GetVisibilityRange());
            return searcher.GetTarget();
        }

        void prepareDataForTriggerSystem(AuraEffect triggeredByAura)
        {
            //==========================================================================================
            // Now fill data for trigger system, need know:
            // can spell trigger another or not (m_canTrigger)
            // Create base triggers flags for Attacker and Victim (m_procAttacker, m_procVictim and m_procEx)
            //==========================================================================================

            m_procVictim = m_procAttacker = 0;
            // Get data for type of attack and fill base info for trigger
            switch (m_spellInfo.DmgClass)
            {
                case SpellDmgClass.Melee:
                    m_procAttacker = ProcFlags.DoneSpellMeleeDmgClass;
                    if (m_attackType == WeaponAttackType.OffAttack)
                        m_procAttacker |= ProcFlags.DoneOffHandAttack;
                    else
                        m_procAttacker |= ProcFlags.DoneMainHandAttack;
                    m_procVictim = ProcFlags.TakenSpellMeleeDmgClass;
                    break;
                case SpellDmgClass.Ranged:
                    // Auto attack
                    if (m_spellInfo.HasAttribute(SpellAttr2.AutorepeatFlag))
                    {
                        m_procAttacker = ProcFlags.DoneRangedAutoAttack;
                        m_procVictim = ProcFlags.TakenRangedAutoAttack;
                    }
                    else // Ranged spell attack
                    {
                        m_procAttacker = ProcFlags.DoneSpellRangedDmgClass;
                        m_procVictim = ProcFlags.TakenSpellRangedDmgClass;
                    }
                    break;
                default:
                    if (m_spellInfo.EquippedItemClass == ItemClass.Weapon &&
                        Convert.ToBoolean(m_spellInfo.EquippedItemSubClassMask & (1 << (int)ItemSubClassWeapon.Wand))
                        && m_spellInfo.HasAttribute(SpellAttr2.AutorepeatFlag)) // Wands auto attack
                    {
                        m_procAttacker = ProcFlags.DoneRangedAutoAttack;
                        m_procVictim = ProcFlags.TakenRangedAutoAttack;
                    }
                    break;
                    // For other spells trigger procflags are set in Spell.DoAllEffectOnTarget
                    // Because spell positivity is dependant on target
            }
            m_procEx = ProcFlagsExLegacy.None;

            // Hunter trap spells - activation proc for Lock and Load, Entrapment and Misdirection
            if (m_spellInfo.SpellFamilyName == SpellFamilyNames.Hunter &&
                (m_spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x18u) ||     // Freezing and Frost Trap, Freezing Arrow
                m_spellInfo.Id == 57879 ||                     // Snake Trap - done this way to avoid double proc
                m_spellInfo.SpellFamilyFlags[2].HasAnyFlag(0x00024000u))) // Explosive and Immolation Trap

                m_procAttacker |= ProcFlags.DoneTrapActivation;

            //Effects which are result of aura proc from triggered spell cannot proc to prevent chain proc of these spells

            // Hellfire Effect - trigger as DOT
            if (m_spellInfo.SpellFamilyName == SpellFamilyNames.Warlock && m_spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x00000040u))
            {
                m_procAttacker = ProcFlags.DonePeriodic;
                m_procVictim = ProcFlags.TakenPeriodic;
            }

            // Ranged autorepeat attack is set as triggered spell - ignore it
            if (!Convert.ToBoolean(m_procAttacker & ProcFlags.DoneRangedAutoAttack))
            {
                if (_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.DisallowProcEvents) &&
                    (m_spellInfo.HasAttribute(SpellAttr2.TriggeredCanTriggerProc) ||
                    m_spellInfo.HasAttribute(SpellAttr3.TriggeredCanTriggerProc2)))
                    m_procEx |= ProcFlagsExLegacy.InternalCantProc;
                else if (_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.DisallowProcEvents))
                    m_procEx |= ProcFlagsExLegacy.InternalTriggered;
            }
            // Totem casts require spellfamilymask defined in spell_proc_event to proc
            if (m_originalCaster != null && m_caster != m_originalCaster && m_caster.IsTypeId(TypeId.Unit) && m_caster.IsTotem() && m_caster.IsControlledByPlayer())
                m_procEx |= ProcFlagsExLegacy.InternalReqFamily;
        }

        public void CleanupTargetList()
        {
            m_UniqueTargetInfo.Clear();
            m_UniqueGOTargetInfo.Clear();
            m_UniqueItemInfo.Clear();
            m_delayMoment = 0;
        }

        void AddUnitTarget(Unit target, uint effectMask, bool checkIfValid = true, bool Implicit = true, Position losPosition = null)
        {
            uint validEffectMask = 0;
            foreach (SpellEffectInfo effect in GetEffects())
                if (effect != null && (effectMask & (1 << (int)effect.EffectIndex)) != 0 && CheckEffectTarget(target, effect, losPosition))
                    validEffectMask |= 1u << (int)effect.EffectIndex;

            effectMask &= validEffectMask;

            // no effects left
            if (effectMask == 0)
                return;

            if (checkIfValid)
                if (m_spellInfo.CheckTarget(m_caster, target, Implicit) != SpellCastResult.SpellCastOk)
                    return;

            // Check for effect immune skip if immuned
            foreach (SpellEffectInfo effect in GetEffects())
                if (effect != null && target.IsImmunedToSpellEffect(m_spellInfo, effect.EffectIndex))
                    effectMask &= ~(uint)(1 << (int)effect.EffectIndex);

            ObjectGuid targetGUID = target.GetGUID();

            // Lookup target in already in list
            foreach (var ihit in m_UniqueTargetInfo)
            {
                if (targetGUID == ihit.targetGUID)             // Found in list
                {
                    ihit.effectMask |= effectMask;             // Immune effects removed from mask
                    ihit.scaleAura = false;
                    if (m_auraScaleMask != 0 && ihit.effectMask == m_auraScaleMask && m_caster != target)
                    {
                        SpellInfo auraSpell = Global.SpellMgr.GetSpellInfo(Global.SpellMgr.GetFirstSpellInChain(m_spellInfo.Id));
                        if (target.getLevel() + 10 >= auraSpell.SpellLevel)
                            ihit.scaleAura = true;
                    }
                    return;
                }
            }

            // This is new target calculate data for him

            // Get spell hit result on target
            TargetInfo targetInfo = new TargetInfo();
            targetInfo.targetGUID = targetGUID;                         // Store target GUID
            targetInfo.effectMask = effectMask;                         // Store all effects not immune
            targetInfo.processed = false;                              // Effects not apply on target
            targetInfo.alive = target.IsAlive();
            targetInfo.damage = 0;
            targetInfo.crit = false;
            targetInfo.scaleAura = false;
            if (m_auraScaleMask != 0 && targetInfo.effectMask == m_auraScaleMask && m_caster != target)
            {
                SpellInfo auraSpell = Global.SpellMgr.GetSpellInfo(Global.SpellMgr.GetFirstSpellInChain(m_spellInfo.Id));
                if (target.getLevel() + 10 >= auraSpell.SpellLevel)
                    targetInfo.scaleAura = true;
            }

            // Calculate hit result
            if (m_originalCaster != null)
            {
                targetInfo.missCondition = m_originalCaster.SpellHitResult(target, m_spellInfo, m_canReflect);
                if (m_skipCheck && targetInfo.missCondition != SpellMissInfo.Immune)
                    targetInfo.missCondition = SpellMissInfo.None;
            }
            else
                targetInfo.missCondition = SpellMissInfo.Evade;

            // Spell have speed - need calculate incoming time
            // Incoming time is zero for self casts. At least I think so.
            if (m_spellInfo.Speed > 0.0f && m_caster != target)
            {
                // calculate spell incoming interval
                /// @todo this is a hack
                float dist = m_caster.GetDistance(target.GetPositionX(), target.GetPositionY(), target.GetPositionZ());

                if (dist < 5.0f)
                    dist = 5.0f;

                if (!(m_spellInfo.HasAttribute(SpellAttr9.SpecialDelayCalculation)))
                    targetInfo.timeDelay = (ulong)Math.Floor((dist / m_spellInfo.Speed) * 1000.0f);
                else
                    targetInfo.timeDelay = (ulong)(m_spellInfo.Speed * 1000.0f);

                // Calculate minimum incoming time
                if (m_delayMoment == 0 || m_delayMoment > targetInfo.timeDelay)
                    m_delayMoment = targetInfo.timeDelay;
            }
            else
                targetInfo.timeDelay = 0L;

            // If target reflect spell back to caster
            if (targetInfo.missCondition == SpellMissInfo.Reflect)
            {
                // Calculate reflected spell result on caster
                targetInfo.reflectResult = m_caster.SpellHitResult(m_caster, m_spellInfo, m_canReflect);

                if (targetInfo.reflectResult == SpellMissInfo.Reflect)     // Impossible reflect again, so simply deflect spell
                    targetInfo.reflectResult = SpellMissInfo.Parry;

                // Increase time interval for reflected spells by 1.5
                targetInfo.timeDelay += targetInfo.timeDelay >> 1;
            }
            else
                targetInfo.reflectResult = SpellMissInfo.None;

            // Add target to list
            m_UniqueTargetInfo.Add(targetInfo);
        }

        void AddGOTarget(GameObject go, uint effectMask)
        {
            uint validEffectMask = 0;
            foreach (SpellEffectInfo effect in GetEffects())
                if (effect != null && (effectMask & (1 << (int)effect.EffectIndex)) != 0 && CheckEffectTarget(go, effect))
                    validEffectMask |= (uint)(1 << (int)effect.EffectIndex);

            effectMask &= validEffectMask;

            // no effects left
            if (effectMask == 0)
                return;

            ObjectGuid targetGUID = go.GetGUID();

            // Lookup target in already in list
            foreach (var ihit in m_UniqueGOTargetInfo)
            {
                if (targetGUID == ihit.targetGUID)                 // Found in list
                {
                    ihit.effectMask |= effectMask;                 // Add only effect mask
                    return;
                }
            }

            // This is new target calculate data for him
            GOTargetInfo target = new GOTargetInfo();
            target.targetGUID = targetGUID;
            target.effectMask = effectMask;
            target.processed = false;                              // Effects not apply on target

            // Spell have speed - need calculate incoming time
            if (m_spellInfo.Speed > 0.0f)
            {
                // calculate spell incoming interval
                float dist = m_caster.GetDistance(go.GetPositionX(), go.GetPositionY(), go.GetPositionZ());
                if (dist < 5.0f)
                    dist = 5.0f;

                if (!m_spellInfo.HasAttribute(SpellAttr9.SpecialDelayCalculation))
                    target.timeDelay = (ulong)Math.Floor(dist / m_spellInfo.Speed * 1000.0f);
                else
                    target.timeDelay = (ulong)(m_spellInfo.Speed * 1000.0f);

                if (m_delayMoment == 0 || m_delayMoment > target.timeDelay)
                    m_delayMoment = target.timeDelay;
            }
            else
                target.timeDelay = 0L;

            // Add target to list
            m_UniqueGOTargetInfo.Add(target);
        }

        void AddItemTarget(Item item, uint effectMask)
        {
            uint validEffectMask = 0;
            foreach (SpellEffectInfo effect in GetEffects())
                if (effect != null && (effectMask & (1 << (int)effect.EffectIndex)) != 0 && CheckEffectTarget(item, effect))
                    validEffectMask |= 1u << (int)effect.EffectIndex;

            // no effects left
            if (effectMask == 0)
                return;

            // Lookup target in already in list
            foreach (var ihit in m_UniqueItemInfo)
            {
                if (item == ihit.item)                            // Found in list
                {
                    ihit.effectMask |= effectMask;                 // Add only effect mask
                    return;
                }
            }

            // This is new target add data

            ItemTargetInfo target = new ItemTargetInfo();
            target.item = item;
            target.effectMask = effectMask;

            m_UniqueItemInfo.Add(target);
        }

        void AddDestTarget(SpellDestination dest, uint effIndex)
        {
            m_destTargets[effIndex] = dest;
        }

        void DoAllEffectOnTarget(TargetInfo target)
        {
            if (target == null || target.processed)
                return;

            target.processed = true;                               // Target checked in apply effects procedure

            // Get mask of effects for target
            uint mask = target.effectMask;

            Unit unit = m_caster.GetGUID() == target.targetGUID ? m_caster : Global.ObjAccessor.GetUnit(m_caster, target.targetGUID);
            if (!unit && target.targetGUID.IsPlayer()) // only players may be targeted across maps
            {
                uint farMask = 0;
                // create far target mask
                foreach (SpellEffectInfo effect in GetEffects())
                {
                    if (effect != null && effect.IsFarUnitTargetEffect())
                        if (Convert.ToBoolean((1 << (int)effect.EffectIndex) & mask))
                            farMask |= (1u << (int)effect.EffectIndex);
                }

                if (farMask == 0)
                    return;

                // find unit in world
                unit = Global.ObjAccessor.FindPlayer(target.targetGUID);
                if (unit == null)
                    return;

                // do far effects on the unit
                // can't use default call because of threading, do stuff as fast as possible
                foreach (SpellEffectInfo effect in GetEffects())
                    if (effect != null && Convert.ToBoolean(farMask & (1 << (int)effect.EffectIndex)))
                        HandleEffects(unit, null, null, effect.EffectIndex, SpellEffectHandleMode.HitTarget);
                return;
            }

            if (!unit)
                return;

            if (unit.IsAlive() != target.alive)
                return;

            if (getState() == SpellState.Delayed && !m_spellInfo.IsPositive() && (Time.GetMSTime() - target.timeDelay) <= unit.m_lastSanctuaryTime)
                return;                                             // No missinfo in that case

            // Get original caster (if exist) and calculate damage/healing from him data
            Unit caster = m_originalCaster ?? m_caster;

            // Skip if m_originalCaster not avaiable
            if (caster == null)
                return;

            SpellMissInfo missInfo = target.missCondition;

            // Need init unitTarget by default unit (can changed in code on reflect)
            // Or on missInfo != SPELL_MISS_NONE unitTarget undefined (but need in trigger subsystem)
            unitTarget = unit;

            // Reset damage/healing counter
            m_damage = target.damage;
            m_healing = -target.damage;

            // Fill base trigger info
            ProcFlags procAttacker = m_procAttacker;
            ProcFlags procVictim = m_procVictim;
            ProcFlagsExLegacy procEx = m_procEx;

            m_spellAura = null;

            //Spells with this flag cannot trigger if effect is cast on self
            bool canEffectTrigger = !m_spellInfo.HasAttribute(SpellAttr3.CantTriggerProc) && unitTarget.CanProc()
                && (CanExecuteTriggersOnHit(mask) || missInfo == SpellMissInfo.Immune || missInfo == SpellMissInfo.Immune2);

            Unit spellHitTarget = null;

            if (missInfo == SpellMissInfo.None)                          // In case spell hit target, do all effect on that target
                spellHitTarget = unit;
            else if (missInfo == SpellMissInfo.Reflect)                // In case spell reflect from target, do all effect on caster (if hit)
            {
                if (target.reflectResult == SpellMissInfo.None)       // If reflected spell hit caster . do all effect on him
                {
                    spellHitTarget = m_caster;
                    if (m_caster.IsTypeId(TypeId.Unit))
                        m_caster.ToCreature().LowerPlayerDamageReq((uint)target.damage);
                }
            }

            PrepareScriptHitHandlers();
            CallScriptBeforeHitHandlers(missInfo);

            bool enablePvP = false; // need to check PvP state before spell effects, but act on it afterwards

            if (spellHitTarget != null)
            {
                // if target is flagged for pvp also flag caster if a player
                if (unit.IsPvP() && m_caster.IsTypeId(TypeId.Player))
                    enablePvP = true; // Decide on PvP flagging now, but act on it later.
                SpellMissInfo missInfo2 = DoSpellHitOnUnit(spellHitTarget, mask, target.scaleAura);
                if (missInfo2 != SpellMissInfo.None)
                {
                    if (missInfo2 != SpellMissInfo.Miss)
                        m_caster.SendSpellMiss(unit, m_spellInfo.Id, missInfo2);
                    m_damage = 0;
                    spellHitTarget = null;
                }
            }

            // Do not take combo points on dodge and miss
            if (missInfo != SpellMissInfo.None && m_needComboPoints &&
                    m_targets.GetUnitTargetGUID() == target.targetGUID)
            {
                m_needComboPoints = false;
                // Restore spell mods for a miss/dodge/parry Cold Blood
                /// @todo check how broad this rule should be
                if (m_caster.IsTypeId(TypeId.Player) && (missInfo == SpellMissInfo.Miss ||
                        missInfo == SpellMissInfo.Dodge || missInfo == SpellMissInfo.Parry))
                    m_caster.ToPlayer().RestoreSpellMods(this, 14177);
            }

            // Trigger info was not filled in spell.preparedatafortriggersystem - we do it now
            if (canEffectTrigger && procAttacker == 0 && procVictim == 0)
            {
                bool positive = true;
                if (m_damage > 0)
                    positive = false;
                else if (m_healing == 0)
                    positive = m_spellInfo.IsPositive();

                switch (m_spellInfo.DmgClass)
                {
                    case SpellDmgClass.Magic:
                        if (positive)
                        {
                            procAttacker |= ProcFlags.DoneSpellMagicDmgClassPos;
                            procVictim |= ProcFlags.TakenSpellMagicDmgClassPos;
                        }
                        else
                        {
                            procAttacker |= ProcFlags.DoneSpellMagicDmgClassNeg;
                            procVictim |= ProcFlags.TakenSpellMagicDmgClassNeg;
                        }
                        break;
                    case SpellDmgClass.None:
                        if (positive)
                        {
                            procAttacker |= ProcFlags.DoneSpellNoneDmgClassPos;
                            procVictim |= ProcFlags.TakenSpellNoneDmgClassPos;
                        }
                        else
                        {
                            procAttacker |= ProcFlags.DoneSpellNoneDmgClassNeg;
                            procVictim |= ProcFlags.TakenSpellNoneDmgClassNeg;
                        }
                        break;
                }
            }
            CallScriptOnHitHandlers();

            // All calculated do it!
            // Do healing and triggers
            if (m_healing > 0)
            {
                bool crit = target.crit;
                uint addhealth = (uint)m_healing;
                if (crit)
                {
                    procEx |= ProcFlagsExLegacy.CriticalHit;
                    addhealth = (uint)caster.SpellCriticalHealingBonus(m_spellInfo, (int)addhealth, null);
                }
                else
                    procEx |= ProcFlagsExLegacy.NormalHit;

                int gain = caster.HealBySpell(unitTarget, m_spellInfo, addhealth, crit);
                unitTarget.getHostileRefManager().threatAssist(caster, gain * 0.5f, m_spellInfo);
                m_healing = gain;

                // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge)
                if (canEffectTrigger && missInfo != SpellMissInfo.Reflect)
                    caster.ProcDamageAndSpell(unitTarget, procAttacker, procVictim, procEx, addhealth, m_attackType, m_spellInfo, m_triggeredByAuraSpell);
            }
            // Do damage and triggers
            else if (m_damage > 0)
            {
                // Fill base damage struct (unitTarget - is real spell target)
                SpellNonMeleeDamage damageInfo = new SpellNonMeleeDamage(caster, unitTarget, m_spellInfo.Id, m_SpellVisual, m_spellSchoolMask, m_castId);

                // Add bonuses and fill damageInfo struct
                caster.CalculateSpellDamageTaken(damageInfo, m_damage, m_spellInfo, m_attackType, target.crit);
                caster.DealDamageMods(damageInfo.target, ref damageInfo.damage, ref damageInfo.absorb);

                procEx |= Unit.createProcExtendMask(damageInfo, missInfo);
                procVictim |= ProcFlags.TakenDamage;

                // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge)
                if (canEffectTrigger && missInfo != SpellMissInfo.Reflect)
                {
                    caster.ProcDamageAndSpell(unitTarget, procAttacker, procVictim, procEx, damageInfo.damage, m_attackType, m_spellInfo, m_triggeredByAuraSpell);
                    if (caster.IsTypeId(TypeId.Player) && !m_spellInfo.HasAttribute(SpellAttr0.StopAttackTarget) &&
                       (m_spellInfo.DmgClass == SpellDmgClass.Melee || m_spellInfo.DmgClass == SpellDmgClass.Ranged))
                        caster.ToPlayer().CastItemCombatSpell(unitTarget, m_attackType, procVictim, procEx);
                }

                m_damage = (int)damageInfo.damage;

                caster.DealSpellDamage(damageInfo, true);

                // Send log damage message to client
                caster.SendSpellNonMeleeDamageLog(damageInfo);
            }
            // Passive spell hits/misses or active spells only misses (only triggers)
            else
            {
                // Fill base damage struct (unitTarget - is real spell target)
                SpellNonMeleeDamage damageInfo = new SpellNonMeleeDamage(caster, unitTarget, m_spellInfo.Id, m_SpellVisual, m_spellSchoolMask);
                procEx |= Unit.createProcExtendMask(damageInfo, missInfo);
                // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge)
                if (canEffectTrigger && missInfo != SpellMissInfo.Reflect)
                    caster.ProcDamageAndSpell(unit, procAttacker, procVictim, procEx, 0, m_attackType, m_spellInfo, m_triggeredByAuraSpell);

                // Failed Pickpocket, reveal rogue
                if (missInfo == SpellMissInfo.Resist && m_spellInfo.HasAttribute(SpellCustomAttributes.PickPocket) && unitTarget.IsTypeId(TypeId.Unit))
                {
                    m_caster.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Talk);
                    if (unitTarget.ToCreature().IsAIEnabled)
                        unitTarget.ToCreature().GetAI().AttackStart(m_caster);
                }
            }

            if (missInfo != SpellMissInfo.Evade && !m_caster.IsFriendlyTo(unit) && (!m_spellInfo.IsPositive() || m_spellInfo.HasEffect(SpellEffectName.Dispel)))
            {
                m_caster.CombatStart(unit, m_spellInfo.HasInitialAggro());

                if (!unit.IsStandState())
                    unit.SetStandState(UnitStandStateType.Stand);
            }

            // Check for SPELL_ATTR7_INTERRUPT_ONLY_NONPLAYER
            if (missInfo == SpellMissInfo.None && m_spellInfo.HasAttribute(SpellAttr7.InterruptOnlyNonplayer) && !unit.IsTypeId(TypeId.Player))
                caster.CastSpell(unit, 32747, true);

            if (spellHitTarget != null)
            {
                //AI functions
                if (spellHitTarget.IsTypeId(TypeId.Unit))
                    if (spellHitTarget.ToCreature().IsAIEnabled)
                        spellHitTarget.ToCreature().GetAI().SpellHit(m_caster, m_spellInfo);

                if (m_caster.IsTypeId(TypeId.Unit) && m_caster.ToCreature().IsAIEnabled)
                    m_caster.ToCreature().GetAI().SpellHitTarget(spellHitTarget, m_spellInfo);

                // Needs to be called after dealing damage/healing to not remove breaking on damage auras
                DoTriggersOnSpellHit(spellHitTarget, mask);

                // if target is fallged for pvp also flag caster if a player
                if (enablePvP)
                    m_caster.ToPlayer().UpdatePvP(true);

                CallScriptAfterHitHandlers();
            }
        }

        SpellMissInfo DoSpellHitOnUnit(Unit unit, uint effectMask, bool scaleAura)
        {
            if (unit == null || effectMask == 0)
                return SpellMissInfo.Evade;

            // For delayed spells immunity may be applied between missile launch and hit - check immunity for that case
            if (m_spellInfo.Speed != 0.0f && (unit.IsImmunedToDamage(m_spellInfo) || unit.IsImmunedToSpell(m_spellInfo)))
                return SpellMissInfo.Immune;

            // disable effects to which unit is immune
            SpellMissInfo returnVal = SpellMissInfo.Immune;
            foreach (SpellEffectInfo effect in GetEffects())
                if (effect != null && Convert.ToBoolean(effectMask & (1 << (int)effect.EffectIndex)))
                    if (unit.IsImmunedToSpellEffect(m_spellInfo, effect.EffectIndex))
                        effectMask &= (uint)~(1 << (int)effect.EffectIndex);

            if (effectMask == 0)
                return returnVal;

            Player player = unit.ToPlayer();
            if (player != null)
            {
                player.StartCriteriaTimer(CriteriaTimedTypes.SpellTarget, m_spellInfo.Id);
                player.UpdateCriteria(CriteriaTypes.BeSpellTarget, m_spellInfo.Id, 0, 0, m_caster);
                player.UpdateCriteria(CriteriaTypes.BeSpellTarget2, m_spellInfo.Id);
            }

            player = m_caster.ToPlayer();
            if (player != null)
            {
                player.StartCriteriaTimer(CriteriaTimedTypes.SpellCaster, m_spellInfo.Id);
                player.UpdateCriteria(CriteriaTypes.CastSpell2, m_spellInfo.Id, 0, 0, unit);
            }

            if (m_caster != unit)
            {
                // Recheck  UNIT_FLAG_NON_ATTACKABLE for delayed spells
                if (m_spellInfo.Speed > 0.0f && unit.HasFlag(UnitFields.Flags, UnitFlags.NonAttackable) && unit.GetCharmerOrOwnerGUID() != m_caster.GetGUID())
                    return SpellMissInfo.Evade;

                if (m_caster._IsValidAttackTarget(unit, m_spellInfo))
                {
                    unit.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Hitbyspell);
                    if (!m_spellInfo.HasAttribute(SpellCustomAttributes.DontBreakStealth))
                        unit.RemoveAurasByType(AuraType.ModStealth);
                }
                else if (m_caster.IsFriendlyTo(unit))
                {
                    // for delayed spells ignore negative spells (after duel end) for friendly targets
                    /// @todo this cause soul transfer bugged
                    // 63881 - Malady of the Mind jump spell (Yogg-Saron)
                    if (m_spellInfo.Speed > 0.0f && unit.IsTypeId(TypeId.Player) && !m_spellInfo.IsPositive() && m_spellInfo.Id != 63881)
                        return SpellMissInfo.Evade;

                    // assisting case, healing and resurrection
                    if (unit.HasUnitState(UnitState.AttackPlayer))
                    {
                        m_caster.SetContestedPvP();
                        if (m_caster.IsTypeId(TypeId.Player))
                            m_caster.ToPlayer().UpdatePvP(true);
                    }
                    if (unit.IsInCombat() && m_spellInfo.HasInitialAggro())
                    {
                        m_caster.SetInCombatState(unit.GetCombatTimer() > 0, unit);
                        unit.getHostileRefManager().threatAssist(m_caster, 0.0f);
                    }
                }
            }

            uint aura_effmask = 0;
            foreach (SpellEffectInfo effect in GetEffects())
                if (effect != null && (Convert.ToBoolean(effectMask & (1 << (int)effect.EffectIndex)) && effect.IsUnitOwnedAuraEffect()))
                    aura_effmask |= (1u << (int)effect.EffectIndex);

            // Get Data Needed for Diminishing Returns, some effects may have multiple auras, so this must be done on spell hit, not aura add
            m_diminishGroup = Global.SpellMgr.GetDiminishingReturnsGroupForSpell(m_spellInfo);
            if (m_diminishGroup != 0 && aura_effmask != 0)
            {

                m_diminishLevel = unit.GetDiminishing(m_diminishGroup);
                DiminishingReturnsType type = Global.SpellMgr.GetDiminishingReturnsGroupType(m_diminishGroup);
                // Increase Diminishing on unit, current informations for actually casts will use values above
                if ((type == DiminishingReturnsType.Player && unit.GetCharmerOrOwnerPlayerOrPlayerItself() != null) || type == DiminishingReturnsType.All)
                    unit.IncrDiminishing(m_diminishGroup);
            }

            if (aura_effmask != 0)
            {
                // Select rank for aura with level requirements only in specific cases
                // Unit has to be target only of aura effect, both caster and target have to be players, target has to be other than unit target
                SpellInfo aurSpellInfo = m_spellInfo;
                int[] basePoints = new int[SpellConst.MaxEffects];
                if (scaleAura)
                {
                    aurSpellInfo = m_spellInfo.GetAuraRankForLevel(unitTarget.getLevel());
                    Contract.Assert(aurSpellInfo != null);
                    foreach (SpellEffectInfo effect in aurSpellInfo.GetEffectsForDifficulty(0))
                    {
                        if (effect == null)
                            continue;

                        basePoints[effect.EffectIndex] = effect.BasePoints;
                        SpellEffectInfo myEffect = GetEffect(effect.EffectIndex);
                        if (myEffect != null && myEffect.Effect != effect.Effect)
                        {
                            aurSpellInfo = m_spellInfo;
                            break;
                        }
                    }
                }

                if (m_originalCaster != null)
                {
                    bool refresh = false;
                    m_spellAura = Aura.TryRefreshStackOrCreate(aurSpellInfo, m_castId, (byte)effectMask, unit,
                        m_originalCaster, out refresh, (aurSpellInfo == m_spellInfo) ? m_spellValue.EffectBasePoints : basePoints, m_CastItem, ObjectGuid.Empty, m_castItemLevel);
                    if (m_spellAura != null)
                    {
                        // Set aura stack amount to desired value
                        if (m_spellValue.AuraStackAmount > 1)
                        {
                            if (!refresh)
                                m_spellAura.SetStackAmount(m_spellValue.AuraStackAmount);
                            else
                                m_spellAura.ModStackAmount(m_spellValue.AuraStackAmount);
                        }

                        // Now Reduce spell duration using data received at spell hit
                        int duration = m_spellAura.GetMaxDuration();
                        int limitduration = m_diminishGroup != 0 ? Global.SpellMgr.GetDiminishingReturnsLimitDuration(aurSpellInfo) : 0;
                        float diminishMod = unit.ApplyDiminishingToDuration(m_diminishGroup, duration, m_originalCaster, m_diminishLevel, limitduration);

                        // unit is immune to aura if it was diminished to 0 duration
                        if (diminishMod == 0.0f)
                        {
                            m_spellAura.Remove();
                            bool found = false;
                            foreach (SpellEffectInfo effect in GetEffects())
                                if (effect != null && (Convert.ToBoolean(effectMask & (1 << (int)effect.EffectIndex)) && effect.Effect != SpellEffectName.ApplyAura))
                                    found = true;
                            if (!found)
                                return SpellMissInfo.Immune;
                        }
                        else
                        {
                            ((UnitAura)m_spellAura).SetDiminishGroup(m_diminishGroup);

                            bool positive = m_spellAura.GetSpellInfo().IsPositive();
                            AuraApplication aurApp = m_spellAura.GetApplicationOfTarget(m_originalCaster.GetGUID());
                            if (aurApp != null)
                                positive = aurApp.IsPositive();

                            duration = m_originalCaster.ModSpellDuration(aurSpellInfo, unit, duration, positive, effectMask);

                            if (duration > 0)
                            {
                                // Haste modifies duration of channeled spells
                                if (m_spellInfo.IsChanneled())
                                    m_originalCaster.ModSpellDurationTime(aurSpellInfo, ref duration, this);
                                else if (m_spellInfo.HasAttribute(SpellAttr5.HasteAffectDuration))
                                {
                                    int origDuration = duration;
                                    duration = 0;
                                    foreach (SpellEffectInfo effect in GetEffects())
                                    {
                                        if (effect != null)
                                        {
                                            AuraEffect eff = m_spellAura.GetEffect(effect.EffectIndex);
                                            if (eff != null)
                                            {
                                                int period = eff.GetPeriod();
                                                if (period != 0)  // period is hastened by UNIT_MOD_CAST_SPEED
                                                    duration = Math.Max(Math.Max(origDuration / period, 1) * period, duration);
                                            }
                                        }
                                    }

                                    // if there is no periodic effect
                                    if (duration == 0)
                                        duration = (int)(origDuration * m_originalCaster.GetFloatValue(UnitFields.ModCastSpeed));
                                }
                            }

                            if (duration != m_spellAura.GetMaxDuration())
                            {
                                m_spellAura.SetMaxDuration(duration);
                                m_spellAura.SetDuration(duration);
                            }
                            m_spellAura._RegisterForTargets();
                        }
                    }
                }
            }

            foreach (SpellEffectInfo effect in GetEffects())
                if (effect != null && Convert.ToBoolean(effectMask & (1 << (int)effect.EffectIndex)))
                    HandleEffects(unit, null, null, effect.EffectIndex, SpellEffectHandleMode.HitTarget);

            return SpellMissInfo.None;
        }

        void DoTriggersOnSpellHit(Unit unit, uint effMask)
        {
            // Apply additional spell effects to target
            /// @todo move this code to scripts
            if (m_preCastSpell != 0)
            {
                // Paladin immunity shields
                if (m_preCastSpell == 61988)
                {
                    // Cast Forbearance
                    m_caster.CastSpell(unit, 25771, true);
                    // Cast Avenging Wrath Marker
                    unit.CastSpell(unit, 61987, true);
                }

                // Avenging Wrath
                if (m_preCastSpell == 61987)
                    // Cast the serverside immunity shield marker
                    m_caster.CastSpell(unit, 61988, true);

                if (Global.SpellMgr.GetSpellInfo(m_preCastSpell) != null)
                    // Blizz seems to just apply aura without bothering to cast
                    m_caster.AddAura(m_preCastSpell, unit);
            }

            // handle SPELL_AURA_ADD_TARGET_TRIGGER auras
            // this is executed after spell proc spells on target hit
            // spells are triggered for each hit spell target
            // info confirmed with retail sniffs of permafrost and shadow weaving
            if (!m_hitTriggerSpells.Empty())
            {
                int _duration = 0;
                foreach (var hit in m_hitTriggerSpells)
                {
                    if (CanExecuteTriggersOnHit(effMask, hit.triggeredByAura) && RandomHelper.randChance(hit.chance))
                    {
                        m_caster.CastSpell(unit, hit.triggeredSpell, true);
                        Log.outDebug(LogFilter.Spells, "Spell {0} triggered spell {1} by SPELL_AURA_ADD_TARGET_TRIGGER aura", m_spellInfo.Id, hit.triggeredSpell.Id);

                        // SPELL_AURA_ADD_TARGET_TRIGGER auras shouldn't trigger auras without duration
                        // set duration of current aura to the triggered spell
                        if (hit.triggeredSpell.GetDuration() == -1)
                        {
                            Aura triggeredAur = unit.GetAura(hit.triggeredSpell.Id, m_caster.GetGUID());
                            if (triggeredAur != null)
                            {
                                // get duration from aura-only once
                                if (_duration == 0)
                                {
                                    Aura aur = unit.GetAura(m_spellInfo.Id, m_caster.GetGUID());
                                    _duration = aur != null ? aur.GetDuration() : -1;
                                }
                                triggeredAur.SetDuration(_duration);
                            }
                        }
                    }
                }
            }

            // trigger linked auras remove/apply
            // @todo remove/cleanup this, as this table is not documented and people are doing stupid things with it
            var spellTriggered = Global.SpellMgr.GetSpellLinked((int)m_spellInfo.Id + (int)SpellLinkedType.Hit);
            if (spellTriggered != null)
            {
                foreach (var id in spellTriggered)
                {
                    if (id < 0)
                        unit.RemoveAurasDueToSpell((uint)-(id));
                    else
                        unit.CastSpell(unit, (uint)id, true, null, null, m_caster.GetGUID());
                }
            }
        }

        void DoAllEffectOnTarget(GOTargetInfo target)
        {
            if (target.processed)                                  // Check target
                return;

            target.processed = true;                               // Target checked in apply effects procedure

            uint effectMask = target.effectMask;
            if (effectMask == 0)
                return;

            GameObject go = m_caster.GetMap().GetGameObject(target.targetGUID);
            if (go == null)
                return;

            PrepareScriptHitHandlers();
            CallScriptBeforeHitHandlers(SpellMissInfo.None);

            foreach (SpellEffectInfo effect in GetEffects())
                if (effect != null && Convert.ToBoolean(effectMask & (1 << (int)effect.EffectIndex)))
                    HandleEffects(null, null, go, effect.EffectIndex, SpellEffectHandleMode.HitTarget);

            CallScriptOnHitHandlers();
            CallScriptAfterHitHandlers();
        }

        void DoAllEffectOnTarget(ItemTargetInfo target)
        {
            uint effectMask = target.effectMask;
            if (target.item == null || effectMask == 0)
                return;

            PrepareScriptHitHandlers();
            CallScriptBeforeHitHandlers(SpellMissInfo.None);

            foreach (SpellEffectInfo effect in GetEffects())
                if (effect != null && Convert.ToBoolean(effectMask & (1 << (int)effect.EffectIndex)))
                    HandleEffects(null, target.item, null, effect.EffectIndex, SpellEffectHandleMode.HitTarget);

            CallScriptOnHitHandlers();

            CallScriptAfterHitHandlers();
        }

        bool UpdateChanneledTargetList()
        {
            // Not need check return true
            if (m_channelTargetEffectMask == 0)
                return true;

            uint channelTargetEffectMask = m_channelTargetEffectMask;
            uint channelAuraMask = 0;
            foreach (SpellEffectInfo effect in GetEffects())
                if (effect != null && effect.Effect == SpellEffectName.ApplyAura)
                    channelAuraMask |= (1u << (int)effect.EffectIndex);

            channelAuraMask &= channelTargetEffectMask;

            float range = 0;
            if (channelAuraMask != 0)
            {
                range = m_spellInfo.GetMaxRange(m_spellInfo.IsPositive());
                Player modOwner = m_caster.GetSpellModOwner();
                if (modOwner != null)
                    modOwner.ApplySpellMod(m_spellInfo.Id, SpellModOp.Range, ref range, this);
            }

            foreach (var ihit in m_UniqueTargetInfo)
            {
                if (ihit.missCondition == SpellMissInfo.None && Convert.ToBoolean(channelTargetEffectMask & ihit.effectMask))
                {
                    Unit unit = m_caster.GetGUID() == ihit.targetGUID ? m_caster : Global.ObjAccessor.GetUnit(m_caster, ihit.targetGUID);

                    if (unit == null)
                        continue;

                    if (IsValidDeadOrAliveTarget(unit))
                    {
                        if (Convert.ToBoolean(channelAuraMask & ihit.effectMask))
                        {
                            var b = unit.GetGUID();
                            AuraApplication aurApp = unit.GetAuraApplication(m_spellInfo.Id, m_originalCasterGUID);
                            if (aurApp != null)
                            {
                                if (m_caster != unit && !m_caster.IsWithinDistInMap(unit, range))
                                {
                                    ihit.effectMask &= ~aurApp.GetEffectMask();
                                    unit.RemoveAura(aurApp);
                                    continue;
                                }
                            }
                            else // aura is dispelled
                                continue;
                        }

                        channelTargetEffectMask &= (byte)~ihit.effectMask;   // remove from need alive mask effect that have alive target
                    }
                }
            }

            // is all effects from m_needAliveTargetMask have alive targets
            return channelTargetEffectMask == 0;
        }

        public void prepare(SpellCastTargets targets, AuraEffect triggeredByAura = null)
        {
            if (m_CastItem != null)
            {
                m_castItemGUID = m_CastItem.GetGUID();
                m_castItemEntry = m_CastItem.GetEntry();

                Player owner = m_CastItem.GetOwner();
                if (owner)
                    m_castItemLevel = (int)m_CastItem.GetItemLevel(owner);
                else if (m_CastItem.GetOwnerGUID() == m_caster.GetGUID())
                    m_castItemLevel = (int)m_CastItem.GetItemLevel(m_caster.ToPlayer());
                else
                {
                    SendCastResult(SpellCastResult.EquippedItem);
                    finish(false);
                    return;
                }
            }

            InitExplicitTargets(targets);

            // Fill aura scaling information
            if (m_caster.IsControlledByPlayer() && !m_spellInfo.IsPassive() && m_spellInfo.SpellLevel != 0 && !m_spellInfo.IsChanneled() && !Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreAuraScaling))
            {
                foreach (SpellEffectInfo effect in GetEffects())
                {
                    if (effect != null && effect.Effect == SpellEffectName.ApplyAura)
                    {
                        // Change aura with ranks only if basepoints are taken from spellInfo and aura is positive
                        if (m_spellInfo.IsPositiveEffect(effect.EffectIndex))
                        {
                            m_auraScaleMask |= (byte)(1 << (int)effect.EffectIndex);
                            if (m_spellValue.EffectBasePoints[effect.EffectIndex] != effect.BasePoints)
                            {
                                m_auraScaleMask = 0;
                                break;
                            }
                        }
                    }
                }
            }

            m_spellState = SpellState.Preparing;

            if (triggeredByAura != null)
            {
                m_triggeredByAuraSpell = triggeredByAura.GetSpellInfo();
                m_castItemLevel = triggeredByAura.GetBase().GetCastItemLevel();
            }

            // create and add update event for this spell
            SpellEvent Event = new SpellEvent(this);
            m_caster.m_Events.AddEvent(Event, m_caster.m_Events.CalculateTime(1));

            //Prevent casting at cast another spell (ServerSide check)
            if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCastInProgress) && m_caster.IsNonMeleeSpellCast(false, true, true) && !m_castId.IsEmpty())
            {
                SendCastResult(SpellCastResult.SpellInProgress);
                finish(false);
                return;
            }

            if (Global.DisableMgr.IsDisabledFor(DisableType.Spell, m_spellInfo.Id, m_caster))
            {
                SendCastResult(SpellCastResult.SpellUnavailable);
                finish(false);
                return;
            }
            LoadScripts();

            if (m_caster.IsTypeId(TypeId.Player))
                m_caster.ToPlayer().SetSpellModTakingSpell(this, true);
            // Fill cost data (not use power for item casts
            if (!m_CastItem)
                m_powerCost = m_spellInfo.CalcPowerCost(m_caster, m_spellSchoolMask);
            if (m_caster.IsTypeId(TypeId.Player))
                m_caster.ToPlayer().SetSpellModTakingSpell(this, false);

            // Set combo point requirement
            if (Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreComboPoints) || m_CastItem != null || m_caster.m_playerMovingMe == null)
                m_needComboPoints = false;

            SpellCastResult result = CheckCast(true);
            // target is checked in too many locations and with different results to handle each of them
            // handle just the general SPELL_FAILED_BAD_TARGETS result which is the default result for most DBC target checks
            if (Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreTargetCheck) && result == SpellCastResult.BadTargets)
                result = SpellCastResult.SpellCastOk;
            if (result != SpellCastResult.SpellCastOk && !IsAutoRepeat())          //always cast autorepeat dummy for triggering
            {
                // Periodic auras should be interrupted when aura triggers a spell which can't be cast
                // for example bladestorm aura should be removed on disarm as of patch 3.3.5
                // channeled periodic spells should be affected by this (arcane missiles, penance, etc)
                // a possible alternative sollution for those would be validating aura target on unit state change
                if (triggeredByAura != null && triggeredByAura.IsPeriodic() && !triggeredByAura.GetBase().IsPassive())
                {
                    SendChannelUpdate(0);
                    triggeredByAura.GetBase().SetDuration(0);
                }

                if (m_caster.IsTypeId(TypeId.Player))
                {
                    m_caster.ToPlayer().RestoreSpellMods(this);
                    // cleanup after mod system
                    // triggered spell pointer can be not removed in some cases
                    m_caster.ToPlayer().SetSpellModTakingSpell(this, false);
                }

                SendCastResult(result);

                finish(false);
                return;
            }

            // Prepare data for triggers
            prepareDataForTriggerSystem(triggeredByAura);

            if (m_caster.IsTypeId(TypeId.Player))
            {
                if (!m_caster.ToPlayer().GetCommandStatus(PlayerCommandStates.Casttime))
                {
                    m_caster.ToPlayer().SetSpellModTakingSpell(this, true);
                    m_casttime = m_spellInfo.CalcCastTime(m_caster.getLevel(), this);
                    m_caster.ToPlayer().SetSpellModTakingSpell(this, false);
                }
                else
                    m_casttime = 0;
            }
            else
                m_casttime = m_spellInfo.CalcCastTime(m_caster.getLevel(), this);

            if (m_caster.IsTypeId(TypeId.Unit) && !m_caster.HasFlag(UnitFields.Flags, UnitFlags.PlayerControlled)) // _UNIT actually means creature. for some reason.
            {
                if (!(IsNextMeleeSwingSpell() || IsAutoRepeat() || _triggeredCastFlags.HasAnyFlag(TriggerCastFlags.IgnoreSetFacing)))
                {
                    if (m_targets.GetObjectTarget() && m_caster != m_targets.GetObjectTarget())
                        m_caster.ToCreature().FocusTarget(this, m_targets.GetObjectTarget());
                    else if (m_spellInfo.HasAttribute(SpellAttr5.DontTurnDuringCast))
                        m_caster.ToCreature().FocusTarget(this, null);
                }
            }

            // don't allow channeled spells / spells with cast time to be casted while moving
            // exception are only channeled spells that have no casttime and SPELL_ATTR5_CAN_CHANNEL_WHEN_MOVING
            // (even if they are interrupted on moving, spells with almost immediate effect get to have their effect processed before movement interrupter kicks in)
            // don't cancel spells which are affected by a SPELL_AURA_CAST_WHILE_WALKING effect
            if (((m_spellInfo.IsChanneled() || m_casttime != 0) && m_caster.IsTypeId(TypeId.Player) && !(m_caster.IsCharmed() && m_caster.GetCharmerGUID().IsCreature()) && m_caster.isMoving() &&
                m_spellInfo.InterruptFlags.HasAnyFlag(SpellInterruptFlags.Movement) && !m_caster.HasAuraTypeWithAffectMask(AuraType.CastWhileWalking, m_spellInfo)))
            {
                // 1. Is a channel spell, 2. Has no casttime, 3. And has flag to allow movement during channel
                if (!(m_spellInfo.IsChanneled() && m_casttime == 0 && m_spellInfo.HasAttribute(SpellAttr5.CanChannelWhenMoving)))
                {
                    SendCastResult(SpellCastResult.Moving);
                    finish(false);
                    return;
                }
            }

            // set timer base at cast time
            ReSetTimer();

            Log.outDebug(LogFilter.Spells, "Spell.prepare: spell id {0} source {1} caster {2} customCastFlags {3} mask {4}", m_spellInfo.Id, m_caster.GetEntry(), m_originalCaster != null ? (int)m_originalCaster.GetEntry() : -1, _triggeredCastFlags, m_targets.GetTargetMask());

            //Containers for channeled spells have to be set
            // @todoApply this to all casted spells if needed
            // Why check duration? 29350: channelled triggers channelled
            if (_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.CastDirectly) && (!m_spellInfo.IsChanneled() || m_spellInfo.GetMaxDuration() == 0))
                cast(true);
            else
            {
                // stealth must be removed at cast starting (at show channel bar)
                // skip triggered spell (item equip spell casting and other not explicit character casts/item uses)
                if (!_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.IgnoreAuraInterruptFlags) && m_spellInfo.IsBreakingStealth())
                {
                    m_caster.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Cast);
                    foreach (SpellEffectInfo effect in GetEffects())
                    {
                        if (effect != null && effect.GetUsedTargetObjectType() == SpellTargetObjectTypes.Unit)
                        {
                            m_caster.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.SpellAttack);
                            break;
                        }
                    }
                }

                m_caster.SetCurrentCastSpell(this);

                SendSpellStart();

                if (!_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.IgnoreGCD))
                    TriggerGlobalCooldown();

                //item: first cast may destroy item and second cast causes crash
                if (m_casttime == 0 && m_spellInfo.StartRecoveryTime == 0 && m_castItemGUID.IsEmpty() && GetCurrentContainer() == CurrentSpellTypes.Generic)
                    cast(true);
            }
        }

        public void cancel()
        {
            if (m_spellState == SpellState.Finished)
                return;

            SpellState oldState = m_spellState;
            m_spellState = SpellState.Finished;

            m_autoRepeat = false;
            switch (oldState)
            {
                case SpellState.Preparing:
                    CancelGlobalCooldown();
                    if (m_caster.IsTypeId(TypeId.Player))
                        m_caster.ToPlayer().RestoreSpellMods(this);
                    goto case SpellState.Delayed;
                case SpellState.Delayed:
                    SendInterrupted(0);
                    SendCastResult(SpellCastResult.Interrupted);
                    break;

                case SpellState.Casting:
                    foreach (var ihit in m_UniqueTargetInfo)
                    {
                        if (ihit.missCondition == SpellMissInfo.None)
                        {
                            Unit unit = m_caster.GetGUID() == ihit.targetGUID ? m_caster : Global.ObjAccessor.GetUnit(m_caster, ihit.targetGUID);
                            if (unit != null)
                                unit.RemoveOwnedAura(m_spellInfo.Id, m_originalCasterGUID, 0, AuraRemoveMode.Cancel);
                        }
                    }

                    SendChannelUpdate(0);
                    SendInterrupted(0);
                    SendCastResult(SpellCastResult.Interrupted);

                    // spell is canceled-take mods and clear list
                    if (m_caster.IsTypeId(TypeId.Player))
                        m_caster.ToPlayer().RemoveSpellMods(this);

                    m_appliedMods.Clear();
                    break;

                default:
                    break;
            }

            SetReferencedFromCurrent(false);
            if (m_selfContainer != null && m_selfContainer == this)
                m_selfContainer = null;

            m_caster.RemoveDynObject(m_spellInfo.Id);
            if (m_spellInfo.IsChanneled()) // if not channeled then the object for the current cast wasn't summoned yet
                m_caster.RemoveGameObject(m_spellInfo.Id, true);

            //set state back so finish will be processed
            m_spellState = oldState;

            finish(false);
        }

        public void cast(bool skipCheck = false)
        {
            if (!UpdatePointers())
            {
                // cancel the spell if UpdatePointers() returned false, something wrong happened there
                cancel();
                return;
            }

            // cancel at lost explicit target during cast
            if (!m_targets.GetObjectTargetGUID().IsEmpty() && m_targets.GetObjectTarget() == null)
            {
                cancel();
                return;
            }

            Player playerCaster = m_caster.ToPlayer();
            if (playerCaster != null)
            {
                // now that we've done the basic check, now run the scripts
                // should be done before the spell is actually executed
                Global.ScriptMgr.OnPlayerSpellCast(playerCaster, this, skipCheck);

                // As of 3.0.2 pets begin attacking their owner's target immediately
                // Let any pets know we've attacked something. Check DmgClass for harmful spells only
                // This prevents spells such as Hunter's Mark from triggering pet attack
                if (m_spellInfo.DmgClass != SpellDmgClass.None)
                {
                    Pet playerPet = playerCaster.GetPet();
                    if (playerPet != null)
                        if (playerPet.IsAlive() && playerPet.isControlled() && Convert.ToBoolean(m_targets.GetTargetMask() & SpellCastTargetFlags.Unit))
                            playerPet.GetAI().OwnerAttacked(m_targets.GetUnitTarget());
                }
            }

            SetExecutedCurrently(true);

            if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreSetFacing))
                if (m_caster.IsTypeId(TypeId.Unit) && m_targets.GetObjectTarget() != null && m_caster != m_targets.GetObjectTarget())
                    m_caster.SetInFront(m_targets.GetObjectTarget());

            // Should this be done for original caster?
            if (m_caster.IsTypeId(TypeId.Player))
            {
                // Set spell which will drop charges for triggered cast spells
                // if not successfully casted, will be remove in finish(false)
                m_caster.ToPlayer().SetSpellModTakingSpell(this, true);
            }

            CallScriptBeforeCastHandlers();

            // skip check if done already (for instant cast spells for example)
            if (!skipCheck)
            {
                SpellCastResult castResult = CheckCast(false);
                if (castResult != SpellCastResult.SpellCastOk)
                {
                    SendCastResult(castResult);
                    SendInterrupted(0);
                    //restore spell mods
                    if (m_caster.IsTypeId(TypeId.Player))
                    {
                        m_caster.ToPlayer().RestoreSpellMods(this);
                        // cleanup after mod system
                        m_caster.ToPlayer().SetSpellModTakingSpell(this, false);
                    }
                    finish(false);
                    SetExecutedCurrently(false);
                    return;
                }

                // additional check after cast bar completes (must not be in CheckCast)
                // if trade not complete then remember it in trade data
                if (Convert.ToBoolean(m_targets.GetTargetMask() & SpellCastTargetFlags.TradeItem))
                {
                    if (m_caster.IsTypeId(TypeId.Player))
                    {
                        TradeData my_trade = m_caster.ToPlayer().GetTradeData();
                        if (my_trade != null)
                        {
                            if (!my_trade.IsInAcceptProcess())
                            {
                                // Spell will be casted at completing the trade. Silently ignore at this place
                                my_trade.SetSpell(m_spellInfo.Id, m_CastItem);
                                SendCastResult(SpellCastResult.DontReport);
                                SendInterrupted(0);
                                m_caster.ToPlayer().RestoreSpellMods(this);
                                // cleanup after mod system
                                m_caster.ToPlayer().SetSpellModTakingSpell(this, false);
                                finish(false);
                                SetExecutedCurrently(false);
                                return;
                            }
                        }
                    }
                }
            }

            // if the spell allows the creature to turn while casting, then adjust server-side orientation to face the target now
            // client-side orientation is handled by the client itself, as the cast target is targeted due to Creature::FocusTarget
            if (m_caster.IsTypeId(TypeId.Unit) && !m_caster.HasFlag(UnitFields.Flags, UnitFlags.PlayerControlled))
            {
                if (!m_spellInfo.HasAttribute(SpellAttr5.DontTurnDuringCast))
                {
                    WorldObject objTarget = m_targets.GetObjectTarget();
                    if (objTarget != null)
                        m_caster.SetInFront(objTarget);
                }
            }

            SelectSpellTargets();

            // Spell may be finished after target map check
            if (m_spellState == SpellState.Finished)
            {
                SendInterrupted(0);
                //restore spell mods
                if (m_caster.IsTypeId(TypeId.Player))
                {
                    m_caster.ToPlayer().RestoreSpellMods(this);
                    // cleanup after mod system
                    m_caster.ToPlayer().SetSpellModTakingSpell(this, false);
                }
                finish(false);
                SetExecutedCurrently(false);
                return;
            }

            if (m_spellInfo.HasAttribute(SpellAttr1.DismissPet))
            {
                Creature pet = ObjectAccessor.GetCreature(m_caster, m_caster.GetPetGUID());
                if (pet)
                    pet.DespawnOrUnsummon();
            }

            PrepareTriggersExecutedOnHit();

            CallScriptOnCastHandlers();

            // traded items have trade slot instead of guid in m_itemTargetGUID
            // set to real guid to be sent later to the client
            m_targets.UpdateTradeSlotItem();

            Player player = m_caster.ToPlayer();
            if (player != null)
            {
                if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCastItem) && m_CastItem != null)
                {
                    player.StartCriteriaTimer(CriteriaTimedTypes.Item, m_CastItem.GetEntry());
                    player.UpdateCriteria(CriteriaTypes.UseItem, m_CastItem.GetEntry());
                }

                player.UpdateCriteria(CriteriaTypes.CastSpell, m_spellInfo.Id);
            }

            Item targetItem = m_targets.GetItemTarget();
            if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnorePowerAndReagentCost))
            {
                // Powers have to be taken before SendSpellGo
                TakePower();
                TakeReagents();                                         // we must remove reagents before HandleEffects to allow place crafted item in same slot
            }
            else if (targetItem != null)
            {
                // Not own traded item (in trader trade slot) req. reagents including triggered spell case
                if (targetItem.GetOwnerGUID() != m_caster.GetGUID())
                    TakeReagents();
            }

            // CAST SPELL
            SendSpellCooldown();

            PrepareScriptHitHandlers();

            HandleLaunchPhase();

            // we must send smsg_spell_go packet before m_castItem delete in TakeCastItem()...
            SendSpellGo();

            // Okay, everything is prepared. Now we need to distinguish between immediate and evented delayed spells
            if ((m_spellInfo.Speed > 0.0f && !m_spellInfo.IsChanneled()) || m_spellInfo.HasAttribute(SpellAttr4.Unk4))
            {
                // Remove used for cast item if need (it can be already NULL after TakeReagents call
                // in case delayed spell remove item at cast delay start
                TakeCastItem();

                // Okay, maps created, now prepare flags
                m_immediateHandled = false;
                m_spellState = SpellState.Delayed;
                SetDelayStart(0);

                if (m_caster.HasUnitState(UnitState.Casting) && !m_caster.IsNonMeleeSpellCast(false, false, true))
                    m_caster.ClearUnitState(UnitState.Casting);
            }
            else
            {
                // Immediate spell, no big deal
                handle_immediate();
            }

            CallScriptAfterCastHandlers();

            var spell_triggered = Global.SpellMgr.GetSpellLinked((int)m_spellInfo.Id);
            if (spell_triggered != null)
            {
                foreach (var spellId in spell_triggered)
                {
                    if (spellId < 0)
                        m_caster.RemoveAurasDueToSpell((uint)-spellId);
                    else
                        m_caster.CastSpell(m_targets.GetUnitTarget() ?? m_caster, (uint)spellId, true);
                }
            }

            if (m_caster.IsTypeId(TypeId.Player))
            {
                m_caster.ToPlayer().SetSpellModTakingSpell(this, false);

                //Clear spell cooldowns after every spell is cast if .cheat cooldown is enabled.
                if (m_caster.ToPlayer().GetCommandStatus(PlayerCommandStates.Cooldown))
                {
                    m_caster.GetSpellHistory().ResetCooldown(m_spellInfo.Id, true);
                    m_caster.GetSpellHistory().RestoreCharge(m_spellInfo.ChargeCategoryId);
                }
            }

            SetExecutedCurrently(false);

            Creature creatureCaster = m_caster.ToCreature();
            if (creatureCaster)
                creatureCaster.ReleaseFocus(this);
        }

        void handle_immediate()
        {
            // start channeling if applicable
            if (m_spellInfo.IsChanneled())
            {
                int duration = m_spellInfo.GetDuration();
                if (duration > 0)
                {
                    // First mod_duration then haste - see Missile Barrage
                    // Apply duration mod
                    Player modOwner = m_caster.GetSpellModOwner();
                    if (modOwner != null)
                        modOwner.ApplySpellMod(m_spellInfo.Id, SpellModOp.Duration, ref duration);
                    // Apply haste mods
                    m_caster.ModSpellDurationTime(m_spellInfo, ref duration, this);

                    m_spellState = SpellState.Casting;
                    m_caster.AddInterruptMask((uint)m_spellInfo.ChannelInterruptFlags);
                    SendChannelStart((uint)duration);
                }
                else if (duration == -1)
                {
                    m_spellState = SpellState.Casting;
                    m_caster.AddInterruptMask((uint)m_spellInfo.ChannelInterruptFlags);
                    SendChannelStart((uint)duration);
                }
            }

            PrepareTargetProcessing();

            // process immediate effects (items, ground, etc.) also initialize some variables
            _handle_immediate_phase();

            foreach (var ihit in m_UniqueTargetInfo)
                DoAllEffectOnTarget(ihit);

            foreach (var ihit in m_UniqueGOTargetInfo)
                DoAllEffectOnTarget(ihit);

            FinishTargetProcessing();

            // spell is finished, perform some last features of the spell here
            _handle_finish_phase();

            // Remove used for cast item if need (it can be already NULL after TakeReagents call
            TakeCastItem();

            if (m_spellState != SpellState.Casting)
                finish(true);                                       // successfully finish spell cast (not last in case autorepeat or channel spell)
        }

        public ulong handle_delayed(ulong t_offset)
        {
            if (!UpdatePointers())
            {
                // finish the spell if UpdatePointers() returned false, something wrong happened there
                finish(false);
                return 0;
            }

            if (m_caster.IsTypeId(TypeId.Player))
                m_caster.ToPlayer().SetSpellModTakingSpell(this, true);

            ulong next_time = 0;

            PrepareTargetProcessing();

            if (!m_immediateHandled)
            {
                _handle_immediate_phase();
                m_immediateHandled = true;
            }

            bool single_missile = m_targets.HasDst();

            // now recheck units targeting correctness (need before any effects apply to prevent adding immunity at first effect not allow apply second spell effect and similar cases)
            foreach (var ihit in m_UniqueTargetInfo)
            {
                if (!ihit.processed)
                {
                    if (single_missile || ihit.timeDelay <= t_offset)
                    {
                        ihit.timeDelay = t_offset;
                        DoAllEffectOnTarget(ihit);
                    }
                    else if (next_time == 0 || ihit.timeDelay < next_time)
                        next_time = ihit.timeDelay;
                }
            }

            // now recheck gameobject targeting correctness
            foreach (var ighit in m_UniqueGOTargetInfo)
            {
                if (!ighit.processed)
                {
                    if (single_missile || ighit.timeDelay <= t_offset)
                        DoAllEffectOnTarget(ighit);
                    else if (next_time == 0 || ighit.timeDelay < next_time)
                        next_time = ighit.timeDelay;
                }
            }

            FinishTargetProcessing();

            if (m_caster.IsTypeId(TypeId.Player))
                m_caster.ToPlayer().SetSpellModTakingSpell(this, false);

            // All targets passed - need finish phase
            if (next_time == 0)
            {
                // spell is finished, perform some last features of the spell here
                _handle_finish_phase();

                finish(true);                                       // successfully finish spell cast

                // return zero, spell is finished now
                return 0;
            }
            else
            {
                // spell is unfinished, return next execution time
                return next_time;
            }
        }

        void _handle_immediate_phase()
        {
            m_spellAura = null;
            // initialize Diminishing Returns Data
            m_diminishLevel = DiminishingLevels.Level1;
            m_diminishGroup = DiminishingGroup.None;

            // handle some immediate features of the spell here
            HandleThreatSpells();

            PrepareScriptHitHandlers();

            // handle effects with SPELL_EFFECT_HANDLE_HIT mode
            foreach (SpellEffectInfo effect in GetEffects())
            {
                // don't do anything for empty effect
                if (effect == null || !effect.IsEffect())
                    continue;

                // call effect handlers to handle destination hit
                HandleEffects(null, null, null, effect.EffectIndex, SpellEffectHandleMode.Hit);
            }

            // process items
            foreach (var ihit in m_UniqueItemInfo)
                DoAllEffectOnTarget(ihit);

            if (m_originalCaster == null)
                return;
            // Handle procs on cast
            // @todo finish new proc system:P
            if (m_UniqueTargetInfo.Empty() && m_targets.HasDst())
            {
                var procAttacker = m_procAttacker;
                if (procAttacker == 0)
                    procAttacker |= ProcFlags.DoneSpellMagicDmgClassPos;

                // Proc the spells that have DEST target
                m_originalCaster.ProcDamageAndSpell(null, procAttacker, 0, m_procEx | ProcFlagsExLegacy.NormalHit, 0, WeaponAttackType.BaseAttack, m_spellInfo, m_triggeredByAuraSpell);
            }
        }

        void _handle_finish_phase()
        {
            if (m_caster.m_playerMovingMe != null)
            {
                // Take for real after all targets are processed
                if (m_needComboPoints)
                    m_caster.m_playerMovingMe.ClearComboPoints();

                // Real add combo points from effects
                if (m_comboPointGain != 0)
                    m_caster.m_playerMovingMe.GainSpellComboPoints(m_comboPointGain);
            }

            if (m_caster.m_extraAttacks != 0 && m_spellInfo.HasEffect(SpellEffectName.AddExtraAttacks))
            {
                Unit victim = Global.ObjAccessor.GetUnit(m_caster, m_targets.GetOrigUnitTargetGUID());
                if (victim)
                    m_caster.HandleProcExtraAttackFor(victim);
                else
                    m_caster.m_extraAttacks = 0;
            }
            // @todo trigger proc phase finish here
        }

        void SendSpellCooldown()
        {
            if (m_CastItem)
                m_caster.GetSpellHistory().HandleCooldowns(m_spellInfo, m_CastItem, this);
            else
                m_caster.GetSpellHistory().HandleCooldowns(m_spellInfo, m_castItemEntry, this);
        }

        public void update(uint difftime)
        {
            if (!UpdatePointers())
            {
                // cancel the spell if UpdatePointers() returned false, something wrong happened there
                cancel();
                return;
            }

            if (!m_targets.GetUnitTargetGUID().IsEmpty() && m_targets.GetUnitTarget() == null)
            {
                Log.outDebug(LogFilter.Spells, "Spell {0} is cancelled due to removal of target.", m_spellInfo.Id);
                cancel();
                return;
            }

            // check if the player caster has moved before the spell finished
            // with the exception of spells affected with SPELL_AURA_CAST_WHILE_WALKING effect
            SpellEffectInfo effect = GetEffect(0);
            if ((m_caster.IsTypeId(TypeId.Player) && m_timer != 0) &&
                m_caster.isMoving() && m_spellInfo.InterruptFlags.HasAnyFlag(SpellInterruptFlags.Movement) &&
                ((effect != null && effect.Effect != SpellEffectName.Stuck) || !m_caster.HasUnitMovementFlag(MovementFlag.FallingFar)) &&
                !m_caster.HasAuraTypeWithAffectMask(AuraType.CastWhileWalking, m_spellInfo))
            {
                // don't cancel for melee, autorepeat, triggered and instant spells
                if (!IsNextMeleeSwingSpell() && !IsAutoRepeat() && !IsTriggered() && !(IsChannelActive() && m_spellInfo.HasAttribute(SpellAttr5.CanChannelWhenMoving)))
                {
                    // if charmed by creature, trust the AI not to cheat and allow the cast to proceed
                    // @todo this is a hack, "creature" movesplines don't differentiate turning/moving right now
                    // however, checking what type of movement the spline is for every single spline would be really expensive
                    if (!m_caster.GetCharmerGUID().IsCreature())
                        cancel();
                }
            }

            switch (m_spellState)
            {
                case SpellState.Preparing:
                    {
                        if (m_timer > 0)
                        {
                            if (difftime >= m_timer)
                                m_timer = 0;
                            else
                                m_timer -= (int)difftime;
                        }

                        if (m_timer == 0 && !IsNextMeleeSwingSpell() && !IsAutoRepeat())
                            // don't CheckCast for instant spells - done in spell.prepare, skip duplicate checks, needed for range checks for example
                            cast(m_casttime == 0);
                        break;
                    }
                case SpellState.Casting:
                    {
                        if (m_timer != 0)
                        {
                            // check if there are alive targets left
                            if (!UpdateChanneledTargetList())
                            {
                                Log.outDebug(LogFilter.Spells, "Channeled spell {0} is removed due to lack of targets", m_spellInfo.Id);
                                SendChannelUpdate(0);
                                finish();
                            }

                            if (m_timer > 0)
                            {
                                if (difftime >= m_timer)
                                    m_timer = 0;
                                else
                                    m_timer -= (int)difftime;
                            }
                        }

                        if (m_timer == 0)
                        {
                            SendChannelUpdate(0);
                            finish();
                        }
                        break;
                    }
                default:
                    break;
            }
        }

        public void finish(bool ok = true)
        {
            if (!m_caster)
                return;

            if (m_spellState == SpellState.Finished)
                return;
            m_spellState = SpellState.Finished;

            if (m_spellInfo.IsChanneled())
                m_caster.UpdateInterruptMask();

            if (m_caster.HasUnitState(UnitState.Casting) && !m_caster.IsNonMeleeSpellCast(false, false, true))
                m_caster.ClearUnitState(UnitState.Casting);

            // Unsummon summon as possessed creatures on spell cancel
            if (m_spellInfo.IsChanneled() && m_caster.IsTypeId(TypeId.Player))
            {
                Unit charm = m_caster.GetCharm();
                if (charm != null)
                    if (charm.IsTypeId(TypeId.Unit) && charm.ToCreature().HasUnitTypeMask(UnitTypeMask.Puppet)
                        && charm.GetUInt32Value(UnitFields.CreatedBySpell) == m_spellInfo.Id)
                        ((Puppet)charm).UnSummon();
            }

            if (m_caster.IsTypeId(TypeId.Unit))
                m_caster.ToCreature().ReleaseFocus(this);

            if (!ok)
                return;

            if (m_caster.IsTypeId(TypeId.Unit) && m_caster.ToCreature().IsSummon())
            {
                // Unsummon statue
                uint spell = m_caster.GetUInt32Value(UnitFields.CreatedBySpell);
                SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spell);
                if (spellInfo != null && spellInfo.IconFileDataId == 134230)
                {
                    Log.outDebug(LogFilter.Spells, "Statue {0} is unsummoned in spell {1} finish", m_caster.GetGUID().ToString(), m_spellInfo.Id);
                    m_caster.setDeathState(DeathState.JustDied);
                    return;
                }
            }

            if (IsAutoActionResetSpell())
            {
                bool found = false;
                var vIgnoreReset = m_caster.GetAuraEffectsByType(AuraType.IgnoreMeleeReset);
                foreach (var i in vIgnoreReset)
                {
                    if (i.IsAffectingSpell(m_spellInfo))
                    {
                        found = true;
                        break;
                    }
                }
                if (!found && !m_spellInfo.HasAttribute(SpellAttr2.NotResetAutoActions))
                {
                    m_caster.resetAttackTimer(WeaponAttackType.BaseAttack);
                    if (m_caster.haveOffhandWeapon())
                        m_caster.resetAttackTimer(WeaponAttackType.OffAttack);
                    m_caster.resetAttackTimer(WeaponAttackType.RangedAttack);
                }
            }

            // potions disabled by client, send event "not in combat" if need
            if (m_caster.IsTypeId(TypeId.Player))
            {
                if (m_triggeredByAuraSpell == null)
                    m_caster.ToPlayer().UpdatePotionCooldown(this);
            }

            Player modOwner = m_caster.GetSpellModOwner();
            if (modOwner)
            {
                // triggered spell pointer can be not set in some cases
                // this is needed for proper apply of triggered spell mods
                modOwner.SetSpellModTakingSpell(this, true);

                // Take mods after trigger spell (needed for 14177 to affect 48664)
                // mods are taken only on succesfull cast and independantly from targets of the spell
                modOwner.RemoveSpellMods(this);
                modOwner.SetSpellModTakingSpell(this, false);
            }

            // Stop Attack for some spells
            if (m_spellInfo.HasAttribute(SpellAttr0.StopAttackTarget))
                m_caster.AttackStop();
        }

        static void FillSpellCastFailedArgs<T>(T packet, ObjectGuid castId, SpellInfo spellInfo, SpellCastResult result, SpellCustomErrors customError, uint[] misc, Player caster) where T : CastFailedBase
        {
            packet.CastID = castId;
            packet.SpellID = (int)spellInfo.Id;
            packet.Reason = result;

            switch (result)
            {
                case SpellCastResult.NotReady:
                    packet.FailedArg1 = 0;                              // unknown (value 1 update cooldowns on client flag)
                    break;
                case SpellCastResult.RequiresSpellFocus:
                    packet.FailedArg1 = (int)spellInfo.RequiresSpellFocus;  // SpellFocusObject.dbc id
                    break;
                case SpellCastResult.RequiresArea:                    // AreaTable.dbc id
                                                                      // hardcode areas limitation case
                    switch (spellInfo.Id)
                    {
                        case 41617:                                 // Cenarion Mana Salve
                        case 41619:                                 // Cenarion Healing Salve
                            packet.FailedArg1 = 3905;
                            break;
                        case 41618:                                 // Bottled Nethergon Energy
                        case 41620:                                 // Bottled Nethergon Vapor
                            packet.FailedArg1 = 3842;
                            break;
                        case 45373:                                 // Bloodberry Elixir
                            packet.FailedArg1 = 4075;
                            break;
                        default:                                    // default case (don't must be)
                            packet.FailedArg1 = 0;
                            break;
                    }
                    break;
                case SpellCastResult.Totems:
                    if (spellInfo.Totem[0] != 0)
                        packet.FailedArg1 = (int)spellInfo.Totem[0];
                    if (spellInfo.Totem[1] != 0)
                        packet.FailedArg2 = (int)spellInfo.Totem[1];
                    break;
                case SpellCastResult.TotemCategory:
                    if (spellInfo.TotemCategory[0] != 0)
                        packet.FailedArg1 = (int)spellInfo.TotemCategory[0];
                    if (spellInfo.TotemCategory[1] != 0)
                        packet.FailedArg2 = (int)spellInfo.TotemCategory[1];
                    break;
                case SpellCastResult.EquippedItemClass:
                case SpellCastResult.EquippedItemClassMainhand:
                case SpellCastResult.EquippedItemClassOffhand:
                    packet.FailedArg1 = (int)spellInfo.EquippedItemClass;
                    packet.FailedArg2 = spellInfo.EquippedItemSubClassMask;
                    break;
                case SpellCastResult.TooManyOfItem:
                    {
                        uint item = 0;
                        foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(caster.GetMap().GetDifficultyID()))
                            if (effect.ItemType != 0)
                                item = effect.ItemType;
                        ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item);
                        if (proto != null && proto.GetItemLimitCategory() != 0)
                            packet.FailedArg1 = (int)proto.GetItemLimitCategory();
                        break;
                    }
                case SpellCastResult.PreventedByMechanic:
                    packet.FailedArg1 = (int)spellInfo.GetAllEffectsMechanicMask();  // SpellMechanic.dbc id
                    break;
                case SpellCastResult.NeedExoticAmmo:
                    packet.FailedArg1 = spellInfo.EquippedItemSubClassMask; // seems correct...
                    break;
                case SpellCastResult.NeedMoreItems:
                    packet.FailedArg1 = 0;                              // Item id
                    packet.FailedArg2 = 0;                              // Item count?
                    break;
                case SpellCastResult.MinSkill:
                    packet.FailedArg1 = 0;                              // SkillLine.dbc id
                    packet.FailedArg2 = 0;                              // required skill value
                    break;
                case SpellCastResult.FishingTooLow:
                    packet.FailedArg1 = 0;                              // required fishing skill
                    break;
                case SpellCastResult.CustomError:
                    packet.FailedArg1 = (int)customError;
                    break;
                case SpellCastResult.Silenced:
                    packet.FailedArg1 = 0;                              // Unknown
                    break;
                case SpellCastResult.Reagents:
                    {
                        uint missingItem = 0;
                        for (uint i = 0; i < SpellConst.MaxReagents; i++)
                        {
                            if (spellInfo.Reagent[i] <= 0)
                                continue;

                            uint itemid = (uint)spellInfo.Reagent[i];
                            uint itemcount = spellInfo.ReagentCount[i];

                            if (!caster.HasItemCount(itemid, itemcount))
                            {
                                missingItem = itemid;
                                break;
                            }
                        }

                        packet.FailedArg1 = (int)missingItem;  // first missing item
                        break;
                    }
                case SpellCastResult.CantUntalent:
                    {
                        if (misc != null)
                        {
                            TalentRecord talent = CliDB.TalentStorage.LookupByKey(misc[0]);
                            if (talent != null)
                                packet.FailedArg1 = (int)talent.SpellID;
                        }
                        break;
                    }
                // TODO: SPELL_FAILED_NOT_STANDING
                default:
                    break;
            }
        }

        public void SendCastResult(SpellCastResult result)
        {
            if (result == SpellCastResult.SpellCastOk)
                return;

            if (!m_caster.IsTypeId(TypeId.Player))
                return;

            if (m_caster.ToPlayer().GetSession().PlayerLoading())  // don't send cast results at loading time
                return;

            if (_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.DontReportCastError))
                result = SpellCastResult.DontReport;

            CastFailed castFailed = new CastFailed();
            castFailed.SpellXSpellVisualID = (int)m_SpellVisual;
            FillSpellCastFailedArgs(castFailed, m_castId, m_spellInfo, result, m_customError, m_misc.GetRawData(), m_caster.ToPlayer());
            m_caster.ToPlayer().SendPacket(castFailed);
        }

        public void SendPetCastResult(SpellCastResult result)
        {
            if (result == SpellCastResult.SpellCastOk)
                return;

            Unit owner = m_caster.GetCharmerOrOwner();
            if (!owner || !owner.IsTypeId(TypeId.Player))
                return;

            if (_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.DontReportCastError))
                result = SpellCastResult.DontReport;

            PetCastFailed petCastFailed = new PetCastFailed();
            FillSpellCastFailedArgs(petCastFailed, m_castId, m_spellInfo, result, SpellCustomErrors.None, m_misc.GetRawData(), owner.ToPlayer());
            owner.ToPlayer().SendPacket(petCastFailed);
        }

        public static void SendCastResult(Player caster, SpellInfo spellInfo, uint spellVisual, ObjectGuid cast_count, SpellCastResult result, SpellCustomErrors customError = SpellCustomErrors.None, uint[] misc = null)
        {
            if (result == SpellCastResult.SpellCastOk)
                return;

            CastFailed packet = new CastFailed();
            packet.SpellXSpellVisualID = (int)spellVisual;
            FillSpellCastFailedArgs(packet, cast_count, spellInfo, result, customError, misc, caster);
            caster.SendPacket(packet);
        }

        void SendSpellStart()
        {
            if (!IsNeedSendToClient())
                return;

            SpellCastFlags castFlags = SpellCastFlags.HasTrajectory;
            uint schoolImmunityMask = m_caster.GetSchoolImmunityMask();
            uint mechanicImmunityMask = m_caster.GetMechanicImmunityMask();
            if (schoolImmunityMask != 0 || mechanicImmunityMask != 0)
                castFlags |= SpellCastFlags.Immunity;

            if (((IsTriggered() && !m_spellInfo.IsAutoRepeatRangedSpell()) || m_triggeredByAuraSpell != null) && !m_fromClient)
                castFlags |= SpellCastFlags.Pending;

            if (m_spellInfo.HasAttribute(SpellAttr0.ReqAmmo) || m_spellInfo.HasAttribute(SpellCustomAttributes.NeedsAmmoData))
                castFlags |= SpellCastFlags.Projectile;

            if ((m_caster.IsTypeId(TypeId.Player) || (m_caster.IsTypeId(TypeId.Unit) && m_caster.IsPet())) && m_powerCost.Any(cost => cost.Power != PowerType.Health))
                castFlags |= SpellCastFlags.PowerLeftSelf;

            if (m_powerCost.Any(cost => cost.Power == PowerType.Runes))
                castFlags |= SpellCastFlags.NoGCD; // not needed, but Blizzard sends it

            SpellStart packet = new SpellStart();
            SpellCastData castData = packet.Cast;

            if (m_CastItem)
                castData.CasterGUID = m_CastItem.GetGUID();
            else
                castData.CasterGUID = m_caster.GetGUID();

            castData.CasterUnit = m_caster.GetGUID();
            castData.CastID = m_castId;
            castData.OriginalCastID = m_originalCastId;
            castData.SpellID = (int)m_spellInfo.Id;
            castData.SpellXSpellVisualID = m_SpellVisual;
            castData.CastFlags = castFlags;
            castData.CastFlagsEx = m_castFlagsEx;
            castData.CastTime = (uint)m_casttime;

            m_targets.Write(castData.Target);

            if (castFlags.HasAnyFlag(SpellCastFlags.PowerLeftSelf))
            {
                foreach (SpellPowerCost cost in m_powerCost)
                {
                    SpellPowerData powerData;
                    powerData.Type = cost.Power;
                    powerData.Cost = m_caster.GetPower(cost.Power);
                    castData.RemainingPower.Add(powerData);
                }
            }

            if (castFlags.HasAnyFlag(SpellCastFlags.RuneList)) // rune cooldowns list
            {
                castData.RemainingRunes.HasValue = true;

                RuneData runeData = castData.RemainingRunes.Value;
                //TODO: There is a crash caused by a spell with CAST_FLAG_RUNE_LIST casted by a creature
                //The creature is the mover of a player, so HandleCastSpellOpcode uses it as the caster

                Player player = m_caster.ToPlayer();
                if (player)
                {
                    runeData.Start = m_runesState; // runes state before
                    runeData.Count = player.GetRunesState(); // runes state after
                    for (byte i = 0; i < PlayerConst.MaxRunes; ++i)
                    {
                        // float casts ensure the division is performed on floats as we need float result
                        float baseCd = player.GetRuneBaseCooldown();
                        runeData.Cooldowns.Add((byte)((baseCd - player.GetRuneCooldown(i)) / baseCd * 255)); // rune cooldown passed
                    }
                }
                else
                {
                    runeData.Start = 0;
                    runeData.Count = 0;
                    for (byte i = 0; i < PlayerConst.MaxRunes; ++i)
                        runeData.Cooldowns.Add(0);
                }
            }

            UpdateSpellCastDataAmmo(castData.Ammo);

            if (castFlags.HasAnyFlag(SpellCastFlags.Immunity))
            {
                castData.Immunities.School = schoolImmunityMask;
                castData.Immunities.Value = mechanicImmunityMask;
            }

            /** @todo implement heal prediction packet data
            if (castFlags & CAST_FLAG_HEAL_PREDICTION)
            {
                castData.Predict.BeconGUID = ??
                castData.Predict.Points = 0;
                castData.Predict.Type = 0;
            }**/

            m_caster.SendMessageToSet(packet, true);
        }

        void SendSpellGo()
        {
            // not send invisible spell casting
            if (!IsNeedSendToClient())
                return;

            Log.outDebug(LogFilter.Spells, "Sending SMSG_SPELL_GO id={0}", m_spellInfo.Id);

            SpellCastFlags castFlags = SpellCastFlags.Unk9;

            // triggered spells with spell visual != 0
            if (((IsTriggered() && !m_spellInfo.IsAutoRepeatRangedSpell()) || m_triggeredByAuraSpell != null) && !m_fromClient)
                castFlags |= SpellCastFlags.Pending;

            if (m_spellInfo.HasAttribute(SpellAttr0.ReqAmmo) || m_spellInfo.HasAttribute(SpellCustomAttributes.NeedsAmmoData))
                castFlags |= SpellCastFlags.Projectile;                        // arrows/bullets visual

            if ((m_caster.IsTypeId(TypeId.Player) || (m_caster.IsTypeId(TypeId.Unit) && m_caster.IsPet())) && m_powerCost.Any(cost => cost.Power != PowerType.Health))
                castFlags |= SpellCastFlags.PowerLeftSelf; // should only be sent to self, but the current messaging doesn't make that possible

            if ((m_caster.IsTypeId(TypeId.Player)) && (m_caster.GetClass() == Class.Deathknight) &&
                m_powerCost.Any(cost => cost.Power == PowerType.Runes) && !_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.IgnorePowerAndReagentCost))
            {
                castFlags |= SpellCastFlags.NoGCD;                   // same as in SMSG_SPELL_START
                castFlags |= SpellCastFlags.RuneList;                    // rune cooldowns list
            }

            if (m_spellInfo.HasEffect(SpellEffectName.ActivateRune))
                castFlags |= SpellCastFlags.RuneList;                    // rune cooldowns list

            if (m_targets.HasTraj())
                castFlags |= SpellCastFlags.AdjustMissile;

            if (m_spellInfo.StartRecoveryTime == 0)
                castFlags |= SpellCastFlags.NoGCD;

            SpellGo packet = new SpellGo();
            SpellCastData castData = packet.Cast;

            if (m_CastItem != null)
                castData.CasterGUID = m_CastItem.GetGUID();
            else
                castData.CasterGUID = m_caster.GetGUID();

            castData.CasterUnit = m_caster.GetGUID();
            castData.CastID = m_castId;
            castData.OriginalCastID = m_originalCastId;
            castData.SpellID = (int)m_spellInfo.Id;
            castData.SpellXSpellVisualID = m_SpellVisual;
            castData.CastFlags = castFlags;
            castData.CastFlagsEx = m_castFlagsEx;
            castData.CastTime = Time.GetMSTime();

            castData.HitTargets = new List<ObjectGuid>();
            UpdateSpellCastDataTargets(castData);

            m_targets.Write(castData.Target);

            if (Convert.ToBoolean(castFlags & SpellCastFlags.PowerLeftSelf))
            {
                castData.RemainingPower = new List<SpellPowerData>();
                foreach (SpellPowerCost cost in m_powerCost)
                {
                    SpellPowerData powerData;
                    powerData.Type = cost.Power;
                    powerData.Cost = m_caster.GetPower(cost.Power);
                    castData.RemainingPower.Add(powerData);
                }
            }

            if (Convert.ToBoolean(castFlags & SpellCastFlags.RuneList))                   // rune cooldowns list
            {
                castData.RemainingRunes.HasValue = true;
                RuneData runeData = castData.RemainingRunes.Value;
                //TODO: There is a crash caused by a spell with CAST_FLAG_RUNE_LIST casted by a creature
                //The creature is the mover of a player, so HandleCastSpellOpcode uses it as the caster
                Player player = m_caster.ToPlayer();
                if (player)
                {
                    runeData.Start = m_runesState; // runes state before
                    runeData.Count = player.GetRunesState(); // runes state after
                    for (byte i = 0; i < PlayerConst.MaxRunes; ++i)
                    {
                        // float casts ensure the division is performed on floats as we need float result
                        float baseCd = player.GetRuneBaseCooldown();
                        runeData.Cooldowns.Add((byte)((baseCd - player.GetRuneCooldown(i)) / baseCd * 255)); // rune cooldown passed
                    }
                }
                else
                {
                    runeData.Start = 0;
                    runeData.Count = 0;
                    for (byte i = 0; i < PlayerConst.MaxRunes; ++i)
                        runeData.Cooldowns.Add(0);
                }
            }

            if (Convert.ToBoolean(castFlags & SpellCastFlags.AdjustMissile))
            {
                castData.MissileTrajectory.TravelTime = (uint)m_delayMoment;
                castData.MissileTrajectory.Pitch = m_targets.GetPitch();
            }

            packet.LogData.Initialize(this);

            m_caster.SendCombatLogMessage(packet);
        }

        // Writes miss and hit targets for a SMSG_SPELL_GO packet
        void UpdateSpellCastDataTargets(SpellCastData data)
        {
            // This function also fill data for channeled spells:
            // m_needAliveTargetMask req for stop channelig if one target die
            foreach (var targetInfo in m_UniqueTargetInfo)
            {
                if (targetInfo.effectMask == 0)                  // No effect apply - all immuned add state
                    // possibly SPELL_MISS_IMMUNE2 for this??
                    targetInfo.missCondition = SpellMissInfo.Immune2;

                if (targetInfo.missCondition == SpellMissInfo.None) // hits
                {
                    data.HitTargets.Add(targetInfo.targetGUID);

                    m_channelTargetEffectMask |= targetInfo.effectMask;
                }
                else // misses
                {
                    data.MissTargets.Add(targetInfo.targetGUID);

                    SpellMissStatus missStatus = new SpellMissStatus();
                    missStatus.Reason = (byte)targetInfo.missCondition;
                    if (targetInfo.missCondition == SpellMissInfo.Reflect)
                        missStatus.ReflectStatus = (byte)targetInfo.reflectResult;

                    data.MissStatus.Add(missStatus);
                }
            }

            foreach (GOTargetInfo targetInfo in m_UniqueGOTargetInfo)
                data.HitTargets.Add(targetInfo.targetGUID); // Always hits

            // Reset m_needAliveTargetMask for non channeled spell
            if (!m_spellInfo.IsChanneled())
                m_channelTargetEffectMask = 0;
        }

        void UpdateSpellCastDataAmmo(SpellAmmo ammo)
        {
            InventoryType ammoInventoryType = 0;
            uint ammoDisplayID = 0;

            if (m_caster.IsTypeId(TypeId.Player))
            {
                Item pItem = m_caster.ToPlayer().GetWeaponForAttack(WeaponAttackType.RangedAttack);
                if (pItem)
                {
                    ammoInventoryType = pItem.GetTemplate().GetInventoryType();
                    if (ammoInventoryType == InventoryType.Thrown)
                        ammoDisplayID = pItem.GetDisplayId(m_caster.ToPlayer());
                    else if (m_caster.HasAura(46699))      // Requires No Ammo
                    {
                        ammoDisplayID = 5996;                   // normal arrow
                        ammoInventoryType = InventoryType.Ammo;
                    }
                }
            }
            else
            {
                for (byte i = 0; i < 3; ++i)
                {
                    uint itemId = m_caster.GetVirtualItemId(i);
                    if (itemId != 0)
                    {
                        ItemRecord itemEntry = CliDB.ItemStorage.LookupByKey(itemId);
                        if (itemEntry != null)
                        {
                            if (itemEntry.Class == ItemClass.Weapon)
                            {
                                switch ((ItemSubClassWeapon)itemEntry.SubClass)
                                {
                                    case ItemSubClassWeapon.Thrown:
                                        ammoDisplayID = Global.DB2Mgr.GetItemDisplayId(itemId, m_caster.GetVirtualItemAppearanceMod(i));
                                        ammoInventoryType = itemEntry.inventoryType;
                                        break;
                                    case ItemSubClassWeapon.Bow:
                                    case ItemSubClassWeapon.Crossbow:
                                        ammoDisplayID = 5996;       // is this need fixing?
                                        ammoInventoryType = InventoryType.Ammo;
                                        break;
                                    case ItemSubClassWeapon.Gun:
                                        ammoDisplayID = 5998;       // is this need fixing?
                                        ammoInventoryType = InventoryType.Ammo;
                                        break;
                                }

                                if (ammoDisplayID != 0)
                                    break;
                            }
                        }
                    }
                }
            }

            ammo.DisplayID = (int)ammoDisplayID;
            ammo.InventoryType = (sbyte)ammoInventoryType;
        }

        void SendSpellExecuteLog()
        {
            SpellExecuteLog spellExecuteLog = new SpellExecuteLog();

            spellExecuteLog.Caster = m_caster.GetGUID();
            spellExecuteLog.SpellID = m_spellInfo.Id;

            foreach (SpellEffectInfo effect in GetEffects())
            {
                if (effect == null)
                    continue;

                if (_powerDrainTargets[effect.EffectIndex].Empty() && _extraAttacksTargets[effect.EffectIndex].Empty() &&
                    _durabilityDamageTargets[effect.EffectIndex].Empty() && _genericVictimTargets[effect.EffectIndex].Empty() &&
                    _tradeSkillTargets[effect.EffectIndex].Empty() && _feedPetTargets[effect.EffectIndex].Empty())
                    continue;

                SpellExecuteLog.SpellLogEffect spellLogEffect = new SpellExecuteLog.SpellLogEffect();
                spellLogEffect.Effect = (int)effect.Effect;
                spellLogEffect.PowerDrainTargets = _powerDrainTargets[effect.EffectIndex];
                spellLogEffect.ExtraAttacksTargets = _extraAttacksTargets[effect.EffectIndex];
                spellLogEffect.DurabilityDamageTargets = _durabilityDamageTargets[effect.EffectIndex];
                spellLogEffect.GenericVictimTargets = _genericVictimTargets[effect.EffectIndex];
                spellLogEffect.TradeSkillTargets = _tradeSkillTargets[effect.EffectIndex];
                spellLogEffect.FeedPetTargets = _feedPetTargets[effect.EffectIndex];
                spellExecuteLog.Effects.Add(spellLogEffect);
            }

            if (!spellExecuteLog.Effects.Empty())
                m_caster.SendCombatLogMessage(spellExecuteLog);

            CleanupExecuteLogList();
        }

        void CleanupExecuteLogList()
        {
            _powerDrainTargets.Clear();
            _extraAttacksTargets.Clear();
            _durabilityDamageTargets.Clear();
            _genericVictimTargets.Clear();
            _tradeSkillTargets.Clear();
            _feedPetTargets.Clear();
        }

        void ExecuteLogEffectTakeTargetPower(uint effIndex, Unit target, PowerType powerType, uint points, float amplitude)
        {
            SpellLogEffectPowerDrainParams spellLogEffectPowerDrainParams;

            spellLogEffectPowerDrainParams.Victim = target.GetGUID();
            spellLogEffectPowerDrainParams.Points = points;
            spellLogEffectPowerDrainParams.PowerType = (uint)powerType;
            spellLogEffectPowerDrainParams.Amplitude = amplitude;

            _powerDrainTargets.Add(effIndex, spellLogEffectPowerDrainParams);
        }

        void ExecuteLogEffectExtraAttacks(uint effIndex, Unit victim, uint numAttacks)
        {
            SpellLogEffectExtraAttacksParams spellLogEffectExtraAttacksParams;
            spellLogEffectExtraAttacksParams.Victim = victim.GetGUID();
            spellLogEffectExtraAttacksParams.NumAttacks = numAttacks;

            _extraAttacksTargets.Add(effIndex, spellLogEffectExtraAttacksParams);
        }

        void ExecuteLogEffectInterruptCast(uint effIndex, Unit victim, uint spellId)
        {
            SpellInterruptLog data = new SpellInterruptLog();
            data.Caster = m_caster.GetGUID();
            data.Victim = victim.GetGUID();
            data.InterruptedSpellID = m_spellInfo.Id;
            data.SpellID = spellId;

            m_caster.SendMessageToSet(data, true);
        }

        void ExecuteLogEffectDurabilityDamage(uint effIndex, Unit victim, int itemId, int amount)
        {
            SpellLogEffectDurabilityDamageParams spellLogEffectDurabilityDamageParams;
            spellLogEffectDurabilityDamageParams.Victim = victim.GetGUID();
            spellLogEffectDurabilityDamageParams.ItemID = itemId;
            spellLogEffectDurabilityDamageParams.Amount = amount;

            _durabilityDamageTargets.Add(effIndex, spellLogEffectDurabilityDamageParams);
        }

        void ExecuteLogEffectOpenLock(uint effIndex, WorldObject obj)
        {
            SpellLogEffectGenericVictimParams spellLogEffectGenericVictimParams;
            spellLogEffectGenericVictimParams.Victim = obj.GetGUID();

            _genericVictimTargets.Add(effIndex, spellLogEffectGenericVictimParams);
        }

        void ExecuteLogEffectCreateItem(uint effIndex, uint entry)
        {
            SpellLogEffectTradeSkillItemParams spellLogEffectTradeSkillItemParams;
            spellLogEffectTradeSkillItemParams.ItemID = (int)entry;

            _tradeSkillTargets.Add(effIndex, spellLogEffectTradeSkillItemParams);
        }

        void ExecuteLogEffectDestroyItem(uint effIndex, uint entry)
        {
            SpellLogEffectFeedPetParams spellLogEffectFeedPetParams;
            spellLogEffectFeedPetParams.ItemID = (int)entry;

            _feedPetTargets.Add(effIndex, spellLogEffectFeedPetParams);
        }

        void ExecuteLogEffectSummonObject(uint effIndex, WorldObject obj)
        {
            SpellLogEffectGenericVictimParams spellLogEffectGenericVictimParams;
            spellLogEffectGenericVictimParams.Victim = obj.GetGUID();

            _genericVictimTargets.Add(effIndex, spellLogEffectGenericVictimParams);
        }

        void ExecuteLogEffectUnsummonObject(uint effIndex, WorldObject obj)
        {
            SpellLogEffectGenericVictimParams spellLogEffectGenericVictimParams;
            spellLogEffectGenericVictimParams.Victim = obj.GetGUID();

            _genericVictimTargets.Add(effIndex, spellLogEffectGenericVictimParams);
        }

        void ExecuteLogEffectResurrect(uint effIndex, Unit target)
        {
            SpellLogEffectGenericVictimParams spellLogEffectGenericVictimParams;
            spellLogEffectGenericVictimParams.Victim = target.GetGUID();

            _genericVictimTargets.Add(effIndex, spellLogEffectGenericVictimParams);
        }

        void SendInterrupted(byte result)
        {
            SpellFailure failurePacket = new SpellFailure();
            failurePacket.CasterUnit = m_caster.GetGUID();
            failurePacket.CastID = m_castId;
            failurePacket.SpellID = m_spellInfo.Id;
            failurePacket.SpellXSpellVisualID = m_SpellVisual;
            failurePacket.Reason = result;
            m_caster.SendMessageToSet(failurePacket, true);

            SpellFailedOther failedPacket = new SpellFailedOther();
            failedPacket.CasterUnit = m_caster.GetGUID();
            failedPacket.CastID = m_castId;
            failedPacket.SpellID = m_spellInfo.Id;
            failedPacket.Reason = result;
            m_caster.SendMessageToSet(failedPacket, true);
        }

        public void SendChannelUpdate(uint time)
        {
            if (time == 0)
            {
                m_caster.ClearDynamicValue(UnitDynamicFields.ChannelObjects);
                m_caster.SetUInt32Value(UnitFields.ChannelSpell, 0);
            }

            SpellChannelUpdate spellChannelUpdate = new SpellChannelUpdate();
            spellChannelUpdate.CasterGUID = m_caster.GetGUID();
            spellChannelUpdate.TimeRemaining = (int)time;
            m_caster.SendMessageToSet(spellChannelUpdate, true);
        }

        void SendChannelStart(uint duration)
        {
            SpellChannelStart spellChannelStart = new SpellChannelStart();
            spellChannelStart.CasterGUID = m_caster.GetGUID();
            spellChannelStart.SpellID = (int)m_spellInfo.Id;
            spellChannelStart.SpellXSpellVisualID = (int)m_SpellVisual;
            spellChannelStart.ChannelDuration = duration;

            uint schoolImmunityMask = m_caster.GetSchoolImmunityMask();
            uint mechanicImmunityMask = m_caster.GetMechanicImmunityMask();

            if (schoolImmunityMask != 0 || mechanicImmunityMask != 0)
            {
                spellChannelStart.InterruptImmunities.HasValue = true;
                spellChannelStart.InterruptImmunities.Value.SchoolImmunities = (int)schoolImmunityMask;
                spellChannelStart.InterruptImmunities.Value.Immunities = (int)mechanicImmunityMask;
            }
            m_caster.SendMessageToSet(spellChannelStart, true);

            m_timer = (int)duration;
            foreach (TargetInfo target in m_UniqueTargetInfo)
                m_caster.AddChannelObject(target.targetGUID);

            foreach (GOTargetInfo target in m_UniqueGOTargetInfo)
                m_caster.AddChannelObject(target.targetGUID);

            m_caster.SetUInt32Value(UnitFields.ChannelSpell, m_spellInfo.Id);
            m_caster.SetUInt32Value(UnitFields.ChannelSpellXSpellVisual, m_SpellVisual);
        }

        void SendResurrectRequest(Player target)
        {
            // get ressurector name for creature resurrections, otherwise packet will be not accepted
            // for player resurrections the name is looked up by guid
            string sentName = (m_caster.IsTypeId(TypeId.Player) ? "" : m_caster.GetName(target.GetSession().GetSessionDbLocaleIndex()));

            ResurrectRequest resurrectRequest = new ResurrectRequest();
            resurrectRequest.ResurrectOffererGUID = m_caster.GetGUID();
            resurrectRequest.ResurrectOffererVirtualRealmAddress = Global.WorldMgr.GetVirtualRealmAddress();

            Pet pet = target.GetPet();
            if (pet)
            {
                CharmInfo charmInfo = pet.GetCharmInfo();
                if (charmInfo != null)
                    resurrectRequest.PetNumber = charmInfo.GetPetNumber();
            }

            resurrectRequest.SpellID = m_spellInfo.Id;

            //packet.ReadBit("UseTimer"); /// @todo: 6.x Has to be implemented
            resurrectRequest.Sickness = !m_caster.IsTypeId(TypeId.Player); // "you'll be afflicted with resurrection sickness"

            resurrectRequest.Name = sentName;

            target.SendPacket(resurrectRequest);
        }

        void TakeCastItem()
        {
            if (m_CastItem == null || !m_caster.IsTypeId(TypeId.Player))
                return;

            // not remove cast item at triggered spell (equipping, weapon damage, etc)
            if (Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCastItem))
                return;

            ItemTemplate proto = m_CastItem.GetTemplate();
            if (proto == null)
            {
                // This code is to avoid a crash
                // I'm not sure, if this is really an error, but I guess every item needs a prototype
                Log.outError(LogFilter.Spells, "Cast item has no item prototype {0}", m_CastItem.GetGUID().ToString());
                return;
            }

            bool expendable = false;
            bool withoutCharges = false;

            for (int i = 0; i < proto.Effects.Count && i < 5; ++i)
            {
                if (proto.Effects[i].SpellID != 0)
                {
                    // item has limited charges
                    if (proto.Effects[i].Charges != 0)
                    {
                        if (proto.Effects[i].Charges < 0)
                            expendable = true;

                        int charges = m_CastItem.GetSpellCharges(i);

                        // item has charges left
                        if (charges != 0)
                        {
                            if (charges > 0)
                                --charges;
                            else
                                ++charges;

                            if (proto.GetMaxStackSize() == 1)
                                m_CastItem.SetSpellCharges(i, charges);
                            m_CastItem.SetState(ItemUpdateState.Changed, m_caster.ToPlayer());
                        }

                        // all charges used
                        withoutCharges = (charges == 0);
                    }
                }
            }

            if (expendable && withoutCharges)
            {
                uint count = 1;
                m_caster.ToPlayer().DestroyItemCount(m_CastItem, ref count, true);

                // prevent crash at access to deleted m_targets.GetItemTarget
                if (m_CastItem == m_targets.GetItemTarget())
                    m_targets.SetItemTarget(null);

                m_CastItem = null;
                m_castItemGUID.Clear();
                m_castItemEntry = 0;
            }
        }

        void TakePower()
        {
            if (m_CastItem != null || m_triggeredByAuraSpell != null)
                return;

            //Don't take power if the spell is cast while .cheat power is enabled.
            if (m_caster.IsTypeId(TypeId.Player))
            {
                if (m_caster.ToPlayer().GetCommandStatus(PlayerCommandStates.Power))
                    return;
            }

            foreach (SpellPowerCost cost in m_powerCost)
            {
                bool hit = true;
                if (m_caster.IsTypeId(TypeId.Player))
                {
                    if (cost.Power == PowerType.Rage || cost.Power == PowerType.Energy || cost.Power == PowerType.Runes)
                    {
                        ObjectGuid targetGUID = m_targets.GetUnitTargetGUID();
                        if (!targetGUID.IsEmpty())
                        {
                            foreach (var ihit in m_UniqueTargetInfo)
                            {
                                if (ihit.targetGUID == targetGUID)
                                {
                                    if (ihit.missCondition != SpellMissInfo.None)
                                    {
                                        hit = false;
                                        //lower spell cost on fail (by talent aura)
                                        Player modOwner = m_caster.ToPlayer().GetSpellModOwner();
                                        if (modOwner)
                                            modOwner.ApplySpellMod(m_spellInfo.Id, SpellModOp.SpellCostRefundOnFail, ref cost.Amount);
                                    }
                                    break;
                                }
                            }
                        }
                    }
                }

                if (cost.Power == PowerType.Runes)
                {
                    TakeRunePower(hit);
                    continue;
                }

                if (cost.Amount == 0)
                    continue;

                // health as power used
                if (cost.Power == PowerType.Health)
                {
                    m_caster.ModifyHealth(-cost.Amount);
                    continue;
                }

                if (cost.Power >= PowerType.Max)
                {
                    Log.outError(LogFilter.Spells, "Spell.TakePower: Unknown power type '{0}'", cost.Power);
                    continue;
                }

                if (hit)
                    m_caster.ModifyPower(cost.Power, -cost.Amount);
                else
                    m_caster.ModifyPower(cost.Power, -RandomHelper.IRand(0, cost.Amount / 4));
            }
        }

        SpellCastResult CheckRuneCost()
        {
            var runeCost = m_powerCost.Find(cost => cost.Power == PowerType.Runes);
            if (runeCost == null)
                return SpellCastResult.SpellCastOk;

            Player player = m_caster.ToPlayer();
            if (!player)
                return SpellCastResult.SpellCastOk;

            if (player.GetClass() != Class.Deathknight)
                return SpellCastResult.SpellCastOk;

            int readyRunes = 0;
            for (byte i = 0; i < player.GetMaxPower(PowerType.Runes); ++i)
                if (player.GetRuneCooldown(i) == 0)
                    ++readyRunes;

            if (readyRunes < runeCost.Amount)
                return SpellCastResult.NoPower;                       // not sure if result code is correct

            return SpellCastResult.SpellCastOk;
        }

        void TakeRunePower(bool didHit)
        {
            if (!m_caster.IsTypeId(TypeId.Player) || m_caster.GetClass() != Class.Deathknight)
                return;

            Player player = m_caster.ToPlayer();
            m_runesState = player.GetRunesState();                 // store previous state

            int runeCost = m_powerCost.Find(cost => cost.Power == PowerType.Runes).Amount;

            for (byte i = 0; i < player.GetMaxPower(PowerType.Runes); ++i)
            {
                if (player.GetRuneCooldown(i) == 0 && runeCost > 0)
                {
                    player.SetRuneCooldown(i, didHit ? player.GetRuneBaseCooldown() : RuneCooldowns.Miss, true);
                    --runeCost;
                }
            }
        }

        void TakeReagents()
        {
            if (!m_caster.IsTypeId(TypeId.Player))
                return;

            ItemTemplate castItemTemplate = m_CastItem?.GetTemplate();

            // do not take reagents for these item casts
            if (castItemTemplate != null && Convert.ToBoolean(castItemTemplate.GetFlags() & ItemFlags.NoReagentCost))
                return;

            Player p_caster = m_caster.ToPlayer();
            if (p_caster.CanNoReagentCast(m_spellInfo))
                return;

            for (int x = 0; x < SpellConst.MaxReagents; ++x)
            {
                if (m_spellInfo.Reagent[x] <= 0)
                    continue;

                uint itemid = (uint)m_spellInfo.Reagent[x];
                uint itemcount = m_spellInfo.ReagentCount[x];

                // if CastItem is also spell reagent
                if (castItemTemplate != null && castItemTemplate.GetId() == itemid)
                {
                    for (int s = 0; s < castItemTemplate.Effects.Count && s < 5; ++s)
                    {
                        // CastItem will be used up and does not count as reagent
                        int charges = m_CastItem.GetSpellCharges(s);
                        if (castItemTemplate.Effects[s].Charges < 0 && Math.Abs(charges) < 2)
                        {
                            ++itemcount;
                            break;
                        }
                    }

                    m_CastItem = null;
                    m_castItemGUID.Clear();
                    m_castItemEntry = 0;
                }

                // if GetItemTarget is also spell reagent
                if (m_targets.GetItemTargetEntry() == itemid)
                    m_targets.SetItemTarget(null);

                p_caster.DestroyItemCount(itemid, itemcount, true);
            }
        }

        void HandleThreatSpells()
        {
            if (m_UniqueTargetInfo.Empty())
                return;

            if (!m_spellInfo.HasInitialAggro())
                return;

            float threat = 0.0f;
            SpellThreatEntry threatEntry = Global.SpellMgr.GetSpellThreatEntry(m_spellInfo.Id);
            if (threatEntry != null)
            {
                if (threatEntry.apPctMod != 0.0f)
                    threat += threatEntry.apPctMod * m_caster.GetTotalAttackPowerValue(WeaponAttackType.BaseAttack);

                threat += threatEntry.flatMod;
            }
            else if (!m_spellInfo.HasAttribute(SpellCustomAttributes.NoInitialThreat))
                threat += m_spellInfo.SpellLevel;

            // past this point only multiplicative effects occur
            if (threat == 0.0f)
                return;

            // since 2.0.1 threat from positive effects also is distributed among all targets, so the overall caused threat is at most the defined bonus
            threat /= m_UniqueTargetInfo.Count;

            foreach (var ihit in m_UniqueTargetInfo)
            {
                float threatToAdd = threat;
                if (ihit.missCondition != SpellMissInfo.None)
                    threatToAdd = 0.0f;

                Unit target = Global.ObjAccessor.GetUnit(m_caster, ihit.targetGUID);
                if (target == null)
                    continue;

                // positive spells distribute threat among all units that are in combat with target, like healing
                if (m_spellInfo._IsPositiveSpell())
                    target.getHostileRefManager().threatAssist(m_caster, threatToAdd, m_spellInfo);
                // for negative spells threat gets distributed among affected targets
                else
                {
                    if (!target.CanHaveThreatList())
                        continue;

                    target.AddThreat(m_caster, threatToAdd, m_spellInfo.GetSchoolMask(), m_spellInfo);
                }
            }
            Log.outDebug(LogFilter.Spells, "Spell {0}, added an additional {1} threat for {2} {3} target(s)", m_spellInfo.Id, threat, m_spellInfo._IsPositiveSpell() ? "assisting" : "harming", m_UniqueTargetInfo.Count);
        }

        void HandleEffects(Unit pUnitTarget, Item pItemTarget, GameObject pGOTarget, uint i, SpellEffectHandleMode mode)
        {
            effectHandleMode = mode;
            unitTarget = pUnitTarget;
            itemTarget = pItemTarget;
            gameObjTarget = pGOTarget;
            destTarget = m_destTargets[i].Position;

            effectInfo = GetEffect(i);
            if (effectInfo == null)
            {
                Log.outError(LogFilter.Spells, "Spell: {0} HandleEffects at EffectIndex: {1} missing effect", m_spellInfo.Id, i);
                return;
            }
            SpellEffectName eff = effectInfo.Effect;

            Log.outDebug(LogFilter.Spells, "Spell: {0} Effect : {1}", m_spellInfo.Id, eff);

            damage = CalculateDamage(i, unitTarget, out variance);

            bool preventDefault = CallScriptEffectHandlers(i, mode);

            if (!preventDefault && eff < SpellEffectName.TotalSpellEffects)
                Global.SpellMgr.GetSpellEffectHandler(eff).Invoke(this, i);
        }

        public SpellCastResult CheckCast(bool strict)
        {
            SpellCastResult castResult = SpellCastResult.SpellCastOk;
            // check death state
            if (!m_caster.IsAlive() && !m_spellInfo.IsPassive() && !(m_spellInfo.HasAttribute(SpellAttr0.CastableWhileDead) || (IsTriggered() && m_triggeredByAuraSpell == null)))
                return SpellCastResult.CasterDead;

            // check cooldowns to prevent cheating
            if (!m_spellInfo.IsPassive())
            {
                if (m_caster.IsTypeId(TypeId.Player))
                {
                    //can cast triggered (by aura only?) spells while have this flag
                    if (!_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.IgnoreCasterAurastate) && m_caster.ToPlayer().HasFlag(PlayerFields.Flags, PlayerFlags.AllowOnlyAbility))
                        return SpellCastResult.SpellInProgress;

                    // check if we are using a potion in combat for the 2nd+ time. Cooldown is added only after caster gets out of combat
                    if (m_caster.ToPlayer().GetLastPotionId() != 0 && m_CastItem && (m_CastItem.IsPotion() || m_spellInfo.IsCooldownStartedOnEvent()))
                        return SpellCastResult.NotReady;
                }

                if (!m_caster.GetSpellHistory().IsReady(m_spellInfo, m_castItemEntry, IsIgnoringCooldowns()))
                {
                    if (m_triggeredByAuraSpell != null)
                        return SpellCastResult.DontReport;
                    else
                        return SpellCastResult.NotReady;
                }
            }

            if (m_spellInfo.HasAttribute(SpellAttr7.IsCheatSpell) && !m_caster.HasFlag(UnitFields.Flags2, UnitFlags2.AllowCheatSpells))
            {
                m_customError = SpellCustomErrors.GmOnly;
                return SpellCastResult.CustomError;
            }

            // Check global cooldown
            if (strict && !Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreGCD) && HasGlobalCooldown())
                return !m_spellInfo.HasAttribute(SpellAttr0.DisabledWhileActive) ? SpellCastResult.NotReady : SpellCastResult.DontReport;

            // only triggered spells can be processed an ended Battleground
            if (!IsTriggered() && m_caster.IsTypeId(TypeId.Player))
            {
                Battleground bg = m_caster.ToPlayer().GetBattleground();
                if (bg)
                    if (bg.GetStatus() == BattlegroundStatus.WaitLeave)
                        return SpellCastResult.DontReport;
            }

            if (m_caster.IsTypeId(TypeId.Player) && Global.VMapMgr.isLineOfSightCalcEnabled())
            {
                if (m_spellInfo.HasAttribute(SpellAttr0.OutdoorsOnly) &&
                        !m_caster.GetMap().IsOutdoors(m_caster.posX, m_caster.posY, m_caster.posZ))
                    return SpellCastResult.OnlyOutdoors;

                if (m_spellInfo.HasAttribute(SpellAttr0.IndoorsOnly) &&
                        m_caster.GetMap().IsOutdoors(m_caster.posX, m_caster.posY, m_caster.posZ))
                    return SpellCastResult.OnlyIndoors;
            }

            // only check at first call, Stealth auras are already removed at second call
            // for now, ignore triggered spells
            if (strict && !Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreShapeshift))
            {
                bool checkForm = true;
                // Ignore form req aura
                var ignore = m_caster.GetAuraEffectsByType(AuraType.ModIgnoreShapeshift);
                foreach (var aura in ignore)
                {
                    if (!aura.IsAffectingSpell(m_spellInfo))
                        continue;
                    checkForm = false;
                    break;
                }
                if (checkForm)
                {
                    // Cannot be used in this stance/form
                    SpellCastResult shapeError = m_spellInfo.CheckShapeshift(m_caster.GetShapeshiftForm());
                    if (shapeError != SpellCastResult.SpellCastOk)
                        return shapeError;

                    if (m_spellInfo.HasAttribute(SpellAttr0.OnlyStealthed) && !m_caster.HasStealthAura())
                        return SpellCastResult.OnlyStealthed;
                }
            }

            var blockSpells = m_caster.GetAuraEffectsByType(AuraType.BlockSpellFamily);
            foreach (var block in blockSpells)
                if (block.GetMiscValue() == (int)m_spellInfo.SpellFamilyName)
                    return SpellCastResult.SpellUnavailable;

            bool reqCombat = true;
            var stateAuras = m_caster.GetAuraEffectsByType(AuraType.AbilityIgnoreAurastate);
            foreach (var aura in stateAuras)
            {
                if (aura.IsAffectingSpell(m_spellInfo))
                {
                    m_needComboPoints = false;
                    if (aura.GetMiscValue() == 1)
                    {
                        reqCombat = false;
                        break;
                    }
                }
            }

            // caster state requirements
            // not for triggered spells (needed by execute)
            if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCasterAurastate))
            {
                if (m_spellInfo.CasterAuraState != 0 && !m_caster.HasAuraState(m_spellInfo.CasterAuraState, m_spellInfo, m_caster))
                    return SpellCastResult.CasterAurastate;
                if (m_spellInfo.CasterAuraStateNot != 0 && m_caster.HasAuraState(m_spellInfo.CasterAuraStateNot, m_spellInfo, m_caster))
                    return SpellCastResult.CasterAurastate;

                // Note: spell 62473 requres casterAuraSpell = triggering spell
                if (m_spellInfo.CasterAuraSpell != 0 && !m_caster.HasAura(m_spellInfo.CasterAuraSpell))
                    return SpellCastResult.CasterAurastate;
                if (m_spellInfo.ExcludeCasterAuraSpell != 0 && m_caster.HasAura(m_spellInfo.ExcludeCasterAuraSpell))
                    return SpellCastResult.CasterAurastate;

                if (reqCombat && m_caster.IsInCombat() && !m_spellInfo.CanBeUsedInCombat())
                    return SpellCastResult.AffectingCombat;
            }

            // cancel autorepeat spells if cast start when moving
            // (not wand currently autorepeat cast delayed to moving stop anyway in spell update code)
            // Do not cancel spells which are affected by a SPELL_AURA_CAST_WHILE_WALKING effect
            if (m_caster.IsTypeId(TypeId.Player) && m_caster.ToPlayer().isMoving() && (!m_caster.IsCharmed() || !m_caster.GetCharmerGUID().IsCreature()) && !m_caster.HasAuraTypeWithAffectMask(AuraType.CastWhileWalking, m_spellInfo))
            {
                // skip stuck spell to allow use it in falling case and apply spell limitations at movement
                SpellEffectInfo effect = GetEffect(0);
                if ((!m_caster.HasUnitMovementFlag(MovementFlag.FallingFar) || (effect != null && effect.Effect != SpellEffectName.Stuck)) &&
                    (IsAutoRepeat() || (m_spellInfo.AuraInterruptFlags & SpellAuraInterruptFlags.NotSeated) != 0))
                    return SpellCastResult.Moving;
            }

            // Check vehicle flags
            if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCasterMountedOrOnVehicle))
            {
                SpellCastResult vehicleCheck = m_spellInfo.CheckVehicle(m_caster);
                if (vehicleCheck != SpellCastResult.SpellCastOk)
                    return vehicleCheck;
            }

            // check spell cast conditions from database
            {
                ConditionSourceInfo condInfo = new ConditionSourceInfo(m_caster, m_targets.GetObjectTarget());
                if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.Spell, m_spellInfo.Id, condInfo))
                {
                    // mLastFailedCondition can be NULL if there was an error processing the condition in Condition.Meets (i.e. wrong data for ConditionTarget or others)
                    if (condInfo.mLastFailedCondition != null && condInfo.mLastFailedCondition.ErrorType != 0)
                    {
                        if (condInfo.mLastFailedCondition.ErrorType == (uint)SpellCastResult.CustomError)
                            m_customError = (SpellCustomErrors)condInfo.mLastFailedCondition.ErrorTextId;
                        return (SpellCastResult)condInfo.mLastFailedCondition.ErrorType;
                    }

                    if (condInfo.mLastFailedCondition == null || condInfo.mLastFailedCondition.ConditionTarget == 0)
                        return SpellCastResult.CasterAurastate;
                    return SpellCastResult.BadTargets;
                }
            }

            // Don't check explicit target for passive spells (workaround) (check should be skipped only for learn case)
            // those spells may have incorrect target entries or not filled at all (for example 15332)
            // such spells when learned are not targeting anyone using targeting system, they should apply directly to caster instead
            // also, such casts shouldn't be sent to client
            if (!(m_spellInfo.IsPassive() && (m_targets.GetUnitTarget() == null || m_targets.GetUnitTarget() == m_caster)))
            {
                // Check explicit target for m_originalCaster - todo: get rid of such workarounds
                castResult = m_spellInfo.CheckExplicitTarget(m_originalCaster ?? m_caster, m_targets.GetObjectTarget(), m_targets.GetItemTarget());
                if (castResult != SpellCastResult.SpellCastOk)
                    return castResult;
            }

            Unit unitTarget = m_targets.GetUnitTarget();
            if (unitTarget != null)
            {
                castResult = m_spellInfo.CheckTarget(m_caster, unitTarget, false);
                if (castResult != SpellCastResult.SpellCastOk)
                    return castResult;

                // If it's not a melee spell, check if vision is obscured by SPELL_AURA_INTERFERE_TARGETTING
                if (m_spellInfo.DmgClass != SpellDmgClass.Melee)
                {
                    foreach (var auraEffect in m_caster.GetAuraEffectsByType(AuraType.InterfereTargetting))
                        if (!m_caster.IsFriendlyTo(auraEffect.GetCaster()) && !unitTarget.HasAura(auraEffect.GetId(), auraEffect.GetCasterGUID()))
                            return SpellCastResult.VisionObscured;

                    foreach (var auraEffect in unitTarget.GetAuraEffectsByType(AuraType.InterfereTargetting))
                        if (!m_caster.IsFriendlyTo(auraEffect.GetCaster()) && (!unitTarget.HasAura(auraEffect.GetId(), auraEffect.GetCasterGUID()) || !m_caster.HasAura(auraEffect.GetId(), auraEffect.GetCasterGUID())))
                            return SpellCastResult.VisionObscured;
                }

                if (unitTarget != m_caster)
                {
                    // Must be behind the target
                    if (m_spellInfo.HasAttribute(SpellCustomAttributes.ReqCasterBehindTarget) && unitTarget.HasInArc(MathFunctions.PI, m_caster))
                        return SpellCastResult.NotBehind;

                    // Target must be facing you
                    if (m_spellInfo.HasAttribute(SpellCustomAttributes.ReqTargetFacingCaster) && !unitTarget.HasInArc(MathFunctions.PI, m_caster))
                        return SpellCastResult.NotInfront;

                    if (m_caster.GetEntry() != SharedConst.WorldTrigger) // Ignore LOS for gameobjects casts (wrongly casted by a trigger)
                    {
                        WorldObject losTarget = m_caster;
                        if (IsTriggered() && m_triggeredByAuraSpell != null)
                        {
                            DynamicObject dynObj = m_caster.GetDynObject(m_triggeredByAuraSpell.Id);
                            if (dynObj)
                                losTarget = dynObj;
                        }

                        if (!m_spellInfo.HasAttribute(SpellAttr2.CanTargetNotInLos) && !Global.DisableMgr.IsDisabledFor(DisableType.Spell, m_spellInfo.Id, null, DisableFlags.SpellLOS)
                            && !unitTarget.IsWithinLOSInMap(losTarget))
                            return SpellCastResult.LineOfSight;
                    }
                }
            }

            // Check for line of sight for spells with dest
            if (m_targets.HasDst())
            {
                float x, y, z;
                m_targets.GetDstPos().GetPosition(out x, out y, out z);

                if (!m_spellInfo.HasAttribute(SpellAttr2.CanTargetDead) && !Global.DisableMgr.IsDisabledFor(DisableType.Spell, m_spellInfo.Id, null, DisableFlags.SpellLOS)
                    && !m_caster.IsWithinLOS(x, y, z))
                    return SpellCastResult.LineOfSight;
            }

            // check pet presence
            foreach (SpellEffectInfo effect in GetEffects())
            {
                if (effect != null && effect.TargetA.GetTarget() == Targets.UnitPet)
                {
                    if (m_caster.GetGuardianPet() == null)
                    {
                        if (m_triggeredByAuraSpell != null)              // not report pet not existence for triggered spells
                            return SpellCastResult.DontReport;
                        else
                            return SpellCastResult.NoPet;
                    }
                    break;
                }
            }

            // Spell casted only on Battleground
            if (m_spellInfo.HasAttribute(SpellAttr3.Battleground) && m_caster.IsTypeId(TypeId.Player))
                if (!m_caster.ToPlayer().InBattleground())
                    return SpellCastResult.OnlyBattlegrounds;

            // do not allow spells to be cast in arenas or rated Battlegrounds
            Player player = m_caster.ToPlayer();
            if (player != null)
                if (player.InArena()/* || player.InRatedBattleground() NYI*/)
                {
                    castResult = CheckArenaAndRatedBattlegroundCastRules();
                    if (castResult != SpellCastResult.SpellCastOk)
                        return castResult;
                }

            // zone check
            if (m_caster.IsTypeId(TypeId.Unit) || !m_caster.ToPlayer().IsGameMaster())
            {
                uint zone, area;
                m_caster.GetZoneAndAreaId(out zone, out area);

                SpellCastResult locRes = m_spellInfo.CheckLocation(m_caster.GetMapId(), zone, area, m_caster.ToPlayer());
                if (locRes != SpellCastResult.SpellCastOk)
                    return locRes;
            }

            // not let players cast spells at mount (and let do it to creatures)
            if (m_caster.IsMounted() && m_caster.IsTypeId(TypeId.Player) && !Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCasterMountedOrOnVehicle) &&
                !m_spellInfo.IsPassive() && !m_spellInfo.HasAttribute(SpellAttr0.CastableWhileMounted))
            {
                if (m_caster.IsInFlight())
                    return SpellCastResult.NotOnTaxi;
                else
                    return SpellCastResult.NotMounted;
            }

            // check spell focus object
            if (m_spellInfo.RequiresSpellFocus != 0)
            {
                focusObject = SearchSpellFocus();
                if (!focusObject)
                    return SpellCastResult.RequiresSpellFocus;
            }

            castResult = SpellCastResult.SpellCastOk;

            // always (except passive spells) check items (focus object can be required for any type casts)
            if (!m_spellInfo.IsPassive())
            {
                castResult = CheckItems();
                if (castResult != SpellCastResult.SpellCastOk)
                    return castResult;
            }

            // Triggered spells also have range check
            // @todo determine if there is some flag to enable/disable the check
            castResult = CheckRange(strict);
            if (castResult != SpellCastResult.SpellCastOk)
                return castResult;

            if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnorePowerAndReagentCost))
            {
                castResult = CheckPower();
                if (castResult != SpellCastResult.SpellCastOk)
                    return castResult;
            }

            if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCasterAuras))
            {
                castResult = CheckCasterAuras();
                if (castResult != SpellCastResult.SpellCastOk)
                    return castResult;
            }

            // script hook
            castResult = CallScriptCheckCastHandlers();
            if (castResult != SpellCastResult.SpellCastOk)
                return castResult;

            foreach (SpellEffectInfo effect in GetEffects())
            {
                if (effect == null)
                    continue;

                // for effects of spells that have only one target
                switch (effect.Effect)
                {
                    case SpellEffectName.Dummy:
                        {
                            if (m_spellInfo.Id == 19938)          // Awaken Peon
                            {
                                Unit unit = m_targets.GetUnitTarget();
                                if (unit == null || !unit.HasAura(17743))
                                    return SpellCastResult.BadTargets;
                            }
                            else if (m_spellInfo.Id == 31789)          // Righteous Defense
                            {
                                if (!m_caster.IsTypeId(TypeId.Player))
                                    return SpellCastResult.DontReport;

                                Unit target1 = m_targets.GetUnitTarget();
                                if (target1 == null || !target1.IsFriendlyTo(m_caster) || target1.getAttackers().Empty())
                                    return SpellCastResult.BadTargets;

                            }
                            break;
                        }
                    case SpellEffectName.LearnSpell:
                        {
                            if (!m_caster.IsTypeId(TypeId.Player))
                                return SpellCastResult.BadTargets;

                            if (effect.TargetA.GetTarget() != Targets.UnitPet)
                                break;

                            Pet pet = m_caster.ToPlayer().GetPet();

                            if (pet == null)
                                return SpellCastResult.NoPet;

                            SpellInfo learn_spellproto = Global.SpellMgr.GetSpellInfo(effect.TriggerSpell);

                            if (learn_spellproto == null)
                                return SpellCastResult.NotKnown;

                            if (m_spellInfo.SpellLevel > pet.getLevel())
                                return SpellCastResult.Lowlevel;

                            break;
                        }
                    case SpellEffectName.UnlockGuildVaultTab:
                        {
                            if (!m_caster.IsTypeId(TypeId.Player))
                                return SpellCastResult.BadTargets;
                            var guild = m_caster.ToPlayer().GetGuild();
                            if (guild != null)
                                if (guild.GetLeaderGUID() != m_caster.ToPlayer().GetGUID())
                                    return SpellCastResult.CantDoThatRightNow;
                            break;
                        }
                    case SpellEffectName.LearnPetSpell:
                        {
                            // check target only for unit target case
                            Unit target = m_targets.GetUnitTarget();
                            if (target != null)
                            {
                                if (!m_caster.IsTypeId(TypeId.Player))
                                    return SpellCastResult.BadTargets;

                                Pet pet = target.ToPet();
                                if (pet == null || pet.GetOwner() != m_caster)
                                    return SpellCastResult.BadTargets;

                                SpellInfo learn_spellproto = Global.SpellMgr.GetSpellInfo(effect.TriggerSpell);

                                if (learn_spellproto == null)
                                    return SpellCastResult.NotKnown;

                                if (m_spellInfo.SpellLevel > pet.getLevel())
                                    return SpellCastResult.Lowlevel;
                            }
                            break;
                        }
                    case SpellEffectName.ApplyGlyph:
                        {
                            if (!m_caster.IsTypeId(TypeId.Player))
                                return SpellCastResult.GlyphNoSpec;

                            Player caster = m_caster.ToPlayer();
                            if (!caster.HasSpell(m_misc.SpellId))
                                return SpellCastResult.NotKnown;

                            uint glyphId = (uint)effect.MiscValue;
                            if (glyphId != 0)
                            {
                                GlyphPropertiesRecord glyphProperties = CliDB.GlyphPropertiesStorage.LookupByKey(glyphId);
                                if (glyphProperties == null)
                                    return SpellCastResult.InvalidGlyph;

                                List<uint> glyphBindableSpells = Global.DB2Mgr.GetGlyphBindableSpells(glyphId);
                                if (glyphBindableSpells.Empty())
                                    return SpellCastResult.InvalidGlyph;

                                if (!glyphBindableSpells.Contains(m_misc.SpellId))
                                    return SpellCastResult.InvalidGlyph;

                                List<uint> glyphRequiredSpecs = Global.DB2Mgr.GetGlyphRequiredSpecs(glyphId);
                                if (!glyphRequiredSpecs.Empty())
                                {
                                    if (caster.GetUInt32Value(PlayerFields.CurrentSpecId) == 0)
                                        return SpellCastResult.GlyphNoSpec;

                                    if (!glyphRequiredSpecs.Contains(caster.GetUInt32Value(PlayerFields.CurrentSpecId)))
                                        return SpellCastResult.GlyphInvalidSpec;
                                }

                                uint replacedGlyph = 0;
                                foreach (uint activeGlyphId in caster.GetGlyphs(caster.GetActiveTalentGroup()))
                                {
                                    List<uint> activeGlyphBindableSpells = Global.DB2Mgr.GetGlyphBindableSpells(activeGlyphId);
                                    if (!activeGlyphBindableSpells.Empty())
                                    {
                                        if (activeGlyphBindableSpells.Contains(m_misc.SpellId))
                                        {
                                            replacedGlyph = activeGlyphId;
                                            break;
                                        }
                                    }
                                }

                                foreach (uint activeGlyphId in caster.GetGlyphs(caster.GetActiveTalentGroup()))
                                {
                                    if (activeGlyphId == replacedGlyph)
                                        continue;

                                    if (activeGlyphId == glyphId)
                                        return SpellCastResult.UniqueGlyph;

                                    if (CliDB.GlyphPropertiesStorage.LookupByKey(activeGlyphId).GlyphExclusiveCategoryID == glyphProperties.GlyphExclusiveCategoryID)
                                        return SpellCastResult.GlyphExclusiveCategory;
                                }
                            }
                            break;
                        }
                    case SpellEffectName.FeedPet:
                        {
                            if (!m_caster.IsTypeId(TypeId.Player))
                                return SpellCastResult.BadTargets;

                            Item foodItem = m_targets.GetItemTarget();
                            if (!foodItem)
                                return SpellCastResult.BadTargets;

                            Pet pet = m_caster.ToPlayer().GetPet();
                            if (!pet)
                                return SpellCastResult.NoPet;

                            if (!pet.HaveInDiet(foodItem.GetTemplate()))
                                return SpellCastResult.WrongPetFood;

                            if (pet.GetCurrentFoodBenefitLevel(foodItem.GetTemplate().GetBaseItemLevel()) == 0)
                                return SpellCastResult.FoodLowlevel;

                            if (m_caster.IsInCombat() || pet.IsInCombat())
                                return SpellCastResult.AffectingCombat;

                            break;
                        }
                    case SpellEffectName.PowerBurn:
                    case SpellEffectName.PowerDrain:
                        {
                            // Can be area effect, Check only for players and not check if target - caster (spell can have multiply drain/burn effects)
                            if (m_caster.IsTypeId(TypeId.Player))
                            {
                                Unit target1 = m_targets.GetUnitTarget();
                                if (target1 != null)
                                    if (target1 != m_caster && unitTarget.getPowerType() != (PowerType)effect.MiscValue)
                                        return SpellCastResult.BadTargets;
                            }
                            break;
                        }
                    case SpellEffectName.Charge:
                        {
                            if (m_caster.HasUnitState(UnitState.Root))
                                return SpellCastResult.Rooted;

                            if (GetSpellInfo().NeedsExplicitUnitTarget())
                            {
                                Unit target1 = m_targets.GetUnitTarget();
                                if (target1 == null)
                                    return SpellCastResult.DontReport;


                                float objSize = target1.GetObjectSize();
                                float range = m_spellInfo.GetMaxRange(true, m_caster, this) * 1.5f + objSize; // can't be overly strict

                                m_preGeneratedPath.SetPathLengthLimit(range);
                                //first try with raycast, if it fails fall back to normal path
                                float targetObjectSize = Math.Min(target1.GetObjectSize(), 4.0f);
                                bool result = m_preGeneratedPath.CalculatePath(target1.GetPositionX(), target1.GetPositionY(), target1.GetPositionZ() + targetObjectSize, false, true);
                                if (m_preGeneratedPath.GetPathType().HasAnyFlag(PathType.Short))
                                    return SpellCastResult.OutOfRange;
                                else if (!result || m_preGeneratedPath.GetPathType().HasAnyFlag(PathType.NoPath | PathType.Incomplete))
                                {
                                    result = m_preGeneratedPath.CalculatePath(target1.GetPositionX(), target1.GetPositionY(), target1.GetPositionZ() + targetObjectSize, false, false);
                                    if (m_preGeneratedPath.GetPathType().HasAnyFlag(PathType.Short))
                                        return SpellCastResult.OutOfRange;
                                    else if (!result || m_preGeneratedPath.GetPathType().HasAnyFlag(PathType.NoPath | PathType.Incomplete))
                                        return SpellCastResult.NoPath;
                                    else if (m_preGeneratedPath.IsInvalidDestinationZ(target1)) // Check position z, if not in a straight line
                                        return SpellCastResult.NoPath;
                                }
                                else if (m_preGeneratedPath.IsInvalidDestinationZ(target1)) // Check position z, if in a straight line
                                    return SpellCastResult.NoPath;

                                m_preGeneratedPath.ReducePathLenghtByDist(objSize); //move back
                            }
                            break;
                        }
                    case SpellEffectName.Skinning:
                        {
                            if (!m_caster.IsTypeId(TypeId.Player) || m_targets.GetUnitTarget() == null || !m_targets.GetUnitTarget().IsTypeId(TypeId.Unit))
                                return SpellCastResult.BadTargets;

                            if (!Convert.ToBoolean(m_targets.GetUnitTarget().GetUInt32Value(UnitFields.Flags) & (uint)UnitFlags.Skinnable))
                                return SpellCastResult.TargetUnskinnable;

                            Creature creature = m_targets.GetUnitTarget().ToCreature();
                            if (!creature.IsCritter() && !creature.loot.isLooted())
                                return SpellCastResult.TargetNotLooted;

                            SkillType skill = creature.GetCreatureTemplate().GetRequiredLootSkill();

                            ushort skillValue = m_caster.ToPlayer().GetSkillValue(skill);
                            uint TargetLevel = m_targets.GetUnitTarget().getLevel();
                            int ReqValue = (int)(skillValue < 100 ? (TargetLevel - 10) * 10 : TargetLevel * 5);
                            if (ReqValue > skillValue)
                                return SpellCastResult.LowCastlevel;

                            break;
                        }
                    case SpellEffectName.OpenLock:
                        {
                            if (effect.TargetA.GetTarget() != Targets.GameobjectTarget &&
                                effect.TargetA.GetTarget() != Targets.GameobjectItemTarget)
                                break;

                            if (!m_caster.IsTypeId(TypeId.Player)  // only players can open locks, gather etc.
                                                      // we need a go target in case of TARGET_GAMEOBJECT_TARGET
                                || (effect.TargetA.GetTarget() == Targets.GameobjectTarget && m_targets.GetGOTarget() == null))
                                return SpellCastResult.BadTargets;

                            Item pTempItem = null;
                            if (Convert.ToBoolean(m_targets.GetTargetMask() & SpellCastTargetFlags.TradeItem))
                            {
                                TradeData pTrade = m_caster.ToPlayer().GetTradeData();
                                if (pTrade != null)
                                    pTempItem = pTrade.GetTraderData().GetItem((TradeSlots)m_targets.GetItemTargetGUID().GetLowValue());
                            }
                            else if (Convert.ToBoolean(m_targets.GetTargetMask() & SpellCastTargetFlags.Item))
                                pTempItem = m_caster.ToPlayer().GetItemByGuid(m_targets.GetItemTargetGUID());

                            // we need a go target, or an openable item target in case of TARGET_GAMEOBJECT_ITEM_TARGET
                            if (effect.TargetA.GetTarget() == Targets.GameobjectItemTarget &&
                                m_targets.GetGOTarget() == null && (pTempItem == null || pTempItem.GetTemplate().GetLockID() == 0 || !pTempItem.IsLocked()))
                                return SpellCastResult.BadTargets;

                            if (m_spellInfo.Id != 1842 || (m_targets.GetGOTarget() != null &&
                                m_targets.GetGOTarget().GetGoInfo().type != GameObjectTypes.Trap))
                                if (m_caster.ToPlayer().InBattleground() && // In Battlegroundplayers can use only flags and banners
                                    !m_caster.ToPlayer().CanUseBattlegroundObject(m_targets.GetGOTarget()))
                                    return SpellCastResult.TryAgain;

                            // get the lock entry
                            uint lockId = 0;
                            GameObject go = m_targets.GetGOTarget();
                            Item itm = m_targets.GetItemTarget();
                            if (go != null)
                            {
                                lockId = go.GetGoInfo().GetLockId();
                                if (lockId == 0)
                                    return SpellCastResult.BadTargets;
                            }
                            else if (itm != null)
                                lockId = itm.GetTemplate().GetLockID();

                            SkillType skillId = SkillType.None;
                            int reqSkillValue = 0;
                            int skillValue = 0;

                            // check lock compatibility
                            SpellCastResult res = CanOpenLock(effect.EffectIndex, lockId, skillId, ref reqSkillValue, ref skillValue);
                            if (res != SpellCastResult.SpellCastOk)
                                return res;
                            break;
                        }
                    case SpellEffectName.ResurrectPet:
                        {
                            Creature pet = m_caster.GetGuardianPet();

                            if (pet != null && pet.IsAlive())
                                return SpellCastResult.AlreadyHaveSummon;

                            break;
                        }
                    // This is generic summon effect
                    case SpellEffectName.Summon:
                        {
                            var SummonProperties = CliDB.SummonPropertiesStorage.LookupByKey(effect.MiscValueB);
                            if (SummonProperties == null)
                                break;

                            switch (SummonProperties.Category)
                            {
                                case SummonCategory.Pet:
                                    if (!m_spellInfo.HasAttribute(SpellAttr1.DismissPet) && !m_caster.GetPetGUID().IsEmpty())
                                        return SpellCastResult.AlreadyHaveSummon;
                                    break;
                                case SummonCategory.Puppet:
                                    if (!m_caster.GetCharmGUID().IsEmpty())
                                        return SpellCastResult.AlreadyHaveCharm;
                                    break;
                            }
                            break;
                        }
                    case SpellEffectName.CreateTamedPet:
                        {
                            if (m_targets.GetUnitTarget() != null)
                            {
                                if (!m_targets.GetUnitTarget().IsTypeId(TypeId.Player))
                                    return SpellCastResult.BadTargets;
                                if (!m_spellInfo.HasAttribute(SpellAttr1.DismissPet) && !m_targets.GetUnitTarget().GetPetGUID().IsEmpty())
                                    return SpellCastResult.AlreadyHaveSummon;
                            }
                            break;
                        }
                    case SpellEffectName.SummonPet:
                        {
                            if (!m_caster.GetPetGUID().IsEmpty())                  //let warlock do a replacement summon
                            {
                                if (m_caster.IsTypeId(TypeId.Player))
                                {
                                    if (strict)                         //starting cast, trigger pet stun (cast by pet so it doesn't attack player)
                                    {
                                        Pet pet = m_caster.ToPlayer().GetPet();
                                        if (pet != null)
                                            pet.CastSpell(pet, 32752, true, null, null, pet.GetGUID());
                                    }
                                }
                                else if (!m_spellInfo.HasAttribute(SpellAttr1.DismissPet))
                                    return SpellCastResult.AlreadyHaveSummon;
                            }

                            if (!m_caster.GetCharmGUID().IsEmpty())
                                return SpellCastResult.AlreadyHaveCharm;
                            break;
                        }
                    case SpellEffectName.SummonPlayer:
                        {
                            if (!m_caster.IsTypeId(TypeId.Player))
                                return SpellCastResult.BadTargets;
                            if (m_caster.ToPlayer().GetTarget().IsEmpty())
                                return SpellCastResult.BadTargets;

                            Player target = Global.ObjAccessor.FindPlayer(m_caster.ToPlayer().GetTarget());
                            if (target == null || m_caster.ToPlayer() == target || (!target.IsInSameRaidWith(m_caster.ToPlayer()) && m_spellInfo.Id != 48955)) // refer-a-friend spell
                                return SpellCastResult.BadTargets;

                            if (target.HasSummonPending())
                                return SpellCastResult.SummonPending;

                            // check if our map is dungeon
                            MapRecord map = CliDB.MapStorage.LookupByKey(m_caster.GetMapId());
                            if (map.IsDungeon())
                            {
                                uint mapId = m_caster.GetMap().GetId();
                                Difficulty difficulty = m_caster.GetMap().GetDifficultyID();
                                if (map.IsRaid())
                                {
                                    InstanceBind targetBind = target.GetBoundInstance(mapId, difficulty);
                                    if (targetBind != null)
                                    {
                                        InstanceBind casterBind = m_caster.ToPlayer().GetBoundInstance(mapId, difficulty);
                                        if (casterBind != null)
                                            if (targetBind.perm && targetBind.save != casterBind.save)
                                                return SpellCastResult.TargetLockedToRaidInstance;
                                    }
                                }
                                InstanceTemplate instance = Global.ObjectMgr.GetInstanceTemplate(mapId);
                                if (instance == null)
                                    return SpellCastResult.TargetNotInInstance;
                                if (!target.Satisfy(Global.ObjectMgr.GetAccessRequirement(mapId, difficulty), mapId))
                                    return SpellCastResult.BadTargets;
                            }
                            break;
                        }
                    // RETURN HERE
                    case SpellEffectName.SummonRafFriend:
                        {
                            if (!m_caster.IsTypeId(TypeId.Player))
                                return SpellCastResult.BadTargets;

                            Player playerCaster = m_caster.ToPlayer();
                            //
                            if (playerCaster.GetTarget().IsEmpty())
                                return SpellCastResult.BadTargets;

                            Player target1 = Global.ObjAccessor.FindPlayer(playerCaster.GetTarget());

                            if (target1 == null ||
                                !(target1.GetSession().GetRecruiterId() == playerCaster.GetSession().GetAccountId() || target1.GetSession().GetAccountId() == playerCaster.GetSession().GetRecruiterId()))
                                return SpellCastResult.BadTargets;

                            break;
                        }
                    case SpellEffectName.Leap:
                    case SpellEffectName.TeleportUnitsFaceCaster:
                        {
                            //Do not allow to cast it before BG starts.
                            if (m_caster.IsTypeId(TypeId.Player))
                            {
                                Battleground bg = m_caster.ToPlayer().GetBattleground();
                                if (bg)
                                    if (bg.GetStatus() != BattlegroundStatus.InProgress)
                                        return SpellCastResult.TryAgain;
                            }
                            break;
                        }
                    case SpellEffectName.StealBeneficialBuff:
                        {
                            if (m_targets.GetUnitTarget() == m_caster)
                                return SpellCastResult.BadTargets;
                            break;
                        }
                    case SpellEffectName.LeapBack:
                        {
                            if (m_caster.HasUnitState(UnitState.Root))
                            {
                                if (m_caster.IsTypeId(TypeId.Player))
                                    return SpellCastResult.Rooted;
                                else
                                    return SpellCastResult.DontReport;
                            }
                            break;
                        }
                    case SpellEffectName.TalentSpecSelect:
                        {
                            ChrSpecializationRecord spec = CliDB.ChrSpecializationStorage.LookupByKey(m_misc.SpecializationId);
                            Player playerCaster = m_caster.ToPlayer();
                            if (!playerCaster)
                                return SpellCastResult.TargetNotPlayer;

                            if (spec == null || (spec.ClassID != (uint)m_caster.GetClass() && !spec.IsPetSpecialization()))
                                return SpellCastResult.NoSpec;

                            if (spec.IsPetSpecialization())
                            {
                                Pet pet = player.GetPet();
                                if (!pet || pet.getPetType() != PetType.Hunter || pet.GetCharmInfo() == null)
                                    return SpellCastResult.NoPet;
                            }

                            // can't change during already started arena/Battleground
                            Battleground bg = player.GetBattleground();
                            if (bg)
                                if (bg.GetStatus() == BattlegroundStatus.InProgress)
                                    return SpellCastResult.NotInBattleground;
                            break;
                        }
                    case SpellEffectName.RemoveTalent:
                        {
                            if (!m_caster.IsTypeId(TypeId.Player))
                                return SpellCastResult.BadTargets;

                            TalentRecord talent = CliDB.TalentStorage.LookupByKey(m_misc.TalentId);
                            if (talent == null)
                                return SpellCastResult.DontReport;
                            if (m_caster.GetSpellHistory().HasCooldown(talent.SpellID))
                                return SpellCastResult.CantUntalent;
                            break;
                        }
                    case SpellEffectName.GiveArtifactPower:
                    case SpellEffectName.GiveArtifactPowerNoBonus:
                        {
                            if (!m_caster.IsTypeId(TypeId.Player))
                                return SpellCastResult.BadTargets;

                            Aura artifactAura = m_caster.GetAura(PlayerConst.ArtifactsAllWeaponsGeneralWeaponEquippedPassive);
                            if (artifactAura == null)
                                return SpellCastResult.NoArtifactEquipped;
                            Item artifact = m_caster.ToPlayer().GetItemByGuid(artifactAura.GetCastItemGUID());
                            if (artifact == null)
                                return SpellCastResult.NoArtifactEquipped;
                            if (effect.Effect == SpellEffectName.GiveArtifactPower)
                            {
                                ArtifactRecord artifactEntry = CliDB.ArtifactStorage.LookupByKey(artifact.GetTemplate().GetArtifactID());
                                if (artifactEntry == null || artifactEntry.ArtifactCategoryID != effect.MiscValue)
                                    return SpellCastResult.WrongArtifactEquipped;
                            }
                            break;
                        }
                    default:
                        break;
                }
            }

            foreach (SpellEffectInfo effect in GetEffects())
            {
                if (effect == null)
                    continue;

                switch (effect.ApplyAuraName)
                {
                    case AuraType.ModPossessPet:
                        {
                            if (!m_caster.IsTypeId(TypeId.Player))
                                return SpellCastResult.NoPet;

                            Pet pet = m_caster.ToPlayer().GetPet();
                            if (pet == null)
                                return SpellCastResult.NoPet;

                            if (!pet.GetCharmerGUID().IsEmpty())
                                return SpellCastResult.Charmed;
                            break;
                        }
                    case AuraType.ModPossess:
                    case AuraType.ModCharm:
                    case AuraType.AoeCharm:
                        {
                            if (!m_caster.GetCharmerGUID().IsEmpty())
                                return SpellCastResult.Charmed;

                            if (effect.ApplyAuraName == AuraType.ModCharm
                                || effect.ApplyAuraName == AuraType.ModPossess)
                            {
                                if (!m_spellInfo.HasAttribute(SpellAttr1.DismissPet) && !m_caster.GetPetGUID().IsEmpty())
                                    return SpellCastResult.AlreadyHaveSummon;

                                if (!m_caster.GetCharmGUID().IsEmpty())
                                    return SpellCastResult.AlreadyHaveCharm;
                            }

                            Unit target1 = m_targets.GetUnitTarget();
                            if (target1 != null)
                            {
                                if (target1.IsTypeId(TypeId.Unit) && target1.ToCreature().IsVehicle())
                                    return SpellCastResult.BadImplicitTargets;

                                if (target1.IsMounted())
                                    return SpellCastResult.CantBeCharmed;

                                if (!target1.GetCharmerGUID().IsEmpty())
                                    return SpellCastResult.Charmed;

                                if (target1.GetOwner() != null && target1.GetOwner().IsTypeId(TypeId.Player))
                                    return SpellCastResult.TargetIsPlayerControlled;

                                int damage = CalculateDamage(effect.EffectIndex, target1);
                                if (damage != 0 && target1.getLevel() > damage)
                                    return SpellCastResult.Highlevel;
                            }

                            break;
                        }
                    case AuraType.Mounted:
                        {
                            if (m_caster.IsInWater() && m_spellInfo.HasAura(m_caster.GetMap().GetDifficultyID(), AuraType.ModIncreaseMountedFlightSpeed))
                                return SpellCastResult.OnlyAbovewater;

                            // Ignore map check if spell have AreaId. AreaId already checked and this prevent special mount spells
                            bool allowMount = !m_caster.GetMap().IsDungeon() || m_caster.GetMap().IsBattlegroundOrArena();
                            InstanceTemplate it = Global.ObjectMgr.GetInstanceTemplate(m_caster.GetMapId());
                            if (it != null)
                                allowMount = it.AllowMount;
                            if (m_caster.IsTypeId(TypeId.Player) && !allowMount && m_spellInfo.RequiredAreasID == 0)
                                return SpellCastResult.NoMountsAllowed;

                            if (m_caster.IsInDisallowedMountForm())
                                return SpellCastResult.NotShapeshift;

                            break;
                        }
                    case AuraType.RangedAttackPowerAttackerBonus:
                        {
                            if (m_targets.GetUnitTarget() == null)
                                return SpellCastResult.BadImplicitTargets;

                            // can be casted at non-friendly unit or own pet/charm
                            if (m_caster.IsFriendlyTo(m_targets.GetUnitTarget()))
                                return SpellCastResult.TargetFriendly;

                            break;
                        }
                    case AuraType.Fly:
                    case AuraType.ModIncreaseFlightSpeed:
                        {
                            // not allow cast fly spells if not have req. skills  (all spells is self target)
                            // allow always ghost flight spells
                            if (m_originalCaster != null && m_originalCaster.IsTypeId(TypeId.Player) && m_originalCaster.IsAlive())
                            {
                                BattleField Bf = Global.BattleFieldMgr.GetBattlefieldToZoneId(m_originalCaster.GetZoneId());
                                var area = CliDB.AreaTableStorage.LookupByKey(m_originalCaster.GetAreaId());
                                if (area != null)
                                    if (area.Flags[0].HasAnyFlag(AreaFlags.NoFlyZone) || (Bf != null && !Bf.CanFlyIn()))
                                        return SpellCastResult.NotHere;
                            }
                            break;
                        }
                    case AuraType.PeriodicManaLeech:
                        {
                            if (effect.IsTargetingArea())
                                break;

                            if (m_targets.GetUnitTarget() == null)
                                return SpellCastResult.BadImplicitTargets;

                            if (!m_caster.IsTypeId(TypeId.Player) || m_CastItem != null)
                                break;

                            if (m_targets.GetUnitTarget().getPowerType() != PowerType.Mana)
                                return SpellCastResult.BadTargets;

                            break;
                        }
                    default:
                        break;
                }
            }

            // check trade slot case (last, for allow catch any another cast problems)
            if (Convert.ToBoolean(m_targets.GetTargetMask() & SpellCastTargetFlags.TradeItem))
            {
                if (m_CastItem != null)
                    return SpellCastResult.ItemEnchantTradeWindow;

                if (!m_caster.IsTypeId(TypeId.Player))
                    return SpellCastResult.NotTrading;

                TradeData my_trade = m_caster.ToPlayer().GetTradeData();

                if (my_trade == null)
                    return SpellCastResult.NotTrading;

                TradeSlots slot = (TradeSlots)m_targets.GetItemTargetGUID().GetLowValue();
                if (slot != TradeSlots.NonTraded)
                    return SpellCastResult.BadTargets;

                if (!IsTriggered())
                    if (my_trade.GetSpell() != 0)
                        return SpellCastResult.ItemAlreadyEnchanted;
            }

            // check if caster has at least 1 combo point for spells that require combo points
            if (m_needComboPoints)
            {
                Player plrCaster = m_caster.ToPlayer();
                if (plrCaster != null)
                    if (plrCaster.GetComboPoints() == 0)
                        return SpellCastResult.NoComboPoints;
            }

            // all ok
            return SpellCastResult.SpellCastOk;
        }

        public SpellCastResult CheckPetCast(Unit target)
        {
            if (m_caster.HasUnitState(UnitState.Casting) && !Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCastInProgress))              //prevent spellcast interruption by another spellcast
                return SpellCastResult.SpellInProgress;

            // dead owner (pets still alive when owners ressed?)
            Unit owner = m_caster.GetCharmerOrOwner();
            if (owner != null)
                if (!owner.IsAlive())
                    return SpellCastResult.CasterDead;

            if (target == null && m_targets.GetUnitTarget() != null)
                target = m_targets.GetUnitTarget();

            if (m_spellInfo.NeedsExplicitUnitTarget())
            {
                if (target == null)
                    return SpellCastResult.BadImplicitTargets;
                m_targets.SetUnitTarget(target);
            }

            // cooldown
            Creature creatureCaster = m_caster.ToCreature();
            if (creatureCaster)
                if (creatureCaster.GetSpellHistory().HasCooldown(m_spellInfo.Id))
                    return SpellCastResult.NotReady;

            // Check if spell is affected by GCD
            if (m_spellInfo.StartRecoveryCategory > 0)
                if (m_caster.GetCharmInfo() != null && m_caster.GetSpellHistory().HasGlobalCooldown(m_spellInfo))
                    return SpellCastResult.NotReady;

            return CheckCast(true);
        }

        SpellCastResult CheckCasterAuras()
        {
            // spells totally immuned to caster auras (wsg flag drop, give marks etc)
            if (m_spellInfo.HasAttribute(SpellAttr6.IgnoreCasterAuras))
                return SpellCastResult.SpellCastOk;

            int school_immune = 0;
            uint mechanic_immune = 0;
            uint dispel_immune = 0;

            // Check if the spell grants school or mechanic immunity.
            // We use bitmasks so the loop is done only once and not on every aura check below.
            if (m_spellInfo.HasAttribute(SpellAttr1.DispelAurasOnImmunity))
            {
                foreach (SpellEffectInfo effect in GetEffects())
                {
                    if (effect == null)
                        continue;

                    if (effect.ApplyAuraName == AuraType.SchoolImmunity)
                        school_immune |= effect.MiscValue;
                    else if (effect.ApplyAuraName == AuraType.MechanicImmunity)
                        mechanic_immune |= (uint)(1 << effect.MiscValue);
                    else if (effect.ApplyAuraName == AuraType.DispelImmunity)
                        dispel_immune |= SpellInfo.GetDispelMask((DispelType)effect.MiscValue);
                }
                // immune movement impairment and loss of control
                if (m_spellInfo.Id == 42292 || m_spellInfo.Id == 59752 || m_spellInfo.Id == 19574 || m_spellInfo.Id == 53490)
                    mechanic_immune = (uint)Mechanics.ImmuneToMovementImpairmentAndLossControlMask;
            }

            bool usableInStun = m_spellInfo.HasAttribute(SpellAttr5.UsableWhileStunned);

            // Glyph of Pain Suppression
            // Allow Pain Suppression and Guardian Spirit to be cast while stunned
            // there is no other way to handle it
            if ((m_spellInfo.Id == 33206 || m_spellInfo.Id == 47788) && !m_caster.HasAura(63248))
                usableInStun = false;

            // Check whether the cast should be prevented by any state you might have.
            SpellCastResult prevented_reason = SpellCastResult.SpellCastOk;
            // Have to check if there is a stun aura. Otherwise will have problems with ghost aura apply while logging out
            UnitFlags unitflag = (UnitFlags)m_caster.GetUInt32Value(UnitFields.Flags);     // Get unit state
            if (Convert.ToBoolean(unitflag & UnitFlags.Stunned))
            {
                // spell is usable while stunned, check if caster has allowed stun auras, another stun types must prevent cast spell
                if (usableInStun)
                {
                    uint allowedStunMask =
                        1 << (int)Mechanics.Stun
                        | 1 << (int)Mechanics.Freeze
                        | 1 << (int)Mechanics.Sapped
                        | 1 << (int)Mechanics.Sleep;

                    bool foundNotStun = false;
                    var stunAuras = m_caster.GetAuraEffectsByType(AuraType.ModStun);
                    foreach (var auraEffect in stunAuras)
                    {
                        uint mechanicMask = auraEffect.GetSpellInfo().GetAllEffectsMechanicMask();
                        if (mechanicMask != 0 && !Convert.ToBoolean(mechanicMask & allowedStunMask))
                        {
                            foundNotStun = true;
                            break;
                        }
                    }
                    if (foundNotStun)
                        prevented_reason = SpellCastResult.Stunned;
                }
                else
                    prevented_reason = SpellCastResult.Stunned;
            }
            else if (unitflag.HasAnyFlag(UnitFlags.Confused) && !m_spellInfo.HasAttribute(SpellAttr5.UsableWhileConfused))
                prevented_reason = SpellCastResult.Confused;
            else if (unitflag.HasAnyFlag(UnitFlags.Fleeing) && !m_spellInfo.HasAttribute(SpellAttr5.UsableWhileFeared))
                prevented_reason = SpellCastResult.Fleeing;
            else if (unitflag.HasAnyFlag(UnitFlags.Silenced) && m_spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.Silence))
                prevented_reason = SpellCastResult.Silenced;
            else if (unitflag.HasAnyFlag(UnitFlags.Pacified) && m_spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.Pacify))
                prevented_reason = SpellCastResult.Pacified;
            else if (m_caster.HasFlag(UnitFields.Flags2, UnitFlags2.NoActions) && m_spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.NoActions))
                prevented_reason = SpellCastResult.NoActions;

            // Attr must make flag drop spell totally immune from all effects
            if (prevented_reason != SpellCastResult.SpellCastOk)
            {
                if (school_immune != 0 || mechanic_immune != 0 || dispel_immune != 0)
                {
                    //Checking auras is needed now, because you are prevented by some state but the spell grants immunity.
                    foreach (var pair in m_caster.GetAppliedAuras())
                    {
                        Aura aura = pair.Value.GetBase();
                        SpellInfo auraInfo = aura.GetSpellInfo();
                        if (Convert.ToBoolean(auraInfo.GetAllEffectsMechanicMask() & mechanic_immune))
                            continue;
                        if (Convert.ToBoolean((int)auraInfo.GetSchoolMask() & school_immune) && !auraInfo.HasAttribute(SpellAttr1.UnaffectedBySchoolImmune))
                            continue;
                        if (Convert.ToBoolean((int)auraInfo.GetDispelMask() & dispel_immune))
                            continue;

                        //Make a second check for spell failed so the right SPELL_FAILED message is returned.
                        //That is needed when your casting is prevented by multiple states and you are only immune to some of them.
                        foreach (SpellEffectInfo effect in GetEffects())
                        {
                            if (effect == null)
                                continue;

                            AuraEffect part = aura.GetEffect(effect.EffectIndex);
                            if (part != null)
                            {
                                switch (part.GetAuraType())
                                {
                                    case AuraType.ModStun:
                                        if (!usableInStun || !Convert.ToBoolean(auraInfo.GetAllEffectsMechanicMask() & (1 << (int)Mechanics.Stun)))
                                            return SpellCastResult.Stunned;
                                        break;
                                    case AuraType.ModConfuse:
                                        if (!m_spellInfo.HasAttribute(SpellAttr5.UsableWhileConfused))
                                            return SpellCastResult.Confused;
                                        break;
                                    case AuraType.ModFear:
                                        if (!m_spellInfo.HasAttribute(SpellAttr5.UsableWhileFeared))
                                            return SpellCastResult.Fleeing;
                                        break;
                                    case AuraType.ModSilence:
                                    case AuraType.ModPacify:
                                    case AuraType.ModPacifySilence:
                                        if (m_spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.Pacify))
                                            return SpellCastResult.Pacified;
                                        else if (m_spellInfo.PreventionType.HasAnyFlag(SpellPreventionType.Silence))
                                            return SpellCastResult.Silenced;
                                        break;
                                    default:
                                        break;
                                }
                            }

                        }
                    }
                }
                // You are prevented from casting and the spell casted does not grant immunity. Return a failed error.
                else
                    return prevented_reason;
            }
            return SpellCastResult.SpellCastOk;
        }

        SpellCastResult CheckArenaAndRatedBattlegroundCastRules()
        {
            bool isRatedBattleground = false; // NYI
            bool isArena = !isRatedBattleground;

            // check USABLE attributes
            // USABLE takes precedence over NOT_USABLE
            if (isRatedBattleground && m_spellInfo.HasAttribute(SpellAttr9.UsableInRatedBattlegrounds))
                return SpellCastResult.SpellCastOk;

            if (isArena && m_spellInfo.HasAttribute(SpellAttr4.UsableInArena))
                return SpellCastResult.SpellCastOk;

            // check NOT_USABLE attributes
            if (m_spellInfo.HasAttribute(SpellAttr4.NotUsableInArenaOrRatedBg))
                return isArena ? SpellCastResult.NotInArena : SpellCastResult.NotInBattleground;

            if (isArena && m_spellInfo.HasAttribute(SpellAttr9.NotUsableInArena))
                return SpellCastResult.NotInArena;

            // check cooldowns
            uint spellCooldown = m_spellInfo.GetRecoveryTime();
            if (isArena && spellCooldown > 10 * Time.Minute * Time.InMilliseconds) // not sure if still needed
                return SpellCastResult.NotInArena;

            if (isRatedBattleground && spellCooldown > 15 * Time.Minute * Time.InMilliseconds)
                return SpellCastResult.NotInBattleground;

            return SpellCastResult.SpellCastOk;
        }

        public bool CanAutoCast(Unit target)
        {
            if (!target)
                return (CheckPetCast(target) == SpellCastResult.SpellCastOk);

            ObjectGuid targetguid = target.GetGUID();

            // check if target already has the same or a more powerful aura
            foreach (SpellEffectInfo effect in GetEffects())
            {
                if (effect == null)
                    continue;

                if (!effect.IsAura())
                    continue;

                AuraType auraType = effect.ApplyAuraName;
                var auras = target.GetAuraEffectsByType(auraType);
                foreach (var eff in auras)
                {
                    if (GetSpellInfo().Id == eff.GetSpellInfo().Id)
                        return false;

                    switch (Global.SpellMgr.CheckSpellGroupStackRules(GetSpellInfo(), eff.GetSpellInfo()))
                    {
                        case SpellGroupStackRule.Exclusive:
                            return false;
                        case SpellGroupStackRule.ExclusiveFromSameCaster:
                            if (GetCaster() == eff.GetCaster())
                                return false;
                            break;
                        case SpellGroupStackRule.ExclusiveSameEffect: // this one has further checks, but i don't think they're necessary for autocast logic
                        case SpellGroupStackRule.ExclusiveHighest:
                            if (Math.Abs(effect.BasePoints) <= Math.Abs(eff.GetAmount()))
                                return false;
                            break;
                        case SpellGroupStackRule.Default:
                        default:
                            break;
                    }
                }
            }

            SpellCastResult result = CheckPetCast(target);

            if (result == SpellCastResult.SpellCastOk || result == SpellCastResult.UnitNotInfront)
            {
                // do not check targets for ground-targeted spells (we target them on top of the intended target anyway)
                if (GetSpellInfo().ExplicitTargetMask.HasAnyFlag((uint)SpellCastTargetFlags.DestLocation))
                    return true;
                SelectSpellTargets();
                //check if among target units, our WANTED target is as well (.only self cast spells return false)
                foreach (var ihit in m_UniqueTargetInfo)
                    if (ihit.targetGUID == targetguid)
                        return true;
            }
            // either the cast failed or the intended target wouldn't be hit
            return false;
        }

        SpellCastResult CheckRange(bool strict)
        {
            // Don't check for instant cast spells
            if (!strict && m_casttime == 0)
                return SpellCastResult.SpellCastOk;

            var pair = GetMinMaxRange(strict);
            float minRange = pair.Item1;
            float maxRange = pair.Item2;

            // get square values for sqr distance checks
            minRange *= minRange;
            maxRange *= maxRange;

            Unit target = m_targets.GetUnitTarget();
            if (target && target != m_caster)
            {
                if (m_caster.GetExactDistSq(target) > maxRange)
                    return SpellCastResult.OutOfRange;

                if (minRange > 0.0f && m_caster.GetExactDistSq(target) < minRange)
                    return SpellCastResult.OutOfRange;

                if (m_caster.IsTypeId(TypeId.Player) &&
                    ((m_spellInfo.FacingCasterFlags.HasAnyFlag(1u) && !m_caster.HasInArc((float)Math.PI, target))
                        && !m_caster.IsWithinBoundaryRadius(target)))
                    return SpellCastResult.UnitNotInfront;
            }

            if (m_targets.HasDst() && !m_targets.HasTraj())
            {
                if (m_caster.GetExactDistSq(m_targets.GetDstPos()) > maxRange)
                    return SpellCastResult.OutOfRange;
                if (minRange > 0.0f && m_caster.GetExactDistSq(m_targets.GetDstPos()) < minRange)
                    return SpellCastResult.OutOfRange;
            }

            return SpellCastResult.SpellCastOk;
        }

        Tuple<float, float> GetMinMaxRange(bool strict)
        {
            float rangeMod = 0.0f;
            float minRange = 0.0f;
            float maxRange = 0.0f;

            if (strict && IsNextMeleeSwingSpell())
            {
                maxRange = 100.0f;
                return Tuple.Create(minRange, maxRange);
            }

            if (m_spellInfo.RangeEntry != null)
            {
                Unit target = m_targets.GetUnitTarget();
                if (m_spellInfo.RangeEntry.Flags.HasAnyFlag(SpellRangeFlag.Melee))
                {
                    rangeMod = m_caster.GetMeleeRange(target ? target : m_caster); // when the target is not a unit, take the caster's combat reach as the target's combat reach.
                }
                else
                {
                    float meleeRange = 0.0f;
                    if (m_spellInfo.RangeEntry.Flags.HasAnyFlag(SpellRangeFlag.Ranged))
                        meleeRange = m_caster.GetMeleeRange(target ? target : m_caster); // when the target is not a unit, take the caster's combat reach as the target's combat reach.

                    minRange = m_caster.GetSpellMinRangeForTarget(target, m_spellInfo) + meleeRange;
                    maxRange = m_caster.GetSpellMaxRangeForTarget(target, m_spellInfo);

                    if (target || m_targets.GetCorpseTarget())
                    {
                        rangeMod = m_caster.GetCombatReach() + (target ? target.GetCombatReach() : m_caster.GetCombatReach());

                        if (minRange > 0.0f && !m_spellInfo.RangeEntry.Flags.HasAnyFlag(SpellRangeFlag.Ranged))
                            minRange += rangeMod;
                    }
                }

                if (target && m_caster.isMoving() && target.isMoving() && !m_caster.IsWalking() && !target.IsWalking() &&
                    (m_spellInfo.RangeEntry.Flags.HasAnyFlag(SpellRangeFlag.Melee) || target.IsTypeId(TypeId.Player)))
                    rangeMod += 8.0f / 3.0f;
            }

            if (m_spellInfo.HasAttribute(SpellAttr0.ReqAmmo) && m_caster.IsTypeId(TypeId.Player))
            {
                Item ranged = m_caster.ToPlayer().GetWeaponForAttack(WeaponAttackType.RangedAttack, true);
                if (ranged)
                    maxRange *= ranged.GetTemplate().GetRangedModRange() * 0.01f;
            }

            Player modOwner = m_caster.GetSpellModOwner();
            if (modOwner)
                modOwner.ApplySpellMod(m_spellInfo.Id, SpellModOp.Range, ref maxRange, this);

            maxRange += rangeMod;

            return Tuple.Create(minRange, maxRange);
        }

        SpellCastResult CheckPower()
        {
            // item cast not used power
            if (m_CastItem != null)
                return SpellCastResult.SpellCastOk;

            foreach (SpellPowerCost cost in m_powerCost)
            {
                // health as power used - need check health amount
                if (cost.Power == PowerType.Health)
                {
                    if (m_caster.GetHealth() <= (uint)cost.Amount)
                        return SpellCastResult.CasterAurastate;
                    continue;
                }
                // Check valid power type
                if (cost.Power >= PowerType.Max)
                {
                    Log.outError(LogFilter.Spells, "Spell.CheckPower: Unknown power type '{0}'", cost.Power);
                    return SpellCastResult.Unknown;
                }

                //check rune cost only if a spell has PowerType == POWER_RUNES
                if (cost.Power == PowerType.Runes)
                {
                    SpellCastResult failReason = CheckRuneCost();
                    if (failReason != SpellCastResult.SpellCastOk)
                        return failReason;
                }

                // Check power amount
                if (m_caster.GetPower(cost.Power) < cost.Amount)
                    return SpellCastResult.NoPower;
            }

            return SpellCastResult.SpellCastOk;
        }

        SpellCastResult CheckItems()
        {
            Player player = m_caster.ToPlayer();

            if (!player)
                return SpellCastResult.SpellCastOk;

            if (m_CastItem == null)
            {
                if (!m_castItemGUID.IsEmpty())
                    return SpellCastResult.ItemNotReady;
            }
            else
            {
                uint itemid = m_CastItem.GetEntry();
                if (!player.HasItemCount(itemid))
                    return SpellCastResult.ItemNotReady;

                ItemTemplate proto = m_CastItem.GetTemplate();
                if (proto == null)
                    return SpellCastResult.ItemNotReady;

                for (int i = 0; i < proto.Effects.Count && i < 5; ++i)
                    if (proto.Effects[i].Charges > 0)
                        if (m_CastItem.GetSpellCharges(i) == 0)
                            return SpellCastResult.NoChargesRemain;

                // consumable cast item checks
                if (proto.GetClass() == ItemClass.Consumable && m_targets.GetUnitTarget() != null)
                {
                    // such items should only fail if there is no suitable effect at all - see Rejuvenation Potions for example
                    SpellCastResult failReason = SpellCastResult.SpellCastOk;
                    foreach (SpellEffectInfo effect in GetEffects())
                    {
                        // skip check, pet not required like checks, and for TARGET_UNIT_PET m_targets.GetUnitTarget() is not the real target but the caster
                        if (effect == null || effect.TargetA.GetTarget() == Targets.UnitPet)
                            continue;

                        if (effect.Effect == SpellEffectName.Heal)
                        {
                            if (m_targets.GetUnitTarget().IsFullHealth())
                            {
                                failReason = SpellCastResult.AlreadyAtFullHealth;
                                continue;
                            }
                            else
                            {
                                failReason = SpellCastResult.SpellCastOk;
                                break;
                            }
                        }

                        // Mana Potion, Rage Potion, Thistle Tea(Rogue), ...
                        if (effect.Effect == SpellEffectName.Energize)
                        {
                            if (effect.MiscValue < 0 || effect.MiscValue >= (int)PowerType.Max)
                            {
                                failReason = SpellCastResult.AlreadyAtFullPower;
                                continue;
                            }

                            PowerType power = (PowerType)effect.MiscValue;
                            if (m_targets.GetUnitTarget().GetPower(power) == m_targets.GetUnitTarget().GetMaxPower(power))
                            {
                                failReason = SpellCastResult.AlreadyAtFullPower;
                                continue;
                            }
                            else
                            {
                                failReason = SpellCastResult.SpellCastOk;
                                break;
                            }
                        }
                    }
                    if (failReason != SpellCastResult.SpellCastOk)
                        return failReason;
                }
            }

            // check target item
            if (!m_targets.GetItemTargetGUID().IsEmpty())
            {
                if (!m_caster.IsTypeId(TypeId.Player))
                    return SpellCastResult.BadTargets;

                if (m_targets.GetItemTarget() == null)
                    return SpellCastResult.ItemGone;

                if (!m_targets.GetItemTarget().IsFitToSpellRequirements(m_spellInfo))
                    return SpellCastResult.EquippedItemClass;
            }
            // if not item target then required item must be equipped
            else
            {
                if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreEquippedItemRequirement))
                    if (m_caster.IsTypeId(TypeId.Player) && !m_caster.ToPlayer().HasItemFitToSpellRequirements(m_spellInfo))
                        return SpellCastResult.EquippedItemClass;
            }

            // do not take reagents for these item casts
            if (!(m_CastItem != null && Convert.ToBoolean(m_CastItem.GetTemplate().GetFlags() & ItemFlags.NoReagentCost)))
            {
                bool checkReagents = !Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnorePowerAndReagentCost) && !player.CanNoReagentCast(m_spellInfo);
                // Not own traded item (in trader trade slot) requires reagents even if triggered spell
                if (!checkReagents)
                {
                    Item targetItem = m_targets.GetItemTarget();
                    if (targetItem != null)
                        if (targetItem.GetOwnerGUID() != m_caster.GetGUID())
                            checkReagents = true;
                }

                // check reagents (ignore triggered spells with reagents processed by original spell) and special reagent ignore case.
                if (checkReagents)
                {
                    for (byte i = 0; i < SpellConst.MaxReagents; i++)
                    {
                        if (m_spellInfo.Reagent[i] <= 0)
                            continue;

                        uint itemid = (uint)m_spellInfo.Reagent[i];
                        uint itemcount = m_spellInfo.ReagentCount[i];

                        // if CastItem is also spell reagent
                        if (m_CastItem != null && m_CastItem.GetEntry() == itemid)
                        {
                            ItemTemplate proto = m_CastItem.GetTemplate();
                            if (proto == null)
                                return SpellCastResult.ItemNotReady;
                            for (int s = 0; s < proto.Effects.Count && s < 5; ++s)
                            {
                                // CastItem will be used up and does not count as reagent
                                int charges = m_CastItem.GetSpellCharges(s);
                                if (proto.Effects[s].Charges < 0 && Math.Abs(charges) < 2)
                                {
                                    ++itemcount;
                                    break;
                                }
                            }
                        }
                        if (!player.HasItemCount(itemid, itemcount))
                            return SpellCastResult.Reagents;
                    }
                }

                // check totem-item requirements (items presence in inventory)
                uint totems = 2;
                for (int i = 0; i < 2; ++i)
                {
                    if (m_spellInfo.Totem[i] != 0)
                    {
                        if (player.HasItemCount(m_spellInfo.Totem[i]))
                        {
                            totems -= 1;
                            continue;
                        }
                    } else
                        totems -= 1;
                }
                if (totems != 0)
                    return SpellCastResult.Totems;

                // Check items for TotemCategory (items presence in inventory)
                uint totemCategory = 2;
                for (byte i = 0; i < 2; ++i)
                {
                    if (m_spellInfo.TotemCategory[i] != 0)
                    {
                        if (player.HasItemTotemCategory(m_spellInfo.TotemCategory[i]))
                        {
                            totemCategory -= 1;
                            continue;
                        }
                    }
                    else
                        totemCategory -= 1;
                }

                if (totemCategory != 0)
                    return SpellCastResult.TotemCategory;
            }

            // special checks for spell effects
            foreach (SpellEffectInfo effect in GetEffects())
            {
                if (effect == null)
                    continue;

                switch (effect.Effect)
                {
                    case SpellEffectName.CreateItem:
                    case SpellEffectName.CreateLoot:
                        {
                            if (!IsTriggered() && effect.ItemType != 0)
                            {
                                List<ItemPosCount> dest = new List<ItemPosCount>();
                                InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, effect.ItemType, 1);
                                if (msg != InventoryResult.Ok)
                                {
                                    ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(effect.ItemType);
                                    // @todo Needs review
                                    if (pProto != null && pProto.GetItemLimitCategory() == 0)
                                    {
                                        player.SendEquipError(msg, null, null, effect.ItemType);
                                        return SpellCastResult.DontReport;
                                    }
                                    else
                                    {
                                        SpellEffectInfo efi;
                                        if (!(m_spellInfo.SpellFamilyName == SpellFamilyNames.Mage && m_spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x40000000u)))
                                            return SpellCastResult.TooManyOfItem;
                                        else if (!player.HasItemCount(effect.ItemType))
                                            return SpellCastResult.TooManyOfItem;
                                        else if ((efi = GetEffect(1)) != null)
                                            player.CastSpell(m_caster, (uint)efi.CalcValue(), false);        // move this to anywhere
                                        return SpellCastResult.DontReport;
                                    }
                                }
                            }
                            break;
                        }
                    case SpellEffectName.EnchantItem:
                        if (effect.ItemType != 0 && m_targets.GetItemTarget() != null && m_targets.GetItemTarget().IsVellum())
                        {
                            // cannot enchant vellum for other player
                            if (m_targets.GetItemTarget().GetOwner() != m_caster)
                                return SpellCastResult.NotTradeable;
                            // do not allow to enchant vellum from scroll made by vellum-prevent exploit
                            if (m_CastItem != null && Convert.ToBoolean(m_CastItem.GetTemplate().GetFlags() & ItemFlags.NoReagentCost))
                                return SpellCastResult.TotemCategory;
                            List<ItemPosCount> dest = new List<ItemPosCount>();
                            InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, effect.ItemType, 1);
                            if (msg != InventoryResult.Ok)
                            {
                                player.SendEquipError(msg, null, null, effect.ItemType);
                                return SpellCastResult.DontReport;
                            }
                        }
                        goto case SpellEffectName.EnchantItemPrismatic;
                    case SpellEffectName.EnchantItemPrismatic:
                        {
                            Item targetItem = m_targets.GetItemTarget();
                            if (targetItem == null)
                                return SpellCastResult.ItemNotFound;

                            if (targetItem.GetTemplate().GetBaseItemLevel() < m_spellInfo.BaseLevel)
                                return SpellCastResult.Lowlevel;

                            bool isItemUsable = false;
                            ItemTemplate proto = targetItem.GetTemplate();
                            for (byte e = 0; e < proto.Effects.Count; ++e)
                            {
                                if (proto.Effects[e].SpellID != 0 && proto.Effects[e].Trigger == ItemSpelltriggerType.OnUse)
                                {
                                    isItemUsable = true;
                                    break;
                                }
                            }

                            var enchantEntry = CliDB.SpellItemEnchantmentStorage.LookupByKey(effect.MiscValue);
                            // do not allow adding usable enchantments to items that have use effect already
                            if (enchantEntry != null)
                            {
                                for (var s = 0; s < ItemConst.MaxItemEnchantmentEffects; ++s)
                                {
                                    switch (enchantEntry.Effect[s])
                                    {
                                        case ItemEnchantmentType.UseSpell:
                                            if (isItemUsable)
                                                return SpellCastResult.OnUseEnchant;
                                            break;
                                        case ItemEnchantmentType.PrismaticSocket:
                                            {
                                                uint numSockets = 0;
                                                for (uint socket = 0; socket < ItemConst.MaxGemSockets; ++socket)
                                                    if (targetItem.GetSocketColor(socket) != 0)
                                                        ++numSockets;

                                                if (numSockets == ItemConst.MaxGemSockets || targetItem.GetEnchantmentId(EnchantmentSlot.Prismatic) != 0)
                                                    return SpellCastResult.MaxSockets;
                                                break;
                                            }
                                    }
                                }
                            }

                            // Not allow enchant in trade slot for some enchant type
                            if (targetItem.GetOwner() != m_caster)
                            {
                                if (enchantEntry == null)
                                    return SpellCastResult.Error;
                                if (enchantEntry.Flags.HasAnyFlag(EnchantmentSlotMask.CanSouldBound))
                                    return SpellCastResult.NotTradeable;
                            }
                            break;
                        }
                    case SpellEffectName.EnchantItemTemporary:
                        {
                            Item item = m_targets.GetItemTarget();
                            if (item == null)
                                return SpellCastResult.ItemNotFound;
                            // Not allow enchant in trade slot for some enchant type
                            if (item.GetOwner() != m_caster)
                            {
                                int enchant_id = effect.MiscValue;
                                var pEnchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id);
                                if (pEnchant == null)
                                    return SpellCastResult.Error;
                                if (pEnchant.Flags.HasAnyFlag(EnchantmentSlotMask.CanSouldBound))
                                    return SpellCastResult.NotTradeable;
                            }
                            break;
                        }
                    case SpellEffectName.EnchantHeldItem:
                        // check item existence in effect code (not output errors at offhand hold item effect to main hand for example
                        break;
                    case SpellEffectName.Disenchant:
                        {
                            if (m_targets.GetItemTarget() == null)
                                return SpellCastResult.CantBeDisenchanted;

                            // prevent disenchanting in trade slot
                            if (m_targets.GetItemTarget().GetOwnerGUID() != m_caster.GetGUID())
                                return SpellCastResult.CantBeDisenchanted;

                            ItemTemplate itemProto = m_targets.GetItemTarget().GetTemplate();
                            if (itemProto == null)
                                return SpellCastResult.CantBeDisenchanted;

                            int item_quality = (int)itemProto.GetQuality();
                            // 2.0.x addon: Check player enchanting level against the item disenchanting requirements
                            uint item_disenchantskilllevel = itemProto.RequiredDisenchantSkill;
                            if (item_disenchantskilllevel == Convert.ToUInt32(-1))
                                return SpellCastResult.CantBeDisenchanted;
                            if (item_disenchantskilllevel > player.GetSkillValue(SkillType.Enchanting))
                                return SpellCastResult.LowCastlevel;
                            if (item_quality > 4 || item_quality < 2)
                                return SpellCastResult.CantBeDisenchanted;
                            if (itemProto.GetClass() != ItemClass.Weapon && itemProto.GetClass() != ItemClass.Armor)
                                return SpellCastResult.CantBeDisenchanted;
                            if (itemProto.DisenchantID == 0)
                                return SpellCastResult.CantBeDisenchanted;
                            break;
                        }
                    case SpellEffectName.Prospecting:
                        {
                            if (m_targets.GetItemTarget() == null)
                                return SpellCastResult.CantBeProspected;
                            //ensure item is a prospectable ore
                            if (!Convert.ToBoolean(m_targets.GetItemTarget().GetTemplate().GetFlags() & ItemFlags.IsProspectable))
                                return SpellCastResult.CantBeProspected;
                            //prevent prospecting in trade slot
                            if (m_targets.GetItemTarget().GetOwnerGUID() != m_caster.GetGUID())
                                return SpellCastResult.CantBeProspected;
                            //Check for enough skill in jewelcrafting
                            uint item_prospectingskilllevel = m_targets.GetItemTarget().GetTemplate().GetRequiredSkillRank();
                            if (item_prospectingskilllevel > player.GetSkillValue(SkillType.Jewelcrafting))
                                return SpellCastResult.LowCastlevel;
                            //make sure the player has the required ores in inventory
                            if (m_targets.GetItemTarget().GetCount() < 5)
                                return SpellCastResult.NeedMoreItems;

                            if (!LootStorage.Prospecting.HaveLootFor(m_targets.GetItemTargetEntry()))
                                return SpellCastResult.CantBeProspected;

                            break;
                        }
                    case SpellEffectName.Milling:
                        {
                            if (m_targets.GetItemTarget() == null)
                                return SpellCastResult.CantBeMilled;
                            //ensure item is a millable herb
                            if (!(m_targets.GetItemTarget().GetTemplate().GetFlags().HasAnyFlag(ItemFlags.IsMillable)))
                                return SpellCastResult.CantBeMilled;
                            //prevent milling in trade slot
                            if (m_targets.GetItemTarget().GetOwnerGUID() != m_caster.GetGUID())
                                return SpellCastResult.CantBeMilled;
                            //Check for enough skill in inscription
                            uint item_millingskilllevel = m_targets.GetItemTarget().GetTemplate().GetRequiredSkillRank();
                            if (item_millingskilllevel > player.GetSkillValue(SkillType.Inscription))
                                return SpellCastResult.LowCastlevel;
                            //make sure the player has the required herbs in inventory
                            if (m_targets.GetItemTarget().GetCount() < 5)
                                return SpellCastResult.NeedMoreItems;

                            if (!LootStorage.Milling.HaveLootFor(m_targets.GetItemTargetEntry()))
                                return SpellCastResult.CantBeMilled;

                            break;
                        }
                    case SpellEffectName.WeaponDamage:
                    case SpellEffectName.WeaponDamageNoschool:
                        {
                            if (!m_caster.IsTypeId(TypeId.Player))
                                return SpellCastResult.TargetNotPlayer;

                            if (m_attackType != WeaponAttackType.RangedAttack)
                                break;

                            Item pItem = m_caster.ToPlayer().GetWeaponForAttack(m_attackType);
                            if (pItem == null || pItem.IsBroken())
                                return SpellCastResult.EquippedItem;
                            break;
                        }
                    case SpellEffectName.CreateManaGem:
                        {
                            uint item_id = effect.ItemType;
                            ItemTemplate proto = Global.ObjectMgr.GetItemTemplate(item_id);

                            if (proto == null)
                                return SpellCastResult.ItemAtMaxCharges;

                            Item pitem = player.GetItemByEntry(item_id);
                            if (pitem != null)
                            {
                                for (int x = 0; x < proto.Effects.Count && x < 5; ++x)
                                    if (proto.Effects[x].Charges != 0 && pitem.GetSpellCharges(x) == proto.Effects[x].Charges)
                                        return SpellCastResult.ItemAtMaxCharges;
                            }
                            break;
                        }
                    default:
                        break;
                }
            }

            // check weapon presence in slots for main/offhand weapons
            if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreEquippedItemRequirement) && m_spellInfo.EquippedItemClass >= 0)
            {
                // main hand weapon required
                if (m_spellInfo.HasAttribute(SpellAttr3.MainHand))
                {
                    Item item = m_caster.ToPlayer().GetWeaponForAttack(WeaponAttackType.BaseAttack);

                    // skip spell if no weapon in slot or broken
                    if (item == null || item.IsBroken())
                        return SpellCastResult.EquippedItemClass;

                    // skip spell if weapon not fit to triggered spell
                    if (!item.IsFitToSpellRequirements(m_spellInfo))
                        return SpellCastResult.EquippedItemClass;
                }

                // offhand hand weapon required
                if (m_spellInfo.HasAttribute(SpellAttr3.ReqOffhand))
                {
                    Item item = m_caster.ToPlayer().GetWeaponForAttack(WeaponAttackType.OffAttack);

                    // skip spell if no weapon in slot or broken
                    if (item == null || item.IsBroken())
                        return SpellCastResult.EquippedItemClass;

                    // skip spell if weapon not fit to triggered spell
                    if (!item.IsFitToSpellRequirements(m_spellInfo))
                        return SpellCastResult.EquippedItemClass;
                }
            }

            return SpellCastResult.SpellCastOk;
        }

        public void Delayed() // only called in DealDamage()
        {
            if (m_caster == null)
                return;

            if (isDelayableNoMore())                                 // Spells may only be delayed twice
                return;

            //check pushback reduce
            int delaytime = 500;                                  // spellcasting delay is normally 500ms
            int delayReduce = 100;                                // must be initialized to 100 for percent modifiers
            m_caster.ToPlayer().ApplySpellMod(m_spellInfo.Id, SpellModOp.NotLoseCastingTime, ref delayReduce, this);
            delayReduce += m_caster.GetTotalAuraModifier(AuraType.ReducePushback) - 100;
            if (delayReduce >= 100)
                return;

            MathFunctions.AddPct(ref delaytime, -delayReduce);

            if (m_timer + delaytime > m_casttime)
            {
                delaytime = m_casttime - m_timer;
                m_timer = m_casttime;
            }
            else
                m_timer += delaytime;

            Log.outDebug(LogFilter.Spells, "Spell {0} partially interrupted for ({1}) ms at damage", m_spellInfo.Id, delaytime);

            SpellDelayed spellDelayed = new SpellDelayed();
            spellDelayed.Caster = m_caster.GetGUID();
            spellDelayed.ActualDelay = delaytime;

            m_caster.SendMessageToSet(spellDelayed, true);
        }

        public void DelayedChannel()
        {
            if (m_caster == null || !m_caster.IsTypeId(TypeId.Player) || getState() != SpellState.Casting)
                return;

            if (isDelayableNoMore())                                    // Spells may only be delayed twice
                return;

            //check pushback reduce
            int delaytime = MathFunctions.CalculatePct(m_spellInfo.GetDuration(), 25); // channeling delay is normally 25% of its time per hit
            int delayReduce = 100;                                    // must be initialized to 100 for percent modifiers
            m_caster.ToPlayer().ApplySpellMod(m_spellInfo.Id, SpellModOp.NotLoseCastingTime, ref delayReduce, this);
            delayReduce += m_caster.GetTotalAuraModifier(AuraType.ReducePushback) - 100;
            if (delayReduce >= 100)
                return;

            MathFunctions.AddPct(ref delaytime, -delayReduce);

            if (m_timer <= delaytime)
            {
                delaytime = m_timer;
                m_timer = 0;
            }
            else
                m_timer -= delaytime;

            Log.outDebug(LogFilter.Spells, "Spell {0} partially interrupted for {1} ms, new duration: {2} ms", m_spellInfo.Id, delaytime, m_timer);

            foreach (var ihit in m_UniqueTargetInfo)
                if (ihit.missCondition == SpellMissInfo.None)
                {
                    Unit unit = (m_caster.GetGUID() == ihit.targetGUID) ? m_caster : Global.ObjAccessor.GetUnit(m_caster, ihit.targetGUID);
                    if (unit != null)
                        unit.DelayOwnedAuras(m_spellInfo.Id, m_originalCasterGUID, delaytime);
                }

            // partially interrupt persistent area auras
            DynamicObject dynObj = m_caster.GetDynObject(m_spellInfo.Id);
            if (dynObj != null)
                dynObj.Delay(delaytime);

            SendChannelUpdate((uint)m_timer);
        }

        bool UpdatePointers()
        {
            if (m_originalCasterGUID == m_caster.GetGUID())
                m_originalCaster = m_caster;
            else
            {
                m_originalCaster = Global.ObjAccessor.GetUnit(m_caster, m_originalCasterGUID);
                if (m_originalCaster != null && !m_originalCaster.IsInWorld)
                    m_originalCaster = null;
            }

            if (!m_castItemGUID.IsEmpty() && m_caster.IsTypeId(TypeId.Player))
            {
                m_CastItem = m_caster.ToPlayer().GetItemByGuid(m_castItemGUID);
                m_castItemLevel = -1;
                // cast item not found, somehow the item is no longer where we expected
                if (!m_CastItem)
                    return false;

                // check if the item is really the same, in case it has been wrapped for example
                if (m_castItemEntry != m_CastItem.GetEntry())
                    return false;

                m_castItemLevel = (int)m_CastItem.GetItemLevel(m_caster.ToPlayer());
            }

            m_targets.Update(m_caster);

            // further actions done only for dest targets
            if (!m_targets.HasDst())
                return true;

            // cache last transport
            WorldObject transport = null;

            // update effect destinations (in case of moved transport dest target)
            foreach (SpellEffectInfo effect in GetEffects())
            {
                if (effect == null)
                    continue;

                SpellDestination dest = m_destTargets[effect.EffectIndex];
                if (dest.TransportGUID.IsEmpty())
                    continue;

                if (transport == null || transport.GetGUID() != dest.TransportGUID)
                    transport = Global.ObjAccessor.GetWorldObject(m_caster, dest.TransportGUID);

                if (transport != null)
                {
                    dest.Position.Relocate(transport.GetPosition());
                    dest.Position.RelocateOffset(dest.TransportOffset);
                }
            }

            return true;
        }

        public CurrentSpellTypes GetCurrentContainer()
        {
            if (IsNextMeleeSwingSpell())
                return CurrentSpellTypes.Melee;
            else if (IsAutoRepeat())
                return CurrentSpellTypes.AutoRepeat;
            else if (m_spellInfo.IsChanneled())
                return CurrentSpellTypes.Channeled;
            else
                return CurrentSpellTypes.Generic;
        }

        bool CheckEffectTarget(Unit target, SpellEffectInfo effect, Position losPosition)
        {
            if (!effect.IsEffect())
                return false;

            switch (effect.ApplyAuraName)
            {
                case AuraType.ModPossess:
                case AuraType.ModCharm:
                case AuraType.ModPossessPet:
                case AuraType.AoeCharm:
                    if (target.IsTypeId(TypeId.Unit) && target.IsVehicle())
                        return false;
                    if (target.IsMounted())
                        return false;
                    if (!target.GetCharmerGUID().IsEmpty())
                        return false;
                    int damage = CalculateDamage(effect.EffectIndex, target);
                    if (damage != 0)
                        if (target.getLevel() > damage)
                            return false;
                    break;
                default:
                    break;
            }

            // check for ignore LOS on the effect itself
            if (m_spellInfo.HasAttribute(SpellAttr2.CanTargetNotInLos) || Global.DisableMgr.IsDisabledFor(DisableType.Spell, m_spellInfo.Id, null, DisableFlags.SpellLOS))
                return true;

            // if spell is triggered, need to check for LOS disable on the aura triggering it and inherit that behaviour
            if (IsTriggered() && m_triggeredByAuraSpell != null && (m_triggeredByAuraSpell.HasAttribute(SpellAttr2.CanTargetNotInLos) || Global.DisableMgr.IsDisabledFor(DisableType.Spell, m_triggeredByAuraSpell.Id, null, DisableFlags.SpellLOS)))
                return true;

            // @todo shit below shouldn't be here, but it's temporary
            if (losPosition != null)
                return target.IsWithinLOS(losPosition.GetPositionX(), losPosition.GetPositionY(), losPosition.GetPositionZ());
            else
            {
                // Get GO cast coordinates if original caster . GO
                WorldObject caster = null;
                if (m_originalCasterGUID.IsGameObject())
                    caster = m_caster.GetMap().GetGameObject(m_originalCasterGUID);
                if (!caster)
                    caster = m_caster;
                if (target != m_caster && !target.IsWithinLOSInMap(caster))
                    return false;
            }

            return true;
        }

        bool CheckEffectTarget(GameObject target, SpellEffectInfo effect)
        {
            if (!effect.IsEffect())
                return false;

            switch (effect.Effect)
            {
                case SpellEffectName.GameObjectDamage:
                case SpellEffectName.GameobjectRepair:
                case SpellEffectName.GameobjectSetDestructionState:
                    if (target.GetGoType() != GameObjectTypes.DestructibleBuilding)
                        return false;
                    break;
                default:
                    break;
            }

            return true;
        }

        bool CheckEffectTarget(Item target, SpellEffectInfo effect)
        {
            if (!effect.IsEffect())
                return false;

            return true;
        }

        bool IsNextMeleeSwingSpell()
        {
            return m_spellInfo.HasAttribute(SpellAttr0.OnNextSwing);
        }

        bool IsAutoActionResetSpell()
        {
            // @todo changed SPELL_INTERRUPT_FLAG_AUTOATTACK . SPELL_INTERRUPT_FLAG_INTERRUPT to fix compile - is this check correct at all?
            if (IsTriggered() || !m_spellInfo.InterruptFlags.HasAnyFlag(SpellInterruptFlags.Interrupt))
                return false;

            if (m_casttime == 0 && m_spellInfo.HasAttribute(SpellAttr6.NotResetSwingIfInstant))
                return false;

            return true;
        }

        bool IsNeedSendToClient()
        {
            return m_SpellVisual != 0 || m_spellInfo.IsChanneled() ||
                m_spellInfo.HasAttribute(SpellAttr8.AuraSendAmount) || m_spellInfo.Speed > 0.0f || (m_triggeredByAuraSpell == null && !IsTriggered());
        }

        bool HaveTargetsForEffect(byte effect)
        {
            foreach (var targetInfo in m_UniqueTargetInfo)
                if (Convert.ToBoolean(targetInfo.effectMask & (1 << effect)))
                    return true;

            foreach (var targetInfo in m_UniqueGOTargetInfo)
                if (Convert.ToBoolean(targetInfo.effectMask & (1 << effect)))
                    return true;

            foreach (var targetInfo in m_UniqueItemInfo)
                if (Convert.ToBoolean(targetInfo.effectMask & (1 << effect)))
                    return true;

            return false;
        }

        bool IsValidDeadOrAliveTarget(Unit target)
        {
            if (target.IsAlive())
                return !m_spellInfo.IsRequiringDeadTarget();
            if (m_spellInfo.IsAllowingDeadTarget())
                return true;
            return false;
        }

        void HandleLaunchPhase()
        {
            // handle effects with SPELL_EFFECT_HANDLE_LAUNCH mode
            foreach (SpellEffectInfo effect in GetEffects())
            {
                // don't do anything for empty effect
                if (effect == null || !effect.IsEffect())
                    continue;

                HandleEffects(null, null, null, effect.EffectIndex, SpellEffectHandleMode.Launch);
            }

            float[] multiplier = new float[SpellConst.MaxEffects];
            foreach (SpellEffectInfo effect in GetEffects())
                if (effect != null && Convert.ToBoolean(m_applyMultiplierMask & (1 << (int)effect.EffectIndex)))
                    multiplier[effect.EffectIndex] = effect.CalcDamageMultiplier(m_originalCaster, this);

            bool usesAmmo = m_spellInfo.HasAttribute(SpellCustomAttributes.DirectDamage);

            foreach (var ihit in m_UniqueTargetInfo)
            {
                TargetInfo target = ihit;

                uint mask = target.effectMask;
                if (mask == 0)
                    continue;

                DoAllEffectOnLaunchTarget(target, multiplier);
            }
        }

        void DoAllEffectOnLaunchTarget(TargetInfo targetInfo, float[] multiplier)
        {
            Unit unit = null;
            // In case spell hit target, do all effect on that target
            if (targetInfo.missCondition == SpellMissInfo.None)
                unit = m_caster.GetGUID() == targetInfo.targetGUID ? m_caster : Global.ObjAccessor.GetUnit(m_caster, targetInfo.targetGUID);
            // In case spell reflect from target, do all effect on caster (if hit)
            else if (targetInfo.missCondition == SpellMissInfo.Reflect && targetInfo.reflectResult == SpellMissInfo.None)
                unit = m_caster;
            if (unit == null)
                return;

            foreach (SpellEffectInfo effect in GetEffects())
            {
                if (effect != null && Convert.ToBoolean(targetInfo.effectMask & (1 << (int)effect.EffectIndex)))
                {
                    m_damage = 0;
                    m_healing = 0;

                    HandleEffects(unit, null, null, effect.EffectIndex, SpellEffectHandleMode.LaunchTarget);

                    if (m_damage > 0)
                    {
                        if (effect.IsTargetingArea() || effect.IsAreaAuraEffect() || effect.IsEffect(SpellEffectName.PersistentAreaAura))
                        {
                            m_damage = (int)(m_damage * unit.GetTotalAuraMultiplierByMiscMask(AuraType.ModAoeDamageAvoidance, (uint)m_spellInfo.SchoolMask));
                            if (!m_caster.IsTypeId(TypeId.Player))
                                m_damage = (int)(m_damage * unit.GetTotalAuraMultiplierByMiscMask(AuraType.ModCreatureAoeDamageAvoidance, (uint)m_spellInfo.SchoolMask));

                            if (m_caster.IsTypeId(TypeId.Player))
                            {
                                int targetAmount = m_UniqueTargetInfo.Count;
                                if (targetAmount > 10)
                                    m_damage = m_damage * 10 / targetAmount;
                            }
                        }
                    }

                    if (Convert.ToBoolean(m_applyMultiplierMask & (1 << (int)effect.EffectIndex)))
                    {
                        m_damage = (int)(m_damage * m_damageMultipliers[effect.EffectIndex]);
                        m_damageMultipliers[effect.EffectIndex] *= multiplier[effect.EffectIndex];
                    }
                    targetInfo.damage += m_damage;
                }
            }

            Player modOwner = m_caster.GetSpellModOwner();
            if (modOwner)
                modOwner.SetSpellModTakingSpell(this, true);

            targetInfo.crit = m_caster.IsSpellCrit(unit, m_spellInfo, m_spellSchoolMask, m_attackType);

            modOwner = m_caster.GetSpellModOwner();
            if (modOwner)
                modOwner.SetSpellModTakingSpell(this, false);
        }

        SpellCastResult CanOpenLock(uint effIndex, uint lockId, SkillType skillId, ref int reqSkillValue, ref int skillValue)
        {
            if (lockId == 0)                                             // possible case for GO and maybe for items.
                return SpellCastResult.SpellCastOk;

            // Get LockInfo
            var lockInfo = CliDB.LockStorage.LookupByKey(lockId);

            if (lockInfo == null)
                return SpellCastResult.BadTargets;

            SpellEffectInfo effect = GetEffect(effIndex);
            if (effect == null)
                return SpellCastResult.BadTargets; // no idea about correct error

            bool reqKey = false;                                    // some locks not have reqs

            for (int j = 0; j < SharedConst.MaxLockCase; ++j)
            {
                switch ((LockKeyType)lockInfo.LockType[j])
                {
                    // check key item (many fit cases can be)
                    case LockKeyType.Item:
                        if (lockInfo.Index[j] != 0 && m_CastItem && m_CastItem.GetEntry() == lockInfo.Index[j])
                            return SpellCastResult.SpellCastOk;
                        reqKey = true;
                        break;
                    // check key skill (only single first fit case can be)
                    case LockKeyType.Skill:
                        {
                            reqKey = true;

                            // wrong locktype, skip
                            if (effect.MiscValue != lockInfo.Index[j])
                                continue;

                            skillId = SharedConst.SkillByLockType((LockType)lockInfo.Index[j]);

                            if (skillId != SkillType.None || lockInfo.Index[j] == (uint)LockType.Picklock)
                            {
                                reqSkillValue = lockInfo.Skill[j];

                                // castitem check: rogue using skeleton keys. the skill values should not be added in this case.
                                skillValue = 0;
                                if (!m_CastItem && m_caster.IsTypeId(TypeId.Player))
                                    skillValue = m_caster.ToPlayer().GetSkillValue(skillId);
                                else if (lockInfo.Index[j] == (uint)LockType.Picklock)
                                    skillValue = (int)m_caster.getLevel() * 5;

                                // skill bonus provided by casting spell (mostly item spells)
                                // add the effect base points modifier from the spell cast (cheat lock / skeleton key etc.)
                                if (effect.TargetA.GetTarget() == Targets.GameobjectItemTarget || effect.TargetB.GetTarget() == Targets.GameobjectItemTarget)
                                    skillValue += effect.CalcValue();

                                if (skillValue < reqSkillValue)
                                    return SpellCastResult.LowCastlevel;
                            }

                            return SpellCastResult.SpellCastOk;
                        }
                }
            }

            if (reqKey)
                return SpellCastResult.BadTargets;

            return SpellCastResult.SpellCastOk;
        }

        public void SetSpellValue(SpellValueMod mod, int value)
        {
            if (mod < SpellValueMod.End)
            {
                SpellEffectInfo effect = GetEffect((uint)mod);
                if (effect != null)
                    m_spellValue.EffectBasePoints[(int)mod] = effect.CalcBaseValue(value);
                return;
            }

            switch (mod)
            {
                case SpellValueMod.RadiusMod:
                    m_spellValue.RadiusMod = (float)value / 10000;
                    break;
                case SpellValueMod.MaxTargets:
                    m_spellValue.MaxAffectedTargets = (uint)value;
                    break;
                case SpellValueMod.AuraStack:
                    m_spellValue.AuraStackAmount = (byte)value;
                    break;
            }
        }

        void PrepareTargetProcessing()
        {
        }

        void FinishTargetProcessing()
        {
            SendSpellExecuteLog();
        }

        void LoadScripts()
        {
            m_loadedScripts = Global.ScriptMgr.CreateSpellScripts(m_spellInfo.Id, this);
            foreach (var script in m_loadedScripts)
            {
                Log.outDebug(LogFilter.Spells, "Spell.LoadScripts: Script `{0}` for spell `{1}` is loaded now", script._GetScriptName(), m_spellInfo.Id);
                script.Register();
            }
        }

        void CallScriptBeforeCastHandlers()
        {
            foreach (var script in m_loadedScripts)
            {
                script._PrepareScriptCall(SpellScriptHookType.BeforeCast);

                foreach (var hook in script.BeforeCast)
                    hook.Call();

                script._FinishScriptCall();
            }
        }

        void CallScriptOnCastHandlers()
        {
            foreach (var script in m_loadedScripts)
            {
                script._PrepareScriptCall(SpellScriptHookType.OnCast);

                foreach (var hook in script.OnCast)
                    hook.Call();

                script._FinishScriptCall();
            }
        }

        void CallScriptAfterCastHandlers()
        {
            foreach (var script in m_loadedScripts)
            {
                script._PrepareScriptCall(SpellScriptHookType.AfterCast);

                foreach (var hook in script.AfterCast)
                    hook.Call();

                script._FinishScriptCall();
            }
        }

        SpellCastResult CallScriptCheckCastHandlers()
        {
            SpellCastResult retVal = SpellCastResult.SpellCastOk;
            foreach (var script in m_loadedScripts)
            {
                script._PrepareScriptCall(SpellScriptHookType.CheckCast);

                foreach (var hook in script.OnCheckCast)
                {
                    SpellCastResult tempResult = hook.Call();
                    if (tempResult != SpellCastResult.SpellCastOk)
                        retVal = tempResult;
                }

                script._FinishScriptCall();
            }
            return retVal;
        }

        void PrepareScriptHitHandlers()
        {
            foreach (var script in m_loadedScripts)
                script._InitHit();
        }

        bool CallScriptEffectHandlers(uint effIndex, SpellEffectHandleMode mode)
        {
            // execute script effect handler hooks and check if effects was prevented
            bool preventDefault = false;
            foreach (var script in m_loadedScripts)
            {
                SpellScriptHookType hookType;
                List<SpellScript.EffectHandler> effList;
                switch (mode)
                {
                    case SpellEffectHandleMode.Launch:
                        effList = script.OnEffectLaunch;
                        hookType = SpellScriptHookType.Launch;
                        break;
                    case SpellEffectHandleMode.LaunchTarget:
                        effList = script.OnEffectLaunchTarget;
                        hookType = SpellScriptHookType.LaunchTarget;
                        break;
                    case SpellEffectHandleMode.Hit:
                        effList = script.OnEffectHit;
                        hookType = SpellScriptHookType.EffectHit;
                        break;
                    case SpellEffectHandleMode.HitTarget:
                        effList = script.OnEffectHitTarget;
                        hookType = SpellScriptHookType.EffectHitTarget;
                        break;
                    default:
                        Contract.Assert(false);
                        return false;
                }
                script._PrepareScriptCall(hookType);
                foreach (var eff in effList)
                {
                    // effect execution can be prevented
                    if (!script._IsEffectPrevented(effIndex) && eff.IsEffectAffected(m_spellInfo, effIndex))
                        eff.Call(effIndex);
                }

                if (!preventDefault)
                    preventDefault = script._IsDefaultEffectPrevented(effIndex);

                script._FinishScriptCall();
            }
            return preventDefault;
        }

        void CallScriptSuccessfulDispel(uint effIndex)
        {
            foreach (var script in m_loadedScripts)
            {
                script._PrepareScriptCall(SpellScriptHookType.EffectSuccessfulDispel);

                foreach (var hook in script.OnEffectSuccessfulDispel)
                    hook.Call(effIndex);

                script._FinishScriptCall();
            }
        }

        void CallScriptBeforeHitHandlers(SpellMissInfo missInfo)
        {
            foreach (var script in m_loadedScripts)
            {
                script._PrepareScriptCall(SpellScriptHookType.BeforeHit);

                foreach (var hook in script.BeforeHit)
                    hook.Call(missInfo);

                script._FinishScriptCall();
            }
        }

        void CallScriptOnHitHandlers()
        {
            foreach (var script in m_loadedScripts)
            {
                script._PrepareScriptCall(SpellScriptHookType.OnHit);

                foreach (var hook in script.OnHit)
                    hook.Call();

                script._FinishScriptCall();
            }
        }

        void CallScriptAfterHitHandlers()
        {
            foreach (var script in m_loadedScripts)
            {
                script._PrepareScriptCall(SpellScriptHookType.AfterHit);

                foreach (var hook in script.AfterHit)
                    hook.Call();

                script._FinishScriptCall();
            }
        }

        void CallScriptObjectAreaTargetSelectHandlers(List<WorldObject> targets, uint effIndex, SpellImplicitTargetInfo targetType)
        {
            foreach (var script in m_loadedScripts)
            {
                script._PrepareScriptCall(SpellScriptHookType.ObjectAreaTargetSelect);

                foreach (var hook in script.OnObjectAreaTargetSelect)
                    if (hook.IsEffectAffected(m_spellInfo, effIndex) && targetType.GetTarget() == hook.GetTarget())
                        hook.Call(targets);

                script._FinishScriptCall();
            }
        }

        void CallScriptObjectTargetSelectHandlers(ref WorldObject target, uint effIndex, SpellImplicitTargetInfo targetType)
        {
            foreach (var script in m_loadedScripts)
            {
                script._PrepareScriptCall(SpellScriptHookType.ObjectTargetSelect);

                foreach (var hook in script.OnObjectTargetSelect)
                    if (hook.IsEffectAffected(m_spellInfo, effIndex) && targetType.GetTarget() == hook.GetTarget())
                        hook.Call(ref target);

                script._FinishScriptCall();
            }
        }

        void CallScriptDestinationTargetSelectHandlers(ref SpellDestination target, uint effIndex, SpellImplicitTargetInfo targetType)
        {
            foreach (var script in m_loadedScripts)
            {
                script._PrepareScriptCall(SpellScriptHookType.DestinationTargetSelect);

                foreach (var hook in script.OnDestinationTargetSelect)
                    if (hook.IsEffectAffected(m_spellInfo, effIndex) && targetType.GetTarget() == hook.GetTarget())
                        hook.Call(ref target);

                script._FinishScriptCall();
            }
        }

        bool CheckScriptEffectImplicitTargets(uint effIndex, uint effIndexToCheck)
        {
            // Skip if there are not any script
            if (m_loadedScripts.Empty())
                return true;

            foreach (var script in m_loadedScripts)
            {
                foreach (var hook in script.OnObjectTargetSelect)
                    if ((hook.IsEffectAffected(m_spellInfo, effIndex) && !hook.IsEffectAffected(m_spellInfo, effIndexToCheck)) ||
                        (!hook.IsEffectAffected(m_spellInfo, effIndex) && hook.IsEffectAffected(m_spellInfo, effIndexToCheck)))
                        return false;

                foreach (var hook in script.OnObjectAreaTargetSelect)
                    if ((hook.IsEffectAffected(m_spellInfo, effIndex) && !hook.IsEffectAffected(m_spellInfo, effIndexToCheck)) ||
                        (!hook.IsEffectAffected(m_spellInfo, effIndex) && hook.IsEffectAffected(m_spellInfo, effIndexToCheck)))
                        return false;
            }
            return true;
        }

        bool CanExecuteTriggersOnHit(uint effMask, SpellInfo triggeredByAura = null)
        {
            bool only_on_caster = (triggeredByAura != null && triggeredByAura.HasAttribute(SpellAttr4.ProcOnlyOnCaster));
            // If triggeredByAura has SPELL_ATTR4_PROC_ONLY_ON_CASTER then it can only proc on a casted spell with TARGET_UNIT_CASTER
            foreach (SpellEffectInfo effect in GetEffects())
            {
                if (effect != null && (Convert.ToBoolean(effMask & (1 << (int)effect.EffectIndex)) && (!only_on_caster || (effect.TargetA.GetTarget() == Targets.UnitCaster))))
                    return true;
            }
            return false;
        }

        void PrepareTriggersExecutedOnHit()
        {
            // @todo move this to scripts
            if (m_spellInfo.SpellFamilyName != 0)
            {
                SpellInfo excludeCasterSpellInfo = Global.SpellMgr.GetSpellInfo(m_spellInfo.ExcludeCasterAuraSpell);
                if (excludeCasterSpellInfo != null && !excludeCasterSpellInfo.IsPositive())
                    m_preCastSpell = m_spellInfo.ExcludeCasterAuraSpell;
                SpellInfo excludeTargetSpellInfo = Global.SpellMgr.GetSpellInfo(m_spellInfo.ExcludeTargetAuraSpell);
                if (excludeTargetSpellInfo != null && !excludeTargetSpellInfo.IsPositive())
                    m_preCastSpell = m_spellInfo.ExcludeTargetAuraSpell;
            }

            // @todo move this to scripts
            switch (m_spellInfo.SpellFamilyName)
            {
                case SpellFamilyNames.Mage:
                    {
                        // Permafrost
                        if (m_spellInfo.SpellFamilyFlags[1].HasAnyFlag(0x00001000u) || m_spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x00100220u))
                            m_preCastSpell = 68391;
                        break;
                    }
            }

            // handle SPELL_AURA_ADD_TARGET_TRIGGER auras:
            // save auras which were present on spell caster on cast, to prevent triggered auras from affecting caster
            // and to correctly calculate proc chance when combopoints are present
            var targetTriggers = m_caster.GetAuraEffectsByType(AuraType.AddTargetTrigger);
            foreach (var eff in targetTriggers)
            {
                if (!eff.IsAffectingSpell(m_spellInfo))
                    continue;

                SpellInfo auraSpellInfo = eff.GetSpellInfo();
                byte auraSpellIdx = eff.GetEffIndex();
                SpellEffectInfo auraEffect = auraSpellInfo.GetEffect(m_caster.GetMap().GetDifficultyID(), auraSpellIdx);
                if (auraEffect != null)
                {
                    SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(auraEffect.TriggerSpell);
                    if (spellInfo != null)
                    {
                        // calculate the chance using spell base amount, because aura amount is not updated on combo-points change
                        // this possibly needs fixing
                        int auraBaseAmount = eff.GetBaseAmount();
                        // proc chance is stored in effect amount
                        int chance = m_caster.CalculateSpellDamage(null, auraSpellInfo, auraSpellIdx, auraBaseAmount);
                        // build trigger and add to the list
                        HitTriggerSpell spellTriggerInfo = new HitTriggerSpell();
                        spellTriggerInfo.triggeredSpell = spellInfo;
                        spellTriggerInfo.triggeredByAura = auraSpellInfo;
                        spellTriggerInfo.chance = chance * eff.GetBase().GetStackAmount();

                        m_hitTriggerSpells.Add(spellTriggerInfo);
                    }
                }
            }
        }

        bool HasGlobalCooldown()
        {
            // Only players or controlled units have global cooldown
            if (!m_caster.IsTypeId(TypeId.Player) && m_caster.GetCharmInfo() == null)
                return false;

            return m_caster.GetSpellHistory().HasGlobalCooldown(m_spellInfo);
        }

        void TriggerGlobalCooldown()
        {
            int gcd = (int)m_spellInfo.StartRecoveryTime;
            if (gcd == 0 || m_spellInfo.StartRecoveryCategory == 0)
                return;

            // Only players or controlled units have global cooldown
            if (!m_caster.IsTypeId(TypeId.Player) && m_caster.GetCharmInfo() == null)
                return;

            if (m_caster.IsTypeId(TypeId.Player))
                if (m_caster.ToPlayer().GetCommandStatus(PlayerCommandStates.Cooldown))
                    return;

            // Global cooldown can't leave range 1..1.5 secs
            // There are some spells (mostly not casted directly by player) that have < 1 sec and > 1.5 sec global cooldowns
            // but as tests show are not affected by any spell mods.
            if (m_spellInfo.StartRecoveryTime >= 750 && m_spellInfo.StartRecoveryTime <= 1500)
            {
                // gcd modifier auras are applied only to own spells and only players have such mods
                if (m_caster.IsTypeId(TypeId.Player))
                    m_caster.ToPlayer().ApplySpellMod(m_spellInfo.Id, SpellModOp.GlobalCooldown, ref gcd, this);

                bool isMeleeOrRangedSpell = m_spellInfo.DmgClass == SpellDmgClass.Melee || m_spellInfo.DmgClass == SpellDmgClass.Ranged ||
                    m_spellInfo.HasAttribute(SpellAttr0.ReqAmmo) || m_spellInfo.HasAttribute(SpellAttr0.Ability);

                // Apply haste rating
                if (gcd > 750 && ((m_spellInfo.StartRecoveryCategory == 133 && !isMeleeOrRangedSpell) || m_caster.HasAuraTypeWithAffectMask(AuraType.ModGlobalCooldownByHaste, m_spellInfo)))
                    gcd = Math.Min(Math.Max((int)(gcd * m_caster.GetFloatValue(UnitFields.ModCastHaste)), 750), 1500);

                if (gcd > 750 && m_caster.HasAuraTypeWithAffectMask(AuraType.ModGlobalCooldownByHasteRegen, m_spellInfo))
                    gcd = Math.Min(Math.Max((int)(gcd * m_caster.GetFloatValue(UnitFields.ModHasteRegen)), 750), 1500);
            }

            m_caster.GetSpellHistory().AddGlobalCooldown(m_spellInfo, (uint)gcd);
        }

        void CancelGlobalCooldown()
        {
            if (m_spellInfo.StartRecoveryTime == 0)
                return;

            // Cancel global cooldown when interrupting current cast
            if (m_caster.GetCurrentSpell(CurrentSpellTypes.Generic) != this)
                return;

            // Only players or controlled units have global cooldown
            if (!m_caster.IsTypeId(TypeId.Player) && m_caster.GetCharmInfo() == null)
                return;

            m_caster.GetSpellHistory().CancelGlobalCooldown(m_spellInfo);
        }

        List<SpellScript> m_loadedScripts = new List<SpellScript>();

        int CalculateDamage(uint i, Unit target)
        {
            return m_caster.CalculateSpellDamage(target, m_spellInfo, i, m_spellValue.EffectBasePoints[i], m_castItemLevel);
        }
        int CalculateDamage(uint i, Unit target, out float variance)
        {
            return m_caster.CalculateSpellDamage(target, m_spellInfo, i, out variance, m_spellValue.EffectBasePoints[i], m_castItemLevel);
        }
        public SpellState getState()
        {
            return m_spellState;
        }
        public void setState(SpellState state)
        {
            m_spellState = state;
        }

        void CheckSrc()
        {
            if (!m_targets.HasSrc()) m_targets.SetSrc(m_caster);
        }
        void CheckDst()
        {
            if (!m_targets.HasDst()) m_targets.SetDst(m_caster);
        }

        public int GetCastTime()
        {
            return m_casttime;
        }
        bool IsAutoRepeat()
        {
            return m_autoRepeat;
        }
        void SetAutoRepeat(bool rep)
        {
            m_autoRepeat = rep;
        }
        void ReSetTimer()
        {
            m_timer = m_casttime > 0 ? m_casttime : 0;
        }

        public bool IsTriggered() { return _triggeredCastFlags.HasAnyFlag(TriggerCastFlags.FullMask); }
        public bool IsIgnoringCooldowns() { return _triggeredCastFlags.HasAnyFlag(TriggerCastFlags.IgnoreSpellAndCategoryCD); }
        public bool IsChannelActive() { return m_caster.GetUInt32Value(UnitFields.ChannelSpell) != 0; }

        public bool IsDeletable()
        {
            return !m_referencedFromCurrentSpell && !m_executedCurrently;
        }
        public void SetReferencedFromCurrent(bool yes)
        {
            m_referencedFromCurrentSpell = yes;
        }
        public bool IsInterruptable()
        {
            return !m_executedCurrently;
        }
        void SetExecutedCurrently(bool yes)
        {
            m_executedCurrently = yes;
        }
        public ulong GetDelayStart()
        {
            return m_delayStart;
        }
        public void SetDelayStart(ulong m_time)
        {
            m_delayStart = m_time;
        }
        public ulong GetDelayMoment()
        {
            return m_delayMoment;
        }

        public Unit GetCaster()
        {
            return m_caster;
        }
        public Unit GetOriginalCaster()
        {
            return m_originalCaster;
        }
        public SpellInfo GetSpellInfo()
        {
            return m_spellInfo;
        }
        public List<SpellPowerCost> GetPowerCost()
        {
            return m_powerCost;
        }

        bool isDelayableNoMore()
        {
            if (m_delayAtDamageCount >= 2)
                return true;

            m_delayAtDamageCount++;
            return false;
        }

        bool DontReport()
        {
            return Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.DontReportCastError);
        }

        public SpellInfo GetTriggeredByAuraSpell() { return m_triggeredByAuraSpell; }

        SpellEffectInfo[] GetEffects() { return _effects; }
        public SpellEffectInfo GetEffect(uint index)
        {
            if (index >= _effects.Length)
                return null;

            return _effects[index];
        }

        bool HasEffect(SpellEffectName effect)
        {
            foreach (SpellEffectInfo eff in GetEffects())
            {
                if (eff != null && eff.IsEffect(effect))
                    return true;
            }
            return false;
        }

        public static implicit operator bool (Spell spell)
        {
            return spell != null;
        }

        #region Fields
        MultiMap<uint, SpellLogEffectPowerDrainParams> _powerDrainTargets = new MultiMap<uint, SpellLogEffectPowerDrainParams>();
        MultiMap<uint, SpellLogEffectExtraAttacksParams> _extraAttacksTargets = new MultiMap<uint, SpellLogEffectExtraAttacksParams>();
        MultiMap<uint, SpellLogEffectDurabilityDamageParams> _durabilityDamageTargets = new MultiMap<uint, SpellLogEffectDurabilityDamageParams>();
        MultiMap<uint, SpellLogEffectGenericVictimParams> _genericVictimTargets = new MultiMap<uint, SpellLogEffectGenericVictimParams>();
        MultiMap<uint, SpellLogEffectTradeSkillItemParams> _tradeSkillTargets = new MultiMap<uint, SpellLogEffectTradeSkillItemParams>();
        MultiMap<uint, SpellLogEffectFeedPetParams> _feedPetTargets = new MultiMap<uint, SpellLogEffectFeedPetParams>();
        PathGenerator m_preGeneratedPath;

        public SpellInfo m_spellInfo;
        public Item m_CastItem;
        public ObjectGuid m_castItemGUID;
        public uint m_castItemEntry;
        public int m_castItemLevel;
        public ObjectGuid m_castId;
        public ObjectGuid m_originalCastId;
        public bool m_fromClient;
        public SpellCastFlagsEx m_castFlagsEx;
        public SpellMisc m_misc;
        public uint m_SpellVisual;
        public uint m_preCastSpell;
        public SpellCastTargets m_targets;
        public sbyte m_comboPointGain;
        public SpellCustomErrors m_customError;

        public List<Aura> m_appliedMods;

        Unit m_caster;
        public SpellValue m_spellValue;
        ObjectGuid m_originalCasterGUID;
        Unit m_originalCaster;
        public Spell m_selfContainer;

        //Spell data
        SpellSchoolMask m_spellSchoolMask;                  // Spell school (can be overwrite for some spells (wand shoot for example)
        WeaponAttackType m_attackType;                      // For weapon based attack

        List<SpellPowerCost> m_powerCost = new List<SpellPowerCost>();
        int m_casttime;                                   // Calculated spell cast time initialized only in Spell.prepare
        bool m_canReflect;                                  // can reflect this spell?
        bool m_autoRepeat;
        byte m_runesState;
        byte m_delayAtDamageCount;

        // Delayed spells system
        ulong m_delayStart;                                // time of spell delay start, filled by event handler, zero = just started
        ulong m_delayMoment;                               // moment of next delay call, used internally
        bool m_immediateHandled;                            // were immediate actions handled? (used by delayed spells only)

        // These vars are used in both delayed spell system and modified immediate spell system
        bool m_referencedFromCurrentSpell;
        bool m_executedCurrently;
        bool m_needComboPoints;
        uint m_applyMultiplierMask;
        float[] m_damageMultipliers = new float[SpellConst.MaxEffects];

        // Current targets, to be used in SpellEffects (MUST BE USED ONLY IN SPELL EFFECTS)
        public Unit unitTarget;
        public Item itemTarget;
        public GameObject gameObjTarget;
        public WorldLocation destTarget;
        public int damage;
        float variance;
        SpellEffectHandleMode effectHandleMode;
        public SpellEffectInfo effectInfo;
        // used in effects handlers
        public Aura m_spellAura;

        // this is set in Spell Hit, but used in Apply Aura handler
        DiminishingLevels m_diminishLevel;
        DiminishingGroup m_diminishGroup;

        // -------------------------------------------
        GameObject focusObject;

        // Damage and healing in effects need just calculate
        public int m_damage;           // Damge   in effects count here
        public int m_healing;          // Healing in effects count here

        // ******************************************
        // Spell trigger system
        // ******************************************
        ProcFlags m_procAttacker;                // Attacker trigger flags
        ProcFlags m_procVictim;                  // Victim   trigger flags
        ProcFlagsExLegacy m_procEx;

        // *****************************************
        // Spell target subsystem
        // *****************************************
        // Targets store structures and data
        List<TargetInfo> m_UniqueTargetInfo = new List<TargetInfo>();
        uint m_channelTargetEffectMask;                        // Mask req. alive targets

        List<GOTargetInfo> m_UniqueGOTargetInfo = new List<GOTargetInfo>();

        List<ItemTargetInfo> m_UniqueItemInfo = new List<ItemTargetInfo>();

        SpellDestination[] m_destTargets = new SpellDestination[SpellConst.MaxEffects];

        List<HitTriggerSpell> m_hitTriggerSpells = new List<HitTriggerSpell>();

        SpellState m_spellState;
        int m_timer;

        TriggerCastFlags _triggeredCastFlags;

        // if need this can be replaced by Aura copy
        // we can't store original aura link to prevent access to deleted auras
        // and in same time need aura data and after aura deleting.
        public SpellInfo m_triggeredByAuraSpell;

        SpellEffectInfo[] _effects = new SpellEffectInfo[SpellConst.MaxEffects];

        bool m_skipCheck;
        byte m_auraScaleMask;
        #endregion
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct SpellMisc
    {
        // Alternate names for this value 
        [FieldOffset(0)]
        public uint TalentId;

        [FieldOffset(0)]
        public uint SpellId;

        [FieldOffset(0)]
        public uint SpecializationId;

        // SPELL_EFFECT_SET_FOLLOWER_QUALITY
        // SPELL_EFFECT_INCREASE_FOLLOWER_ITEM_LEVEL
        // SPELL_EFFECT_INCREASE_FOLLOWER_EXPERIENCE
        // SPELL_EFFECT_RANDOMIZE_FOLLOWER_ABILITIES
        // SPELL_EFFECT_LEARN_FOLLOWER_ABILITY
        [FieldOffset(0)]
        public uint FollowerId;

        [FieldOffset(4)]
        public uint FollowerAbilityId;   // only SPELL_EFFECT_LEARN_FOLLOWER_ABILITY

        // SPELL_EFFECT_FINISH_GARRISON_MISSION
        [FieldOffset(0)]
        public uint GarrMissionId;

        // SPELL_EFFECT_UPGRADE_HEIRLOOM
        [FieldOffset(0)]
        public uint ItemId;

        [FieldOffset(0)]
        public uint Data0;

        [FieldOffset(4)]
        public uint Data1;

        public uint[] GetRawData()
        {
            return new uint[] { Data0, Data1 };
        }
    }

    public struct HitTriggerSpell
    {
        public SpellInfo triggeredSpell;
        public SpellInfo triggeredByAura;
        // ubyte triggeredByEffIdx          This might be needed at a later stage - No need known for now
        public int chance;
    }

    public enum SpellEffectHandleMode
    {
        Launch,
        LaunchTarget,
        Hit,
        HitTarget
    }

    public class SkillStatusData
    {
        public SkillStatusData(uint _pos, SkillState state)
        {
            Pos = (byte)_pos;
            State = state;
        }
        public byte Pos;
        public SkillState State;
    }

    public class SpellChainNode
    {
        public SpellInfo prev;
        public SpellInfo next;
        public SpellInfo first;
        public SpellInfo last;
        public byte rank;
    }

    public class SpellLearnSkillNode
    {
        public SkillType skill;
        public ushort step;
        public ushort value;                                           // 0  - max skill value for player level
        public ushort maxvalue;                                        // 0  - max skill value for player level
    }

    public class SpellLearnSpellNode
    {
        public uint Spell;
        public uint OverridesSpell;
        public bool Active;         // show in spellbook or not
        public bool AutoLearned;    // This marks the spell as automatically learned from another source that - will only be used for unlearning
    }

    public class SpellDestination
    {
        public SpellDestination()
        {
            Position = new WorldLocation();
            TransportGUID = ObjectGuid.Empty;
            TransportOffset = new Position();
        }

        public SpellDestination(float x, float y, float z, float orientation = 0.0f, uint mapId = 0xFFFFFFFF) : this()
        {
            Position.Relocate(x, y, z, orientation);
            TransportGUID = ObjectGuid.Empty;
            Position.SetMapId(mapId);
        }

        public SpellDestination(Position pos) : this()
        {
            Position.Relocate(pos);
            TransportGUID = ObjectGuid.Empty;
        }

        public SpellDestination(WorldObject wObj) : this()
        {
            TransportGUID = wObj.GetTransGUID();
            TransportOffset.Relocate(wObj.GetTransOffsetX(), wObj.GetTransOffsetY(), wObj.GetTransOffsetZ(), wObj.GetTransOffsetO());
            Position.Relocate(wObj.GetPosition());
        }

        public void Relocate(Position pos)
        {
            if (!TransportGUID.IsEmpty())
            {
                Position offset;
                Position.GetPositionOffsetTo(pos, out offset);
                TransportOffset.RelocateOffset(offset);
            }
            Position.Relocate(pos);
        }

        public void RelocateOffset(Position offset)
        {
            if (!TransportGUID.IsEmpty())
                TransportOffset.RelocateOffset(offset);

            Position.RelocateOffset(offset);
        }

        public WorldLocation Position;
        public ObjectGuid TransportGUID;
        public Position TransportOffset;
    }

    public class TargetInfo
    {
        public ObjectGuid targetGUID;
        public ulong timeDelay;
        public SpellMissInfo missCondition;
        public SpellMissInfo reflectResult;
        public uint effectMask;
        public bool processed;
        public bool alive;
        public bool crit;
        public bool scaleAura;
        public int damage;
    }
    public class GOTargetInfo
    {
        public ObjectGuid targetGUID;
        public ulong timeDelay;
        public uint effectMask;
        public bool processed;
    }

    public class ItemTargetInfo
    {
        public Item item;
        public uint effectMask;
    }

    public class SpellValue
    {
        public SpellValue(Difficulty difficulty, SpellInfo proto)
        {
            var effects = proto.GetEffectsForDifficulty(difficulty);
            Contract.Assert(effects.Length <= SpellConst.MaxEffects);
            foreach (SpellEffectInfo effect in effects)
                if (effect != null)
                    EffectBasePoints[effect.EffectIndex] = effect.BasePoints;

            MaxAffectedTargets = proto.MaxAffectedTargets;
            RadiusMod = 1.0f;
            AuraStackAmount = 1;
        }

        public int[] EffectBasePoints = new int[SpellConst.MaxEffects];
        public uint MaxAffectedTargets;
        public float RadiusMod;
        public byte AuraStackAmount;
    }

    // Spell modifier (used for modify other spells)
    public class SpellModifier
    {
        public SpellModifier(Aura _ownerAura = null)
        {
            op = SpellModOp.Damage;
            type = SpellModType.Flat;
            charges = 0;
            value = 0;
            mask = new FlagArray128();
            spellId = 0;
            ownerAura = _ownerAura;
        }

        public SpellModOp op { get; set; }
        public SpellModType type { get; set; }
        public short charges { get; set; }
        public int value { get; set; }
        public FlagArray128 mask { get; set; }
        public uint spellId { get; set; }
        public Aura ownerAura { get; set; }
    }

    public class WorldObjectSpellTargetCheck : ICheck<WorldObject>
    {
        public WorldObjectSpellTargetCheck(Unit caster, Unit referer, SpellInfo spellInfo,
            SpellTargetCheckTypes selectionType, List<Condition> condList)
        {
            _caster = caster;
            _referer = referer;
            _spellInfo = spellInfo;
            _targetSelectionType = selectionType;
            _condList = condList;

            if (condList != null)
                _condSrcInfo = new ConditionSourceInfo(null, caster);
            else
                _condSrcInfo = null;
        }

        public virtual bool Invoke(WorldObject obj)
        {
            if (_spellInfo.CheckTarget(_caster, obj, true) != SpellCastResult.SpellCastOk)
                return false;
            Unit unitTarget = obj.ToUnit();
            Corpse corpseTarget = obj.ToCorpse();
            if (corpseTarget != null)
            {
                // use ofter for party/assistance checks
                Player owner = Global.ObjAccessor.FindPlayer(corpseTarget.GetOwnerGUID());
                if (owner != null)
                    unitTarget = owner;
                else
                    return false;
            }
            if (unitTarget != null)
            {
                switch (_targetSelectionType)
                {
                    case SpellTargetCheckTypes.Enemy:
                        if (unitTarget.IsTotem())
                            return false;
                        if (!_caster._IsValidAttackTarget(unitTarget, _spellInfo))
                            return false;
                        break;
                    case SpellTargetCheckTypes.Ally:
                        if (unitTarget.IsTotem())
                            return false;
                        if (!_caster._IsValidAssistTarget(unitTarget, _spellInfo))
                            return false;
                        break;
                    case SpellTargetCheckTypes.Party:
                        if (unitTarget.IsTotem())
                            return false;
                        if (!_caster._IsValidAssistTarget(unitTarget, _spellInfo))
                            return false;
                        if (!_referer.IsInPartyWith(unitTarget))
                            return false;
                        break;
                    case SpellTargetCheckTypes.RaidClass:
                    case SpellTargetCheckTypes.Raid:
                        if (_referer.GetClass() != unitTarget.GetClass())
                            return false;
                        if (unitTarget.IsTotem())
                            return false;
                        if (!_caster._IsValidAssistTarget(unitTarget, _spellInfo))
                            return false;
                        if (!_referer.IsInRaidWith(unitTarget))
                            return false;
                        break;
                    default:
                        break;
                }
            }
            if (_condSrcInfo == null)
                return true;
            _condSrcInfo.mConditionTargets[0] = obj;
            return Global.ConditionMgr.IsObjectMeetToConditions(_condSrcInfo, _condList);
        }

        public Unit _caster { get; set; }
        Unit _referer;
        public SpellInfo _spellInfo { get; set; }
        SpellTargetCheckTypes _targetSelectionType;
        ConditionSourceInfo _condSrcInfo;
        List<Condition> _condList;
    }

    public class WorldObjectSpellNearbyTargetCheck : WorldObjectSpellTargetCheck
    {
        float _range;
        Position _position;
        public WorldObjectSpellNearbyTargetCheck(float range, Unit caster, SpellInfo spellInfo,
            SpellTargetCheckTypes selectionType, List<Condition> condList)
            : base(caster, caster, spellInfo, selectionType, condList)
        {
            _range = range;
            _position = caster.GetPosition();
        }

        public override bool Invoke(WorldObject target)
        {
            float dist = target.GetDistance(_position);
            if (dist < _range && base.Invoke(target))
            {
                _range = dist;
                return true;
            }
            return false;
        }
    }

    public class WorldObjectSpellAreaTargetCheck : WorldObjectSpellTargetCheck
    {
        float _range;
        Position _position;
        public WorldObjectSpellAreaTargetCheck(float range, Position position, Unit caster,
            Unit referer, SpellInfo spellInfo, SpellTargetCheckTypes selectionType, List<Condition> condList)
            : base(caster, referer, spellInfo, selectionType, condList)
        {
            _range = range;
            _position = position;

        }

        public override bool Invoke(WorldObject target)
        {
            if (!target.IsWithinDist3d(_position, _range) && !(target.IsTypeId(TypeId.GameObject) && target.ToGameObject().IsInRange(_position.posX, _position.posY, _position.posZ, _range)))
                return false;
            return base.Invoke(target);
        }
    }

    public class WorldObjectSpellConeTargetCheck : WorldObjectSpellAreaTargetCheck
    {
        public WorldObjectSpellConeTargetCheck(float coneAngle, float range, Unit caster, SpellInfo spellInfo, SpellTargetCheckTypes selectionType, List<Condition> condList)
            : base(range, caster.GetPosition(), caster, caster, spellInfo, selectionType, condList)
        {
            _coneAngle = coneAngle;
        }

        public override bool Invoke(WorldObject target)
        {
            if (_spellInfo.HasAttribute(SpellCustomAttributes.ConeBack))
            {
                if (!_caster.isInBack(target, _coneAngle))
                    return false;
            }
            else if (_spellInfo.HasAttribute(SpellCustomAttributes.ConeLine))
            {
                if (!_caster.HasInLine(target, _caster.GetObjectSize() + target.GetObjectSize()))
                    return false;
            }
            else
            {
                if (!_caster.IsWithinBoundaryRadius(target.ToUnit()))
                    if (!_caster.isInFront(target, _coneAngle))
                        return false;
            }
            return base.Invoke(target);
        }

        float _coneAngle;
    }

    public class WorldObjectSpellTrajTargetCheck : WorldObjectSpellAreaTargetCheck
    {
        public WorldObjectSpellTrajTargetCheck(float range, Position position, Unit caster, SpellInfo spellInfo)
            : base(range, position, caster, caster, spellInfo, SpellTargetCheckTypes.Default, null) { }

        public override bool Invoke(WorldObject target)
        {
            // return all targets on missile trajectory (0 - size of a missile)
            if (!_caster.HasInLine(target, target.GetObjectSize()))
                return false;
            return base.Invoke(target);
        }
    }

    public class SpellEvent : BasicEvent
    {
        public SpellEvent(Spell spell)
        {
            m_Spell = spell;
        }

        public override bool Execute(ulong e_time, uint p_time)
        {
            // update spell if it is not finished
            if (m_Spell.getState() != SpellState.Finished)
                m_Spell.update(p_time);

            // check spell state to process
            switch (m_Spell.getState())
            {
                case SpellState.Finished:
                    {
                        // spell was finished, check deletable state
                        if (m_Spell.IsDeletable())
                        {
                            // check, if we do have unfinished triggered spells
                            return true;                                // spell is deletable, finish event
                        }
                        // event will be re-added automatically at the end of routine)
                        break;
                    }
                case SpellState.Delayed:
                    {
                        // first, check, if we have just started
                        if (m_Spell.GetDelayStart() != 0)
                        {
                            // run the spell handler and think about what we can do next
                            ulong t_offset = e_time - m_Spell.GetDelayStart();
                            ulong n_offset = m_Spell.handle_delayed(t_offset);
                            if (n_offset != 0)
                            {
                                // re-add us to the queue
                                m_Spell.GetCaster().m_Events.AddEvent(this, m_Spell.GetDelayStart() + n_offset, false);
                                return false;                       // event not complete
                            }
                            // event complete
                            // finish update event will be re-added automatically at the end of routine)
                        }
                        else
                        {
                            // delaying had just started, record the moment
                            m_Spell.SetDelayStart(e_time);
                            // handle effects on caster if the spell has travel time but also affects the caster in some way
                            if (!m_Spell.m_targets.HasDst())
                            {
                                ulong n_offset = m_Spell.handle_delayed(0);
                                Contract.Assert(n_offset == m_Spell.GetDelayMoment());
                            }
                            // re-plan the event for the delay moment
                            m_Spell.GetCaster().m_Events.AddEvent(this, e_time + m_Spell.GetDelayMoment(), false);
                            return false;                               // event not complete
                        }
                        break;
                    }
                default:
                    {
                        // all other states
                        // event will be re-added automatically at the end of routine)
                        break;
                    }
            }

            // spell processing not complete, plan event on the next update interval
            m_Spell.GetCaster().m_Events.AddEvent(this, e_time + 1, false);
            return false;                                           // event not complete
        }
        public override void Abort(ulong e_time)
        {
            // oops, the spell we try to do is aborted
            if (m_Spell.getState() != SpellState.Finished)
                m_Spell.cancel();
        }
        public override bool IsDeletable()
        {
            return m_Spell.IsDeletable();
        }

        Spell m_Spell;
    }
}
