法术系统具体参数在下面文章中 文章中有不懂得 可以加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);
}