Mangos芒果魔兽世界 法术 坐骑 光环 BUFF DEBUFF 系统

法术系统具体参数在下面文章中 文章中有不懂得 可以加Q群397926909

自定义技能终极教程 - 知乎

但是芒果魔兽的对应数据不在DBC里面 主要数据改到了数据库的spell_template 里

剩下的还有触发事件表  spell_proc_event  附魔物品触发表 spell_proc_item_enchant (十字军附魔等) 具体看下图

上面2个图展示了所有法术技能框架 上面文件你会发现 法术系统包括了法术  光环类 效果类 管理类 以及法术目标系统 魔兽世界的道具使用特效 坐骑系统 其实都包含在法术系统内 具体数据表及枚举类作用 看第一张图 我们开始整理整个法术释放流程

1.玩家发送释放法术命令


void WorldSession::HandleCastSpellOpcode(WorldPacket& recvPacket)
{
    uint32 spellId;
    uint8  cast_count;
    recvPacket >> spellId;
    recvPacket >> cast_count;

    // ignore for remote control state (for player case)
    Unit* mover = _player->GetMover();
    if (mover != _player && mover->GetTypeId() == TYPEID_PLAYER)
    {
        recvPacket.rpos(recvPacket.wpos());                 // prevent spam at ignore packet
        return;
    }

    DEBUG_LOG("WORLD: CMSG_CAST_SPELL, spellId - %u, cast_count: %u data length = " SIZEFMTD,
              spellId, cast_count, recvPacket.size());

    SpellEntry const* spellInfo = sSpellTemplate.LookupEntry<SpellEntry>(spellId);
    if (!spellInfo)
    {
        sLog.outError("WORLD: unknown spell id %u", spellId);
        recvPacket.rpos(recvPacket.wpos());                 // prevent spam at ignore packet
        return;
    }

    Unit* caster = mover;
    if (mover->GetTypeId() == TYPEID_PLAYER)
    {
        // not have spell in spellbook or spell passive and not casted by client
        if (!((Player*)mover)->HasActiveSpell(spellId) || IsPassiveSpell(spellInfo))
        {
            sLog.outError("World: Player %u casts spell %u which he shouldn't have", mover->GetGUIDLow(), spellId);
            // cheater? kick? ban?
            recvPacket.rpos(recvPacket.wpos());             // prevent spam at ignore packet
            return;
        }
    }
    else
    {
        bool isPassive = IsPassiveSpell(spellInfo);
        // not have spell in spellbook or spell passive and not casted by client
        if (!mover->HasSpell(spellId) || isPassive)
        {
            if (!_player->HasSpell(spellId) || isPassive)
            {
                // cheater? kick? ban?
                recvPacket.rpos(recvPacket.wpos());             // prevent spam at ignore packet
                return;
            }
            else
                caster = _player;
        }
    }

    // client provided targets
    SpellCastTargets targets;

#ifdef BUILD_PLAYERBOT
    recvPacket >> targets.ReadForCaster(mover);
#else
    recvPacket >> targets.ReadForCaster(_player);
#endif

    // auto-selection buff level base at target level (in spellInfo)
    if (Unit* target = targets.getUnitTarget())
    {
        // if rank not found then function return nullptr but in explicit cast case original spell can be casted and later failed with appropriate error message
        if (SpellEntry const* actualSpellInfo = sSpellMgr.SelectAuraRankForLevel(spellInfo, target->GetLevel()))
            spellInfo = actualSpellInfo;
    }

    if (HasMissingTargetFromClient(spellInfo))
        targets.setUnitTarget(mover->GetTarget());

    if (_player->HasQueuedSpell())
        return;

    bool handled = false;
    Spell* spell = new Spell(caster, spellInfo, TRIGGERED_NONE);
    spell->m_cast_count = cast_count;                       // set count of casts
    spell->m_clientCast = true;
    if (caster->HasGCD(spellInfo) || !caster->IsSpellReady(*spellInfo))
    {
        if (caster->HasGCDOrCooldownWithinMargin(*spellInfo))
        {
            handled = true;
            _player->SetQueuedSpell(spell);
            GetMessager().AddMessage([guid = caster->GetObjectGuid(), isPlayer = caster != mover, targets = targets](WorldSession* session) mutable
            {
                if (session->GetPlayer()) // in case of logout
                {
                    // in case of mind control end
                    if ((isPlayer && session->GetPlayer()->GetObjectGuid() == guid) || (!isPlayer && session->GetPlayer()->GetMover()->GetObjectGuid() == guid))
                        session->GetPlayer()->CastQueuedSpell(targets);
                    else
                        session->GetPlayer()->ClearQueuedSpell();
                }
            });
        }
    }

    if (!handled)
        spell->SpellStart(&targets);
}

上面代码做了一系列验证以及公共CD验证  验证成功后         spell->SpellStart(&targets); 开始执行法术启动

2.SpellStart函数

SpellCastResult Spell::SpellStart(SpellCastTargets const* targets, Aura* triggeredByAura)
{
    if (!m_trueCaster)
        m_trueCaster = m_caster;
    m_spellState = SPELL_STATE_TARGETING;
    m_targets = *targets;

    if (triggeredByAura)
        m_triggeredByAuraSpell = triggeredByAura->GetSpellProto();

    // create and add update event for this spell
    SpellEvent* Event = new SpellEvent(this);
    m_trueCaster->m_events.AddEvent(Event, m_trueCaster->m_events.CalculateTime(1));

    if (!m_trueCaster->IsGameObject()) // gameobjects dont have a sense of already casting a spell
    {
        // Prevent casting at cast another spell (ServerSide check)
        if (m_caster->IsNonMeleeSpellCasted(false, true, true) && m_cast_count && !m_ignoreConcurrentCasts)
        {
            SendCastResult(SPELL_FAILED_SPELL_IN_PROGRESS);
            finish(false);
            return SPELL_FAILED_SPELL_IN_PROGRESS;
        }
    }

    SpellCastResult result = PreCastCheck();
    if (result != SPELL_CAST_OK)
    {
        SendCastResult(result);
        finish(false);
        return result;
    }
    Prepare();

    return SPELL_CAST_OK;
}

上面代码需要注意的是

    SpellEvent* Event = new SpellEvent(this);
    m_trueCaster->m_events.AddEvent(Event, m_trueCaster->m_events.CalculateTime(1));
魔兽有持续技能或者延迟的飞行技能 就需要通过这个事件函数回调执行 在下一帧执行 最后延时执行完毕后 会修改state为 SPELL_STATE_FINISHED 后删除掉事件


void EventProcessor::Update(uint32 p_time)
{
    // update time
    m_time += p_time;

    // main event loop
    EventList::iterator i;
    while (((i = m_events.begin()) != m_events.end()) && i->first <= m_time)
    {
        // get and remove event from queue
        BasicEvent* Event = i->second;
        m_events.erase(i);

        if (!Event->to_Abort)
        {
            if (Event->Execute(m_time, p_time))
            {
                // completely destroy event if it is not re-added
                delete Event;
            }
        }
        else
        {
            Event->Abort(m_time);
            delete Event;
        }
    }
}

bool SpellEvent::Execute(uint64 e_time, uint32 p_time)
{
    // update spell if it is not finished
    if (m_Spell->getState() != SPELL_STATE_FINISHED)
        m_Spell->update(p_time);

    // check spell state to process
    switch (m_Spell->getState())
    {
        case SPELL_STATE_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 SPELL_STATE_CHANNELING:
        {
            // this spell is in channeled state, process it on the next update
            // event will be re-added automatically at the end of routine)
        } break;

        case SPELL_STATE_TRAVELING:
        {
            // first, check, if we have just started
            if (m_Spell->GetDelayStart() != 0)
            {
                // no, we aren't, do the typical update
                // check, if we have channeled spell on our hands
                if (IsChanneledSpell(m_Spell->m_spellInfo))
                {
                    // evented channeled spell is processed separately, casted once after delay, and not destroyed till finish
                    // check, if we have casting anything else except this channeled spell and autorepeat
                    if (m_Spell->GetCaster()->IsNonMeleeSpellCasted(false, true, true))
                    {
                        // another non-melee non-delayed spell is casted now, abort
                        m_Spell->cancel();
                    }
                    else
                    {
                        // do the action (pass spell to channeling state)
                        m_Spell->handle_immediate();
                    }
                    // event will be re-added automatically at the end of routine)
                }
                else
                {
                    // run the spell handler and think about what we can do next
                    uint64 t_offset = e_time - m_Spell->GetDelayStart();
                    uint64 n_offset = m_Spell->handle_delayed(t_offset);
                    if (n_offset)
                    {
                        // re-add us to the queue
                        m_Spell->GetTrueCaster()->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);
                // re-plan the event for the delay moment
                m_Spell->GetTrueCaster()->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->GetTrueCaster()->m_events.AddEvent(this, e_time + 1, false);
    return false;                                           // event not complete
}

    SpellCastResult result = PreCastCheck(); 函数会执行一些检查函数



SpellCastResult Spell::PreCastCheck(Aura* triggeredByAura /*= nullptr*/)
{
    SpellCastResult result = CheckCast(true);
    if (result != SPELL_CAST_OK && (!IsAutoRepeat() || m_triggerAutorepeat)) // always cast autorepeat dummy for triggering
    {
        if (triggeredByAura)
        {
            SendChannelUpdate(0);
            triggeredByAura->GetHolder()->SetAuraDuration(0);
        }
        return result;
    }

    return SPELL_CAST_OK;
}
SpellCastResult Spell::CheckCast(bool strict)
{
    // check cooldowns to prevent cheating (ignore passive spells, that client side visual only)
    if (!m_ignoreCooldowns && !m_spellInfo->HasAttribute(SPELL_ATTR_PASSIVE)
            && !m_trueCaster->IsSpellReady(*m_spellInfo, m_CastItem ? m_CastItem->GetProto() : nullptr))
    {
        if (m_triggeredByAuraSpell)
            return SPELL_FAILED_DONT_REPORT;
        else
            return SPELL_FAILED_NOT_READY;
    }

    // check global cooldown
    if (strict && !m_ignoreGCD && m_trueCaster->HasGCD(m_spellInfo))
        return SPELL_FAILED_NOT_READY;

    if (m_caster)
    {
        //检查是否玩家 并且存活状态 如果已经死了 返回
        if (!m_caster->IsAlive() && m_caster->GetTypeId() == TYPEID_PLAYER && !m_spellInfo->HasAttribute(SPELL_ATTR_ALLOW_CAST_WHILE_DEAD) && !m_spellInfo->HasAttribute(SPELL_ATTR_PASSIVE))
            return SPELL_FAILED_CASTER_DEAD;
        //检测是否在是瞬发法师 不是的话 施法者必须站立状态
        if (!m_IsTriggeredSpell && !m_caster->IsStandState() && m_caster->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) && !m_spellInfo->HasAttribute(SPELL_ATTR_ALLOW_WHILE_SITTING))
            return SPELL_FAILED_NOT_STANDING;

        //检测是否在战场离开状态
        if (!m_IsTriggeredSpell && m_caster->GetTypeId() == TYPEID_PLAYER)
            if (BattleGround* bg = ((Player*)m_caster)->GetBattleGround())
                if (bg->GetStatus() == STATUS_WAIT_LEAVE)
                    return SPELL_FAILED_DONT_REPORT;
        //检测是否非战斗状态才能施放的法术
        if ((!m_IsTriggeredSpell || m_triggeredByAuraSpell) && IsNonCombatSpell(m_spellInfo) && m_caster->IsInCombat())
            return SPELL_FAILED_AFFECTING_COMBAT;
        //是否开始室内室外检测 判断法术是否只能室内使用 或者室外使用 
        if (m_caster->GetTypeId() == TYPEID_PLAYER && !((Player*)m_caster)->IsGameMaster() &&
            sWorld.getConfig(CONFIG_BOOL_VMAP_INDOOR_CHECK) &&
            VMAP::VMapFactory::createOrGetVMapManager()->isLineOfSightCalcEnabled())
        {
            if (m_spellInfo->HasAttribute(SPELL_ATTR_ONLY_OUTDOORS) &&
                !m_caster->GetTerrain()->IsOutdoors(m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ()))
                return SPELL_FAILED_ONLY_OUTDOORS; // TODO: If at least one effect is SPELL_AURA_MOUNTED return mounts not allowed

            if (m_spellInfo->HasAttribute(SPELL_ATTR_ONLY_INDOORS) &&
                m_caster->GetTerrain()->IsOutdoors(m_caster->GetPositionX(), m_caster->GetPositionY(), m_caster->GetPositionZ()))
                return SPELL_FAILED_ONLY_INDOORS;
        }

        //检查特殊姿态才能使用的法术
        if (strict && !m_IsTriggeredSpell)
        {
            //检查形态或者姿态是否正确 不正确就不能使用
            SpellCastResult shapeError = GetErrorAtShapeshiftedCast(m_spellInfo, m_caster->GetShapeshiftForm());
            if (shapeError != SPELL_CAST_OK)
                return shapeError;
            //检查是否是潜行姿态才能使用的
            if (m_spellInfo->HasAttribute(SPELL_ATTR_ONLY_STEALTHED) && !(m_caster->HasStealthAura()))
                return SPELL_FAILED_ONLY_STEALTHED;
        }

        // 施法状态要求 是否满足施法当前状态
        if (m_spellInfo->CasterAuraState && !m_caster->HasAuraState(AuraState(m_spellInfo->CasterAuraState)))
            return SPELL_FAILED_CASTER_AURASTATE;
        //不满足释放状态的要求  不满足当前施法状态
        if (m_spellInfo->CasterAuraStateNot && m_caster->HasAuraState(AuraState(m_spellInfo->CasterAuraStateNot)))
            return SPELL_FAILED_CASTER_AURASTATE;
        //检查是否有连击点
        if (!m_IsTriggeredSpell && NeedsComboPoints(m_spellInfo) && (!m_targets.getUnitTarget() || m_targets.getUnitTarget()->GetObjectGuid() != m_caster->GetComboTargetGuid()))
            //是否是战士 如果是战士 压制返回 SPELL_FAILED_CASTER_AURASTATE  盗贼就返回没有连击点
            return m_caster->getClass() == CLASS_WARRIOR ? SPELL_FAILED_CASTER_AURASTATE : SPELL_FAILED_NO_COMBO_POINTS;
        //玩家判断
        if (m_caster->GetTypeId() == TYPEID_PLAYER)
        {
            //检查是否飞行状态 或者 移动状态 取消施法
            if (m_caster->IsMovingIgnoreFlying())
            {
                // 检查施法是否自动施法 判断是否在下落状态 
                if ((!m_caster->m_movementInfo.HasMovementFlag(MOVEFLAG_FALLINGFAR) || m_spellInfo->Effect[EFFECT_INDEX_0] != SPELL_EFFECT_STUCK) &&
                    (IsAutoRepeat() || (m_spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_STANDING_CANCELS) != 0))
                    return SPELL_FAILED_MOVING;
            }

            // 检查角色职业状态 一些特殊脚本错误
            switch (m_spellInfo->SpellFamilyName)
            {
                //德鲁伊 
                case SPELLFAMILY_DRUID:
                {
                    if (IsSpellHaveAura(m_spellInfo, SPELL_AURA_MOD_SHAPESHIFT))
                        if (m_caster->HasOverrideScript(3655))
                            return SPELL_FAILED_TARGET_AURASTATE;
                    //[[fallthrough]]
                }
                //萨满 骑士 牧师  
                case SPELLFAMILY_PRIEST:
                case SPELLFAMILY_SHAMAN:
                case SPELLFAMILY_PALADIN:
                {
                    if (IsSpellHaveEffect(m_spellInfo, SPELL_EFFECT_HEAL) ||
                        IsSpellHaveAura(m_spellInfo, SPELL_AURA_PERIODIC_HEAL) ||
                        IsSpellHaveEffect(m_spellInfo, SPELL_EFFECT_DISPEL))
                    {
                        if (m_caster->HasOverrideScript(4327))
                            return SPELL_FAILED_FIZZLE;
                    }
                    break;
                }
                case SPELLFAMILY_WARRIOR:
                {
                    if (IsSpellHaveAura(m_spellInfo, SPELL_AURA_MOD_SHAPESHIFT))
                    {
                        if (m_caster->HasOverrideScript(3654))
                            return SPELL_FAILED_TARGET_AURASTATE;
                    }
                    break;
                }
                default:
                    break;
            }
        }
    }
    //目标
    Unit* target = m_targets.getUnitTarget();
    uint32 affectedMask = GetCheckCastEffectMask(m_spellInfo);
    //检查 技能是否可以对目标的目标是否 如果当前目标不满足 对目标的目标释放
    if (m_spellInfo->HasAttribute(SPELL_ATTR_EX5_IMPLIED_TARGETING) && target)
    {
        if (!(m_trueCaster->CanAssistSpell(target, m_spellInfo)))
        {
            if (m_spellInfo->HasAttribute(SPELL_ATTR_EX5_IMPLIED_TARGETING))
            {
                if (Unit* targetOfUnitTarget = target->GetTarget(m_trueCaster))
                {
                    if (m_trueCaster->CanAssistSpell(targetOfUnitTarget, m_spellInfo))
                        target = targetOfUnitTarget;
                }
            }
        }
    }

    bool selfTargeting = false;
    if (!target)
    {
        uint32 selfImmuneMask = GetCheckCastSelfEffectMask(m_spellInfo);
        if (selfImmuneMask)
        {
            target = m_caster;
            affectedMask = selfImmuneMask;
            selfTargeting = true;
        }
    }

    if (target)
    {
         //脚本检测 
        //是否满足附近目标特殊单位 法术目标为:TARGET_UNIT_SCRIPT_NEAR_CASTER 枚举
        if (!IsSpellWithScriptUnitTarget(m_spellInfo))
        {
            // 是否不具备这个光环特效
            if (m_spellInfo->TargetAuraStateNot && target->HasAuraState(AuraState(m_spellInfo->TargetAuraStateNot)))
                return SPELL_FAILED_TARGET_AURASTATE;
            //检查是否瞬发 并且能不能死亡释放的法术 目标是不是还或者 活着就报错 
            if (!m_IsTriggeredSpell && IsDeathOnlySpell(m_spellInfo) && target->IsAlive())
                return SPELL_FAILED_TARGET_NOT_DEAD;

            //检测目标是不是图腾 是图腾 就免疫持续施法的技能等
            if (m_spellInfo->HasAttribute(SPELL_ATTR_EX_TRACK_TARGET_IN_CHANNEL) // TODO: Investigate this condition
                && m_spellInfo->HasAttribute(SPELL_ATTR_EX5_SPELL_HASTE_AFFECTS_PERIODIC)
                && target->GetTypeId() == TYPEID_UNIT && ((Creature*)target)->IsTotem())
                return SPELL_FAILED_IMMUNE;

            //目标跟施法者是否为同一个 并且 不为唯一施法目标技能
            bool non_caster_target = target != m_trueCaster && !IsSpellWithCasterSourceTargetsOnly(m_spellInfo);
           
            if (non_caster_target)
            {
                // 身上已经有该光环了
                if (m_spellInfo->TargetAuraState && !target->HasAuraStateForCaster(AuraState(m_spellInfo->TargetAuraState), m_trueCaster->GetObjectGuid()))
                    return SPELL_FAILED_TARGET_AURASTATE;

                // 除了以下法术 其他法术不能在 飞行途中 状态下释放
                if (target->IsTaxiFlying())
                {
                    switch (m_spellInfo->Id)
                    {
                        // Except some spells from Taxi Flying cast
                        case 7720:                              // Ritual of Summoning Effect
                        case 36573:                             // Vision Guide
                        case 42316:                             // Alcaz Survey Credit
                        case 42385:                             // Alcaz Survey Aura
                            break;
                        default:
                            return SPELL_FAILED_BAD_TARGETS;
                    }
                }
                //检测是否 视线被遮挡
                if (m_spellInfo->HasAttribute(SPELL_ATTR_EX5_ALWAYS_LINE_OF_SIGHT) ||
                    (!IsIgnoreLosSpellCast(m_spellInfo) && !m_IsTriggeredSpell))
                    if (!m_trueCaster->IsWithinLOSInMap(target, true))
                        return SPELL_FAILED_LINE_OF_SIGHT;
                //施法者是玩家的话
                if (m_trueCaster->IsPlayer())
                {
                    //不是瞬发 不是使用物品触发的法术  并且有等级过低检测
                    if (!m_CastItem && !m_IsTriggeredSpell && !m_spellInfo->HasAttribute(SPELL_ATTR_EX2_ALLOW_LOW_LEVEL_BUFF))
                        // 检查等级是否足够
                        if (m_spellInfo != sSpellMgr.SelectAuraRankForLevel(m_spellInfo, target->GetLevel()))
                            return SPELL_FAILED_LOWLEVEL;

                    //检查法术是否具有不能 重复释放的技能  例如 术士的驱逐术 检查是不是范围技能 并且检测是不是属于施法人目标所有的怪物
                    if (m_spellInfo->HasAttribute(SPELL_ATTR_EX2_CANNOT_CAST_ON_TAPPED) && !IsAreaOfEffectSpell(m_spellInfo))
                        if (Creature const* targetCreature = dynamic_cast<Creature*>(target))
                            if ((!targetCreature->GetLootRecipientGuid().IsEmpty()) && !targetCreature->IsTappedBy(static_cast<Player*>(m_trueCaster)))
                                return SPELL_FAILED_CANT_CAST_ON_TAPPED;
                    
                    // 检测是否对施法者 是否无效的目标 
                    if (!m_trueCaster->IsGameObject() && target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) && !IsPositiveEffectMask(m_spellInfo, affectedMask, m_trueCaster, target) && !target->IsVisibleForOrDetect(m_caster, m_trueCaster, false, false, true, false, m_spellInfo->HasAttribute(SPELL_ATTR_EX6_IGNORE_PHASE_SHIFT)))
                        return SPELL_FAILED_BAD_TARGETS;
                }
                //检测是否只能对玩家目标使用
                if (strict && m_spellInfo->HasAttribute(SPELL_ATTR_EX3_ONLY_ON_PLAYER) && target->GetTypeId() != TYPEID_PLAYER && !IsAreaOfEffectSpell(m_spellInfo))
                    return SPELL_FAILED_BAD_TARGETS;
                //检测是否只能对不是玩家目标使用
                if (strict && m_spellInfo->HasAttribute(SPELL_ATTR_EX5_NOT_ON_PLAYER) && target->GetTypeId() == TYPEID_PLAYER && !IsAreaOfEffectSpell(m_spellInfo))
                    return SPELL_FAILED_BAD_TARGETS;
                //检测是否只能对玩家控制的角色使用
                if (strict && m_spellInfo->HasAttribute(SPELL_ATTR_EX5_NOT_ON_PLAYER_CONTROLLED_NPC) && target->IsPlayerControlled() && target->GetTypeId() != TYPEID_PLAYER && !IsAreaOfEffectSpell(m_spellInfo))
                    return SPELL_FAILED_BAD_TARGETS;
            }
            //检测目标为自己的话 但是法术标识为不能给自己施法  
            if (!selfTargeting && (m_targets.m_targetMask == TARGET_FLAG_SELF || m_trueCaster == target) && m_spellInfo->HasAttribute(SPELL_ATTR_EX_EXCLUDE_CASTER))
            {
                if (IsOnlySelfTargeting(m_spellInfo))
                    sLog.outCustomLog("Spell ID %u cast at self explicitly even though it has SPELL_ATTR_EX_EXCLUDE_CASTER", m_spellInfo->Id);

                return SPELL_FAILED_BAD_TARGETS;
            }
            //目标为不能选中的情况下 不能施法
            if (!selfTargeting && target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_UNTARGETABLE))
                return SPELL_FAILED_BAD_TARGETS;

            
            if (non_caster_target)
            {
                //检查技能是否为一些针对生物的特殊技能 
                if (!CheckTargetCreatureType(target, m_spellInfo))
                {
                    if (target->GetTypeId() == TYPEID_PLAYER)
                        return SPELL_FAILED_TARGET_IS_PLAYER;
                    return SPELL_FAILED_BAD_TARGETS;
                }
            }
            //检查法术是否对于免疫
            if (IsPositiveSpell(m_spellInfo->Id, m_trueCaster, target) && affectedMask)
                if (target->IsImmuneToSpell(m_spellInfo, target == m_trueCaster, affectedMask, m_trueCaster))
                    return SPELL_FAILED_TARGET_AURASTATE;

            // 检测怪物是否在背后 如果是德鲁伊的话  可以背后使用
            if (m_spellInfo->HasAttribute(SPELL_ATTR_EX2_INITIATE_COMBAT_POST_CAST) && m_spellInfo->HasAttribute(SPELL_ATTR_EX_INITIATES_COMBAT_ENABLES_AUTO_ATTACK) && !m_trueCaster->IsFacingTargetsBack(target))
            {
                // Exclusion for Pounce: Facing Limitation was removed in 2.0.1, but it still uses the same, old Ex-Flags
                if (!m_spellInfo->IsFitToFamily(SPELLFAMILY_DRUID, uint64(0x0000000000020000)))
                    return SPELL_FAILED_NOT_BEHIND;
            }

            // 检测是否需要背对目标
            if (m_spellInfo->HasAttribute(SPELL_ATTR_SS_FACING_BACK) && !m_trueCaster->IsFacingTargetsBack(target))
                return SPELL_FAILED_NOT_BEHIND;

            // 检测是否需要面对目标
            if (((m_spellInfo->Attributes == (SPELL_ATTR_IS_ABILITY | SPELL_ATTR_NOT_SHAPESHIFT | SPELL_ATTR_DO_NOT_SHEATH | SPELL_ATTR_CANCELS_AUTO_ATTACK_COMBAT)) && !m_trueCaster->IsFacingTargetsFront(target)))
                return SPELL_FAILED_NOT_INFRONT;

            // 检测目标是否只能对未进战斗的使用
            if (non_caster_target && m_spellInfo->HasAttribute(SPELL_ATTR_EX_ONLY_PEACEFUL_TARGETS) && target->IsInCombat())
                return SPELL_FAILED_TARGET_AFFECTING_COMBAT;

            // 检测目标目标是否在救赎之魂状态下
            if (target->HasAuraType(SPELL_AURA_SPIRIT_OF_REDEMPTION) && !m_spellInfo->HasAttribute(SPELL_ATTR_EX3_ALLOW_AURA_WHILE_DEAD))
                return SPELL_FAILED_BAD_TARGETS;

            // 检查目标是否受到更强大法术的影响(如果法术仅包含非区域效果光环)
            if (IsAuraApplyEffects(m_spellInfo, SpellEffectIndexMask(affectedMask)) && !IsAreaOfEffectSpell(m_spellInfo) && !HasAreaAuraEffect(m_spellInfo) && !selfTargeting && !m_spellInfo->HasAttribute(SPELL_ATTR_EX4_AURA_NEVER_BOUNCES))
            {
                bool computed = false; // optimization
                int32 amounts[MAX_EFFECT_INDEX];
                for (auto const& pair : target->GetSpellAuraHolderMap())
                {
                    const SpellAuraHolder* existing = pair.second;
                    const SpellEntry* existingSpell = existing->GetSpellProto();

                    if (m_trueCaster->GetObjectGuid() != existing->GetCasterGuid())
                    {
                        if (sSpellMgr.IsSpellStackableWithSpellForDifferentCasters(m_spellInfo, existingSpell))
                            continue;
                    }
                    else if (sSpellMgr.IsSpellStackableWithSpell(m_spellInfo, existingSpell))
                        continue;

                    if (!computed)
                    {
                        for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i)
                        {
                            if (affectedMask & (1 << i))
                            {
                                amounts[i] = CalculateSpellEffectValue(SpellEffectIndex(i), target, true, false);
                                amounts[i] = Aura::CalculateAuraEffectValue(m_caster, target, m_spellInfo, SpellEffectIndex(i), amounts[i]);
                                if (m_auraScript)
                                {
                                    AuraCalcData data(nullptr, m_caster, target, m_spellInfo, SpellEffectIndex(i), m_CastItem);
                                    amounts[i] = m_auraScript->OnAuraValueCalculate(data, amounts[i]);
                                }
                            }
                        }
                        computed = true;
                    }

                    if (IsSimilarExistingAuraStronger(m_caster, m_spellInfo, existing, affectedMask, amounts))
                        return SPELL_FAILED_AURA_BOUNCED;

                    if (sSpellMgr.IsSpellAnotherRankOfSpell(m_spellInfo->Id, existingSpell->Id))
                        if (sSpellMgr.IsSpellHigherRankOfSpell(existingSpell->Id, m_spellInfo->Id))
                            return SPELL_FAILED_AURA_BOUNCED;
                }
            }

            if (m_spellInfo->MaxTargetLevel && target->GetLevel() > m_spellInfo->MaxTargetLevel)
                return SPELL_FAILED_HIGHLEVEL;

            if (m_spellInfo->HasAttribute(SPELL_ATTR_EX5_NOT_ON_TRIVIAL) && target->IsTrivialForTarget(m_caster))
                return SPELL_FAILED_TARGET_IS_TRIVIAL;
        }
    }

    // 检测效果目标类型
    for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i)
    {
        uint32 targetType = m_spellInfo->EffectImplicitTargetA[i];
        switch (targetType)
        {
            case TARGET_UNIT_ENEMY_NEAR_CASTER:
            case TARGET_UNIT_FRIEND_NEAR_CASTER:
            case TARGET_UNIT_NEAR_CASTER:
            case TARGET_UNIT_CASTER_MASTER:
            case TARGET_UNIT_CASTER: break; // never check anything
            case TARGET_UNIT_CASTER_PET: // special pet checks
            {
                Unit* pet = m_caster->GetPet();
                if (!pet)
                    pet = m_caster->GetCharm();
                if (!pet)
                    return SPELL_FAILED_NO_PET;
                else
                {
                    if (!pet->IsAlive())
                        return SPELL_FAILED_TARGETS_DEAD;
                    if (!IsIgnoreLosSpellEffect(m_spellInfo, SpellEffectIndex(i)) && !m_caster->IsWithinLOSInMap(pet, true))
                        return SPELL_FAILED_LINE_OF_SIGHT;
                }
                break;
            }
            default: // needs target
            {
                auto& data = SpellTargetInfoTable[targetType];
                WorldObject* originalCaster = GetCastingObject();
                if (!originalCaster)
                    originalCaster = m_trueCaster;
                if (data.type == TARGET_TYPE_UNIT && data.filter != TARGET_SCRIPT && (data.enumerator == TARGET_ENUMERATOR_SINGLE || data.enumerator == TARGET_ENUMERATOR_CHAIN))
                {
                    if (!target)
                        return SPELL_FAILED_BAD_TARGETS;
                    switch (data.filter)
                    {
                        case TARGET_HARMFUL: if (!originalCaster->CanAttackSpell(target, m_spellInfo)) return SPELL_FAILED_BAD_TARGETS; break;
                        case TARGET_HELPFUL: if (!originalCaster->CanAssistSpell(target, m_spellInfo)) return SPELL_FAILED_BAD_TARGETS; break;
                        case TARGET_PARTY:
                        case TARGET_GROUP: if (!m_trueCaster->CanAssistSpell(target, m_spellInfo) || !m_caster->IsInGroup(target, targetType == TARGET_UNIT_PARTY)) return SPELL_FAILED_BAD_TARGETS; break;
                        default: break;
                    }
                }
            }
        }
    }

    // zone check
    uint32 zone, area;
    m_trueCaster->GetZoneAndAreaId(zone, area);

    //检测法术是否特定区域才能使用
    SpellCastResult locRes = sSpellMgr.GetSpellAllowedInLocationError(m_spellInfo, m_trueCaster->GetMapId(), zone, area,
                             m_caster ? m_caster->GetBeneficiaryPlayer() : nullptr);
    if (locRes != SPELL_CAST_OK)
    {
        if (!m_IsTriggeredSpell)
            return locRes;
        return SPELL_FAILED_DONT_REPORT;
    }


    // 检测施法者可以在坐骑上使用
    if (m_trueCaster->IsPlayer() && m_caster->GetMountID() && !m_IsTriggeredSpell &&
            !IsPassiveSpell(m_spellInfo) && !m_spellInfo->HasAttribute(SPELL_ATTR_ALLOW_WHILE_MOUNTED))
    {
        if (m_caster->IsTaxiFlying())
            return SPELL_FAILED_NOT_ON_TAXI;
        return SPELL_FAILED_NOT_MOUNTED;
    }

    // 检查是否主动物品 被动物品法术由 光环触发 不能使用 
    if (!IsPassiveSpell(m_spellInfo))
    {
        SpellCastResult castResult = CheckItems();
        if (castResult != SPELL_CAST_OK)
            return castResult;
    }

    // 检测是否需要周围的特定游戏物品 找到的话 设置参数
    if (m_spellInfo->RequiresSpellFocus)
    {
        GameObject* ok = nullptr;
        MaNGOS::GameObjectFocusCheck go_check(m_trueCaster, m_spellInfo->RequiresSpellFocus);
        MaNGOS::GameObjectSearcher<MaNGOS::GameObjectFocusCheck> checker(ok, go_check);
        Cell::VisitGridObjects(m_trueCaster, checker, m_trueCaster->GetMap()->GetVisibilityDistance());

        if (!ok)
            return SPELL_FAILED_REQUIRES_SPELL_FOCUS;

        m_eventTarget = ok;                                   // game object found in range
    }
    //检测 是否光环类法术 并且 目标是否在光环范围以内 
    if (!m_IsTriggeredSpell)
    {
        SpellCastResult castResult;
        if (!m_triggeredByAuraSpell)
        {
            castResult = CheckRange(strict);
            if (castResult != SPELL_CAST_OK)
                return castResult;
        }
    }
    //如果由消耗  检测消耗 并且检测特殊负面效果状态  被击晕等情况下不能使用
    if (!m_ignoreCosts && !m_IsTriggeredSpell)
    {
        SpellCastResult castResult = CheckPower(strict);
        if (castResult != SPELL_CAST_OK)
            return castResult;

        // triggered spell not affected by stun/etc
        castResult = CheckCasterAuras(m_param1);
        if (castResult != SPELL_CAST_OK)
            return castResult;
    }
    // 标识为 修改3个效果位置掩码 瞬发或者 可以使用
    if (m_channelOnly)
    {
        m_partialApplicationMask = EFFECT_MASK_ALL; // no effects to be executed but spell needs to go through
        return SPELL_CAST_OK;
    }
    //计算 法术效果掩码
    uint32 availableEffectMask = 0;
    for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i)
        if (m_spellInfo->Effect[i])
            availableEffectMask |= (1 << i);
    //回调匿名函数  增加效果技能应用掩码
    auto partialApplication = [&](uint32 i) -> SpellCastResult
    {
        availableEffectMask &= (~(1 << i));
        if (availableEffectMask == 0)
            return SPELL_FAILED_BAD_TARGETS;
        else
            m_partialApplicationMask |= (1 << i);

        return SPELL_CAST_OK;
    };
    //检测效果技能效果的 一些特殊情况处理
    for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i)
    {
        // 每个效果处理
        switch (m_spellInfo->Effect[i])
        {
            //特殊技能处理
            case SPELL_EFFECT_DUMMY:
            {
                // By Spell ID
                //唤醒苦工技能
                if (m_spellInfo->Id == 19938)               // Awaken Lazy Peon
                {
                    Unit* target = m_targets.getUnitTarget();
                    // 17743 = Lazy Peon Sleep | 10556 = Lazy Peon
                    if (!target || !target->HasAura(17743) || target->GetEntry() != 10556)
                        return SPELL_FAILED_BAD_TARGETS;
                }
                else if (m_spellInfo->Id == 20577)
                {
                    WorldObject* result = FindCorpseUsing<MaNGOS::CannibalizeObjectCheck>();
                    if (result)
                    {
                        switch (result->GetTypeId())
                        {
                            case TYPEID_UNIT:
                            case TYPEID_PLAYER:
                                if (!CheckTarget(static_cast<Unit*>(result), SpellEffectIndex(i), false, EXCEPTION_NONE))
                                    return SPELL_FAILED_NO_EDIBLE_CORPSES;
                                break;
                            case TYPEID_CORPSE:
                                if (Player* owner = ObjectAccessor::FindPlayer(static_cast<Corpse*>(result)->GetOwnerGuid()))
                                    if (!CheckTarget(owner, SpellEffectIndex(i), false, EXCEPTION_NONE))
                                        return SPELL_FAILED_NO_EDIBLE_CORPSES;
                                break;
                        }
                    }
                    else
                        return SPELL_FAILED_NO_EDIBLE_CORPSES;
                }
                // By SpellIconID
                else if (m_spellInfo->SpellIconID == 1648)       // Execute
                {
                    Unit* target = m_targets.getUnitTarget();
                    if (!target || target->GetHealth() > target->GetMaxHealth() * 0.2)
                        return SPELL_FAILED_BAD_TARGETS;
                }
                else if (m_spellInfo->Id == 51582)          // Rocket Boots Engaged
                {
                    if (m_caster->IsInWater() && (m_caster->GetTypeId() != TYPEID_PLAYER || static_cast<Player*>(m_caster)->IsInHighLiquid()))
                        return SPELL_FAILED_ONLY_ABOVEWATER;
                }
                else if (m_spellInfo->SpellIconID == 156)   // Holy Shock
                {
                    Unit* target = m_targets.getUnitTarget();
                    if (!target)
                        return SPELL_FAILED_BAD_TARGETS;

                    // Prevents usage when cant neither attack or assist and not in front for shock attack
                    if (m_caster->CanAttack(target))
                    {
                        if (!m_caster->HasInArc(target))
                            return SPELL_FAILED_UNIT_NOT_INFRONT;
                    }
                    else if (!m_caster->CanAssistSpell(target, m_spellInfo))
                        return SPELL_FAILED_BAD_TARGETS;
                }
                break;
            }
            case SPELL_EFFECT_DISTRACT:                     // All nearby enemies must not be in combat
            {
                if (m_targets.m_targetMask & (TARGET_FLAG_DEST_LOCATION | TARGET_FLAG_SOURCE_LOCATION))
                {
                    UnitList targetsCombat;
                    float radius = GetSpellRadius(sSpellRadiusStore.LookupEntry(m_spellInfo->EffectRadiusIndex[i]));

                    FillAreaTargets(targetsCombat, radius, 0.f, PUSH_DEST_CENTER, SPELL_TARGETS_AOE_ATTACKABLE);

                    if (targetsCombat.empty())
                        break;

                    bool inCombat = true;
                    for (auto& itr : targetsCombat)
                    {
                        if (!itr->IsInCombat())
                        {
                            inCombat = false;
                            break;
                        }
                    }
                            
                    if (inCombat)
                        return SPELL_FAILED_TARGET_IN_COMBAT;
                }
                break;
            }
            case SPELL_EFFECT_SCHOOL_DAMAGE:
            {
                // Hammer of Wrath
                if (m_spellInfo->SpellVisual == 7250)
                {
                    if (!m_targets.getUnitTarget())
                        return SPELL_FAILED_BAD_IMPLICIT_TARGETS;

                    if (m_targets.getUnitTarget()->GetHealth() > m_targets.getUnitTarget()->GetMaxHealth() * 0.2)
                        return SPELL_FAILED_BAD_TARGETS;
                }
                break;
            }
            case SPELL_EFFECT_CREATE_ITEM:
            {
                if (Unit* target = m_targets.getUnitTarget())
                {
                    if (!target->IsPlayer() && m_spellInfo->EffectImplicitTargetA[i] != TARGET_UNIT_CASTER && m_spellInfo->EffectImplicitTargetB[i] != TARGET_UNIT_CASTER)
                        return SPELL_FAILED_BAD_TARGETS;

                    if (!target->IsPlayer())
                        target = m_caster;

                    if (i != EFFECT_INDEX_0) // TODO: Partial application
                        break;

                    uint32 count = CalculateSpellEffectValue(SpellEffectIndex(i), target);
                    ItemPosCountVec dest;
                    uint32 no_space = 0;
                    InventoryResult msg = static_cast<Player*>(target)->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, m_spellInfo->EffectItemType[i], count, &no_space);
                    if (msg != EQUIP_ERR_OK)
                        return SPELL_FAILED_TOO_MANY_OF_ITEM;
                }

                break;
            }
            case SPELL_EFFECT_TAMECREATURE:
            {
                // Spell can be triggered, we need to check original caster prior to caster
                Unit* caster = GetAffectiveCaster();
                Unit* tameTarget;

                bool gmmode = m_triggeredBySpellInfo == nullptr;
                if (gmmode)
                    tameTarget = caster->GetTarget();
                else
                    tameTarget = dynamic_cast<Unit*>(m_caster->GetChannelObject());

                if (!caster || caster->GetTypeId() != TYPEID_PLAYER ||
                        !tameTarget || tameTarget->GetTypeId() == TYPEID_PLAYER)
                    return SPELL_FAILED_BAD_TARGETS;

                Player* plrCaster = (Player*)caster;

                if (gmmode && !ChatHandler(plrCaster).FindCommand("npc tame"))
                {
                    plrCaster->SendPetTameFailure(PETTAME_UNKNOWNERROR);
                    return SPELL_FAILED_DONT_REPORT;
                }

                if (plrCaster->getClass() != CLASS_HUNTER && !gmmode)
                {
                    plrCaster->SendPetTameFailure(PETTAME_UNITSCANTTAME);
                    return SPELL_FAILED_DONT_REPORT;
                }

                Creature* target = (Creature*)tameTarget;

                if (target->IsPet() || target->HasCharmer())
                {
                    plrCaster->SendPetTameFailure(PETTAME_CREATUREALREADYOWNED);
                    return SPELL_FAILED_DONT_REPORT;
                }

                if (target->GetLevel() > plrCaster->GetLevel() && !gmmode)
                {
                    plrCaster->SendPetTameFailure(PETTAME_TOOHIGHLEVEL);
                    return SPELL_FAILED_DONT_REPORT;
                }

                if (!target->GetCreatureInfo()->isTameable())
                {
                    plrCaster->SendPetTameFailure(PETTAME_NOTTAMEABLE);
                    return SPELL_FAILED_DONT_REPORT;
                }

                if (plrCaster->GetPetGuid() || plrCaster->HasCharm())
                {
                    plrCaster->SendPetTameFailure(PETTAME_ANOTHERSUMMONACTIVE);
                    return SPELL_FAILED_DONT_REPORT;
                }

                break;
            }
            case SPELL_EFFECT_LEARN_SPELL:
            {
                if (m_spellInfo->EffectImplicitTargetA[i] != TARGET_UNIT_CASTER_PET)
                    break;

                Pet* pet = m_caster->GetPet();

                if (!pet)
                    return SPELL_FAILED_NO_PET;

                SpellEntry const* learn_spellproto = sSpellTemplate.LookupEntry<SpellEntry>(m_spellInfo->EffectTriggerSpell[i]);

                if (!learn_spellproto)
                    return SPELL_FAILED_NOT_KNOWN;

                if (!pet->CanTakeMoreActiveSpells(learn_spellproto->Id))
                    return SPELL_FAILED_TOO_MANY_SKILLS;

                if (learn_spellproto->spellLevel > pet->GetLevel())
                    return SPELL_FAILED_LOWLEVEL;

                if (!pet->HasTPForSpell(learn_spellproto->Id))
                    return SPELL_FAILED_TRAINING_POINTS;

                break;
            }
            case SPELL_EFFECT_LEARN_PET_SPELL:
            {
                Pet* pet = m_caster->GetPet();

                if (!pet)
                    return SPELL_FAILED_NO_PET;

                SpellEntry const* learn_spellproto = sSpellTemplate.LookupEntry<SpellEntry>(m_spellInfo->EffectTriggerSpell[i]);

                if (!learn_spellproto)
                    return SPELL_FAILED_NOT_KNOWN;

                if (!pet->CanTakeMoreActiveSpells(learn_spellproto->Id))
                    return SPELL_FAILED_TOO_MANY_SKILLS;

                if (learn_spellproto->spellLevel > pet->GetLevel())
                    return SPELL_FAILED_LOWLEVEL;

                if (!pet->HasTPForSpell(learn_spellproto->Id))
                    return SPELL_FAILED_TRAINING_POINTS;

                break;
            }
            case SPELL_EFFECT_FEED_PET:
            {
                if (m_caster->GetTypeId() != TYPEID_PLAYER)
                    return SPELL_FAILED_BAD_TARGETS;

                Item* foodItem = m_targets.getItemTarget();
                if (!foodItem)
                    return SPELL_FAILED_BAD_TARGETS;

                Pet* pet = m_caster->GetPet();

                if (!pet)
                    return SPELL_FAILED_NO_PET;

                if (!pet->HaveInDiet(foodItem->GetProto()))
                    return SPELL_FAILED_WRONG_PET_FOOD;

                if (!pet->GetCurrentFoodBenefitLevel(foodItem->GetProto()->ItemLevel))
                    return SPELL_FAILED_FOOD_LOWLEVEL;

                if (pet->IsInCombat())
                    return SPELL_FAILED_AFFECTING_COMBAT;

                break;
            }
            case SPELL_EFFECT_POWER_BURN:
            case SPELL_EFFECT_POWER_DRAIN:
            {
                // Can be area effect, Check only for players and not check if target - caster (spell can have multiply drain/burn effects)
                if (m_caster->GetTypeId() == TYPEID_PLAYER)
                    if (Unit* target = m_targets.getUnitTarget())
                        if (target != m_caster && int32(target->GetPowerType()) != m_spellInfo->EffectMiscValue[i])
                            return SPELL_FAILED_BAD_TARGETS;
                break;
            }
            case SPELL_EFFECT_CHARGE:
            {
                if (!m_ignoreRoot && m_caster->hasUnitState(UNIT_STAT_ROOT))
                    return SPELL_FAILED_ROOTED;

                if (Unit* target = m_targets.getUnitTarget())
                {
                    float range = GetSpellMaxRange(sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex));

                    Position pos;
                    target->GetFirstCollisionPosition(pos, target->GetCombatReach(), target->GetAngle(m_caster));

                    // TODO: Implement jumpin case check
                    //if (!m_caster->m_movementInfo.HasMovementFlag(MovementFlags(MOVEFLAG_FALLING | MOVEFLAG_FALLINGFAR)) && (pos.coord_z < m_caster->GetPositionZ()) && (fabs(pos.coord_z - m_caster->GetPositionZ()) < 3.0f))
                    //{
                    PathFinder pathFinder(m_caster);
                    pathFinder.setPathLengthLimit(range * 1.5f);
                    bool result = pathFinder.calculate(pos.x, pos.y, pos.z);

                    if (pathFinder.getPathType() & PATHFIND_SHORT)
                        return SPELL_FAILED_OUT_OF_RANGE;
                    if (!result || pathFinder.getPathType() & PATHFIND_NOPATH)
                        return SPELL_FAILED_NOPATH;
                    //}

                }
                break;
            }
            case SPELL_EFFECT_SKINNING:
            {
                if (m_caster->GetTypeId() != TYPEID_PLAYER || !m_targets.getUnitTarget() || m_targets.getUnitTarget()->GetTypeId() != TYPEID_UNIT)
                    return SPELL_FAILED_BAD_TARGETS;

                if (!m_targets.getUnitTarget()->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE))
                    return SPELL_FAILED_TARGET_UNSKINNABLE;

                Creature* creature = (Creature*)m_targets.getUnitTarget();

                if (creature->IsAlive())
                    return SPELL_FAILED_TARGET_NOT_DEAD;

                if (creature->GetLootStatus() != CREATURE_LOOT_STATUS_LOOTED)// || creature->GetCreatureType() != CREATURE_TYPE_CRITTER)
                    return SPELL_FAILED_TARGET_NOT_LOOTED;

                uint32 skill = creature->GetCreatureInfo()->GetRequiredLootSkill();

                int32 skillValue = ((Player*)m_caster)->GetSkillValue(skill);
                int32 TargetLevel = m_targets.getUnitTarget()->GetLevel();
                int32 ReqValue = (skillValue < 100 ? (TargetLevel - 10) * 10 : TargetLevel * 5);
                if (ReqValue > skillValue)
                    return SPELL_FAILED_LOW_CASTLEVEL;

                // chance for fail at orange skinning attempt
                if (!strict && skillValue < sWorld.GetConfigMaxSkillValue() && (ReqValue < 0 ? 0 : ReqValue) > irand(skillValue - 25, skillValue + 37))
                    return SPELL_FAILED_TRY_AGAIN;

                break;
            }
            case SPELL_EFFECT_OPEN_LOCK_ITEM:
            case SPELL_EFFECT_OPEN_LOCK:
            {
                if (m_caster->GetTypeId() != TYPEID_PLAYER) // only players can open locks, gather etc.
                    return SPELL_FAILED_BAD_TARGETS;

                // we need a go target in case of TARGET_GAMEOBJECT (for other targets acceptable GO and items)
                if (m_spellInfo->EffectImplicitTargetA[i] == TARGET_GAMEOBJECT)
                {
                    if (!m_targets.getGOTarget())
                        return SPELL_FAILED_BAD_TARGETS;
                }

                // get the lock entry
                uint32 lockId;
                if (GameObject* go = m_targets.getGOTarget())
                {
                    // In BattleGround players can use only flags and banners
                    if (((Player*)m_caster)->InBattleGround() &&
                            !((Player*)m_caster)->CanUseBattleGroundObject())
                        return SPELL_FAILED_TRY_AGAIN;

                    lockId = go->GetGOInfo()->GetLockId();
                    if (!lockId)
                        return SPELL_FAILED_ALREADY_OPEN;

                    // check if its in use only when cast is finished (called from spell::cast() with strict = false)
                    if (!strict && go->IsInUse())
                        return SPELL_FAILED_CHEST_IN_USE;

                    if (!strict && go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_IN_USE))
                        return SPELL_FAILED_CHEST_IN_USE;

                    // done in client but we need to recheck anyway
                    if (go->GetGOInfo()->CannotBeUsedUnderImmunity() && m_caster->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE))
                        return SPELL_FAILED_DAMAGE_IMMUNE;
                }
                else if (Item* item = m_targets.getItemTarget())
                {
                    // not own (trade?)
                    if (item->GetOwner() != m_caster)
                        return SPELL_FAILED_ITEM_GONE;

                    lockId = item->GetProto()->LockID;

                    // if already unlocked
                    if (!lockId || item->HasFlag(ITEM_FIELD_FLAGS, ITEM_DYNFLAG_UNLOCKED))
                        return SPELL_FAILED_ALREADY_OPEN;
                }
                else
                    return SPELL_FAILED_BAD_TARGETS;

                // check lock compatibility
                SpellEffectIndex effIdx = SpellEffectIndex(i);
                SpellCastResult res = CanOpenLock(effIdx, lockId, m_effectSkillInfo[effIdx].skillId, m_effectSkillInfo[effIdx].reqSkillValue, m_effectSkillInfo[effIdx].skillValue);
                if (res != SPELL_CAST_OK)
                    return res;

                // chance for fail at orange mining/herb/LockPicking gathering attempt
                // second check prevent fail at rechecks
                if (!strict && m_effectSkillInfo[effIdx].skillId != SKILL_NONE)
                {
                    bool canFailAtMax = m_effectSkillInfo[effIdx].skillId != SKILL_HERBALISM && m_effectSkillInfo[effIdx].skillId != SKILL_MINING;

                    // chance for failure in orange gather / lockpick (gathering skill can't fail at maxskill)
                    if ((canFailAtMax || m_effectSkillInfo[effIdx].skillValue < sWorld.GetConfigMaxSkillValue())
                        && m_effectSkillInfo[effIdx].reqSkillValue > irand(m_effectSkillInfo[effIdx].skillValue - 25, m_effectSkillInfo[effIdx].skillValue + 37))
                        return SPELL_FAILED_TRY_AGAIN;
                }
                if (m_CastItem)
                    m_effectSkillInfo[effIdx].skillId = SKILL_NONE;
                break;
            }
            case SPELL_EFFECT_PICKPOCKET:
            {
                // should always fail above if not exists
                Unit* target = m_targets.getUnitTarget();
                if (!target->IsCreature())
                    return SPELL_FAILED_BAD_TARGETS;

                Creature* creatureTarget = static_cast<Creature*>(target);
                if (!creatureTarget->GetCreatureInfo()->PickpocketLootId)
                    return SPELL_FAILED_TARGET_NO_POCKETS;
                break;
            }
            case SPELL_EFFECT_SUMMON_DEAD_PET:
            {
                Creature* pet = m_caster->GetPet();
                if (!pet)
                {
                    SpellCastResult result = Pet::TryLoadFromDB(m_caster);
                    if (result == SPELL_FAILED_NO_PET)
                        return SPELL_FAILED_NO_PET;

                    if (result == SPELL_CAST_OK)
                    {
                        ((Player*)m_caster)->SendPetTameFailure(PETTAME_NOTDEAD);
                        return SPELL_FAILED_DONT_REPORT;
                    }

                    if (result != SPELL_FAILED_TARGETS_DEAD)
                        return SPELL_FAILED_UNKNOWN;
                }
                else if (pet->IsAlive())
                    return SPELL_FAILED_ALREADY_HAVE_SUMMON;

                break;
            }
            // This is generic summon effect now and don't make this check for summon types similar
            // SPELL_EFFECT_SUMMON_CRITTER, SPELL_EFFECT_SUMMON_WILD or SPELL_EFFECT_SUMMON_GUARDIAN.
            // These won't show up in m_caster->GetPetGUID()
            case SPELL_EFFECT_SUMMON:
            {
                if (SummonPropertiesEntry const* summon_prop = sSummonPropertiesStore.LookupEntry(m_spellInfo->EffectMiscValueB[i]))
                {
                    if (summon_prop->Group == SUMMON_PROP_GROUP_PETS && m_caster)
                    {
                        if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX_DISMISS_PET_FIRST) && m_caster->GetPetGuid())
                            return SPELL_FAILED_ALREADY_HAVE_SUMMON;

                        if (m_caster->HasCharm())
                            return SPELL_FAILED_ALREADY_HAVE_CHARM;
                    }
                }

                if (m_spellInfo->HasAttribute(SPELL_ATTR_EX_DISMISS_PET_FIRST))
                    if (m_caster->FindGuardianWithEntry(m_spellInfo->EffectMiscValue[i]))
                        return SPELL_FAILED_ALREADY_HAVE_SUMMON;

                break;
            }
            case SPELL_EFFECT_SUMMON_PET:
            {
                if (m_caster->HasCharm())
                    return SPELL_FAILED_ALREADY_HAVE_CHARM;

                uint32 plClass = m_caster->getClass();
                if (plClass == CLASS_HUNTER)
                {
                    if (Creature* pet = m_caster->GetPet())
                    {
                        if (!pet->IsAlive())
                        {
                            ((Player*)m_caster)->SendPetTameFailure(PETTAME_DEAD);
                            return SPELL_FAILED_DONT_REPORT;
                        }
                        else if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX_DISMISS_PET_FIRST))
                            return SPELL_FAILED_ALREADY_HAVE_SUMMON;
                    }
                    SpellCastResult result = Pet::TryLoadFromDB(m_caster);
                    if (result == SPELL_FAILED_TARGETS_DEAD)
                    {
                        ((Player*)m_caster)->SendPetTameFailure(PETTAME_DEAD);
                        return SPELL_FAILED_DONT_REPORT;
                    }
                    if (result != SPELL_CAST_OK)
                        return result;
                }
                else if (m_caster->GetPetGuid())
                {
                    if (plClass == CLASS_WARLOCK)                  // let warlock do a replacement summon
                    {
                        if (strict)     // Summoning Disorientation, trigger pet stun (cast by pet so it doesn't attack player)
                            if (Pet* pet = ((Player*)m_caster)->GetPet())
                                pet->CastSpell(pet, 32752, TRIGGERED_OLD_TRIGGERED, nullptr, nullptr, pet->GetObjectGuid());
                    }
                    else if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX_DISMISS_PET_FIRST))
                        return SPELL_FAILED_ALREADY_HAVE_SUMMON;
                }

                break;
            }
            case SPELL_EFFECT_TRIGGER_SPELL_2:
            {
                if (m_caster->GetTypeId() != TYPEID_PLAYER)
                    return SPELL_FAILED_BAD_TARGETS;

                Player* caster = static_cast<Player*>(m_caster);
                if (!caster->GetSelectionGuid())
                    return SPELL_FAILED_BAD_TARGETS;

                Player* target = sObjectMgr.GetPlayer(caster->GetSelectionGuid());
                if (!target || caster == target || !target->IsInGroup(m_caster))
                    return SPELL_FAILED_BAD_TARGETS;

#ifdef PRENERF_2_3
                // pre 2.4 - could not summon to netherstorm
                if (m_caster->GetZoneId() == 3523)
                    return SPELL_FAILED_FIZZLE;

                uint32 mapId = m_caster->GetMapId();
                const MapEntry* map = sMapStore.LookupEntry(mapId);
                if (map->IsDungeon())
                {
                    // pre 2.4 - could not summon to instance
                    if (target->GetMapId() != mapId)
                        return SPELL_FAILED_TARGET_NOT_IN_INSTANCE;
                }
                break;
#endif
            }
            case SPELL_EFFECT_SUMMON_PLAYER:
            {
                if (m_caster->GetTypeId() != TYPEID_PLAYER)
                    return SPELL_FAILED_BAD_TARGETS;
                Player* caster = static_cast<Player*>(m_caster);
                if (!caster->GetSelectionGuid())
                    return SPELL_FAILED_BAD_TARGETS;

                Player* target = sObjectMgr.GetPlayer(caster->GetSelectionGuid());
                if (!target || caster == target || !target->IsInGroup(m_caster))
                    return SPELL_FAILED_BAD_TARGETS;

                // check if our map is dungeon
                uint32 mapId = m_caster->GetMapId();
                const MapEntry* map = sMapStore.LookupEntry(mapId);
                if (map->IsDungeon())
                {
#ifdef PRENERF_2_3
                    // pre 2.4 - could not summon to instance
                    if (target->GetMapId() != mapId)
                        return SPELL_FAILED_TARGET_NOT_IN_INSTANCE;
#endif

                    InstanceTemplate const* instance = ObjectMgr::GetInstanceTemplate(mapId);
                    if (!instance)
                        return SPELL_FAILED_TARGET_NOT_IN_INSTANCE;
                    if (instance->levelMin > target->GetLevel())
                        return SPELL_FAILED_LOWLEVEL;
                    if (instance->levelMax && instance->levelMax < target->GetLevel())
                        return SPELL_FAILED_HIGHLEVEL;

                    Difficulty difficulty = m_caster->GetMap()->GetDifficulty();
                    if (InstancePlayerBind* targetBind = target->GetBoundInstance(mapId, difficulty))
                        if (InstancePlayerBind* casterBind = caster->GetBoundInstance(mapId, difficulty))
                            if (targetBind->perm && targetBind->state != casterBind->state)
                                return SPELL_FAILED_TARGET_LOCKED_TO_RAID_INSTANCE;

                    if (AreaTrigger const* at = sObjectMgr.GetMapEntranceTrigger(mapId))
                    {
                        uint32 miscRequirement;
                        if (AREA_LOCKSTATUS_OK != target->GetAreaTriggerLockStatus(at, miscRequirement))
                            return SPELL_FAILED_BAD_TARGETS; // TODO: Verify this result
                    }
                }
                break;
            }
            case SPELL_EFFECT_LEAP:
            case SPELL_EFFECT_TELEPORT_UNITS_FACE_CASTER:
            {
                if (!m_caster || m_caster->IsTaxiFlying())
                    return SPELL_FAILED_NOT_ON_TAXI;

                if (!m_ignoreRoot && m_caster->hasUnitState(UNIT_STAT_ROOT))
                    return SPELL_FAILED_ROOTED;

                if (m_caster->GetTypeId() == TYPEID_PLAYER)
                {
                    if (((Player*)m_caster)->HasMovementFlag(MOVEFLAG_ONTRANSPORT))
                        return SPELL_FAILED_NOT_ON_TRANSPORT;

                    // not allow use this effect at battleground until battleground start
                    if (BattleGround const* bg = ((Player*)m_caster)->GetBattleGround())
                        if (bg->GetStatus() != STATUS_IN_PROGRESS)
                            return SPELL_FAILED_TRY_AGAIN;
                }

                break;
            }
            case SPELL_EFFECT_STEAL_BENEFICIAL_BUFF:
            {
                if (m_targets.getUnitTarget() == m_caster)
                    return SPELL_FAILED_BAD_TARGETS;
                break;
            }
            case SPELL_EFFECT_SUMMON_RAF_FRIEND:
            {
                if (m_caster->GetTypeId() != TYPEID_PLAYER)
                    return SPELL_FAILED_BAD_TARGETS;

                Player* caster = static_cast<Player*>(m_caster);
                if (!caster->GetSession()->GetRecruitingFriendId())
                    return SPELL_FAILED_BAD_TARGETS;
                break;
            }
            case SPELL_EFFECT_CREATE_PET:
            {
                if (m_targets.getUnitTarget())
                {
                    if (m_targets.getUnitTarget()->GetTypeId() != TYPEID_PLAYER)
                        return SPELL_FAILED_BAD_TARGETS;
                    if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX_DISMISS_PET_FIRST) && m_targets.getUnitTarget()->GetPetGuid())
                        return SPELL_FAILED_ALREADY_HAVE_SUMMON;
                }
                break;
            }
            default: break;
        }
    }
    //检测光环类技能的 一些特殊情况
    for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i)
    {
        // Do not check in case of junk in DBC
        if (!IsAuraApplyEffect(m_spellInfo, SpellEffectIndex(i)))
            continue;

        // Possible Unit-target for the spell
        Unit* expectedTarget = GetPrefilledUnitTargetOrUnitTarget(SpellEffectIndex(i));

        switch (m_spellInfo->EffectApplyAuraName[i])
        {
            case SPELL_AURA_MOD_POSSESS:
            {
                if (!m_trueCaster->IsPlayer())
                    return SPELL_FAILED_UNKNOWN;

                if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX_DISMISS_PET_FIRST) && m_caster->HasCharm())
                    return SPELL_FAILED_ALREADY_HAVE_CHARM;

                if (m_caster->HasCharmer())
                    return SPELL_FAILED_CHARMED;

                if (expectedTarget) // target may not be known at CheckCast time - TODO: add support for these checks on CheckTarget
                {
                    if (expectedTarget == m_caster)
                        return SPELL_FAILED_BAD_TARGETS;

                    if (expectedTarget->HasCharmer())
                        return SPELL_FAILED_CHARMED;

                    if (int32(expectedTarget->GetLevel()) > CalculateSpellEffectValue(SpellEffectIndex(i), expectedTarget))
                        return SPELL_FAILED_HIGHLEVEL;

                    if (expectedTarget->GetOwner())
                        return expectedTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) ? SPELL_FAILED_TARGET_IS_PLAYER_CONTROLLED : SPELL_FAILED_CANT_BE_CHARMED;
                }
                break;
            }
            case SPELL_AURA_MOD_CHARM:
            {
                if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX_DISMISS_PET_FIRST) && m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->HasCharm())
                    return SPELL_FAILED_ALREADY_HAVE_CHARM;

                if (m_caster->HasCharmer())
                    return SPELL_FAILED_CHARMED;

                if (expectedTarget) // target may not be known at CheckCast time - TODO: add support for these checks on CheckTarget
                {
                    if (expectedTarget == m_caster)
                        return SPELL_FAILED_BAD_TARGETS;

                    if (expectedTarget->HasCharmer())
                        return SPELL_FAILED_CHARMED;

                    if (int32(expectedTarget->GetLevel()) > CalculateSpellEffectValue(SpellEffectIndex(i), expectedTarget))
                        return SPELL_FAILED_HIGHLEVEL;

                    if (expectedTarget->GetOwner())
                        return expectedTarget->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) ? SPELL_FAILED_TARGET_IS_PLAYER_CONTROLLED : SPELL_FAILED_CANT_BE_CHARMED;
                }
                break;
            }
            case SPELL_AURA_MOD_POSSESS_PET:
            {
                if (!m_trueCaster->IsPlayer())
                    return SPELL_FAILED_UNKNOWN;

                if (m_caster->HasCharm())
                    return SPELL_FAILED_ALREADY_HAVE_CHARM;

                if (m_caster->HasCharmer())
                    return SPELL_FAILED_CHARMED;

                Pet* pet = m_caster->GetPet();
                if (!pet)
                    return SPELL_FAILED_NO_PET;

                if (pet->HasCharmer())
                    return SPELL_FAILED_CHARMED;

                break;
            }
            case SPELL_AURA_AOE_CHARM:
            {
                if (m_caster->HasCharmer())
                    return SPELL_FAILED_CHARMED;

                break;
            }
            case SPELL_AURA_MOD_CONFUSE:
            case SPELL_AURA_MOD_FEAR:
            case SPELL_AURA_MOD_STUN:
            {
                if (expectedTarget)
                {
                    // flying players on mounts are immune to cc
                    if (expectedTarget->IsMounted() && expectedTarget->IsFlying() && expectedTarget->IsPlayerControlled() && expectedTarget->IsAboveGround())
                    {
                        if (m_spellInfo->Mechanic)
                            return SPELL_FAILED_IMMUNE;

                        if (m_spellInfo->EffectMechanic[i])
                        {
                            SpellCastResult result = partialApplication(i);
                            if (result != SPELL_CAST_OK)
                                return SPELL_FAILED_IMMUNE;
                        }
                    }
                }
                break;
            }
            case SPELL_AURA_MOUNTED:
            {
                if (m_caster->IsInWater() && (m_caster->GetTypeId() != TYPEID_PLAYER || static_cast<Player*>(m_caster)->IsInHighLiquid()))
                    return SPELL_FAILED_ONLY_ABOVEWATER;

                if (m_caster->GetTypeId() == TYPEID_PLAYER && ((Player*)m_caster)->GetTransport())
                    return SPELL_FAILED_NO_MOUNTS_ALLOWED;

                // Ignore map check if spell have AreaId. AreaId already checked and this prevent special mount spells
                if (m_caster->GetTypeId() == TYPEID_PLAYER &&
                        !m_IsTriggeredSpell &&
                        !m_spellInfo->AreaId &&
                        (m_caster->GetMap() && !m_caster->GetMap()->IsMountAllowed()))
                {
                    return SPELL_FAILED_NO_MOUNTS_ALLOWED;
                }

                if (m_caster->GetAreaId() == 35)
                    return SPELL_FAILED_NO_MOUNTS_ALLOWED;

                if (m_caster->IsInDisallowedMountForm())
                    return SPELL_FAILED_NOT_SHAPESHIFT;

                break;
            }
            case SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS:
            {
                if (!expectedTarget)
                    return SPELL_FAILED_BAD_IMPLICIT_TARGETS;

                // can be casted at non-friendly unit or own pet/charm
                if (!m_caster->CanAttack(expectedTarget))
                    return SPELL_FAILED_TARGET_FRIENDLY;

                break;
            }
            case SPELL_AURA_PERIODIC_MANA_LEECH:
            {
                if (expectedTarget)
                {
                    if (expectedTarget->GetPowerType() != POWER_MANA)
                    {
                        SpellCastResult result = partialApplication(i);
                        if (result != SPELL_CAST_OK)
                            return result;
                    }
                }

                break;
            }
            case SPELL_AURA_WATER_WALK:
            {
                if (expectedTarget && expectedTarget->GetTypeId() == TYPEID_PLAYER)
                {
                    Player const* player = static_cast<Player const*>(expectedTarget);

                    // Player is not allowed to cast water walk on shapeshifted/mounted player
                    if (player->IsShapeShifted() || player->IsMounted())
                        return SPELL_FAILED_BAD_TARGETS;
                }

                break;
            }
            case SPELL_AURA_MIRROR_IMAGE:
            {
                if (expectedTarget)
                {
                    // Target must be creature. TODO: Check if target can also be player
                    if (expectedTarget->GetTypeId() != TYPEID_UNIT)
                        return SPELL_FAILED_BAD_TARGETS;

                    if (expectedTarget == m_caster)             // Clone self can't be accepted
                        return SPELL_FAILED_BAD_TARGETS;

                    // It is assumed that target can not be cloned if already cloned by same or other clone auras
                    if (expectedTarget->HasAuraType(SPELL_AURA_MIRROR_IMAGE))
                        return SPELL_FAILED_BAD_TARGETS;
                }
                break;
            }
            case SPELL_AURA_MOD_DISARM: // supports partial application
            {
                if (expectedTarget)
                {
                    // Target must be a weapon wielder
                    if (!expectedTarget->hasMainhandWeapon())
                    {
                        SpellCastResult result = partialApplication(i);
                        if (result != SPELL_CAST_OK)
                            return SPELL_FAILED_TARGET_NO_WEAPONS;
                    }
                }
                break;
            }
            default:
                break;
        }
    }

    // 如果技能是交易物品技能的话 检查一些特殊情况
    if (m_targets.m_targetMask & TARGET_FLAG_TRADE_ITEM)
    {
        if (!m_trueCaster->IsPlayer())
            return SPELL_FAILED_NOT_TRADING;

        if (TradeSlots(m_targets.getItemTargetGuid().GetRawValue()) != TRADE_SLOT_NONTRADED)
            return SPELL_FAILED_ITEM_NOT_READY;

        // if trade not complete then remember it in trade data
        if (TradeData* my_trade = static_cast<Player*>(m_caster)->GetTradeData())
        {
            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);
                return SPELL_FAILED_DONT_REPORT;
            }
        }
        else
            return SPELL_FAILED_NOT_TRADING;
    }
    //取回宠物技能  检测
    if (m_trueCaster->IsPlayer() && m_spellInfo->HasAttribute(SPELL_ATTR_EX2_NO_ACTIVE_PETS))
    {
        Player* player = static_cast<Player*>(m_caster);
        if (player->GetPetGuid() || player->HasCharm())
        {
            player->SendPetTameFailure(PETTAME_ANOTHERSUMMONACTIVE);
            return SPELL_FAILED_DONT_REPORT;
        }
        SpellCastResult result = Pet::TryLoadFromDB(player);
        if (result == SPELL_FAILED_TARGETS_DEAD || result == SPELL_CAST_OK)//???这里应该由问题 result != SPELL_CAST_OK 才对
        {
            player->SendPetTameFailure(PETTAME_ANOTHERSUMMONACTIVE);
            return SPELL_FAILED_DONT_REPORT;
        }
    }

    // 最后一些特定技能检测
    return OnCheckCast(strict);
}

 上面检测完毕后 就会来到施放函数 需要注意的是持续技能 或者飞行延迟技能因为添加事件 走的是update函数 这里是不处理的

void Spell::Prepare()
{
    m_spellState = SPELL_STATE_CASTING;

    // Prepare data for triggers
    prepareDataForTriggerSystem();

    // calculate cast time (calculated after first CheckCast check to prevent charge counting for first CheckCast fail)
    if (!m_ignoreCastTime)
    {
        SpellModRAII spellModController(this, m_trueCaster->GetSpellModOwner(), false, true);
        m_casttime = GetSpellCastTime(m_spellInfo, m_trueCaster, this, true);
    }

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

    if (!m_IsTriggeredSpell && !m_trueCaster->IsGameObject())
    {
        if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX2_NOT_AN_ACTION))
            m_caster->RemoveAurasOnCast(AURA_INTERRUPT_FLAG_ACTION, m_spellInfo);

        // Orientation changes inside
        if (m_notifyAI && m_caster->AI())
            m_caster->AI()->OnSpellCastStateChange(this, true, m_targets.getUnitTarget());
    }

    m_castPositionX = m_trueCaster->GetPositionX();
    m_castPositionY = m_trueCaster->GetPositionY();
    m_castPositionZ = m_trueCaster->GetPositionZ();
    m_castOrientation = m_trueCaster->GetOrientation();

    OnSuccessfulStart();

    // add non-triggered (with cast time and without)
    if (!m_IsTriggeredSpell)
    {
        if (!m_trueCaster->IsGameObject())
        {
            // add to cast type slot
            if ((!m_ignoreConcurrentCasts || IsChanneledSpell(m_spellInfo)) && !m_triggerAutorepeat)
                m_caster->SetCurrentCastedSpell(this);

            // add gcd server side (client side is handled by client itself)
            m_caster->AddGCD(*m_spellInfo);
        }

        // will show cast bar
        SendSpellStart();

        // Execute instant spells immediate
        if (m_timer == 0 && !IsNextMeleeSwingSpell(m_spellInfo) && (!IsAutoRepeat() || m_triggerAutorepeat))
            cast();
    }
    // execute triggered without cast time explicitly in call point
    else
    {
        // Channeled spell is always one per caster and needs to be tracked and removed on death
        if (IsChanneledSpell(m_spellInfo)) // GO casters cant cast channeled spells
            m_caster->SetCurrentCastedSpell(this);

        if (m_timer == 0)
            cast(true);
    }
    // else triggered with cast time will execute execute at next tick or later
    // without adding to cast type slot
    // will not show cast bar but will show effects at casting time etc
}

这里的cast函数就是施法具体处理函数  参数为trrue就是不需要再进行检测了

SpellCastResult Spell::cast(bool skipCheck)
{
    //设置函数执行
    SetExecutedCurrently(true);
    SpellModRAII spellModController(this, m_trueCaster->GetSpellModOwner());
    //检测光环 或者其他法术挂载是否已满
    if (!m_trueCaster->CheckAndIncreaseCastCounter())
    {
        if (m_triggeredByAuraSpell)
            sLog.outError("Spell %u triggered by aura spell %u too deep in cast chain for cast. Cast not allowed for prevent overflow stack crash.", m_spellInfo->Id, m_triggeredByAuraSpell->Id);
        else
            sLog.outError("Spell %u too deep in cast chain for cast. Cast not allowed for prevent overflow stack crash.", m_spellInfo->Id);

        SendCastResult(SPELL_FAILED_ERROR);
        finish(false);
        SetExecutedCurrently(false);
        return SPELL_FAILED_ERROR;
    }

    // 更新施法者 是否在世界中 和施法目标最新位置
    UpdatePointers();

    // 丢失目标或者其他情况下 取消施法
    if (!m_targets.getUnitTarget() && m_targets.getUnitTargetGuid() && m_targets.getUnitTargetGuid() != m_trueCaster->GetObjectGuid())
    {
        cancel();
        m_trueCaster->DecreaseCastCounter();
        SetExecutedCurrently(false);
        return SPELL_FAILED_ERROR;
    }
    //如果施法者目标没有面对目标 修改施法者面对目标
    if (m_trueCaster->IsCreature() && m_targets.getUnitTarget() && m_targets.getUnitTarget() != m_caster && !m_spellInfo->HasAttribute(SPELL_ATTR_EX5_AI_DOESNT_FACE_TARGET))
    {
        Unit* charmer = m_caster->GetCharmer();
        if (charmer && !(charmer->GetTypeId() == TYPEID_PLAYER && ((Player*)charmer)->GetCamera().GetBody() == m_caster)) // need to check if target doesnt have a player controlling it
            m_caster->SetInFront(m_targets.getUnitTarget());
    }

    // 检测各种施法条件
    SpellCastResult castResult = SPELL_CAST_OK;
    if (!skipCheck)
    {
        castResult = CheckCast(false);
        if (castResult != SPELL_CAST_OK)
        {
            StopCast(castResult);
            return castResult;
        }
    }

    // 非触发法术 例如法师 急冷技能 需要添加到特殊队列 m_preCastSpells 里面处理
    switch (m_spellInfo->SpellFamilyName)
    {
        case SPELLFAMILY_GENERIC:
        {
            // Bandages
            if (m_spellInfo->Mechanic == MECHANIC_BANDAGE)
                AddPrecastSpell(11196);                     // Recently Bandaged
            // Blood Fury (Racial)
            else if (m_spellInfo->SpellIconID == 1662 && m_spellInfo->AttributesEx & 0x20)
                AddPrecastSpell(23230);                     // Blood Fury - Healing Reduction
            // Weak Alcohol
            else if (m_spellInfo->SpellIconID == 1306 && m_spellInfo->SpellVisual == 11359)
                AddTriggeredSpell(51655);                   // BOTM - Create Empty Brew Bottle
            break;
        }
        case SPELLFAMILY_MAGE:
        {
            // Ice Block
            if (m_spellInfo->CasterAuraStateNot == AURA_STATE_HYPOTHERMIA)
                AddPrecastSpell(41425);                     // Hypothermia
            break;
        }
        case SPELLFAMILY_WARRIOR:
            break;
        case SPELLFAMILY_PRIEST:
        {
            // Power Word: Shield
            if (m_spellInfo->CasterAuraStateNot == AURA_STATE_WEAKENED_SOUL || m_spellInfo->TargetAuraStateNot == AURA_STATE_WEAKENED_SOUL)
                AddPrecastSpell(6788);                      // Weakened Soul

            switch (m_spellInfo->Id)
            {
                case 15237: AddTriggeredSpell(23455); break;// Holy Nova, rank 1
                case 15430: AddTriggeredSpell(23458); break;// Holy Nova, rank 2
                case 15431: AddTriggeredSpell(23459); break;// Holy Nova, rank 3
                case 27799: AddTriggeredSpell(27803); break;// Holy Nova, rank 4
                case 27800: AddTriggeredSpell(27804); break;// Holy Nova, rank 5
                case 27801: AddTriggeredSpell(27805); break;// Holy Nova, rank 6
                case 25331: AddTriggeredSpell(25329); break;// Holy Nova, rank 7
                default: break;
            }
            break;
        }
        case SPELLFAMILY_PALADIN:
        {
            // Divine Shield, Divine Protection, Blessing of Protection or Avenging Wrath
            if (m_spellInfo->CasterAuraStateNot == AURA_STATE_FORBEARANCE || m_spellInfo->TargetAuraStateNot == AURA_STATE_FORBEARANCE)
                AddPrecastSpell(25771);                     // Forbearance
            break;
        }
        default:
            break;
    }

    // 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();

#ifdef BUILD_ELUNA
    // used by eluna
    if (m_caster)
    {
        if (m_caster->GetTypeId() == TYPEID_PLAYER)
            sEluna->OnSpellCast(m_caster->ToPlayer(), this, skipCheck);
    }
#endif
    //计算法术的持续事件
    m_duration = CalculateSpellDuration(m_spellInfo, m_caster, nullptr, m_auraScript);
    //从地图中填充目标
    FillTargetMap();

    if (m_spellState == SPELL_STATE_FINISHED)               // stop cast if spell marked as finish somewhere in FillTargetMap
    {
        m_trueCaster->DecreaseCastCounter();
        SetExecutedCurrently(false);
        return SPELL_FAILED_ERROR; // currently not propagating right error here but it should not be needed
    }

    spellModController.SetSuccess();

    if (Unit* unitCaster = dynamic_cast<Unit*>(m_trueCaster))
        if (m_spellInfo->HasAttribute(SPELL_ATTR_EX_DISMISS_PET_FIRST))
            if (Pet* pet = unitCaster->GetPet())
                pet->Unsummon(PET_SAVE_NOT_IN_SLOT, unitCaster);

    // 设置法术冷却时间
    SendSpellCooldown();
    if (m_notifyAI && m_caster && m_caster->AI())
        m_caster->AI()->OnSpellCooldownAdded(m_spellInfo);
    //扣除施法消耗
    TakePower();
    //扣除施法材料消耗
    TakeReagents();                                         // we must remove reagents before HandleEffects to allow place crafted item in same slot
   //扣除武器等耐久度
    TakeAmmo();
    //发送给玩家施法结果 
    SendCastResult(castResult);
   //广播
    SendSpellGo();                                          // we must send smsg_spell_go packet before m_castItem delete in TakeCastItem()...
    //查询伤害是否针对多个目标 并且初始化对应法术效果的掩码
    InitializeDamageMultipliers();

    OnCast();

    if (!m_IsTriggeredSpell && !m_trueCaster->IsGameObject() && !m_spellInfo->HasAttribute(SPELL_ATTR_EX2_NOT_AN_ACTION))
        m_caster->RemoveAurasOnCast(AURA_INTERRUPT_FLAG_ACTION_LATE, m_spellInfo);

    // 根据上面计算出的目标 处理 仇恨 物品触发对象 以及激活法术的各种额外特殊效果  
    _handle_immediate_phase();

    Unit* procTarget = m_targets.getUnitTarget();
    if (!procTarget)
        procTarget = m_caster;

    // 根据FillTargetMap函数里面  计算出是飞行法术 延时法术 初始化一些数据 
    if (IsDelayedSpell())
    {
        // For channels, delay starts at channel end
        if (m_spellState != SPELL_STATE_CHANNELING)
        {
            // Okay, maps created, now prepare flags
            m_spellState = SPELL_STATE_TRAVELING;
            SetDelayStart(0);
            SetSpellStartTravelling(m_caster->GetMap()->GetCurrentMSTime());

            if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX_NO_THREAT) &&
                !m_spellInfo->HasAttribute(SPELL_ATTR_EX_THREAT_ONLY_ON_MISS) &&
                !m_spellInfo->HasAttribute(SPELL_ATTR_EX2_NO_INITIAL_THREAT)) // attribute checks
            {
                if (m_caster && m_caster->IsPlayerControlled()) // only player casters
                {
                    if (Unit* target = m_targets.getUnitTarget())
                    {
                        for (auto const& ihit : m_UniqueTargetInfo)
                        {
                            if (target->GetObjectGuid() == ihit.targetGUID)                 // Found in list
                            {
                                if (m_caster->CanAttack(target)) // can attack
                                    if ((!IsPositiveEffectMask(m_spellInfo, ihit.effectMask, m_caster, target)
                                        && m_caster->IsVisibleForOrDetect(target, target, false)
                                        && m_caster->CanEnterCombat() && target->CanEnterCombat())) // can see and enter combat
                                    {
                                        m_caster->SetInCombatWithVictim(target);
                                        m_caster->GetCombatManager().TriggerCombatTimer(uint32(ihit.timeDelay + 500));
                                    }
                                break;
                            }
                        }
                    }
                }
            }
        }

        // on spell cast end proc,
        // critical hit related part is currently done on hit so proc there,
        // 0 damage since any damage based procs should be on hit
        // 0 victim proc since there is no victim proc dependent on successfull cast for caster
        Unit::ProcDamageAndSpell(ProcSystemArguments(m_caster, procTarget, PROC_EX_NORMAL_HIT, 0, PROC_EX_CAST_END, 0, m_attackType, m_spellInfo));
    }
    else // Immediate spell, no big deal
    {
        // 计算是否触发 一些特殊道具 跟被动技能
        Unit::ProcDamageAndSpell(ProcSystemArguments(m_caster, procTarget, PROC_EX_NORMAL_HIT, 0, PROC_EX_CAST_END, 0, m_attackType, m_spellInfo));
        //计算伤害
        handle_immediate();
    }

    m_trueCaster->DecreaseCastCounter();
    SetExecutedCurrently(false);
    return SPELL_CAST_OK;
}

目标填充函数FillTargetMap


void Spell::FillTargetMap()
{
    // 临时目标函数
    TempTargetingData targetingData;
    uint8 effToIndex[MAX_EFFECT_INDEX] = {0, 1, 2};         // Helper array, to link to another tmpUnitList, if the targets for both effects match
    for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i)
    {
        //效果筛选
        if (m_spellInfo->Effect[i] == SPELL_EFFECT_NONE)
            continue;

        auto& data = SpellTargetMgr::GetSpellTargetingData(m_spellInfo->Id);
        SpellTargetImplicitType effectTargetType = data.implicitType[i]; // prefilled data on load
        auto& targetMask = data.targetMask[i];
        auto& ignoredTargets = data.ignoredTargets[i];
        auto& filterScheme = m_filteringScheme[i];
        targetingData.chainTargetCount[i] = m_spellInfo->EffectChainTarget[SpellEffectIndex(i)];
        
        if (effectTargetType == TARGET_TYPE_SPECIAL_UNIT) // area auras need custom handling
        {
            uint32 targetA = m_spellInfo->EffectImplicitTargetA[i];
            uint32 targetB = m_spellInfo->EffectImplicitTargetB[i];
            bool hadTarget = false;
            // need to pick a single unit target if existant and use it for area aura owner
            if (targetA && !ignoredTargets.first)
            {
                if (SpellTargetInfoTable[targetA].type == TARGET_TYPE_UNIT && SpellTargetInfoTable[targetA].enumerator == TARGET_ENUMERATOR_SINGLE)
                    hadTarget = true;
                SetTargetMap(SpellEffectIndex(i), targetA, false, targetingData);
            }
            if (targetB && !ignoredTargets.second)
            {
                if (SpellTargetInfoTable[targetB].type == TARGET_TYPE_UNIT && SpellTargetInfoTable[targetB].enumerator == TARGET_ENUMERATOR_SINGLE)
                    hadTarget = true;
                if (SpellTargetInfoTable[targetB].enumerator != TARGET_ENUMERATOR_AOE)
                    SetTargetMap(SpellEffectIndex(i), targetB, true, targetingData);
            }
            if (!hadTarget && m_caster) // if no single targeting available use caster as default
                targetingData.data[i].tmpUnitList[false].push_back(m_caster);
        }
        else
        {
            uint32 targetA = m_spellInfo->EffectImplicitTargetA[i];
            uint32 targetB = m_spellInfo->EffectImplicitTargetB[i];
            if (targetA == TARGET_NONE && targetB == TARGET_NONE)
            {
                // if no targeting available, attempt to use entry mask
                if (m_spellInfo->Targets && SpellTargetMgr::CanEffectBeFilledWithMask(m_spellInfo->Id, i, m_spellInfo->Targets))
                    FillFromTargetFlags(targetingData, SpellEffectIndex(i));
                else if (uint32 defaultTarget = SpellEffectInfoTable[m_spellInfo->Effect[i]].defaultTarget) // else resort to default effect type if it exists
                    SetTargetMap(SpellEffectIndex(i), defaultTarget, false, targetingData);
            }
            else // normal case, use existing spell data
            {
                if (targetA && !ignoredTargets.first)
                    SetTargetMap(SpellEffectIndex(i), targetA, false, targetingData);
                if (targetB && !ignoredTargets.second)
                    SetTargetMap(SpellEffectIndex(i), targetB, true, targetingData);
                if (effectTargetType == TARGET_TYPE_UNIT_DEST) // special case - no unit target, but need to check for valid units
                    if (SpellTargetInfoTable[targetA].type != TARGET_TYPE_UNIT && SpellTargetInfoTable[targetB].type != TARGET_TYPE_UNIT) // no fill for unit out of targets
                        FillFromTargetFlags(targetingData, SpellEffectIndex(i)); // inefficient call, very rare, less code duplicity
            }
        }

        bool processedUnits = false; // TODO: Review area aura packet fills for area auras with AOE targets
        bool processedGOs = false; // likely purpose - animations
        switch (effectTargetType)
        {
            case TARGET_TYPE_NONE:
                m_targetlessMask |= (1 << i);
                break;
            case TARGET_TYPE_LOCATION_DEST:
            case TARGET_TYPE_SPECIAL_DEST:
                OnDestTarget();
                //计算飞行时间
                AddDestExecution(SpellEffectIndex(i));
                if (effectTargetType == TARGET_TYPE_SPECIAL_DEST)
                    if (!FillUnitTargets(targetingData, data, i))
                        return;
                break;
            case TARGET_TYPE_CORPSE: // can be unit and corpse
                if (!targetingData.data[i].tempCorpseList.empty())
                {
                    CorpseList& corpseTargetList = targetingData.data[i].tempCorpseList;
                    uint8 effectMask = targetMask[0] | targetMask[1];
                    for (Corpse* corpse : corpseTargetList)
                        AddCorpseTarget(corpse, effectMask);
                }
            case TARGET_TYPE_UNIT:
            case TARGET_TYPE_UNIT_DEST:
            case TARGET_TYPE_PLAYER: // for now player handled here
            case TARGET_TYPE_SPECIAL_UNIT:
                if (effectTargetType == TARGET_TYPE_UNIT_DEST)
                    OnDestTarget();
                processedUnits = true;
                if (!FillUnitTargets(targetingData, data, i))
                    return;
                break;
            case TARGET_TYPE_LOCK:
            case TARGET_TYPE_ITEM:
                if (targetingData.data[i].tempItemList.size() > 0) // Item case
                    for (Item* item : targetingData.data[i].tempItemList)
                        AddItemTarget(item, targetMask[0]); // no spell in all of vanilla through wotlk has item target in B
                if (effectTargetType == TARGET_TYPE_ITEM)
                    break;
                // [[fallthrough]]
            case TARGET_TYPE_GAMEOBJECT:
                processedGOs = true;
                for (uint8 rightTarget = 0; rightTarget < 2; ++rightTarget) // need to process target A and B separately due to effect masks
                {
                    bool ignored = rightTarget ? ignoredTargets.second : ignoredTargets.first;
                    if (ignored)
                        continue;
                    GameObjectList& goTargetList = targetingData.data[i].tmpGOList[rightTarget];
                    uint8 effectMask = targetMask[rightTarget];
                    if (!goTargetList.empty()) // GO case
                    {
                        for (auto iter = goTargetList.begin(); iter != goTargetList.end();)
                        {
                            if (OnCheckTarget(*iter, SpellEffectIndex(i)))
                                ++iter;
                            else
                                iter = goTargetList.erase(iter);
                        }
                        if (m_affectedTargetCount && goTargetList.size() > m_affectedTargetCount)
                        {
                            // remove random units from the map
                            while (goTargetList.size() > m_affectedTargetCount)
                            {
                                uint32 poz = urand(0, goTargetList.size() - 1);
                                for (auto itr = goTargetList.begin(); itr != goTargetList.end(); ++itr, --poz)
                                {
                                    if (!*itr) continue;

                                    if (!poz)
                                    {
                                        goTargetList.erase(itr);
                                        break;
                                    }
                                }
                            }
                        }
                        for (GameObject* go : goTargetList)
                            AddGOTarget(go, effectMask);
                    }
                }
                break;
            default:
                break;
        }
    }

    if (m_spellInfo->HasAttribute(SPELL_ATTR_EX2_FAIL_ON_ALL_TARGETS_IMMUNE))
    {
        bool allImmune = true;
        for (auto& ihit : m_UniqueTargetInfo)
        {
            if (ihit.missCondition != SPELL_MISS_IMMUNE && ihit.missCondition != SPELL_MISS_IMMUNE2)
            {
                allImmune = false;
                break;
            }
        }

        if (allImmune)
        {
            SendCastResult(SPELL_FAILED_IMMUNE); // guessed error
            finish(false);
        }
    }

    if (m_spellInfo->HasAttribute(SPELL_ATTR_EX_REQUIRE_ALL_TARGETS))
    {
        for (auto& ihit : m_UniqueTargetInfo)
        {
            if (ihit.targetGUID == m_targets.getUnitTargetGuid() && ihit.missCondition != SPELL_MISS_NONE)
            {
                for (auto& ihit : m_UniqueTargetInfo)
                {
                    ihit.effectHitMask = 0;
                    ihit.effectMask = 0;
                }
                return;
            }
        }
    }
}

这里需要注意2个函数 一个是SetTargetMap 函数  这个是获取目标具体坐标点  会根据 需要注意的是 这三枚举类型 会根据spell_cone 表的范围填充 伤害对象

        case TARGET_UNIT_ENEMY_NEAR_CASTER:
        case TARGET_UNIT_FRIEND_NEAR_CASTER:
        case TARGET_UNIT_NEAR_CASTER:
        {
            m_targets.m_targetMask = 0;
            float max_range = radius + unMaxTargets * m_jumpRadius;

            UnitList tempTargetUnitMap;

            switch (targetMode)
            {
                case TARGET_UNIT_ENEMY_NEAR_CASTER:
                {
                    FillAreaTargets(tempTargetUnitMap, max_range, 0.f, PUSH_SELF_CENTER, SPELL_TARGETS_CHAIN_ATTACKABLE);
                    break;
                }
                case TARGET_UNIT_NEAR_CASTER: // TODO: Rename TARGET_UNIT_NEAR_CASTER to something better and find real difference with TARGET_UNIT_FRIEND_NEAR_CASTER.
                {
                    FillAreaTargets(tempTargetUnitMap, max_range, cone, PUSH_SELF_CENTER, SPELL_TARGETS_ALL);
                    break;
                }
                case TARGET_UNIT_FRIEND_NEAR_CASTER:
                {
                    FillAreaTargets(tempTargetUnitMap, max_range, cone, PUSH_SELF_CENTER, SPELL_TARGETS_ASSISTABLE);
                    break;
                }
            }

 还需要注意的是如果是AOE范围伤害等  填充目标函数为下面这个

      case TARGET_ENUM_UNITS_ENEMY_AOE_AT_SRC_LOC:
            FillAreaTargets(tempUnitList, radius, cone, PUSH_SRC_CENTER, SPELL_TARGETS_AOE_ATTACKABLE);
            break;

 这个同步函数 不明白这恩么使用的 可以去参考之前的同步算法 容器文章

void Spell::FillAreaTargets(UnitList& targetUnitMap, float radius, float cone, SpellNotifyPushType pushType, SpellTargets spellTargets, WorldObject* originalCaster /*=nullptr*/)
{
    MaNGOS::SpellNotifierCreatureAndPlayer notifier(*this, targetUnitMap, radius, cone, pushType, spellTargets, originalCaster);
    Cell::VisitAllObjects(notifier.GetCenterX(), notifier.GetCenterY(), m_trueCaster->GetMap(), notifier, radius);
}


void Spell::SetTargetMap(SpellEffectIndex effIndex, uint32 targetMode, bool targetB, TempTargetingData& targetingData)
{
    TempTargetData& data = targetingData.data[effIndex];
    float radius;
    uint32 unMaxTargets = m_affectedTargetCount;  // Get spell max affected targets

    GetSpellRangeAndRadius(effIndex, radius, targetB, targetingData.chainTargetCount[effIndex]);
    float cone = GetCone();

    UnitList& tempUnitList = data.tmpUnitList[targetB];
    GameObjectList& tempGOList = data.tmpGOList[targetB];

    switch (targetMode)
    {
        case TARGET_LOCATION_UNIT_RANDOM_SIDE:
        case TARGET_LOCATION_CASTER_RANDOM_SIDE:
            // special case for Fatal Attraction (BT, Mother Shahraz)
            if (m_spellInfo->Id == 40869)
                radius = 30.0f;

            // Get a random point in circle. Use sqrt(rand) to correct distribution when converting polar to Cartesian coordinates.
            radius *= sqrtf(rand_norm_f());
        // no 'break' expected since we use code in case TARGET_LOCATION_CASTER_RANDOM_CIRCUMFERENCE!!!
        case TARGET_LOCATION_UNIT_RANDOM_CIRCUMFERENCE:
        case TARGET_LOCATION_CASTER_RANDOM_CIRCUMFERENCE:
        {
            WorldObject* target;
            switch (targetMode)
            {
                case TARGET_LOCATION_UNIT_RANDOM_SIDE:
                case TARGET_LOCATION_UNIT_RANDOM_CIRCUMFERENCE:
                    target = m_targets.getUnitTarget() ? m_targets.getUnitTarget() : m_trueCaster; break;
                default:
                    target = m_trueCaster; break;
            }
            // Get a random point AT the circumference
            float angle = 2.0f * M_PI_F * rand_norm_f();
            Position pos = target->GetFirstRandomAngleCollisionPosition(radius, angle);
            m_targets.setDestination(pos.x, pos.y, pos.z);
            break;
        }
        case TARGET_LOCATION_RANDOM_SIDE:
        {
            if (radius > 0.0f) // Get a random point in the circle around current dest target
            {
                // Use sqrt(rand) to correct distribution when converting polar to Cartesian coordinates.
                radius *= sqrtf(rand_norm_f());
                float angle = 2.0f * M_PI_F * rand_norm_f();
                Position pos = m_targets.getDestination();
                m_trueCaster->MovePositionToFirstCollision(pos, radius, angle);
                m_targets.setDestination(pos.x, pos.y, pos.z);
            }
            break;
        }
        case TARGET_LOCATION_UNIT_MINION_POSITION: // unknown how pet summon is different - maybe some formation support?
        case TARGET_LOCATION_CASTER_FRONT_RIGHT:
        case TARGET_LOCATION_CASTER_BACK_RIGHT:
        case TARGET_LOCATION_CASTER_BACK_LEFT:
        case TARGET_LOCATION_CASTER_FRONT_LEFT:
        case TARGET_LOCATION_CASTER_FRONT:
        case TARGET_LOCATION_CASTER_BACK:
        case TARGET_LOCATION_CASTER_LEFT:
        case TARGET_LOCATION_CASTER_RIGHT:
        {
            WorldObject* caster = m_trueCaster;
            float angle = m_trueCaster->GetOrientation();
            switch (targetMode)
            {
                case TARGET_LOCATION_CASTER_FRONT_LEFT:  angle += M_PI_F * 0.25f; break;
                case TARGET_LOCATION_CASTER_BACK_LEFT:   angle += M_PI_F * 0.75f; break;
                case TARGET_LOCATION_CASTER_BACK_RIGHT:  angle += M_PI_F * 1.25f; break;
                case TARGET_LOCATION_CASTER_FRONT_RIGHT: angle += M_PI_F * 1.75f; break;
                case TARGET_LOCATION_CASTER_FRONT:                                break;
                case TARGET_LOCATION_CASTER_BACK:        angle += M_PI_F;         break;
                case TARGET_LOCATION_UNIT_MINION_POSITION: caster = GetCastingObject(); // projected from original caster if necessary
                case TARGET_LOCATION_CASTER_LEFT:        angle += M_PI_F / 2;     break;
                case TARGET_LOCATION_CASTER_RIGHT:       angle -= M_PI_F / 2;     break;
            }

            Position pos;
            caster->GetFirstCollisionPosition(pos, radius, angle);
            m_targets.setDestination(pos.x, pos.y, pos.z);
            break;
        }
        case TARGET_LOCATION_CURRENT_REFERENCE:
            // Uses destination supplied to spell or fill caster position
            if ((m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) == 0)
            {
                float x, y, z;
                m_trueCaster->GetPosition(x, y, z);
                m_targets.setDestination(x, y, z);
            }
            break;
        case TARGET_LOCATION_CHANNEL_TARGET_DEST:
            // if parent spell create dynamic object extract area from it
            if (WorldObject* channelObject = m_caster->GetChannelObject())
                m_targets.setDestination(channelObject->GetPositionX(), channelObject->GetPositionY(), channelObject->GetPositionZ());
            break;
        case TARGET_LOCATION_NORTH:
        case TARGET_LOCATION_SOUTH:
        case TARGET_LOCATION_EAST:
        case TARGET_LOCATION_WEST:
        case TARGET_LOCATION_NE:
        case TARGET_LOCATION_NW:
        case TARGET_LOCATION_SE:
        case TARGET_LOCATION_SW:
        {
            if (m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION) // takes current dest and offsets it
            {
                WorldObject* currentTarget = m_targets.getUnitTarget() ? m_targets.getUnitTarget() : m_trueCaster;
                float angle = currentTarget != m_trueCaster ? currentTarget->GetAngle(m_trueCaster) : m_trueCaster->GetOrientation();

                switch (targetMode)
                {
                    case TARGET_LOCATION_NORTH:                         break;
                    case TARGET_LOCATION_SOUTH: angle += M_PI_F;        break;
                    case TARGET_LOCATION_EAST:  angle -= M_PI_F / 2;    break;
                    case TARGET_LOCATION_WEST:  angle += M_PI_F / 2;    break;
                    case TARGET_LOCATION_NE:    angle -= M_PI_F / 4;    break;
                    case TARGET_LOCATION_NW:    angle += M_PI_F / 4;    break;
                    case TARGET_LOCATION_SE:    angle -= 3 * M_PI_F / 4;    break;
                    case TARGET_LOCATION_SW:    angle += 3 * M_PI_F / 4;    break;
                }

                Position pos = m_targets.getDestination();
                currentTarget->MovePositionToFirstCollision(pos, radius, angle);
                m_targets.setDestination(pos.x, pos.y, pos.z);
            }
            break;
        }
        case TARGET_LOCATION_CASTER_DEST:
        {
            if (!(m_targets.m_targetMask & TARGET_FLAG_DEST_LOCATION))
                if (WorldObject* caster = GetCastingObject())
                    m_targets.setDestination(caster->GetPositionX(), caster->GetPositionY(), caster->GetPositionZ());
            break;
        }
        case TARGET_LOCATION_DATABASE:
        {
            if (SpellTargetPosition const* st = sSpellMgr.GetSpellTargetPosition(m_spellInfo->Id))
            {
                m_targets.setDestination(st->target_X, st->target_Y, st->target_Z);
                m_targets.m_destPos.o = st->target_Orientation;
                m_targets.m_mapId = st->target_mapId;

                // far-teleport spells are handled in SpellEffect, elsewise report an error about an unexpected map (spells are always locally)
                if (st->target_mapId != m_trueCaster->GetMapId() && m_spellInfo->Effect[effIndex] != SPELL_EFFECT_TELEPORT_UNITS && m_spellInfo->Effect[effIndex] != SPELL_EFFECT_BIND)
                    sLog.outError("SPELL: wrong map (%u instead %u) target coordinates for spell ID %u", st->target_mapId, m_trueCaster->GetMapId(), m_spellInfo->Id);
            }
            else
                sLog.outError("SPELL: unknown target coordinates for spell ID %u", m_spellInfo->Id);
            break;
        }
        case TARGET_LOCATION_CASTER_HOME_BIND:
        {
            if (m_trueCaster->IsPlayer())
            {
                float x, y, z;
                static_cast<Player*>(m_trueCaster)->GetHomebindLocation(x, y, z, m_targets.m_mapId);
                m_targets.setDestination(x, y, z);
            }
            break;
        }
        case TARGET_LOCATION_UNIT_FRONT:
        case TARGET_LOCATION_UNIT_BACK:
        case TARGET_LOCATION_UNIT_RIGHT:
        case TARGET_LOCATION_UNIT_LEFT:
        case TARGET_LOCATION_UNIT_FRONT_RIGHT:
        case TARGET_LOCATION_UNIT_BACK_RIGHT:
        case TARGET_LOCATION_UNIT_BACK_LEFT:
        case TARGET_LOCATION_UNIT_FRONT_LEFT:
        {
            Unit* target = m_targets.getUnitTarget();
            if (target)
            {
                float angle = target->GetOrientation();

                switch (targetMode)
                {
                    case TARGET_LOCATION_UNIT_FRONT:                                 break;
                    case TARGET_LOCATION_UNIT_BACK:         angle += M_PI_F;          break;
                    case TARGET_LOCATION_UNIT_RIGHT:        angle += -M_PI_F / 2;     break;
                    case TARGET_LOCATION_UNIT_LEFT:         angle += M_PI_F / 2;      break;
                    case TARGET_LOCATION_UNIT_FRONT_RIGHT:  angle += M_PI_F * 1.75f; break;
                    case TARGET_LOCATION_UNIT_BACK_RIGHT:   angle += M_PI_F * 1.25f; break;
                    case TARGET_LOCATION_UNIT_BACK_LEFT:    angle += M_PI_F * 0.75f; break;
                    case TARGET_LOCATION_UNIT_FRONT_LEFT:   angle += M_PI_F * 0.25f; break;
                }

                Position pos;
                target->GetFirstCollisionPosition(pos, radius, angle);
                m_targets.setDestination(pos.x, pos.y, pos.z);
            }
            break;
        }
        case TARGET_LOCATION_CASTER_FRONT_LEAP:
        {
            float dist = GetSpellRadius(sSpellRadiusStore.LookupEntry(m_spellInfo->EffectRadiusIndex[effIndex]));
            const float IN_OR_UNDER_LIQUID_RANGE = 0.8f;                // range to make player under liquid or on liquid surface from liquid level

            G3D::Vector3 prevPos, nextPos;
            float orientation = m_caster->GetOrientation();

            prevPos.x = m_caster->GetPositionX();
            prevPos.y = m_caster->GetPositionY();
            prevPos.z = m_caster->GetPositionZ();

            float groundZ = prevPos.z;
            bool isPrevInLiquid = false;

            // falling case
            if (!m_caster->GetMap()->GetHeightInRange(prevPos.x, prevPos.y, groundZ, 3.0f) && m_caster->m_movementInfo.HasMovementFlag(MOVEFLAG_FALLING))
            {
                nextPos.x = prevPos.x + dist * cos(orientation);
                nextPos.y = prevPos.y + dist * sin(orientation);
                nextPos.z = prevPos.z - 2.0f; // little hack to avoid the impression to go up when teleporting instead of continue to fall. This value may need some tweak

                //
                GridMapLiquidData liquidData;
                if (m_caster->GetMap()->GetTerrain()->IsInWater(nextPos.x, nextPos.y, nextPos.z, &liquidData))
                {
                    if (fabs(nextPos.z - liquidData.level) < 10.0f)
                        nextPos.z = liquidData.level - IN_OR_UNDER_LIQUID_RANGE;
                }
                else
                {
                    // fix z to ground if near of it
                    m_caster->GetMap()->GetHeightInRange(nextPos.x, nextPos.y, nextPos.z, 10.0f);
                }

                if (fabs(prevPos.z - nextPos.z) > 40.f) // dont move too high - magical constant - might need verification
                {
                    nextPos.x = prevPos.x;
                    nextPos.y = prevPos.y;
                    nextPos.z = prevPos.z - 2.0f;
                }
                else // check any obstacle and fix coords
                    m_caster->GetMap()->GetHitPosition(prevPos.x, prevPos.y, prevPos.z + 0.5f, nextPos.x, nextPos.y, nextPos.z, -0.5f);
            }
            else
            {
                // fix origin position if player was jumping and near of the ground but not in ground
                if (fabs(prevPos.z - groundZ) > 0.5f)
                    prevPos.z = groundZ;

                //check if in liquid
                isPrevInLiquid = m_caster->GetMap()->GetTerrain()->IsInWater(prevPos.x, prevPos.y, prevPos.z);

                const float step = 2.0f;                                    // step length before next check slope/edge/water
                const float maxSlope = 50.0f;                               // 50(degree) max seem best value for walkable slope
                const float MAX_SLOPE_IN_RADIAN = maxSlope / 180.0f * M_PI_F;
                float nextZPointEstimation = 1.0f;
                float destx = prevPos.x + dist * cos(orientation);
                float desty = prevPos.y + dist * sin(orientation);
                const uint32 numChecks = ceil(fabs(dist / step));
                const float DELTA_X = (destx - prevPos.x) / numChecks;
                const float DELTA_Y = (desty - prevPos.y) / numChecks;

                for (uint32 i = 1; i < numChecks + 1; ++i)
                {
                    // compute next point average position
                    nextPos.x = prevPos.x + DELTA_X;
                    nextPos.y = prevPos.y + DELTA_Y;
                    nextPos.z = prevPos.z + nextZPointEstimation;

                    bool isInLiquid = false;
                    bool isInLiquidTested = false;
                    bool isOnGround = false;
                    GridMapLiquidData liquidData = {};

                    // try fix height for next position
                    if (!m_caster->GetMap()->GetHeightInRange(nextPos.x, nextPos.y, nextPos.z))
                    {
                        // we cant so test if we are on water
                        if (!m_caster->GetMap()->GetTerrain()->IsInWater(nextPos.x, nextPos.y, nextPos.z, &liquidData))
                        {
                            // not in water and cannot get correct height, maybe flying?
                            //sLog.outString("Can't get height of point %u, point value %s", i, nextPos.toString().c_str());
                            nextPos = prevPos;
                            break;
                        }
                        isInLiquid = true;
                        isInLiquidTested = true;
                    }
                    else
                        isOnGround = true;                                  // player is on ground

                    if (isInLiquid || (!isInLiquidTested && m_caster->GetMap()->GetTerrain()->IsInWater(nextPos.x, nextPos.y, nextPos.z, &liquidData)))
                    {
                        if (!isPrevInLiquid && fabs(liquidData.level - prevPos.z) > 2.0f)
                        {
                            // on edge of water with difference a bit to high to continue
                            //sLog.outString("Ground vs liquid edge detected!");
                            nextPos = prevPos;
                            break;
                        }

                        if ((liquidData.level - IN_OR_UNDER_LIQUID_RANGE) > nextPos.z)
                            nextPos.z = prevPos.z;                                      // we are under water so next z equal prev z
                        else
                            nextPos.z = liquidData.level - IN_OR_UNDER_LIQUID_RANGE;    // we are on water surface, so next z equal liquid level

                        isInLiquid = true;

                        float ground = nextPos.z;
                        if (m_caster->GetMap()->GetHeightInRange(nextPos.x, nextPos.y, ground))
                        {
                            if (nextPos.z < ground)
                            {
                                nextPos.z = ground;
                                isOnGround = true;                          // player is on ground of the water
                            }
                        }
                    }

                    //unitTarget->SummonCreature(VISUAL_WAYPOINT, nextPos.x, nextPos.y, nextPos.z, 0, TEMPSUMMON_TIMED_DESPAWN, 15000);
                    float hitZ = nextPos.z + 1.5f;
                    if (m_caster->GetMap()->GetHitPosition(prevPos.x, prevPos.y, prevPos.z + 1.5f, nextPos.x, nextPos.y, hitZ, -1.0f))
                    {
                        //sLog.outString("Blink collision detected!");
                        nextPos = prevPos;
                        break;
                    }

                    if (isOnGround)
                    {
                        // project vector to get only positive value
                        float ac = fabs(prevPos.z - nextPos.z);

                        // compute slope (in radian)
                        float slope = atan(ac / step);

                        // check slope value
                        if (slope > MAX_SLOPE_IN_RADIAN)
                        {
                            //sLog.outString("bad slope detected! %4.2f max %4.2f, ac(%4.2f)", slope * 180 / M_PI_F, maxSlope, ac);
                            nextPos = prevPos;
                            break;
                        }
                        //sLog.outString("slope is ok! %4.2f max %4.2f, ac(%4.2f)", slope * 180 / M_PI_F, maxSlope, ac);
                    }

                    //sLog.outString("point %u is ok, coords %s", i, nextPos.toString().c_str());
                    nextZPointEstimation = (nextPos.z - prevPos.z) / 2.0f;
                    isPrevInLiquid = isInLiquid;
                    prevPos = nextPos;
                }
            }
            m_targets.setDestination(nextPos.x, nextPos.y, nextPos.z);
            break;
        }
        case TARGET_LOCATION_UNIT_POSITION:
        {
            if (Unit* currentTarget = m_targets.getUnitTarget())
                m_targets.setDestination(currentTarget->GetPositionX(), currentTarget->GetPositionY(), currentTarget->GetPositionZ());
            break;
        }
        case TARGET_LOCATION_CASTER_FISHING_SPOT:
        {
            float x, y, z;
            // special code for fishing bobber (TARGET_LOCATION_CASTER_FISHING_SPOT), should not try to avoid objects
            // nor try to find ground level, but randomly vary in angle
            float min_dis = GetSpellMinRange(sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex));
            float max_dis = GetSpellMaxRange(sSpellRangeStore.LookupEntry(m_spellInfo->rangeIndex));
            SpellCastResult result = SPELL_CAST_OK;
            for (uint32 i = 0; i < 10; ++i)
            {
                float dis = rand_norm_f() * (max_dis - min_dis) + min_dis;
                // calculate angle variation for roughly equal dimensions of target area
                float max_angle = (max_dis - min_dis) / (max_dis + m_caster->GetObjectBoundingRadius());
                float angle_offset = max_angle * (rand_norm_f() - 0.5f);
                m_caster->GetNearPoint2d(x, y, dis + m_caster->GetObjectBoundingRadius(), m_caster->GetOrientation() + angle_offset);

                GridMapLiquidData liqData;
                if (!m_caster->GetTerrain()->IsInWater(x, y, m_caster->GetTerrain()->GetWaterLevel(x, y, m_caster->GetPositionZ()) - 1.0f, &liqData))
                {
                    result = SPELL_FAILED_NOT_FISHABLE;
                    continue;
                }

                z = liqData.level;
                // finally, check LoS
                if (!m_caster->IsWithinLOS(x, y, z + 1.f))
                {
                    result = SPELL_FAILED_LINE_OF_SIGHT;
                    continue;
                }
            }
            if (result != SPELL_CAST_OK)
            {
                SendCastResult(result);
                finish(false);
                return;
            }

            m_targets.setDestination(x, y, z);
            break;
        }
        case TARGET_LOCATION_CASTER_TARGET_POSITION:
        {
            Unit* currentTarget = m_targets.getUnitTarget();
            if (currentTarget)
                m_targets.setDestination(currentTarget->GetPositionX(), currentTarget->GetPositionY(), currentTarget->GetPositionZ());
            break;
        }
        case TARGET_UNIT_CASTER:
            tempUnitList.push_back(m_caster);
            break;
        case TARGET_UNIT_ENEMY_NEAR_CASTER:
        case TARGET_UNIT_FRIEND_NEAR_CASTER:
        case TARGET_UNIT_NEAR_CASTER:
        {
            m_targets.m_targetMask = 0;
            float max_range = radius + unMaxTargets * m_jumpRadius;

            UnitList tempTargetUnitMap;

            switch (targetMode)
            {
                case TARGET_UNIT_ENEMY_NEAR_CASTER:
                {
                    FillAreaTargets(tempTargetUnitMap, max_range, 0.f, PUSH_SELF_CENTER, SPELL_TARGETS_CHAIN_ATTACKABLE);
                    break;
                }
                case TARGET_UNIT_NEAR_CASTER: // TODO: Rename TARGET_UNIT_NEAR_CASTER to something better and find real difference with TARGET_UNIT_FRIEND_NEAR_CASTER.
                {
                    FillAreaTargets(tempTargetUnitMap, max_range, cone, PUSH_SELF_CENTER, SPELL_TARGETS_ALL);
                    break;
                }
                case TARGET_UNIT_FRIEND_NEAR_CASTER:
                {
                    FillAreaTargets(tempTargetUnitMap, max_range, cone, PUSH_SELF_CENTER, SPELL_TARGETS_ASSISTABLE);
                    break;
                }
            }

            if (tempTargetUnitMap.empty())
                break;

            SQLMultiStorage::SQLMSIteratorBounds<SpellTargetEntry> bounds = sSpellScriptTargetStorage.getBounds<SpellTargetEntry>(m_spellInfo->Id);
            if (bounds.first != bounds.second)
                CheckSpellScriptTargets(bounds, tempTargetUnitMap, tempUnitList, effIndex);
            else
                tempUnitList.splice(tempUnitList.end(), tempTargetUnitMap);
            break;
        }
        case TARGET_UNIT_CASTER_PET:
        {
            if (Pet* tmpUnit = m_caster->GetPet())
                tempUnitList.push_back(tmpUnit);
            else if (Unit* charm = m_caster->GetCharm())
                tempUnitList.push_back(charm);
            break;
        }
        case TARGET_UNIT:
        {
            Unit* newUnitTarget = m_targets.getUnitTarget();
            if (!newUnitTarget)
                break;

            if (!m_trueCaster->CanAssistSpell(newUnitTarget, m_spellInfo))
            {
                if (CheckAndAddMagnetTarget(newUnitTarget, effIndex, targetB, targetingData))
                    break;
            }

            tempUnitList.push_back(newUnitTarget);

            // More than one target
            if (targetingData.chainTargetCount[effIndex] > 1)
            {
                // Getting spell casting distance
                float minRadiusCaster = 0, maxRadiusTarget = 0;
                GetChainJumpRange(m_spellInfo, effIndex, minRadiusCaster, maxRadiusTarget);

                // Filling target map
                UnitList tempAoeList;
                {
                    FillAreaTargets(tempAoeList, maxRadiusTarget, cone, m_spellInfo->HasAttribute(SPELL_ATTR_EX2_CHAIN_FROM_CASTER) ? PUSH_SELF_CENTER : PUSH_TARGET_CENTER, SPELL_TARGETS_ALL);
                    tempAoeList.erase(std::remove(tempAoeList.begin(), tempAoeList.end(), newUnitTarget), tempAoeList.end());
                }

                tempUnitList.splice(tempUnitList.end(), tempAoeList);
            }
            break;
        }
        case TARGET_UNIT_ENEMY:
        {
            Unit* newUnitTarget = m_targets.getUnitTarget();
            if (!newUnitTarget)
                break;

            // Check if target have Grounding Totem Aura(Magnet target). Check for physical school inside included.
            if (CheckAndAddMagnetTarget(newUnitTarget, effIndex, targetB, targetingData))
                break;

            tempUnitList.push_back(newUnitTarget);

            // More than one target
            if (targetingData.chainTargetCount[effIndex] > 1)
            {
                WorldObject* originalCaster = GetAffectiveCasterObject();
                if (!originalCaster)
                    break;

                if (m_caster->GetTypeId() == TYPEID_PLAYER)
                {
                    Player* me = (Player*)m_caster;
                    Player* targetOwner = newUnitTarget->GetBeneficiaryPlayer();
                    if (targetOwner && targetOwner != me && targetOwner->IsPvP() && !me->IsInDuelWith(targetOwner))
                    {
                        me->UpdatePvP(true);
                        me->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_PVP_ACTIVE_CANCELS);
                    }
                }

                // Getting spell casting distance
                float minRadiusCaster = 0, maxRadiusTarget = 0;
                GetChainJumpRange(m_spellInfo, effIndex, minRadiusCaster, maxRadiusTarget);

                // Filling target map
                UnitList tempAoeList;
                {
                    SpellNotifyPushType pushType = PUSH_TARGET_CENTER;
                    if (m_spellInfo->HasAttribute(SPELL_ATTR_EX2_CHAIN_FROM_CASTER))
                        pushType = PUSH_SELF_CENTER;
                    if (m_spellInfo->HasAttribute(SPELL_ATTR_EX5_MELEE_CHAIN_TARGETING))
                        pushType = PUSH_CONE;
                    FillAreaTargets(tempAoeList, maxRadiusTarget, cone, pushType, SPELL_TARGETS_CHAIN_ATTACKABLE);
                    tempAoeList.erase(std::remove(tempAoeList.begin(), tempAoeList.end(), newUnitTarget), tempAoeList.end());

                    for (auto itr = tempAoeList.begin(); itr != tempAoeList.end();)
                    {
                        if (!(*itr)->IsVisibleForOrDetect(m_caster, m_originalCaster, false, false, true, false, m_spellInfo->HasAttribute(SPELL_ATTR_EX6_IGNORE_PHASE_SHIFT)))
                            itr = tempAoeList.erase(itr);
                        else
                            ++itr;
                    }
                }

                // No targets. No need to process.
                if (tempAoeList.empty())
                    break;

                if (minRadiusCaster)
                {
                    float x, y, z;
                    m_caster->GetPosition(x, y, z);
                    auto itr = tempAoeList.begin();
                    ++itr; // start from 2nd
                    for (; itr != tempAoeList.end();)
                    {
                        if ((*itr)->GetDistance(x, y, z, DIST_CALC_COMBAT_REACH) < minRadiusCaster)
                            itr = tempAoeList.erase(itr);
                        else
                            ++itr;
                    }
                }
                tempUnitList.splice(tempUnitList.end(), tempAoeList);
            }
            break;
        }
        case TARGET_ENUM_UNITS_ENEMY_AOE_AT_SRC_LOC:
            FillAreaTargets(tempUnitList, radius, cone, PUSH_SRC_CENTER, SPELL_TARGETS_AOE_ATTACKABLE);
            break;
        case TARGET_ENUM_UNITS_SCRIPT_AOE_AT_SRC_LOC:
        {
            UnitList tempTargetUnitMap;
            SQLMultiStorage::SQLMSIteratorBounds<SpellTargetEntry> bounds = sSpellScriptTargetStorage.getBounds<SpellTargetEntry>(m_spellInfo->Id);

            // fill real target list if no spell script target defined
            FillAreaTargets(bounds.first != bounds.second ? tempTargetUnitMap : tempUnitList,
                            radius, cone, PUSH_SRC_CENTER, SPELL_TARGETS_ALL);

            if (!tempTargetUnitMap.empty())
                CheckSpellScriptTargets(bounds, tempTargetUnitMap, tempUnitList, effIndex);

            switch (m_spellInfo->Id)
            {
                case 45339:
                {
                    for (auto iter = tempUnitList.begin(); iter != tempUnitList.end();)
                    {
                        if ((*iter)->HasCharmer(m_caster->GetObjectGuid()))
                            iter = tempUnitList.erase(iter);
                        else
                            ++iter;
                    }
                    break;
                }
            }

            break;
        }
        case TARGET_ENUM_UNITS_SCRIPT_AOE_AT_DEST_LOC:
        {
            UnitList tempTargetUnitMap;
            SQLMultiStorage::SQLMSIteratorBounds<SpellTargetEntry> bounds = sSpellScriptTargetStorage.getBounds<SpellTargetEntry>(m_spellInfo->Id);
            // fill real target list if no spell script target defined
            FillAreaTargets(bounds.first != bounds.second ? tempTargetUnitMap : tempUnitList, radius, cone, PUSH_DEST_CENTER, SPELL_TARGETS_ALL);

            if (!tempTargetUnitMap.empty())
                CheckSpellScriptTargets(bounds, tempTargetUnitMap, tempUnitList, effIndex);

            break;
        }
        case TARGET_ENUM_GAMEOBJECTS_SCRIPT_AOE_AT_SRC_LOC:
        case TARGET_ENUM_GAMEOBJECTS_SCRIPT_AOE_AT_DEST_LOC:
        {
            float x, y, z;
            if (targetMode == TARGET_ENUM_GAMEOBJECTS_SCRIPT_AOE_AT_SRC_LOC)
                m_targets.getSource(x, y, z);
            else
                m_targets.getDestination(x, y, z);

            // It may be possible to fill targets for some spell effects
            // automatically (SPELL_EFFECT_WMO_REPAIR(88) for example) but
            // for some/most spells we clearly need/want to limit with spell_target_script

            // Some spells untested, for affected GO type 33. May need further adjustments for spells related.

            std::set<uint32> entriesToUse;

            SQLMultiStorage::SQLMSIteratorBounds<SpellTargetEntry> bounds = sSpellScriptTargetStorage.getBounds<SpellTargetEntry>(m_spellInfo->Id);
            for (SQLMultiStorage::SQLMultiSIterator<SpellTargetEntry> i_spellST = bounds.first; i_spellST != bounds.second; ++i_spellST)
            {
                if (i_spellST->CanNotHitWithSpellEffect(effIndex))
                    continue;

                if (i_spellST->type == SPELL_TARGET_TYPE_GAMEOBJECT)
                    entriesToUse.insert(i_spellST->targetEntry);
            }

            if (!entriesToUse.empty())
            {
                MaNGOS::AllGameObjectEntriesListInPosRangeCheck go_check(x, y, z, entriesToUse, radius);
                MaNGOS::GameObjectListSearcher<MaNGOS::AllGameObjectEntriesListInPosRangeCheck> checker(tempGOList, go_check);
                Cell::VisitGridObjects(m_trueCaster, checker, radius);
            }

            break;
        }
        case TARGET_ENUM_UNITS_ENEMY_AOE_AT_DEST_LOC:
        {
            // targets the ground, not the units in the area
            FillAreaTargets(tempUnitList, radius, cone, PUSH_DEST_CENTER, SPELL_TARGETS_AOE_ATTACKABLE);
            break;
        }
        case TARGET_ENUM_UNITS_PARTY_WITHIN_CASTER_RANGE:
        case TARGET_ENUM_UNITS_PARTY_AOE_AT_DEST_LOC:
        case TARGET_ENUM_UNITS_PARTY_AOE_AT_SRC_LOC:
        {
            FillRaidOrPartyTargets(tempUnitList, m_caster, radius, false, true, true);
            break;
        }
        case TARGET_ENUM_UNITS_RAID_WITHIN_CASTER_RANGE:
        {
            FillRaidOrPartyTargets(tempUnitList, m_caster, radius, true, true, IsPositiveSpell(m_spellInfo->Id));
            break;
        }
        case TARGET_UNIT_FRIEND:
        case TARGET_UNIT_RAID:
        {
            if (Unit* target = m_targets.getUnitTarget())
            {
                if (m_trueCaster->CanAssistSpell(target, m_spellInfo) && (targetMode != TARGET_UNIT_RAID || target->IsInGroup(m_caster)))
                    tempUnitList.push_back(target);
                else
                {
                    if (m_spellInfo->HasAttribute(SPELL_ATTR_EX5_IMPLIED_TARGETING))
                    {
                        if (Unit* targetOfUnitTarget = target->GetTarget(m_trueCaster))
                        {
                            if (m_trueCaster->CanAssistSpell(targetOfUnitTarget, m_spellInfo) && (targetMode != TARGET_UNIT_RAID || targetOfUnitTarget->IsInGroup(m_caster)))
                                tempUnitList.push_back(targetOfUnitTarget);
                        }
                    }
                }
            }
            break;
        }
        case TARGET_UNIT_CASTER_COMPANION:
            if (Unit* target = m_targets.getUnitTarget())
                if (target->GetTypeId() == TYPEID_UNIT && ((Creature*)target)->IsPet() && ((Pet*)target)->getPetType() == MINI_PET)
                    tempUnitList.push_back(target);
            break;
        case TARGET_LOCATION_CASTER_SRC:
        {
            // Check original caster is GO - set its coordinates as src cast
            if (WorldObject* caster = GetCastingObject())
                m_targets.setSource(caster->GetPositionX(), caster->GetPositionY(), caster->GetPositionZ());
            break;
        }
        case TARGET_ENUM_UNITS_ENEMY_WITHIN_CASTER_RANGE:
            FillAreaTargets(tempUnitList, radius, cone, PUSH_SELF_CENTER, SPELL_TARGETS_AOE_ATTACKABLE);
            break;
        case TARGET_ENUM_UNITS_FRIEND_AOE_AT_SRC_LOC:
            // selected friendly units (for casting objects) around casting object
            FillAreaTargets(tempUnitList, radius, cone, PUSH_SRC_CENTER, SPELL_TARGETS_ASSISTABLE, GetCastingObject());
            break;
        case TARGET_ENUM_UNITS_FRIEND_AOE_AT_DEST_LOC:
            FillAreaTargets(tempUnitList, radius, cone, PUSH_DEST_CENTER, SPELL_TARGETS_ASSISTABLE);
            break;
        // TARGET_UNIT_PARTY means that the spells can only be casted on a party member and not on the caster (some seals, fire shield from imp, etc..)
        case TARGET_UNIT_PARTY:
        {
            Unit* target = m_targets.getUnitTarget();
            // Those spells apparently can't be casted on the caster.
            if (target && target != m_caster)
            {
                // Can only be casted on group's members or its pets
                Group*  pGroup = nullptr;

                Unit* owner = m_caster->GetMaster();
                Unit* targetOwner = target->GetMaster();
                if (owner)
                {
                    if (owner->GetTypeId() == TYPEID_PLAYER)
                    {
                        if (target == owner)
                        {
                            tempUnitList.push_back(target);
                            break;
                        }
                        pGroup = ((Player*)owner)->GetGroup();
                    }
                }
                else if (m_caster->GetTypeId() == TYPEID_PLAYER)
                {
                    if (targetOwner == m_caster && target->GetTypeId() == TYPEID_UNIT && ((Creature*)target)->IsPet())
                    {
                        tempUnitList.push_back(target);
                        break;
                    }
                    pGroup = ((Player*)m_caster)->GetGroup();
                }

                if (pGroup)
                {
                    // Our target can also be a player's pet who's grouped with us or our pet. But can't be controlled player
                    if (targetOwner)
                    {
                        if (targetOwner->GetTypeId() == TYPEID_PLAYER &&
                                target->GetTypeId() == TYPEID_UNIT && (((Creature*)target)->IsPet()) &&
                                target->GetOwnerGuid() == targetOwner->GetObjectGuid() &&
                                pGroup->IsMember(((Player*)targetOwner)->GetObjectGuid()))
                        {
                            tempUnitList.push_back(target);
                        }
                    }
                    // 1Our target can be a player who is on our group
                    else if (target->GetTypeId() == TYPEID_PLAYER && pGroup->IsMember(((Player*)target)->GetObjectGuid()))
                    {
                        tempUnitList.push_back(target);
                    }
                }
            }
            break;
        }
        case TARGET_GAMEOBJECT:
            if (GameObject* go = m_targets.getGOTarget())
                tempGOList.push_back(go);
            break;
        case TARGET_ENUM_UNITS_ENEMY_IN_CONE_24:
        case TARGET_ENUM_UNITS_ENEMY_IN_CONE_54:
        case TARGET_ENUM_UNITS_FRIEND_IN_CONE:
        case TARGET_ENUM_UNITS_SCRIPT_IN_CONE_60:
        {
            SpellTargets targetType = SPELL_TARGETS_ALL;
            switch (targetMode)
            {
                case TARGET_ENUM_UNITS_ENEMY_IN_CONE_24:
                case TARGET_ENUM_UNITS_ENEMY_IN_CONE_54: targetType = SPELL_TARGETS_AOE_ATTACKABLE; break;
                case TARGET_ENUM_UNITS_FRIEND_IN_CONE: targetType = SPELL_TARGETS_ASSISTABLE; break;
            }

            switch (targetMode)
            {
                case TARGET_ENUM_UNITS_SCRIPT_IN_CONE_60:
                {
                    UnitList tempTargetUnitMap;
                    SQLMultiStorage::SQLMSIteratorBounds<SpellTargetEntry> bounds = sSpellScriptTargetStorage.getBounds<SpellTargetEntry>(m_spellInfo->Id);
                    // fill real target list if no spell script target defined
                    FillAreaTargets(bounds.first != bounds.second ? tempTargetUnitMap : tempUnitList, radius, cone, PUSH_CONE, targetType);
                    if (!tempTargetUnitMap.empty())
                        CheckSpellScriptTargets(bounds, tempTargetUnitMap, tempUnitList, effIndex);
                    break;
                }
                default:
                    FillAreaTargets(tempUnitList, radius, cone, PUSH_CONE, targetType);
                    break;
            }
            break;
        }
        case TARGET_LOCKED:
            if (GameObject* go = m_targets.getGOTarget())
                tempGOList.push_back(go);
            else if (Item* item = m_targets.getItemTarget())
                data.tempItemList.push_back(item);
            break;
        case TARGET_UNIT_CASTER_MASTER:
            if (Unit* owner = m_caster->GetMaster())
                tempUnitList.push_back(owner);
            break;
        case TARGET_ENUM_UNITS_ENEMY_AOE_AT_DYNOBJ_LOC:
            // targets the ground, not the units in the area
            FillAreaTargets(tempUnitList, radius, cone, PUSH_DEST_CENTER, SPELL_TARGETS_AOE_ATTACKABLE);
            break;
        case TARGET_UNIT_CHANNEL_TARGET:
        {
            if (Unit* target = dynamic_cast<Unit*>(m_caster->GetChannelObject()))
                if (!CheckAndAddMagnetTarget(target, effIndex, targetB, targetingData))
                    tempUnitList.push_back(target);
            break;
        }
        case TARGET_UNIT_FRIEND_AND_PARTY:
        {
            Unit* target = m_targets.getUnitTarget();
            if (!target || !m_caster->CanAssistSpell(target, m_spellInfo))
                break;

            tempUnitList.push_back(target);

            Player* pTarget = const_cast<Player*>(target->GetControllingPlayer());
            if (!pTarget)
                break;

            if (pTarget != target)
                tempUnitList.push_back(pTarget);

            Group* pGroup = pTarget->GetGroup();

            if (pGroup)
            {
                uint8 subgroup = pTarget->GetSubGroup();

                for (GroupReference* itr = pGroup->GetFirstMember(); itr != nullptr; itr = itr->next())
                {
                    Player* groupTarget = itr->getSource();

                    if (groupTarget == target || groupTarget == pTarget)
                        continue;

                    // CanAssist check duel and controlled by enemy
                    if (groupTarget && groupTarget->GetSubGroup() == subgroup && m_caster->CanAssistSpell(groupTarget, m_spellInfo))
                    {
                        if (pTarget->IsWithinDistInMap(groupTarget, radius))
                            tempUnitList.push_back(groupTarget);

                        if (Pet* pet = groupTarget->GetPet())
                            if (pTarget->IsWithinDistInMap(pet, radius))
                                tempUnitList.push_back(pet);
                    }
                }
            }
            else if (pTarget)
            {
                if (Pet* pet = pTarget->GetPet())
                    if (m_caster->IsWithinDistInMap(pet, radius))
                        tempUnitList.push_back(pet);
            }
            break;
        }
        case TARGET_UNIT_FRIEND_CHAIN_HEAL:
        {
            Unit* unitTarget = m_targets.getUnitTarget();
            if (!unitTarget)
                break;

            if (!m_caster->CanAssistSpell(unitTarget, m_spellInfo))
                break;

            tempUnitList.push_back(unitTarget);

            if (targetingData.chainTargetCount[effIndex] > 1)
            {
                float max_range = targetingData.chainTargetCount[effIndex] * m_jumpRadius;
                Group* group = nullptr;

                //if target and caster are members in the same group then apply member only filtering
                //in regards to mc by player concerns the mc'ed player has simple toolbar thus chain heal not available
                //TODO: in regards to mc by npc concerns this is something that needs to be answered
                if (m_caster->GetTypeId() == TYPEID_PLAYER)
                {
                    if (Group* casterGroup = static_cast<Player*>(m_caster)->GetGroup())
                    {
                        switch (unitTarget->GetTypeId())
                        {
                            case TYPEID_PLAYER:
                            {
                                if (casterGroup == static_cast<Player*>(unitTarget)->GetGroup())
                                    group = casterGroup;

                                break;
                            }
                            case TYPEID_UNIT:
                            {
                                Creature* creature = static_cast<Creature*>(unitTarget);

                                if (Unit* owner = creature->GetOwner(nullptr, true))
                                {
                                    if (owner->GetTypeId() == TYPEID_PLAYER && casterGroup == static_cast<Player*>(owner)->GetGroup())
                                    {
                                        group = casterGroup;
                                    }
                                }

                                break;
                            }
                        }

                    }
                }

                UnitList tempAoeList;
                MaNGOS::AnyFriendlyOrGroupMemberUnitInUnitRangeCheck u_check(m_caster, unitTarget, group, m_spellInfo, max_range);
                MaNGOS::UnitListSearcher<MaNGOS::AnyFriendlyOrGroupMemberUnitInUnitRangeCheck> searcher(tempAoeList, u_check);
                Cell::VisitAllObjects(m_caster, searcher, max_range);
                tempAoeList.erase(std::remove(tempAoeList.begin(), tempAoeList.end(), unitTarget), tempAoeList.end());
                if (!tempAoeList.empty())
                    tempUnitList.splice(tempUnitList.end(), tempAoeList);
            }
            break;
        }
        case TARGET_UNIT_RAID_AND_CLASS:
        {
            Player* targetPlayer = m_targets.getUnitTarget() && m_targets.getUnitTarget()->GetTypeId() == TYPEID_PLAYER
                                   ? (Player*)m_targets.getUnitTarget() : nullptr;

            Group* pGroup = targetPlayer ? targetPlayer->GetGroup() : nullptr;
            if (pGroup)
            {
                float x, y, z;
                targetPlayer->GetPosition(x, y, z);
                for (GroupReference* itr = pGroup->GetFirstMember(); itr != nullptr; itr = itr->next())
                {
                    Player* target = itr->getSource();

                    // CanAssist check duel and controlled by enemy
                    if (target)
                    {
                        if (target->GetDistance(x, y, z, DIST_CALC_COMBAT_REACH) <= radius &&
                            targetPlayer->getClass() == target->getClass() &&
                            m_caster->CanAssistSpell(target, m_spellInfo))
                        {
                            tempUnitList.push_back(target);
                        }
                        if (Pet* pet = target->GetPet())
                        {
                            if (pet->GetDistance(x, y, z, DIST_CALC_COMBAT_REACH) <= radius &&
                                targetPlayer->getClass() == pet->getClass() &&
                                m_caster->CanAssistSpell(pet, m_spellInfo))
                                tempUnitList.push_back(pet);
                        }
                    }
                }
            }
            else if (m_targets.getUnitTarget())
                tempUnitList.push_back(m_targets.getUnitTarget());
            break;
        }
        case TARGET_CORPSE_ENEMY_NEAR_CASTER:
        {
            WorldObject* result = FindCorpseUsing<MaNGOS::TauntFlagObjectCheck>();
            if (result)
            {
                switch (result->GetTypeId())
                {
                    case TYPEID_UNIT:
                    case TYPEID_PLAYER:
                        tempUnitList.push_back(static_cast<Unit*>(result));
                        break;
                    case TYPEID_CORPSE:
                        if (Player* owner = ObjectAccessor::FindPlayer(static_cast<Corpse*>(result)->GetOwnerGuid()))
                            tempUnitList.push_back(owner);
                        break;
                }
            }
            break;
        }
        case TARGET_UNIT_SCRIPT_NEAR_CASTER:
        case TARGET_GAMEOBJECT_SCRIPT_NEAR_CASTER:
        case TARGET_LOCATION_SCRIPT_NEAR_CASTER:
        {
            SpellCastResult result = CheckScriptTargeting(effIndex, targetingData.chainTargetCount[effIndex], radius, targetMode, tempUnitList, tempGOList);
            if (result != SPELL_CAST_OK)
            {
                SendCastResult(result);
                finish(false);
                return;
            }
            break;
        }
        default:
            // sLog.outError( "SPELL: Unknown implicit target (%u) for spell ID %u", targetMode, m_spellInfo->Id );
            break;
    }

    // remove caster from the list if required by attribute
    if (m_spellInfo->HasAttribute(SPELL_ATTR_EX_EXCLUDE_CASTER))
        if (targetMode != TARGET_UNIT_CASTER)
            tempUnitList.remove(m_caster);
}

AddDestExecution 函数会计算飞行时间 并且设置延迟

void Spell::AddDestExecution(SpellEffectIndex effIndex)
{
    if (m_destTargetInfo.effectMask == 0)
    {
        // spell fly from visual cast object
        WorldObject* affectiveObject = GetAffectiveCasterObject();
        float speed = GetSpellSpeed();
        if (speed > 0.0f)
        {
            // calculate spell incoming interval
            float x, y, z;
            m_targets.getDestination(x, y, z);
            float dist = affectiveObject->GetDistance(x, y, z, DIST_CALC_NONE);
            dist = sqrt(dist); // default distance calculation is raw, apply sqrt before the next step
            if (dist < 5.0f)
                dist = 5.0f;
            m_destTargetInfo.timeDelay = (uint64)floor(dist / speed * 1000.0f);
            if (m_delayMoment == 0 || m_delayMoment > m_destTargetInfo.timeDelay)
                m_delayMoment = m_destTargetInfo.timeDelay;
        }
        else
            m_destTargetInfo.timeDelay = uint64(0);
    }

    uint32 effectMask = (1 << effIndex);

    effectMask &= (~m_partialApplicationMask);

    m_destTargetInfo.effectMask |= effectMask;
}

FillUnitTargets 函数填充目标 并且添加进 m_UniqueTargetInf处理容器内  这个函数需要注意的是的FilterTargetMap 这个函数会根据不同的处理策略 处理不同的对象数据

enum SpellTargetFilterScheme
{
    SCHEME_RANDOM = 0,
    SCHEME_RANDOM_CHAIN,
    SCHEME_CLOSEST,
    SCHEME_CLOSEST_CHAIN,
    SCHEME_FURTHEST,
    SCHEME_HIGHEST_HP,
    SCHEME_LOWEST_HP_CHAIN,
    SCHEME_PRIORITIZE_HEALTH,
    SCHEME_PRIORITIZE_MANA,
};
bool Spell::FillUnitTargets(TempTargetingData& targetingData, SpellTargetingData& data, uint32 i)
{
    auto& targetMask = data.targetMask[i];
    auto& ignoredTargets = data.ignoredTargets[i];
    auto& filterScheme = m_filteringScheme[i];
    for (uint8 rightTarget = 0; rightTarget < 2; ++rightTarget) // need to process target A and B separately due to effect masks
    {
        bool ignored = rightTarget ? ignoredTargets.second : ignoredTargets.first;
        if (ignored)
            continue;
        UnitList& unitTargetList = targetingData.data[i].tmpUnitList[rightTarget];
        uint8 effectMask = targetMask[rightTarget];
        SpellTargetFilterScheme scheme = filterScheme[rightTarget];
        uint32 target = rightTarget ? m_spellInfo->EffectImplicitTargetB[i] : m_spellInfo->EffectImplicitTargetA[i];
        SpellTargetImplicitType type = SpellTargetInfoTable[target].type;
        if (!unitTargetList.empty()) // Unit case
        {
            for (auto itr = unitTargetList.begin(); itr != unitTargetList.end();)
            {
                if (!CheckTarget(*itr, SpellEffectIndex(i), bool(rightTarget), CheckException(targetingData.magnet)))
                    itr = unitTargetList.erase(itr);
                else
                    ++itr;
            }

            // Special target filter before adding targets to list
            FilterTargetMap(unitTargetList, scheme, targetingData.chainTargetCount[i]);

            if (m_trueCaster->IsPlayer())
            {
                Player* me = static_cast<Player*>(m_trueCaster);
                for (auto itr = unitTargetList.begin(); itr != unitTargetList.end(); ++itr)
                {
                    Player* targetOwner = (*itr)->GetBeneficiaryPlayer();
                    if (targetOwner && targetOwner != me && targetOwner->IsPvP() && !me->IsInDuelWith(targetOwner))
                    {
                        me->UpdatePvP(true);
                        me->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_PVP_ACTIVE_CANCELS);
                        break;
                    }
                }
            }
        }

        if (m_spellInfo->HasAttribute(SPELL_ATTR_EX_REQUIRE_ALL_TARGETS) &&
            (type == TARGET_TYPE_UNIT || type == TARGET_TYPE_PLAYER))
        {
            // spells which should only be cast if a target was found
            if (unitTargetList.size() <= 0)
            {
                SendCastResult(SPELL_FAILED_BAD_TARGETS);
                finish(false);
                return false;
            }
        }

        for (Unit* unit : unitTargetList)
            AddUnitTarget(unit, effectMask);
    }

    return true;
}

void Spell::FilterTargetMap(UnitList& filterUnitList, SpellTargetFilterScheme scheme, uint32 chainTargetCount)
{
    switch (scheme)
    {
        case SCHEME_RANDOM:
        {
            if (m_affectedTargetCount && filterUnitList.size() > m_affectedTargetCount)
            {
                // remove random units from the map
                while (filterUnitList.size() > m_affectedTargetCount)
                {
                    uint32 poz = urand(0, filterUnitList.size() - 1);
                    auto itr = filterUnitList.begin();
                    std::advance(itr, poz);
                    itr = filterUnitList.erase(itr);
                }
            }
            break;
        }
        case SCHEME_CLOSEST:
        {
            if (m_affectedTargetCount && filterUnitList.size() > m_affectedTargetCount)
            {
                filterUnitList.sort(TargetDistanceOrderNear(m_trueCaster));
                filterUnitList.resize(m_affectedTargetCount);
            }
            break;
        }
        case SCHEME_FURTHEST:
        {
            if (m_affectedTargetCount && filterUnitList.size() > m_affectedTargetCount)
            {
                filterUnitList.sort(TargetDistanceOrderFarAway(m_trueCaster));
                filterUnitList.resize(m_affectedTargetCount);
            }
            break;
        }
        case SCHEME_HIGHEST_HP:
        {
            // Returns the target with the highest HP in melee range
            Unit* hatedTarget = nullptr;
            uint32 maxHP = 0;
            for (auto& unit : filterUnitList)
            {
                if (m_caster->CanReachWithMeleeAttack(unit))
                {
                    if (unit->GetHealth() > maxHP)
                    {
                        maxHP = unit->GetHealth();
                        hatedTarget = unit;
                    }
                }
            }
            if (!hatedTarget)
                return;

            filterUnitList.clear();
            filterUnitList.push_back(hatedTarget);
            break;
        }
        case SCHEME_RANDOM_CHAIN:
        {
            Unit* unitTarget = m_targets.getUnitTarget();
            if (filterUnitList.empty() || filterUnitList.front() != unitTarget)
                break;
            if (chainTargetCount > 1 && filterUnitList.size() > chainTargetCount)
            {
                // remove random units from the map
                while (filterUnitList.size() > chainTargetCount)
                {
                    uint32 poz = urand(1, filterUnitList.size() - 1);
                    auto itr = filterUnitList.begin();
                    std::advance(itr, poz);
                    itr = filterUnitList.erase(itr);
                }
            }
            break;
        }
        case SCHEME_CLOSEST_CHAIN:
        {
            Unit* unitTarget = m_targets.getUnitTarget();
            if (filterUnitList.empty() || filterUnitList.front() != unitTarget)
                break;
            UnitList newList;
            newList.push_back(unitTarget);
            filterUnitList.pop_front();
            filterUnitList.sort(TargetDistanceOrderNear(unitTarget));
            Unit* prev = unitTarget;
            UnitList::iterator next = filterUnitList.begin();
            chainTargetCount -= 1; // unit target is one

            while (chainTargetCount && next != filterUnitList.end())
            {
                if (prev->GetDistance(*next, true, DIST_CALC_NONE) > m_jumpRadius * m_jumpRadius)
                    break;

                if (!prev->IsWithinLOSInMap(*next, true))
                {
                    ++next;
                    continue;
                }
                prev = *next;
                newList.push_back(prev);
                filterUnitList.erase(next);
                filterUnitList.sort(TargetDistanceOrderNear(prev));
                next = filterUnitList.begin();

                --chainTargetCount;
            }
            std::swap(filterUnitList, newList);
            break;
        }
        case SCHEME_LOWEST_HP_CHAIN:
        {
            Unit* unitTarget = m_targets.getUnitTarget();
            if (filterUnitList.empty() || filterUnitList.front() != unitTarget)
                break;
            UnitList newList;
            newList.push_back(unitTarget);
            bool isInGroup = m_caster->IsInGroup(unitTarget);
            filterUnitList.pop_front();
            filterUnitList.sort(LowestHPNearestOrder(unitTarget));
            Unit* prev = unitTarget;
            UnitList::iterator next = filterUnitList.begin();
            chainTargetCount -= 1; // unit target is one

            while (chainTargetCount && next != filterUnitList.end())
            {
                // since we do not iterate through closest but through HP, we must check all units
                if (prev->GetDistance(*next, true, DIST_CALC_NONE) > m_jumpRadius * m_jumpRadius)
                {
                    ++next;
                    continue;
                }

                if (!prev->IsWithinLOSInMap(*next, true))
                {
                    ++next;
                    continue;
                }

                if (isInGroup && !m_caster->IsInGroup(*next))
                {
                    ++next;
                    continue;
                }

                prev = *next;
                newList.push_back(prev);
                filterUnitList.erase(next);
                filterUnitList.sort(LowestHPNearestOrder(prev));
                next = filterUnitList.begin();

                --chainTargetCount;
            }
            std::swap(filterUnitList, newList);
            break;
        }
        case SCHEME_PRIORITIZE_HEALTH:
        {
            PrioritizeHealthUnitQueue healthQueue;
            for (Unit* unit : filterUnitList)
                if (!unit->IsDead())
                    healthQueue.push(PrioritizeHealthUnitWraper(unit));

            filterUnitList.clear();
            while (!healthQueue.empty() && filterUnitList.size() < m_affectedTargetCount)
            {
                filterUnitList.push_back(healthQueue.top().getUnit());
                healthQueue.pop();
            }
            break;
        }
        case SCHEME_PRIORITIZE_MANA:
        {
            PrioritizeManaUnitQueue manaUsers;
            for (Unit* unit : filterUnitList)
                if (unit->GetPowerType() == POWER_MANA && !unit->IsDead())
                    manaUsers.push(PrioritizeManaUnitWraper(unit));

            filterUnitList.clear();
            while (!manaUsers.empty() && filterUnitList.size() < m_affectedTargetCount)
            {
                filterUnitList.push_back(manaUsers.top().getUnit());
                manaUsers.pop();
            }
            break;
        }
    }
}

void Spell::AddUnitTarget(Unit* target, uint8 effectMask, CheckException exception)
{
    if (m_trueCaster != target)
        m_targets.m_targetMask |= TARGET_FLAG_UNIT; // all spells with unit target must have this flag

    effectMask &= (~m_partialApplicationMask); // remove partially immuned out effects

    uint8 executionlessMask = 0;
    auto& data = SpellTargetMgr::GetSpellTargetingData(m_spellInfo->Id);
    for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i)
        if (data.implicitType[i] == TARGET_TYPE_SPECIAL_DEST)
            executionlessMask |= (1 << i);

    // Check for effect immune skip if immuned
    uint8 notImmunedMask = 0;
    for (uint8 effIndex = 0; effIndex < MAX_EFFECT_INDEX; ++effIndex)
        if ((effectMask & (1 << effIndex)) != 0)
            if (!target->IsImmuneToSpellEffect(m_spellInfo, SpellEffectIndex(effIndex), target == m_trueCaster))
                notImmunedMask |= (1 << effIndex);

    if (m_spellInfo->HasAttribute(SPELL_ATTR_EX4_NO_PARTIAL_IMMUNITY) && notImmunedMask != effectMask)
        notImmunedMask = 0; // if one effect immunes out, whole target immunes out

    ObjectGuid targetGUID = target->GetObjectGuid();

    // Lookup target in already in list
    for (auto& ihit : m_UniqueTargetInfo)
    {
        if (targetGUID == ihit.targetGUID)                 // Found in list
        {
            ihit.effectMask |= (effectMask &~ executionlessMask);
            ihit.effectHitMask |= (notImmunedMask &~ executionlessMask);
            return;
        }
    }

    // This is new target calculate data for him

    // Get spell hit result on target
    TargetInfo targetInfo;
    targetInfo.targetGUID = targetGUID;                         // Store target GUID
    targetInfo.effectHitMask = exception != EXCEPTION_MAGNET ? notImmunedMask : effectMask; // Store not immuned effects
    targetInfo.effectMask = effectMask;                         // Store index of effect
    targetInfo.effectMaskProcessed = 0;
    targetInfo.processed  = false;                              // Effects not applied on target
    targetInfo.magnet = (exception == EXCEPTION_MAGNET);
    targetInfo.procReflect = false;
    targetInfo.isCrit = false;
    targetInfo.heartbeatResistChance = 0;
    targetInfo.effectDuration = CalculateSpellDuration(m_spellInfo, m_caster, target, m_auraScript);
    targetInfo.diminishDuration = targetInfo.effectDuration;
    targetInfo.diminishLevel = DIMINISHING_LEVEL_1;
    targetInfo.diminishGroup = DIMINISHING_NONE;
    targetInfo.executionless = false;
    targetInfo.timeDelay = 0;

    // Calculate hit result
    targetInfo.missCondition = m_ignoreHitResult ? SPELL_MISS_NONE : Unit::SpellHitResult(m_trueCaster, target, m_spellInfo, targetInfo.effectMask, m_reflectable, false, &targetInfo.heartbeatResistChance);

    if ((executionlessMask & targetInfo.effectMask) != 0)
    {
        targetInfo.effectMask &= ~executionlessMask;
        targetInfo.effectHitMask &= ~executionlessMask;
        targetInfo.executionless = true;
    }

    // spell fly from visual cast object
    WorldObject* affectiveObject = GetAffectiveCasterObject();

    // Spell have speed (possible inherited from triggering spell) - need calculate incoming time
    float speed = GetSpellSpeed();
    if (speed > 0.0f && affectiveObject && target != affectiveObject)
    {
        WorldObject const* missileSource = affectiveObject;

        if (m_spellInfo->HasAttribute(SPELL_ATTR_EX4_BOUNCY_CHAIN_MISSILES))
        {
            auto previousTargetItr = std::find_if(m_UniqueTargetInfo.rbegin(), m_UniqueTargetInfo.rend(), [effectMask](TargetInfo const& target)
            {
                return (target.effectMask & effectMask) != 0;
            });
            if (previousTargetItr != std::rend(m_UniqueTargetInfo))
            {
                if (WorldObject* previousTarget = m_caster->GetMap()->GetWorldObject(previousTargetItr->targetGUID))
                    missileSource = previousTarget;

                targetInfo.timeDelay += previousTargetItr->timeDelay;
            }
        }

        // calculate spell incoming interval
        float dist = missileSource->GetDistance(target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), DIST_CALC_NONE);
        dist = sqrt(dist); // default distance calculation is raw, apply sqrt before the next step

        if (dist < 5.0f)
            dist = 5.0f;
        targetInfo.timeDelay += (uint64) floor(dist / speed * 1000.0f);

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

    // If target reflect spell back to caster
    if (targetInfo.missCondition == SPELL_MISS_REFLECT)
    {
        // Objects vs units case: traps and similar
        // TBC+: Reflect simply negates the spell
        if (!m_originalCasterGUID.IsUnit())
        {
            Unit::ProcDamageAndSpell(ProcSystemArguments(nullptr, target, PROC_FLAG_NONE, PROC_FLAG_TAKE_HARMFUL_SPELL, PROC_EX_REFLECT, 1, BASE_ATTACK, m_spellInfo));
            targetInfo.reflectResult = SPELL_MISS_REFLECT;
        }
        else
        {
            // need to recheck reflect immuned effects
            notImmunedMask = 0;
            for (uint8 effIndex = 0; effIndex < MAX_EFFECT_INDEX; ++effIndex)
                if ((effectMask & (1 << effIndex)) != 0)
                    if (!target->IsImmuneToSpellEffect(m_spellInfo, SpellEffectIndex(effIndex), target == m_trueCaster))
                        notImmunedMask |= (1 << effIndex);

            targetInfo.effectHitMask = notImmunedMask;
            // Calculate reflected spell result on caster
            if (m_caster)
                targetInfo.reflectResult = Unit::SpellHitResult(m_caster, m_caster, m_spellInfo, targetInfo.effectMask, m_reflectable, true, &targetInfo.heartbeatResistChance);
            // Caster reflects back spell which was already reflected by victim
            if (targetInfo.reflectResult == SPELL_MISS_REFLECT || !m_caster)
                // Full circle: it's impossible to reflect further, "Immune" shows up
                targetInfo.reflectResult = SPELL_MISS_IMMUNE;

            // Store time interval so we know how fast to return it back
            targetInfo.initialDelay = targetInfo.timeDelay;
            targetInfo.procReflect = true;
            if (targetInfo.timeDelay == 0) // if no time delay, proc reflect procs immediately
                ProcReflectProcs(targetInfo);
        }
    }
    else
        targetInfo.reflectResult = SPELL_MISS_NONE;

    // only check DR for units and unit owned GOs
    if (Unit* realCaster = GetAffectiveCasterOrOwner())
    {
        bool isReflected = targetInfo.missCondition == SPELL_MISS_REFLECT && targetInfo.reflectResult == SPELL_MISS_NONE;
        Unit* targetForDiminish = isReflected ? m_caster : target;
        if (IsAuraApplyEffects(m_spellInfo, SpellEffectIndexMask(targetInfo.effectHitMask)))
            targetInfo.effectDuration = targetForDiminish->CalculateAuraDuration(m_spellInfo, effectMask, targetInfo.effectDuration, realCaster);
        if ((targetInfo.missCondition == SPELL_MISS_NONE || isReflected) && CanSpellDiminish())
        {
            targetInfo.diminishGroup = GetDiminishingReturnsGroupForSpell(m_spellInfo, m_triggeredByAuraSpell != nullptr || (m_IsTriggeredSpell && m_CastItem));
            targetInfo.diminishLevel = targetForDiminish->GetDiminishing(targetInfo.diminishGroup);

            if (targetInfo.effectDuration > 0)
            {
                int32 duration = targetInfo.effectDuration;
                targetForDiminish->ApplyDiminishingToDuration(targetInfo.diminishGroup, duration, realCaster, targetInfo.diminishLevel, isReflected, m_spellInfo, m_auraScript);
                targetInfo.diminishDuration = duration;
                if (targetInfo.diminishDuration == 0 && targetInfo.diminishLevel == DIMINISHING_LEVEL_IMMUNE)
                    targetInfo.effectHitMask &= (~GetAuraEffectMask(m_spellInfo));
            }
        }
    }

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

void Spell::AddGOTarget(GameObject* target, uint8 effectMask)
{
    m_targets.m_targetMask |= TARGET_FLAG_GAMEOBJECT; // all spells with GO target must have this flag TODO: do this based on target type

    ObjectGuid targetGUID = target->GetObjectGuid();

    // Lookup target in already in list
    for (auto& ihit : 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 targetInfo;
    targetInfo.targetGUID = targetGUID;
    targetInfo.effectMask = effectMask;
    targetInfo.processed  = false;                              // Effects not apply on target

    // spell fly from visual cast object
    WorldObject* affectiveObject = GetAffectiveCasterObject();

    // Spell can have speed - need calculate incoming time
    float speed = GetSpellSpeed();
    if (speed > 0.0f && affectiveObject && target != affectiveObject)
    {
        // calculate spell incoming interval
        float dist = affectiveObject->GetDistance(target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), DIST_CALC_NONE);
        dist = sqrt(dist); // default distance calculation is raw, apply sqrt before the next step
        if (dist < 5.0f)
            dist = 5.0f;
        targetInfo.timeDelay = (uint64) floor(dist / speed * 1000.0f);
        if (m_delayMoment == 0 || m_delayMoment > targetInfo.timeDelay)
            m_delayMoment = targetInfo.timeDelay;
    }
    else
        targetInfo.timeDelay = uint64(0);

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

目标填充完毕后  就到了预处理函数了  填充伤害 以及 一些光环效果是否触发

void Spell::_handle_immediate_phase()
{
    m_spellState = SPELL_STATE_LANDING;

    if (m_caster && IsMeleeAttackResetSpell())
    {
        if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX2_DO_NOT_RESET_COMBAT_TIMERS))
        {
            m_caster->resetAttackTimer(BASE_ATTACK);
            if (m_caster->hasOffhandWeaponForAttack())
                m_caster->resetAttackTimer(OFF_ATTACK);
        }
    }

    // 处理技能仇恨值
    HandleThreatSpells();

    // 生成没有目标的特殊效果 
    DoAllTargetlessEffects(false);

    if (!IsDelayedSpell())
        DoAllTargetlessEffects(true);

    // 处理由物品触发目标的效果 计算伤害倍数
    for (auto& ihit : m_UniqueItemInfo)
        DoAllEffectOnTarget(&ihit);

    // take cast item after processing items
    TakeCastItem();
    
    // 计算有每个目标触发的效果伤害等
    for (auto& ihit : m_UniqueTargetInfo)
        HandleImmediateEffectExecution(&ihit);

    // hi姐处理玩家给自己释放的效果 
    for (auto& ihit : m_UniqueTargetInfo)
    {
        if (ihit.targetGUID == m_trueCaster->GetObjectGuid())
        {
            DoAllEffectOnTarget(&ihit);
            break;
        }
    }
    //检查是否触发玩家光环技能的额外法术
    ProcSpellAuraTriggers();

    // start channeling if applicable (after _handle_immediate_phase for get persistent effect dynamic object for channel target
    if (IsChanneledSpell(m_spellInfo) && m_duration)
    {
        m_spellState = SPELL_STATE_CHANNELING;
        SendChannelStart(m_duration);
    }
}

DoAllTargetlessEffects 跟DoAllEffectOnTarget等函数最关键的函数是HandleEffect  


void Spell::HandleEffect(Unit* pUnitTarget, Item* pItemTarget, GameObject* pGOTarget, SpellEffectIndex i, float DamageMultiplier)
{
    if (m_effectTriggerChance[i] != -1)
    {
        if (m_effectTriggerChance[i] == 0 || irand(1, 100) > m_effectTriggerChance[i])
            return;
    }

    unitTarget = pUnitTarget;
    itemTarget = pItemTarget;
    gameObjTarget = pGOTarget;

    uint8 eff = m_spellInfo->Effect[i];

    if (IsEffectWithImplementedMultiplier(eff))
    {
        m_healingPerEffect[i] = 0;
        m_damagePerEffect[i] = 0;
        damage = CalculateSpellEffectValue(i, unitTarget);
    }
    else
        damage = int32(CalculateSpellEffectValue(i, unitTarget) * DamageMultiplier);

    DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell %u Effect%d : %u Targets: %s, %s, %s",
                     m_spellInfo->Id, i, eff,
                     unitTarget ? unitTarget->GetGuidStr().c_str() : "-",
                     itemTarget ? itemTarget->GetGuidStr().c_str() : "-",
                     gameObjTarget ? gameObjTarget->GetGuidStr().c_str() : "-");

    if (eff < MAX_SPELL_EFFECTS)
    {
        OnEffectExecute(i);
        damagePerEffect[i] = damage;
        (*this.*SpellEffects[eff])(i);
    }
    else
        sLog.outError("WORLD: Spell FX %d > MAX_SPELL_EFFECTS ", eff);

    if (IsEffectWithImplementedMultiplier(eff))
    {
        if (m_healingPerEffect[i] > 0)
            m_healing += int32(m_healingPerEffect[i] * DamageMultiplier);
        else if (m_damagePerEffect[i] > 0)
            m_damage += int32(m_damagePerEffect[i] * DamageMultiplier);
    }
}

效果中最重要的为光环类效果 例如BUFF系统 坐骑系统  等都是使用的BUFF系统 下面以光环类特效举例 


pEffect SpellEffects[MAX_SPELL_EFFECTS] =
{
    &Spell::EffectNULL,                                     //  0
    &Spell::EffectInstaKill,                                //  1 SPELL_EFFECT_INSTAKILL
    &Spell::EffectSchoolDMG,                                //  2 SPELL_EFFECT_SCHOOL_DAMAGE
    &Spell::EffectDummy,                                    //  3 SPELL_EFFECT_DUMMY
    &Spell::EffectUnused,                                   //  4 SPELL_EFFECT_PORTAL_TELEPORT          unused from pre-1.2.1
    &Spell::EffectTeleportUnits,                            //  5 SPELL_EFFECT_TELEPORT_UNITS
    &Spell::EffectApplyAura,                                //  6 SPELL_EFFECT_APPLY_AURA
    &Spell::EffectEnvironmentalDMG,                         //  7 SPELL_EFFECT_ENVIRONMENTAL_DAMAGE
    &Spell::EffectPowerDrain,                               //  8 SPELL_EFFECT_POWER_DRAIN
    &Spell::EffectHealthLeech,                              //  9 SPELL_EFFECT_HEALTH_LEECH
}

EffectApplyAura回调函数

void Spell::EffectApplyAura(SpellEffectIndex eff_idx)
{
    if (!unitTarget)
        return;

    // ghost spell check, allow apply any auras at player loading in ghost mode (will be cleanup after load)
    if ((!unitTarget->IsAlive() && !(IsDeathOnlySpell(m_spellInfo) || IsDeathPersistentSpell(m_spellInfo))) &&
        (unitTarget->GetTypeId() != TYPEID_PLAYER || !((Player*)unitTarget)->GetSession()->PlayerLoading()))
        return;

    // GO auras have caster == nullptr
    Unit* caster = GetAffectiveCaster();

    DEBUG_FILTER_LOG(LOG_FILTER_SPELL_CAST, "Spell: Aura is: %u", m_spellInfo->EffectApplyAuraName[eff_idx]);

    Aura* aur = CreateAura(m_spellInfo, eff_idx, &damage, &m_currentBasePoints[eff_idx], m_spellAuraHolder, unitTarget, caster, m_CastItem, GetScriptValue());
    m_spellAuraHolder->AddAura(aur, eff_idx);
}
void SpellAuraHolder::AddAura(Aura* aura, SpellEffectIndex index)
{
    m_auras[index] = aura;
}

这里就创建了光环系统 挂靠到了法术下面  最后通过DoSpellHitOnUnit挂载到对象身上

继续法术系统  Unit::ProcDamageAndSpell 会计算玩家身上现在有的一些回调函数

假如不为延迟法术 就会进入到最后处理计算伤害函数

void Spell::handle_immediate()
{
    // AOE caps implementation - only works for non-travelling spells
    ProcessAOECaps();

    for (auto& ihit : m_UniqueTargetInfo)
        DoAllEffectOnTarget(&ihit);

    for (auto& ihit : m_uniqueCorpseTargetInfo)
        DoAllEffectOnTarget(&ihit);

    for (auto& ihit : m_UniqueGOTargetInfo)
        DoAllEffectOnTarget(&ihit);

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

    if (m_spellState != SPELL_STATE_CHANNELING)
        finish();                                       // successfully finish spell cast (not last in case autorepeat or channel spell)
}
void Spell::DoAllEffectOnTarget(TargetInfo* target)
{
    if (target->processed || target->effectMask == 0)       // Check target
        return;
    target->processed = true;                               // Target checked in apply effects procedure

    // Get mask of effects for target
    uint32 effectMask = target->effectHitMask &~ target->effectMaskProcessed;

    Unit* unit = m_trueCaster->GetObjectGuid() == target->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_trueCaster, target->targetGUID);
    if (!unit)
        return;

    // Get original caster (if exist) and calculate damage/healing from him data
    Unit* affectiveCaster = GetAffectiveCasterOrOwner();
    // FIXME: in case wild GO heal/damage spells will be used target bonuses
    WorldObject* caster = affectiveCaster ? affectiveCaster : m_trueCaster;

    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;
    Unit* reflectTarget = nullptr;

    // Reset damage/healing counter
    ResetEffectDamageAndHeal();

    // Fill base trigger info
    uint32 procAttacker;
    uint32 procVictim;
    uint32 procEx = PROC_EX_NONE;
    PrepareMasksForProcSystem(target->effectMask, procAttacker, procVictim, caster, unitTarget);
    if (target->magnet)
        procEx |= PROC_EX_MAGNET;

    // drop proc flags in case target not affected negative effects in negative spell
    // for example caster bonus or animation,
    // except miss case where will assigned PROC_EX_* flags later
    if (((procAttacker | procVictim) & NEGATIVE_TRIGGER_MASK) &&
            !(target->effectHitMask & m_negativeEffectMask) && missInfo == SPELL_MISS_NONE)
    {
        procAttacker = PROC_FLAG_NONE;
        procVictim   = PROC_FLAG_NONE;
    }

    m_damage = target->damage;
    m_healing = target->healing;

    if (missInfo == SPELL_MISS_NONE)                        // In case spell hit target, do all effect on that target
        DoSpellHitOnUnit(unit, effectMask, target);
    else if (missInfo != SPELL_MISS_EVADE)
    {
        if (missInfo == SPELL_MISS_REFLECT)                // In case spell reflect from target, do all effect on caster (if hit)
        {
            if (target->reflectResult == SPELL_MISS_NONE)       // If reflected spell hit caster -> do all effect on him
            {
                DoSpellHitOnUnit(m_caster, effectMask, target, true);
                reflectTarget = unit;
                unitTarget = m_caster;
            }
        }

        // Failed hostile spell hits count as attack made against target (if detected)
        if (affectiveCaster && affectiveCaster != unit)
            m_caster->CasterHitTargetWithSpell(affectiveCaster, unit, m_spellInfo, m_IsTriggeredSpell, false);
    }

    // All calculated do it!
    // Do healing and triggers
    if (m_healing && affectiveCaster) // does not support GO casts atm
    {
        uint32 addhealth = m_healing;
        if (target->isCrit)
        {
            procEx |= PROC_EX_CRITICAL_HIT;
            addhealth = affectiveCaster->CalculateCritAmount(nullptr, addhealth, m_spellInfo, true);
        }
        else
            procEx |= PROC_EX_NORMAL_HIT;

        m_healing = addhealth; // update value so that script handler has access
        OnHit(missInfo); // TODO: After spell damage calc is moved to proper handler - move this before the first if

        int32 gain = affectiveCaster->DealHeal(unitTarget, addhealth, m_spellInfo, target->isCrit);

        if (affectiveCaster)
            unitTarget->getHostileRefManager().threatAssist(affectiveCaster, float(gain) * 0.5f * sSpellMgr.GetSpellThreatMultiplier(m_spellInfo), m_spellInfo, false, m_IsTriggeredSpell);

        // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge)
        if (m_canTrigger && missInfo != SPELL_MISS_REFLECT)
            Unit::ProcDamageAndSpell(ProcSystemArguments(affectiveCaster, unitTarget, affectiveCaster ? procAttacker : uint32(PROC_FLAG_NONE), procVictim, procEx, addhealth, m_attackType, m_spellInfo, this, gain, true));
    }
    // Do damage and triggers
    else if (m_damage)
    {
        // Fill base damage struct (unitTarget - is real spell target)
        SpellNonMeleeDamage spellDamageInfo(caster, unitTarget, m_spellInfo->Id, m_spellSchoolMask, this);

        spellDamageInfo.damage = m_damage;
        spellDamageInfo.HitInfo = target->HitInfo;
        if (!m_spellInfo->HasAttribute(SPELL_ATTR_EX3_IGNORE_CASTER_MODIFIERS))
        {
            if (target->isCrit) // GOs cant crit
            {
                spellDamageInfo.HitInfo |= SPELL_HIT_TYPE_CRIT;
                spellDamageInfo.damage = affectiveCaster->CalculateCritAmount(unitTarget, spellDamageInfo.damage, m_spellInfo);
            }
        }
        // damage mitigation
        if (spellDamageInfo.damage > 0)
        {
            // physical damage => armor
            if (m_spellSchoolMask & SPELL_SCHOOL_MASK_NORMAL)
                spellDamageInfo.damage = Unit::CalcArmorReducedDamage(affectiveCaster ? affectiveCaster : m_trueCaster, unitTarget, spellDamageInfo.damage);
        }

        unitTarget->CalculateAbsorbResistBlock(affectiveCaster, &spellDamageInfo, m_spellInfo);

        Unit::DealDamageMods(affectiveCaster, spellDamageInfo.target, spellDamageInfo.damage, &spellDamageInfo.absorb, SPELL_DIRECT_DAMAGE, m_spellInfo);

        m_absorb = spellDamageInfo.absorb;
        m_damage = spellDamageInfo.damage; // update value so that script handler has access
        OnHit(missInfo); // TODO: After spell damage calc is moved to proper handler - move this before the first if

        // Send log damage message to client
        Unit::SendSpellNonMeleeDamageLog(&spellDamageInfo);

        procEx |= createProcExtendMask(&spellDamageInfo, missInfo);
        procVictim |= PROC_FLAG_TAKE_ANY_DAMAGE;

        if (reflectTarget)
            Unit::DealSpellDamage(affectiveCaster, &spellDamageInfo, true, m_resetLeash);
        else
            Unit::DealSpellDamage(affectiveCaster, &spellDamageInfo, true, m_resetLeash);

        if (m_spellInfo->SpellFamilyName == SPELLFAMILY_WARRIOR && m_spellInfo->SpellFamilyFlags & uint64(0x40000000000))
        {
            uint32 BTAura = 0;
            switch (m_spellInfo->Id)
            {
                case 23881: BTAura = 23885; break;
                case 23892: BTAura = 23886; break;
                case 23893: BTAura = 23887; break;
                case 23894: BTAura = 23888; break;
                case 25251: BTAura = 25252; break;
                case 30335: BTAura = 30339; break;
                default:
                    sLog.outError("Spell::EffectSchoolDMG: Spell %u not handled in BTAura", m_spellInfo->Id);
                    break;
            }
            if (BTAura)
                m_caster->CastSpell(m_caster, BTAura, TRIGGERED_OLD_TRIGGERED);
        }

        // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge)
        if (m_canTrigger && missInfo != SPELL_MISS_REFLECT)
            Unit::ProcDamageAndSpell(ProcSystemArguments(affectiveCaster, unitTarget, affectiveCaster ? procAttacker : uint32(PROC_FLAG_NONE), procVictim, procEx, spellDamageInfo.damage, m_attackType, m_spellInfo, this));
    }
    // Passive spell hits/misses or active spells only misses (only triggers if proc flags set)
    else if (procAttacker || procVictim)
    {
        OnHit(missInfo); // TODO: After spell damage calc is moved to proper handler - move this before the first if
        // Fill base damage struct (unitTarget - is real spell target)
        SpellNonMeleeDamage damageInfo(caster, unitTarget, m_spellInfo->Id, m_spellSchoolMask);
        procEx = createProcExtendMask(&damageInfo, missInfo);
        // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge)
        if (m_canTrigger && missInfo != SPELL_MISS_REFLECT)
            // traps need to be procced at trap triggerer
            Unit::ProcDamageAndSpell(ProcSystemArguments(affectiveCaster, procAttacker & PROC_FLAG_ON_TRAP_ACTIVATION ? m_targets.getUnitTarget() : unit, affectiveCaster ? procAttacker : uint32(PROC_FLAG_NONE), procVictim, procEx, 0, m_attackType, m_spellInfo, this));
    }

    OnAfterHit();

    if (unit->IsCreature())
        // cast at creature (or GO) quest objectives update at successful cast finished (+channel finished)
        // ignore pets or autorepeat/melee casts for speed (not exist quest for spells (hm... )
        if (affectiveCaster && !static_cast<Creature*>(unit)->IsPet() && !IsAutoRepeat() && !IsNextMeleeSwingSpell(m_spellInfo) && !IsChannelActive())
            if (Player* p = affectiveCaster->GetBeneficiaryPlayer())
                p->RewardPlayerAndGroupAtCast(unit, m_spellInfo->Id);

    // Call scripted function for AI if this spell is casted upon a creature
    if (unit->AI())
        unit->AI()->SpellHit(m_caster, m_spellInfo);

    // Call scripted function for AI if this spell is casted by a creature
    if (m_trueCaster->IsCreature() && static_cast<Creature*>(m_trueCaster)->AI())
        static_cast<Creature*>(m_trueCaster)->AI()->SpellHitTarget(unit, m_spellInfo, missInfo);
    if (affectiveCaster && affectiveCaster != m_caster && affectiveCaster->IsCreature() && static_cast<Creature*>(affectiveCaster)->AI())
        static_cast<Creature*>(affectiveCaster)->AI()->SpellHitTarget(unit, m_spellInfo, missInfo);

    if (m_spellAuraHolder && m_caster)
    {
        if (m_caster->IsSpellProccingHappening())
            m_caster->AddDelayedHolderDueToProc(m_spellAuraHolder);
        else
            m_spellAuraHolder->SetState(SPELLAURAHOLDER_STATE_READY);
    }
}

上面函数 最关键的2个函数是DoSpellHitOnUnit 及DealSpellDamage函数


void Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, TargetInfo* target, bool isReflected)
{
    if (!unit)
        return;

    const bool traveling = m_spellState == SPELL_STATE_TRAVELING;

    // Recheck immune (only for delayed spells)
    if (traveling && !m_spellInfo->HasAttribute(SPELL_ATTR_NO_IMMUNITIES))
    {
        uint8 notImmunedMask = 0;
        for (uint8 effIndex = 0; effIndex < MAX_EFFECT_INDEX; ++effIndex)
            if ((target->effectHitMask & (1 << effIndex)) != 0)
                if (!unit->IsImmuneToSpellEffect(m_spellInfo, SpellEffectIndex(effIndex), unit == m_trueCaster))
                    notImmunedMask |= (1 << effIndex);
        effectMask = notImmunedMask & ~target->effectMaskProcessed;
        if (!notImmunedMask ||
            unit->IsImmuneToDamage(GetSpellSchoolMask(m_spellInfo)) ||
            unit->IsImmuneToSpell(m_spellInfo, unit == m_trueCaster, effectMask, m_trueCaster))
        {
            Unit::SendSpellMiss(m_trueCaster, unit, m_spellInfo->Id, SPELL_MISS_IMMUNE);

            ResetEffectDamageAndHeal();
            return;
        }
    }

    if (traveling && m_trueCaster != unit)
    {
        if (m_trueCaster->CanAttackSpell(unit, m_spellInfo, !m_spellInfo->HasAttribute(SPELL_ATTR_EX5_IGNORE_AREA_EFFECT_PVP_CHECK)))
        {
            // for delayed spells ignore not visible explicit target, if caster is dead, nothing is visible for him
            if (unit == m_targets.getUnitTarget() && ((m_trueCaster->IsGameObject() && !unit->IsAlive()) || !unit->IsVisibleForOrDetect(m_caster, m_caster, false, false, true, true, m_spellInfo->HasAttribute(SPELL_ATTR_EX6_IGNORE_PHASE_SHIFT))))
            {
                ResetEffectDamageAndHeal();
                return;
            }
        }
        else
        {
            // for delayed spells ignore negative spells (after duel end) for friendly targets
            if (!IsPositiveSpell(m_spellInfo->Id, m_trueCaster, unit))
            {
                Unit::SendSpellMiss(m_trueCaster, unit, m_spellInfo->Id, SPELL_MISS_IMMUNE);
                ResetEffectDamageAndHeal();
                return;
            }
        }
    }

    // Apply additional spell effects to target
    CastPreCastSpells(unit);

    Unit* affectiveCaster = GetAffectiveCasterOrOwner();
    if (IsSpellAppliesAura(m_spellInfo, effectMask))
    {
        if (affectiveCaster)
        {
            if (traveling) // if travelling, need to recalculate diminishing level and duration
            {
                target->diminishLevel = unit->GetDiminishing(target->diminishGroup);
                if (target->effectDuration > 0)
                {
                    int32 duration = target->effectDuration;
                    unit->ApplyDiminishingToDuration(target->diminishGroup, duration, m_caster, target->diminishLevel, isReflected, m_spellInfo, m_auraScript);
                    target->diminishDuration = duration;
                }
            }

            if (m_duration != target->diminishDuration && target->diminishDuration == 0 && target->diminishLevel > DIMINISHING_LEVEL_1 && !IsSpellWithNonAuraEffect(m_spellInfo))
            {
                Unit::SendSpellMiss(m_trueCaster, unit, m_spellInfo->Id, SPELL_MISS_IMMUNE);
                ResetEffectDamageAndHeal();
                return;
            }

            const bool pvp = (unit->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) && affectiveCaster->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED));
            // Increase Diminishing on unit, actual durations are already calculated
            if (IsSubjectToDiminishingLevels(target->diminishGroup, pvp))
                unit->IncrDiminishing(target->diminishGroup, pvp);
        }

        // Only unit caster to be passed here
        m_spellAuraHolder = CreateSpellAuraHolder(m_spellInfo, unit, m_trueCaster, m_CastItem, m_triggeredBySpellInfo);
        m_spellAuraHolder->setDiminishGroup(target->diminishGroup);
    }
    else
        m_spellAuraHolder = nullptr;

    ExecuteEffects(unit, nullptr, nullptr, effectMask);

    if (affectiveCaster && affectiveCaster != unit && m_caster)
        Unit::CasterHitTargetWithSpell(affectiveCaster, unit, m_spellInfo, m_IsTriggeredSpell);

    // now apply all created auras
    if (m_spellAuraHolder)
    {
        // normally shouldn't happen
        if (!m_spellAuraHolder->IsEmptyHolder())
        {
            int32 duration = m_spellAuraHolder->GetAuraMaxDuration();
            int32 originalDuration = duration;

            if (duration > 0 && target->diminishGroup > DIMINISHING_NONE)
            {
                duration = target->diminishDuration;
                // Fully diminished
                if (duration == 0)
                {
                    delete m_spellAuraHolder;
                    m_spellAuraHolder = nullptr;
                    return;
                }
            }

            if (duration != originalDuration)
            {
                m_spellAuraHolder->SetAuraMaxDuration(duration);
                m_spellAuraHolder->SetAuraDuration(duration);
            }

            if (originalDuration > 0 && target && target->heartbeatResistChance && !m_spellAuraHolder->IsPositive())
            {
                // TBC+: do not set heartbeat in pvp
                if (bool heartbeat = !(m_trueCaster->IsControlledByPlayer() && unit->IsPlayerControlled()))
                    m_spellAuraHolder->SetHeartbeatResist(target->heartbeatResistChance, originalDuration, uint32(target->diminishLevel));
            }

            if (!unit->AddSpellAuraHolder(m_spellAuraHolder))
            {
                delete m_spellAuraHolder;
                m_spellAuraHolder = nullptr;
            }
        }
        else
        {
            delete m_spellAuraHolder;
            m_spellAuraHolder = nullptr;
        }
    }
}

注意的是 下面2个函数 会给目标身上挂上光环属性

        m_spellAuraHolder = CreateSpellAuraHolder(m_spellInfo, unit, m_trueCaster, m_CastItem, m_triggeredBySpellInfo);
        m_spellAuraHolder->setDiminishGroup(target->diminishGroup);
  if (m_spellAuraHolder)
    {
        // normally shouldn't happen
        if (!m_spellAuraHolder->IsEmptyHolder())
        {
            int32 duration = m_spellAuraHolder->GetAuraMaxDuration();
            int32 originalDuration = duration;

            if (duration > 0 && target->diminishGroup > DIMINISHING_NONE)
            {
                duration = target->diminishDuration;
                // Fully diminished
                if (duration == 0)
                {
                    delete m_spellAuraHolder;
                    m_spellAuraHolder = nullptr;
                    return;
                }
            }

            if (duration != originalDuration)
            {
                m_spellAuraHolder->SetAuraMaxDuration(duration);
                m_spellAuraHolder->SetAuraDuration(duration);
            }

            if (originalDuration > 0 && target && target->heartbeatResistChance && !m_spellAuraHolder->IsPositive())
            {
                // TBC+: do not set heartbeat in pvp
                if (bool heartbeat = !(m_trueCaster->IsControlledByPlayer() && unit->IsPlayerControlled()))
                    m_spellAuraHolder->SetHeartbeatResist(target->heartbeatResistChance, originalDuration, uint32(target->diminishLevel));
            }

            if (!unit->AddSpellAuraHolder(m_spellAuraHolder))
            {
                delete m_spellAuraHolder;
                m_spellAuraHolder = nullptr;
            }
        }
        else
        {
            delete m_spellAuraHolder;
            m_spellAuraHolder = nullptr;
        }
    }

void Unit::DealSpellDamage(Unit* affectiveCaster, SpellNonMeleeDamage* spellDamageInfo, bool durabilityLoss, bool resetLeash)
{
    if (!spellDamageInfo)
        return;

    Unit* victim = spellDamageInfo->target;
    if (!victim)
        return;

    if (!victim->IsAlive() || victim->IsTaxiFlying() || victim->GetCombatManager().IsInEvadeMode())
        return;

    SpellEntry const* spellProto = sSpellTemplate.LookupEntry<SpellEntry>(spellDamageInfo->SpellID);
    if (spellProto == nullptr)
    {
        sLog.outError("Unit::DealSpellDamage have wrong damageInfo->SpellID: %u", spellDamageInfo->SpellID);
        return;
    }

    // You don't lose health from damage taken from another player while in a sanctuary
    // You still see it in the combat log though
    if (!IsAllowedDamageInArea(affectiveCaster, victim))
        return;

    // update at damage Judgement aura duration that applied by attacker at victim
    if (spellDamageInfo->damage && spellProto->Id == 35395)
    {
        SpellAuraHolderMap const& vAuras = victim->GetSpellAuraHolderMap();
        for (SpellAuraHolderMap::const_iterator itr = vAuras.begin(); itr != vAuras.end(); ++itr)
        {
            SpellEntry const* spellInfo = (*itr).second->GetSpellProto();
            if (spellInfo->HasAttribute(SPELL_ATTR_EX3_ALWAYS_HIT) && spellInfo->SpellFamilyName == SPELLFAMILY_PALADIN)
                (*itr).second->RefreshHolder();
        }
    }

    // Call default DealDamage (send critical in hit info for threat calculation)
    CleanDamage cleanDamage(spellDamageInfo->damage, BASE_ATTACK, spellDamageInfo->HitInfo & SPELL_HIT_TYPE_CRIT ? MELEE_HIT_CRIT : MELEE_HIT_NORMAL, (spellDamageInfo->damage > 0 || spellDamageInfo->absorb > 0));
    DealDamage(affectiveCaster, victim, spellDamageInfo->damage, &cleanDamage, resetLeash ? SPELL_DIRECT_DAMAGE : SPELL_DAMAGE_SHIELD, spellDamageInfo->schoolMask, spellProto, durabilityLoss, spellDamageInfo->spell);
}

  • 25
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值