From 65dca120d34febdaa84a63e17f638ab0fa59b3df Mon Sep 17 00:00:00 2001 From: ariel- Date: Thu, 1 Feb 2018 13:10:51 -0300 Subject: Core/Spells: rework part 4: iterate over effects first Ref #18395 Implement far spell queue processing Closes #7395 (cherry picked from commit 080d2c6cd439acb2059adc4e24a279de98aa0db6) --- src/server/game/Entities/Player/Player.cpp | 2 +- src/server/game/Maps/Map.cpp | 14 + src/server/game/Maps/Map.h | 6 + src/server/game/Spells/Spell.cpp | 979 ++++++++++----------- src/server/game/Spells/Spell.h | 79 +- src/server/game/Spells/SpellEffects.cpp | 12 +- src/server/game/Spells/SpellInfo.cpp | 16 +- src/server/game/Spells/SpellInfo.h | 2 - src/server/game/Spells/SpellMgr.cpp | 14 - .../scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp | 30 +- 10 files changed, 568 insertions(+), 586 deletions(-) (limited to 'src') diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index bbb4ccf5887..62bcd92b3d3 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -24078,7 +24078,7 @@ Player* Player::GetSelectedPlayer() const { ObjectGuid selectionGUID = GetTarget(); if (!selectionGUID.IsEmpty()) - return ObjectAccessor::GetPlayer(*this, selectionGUID); + return ObjectAccessor::FindConnectedPlayer(selectionGUID); return nullptr; } diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index fc2c950b57c..81578dbc41a 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -3534,8 +3534,22 @@ bool Map::IsSpawnGroupActive(uint32 groupId) const return (_toggledSpawnGroupIds.find(groupId) != _toggledSpawnGroupIds.end()) != !(data->flags & SPAWNGROUP_FLAG_MANUAL_SPAWN); } +void Map::AddFarSpellCallback(FarSpellCallback&& callback) +{ + _farSpellCallbacks.Enqueue(new FarSpellCallback(std::move(callback))); +} + void Map::DelayedUpdate(uint32 t_diff) { + { + FarSpellCallback* callback; + while (_farSpellCallbacks.Dequeue(callback)) + { + (*callback)(this); + delete callback; + } + } + for (_transportsUpdateIter = _transports.begin(); _transportsUpdateIter != _transports.end();) { Transport* transport = *_transportsUpdateIter; diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h index 3362670dbb3..44c46c9219c 100644 --- a/src/server/game/Maps/Map.h +++ b/src/server/game/Maps/Map.h @@ -27,6 +27,7 @@ #include "GridRefManager.h" #include "MapDefines.h" #include "MapRefManager.h" +#include "MPSCQueue.h" #include "ObjectGuid.h" #include "Optional.h" #include "SharedDefines.h" @@ -775,6 +776,9 @@ class TC_GAME_API Map : public GridRefManager // This will not affect any already-present creatures in the group void SetSpawnGroupInactive(uint32 groupId) { SetSpawnGroupActive(groupId, false); } + typedef std::function FarSpellCallback; + void AddFarSpellCallback(FarSpellCallback&& callback); + private: // Type specific code for add/remove to/from grid template @@ -839,6 +843,8 @@ class TC_GAME_API Map : public GridRefManager std::unordered_set _corpseBones; std::unordered_set _updateObjects; + + MPSCQueue _farSpellCallbacks; }; enum InstanceResetMethod diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 209a1de6d28..de06b241220 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -794,9 +794,9 @@ void Spell::SelectSpellTargets() } uint32 mask = (1 << effect->EffectIndex); - for (std::vector::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + for (auto ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { - if (ihit->effectMask & mask) + if (ihit->EffectMask & mask) { m_channelTargetEffectMask |= mask; break; @@ -1217,11 +1217,11 @@ void Spell::SelectImplicitAreaTargets(SpellEffIndex effIndex, SpellImplicitTarge case TARGET_REFERENCE_TYPE_LAST: { // find last added target for this effect - for (std::vector::reverse_iterator ihit = m_UniqueTargetInfo.rbegin(); ihit != m_UniqueTargetInfo.rend(); ++ihit) + for (auto ihit = m_UniqueTargetInfo.rbegin(); ihit != m_UniqueTargetInfo.rend(); ++ihit) { - if (ihit->effectMask & (1 << effIndex)) + if (ihit->EffectMask & (1 << effIndex)) { - referer = ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID); + referer = ObjectAccessor::GetUnit(*m_caster, ihit->TargetGUID); break; } } @@ -1801,8 +1801,7 @@ void Spell::SelectImplicitLineTargets(SpellEffIndex effIndex, SpellImplicitTarge void Spell::SelectEffectTypeImplicitTargets(uint32 effIndex) { - // special case for SPELL_EFFECT_SUMMON_RAF_FRIEND and SPELL_EFFECT_SUMMON_PLAYER - /// @todo this is a workaround - target shouldn't be stored in target map for those spells + // special case for SPELL_EFFECT_SUMMON_RAF_FRIEND and SPELL_EFFECT_SUMMON_PLAYER, queue them on map for later execution SpellEffectInfo const* effect = m_spellInfo->GetEffect(effIndex); if (!effect) return; @@ -1813,11 +1812,31 @@ void Spell::SelectEffectTypeImplicitTargets(uint32 effIndex) if (m_caster->GetTypeId() == TYPEID_PLAYER && !m_caster->GetTarget().IsEmpty()) { WorldObject* target = ObjectAccessor::FindPlayer(m_caster->GetTarget()); - CallScriptObjectTargetSelectHandlers(target, SpellEffIndex(effIndex), SpellImplicitTargetInfo()); - if (target && target->ToPlayer()) - AddUnitTarget(target->ToUnit(), 1 << effIndex, false); + // scripts may modify the target - recheck + if (target && target->GetTypeId() == TYPEID_PLAYER) + { + // target is not stored in target map for those spells + // since we're completely skipping AddUnitTarget logic, we need to check immunity manually + // eg. aura 21546 makes target immune to summons + Player* player = target->ToPlayer(); + if (player->IsImmunedToSpellEffect(m_spellInfo, effIndex, nullptr)) + return; + + target->GetMap()->AddFarSpellCallback(std::bind([](Map* map, Spell* spell, uint8 effIndex, ObjectGuid const& targetGuid) + { + Player* player = ObjectAccessor::GetPlayer(map, targetGuid); + if (!player) + return; + + // check immunity again in case it changed during update + if (player->IsImmunedToSpellEffect(spell->GetSpellInfo(), effIndex, nullptr)) + return; + + spell->HandleEffects(player, nullptr, nullptr, effIndex, SPELL_EFFECT_HANDLE_HIT_TARGET); + }, std::placeholders::_1, this, effIndex, target->GetGUID())); + } } return; default: @@ -2118,7 +2137,7 @@ void Spell::prepareDataForTriggerSystem() m_procAttacker = PROC_FLAG_DONE_RANGED_AUTO_ATTACK; m_procVictim = PROC_FLAG_TAKEN_RANGED_AUTO_ATTACK; } - // For other spells trigger procflags are set in Spell::DoAllEffectOnTarget + // For other spells trigger procflags are set in Spell::TargetInfo::DoDamageAndTriggers // Because spell positivity is dependant on target } @@ -2130,7 +2149,7 @@ void Spell::prepareDataForTriggerSystem() { m_procAttacker |= PROC_FLAG_DONE_TRAP_ACTIVATION; - // also fill up other flags (DoAllEffectOnTarget only fills up flag if both are not set) + // also fill up other flags (TargetInfo::DoDamageAndTriggers only fills up flag if both are not set) m_procAttacker |= PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG; m_procVictim |= PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG; } @@ -2202,31 +2221,30 @@ void Spell::AddUnitTarget(Unit* target, uint32 effectMask, bool checkIfValid /*= ObjectGuid targetGUID = target->GetGUID(); // Lookup target in already in list - for (std::vector::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + auto ihit = std::find_if(std::begin(m_UniqueTargetInfo), std::end(m_UniqueTargetInfo), [targetGUID](TargetInfo const& target) { return target.TargetGUID == targetGUID; }); + if (ihit != std::end(m_UniqueTargetInfo)) // Found in list { - if (targetGUID == ihit->targetGUID) // Found in list - { - ihit->effectMask |= effectMask; // Immune effects removed from mask - return; - } + // Immune effects removed from mask + ihit->EffectMask |= effectMask; + return; } // This is new target calculate data for him // Get spell hit result on target TargetInfo targetInfo; - targetInfo.targetGUID = targetGUID; // Store target GUID - targetInfo.effectMask = effectMask; // Store all effects not immune - targetInfo.processed = false; // Effects not apply on target - targetInfo.alive = target->IsAlive(); - targetInfo.damage = 0; - targetInfo.crit = false; + targetInfo.TargetGUID = targetGUID; // Store target GUID + targetInfo.EffectMask = effectMask; // Store all effects not immune + targetInfo.IsAlive = target->IsAlive(); + targetInfo.Damage = 0; + targetInfo.Healing = 0; + targetInfo.IsCrit = false; // Calculate hit result if (m_originalCaster) - targetInfo.missCondition = m_originalCaster->SpellHitResult(target, m_spellInfo, m_canReflect && !(IsPositive() && m_caster->IsFriendlyTo(target))); + targetInfo.MissCondition = m_originalCaster->SpellHitResult(target, m_spellInfo, m_canReflect && !(IsPositive() && m_caster->IsFriendlyTo(target))); else - targetInfo.missCondition = SPELL_MISS_EVADE; //SPELL_MISS_NONE; + targetInfo.MissCondition = SPELL_MISS_EVADE; //SPELL_MISS_NONE; // Spell have speed - need calculate incoming time // Incoming time is zero for self casts. At least I think so. @@ -2243,32 +2261,32 @@ void Spell::AddUnitTarget(Unit* target, uint32 effectMask, bool checkIfValid /*= hitDelay += dist / m_spellInfo->Speed; } - targetInfo.timeDelay = uint64(std::floor(hitDelay * 1000.0f)); + targetInfo.TimeDelay = uint64(std::floor(hitDelay * 1000.0f)); } else - targetInfo.timeDelay = 0ULL; + targetInfo.TimeDelay = 0ULL; // If target reflect spell back to caster - if (targetInfo.missCondition == SPELL_MISS_REFLECT) + if (targetInfo.MissCondition == SPELL_MISS_REFLECT) { // Calculate reflected spell result on caster - targetInfo.reflectResult = m_caster->SpellHitResult(m_caster, m_spellInfo, false); // can't reflect twice + targetInfo.ReflectResult = m_caster->SpellHitResult(m_caster, m_spellInfo, false); // can't reflect twice // Proc spell reflect aura when missile hits the original target - target->m_Events.AddEvent(new ProcReflectDelayed(target, m_originalCasterGUID), target->m_Events.CalculateTime(targetInfo.timeDelay)); + target->m_Events.AddEvent(new ProcReflectDelayed(target, m_originalCasterGUID), target->m_Events.CalculateTime(targetInfo.TimeDelay)); // Increase time interval for reflected spells by 1.5 - targetInfo.timeDelay += targetInfo.timeDelay >> 1; + targetInfo.TimeDelay += targetInfo.TimeDelay >> 1; } else - targetInfo.reflectResult = SPELL_MISS_NONE; + targetInfo.ReflectResult = SPELL_MISS_NONE; // Calculate minimum incoming time - if (targetInfo.timeDelay && (!m_delayMoment || m_delayMoment > targetInfo.timeDelay)) - m_delayMoment = targetInfo.timeDelay; + if (targetInfo.TimeDelay && (!m_delayMoment || m_delayMoment > targetInfo.TimeDelay)) + m_delayMoment = targetInfo.TimeDelay; // Add target to list - m_UniqueTargetInfo.push_back(targetInfo); + m_UniqueTargetInfo.emplace_back(std::move(targetInfo)); } void Spell::AddGOTarget(GameObject* go, uint32 effectMask) @@ -2287,21 +2305,19 @@ void Spell::AddGOTarget(GameObject* go, uint32 effectMask) ObjectGuid targetGUID = go->GetGUID(); // Lookup target in already in list - for (std::vector::iterator ihit = m_UniqueGOTargetInfo.begin(); ihit != m_UniqueGOTargetInfo.end(); ++ihit) + auto ihit = std::find_if(std::begin(m_UniqueGOTargetInfo), std::end(m_UniqueGOTargetInfo), [targetGUID](GOTargetInfo const& target) { return target.TargetGUID == targetGUID; }); + if (ihit != std::end(m_UniqueGOTargetInfo)) // Found in list { - if (targetGUID == ihit->targetGUID) // Found in list - { - ihit->effectMask |= effectMask; // Add only effect mask - return; - } + // Add only effect mask + ihit->EffectMask |= effectMask; + return; } // This is new target calculate data for him GOTargetInfo target; - target.targetGUID = targetGUID; - target.effectMask = effectMask; - target.processed = false; // Effects not apply on target + target.TargetGUID = targetGUID; + target.EffectMask = effectMask; // Spell have speed - need calculate incoming time if (static_cast(m_caster) != go) @@ -2316,17 +2332,17 @@ void Spell::AddGOTarget(GameObject* go, uint32 effectMask) hitDelay += dist / m_spellInfo->Speed; } - target.timeDelay = uint64(std::floor(hitDelay * 1000.0f)); + target.TimeDelay = uint64(std::floor(hitDelay * 1000.0f)); } else - target.timeDelay = 0ULL; + target.TimeDelay = 0ULL; // Calculate minimum incoming time - if (target.timeDelay && (!m_delayMoment || m_delayMoment > target.timeDelay)) - m_delayMoment = target.timeDelay; + if (target.TimeDelay && (!m_delayMoment || m_delayMoment > target.TimeDelay)) + m_delayMoment = target.TimeDelay; // Add target to list - m_UniqueGOTargetInfo.push_back(target); + m_UniqueGOTargetInfo.emplace_back(std::move(target)); } void Spell::AddItemTarget(Item* item, uint32 effectMask) @@ -2343,22 +2359,21 @@ void Spell::AddItemTarget(Item* item, uint32 effectMask) return; // Lookup target in already in list - for (std::vector::iterator ihit = m_UniqueItemInfo.begin(); ihit != m_UniqueItemInfo.end(); ++ihit) + auto ihit = std::find_if(std::begin(m_UniqueItemInfo), std::end(m_UniqueItemInfo), [item](ItemTargetInfo const& target) { return target.TargetItem == item; }); + if (ihit != std::end(m_UniqueItemInfo)) // Found in list { - if (item == ihit->item) // Found in list - { - ihit->effectMask |= effectMask; // Add only effect mask - return; - } + // Add only effect mask + ihit->EffectMask |= effectMask; + return; } // This is new target add data ItemTargetInfo target; - target.item = item; - target.effectMask = effectMask; + target.TargetItem = item; + target.EffectMask = effectMask; - m_UniqueItemInfo.push_back(target); + m_UniqueItemInfo.emplace_back(std::move(target)); } void Spell::AddDestTarget(SpellDestination const& dest, uint32 effIndex) @@ -2370,7 +2385,7 @@ int64 Spell::GetUnitTargetCountForEffect(SpellEffIndex effect) const { return std::count_if(m_UniqueTargetInfo.begin(), m_UniqueTargetInfo.end(), [effect](TargetInfo const& targetInfo) { - return targetInfo.effectMask & (1 << effect); + return targetInfo.EffectMask & (1 << effect); }); } @@ -2378,7 +2393,7 @@ int64 Spell::GetGameObjectTargetCountForEffect(SpellEffIndex effect) const { return std::count_if(m_UniqueGOTargetInfo.begin(), m_UniqueGOTargetInfo.end(), [effect](GOTargetInfo const& targetInfo) { - return targetInfo.effectMask & (1 << effect); + return targetInfo.EffectMask & (1 << effect); }); } @@ -2386,131 +2401,128 @@ int64 Spell::GetItemTargetCountForEffect(SpellEffIndex effect) const { return std::count_if(m_UniqueItemInfo.begin(), m_UniqueItemInfo.end(), [effect](ItemTargetInfo const& targetInfo) { - return targetInfo.effectMask & (1 << effect); + return targetInfo.EffectMask & (1 << effect); }); } -void Spell::DoAllEffectOnTarget(TargetInfo* target) +void Spell::TargetInfo::PreprocessTarget(Spell* spell) { - if (!target || target->processed) + Unit* unit = spell->m_caster->GetGUID() == TargetGUID ? spell->m_caster : ObjectAccessor::GetUnit(*spell->m_caster, TargetGUID); + if (!unit) return; - target->processed = true; // Target checked in apply effects procedure + // Need init unitTarget by default unit (can changed in code on reflect) + spell->unitTarget = unit; + + // Reset damage/healing counter + spell->m_damage = Damage; + spell->m_healing = Healing; - // Get mask of effects for target - uint32 mask = target->effectMask; + _spellHitTarget = nullptr; + if (MissCondition == SPELL_MISS_NONE) + _spellHitTarget = unit; + else if (MissCondition == SPELL_MISS_REFLECT && ReflectResult == SPELL_MISS_NONE) + _spellHitTarget = spell->m_caster; - Unit* unit = m_caster->GetGUID() == target->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, target->targetGUID); - if (!unit && target->targetGUID.IsPlayer()) // only players may be targeted across maps + _enablePVP = false; // need to check PvP state before spell effects, but act on it afterwards + if (_spellHitTarget) { - uint32 farMask = 0; - // create far target mask - for (SpellEffectInfo const* effect : m_spellInfo->GetEffects()) - if (effect && effect->IsFarUnitTargetEffect()) - if ((1 << effect->EffectIndex) & mask) - farMask |= (1 << effect->EffectIndex); + // if target is flagged for pvp also flag caster if a player + if (unit->IsPvP() && spell->m_caster->GetTypeId() == TYPEID_PLAYER) + _enablePVP = true; // Decide on PvP flagging now, but act on it later. - if (!farMask) - return; + SpellMissInfo missInfo = spell->PreprocessSpellHit(_spellHitTarget, *this); + if (missInfo != SPELL_MISS_NONE) + { + if (missInfo != SPELL_MISS_MISS) + spell->m_caster->SendSpellMiss(unit, spell->m_spellInfo->Id, missInfo); + spell->m_damage = 0; + _spellHitTarget = nullptr; + } + } - // find unit in world - unit = ObjectAccessor::FindPlayer(target->targetGUID); - if (!unit) - return; + spell->CallScriptOnHitHandlers(); - // do far effects on the unit - // can't use default call because of threading, do stuff as fast as possible - for (SpellEffectInfo const* effect : m_spellInfo->GetEffects()) - if (effect && (farMask & (1 << effect->EffectIndex))) - HandleEffects(unit, nullptr, nullptr, effect->EffectIndex, SPELL_EFFECT_HANDLE_HIT_TARGET); - return; - } + // scripts can modify damage/healing for current target, save them + Damage = spell->m_damage; + Healing = spell->m_healing; +} +void Spell::TargetInfo::DoTargetSpellHit(Spell* spell, uint8 effIndex) +{ + Unit* unit = spell->m_caster->GetGUID() == TargetGUID ? spell->m_caster : ObjectAccessor::GetUnit(*spell->m_caster, TargetGUID); if (!unit) return; - if (unit->IsAlive() != target->alive) + // 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) + spell->unitTarget = unit; + spell->targetMissInfo = MissCondition; + + // Reset damage/healing counter + spell->m_damage = Damage; + spell->m_healing = Healing; + + if (unit->IsAlive() != IsAlive) return; - if (getState() == SPELL_STATE_DELAYED && !IsPositive() && (GameTime::GetGameTimeMS() - target->timeDelay) <= unit->m_lastSanctuaryTime) + if (spell->getState() == SPELL_STATE_DELAYED && !spell->IsPositive() && (GameTime::GetGameTimeMS() - TimeDelay) <= unit->m_lastSanctuaryTime) return; // No missinfo in that case - // Get original caster (if exist) and calculate damage/healing from him data - Unit* caster = m_originalCaster ? m_originalCaster : m_caster; + if (_spellHitTarget) + spell->DoSpellEffectHit(_spellHitTarget, effIndex, *this); - // Skip if m_originalCaster not avaiable - if (!caster) - return; + // scripts can modify damage/healing for current target, save them + Damage = spell->m_damage; + Healing = spell->m_healing; +} - SpellMissInfo missInfo = target->missCondition; +void Spell::TargetInfo::DoDamageAndTriggers(Spell* spell) +{ + Unit* unit = spell->m_caster->GetGUID() == TargetGUID ? spell->m_caster : ObjectAccessor::GetUnit(*spell->m_caster, TargetGUID); + if (!unit) + return; - // 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; - targetMissInfo = missInfo; + // other targets executed before this one changed pointer + spell->unitTarget = unit; + if (_spellHitTarget) + spell->unitTarget = _spellHitTarget; // Reset damage/healing counter - m_damage = target->damage; - m_healing = -target->damage; + spell->m_damage = Damage; + spell->m_healing = Healing; + + // Get original caster (if exist) and calculate damage/healing from him data + // Skip if m_originalCaster not available + Unit* caster = spell->m_originalCaster ? spell->m_originalCaster : spell->m_caster; + if (!caster) + return; // Fill base trigger info - uint32 procAttacker = m_procAttacker; - uint32 procVictim = m_procVictim; + uint32 procAttacker = spell->m_procAttacker; + uint32 procVictim = spell->m_procVictim; + uint32 procSpellType = PROC_SPELL_TYPE_NONE; uint32 hitMask = PROC_HIT_NONE; // Spells with this flag cannot trigger if effect is cast on self - bool const canEffectTrigger = !m_spellInfo->HasAttribute(SPELL_ATTR3_CANT_TRIGGER_PROC) && unitTarget->CanProc() && (CanExecuteTriggersOnHit(mask) || missInfo == SPELL_MISS_IMMUNE || missInfo == SPELL_MISS_IMMUNE2); - Unit* spellHitTarget = nullptr; - - if (missInfo == SPELL_MISS_NONE) // In case spell hit target, do all effect on that target - spellHitTarget = unit; - else 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 - spellHitTarget = m_caster; - } - - PrepareScriptHitHandlers(); - CallScriptBeforeHitHandlers(missInfo); - - bool enablePvP = false; // need to check PvP state before spell effects, but act on it afterwards - - if (spellHitTarget) - { - // if target is flagged for pvp also flag caster if a player - if (unit->IsPvP() && m_caster->GetTypeId() == TYPEID_PLAYER) - enablePvP = true; // Decide on PvP flagging now, but act on it later. - - SpellMissInfo missInfo2 = DoSpellHitOnUnit(spellHitTarget, mask); - - if (missInfo2 != SPELL_MISS_NONE) - { - if (missInfo2 != SPELL_MISS_MISS) - m_caster->SendSpellMiss(unit, m_spellInfo->Id, missInfo2); - m_damage = 0; - spellHitTarget = nullptr; - } - } - - // Do not take combo points on dodge and miss - if (missInfo != SPELL_MISS_NONE && m_needComboPoints && m_targets.GetUnitTargetGUID() == target->targetGUID) - m_needComboPoints = false; + bool const canEffectTrigger = !spell->m_spellInfo->HasAttribute(SPELL_ATTR3_CANT_TRIGGER_PROC) && spell->unitTarget->CanProc() && + (spell->CanExecuteTriggersOnHit(EffectMask) || MissCondition == SPELL_MISS_IMMUNE || MissCondition == SPELL_MISS_IMMUNE2); // Trigger info was not filled in Spell::prepareDataForTriggerSystem - we do it now if (canEffectTrigger && !procAttacker && !procVictim) { bool positive = true; - if (m_damage > 0) + if (spell->m_damage > 0) positive = false; - else if (!m_healing) + else if (!spell->m_healing) { for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { // in case of immunity, check all effects to choose correct procFlags, as none has technically hit - if (target->effectMask && !(target->effectMask & (1 << i))) + if (EffectMask && !(EffectMask & (1 << i))) continue; - if (!m_spellInfo->IsPositiveEffect(i)) + if (!spell->m_spellInfo->IsPositiveEffect(i)) { positive = false; break; @@ -2518,83 +2530,81 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) } } - switch (m_spellInfo->DmgClass) + switch (spell->m_spellInfo->DmgClass) { case SPELL_DAMAGE_CLASS_MAGIC: if (positive) { procAttacker |= PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS; - procVictim |= PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_POS; + procVictim |= PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_POS; } else { procAttacker |= PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG; - procVictim |= PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG; + procVictim |= PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG; } - break; + break; case SPELL_DAMAGE_CLASS_NONE: if (positive) { procAttacker |= PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS; - procVictim |= PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_POS; + procVictim |= PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_POS; } else { procAttacker |= PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG; - procVictim |= PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_NEG; + procVictim |= PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_NEG; } - break; + break; } } - CallScriptOnHitHandlers(); // All calculated do it! - // Do healing and triggers - if (m_healing > 0) + // Do healing + std::unique_ptr spellDamageInfo; + std::unique_ptr healInfo; + if (spell->m_healing > 0) { - bool crit = target->crit; - uint32 addhealth = m_healing; - if (crit) + uint32 addhealth = spell->m_healing; + if (IsCrit) { hitMask |= PROC_HIT_CRITICAL; - addhealth = Unit::SpellCriticalHealingBonus(caster, m_spellInfo, addhealth, nullptr); + addhealth = Unit::SpellCriticalHealingBonus(caster, spell->m_spellInfo, addhealth, nullptr); } else hitMask |= PROC_HIT_NORMAL; - HealInfo healInfo(caster, unitTarget, addhealth, m_spellInfo, m_spellInfo->GetSchoolMask()); - caster->HealBySpell(healInfo, crit); - unitTarget->GetThreatManager().ForwardThreatForAssistingMe(caster, float(healInfo.GetEffectiveHeal())*0.5f, m_spellInfo); - m_healing = healInfo.GetEffectiveHeal(); + healInfo = std::make_unique(caster, spell->unitTarget, addhealth, spell->m_spellInfo, spell->m_spellInfo->GetSchoolMask()); + caster->HealBySpell(*healInfo, IsCrit); + spell->unitTarget->GetThreatManager().ForwardThreatForAssistingMe(caster, float(healInfo->GetEffectiveHeal()) * 0.5f, spell->m_spellInfo); + spell->m_healing = healInfo->GetEffectiveHeal(); - // Do triggers for unit - if (canEffectTrigger) - Unit::ProcSkillsAndAuras(caster, unitTarget, procAttacker, procVictim, PROC_SPELL_TYPE_HEAL, PROC_SPELL_PHASE_HIT, hitMask, this, nullptr, &healInfo); + procSpellType |= PROC_SPELL_TYPE_HEAL; } - // Do damage and triggers - else if (m_damage > 0) + + // Do damage + if (spell->m_damage > 0) { // Fill base damage struct (unitTarget - is real spell target) - SpellNonMeleeDamage damageInfo(caster, unitTarget, m_spellInfo, m_SpellVisual, m_spellSchoolMask, m_castId); - + SpellNonMeleeDamage damageInfo(caster, spell->unitTarget, spell->m_spellInfo, spell->m_SpellVisual, spell->m_spellSchoolMask, spell->m_castId); // Check damage immunity - if (unitTarget->IsImmunedToDamage(m_spellInfo)) + if (spell->unitTarget->IsImmunedToDamage(spell->m_spellInfo)) { hitMask = PROC_HIT_IMMUNE; - m_damage = 0; + spell->m_damage = 0; // no packet found in sniffs } else { // Add bonuses and fill damageInfo struct - caster->CalculateSpellDamageTaken(&damageInfo, m_damage, m_spellInfo, m_attackType, target->crit); + caster->CalculateSpellDamageTaken(&damageInfo, spell->m_damage, spell->m_spellInfo, spell->m_attackType, IsCrit); Unit::DealDamageMods(damageInfo.attacker, damageInfo.target, damageInfo.damage, &damageInfo.absorb); - hitMask |= createProcHitMask(&damageInfo, missInfo); + hitMask |= createProcHitMask(&damageInfo, MissCondition); procVictim |= PROC_FLAG_TAKEN_DAMAGE; - m_damage = damageInfo.damage; + spell->m_damage = damageInfo.damage; caster->DealSpellDamage(&damageInfo, true); @@ -2605,78 +2615,120 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) // Do triggers for unit if (canEffectTrigger) { - DamageInfo spellDamageInfo(damageInfo, SPELL_DIRECT_DAMAGE, m_attackType, hitMask); - Unit::ProcSkillsAndAuras(caster, unitTarget, procAttacker, procVictim, PROC_SPELL_TYPE_DAMAGE, PROC_SPELL_PHASE_HIT, hitMask, this, &spellDamageInfo, nullptr); + spellDamageInfo = std::make_unique(damageInfo, SPELL_DIRECT_DAMAGE, spell->m_attackType, hitMask); + procSpellType |= PROC_SPELL_TYPE_DAMAGE; - if (caster->GetTypeId() == TYPEID_PLAYER && !m_spellInfo->HasAttribute(SPELL_ATTR0_STOP_ATTACK_TARGET) && !m_spellInfo->HasAttribute(SPELL_ATTR4_SUPPRESS_WEAPON_PROCS) && - (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED)) - caster->ToPlayer()->CastItemCombatSpell(spellDamageInfo); + if (caster->GetTypeId() == TYPEID_PLAYER && !spell->m_spellInfo->HasAttribute(SPELL_ATTR0_STOP_ATTACK_TARGET) && !spell->m_spellInfo->HasAttribute(SPELL_ATTR4_SUPPRESS_WEAPON_PROCS) && + (spell->m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || spell->m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED)) + caster->ToPlayer()->CastItemCombatSpell(*spellDamageInfo); } } + // Passive spell hits/misses or active spells only misses (only triggers) - else + if (spell->m_damage <= 0 && spell->m_healing <= 0) { // Fill base damage struct (unitTarget - is real spell target) - SpellNonMeleeDamage damageInfo(caster, unitTarget, m_spellInfo, m_SpellVisual, m_spellSchoolMask); - hitMask |= createProcHitMask(&damageInfo, missInfo); + SpellNonMeleeDamage damageInfo(caster, spell->unitTarget, spell->m_spellInfo, spell->m_SpellVisual, spell->m_spellSchoolMask); + hitMask |= createProcHitMask(&damageInfo, MissCondition); // Do triggers for unit if (canEffectTrigger) { - DamageInfo spellNoDamageInfo(damageInfo, NODAMAGE, m_attackType, hitMask); - Unit::ProcSkillsAndAuras(caster, unitTarget, procAttacker, procVictim, PROC_SPELL_TYPE_NO_DMG_HEAL, PROC_SPELL_PHASE_HIT, hitMask, this, &spellNoDamageInfo, nullptr); - - if (caster->GetTypeId() == TYPEID_PLAYER && !m_spellInfo->HasAttribute(SPELL_ATTR0_STOP_ATTACK_TARGET) && !m_spellInfo->HasAttribute(SPELL_ATTR4_SUPPRESS_WEAPON_PROCS) && - (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED)) - caster->ToPlayer()->CastItemCombatSpell(spellNoDamageInfo); + spellDamageInfo = std::make_unique(damageInfo, NODAMAGE, spell->m_attackType, hitMask); + procSpellType |= PROC_SPELL_TYPE_NO_DMG_HEAL; } // Failed Pickpocket, reveal rogue - if (missInfo == SPELL_MISS_RESIST && m_spellInfo->HasAttribute(SPELL_ATTR0_CU_PICKPOCKET) && unitTarget->GetTypeId() == TYPEID_UNIT) + if (MissCondition == SPELL_MISS_RESIST && spell->m_spellInfo->HasAttribute(SPELL_ATTR0_CU_PICKPOCKET) && spell->unitTarget->GetTypeId() == TYPEID_UNIT) { - m_caster->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Interacting); - unitTarget->ToCreature()->EngageWithTarget(m_caster); + spell->m_caster->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Interacting); + spell->unitTarget->ToCreature()->EngageWithTarget(spell->m_caster); + } + } + + // Do triggers for unit + if (canEffectTrigger) + { + Unit::ProcSkillsAndAuras(caster, spell->unitTarget, procAttacker, procVictim, procSpellType, PROC_SPELL_PHASE_HIT, hitMask, spell, spellDamageInfo.get(), healInfo.get()); + + // item spells (spell hit of non-damage spell may also activate items, for example seal of corruption hidden hit) + if (caster->GetTypeId() == TYPEID_PLAYER && (procSpellType & (PROC_SPELL_TYPE_DAMAGE | PROC_SPELL_TYPE_NO_DMG_HEAL))) + { + if (spell->m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || spell->m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED) + if (!spell->m_spellInfo->HasAttribute(SPELL_ATTR0_STOP_ATTACK_TARGET) && !spell->m_spellInfo->HasAttribute(SPELL_ATTR4_SUPPRESS_WEAPON_PROCS)) + caster->ToPlayer()->CastItemCombatSpell(*spellDamageInfo); } } // set hitmask for finish procs - m_hitMask |= hitMask; + spell->m_hitMask |= hitMask; + + // Do not take combo points on dodge and miss + if (MissCondition != SPELL_MISS_NONE && spell->m_needComboPoints && spell->m_targets.GetUnitTargetGUID() == TargetGUID) + spell->m_needComboPoints = false; - // spellHitTarget can be null if spell is missed in DoSpellHitOnUnit - if (missInfo != SPELL_MISS_EVADE && spellHitTarget && !m_caster->IsFriendlyTo(unit) && (!IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL))) + // _spellHitTarget can be null if spell is missed in DoSpellHitOnUnit + if (MissCondition != SPELL_MISS_EVADE && _spellHitTarget && !spell->m_caster->IsFriendlyTo(unit) && (!spell->IsPositive() || spell->m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL))) { - m_caster->AttackedTarget(unit, m_spellInfo->HasInitialAggro()); + spell->m_caster->AttackedTarget(unit, spell->m_spellInfo->HasInitialAggro()); if (!unit->IsStandState()) unit->SetStandState(UNIT_STAND_STATE_STAND); } // Check for SPELL_ATTR7_INTERRUPT_ONLY_NONPLAYER - if (missInfo == SPELL_MISS_NONE && m_spellInfo->HasAttribute(SPELL_ATTR7_INTERRUPT_ONLY_NONPLAYER) && unit->GetTypeId() != TYPEID_PLAYER) + if (MissCondition == SPELL_MISS_NONE && spell->m_spellInfo->HasAttribute(SPELL_ATTR7_INTERRUPT_ONLY_NONPLAYER) && unit->GetTypeId() != TYPEID_PLAYER) caster->CastSpell(unit, SPELL_INTERRUPT_NONPLAYER, true); - if (spellHitTarget) + if (_spellHitTarget) { //AI functions - if (spellHitTarget->GetTypeId() == TYPEID_UNIT) - if (spellHitTarget->ToCreature()->IsAIEnabled) - spellHitTarget->ToCreature()->AI()->SpellHit(m_caster, m_spellInfo); + if (_spellHitTarget->GetTypeId() == TYPEID_UNIT) + if (_spellHitTarget->ToCreature()->IsAIEnabled) + _spellHitTarget->ToCreature()->AI()->SpellHit(spell->m_caster, spell->m_spellInfo); - if (m_caster->GetTypeId() == TYPEID_UNIT && m_caster->ToCreature()->IsAIEnabled) - m_caster->ToCreature()->AI()->SpellHitTarget(spellHitTarget, m_spellInfo); + if (spell->m_caster->GetTypeId() == TYPEID_UNIT && spell->m_caster->ToCreature()->IsAIEnabled) + spell->m_caster->ToCreature()->AI()->SpellHitTarget(_spellHitTarget, spell->m_spellInfo); // Needs to be called after dealing damage/healing to not remove breaking on damage auras - DoTriggersOnSpellHit(spellHitTarget, mask); - - if (enablePvP) - m_caster->ToPlayer()->UpdatePvP(true); + spell->DoTriggersOnSpellHit(_spellHitTarget, EffectMask); - CallScriptAfterHitHandlers(); + if (_enablePVP) + spell->m_caster->ToPlayer()->UpdatePvP(true); } + + spell->CallScriptAfterHitHandlers(); } -SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask) +void Spell::GOTargetInfo::DoTargetSpellHit(Spell* spell, uint8 effIndex) { - if (!unit || !effectMask) + GameObject* go = ObjectAccessor::GetGameObject(*spell->m_caster, TargetGUID); + if (!go) + return; + + spell->CallScriptBeforeHitHandlers(SPELL_MISS_NONE); + + spell->HandleEffects(nullptr, nullptr, go, effIndex, SPELL_EFFECT_HANDLE_HIT_TARGET); + + if (go->AI()) + go->AI()->SpellHit(spell->m_caster, spell->m_spellInfo); + + spell->CallScriptOnHitHandlers(); + spell->CallScriptAfterHitHandlers(); +} + +void Spell::ItemTargetInfo::DoTargetSpellHit(Spell* spell, uint8 effIndex) +{ + spell->CallScriptBeforeHitHandlers(SPELL_MISS_NONE); + + spell->HandleEffects(nullptr, TargetItem, nullptr, effIndex, SPELL_EFFECT_HANDLE_HIT_TARGET); + + spell->CallScriptOnHitHandlers(); + spell->CallScriptAfterHitHandlers(); +} + +SpellMissInfo Spell::PreprocessSpellHit(Unit* unit, TargetInfo& hitInfo) +{ + if (!unit) return SPELL_MISS_EVADE; // Target may have begun evading between launch and hit phases - re-check now @@ -2688,15 +2740,7 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask) if (m_spellInfo->HasHitDelay() && unit->IsImmunedToSpell(m_spellInfo, m_caster)) return SPELL_MISS_IMMUNE; - // disable effects to which unit is immune - SpellMissInfo returnVal = SPELL_MISS_IMMUNE; - for (SpellEffectInfo const* effect : m_spellInfo->GetEffects()) - if (effect && (effectMask & (1 << effect->EffectIndex))) - if (unit->IsImmunedToSpellEffect(m_spellInfo, effect->EffectIndex, m_caster)) - effectMask &= ~(1 << effect->EffectIndex); - - if (!effectMask) - return returnVal; + CallScriptBeforeHitHandlers(hitInfo.MissCondition); if (Player* player = unit->ToPlayer()) { @@ -2743,32 +2787,65 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask) } } - uint32 aura_effmask = Aura::BuildEffectMaskForOwner(m_spellInfo, effectMask, unit); + // check immunity due to diminishing returns + if (m_originalCaster && Aura::BuildEffectMaskForOwner(m_spellInfo, MAX_EFFECT_MASK, unit)) + { + for (SpellEffectInfo const* auraSpellEffect : m_spellInfo->GetEffects()) + if (auraSpellEffect) + hitInfo.AuraBasePoints[auraSpellEffect->EffectIndex] = (m_spellValue->CustomBasePointsMask & (1 << auraSpellEffect->EffectIndex)) ? + m_spellValue->EffectBasePoints[auraSpellEffect->EffectIndex] : + auraSpellEffect->CalcBaseValue(m_originalCaster, unit, m_castItemEntry, m_castItemLevel); - // Get Data Needed for Diminishing Returns, some effects may have multiple auras, so this must be done on spell hit, not aura add - DiminishingGroup diminishGroup = m_spellInfo->GetDiminishingReturnsGroupForSpell(); + // Get Data Needed for Diminishing Returns, some effects may have multiple auras, so this must be done on spell hit, not aura add + hitInfo.DRGroup = m_spellInfo->GetDiminishingReturnsGroupForSpell(); - DiminishingLevels diminishLevel = DIMINISHING_LEVEL_1; - if (diminishGroup && aura_effmask) - { - diminishLevel = unit->GetDiminishing(diminishGroup); - DiminishingReturnsType type = m_spellInfo->GetDiminishingReturnsGroupType(); - // Increase Diminishing on unit, current informations for actually casts will use values above - if (type == DRTYPE_ALL || (type == DRTYPE_PLAYER && unit->IsAffectedByDiminishingReturns())) - unit->IncrDiminishing(m_spellInfo); + DiminishingLevels diminishLevel = DIMINISHING_LEVEL_1; + if (hitInfo.DRGroup) + { + diminishLevel = unit->GetDiminishing(hitInfo.DRGroup); + DiminishingReturnsType type = m_spellInfo->GetDiminishingReturnsGroupType(); + // Increase Diminishing on unit, current informations for actually casts will use values above + if (type == DRTYPE_ALL || (type == DRTYPE_PLAYER && unit->IsAffectedByDiminishingReturns())) + unit->IncrDiminishing(m_spellInfo); + } + + // Now Reduce spell duration using data received at spell hit + // check whatever effects we're going to apply, diminishing returns only apply to negative aura effects + hitInfo.Positive = true; + if (m_originalCaster == unit || !m_originalCaster->IsFriendlyTo(unit)) + { + for (SpellEffectInfo const* effect : m_spellInfo->GetEffects()) + { + // mod duration only for effects applying aura! + if (effect && + hitInfo.EffectMask & (1 << effect->EffectIndex) && + effect->IsUnitOwnedAuraEffect() && + !m_spellInfo->IsPositiveEffect(effect->EffectIndex)) + { + hitInfo.Positive = false; + break; + } + } + } + + hitInfo.AuraDuration = m_spellInfo->GetMaxDuration(); + + // unit is immune to aura if it was diminished to 0 duration + if (!hitInfo.Positive && !unit->ApplyDiminishingToDuration(m_spellInfo, hitInfo.AuraDuration, m_originalCaster, diminishLevel)) + if (std::all_of(std::begin(m_spellInfo->GetEffects()), std::end(m_spellInfo->GetEffects()), [](SpellEffectInfo const* effInfo) { return !effInfo || !effInfo->IsEffect() || effInfo->IsEffect(SPELL_EFFECT_APPLY_AURA); })) + return SPELL_MISS_IMMUNE; } + return SPELL_MISS_NONE; +} + +void Spell::DoSpellEffectHit(Unit* unit, uint8 effIndex, TargetInfo& hitInfo) +{ + uint8 aura_effmask = Aura::BuildEffectMaskForOwner(m_spellInfo, 1 << effIndex, unit); if (aura_effmask) { if (m_originalCaster) { - int32 basePoints[MAX_SPELL_EFFECTS]; - for (SpellEffectInfo const* auraSpellEffect : m_spellInfo->GetEffects()) - if (auraSpellEffect) - basePoints[auraSpellEffect->EffectIndex] = (m_spellValue->CustomBasePointsMask & (1 << auraSpellEffect->EffectIndex)) ? - m_spellValue->EffectBasePoints[auraSpellEffect->EffectIndex] : - auraSpellEffect->CalcBaseValue(m_originalCaster, unit, m_castItemEntry, m_castItemLevel); - bool refresh = false; if (!_spellAura) @@ -2779,7 +2856,7 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask) AuraCreateInfo createInfo(m_castId, m_spellInfo, GetCastDifficulty(), allAuraEffectMask, unit); createInfo .SetCaster(m_originalCaster) - .SetBaseAmount(basePoints) + .SetBaseAmount(&hitInfo.AuraBasePoints[0]) .SetCastItem(m_castItemGUID, m_castItemEntry, m_castItemLevel) .SetPeriodicReset(resetPeriodicTimer) .SetOwnerEffectMask(aura_effmask) @@ -2802,81 +2879,43 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask) _spellAura->ModStackAmount(m_spellValue->AuraStackAmount); } - // Now Reduce spell duration using data received at spell hit - // check whatever effects we're going to apply, diminishing returns only apply to negative aura effects - bool positive = true; - if (m_originalCaster == unit || !m_originalCaster->IsFriendlyTo(unit)) - { - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - { - // mod duration only for effects applying aura! - if ((aura_effmask & (1 << i)) && !m_spellInfo->IsPositiveEffect(i)) - { - positive = false; - break; - } - } - } + _spellAura->SetDiminishGroup(hitInfo.DRGroup); - int32 duration = _spellAura->GetMaxDuration(); + hitInfo.AuraDuration = m_originalCaster->ModSpellDuration(m_spellInfo, unit, hitInfo.AuraDuration, hitInfo.Positive, _spellAura->GetEffectMask()); - // unit is immune to aura if it was diminished to 0 duration - if (!positive && !unit->ApplyDiminishingToDuration(m_spellInfo, duration, m_originalCaster, diminishLevel)) + if (hitInfo.AuraDuration > 0) { - _spellAura->Remove(); - _spellAura = nullptr; - - bool found = false; - for (SpellEffectInfo const* effect : m_spellInfo->GetEffects()) - if (effect && (effectMask & (1 << effect->EffectIndex) && effect->Effect != SPELL_EFFECT_APPLY_AURA)) - found = true; - if (!found) - return SPELL_MISS_IMMUNE; - } - else - { - _spellAura->SetDiminishGroup(diminishGroup); - - duration = m_originalCaster->ModSpellDuration(m_spellInfo, unit, duration, positive, effectMask); + hitInfo.AuraDuration *= m_spellValue->DurationMul; - if (duration > 0) + // Haste modifies duration of channeled spells + if (m_spellInfo->IsChanneled()) + m_originalCaster->ModSpellDurationTime(m_spellInfo, hitInfo.AuraDuration, this); + else if (m_spellInfo->HasAttribute(SPELL_ATTR5_HASTE_AFFECT_DURATION)) { - duration *= m_spellValue->DurationMul; - - // Haste modifies duration of channeled spells - if (m_spellInfo->IsChanneled()) - m_originalCaster->ModSpellDurationTime(m_spellInfo, duration, this); - else if (m_spellInfo->HasAttribute(SPELL_ATTR5_HASTE_AFFECT_DURATION)) - { - int32 origDuration = duration; - duration = 0; - for (SpellEffectInfo const* effect : m_spellInfo->GetEffects()) - if (effect) - if (AuraEffect const* eff = _spellAura->GetEffect(effect->EffectIndex)) - if (int32 period = eff->GetPeriod()) // period is hastened by UNIT_MOD_CAST_SPEED - duration = std::max(std::max(origDuration / period, 1) * period, duration); - - // if there is no periodic effect - if (!duration) - duration = int32(origDuration * m_originalCaster->m_unitData->ModCastingSpeed); - } + int32 origDuration = hitInfo.AuraDuration; + hitInfo.AuraDuration = 0; + for (SpellEffectInfo const* effect : m_spellInfo->GetEffects()) + if (effect) + if (AuraEffect const* eff = _spellAura->GetEffect(effect->EffectIndex)) + if (int32 period = eff->GetPeriod()) // period is hastened by UNIT_MOD_CAST_SPEED + hitInfo.AuraDuration = std::max(std::max(origDuration / period, 1) * period, hitInfo.AuraDuration); + + // if there is no periodic effect + if (!hitInfo.AuraDuration) + hitInfo.AuraDuration = int32(origDuration * m_originalCaster->m_unitData->ModCastingSpeed); } + } - if (duration != _spellAura->GetMaxDuration()) - { - _spellAura->SetMaxDuration(duration); - _spellAura->SetDuration(duration); - } + if (hitInfo.AuraDuration != _spellAura->GetMaxDuration()) + { + _spellAura->SetMaxDuration(hitInfo.AuraDuration); + _spellAura->SetDuration(hitInfo.AuraDuration); } } } } - for (SpellEffectInfo const* effect : m_spellInfo->GetEffects()) - if (effect && (effectMask & (1 << effect->EffectIndex))) - HandleEffects(unit, nullptr, nullptr, effect->EffectIndex, SPELL_EFFECT_HANDLE_HIT_TARGET); - - return SPELL_MISS_NONE; + HandleEffects(unit, nullptr, nullptr, effIndex, SPELL_EFFECT_HANDLE_HIT_TARGET); } void Spell::DoTriggersOnSpellHit(Unit* unit, uint32 effMask) @@ -2928,52 +2967,6 @@ void Spell::DoTriggersOnSpellHit(Unit* unit, uint32 effMask) } } -void Spell::DoAllEffectOnTarget(GOTargetInfo* target) -{ - if (target->processed) // Check target - return; - target->processed = true; // Target checked in apply effects procedure - - uint32 effectMask = target->effectMask; - if (!effectMask) - return; - - GameObject* go = m_caster->GetMap()->GetGameObject(target->targetGUID); - if (!go) - return; - - PrepareScriptHitHandlers(); - CallScriptBeforeHitHandlers(SPELL_MISS_NONE); - - for (SpellEffectInfo const* effect : m_spellInfo->GetEffects()) - if (effect && (effectMask & (1 << effect->EffectIndex))) - HandleEffects(nullptr, nullptr, go, effect->EffectIndex, SPELL_EFFECT_HANDLE_HIT_TARGET); - - if (go->AI()) - go->AI()->SpellHit(m_caster, m_spellInfo); - - CallScriptOnHitHandlers(); - CallScriptAfterHitHandlers(); -} - -void Spell::DoAllEffectOnTarget(ItemTargetInfo* target) -{ - uint32 effectMask = target->effectMask; - if (!target->item || !effectMask) - return; - - PrepareScriptHitHandlers(); - CallScriptBeforeHitHandlers(SPELL_MISS_NONE); - - for (SpellEffectInfo const* effect : m_spellInfo->GetEffects()) - if (effect && (effectMask & (1 << effect->EffectIndex))) - HandleEffects(nullptr, target->item, nullptr, effect->EffectIndex, SPELL_EFFECT_HANDLE_HIT_TARGET); - - CallScriptOnHitHandlers(); - - CallScriptAfterHitHandlers(); -} - bool Spell::UpdateChanneledTargetList() { // Not need check return true @@ -2999,40 +2992,39 @@ bool Spell::UpdateChanneledTargetList() range += std::min(MAX_SPELL_RANGE_TOLERANCE, range*0.1f); // 10% but no more than MAX_SPELL_RANGE_TOLERANCE } - for (std::vector::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + for (TargetInfo& targetInfo : m_UniqueTargetInfo) { - if (ihit->missCondition == SPELL_MISS_NONE && (channelTargetEffectMask & ihit->effectMask)) + if (targetInfo.MissCondition == SPELL_MISS_NONE && (channelTargetEffectMask & targetInfo.EffectMask)) { - Unit* unit = m_caster->GetGUID() == ihit->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID); - + Unit* unit = m_caster->GetGUID() == targetInfo.TargetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, targetInfo.TargetGUID); if (!unit) { - m_caster->RemoveChannelObject(ihit->targetGUID); + m_caster->RemoveChannelObject(targetInfo.TargetGUID); continue; } if (IsValidDeadOrAliveTarget(unit)) { - if (channelAuraMask & ihit->effectMask) + if (channelAuraMask & targetInfo.EffectMask) { - if (AuraApplication * aurApp = unit->GetAuraApplication(m_spellInfo->Id, m_originalCasterGUID)) + if (AuraApplication* aurApp = unit->GetAuraApplication(m_spellInfo->Id, m_originalCasterGUID)) { if (m_caster != unit && !m_caster->IsWithinDistInMap(unit, range)) { - ihit->effectMask &= ~aurApp->GetEffectMask(); + targetInfo.EffectMask &= ~aurApp->GetEffectMask(); unit->RemoveAura(aurApp); - m_caster->RemoveChannelObject(ihit->targetGUID); + m_caster->RemoveChannelObject(targetInfo.TargetGUID); continue; } } else // aura is dispelled { - m_caster->RemoveChannelObject(ihit->targetGUID); + m_caster->RemoveChannelObject(targetInfo.TargetGUID); continue; } } - channelTargetEffectMask &= ~ihit->effectMask; // remove from need alive mask effect that have alive target + channelTargetEffectMask &= ~targetInfo.EffectMask; // remove from need alive mask effect that have alive target } } } @@ -3224,9 +3216,9 @@ void Spell::cancel() break; case SPELL_STATE_CASTING: - for (std::vector::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) - if ((*ihit).missCondition == SPELL_MISS_NONE) - if (Unit* unit = m_caster->GetGUID() == ihit->targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID)) + for (TargetInfo const& targetInfo : m_UniqueTargetInfo) + if (targetInfo.MissCondition == SPELL_MISS_NONE) + if (Unit* unit = m_caster->GetGUID() == targetInfo.TargetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, targetInfo.TargetGUID)) unit->RemoveOwnedAura(m_spellInfo->Id, m_originalCasterGUID, 0, AURA_REMOVE_BY_CANCEL); SendChannelUpdate(0); @@ -3367,10 +3359,10 @@ void Spell::_cast(bool skipCheck) // check diminishing returns (again, only after finish cast bar, tested on retail) if (Unit* target = m_targets.GetUnitTarget()) { - uint8 aura_effmask = 0; - for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) - if (m_spellInfo->GetEffect(i) && m_spellInfo->GetEffect(i)->IsUnitOwnedAuraEffect()) - aura_effmask |= 1 << i; + uint32 aura_effmask = 0; + for (SpellEffectInfo const* effect : m_spellInfo->GetEffects()) + if (effect && effect->IsUnitOwnedAuraEffect()) + aura_effmask |= 1 << effect->EffectIndex; if (aura_effmask) { @@ -3455,8 +3447,6 @@ void Spell::_cast(bool skipCheck) if (!m_spellInfo->HasAttribute(SPELL_ATTR12_START_COOLDOWN_ON_CAST_START)) SendSpellCooldown(); - PrepareScriptHitHandlers(); - if (!m_spellInfo->LaunchDelay) { HandleLaunchPhase(); @@ -3546,6 +3536,21 @@ void Spell::_cast(bool skipCheck) caster->AI()->OnSuccessfulSpellCast(GetSpellInfo()); } +template +void Spell::DoProcessTargetContainer(Container& targetContainer) +{ + for (TargetInfoBase& target : targetContainer) + target.PreprocessTarget(this); + + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + for (TargetInfoBase& target : targetContainer) + if (target.EffectMask & (1 << i)) + target.DoTargetSpellHit(this, i); + + for (TargetInfoBase& target : targetContainer) + target.DoDamageAndTriggers(this); +} + void Spell::handle_immediate() { // start channeling if applicable @@ -3586,13 +3591,9 @@ void Spell::handle_immediate() if (m_UniqueTargetInfo.empty()) m_hitMask = PROC_HIT_NORMAL; else - { - for (std::vector::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) - DoAllEffectOnTarget(&(*ihit)); - } + DoProcessTargetContainer(m_UniqueTargetInfo); - for (std::vector::iterator ihit = m_UniqueGOTargetInfo.begin(); ihit != m_UniqueGOTargetInfo.end(); ++ihit) - DoAllEffectOnTarget(&(*ihit)); + DoProcessTargetContainer(m_UniqueGOTargetInfo); FinishTargetProcessing(); @@ -3632,7 +3633,7 @@ uint64 Spell::handle_delayed(uint64 t_offset) return m_delayMoment; next_time = m_delayMoment; - if ((m_UniqueTargetInfo.size() > 2 || (m_UniqueTargetInfo.size() == 1 && m_UniqueTargetInfo.front().targetGUID == m_caster->GetGUID())) || !m_UniqueGOTargetInfo.empty()) + if ((m_UniqueTargetInfo.size() > 2 || (m_UniqueTargetInfo.size() == 1 && m_UniqueTargetInfo.front().TargetGUID == m_caster->GetGUID())) || !m_UniqueGOTargetInfo.empty()) { t_offset = 0; // if LaunchDelay was present then the only target that has timeDelay = 0 is m_caster - and that is the only target we want to process now } @@ -3655,30 +3656,47 @@ uint64 Spell::handle_delayed(uint64 t_offset) } // now recheck units targeting correctness (need before any effects apply to prevent adding immunity at first effect not allow apply second spell effect and similar cases) - for (std::vector::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { - if (ihit->processed == false) + std::vector delayedTargets; + auto itr = std::remove_if(std::begin(m_UniqueTargetInfo), std::end(m_UniqueTargetInfo), [&](TargetInfo& target) -> bool { - if (single_missile || ihit->timeDelay <= t_offset) + if (single_missile || target.TimeDelay <= t_offset) { - ihit->timeDelay = t_offset; - DoAllEffectOnTarget(&(*ihit)); + target.TimeDelay = t_offset; + return true; } - else if (next_time == 0 || ihit->timeDelay < next_time) - next_time = ihit->timeDelay; - } + else if (next_time == 0 || target.TimeDelay < next_time) + next_time = target.TimeDelay; + + return false; + }); + + delayedTargets.insert(delayedTargets.end(), std::make_move_iterator(itr), std::make_move_iterator(m_UniqueTargetInfo.end())); + m_UniqueTargetInfo.erase(itr, m_UniqueTargetInfo.end()); + + DoProcessTargetContainer(delayedTargets); } // now recheck gameobject targeting correctness - for (std::vector::iterator ighit = m_UniqueGOTargetInfo.begin(); ighit != m_UniqueGOTargetInfo.end(); ++ighit) { - if (ighit->processed == false) + std::vector delayedGOTargets; + auto itr = std::remove_if(std::begin(m_UniqueGOTargetInfo), std::end(m_UniqueGOTargetInfo), [&](GOTargetInfo& goTarget) -> bool { - if (single_missile || ighit->timeDelay <= t_offset) - DoAllEffectOnTarget(&(*ighit)); - else if (next_time == 0 || ighit->timeDelay < next_time) - next_time = ighit->timeDelay; - } + if (single_missile || goTarget.TimeDelay <= t_offset) + { + goTarget.TimeDelay = t_offset; + return true; + } + else if (next_time == 0 || goTarget.TimeDelay < next_time) + next_time = goTarget.TimeDelay; + + return false; + }); + + delayedGOTargets.insert(delayedGOTargets.end(), std::make_move_iterator(itr), std::make_move_iterator(m_UniqueGOTargetInfo.end())); + m_UniqueGOTargetInfo.erase(itr, m_UniqueGOTargetInfo.end()); + + DoProcessTargetContainer(delayedGOTargets); } FinishTargetProcessing(); @@ -3709,8 +3727,6 @@ void Spell::_handle_immediate_phase() // handle some immediate features of the spell here HandleThreatSpells(); - PrepareScriptHitHandlers(); - // handle effects with SPELL_EFFECT_HANDLE_HIT mode for (SpellEffectInfo const* effect : m_spellInfo->GetEffects()) { @@ -3723,8 +3739,7 @@ void Spell::_handle_immediate_phase() } // process items - for (std::vector::iterator ihit = m_UniqueItemInfo.begin(); ihit != m_UniqueItemInfo.end(); ++ihit) - DoAllEffectOnTarget(&(*ihit)); + DoProcessTargetContainer(m_UniqueItemInfo); } void Spell::_handle_finish_phase() @@ -3837,7 +3852,7 @@ void Spell::update(uint32 difftime) // Also remove applied auras for (TargetInfo const& target : m_UniqueTargetInfo) - if (Unit* unit = m_caster->GetGUID() == target.targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, target.targetGUID)) + if (Unit* unit = m_caster->GetGUID() == target.TargetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, target.TargetGUID)) unit->RemoveOwnedAura(m_spellInfo->Id, m_originalCasterGUID, 0, AURA_REMOVE_BY_CANCEL); } @@ -4404,26 +4419,26 @@ void Spell::UpdateSpellCastDataTargets(WorldPackets::Spells::SpellCastData& data // m_needAliveTargetMask req for stop channeling if one target die for (TargetInfo& targetInfo : m_UniqueTargetInfo) { - if (targetInfo.effectMask == 0) // No effect apply - all immune add state + if (targetInfo.EffectMask == 0) // No effect apply - all immune add state // possibly SPELL_MISS_IMMUNE2 for this?? - targetInfo.missCondition = SPELL_MISS_IMMUNE2; + targetInfo.MissCondition = SPELL_MISS_IMMUNE2; - if (targetInfo.missCondition == SPELL_MISS_NONE) // hits + if (targetInfo.MissCondition == SPELL_MISS_NONE) // hits { - data.HitTargets.push_back(targetInfo.targetGUID); + data.HitTargets.push_back(targetInfo.TargetGUID); data.HitStatus.emplace_back(SPELL_MISS_NONE); - m_channelTargetEffectMask |= targetInfo.effectMask; + m_channelTargetEffectMask |= targetInfo.EffectMask; } else // misses { - data.MissTargets.push_back(targetInfo.targetGUID); - data.MissStatus.emplace_back(targetInfo.missCondition, targetInfo.reflectResult); + data.MissTargets.push_back(targetInfo.TargetGUID); + data.MissStatus.emplace_back(targetInfo.MissCondition, targetInfo.ReflectResult); } } for (GOTargetInfo const& targetInfo : m_UniqueGOTargetInfo) - data.HitTargets.push_back(targetInfo.targetGUID); // Always hits + data.HitTargets.push_back(targetInfo.TargetGUID); // Always hits // Reset m_needAliveTargetMask for non channeled spell if (!m_spellInfo->IsChanneled()) @@ -4673,10 +4688,10 @@ void Spell::SendChannelStart(uint32 duration) { auto explicitTargetItr = std::find_if(m_UniqueTargetInfo.begin(), m_UniqueTargetInfo.end(), [&](TargetInfo const& target) { - return target.targetGUID == m_targets.GetUnitTargetGUID(); + return target.TargetGUID == m_targets.GetUnitTargetGUID(); }); if (explicitTargetItr != m_UniqueTargetInfo.end()) - explicitTargetEffectMask = explicitTargetItr->effectMask; + explicitTargetEffectMask = explicitTargetItr->EffectMask; } for (SpellEffectInfo const* effect : m_spellInfo->GetEffects()) @@ -4685,19 +4700,19 @@ void Spell::SendChannelStart(uint32 duration) for (TargetInfo const& target : m_UniqueTargetInfo) { - if (target.effectMask & channelAuraMask) - m_caster->AddChannelObject(target.targetGUID); + if (target.EffectMask & channelAuraMask) + m_caster->AddChannelObject(target.TargetGUID); if (m_UniqueTargetInfo.size() == 1 && m_UniqueGOTargetInfo.empty()) - if(target.targetGUID != m_caster->GetGUID()) + if(target.TargetGUID != m_caster->GetGUID()) if (Creature* creatureCaster = m_caster->ToCreature()) if (!creatureCaster->IsFocusing(this)) - creatureCaster->FocusTarget(this, ObjectAccessor::GetWorldObject(*creatureCaster, target.targetGUID)); + creatureCaster->FocusTarget(this, ObjectAccessor::GetWorldObject(*creatureCaster, target.TargetGUID)); } for (GOTargetInfo const& target : m_UniqueGOTargetInfo) - if (target.effectMask & channelAuraMask) - m_caster->AddChannelObject(target.targetGUID); + if (target.EffectMask & channelAuraMask) + m_caster->AddChannelObject(target.TargetGUID); m_caster->SetChannelSpellId(m_spellInfo->Id); m_caster->SetChannelVisual(m_SpellVisual); @@ -4815,19 +4830,13 @@ void Spell::TakePower() ObjectGuid targetGUID = m_targets.GetUnitTargetGUID(); if (!targetGUID.IsEmpty()) { - for (std::vector::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + auto ihit = std::find_if(std::begin(m_UniqueTargetInfo), std::end(m_UniqueTargetInfo), [&](TargetInfo const& targetInfo) { return targetInfo.TargetGUID == targetGUID && targetInfo.MissCondition != SPELL_MISS_NONE; }); + if (ihit != std::end(m_UniqueTargetInfo)) { - if (ihit->targetGUID == targetGUID) - { - if (ihit->missCondition != SPELL_MISS_NONE) - { - hit = false; - //lower spell cost on fail (by talent aura) - if (Player* modOwner = m_caster->ToPlayer()->GetSpellModOwner()) - modOwner->ApplySpellMod(m_spellInfo, SpellModOp::PowerCostOnMiss, cost.Amount); - } - break; - } + hit = false; + //lower spell cost on fail (by talent aura) + if (Player* modOwner = m_caster->ToPlayer()->GetSpellModOwner()) + modOwner->ApplySpellMod(m_spellInfo, SpellModOp::PowerCostOnMiss, cost.Amount); } } } @@ -4987,13 +4996,13 @@ void Spell::HandleThreatSpells() // since 2.0.1 threat from positive effects also is distributed among all targets, so the overall caused threat is at most the defined bonus threat /= m_UniqueTargetInfo.size(); - for (std::vector::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + for (auto ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) { float threatToAdd = threat; - if (ihit->missCondition != SPELL_MISS_NONE) + if (ihit->MissCondition != SPELL_MISS_NONE) threatToAdd = 0.0f; - Unit* target = ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID); + Unit* target = ObjectAccessor::GetUnit(*m_caster, ihit->TargetGUID); if (!target) continue; @@ -6346,8 +6355,8 @@ bool Spell::CanAutoCast(Unit* target) return true; SelectSpellTargets(); //check if among target units, our WANTED target is as well (->only self cast spells return false) - for (std::vector::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) - if (ihit->targetGUID == targetguid) + for (auto ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + if (ihit->TargetGUID == targetguid) return true; } // either the cast failed or the intended target wouldn't be hit @@ -7104,9 +7113,9 @@ void Spell::DelayedChannel() TC_LOG_DEBUG("spells", "Spell %u partially interrupted for %i ms, new duration: %u ms", m_spellInfo->Id, delaytime, m_timer); - for (std::vector::const_iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) - if ((*ihit).missCondition == SPELL_MISS_NONE) - if (Unit* unit = (m_caster->GetGUID() == ihit->targetGUID) ? m_caster : ObjectAccessor::GetUnit(*m_caster, ihit->targetGUID)) + for (TargetInfo const& targetInfo : m_UniqueTargetInfo) + if (targetInfo.MissCondition == SPELL_MISS_NONE) + if (Unit* unit = (m_caster->GetGUID() == targetInfo.TargetGUID) ? m_caster : ObjectAccessor::GetUnit(*m_caster, targetInfo.TargetGUID)) unit->DelayOwnedAuras(m_spellInfo->Id, m_originalCasterGUID, delaytime); // partially interrupt persistent area auras @@ -7357,23 +7366,6 @@ bool Spell::IsNeedSendToClient() const (m_spellInfo->HasAttribute(SPELL_ATTR8_AURA_SEND_AMOUNT)) || m_spellInfo->HasHitDelay() || (!m_triggeredByAuraSpell && !IsTriggered()); } -bool Spell::HaveTargetsForEffect(uint8 effect) const -{ - for (std::vector::const_iterator itr = m_UniqueTargetInfo.begin(); itr != m_UniqueTargetInfo.end(); ++itr) - if (itr->effectMask & (1 << effect)) - return true; - - for (std::vector::const_iterator itr = m_UniqueGOTargetInfo.begin(); itr != m_UniqueGOTargetInfo.end(); ++itr) - if (itr->effectMask & (1 << effect)) - return true; - - for (std::vector::const_iterator itr = m_UniqueItemInfo.begin(); itr != m_UniqueItemInfo.end(); ++itr) - if (itr->effectMask & (1 << effect)) - return true; - - return false; -} - SpellEvent::SpellEvent(Spell* spell) : BasicEvent() { m_Spell = spell; @@ -7523,80 +7515,82 @@ void Spell::HandleLaunchPhase() HandleEffects(nullptr, nullptr, nullptr, effect->EffectIndex, SPELL_EFFECT_HANDLE_LAUNCH); } - float multiplier[MAX_SPELL_EFFECTS]; - for (SpellEffectInfo const* effect : m_spellInfo->GetEffects()) - if (effect && (m_applyMultiplierMask & (1 << effect->EffectIndex))) - multiplier[effect->EffectIndex] = effect->CalcDamageMultiplier(m_originalCaster, this); - PrepareTargetProcessing(); - for (auto ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + for (SpellEffectInfo const* effect : m_spellInfo->GetEffects()) { - TargetInfo& target = *ihit; - - uint32 mask = target.effectMask; - if (!mask) + if (!effect) continue; - DoAllEffectOnLaunchTarget(target, multiplier); + float multiplier = 1.0f; + if (m_applyMultiplierMask & (1 << effect->EffectIndex)) + multiplier = effect->CalcDamageMultiplier(m_originalCaster, this); + + for (TargetInfo& target : m_UniqueTargetInfo) + { + uint32 mask = target.EffectMask; + if (!(mask & (1 << effect->EffectIndex))) + continue; + + DoEffectOnLaunchTarget(target, multiplier, effect); + } } FinishTargetProcessing(); } -void Spell::DoAllEffectOnLaunchTarget(TargetInfo& targetInfo, float* multiplier) +void Spell::DoEffectOnLaunchTarget(TargetInfo& targetInfo, float multiplier, SpellEffectInfo const* effect) { Unit* unit = nullptr; // In case spell hit target, do all effect on that target - if (targetInfo.missCondition == SPELL_MISS_NONE) - unit = m_caster->GetGUID() == targetInfo.targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, targetInfo.targetGUID); + if (targetInfo.MissCondition == SPELL_MISS_NONE) + unit = m_caster->GetGUID() == targetInfo.TargetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, targetInfo.TargetGUID); // In case spell reflect from target, do all effect on caster (if hit) - else if (targetInfo.missCondition == SPELL_MISS_REFLECT && targetInfo.reflectResult == SPELL_MISS_NONE) + else if (targetInfo.MissCondition == SPELL_MISS_REFLECT && targetInfo.ReflectResult == SPELL_MISS_NONE) unit = m_caster; if (!unit) return; // This will only cause combat - the target will engage once the projectile hits (in DoAllEffectOnTarget) - if (targetInfo.missCondition != SPELL_MISS_EVADE && !m_caster->IsFriendlyTo(unit) && (!m_spellInfo->IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL)) && (m_spellInfo->HasInitialAggro() || unit->IsEngaged())) + if (targetInfo.MissCondition != SPELL_MISS_EVADE && !m_caster->IsFriendlyTo(unit) && (!m_spellInfo->IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL)) && (m_spellInfo->HasInitialAggro() || unit->IsEngaged())) m_caster->SetInCombatWith(unit); - for (SpellEffectInfo const* effect : m_spellInfo->GetEffects()) - { - if (effect && (targetInfo.effectMask & (1<EffectIndex))) - { - m_damage = 0; - m_healing = 0; - - HandleEffects(unit, nullptr, nullptr, effect->EffectIndex, SPELL_EFFECT_HANDLE_LAUNCH_TARGET); + m_damage = 0; + m_healing = 0; - if (m_damage > 0) - { - if (effect->IsTargetingArea() || effect->IsAreaAuraEffect() || effect->IsEffect(SPELL_EFFECT_PERSISTENT_AREA_AURA)) - { - m_damage = unit->CalculateAOEAvoidance(m_damage, m_spellInfo->SchoolMask, m_caster->GetGUID()); + HandleEffects(unit, nullptr, nullptr, effect->EffectIndex, SPELL_EFFECT_HANDLE_LAUNCH_TARGET); - if (m_caster->GetTypeId() == TYPEID_PLAYER) - { - int64 targetAmount = GetUnitTargetCountForEffect(SpellEffIndex(effect->EffectIndex)); - if (targetAmount > 20) - m_damage = m_damage * 20 / targetAmount; - } - } - } + if (m_damage > 0) + { + if (effect->IsTargetingArea() || effect->IsAreaAuraEffect() || effect->IsEffect(SPELL_EFFECT_PERSISTENT_AREA_AURA)) + { + m_damage = unit->CalculateAOEAvoidance(m_damage, m_spellInfo->SchoolMask, m_caster->GetGUID()); - if (m_applyMultiplierMask & (1 << effect->EffectIndex)) + if (m_caster->GetTypeId() == TYPEID_PLAYER) { - m_damage = int32(m_damage * m_damageMultipliers[effect->EffectIndex]); - m_damageMultipliers[effect->EffectIndex] *= multiplier[effect->EffectIndex]; + // cap damage of player AOE + int64 targetAmount = GetUnitTargetCountForEffect(SpellEffIndex(effect->EffectIndex)); + if (targetAmount > 20) + m_damage = m_damage * 20 / targetAmount; } - targetInfo.damage += m_damage; } } + if (m_applyMultiplierMask & (1 << effect->EffectIndex)) + { + m_damage = int32(m_damage * m_damageMultipliers[effect->EffectIndex]); + m_healing = int32(m_healing * m_damageMultipliers[effect->EffectIndex]); + + m_damageMultipliers[effect->EffectIndex] *= multiplier; + } + + targetInfo.Damage += m_damage; + targetInfo.Healing += m_healing; + float critChance = m_spellValue->CriticalChance; if (!critChance) critChance = m_caster->SpellCritChanceDone(this, nullptr, m_spellSchoolMask, m_attackType); - targetInfo.crit = roll_chance_f(unit->SpellCritChanceTaken(m_caster, this, nullptr, m_spellSchoolMask, critChance, m_attackType)); + targetInfo.IsCrit = roll_chance_f(unit->SpellCritChanceTaken(m_caster, this, nullptr, m_spellSchoolMask, critChance, m_attackType)); } SpellCastResult Spell::CanOpenLock(uint32 effIndex, uint32 lockId, SkillType& skillId, int32& reqSkillValue, int32& skillValue) @@ -7776,18 +7770,14 @@ SpellCastResult Spell::CallScriptCheckCastHandlers() return retVal; } -void Spell::PrepareScriptHitHandlers() -{ - for (auto scritr = m_loadedScripts.begin(); scritr != m_loadedScripts.end(); ++scritr) - (*scritr)->_InitHit(); -} - bool Spell::CallScriptEffectHandlers(SpellEffIndex effIndex, SpellEffectHandleMode mode) { // execute script effect handler hooks and check if effects was prevented bool preventDefault = false; for (auto scritr = m_loadedScripts.begin(); scritr != m_loadedScripts.end(); ++scritr) { + (*scritr)->_InitHit(); + HookList::iterator effItr, effEndItr; SpellScriptHookType hookType; switch (mode) @@ -7847,6 +7837,7 @@ void Spell::CallScriptBeforeHitHandlers(SpellMissInfo missInfo) { for (auto scritr = m_loadedScripts.begin(); scritr != m_loadedScripts.end(); ++scritr) { + (*scritr)->_InitHit(); (*scritr)->_PrepareScriptCall(SPELL_SCRIPT_HOOK_BEFORE_HIT); auto hookItrEnd = (*scritr)->BeforeHit.end(), hookItr = (*scritr)->BeforeHit.begin(); for (; hookItr != hookItrEnd; ++hookItr) diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index cb34bb00c0c..ef971cccac2 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -567,7 +567,6 @@ class TC_GAME_API Spell int32 CalculateDamage(uint8 i, Unit const* target, float* var = nullptr) const; - bool HaveTargetsForEffect(uint8 effect) const; void Delayed(); void DelayedChannel(); uint32 getState() const { return m_spellState; } @@ -793,39 +792,69 @@ class TC_GAME_API Spell // Spell target subsystem // ***************************************** // Targets store structures and data - struct TargetInfo + struct TargetInfoBase { - ObjectGuid targetGUID; - uint64 timeDelay; - int32 damage; + virtual void PreprocessTarget(Spell* /*spell*/) { } + virtual void DoTargetSpellHit(Spell* spell, uint8 effIndex) = 0; + virtual void DoDamageAndTriggers(Spell* /*spell*/) { } - SpellMissInfo missCondition; - SpellMissInfo reflectResult; + uint32 EffectMask = 0; - uint32 effectMask; - bool processed; - bool alive; - bool crit; + protected: + TargetInfoBase() { } + virtual ~TargetInfoBase() { } + }; + + struct TargetInfo : public TargetInfoBase + { + void PreprocessTarget(Spell* spell) override; + void DoTargetSpellHit(Spell* spell, uint8 effIndex) override; + void DoDamageAndTriggers(Spell* spell) override; + + ObjectGuid TargetGUID; + uint64 TimeDelay = 0ULL; + int32 Damage = 0; + int32 Healing = 0; + + SpellMissInfo MissCondition = SPELL_MISS_NONE; + SpellMissInfo ReflectResult = SPELL_MISS_NONE; + + bool IsAlive = false; + bool IsCrit = false; + + // info set at PreprocessTarget, used by DoTargetSpellHit + DiminishingGroup DRGroup = DIMINISHING_NONE; + int32 AuraDuration = 0; + int32 AuraBasePoints[MAX_SPELL_EFFECTS] = { }; + bool Positive = true; + + private: + Unit* _spellHitTarget = nullptr; // changed for example by reflect + bool _enablePVP = false; // need to enable PVP at DoDamageAndTriggers? }; std::vector m_UniqueTargetInfo; - uint32 m_channelTargetEffectMask; // Mask req. alive targets + uint32 m_channelTargetEffectMask; // Mask req. alive targets - struct GOTargetInfo + struct GOTargetInfo : public TargetInfoBase { - ObjectGuid targetGUID; - uint64 timeDelay; - uint32 effectMask; - bool processed; + void DoTargetSpellHit(Spell* spell, uint8 effIndex) override; + + ObjectGuid TargetGUID; + uint64 TimeDelay = 0ULL; }; std::vector m_UniqueGOTargetInfo; - struct ItemTargetInfo + struct ItemTargetInfo : public TargetInfoBase { - Item *item; - uint32 effectMask; + void DoTargetSpellHit(Spell* spell, uint8 effIndex) override; + + Item* TargetItem = nullptr; }; std::vector m_UniqueItemInfo; + template + void DoProcessTargetContainer(Container& targetContainer); + SpellDestination m_destTargets[MAX_SPELL_EFFECTS]; void AddUnitTarget(Unit* target, uint32 effectMask, bool checkIfValid = true, bool implicit = true, Position const* losPosition = nullptr); @@ -833,15 +862,14 @@ class TC_GAME_API Spell void AddItemTarget(Item* item, uint32 effectMask); void AddDestTarget(SpellDestination const& dest, uint32 effIndex); - void DoAllEffectOnTarget(TargetInfo* target); - SpellMissInfo DoSpellHitOnUnit(Unit* unit, uint32 effectMask); + SpellMissInfo PreprocessSpellHit(Unit* unit, TargetInfo& targetInfo); + void DoSpellEffectHit(Unit* unit, uint8 effIndex, TargetInfo& targetInfo); + void DoTriggersOnSpellHit(Unit* unit, uint32 effMask); - void DoAllEffectOnTarget(GOTargetInfo* target); - void DoAllEffectOnTarget(ItemTargetInfo* target); bool UpdateChanneledTargetList(); bool IsValidDeadOrAliveTarget(Unit const* target) const; void HandleLaunchPhase(); - void DoAllEffectOnLaunchTarget(TargetInfo& targetInfo, float* multiplier); + void DoEffectOnLaunchTarget(TargetInfo& targetInfo, float multiplier, SpellEffectInfo const* effect); void PrepareTargetProcessing(); void FinishTargetProcessing(); @@ -852,7 +880,6 @@ class TC_GAME_API Spell void CallScriptOnCastHandlers(); void CallScriptAfterCastHandlers(); SpellCastResult CallScriptCheckCastHandlers(); - void PrepareScriptHitHandlers(); bool CallScriptEffectHandlers(SpellEffIndex effIndex, SpellEffectHandleMode mode); void CallScriptSuccessfulDispel(SpellEffIndex effIndex); void CallScriptBeforeHitHandlers(SpellMissInfo missInfo); diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 0a135d782ad..1551517dcb5 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -577,8 +577,8 @@ void Spell::EffectDummy(SpellEffIndex effIndex) case 31789: // Righteous Defense (step 1) { // Clear targets for eff 1 - for (std::vector::iterator ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) - ihit->effectMask &= ~(1<<1); + for (auto ihit = m_UniqueTargetInfo.begin(); ihit != m_UniqueTargetInfo.end(); ++ihit) + ihit->EffectMask &= ~(1 << 1); // not empty (checked), copy Unit::AttackerSet attackers = unitTarget->getAttackers(); @@ -1152,7 +1152,7 @@ void Spell::EffectPowerBurn(SpellEffIndex effIndex) // add log data before multiplication (need power amount, not damage) ExecuteLogEffectTakeTargetPower(effIndex, unitTarget, powerType, newDamage, 0.0f); - newDamage = int32(newDamage* dmgMultiplier); + newDamage = int32(newDamage * dmgMultiplier); m_damage += newDamage; } @@ -1177,10 +1177,10 @@ void Spell::EffectHeal(SpellEffIndex /*effIndex*/) if (m_spellInfo->Id == 45064) { // Amount of heal - depends from stacked Holy Energy - int damageAmount = 0; + int32 damageAmount = 0; if (AuraEffect const* aurEff = m_caster->GetAuraEffect(45062, 0)) { - damageAmount+= aurEff->GetAmount(); + damageAmount += aurEff->GetAmount(); m_caster->RemoveAurasDueToSpell(45062); } @@ -1208,7 +1208,7 @@ void Spell::EffectHeal(SpellEffIndex /*effIndex*/) if (unitTarget->HasAura(48920) && (unitTarget->GetHealth() + addhealth >= unitTarget->GetMaxHealth())) unitTarget->RemoveAura(48920); - m_damage -= addhealth; + m_healing += addhealth; } } diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index e1aceb1de97..6f74c90ece9 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -447,18 +447,6 @@ bool SpellEffectInfo::IsAreaAuraEffect() const return false; } -bool SpellEffectInfo::IsFarUnitTargetEffect() const -{ - return (Effect == SPELL_EFFECT_SUMMON_PLAYER) - || (Effect == SPELL_EFFECT_SUMMON_RAF_FRIEND) - || (Effect == SPELL_EFFECT_RESURRECT); -} - -bool SpellEffectInfo::IsFarDestTargetEffect() const -{ - return Effect == SPELL_EFFECT_TELEPORT_UNITS; -} - bool SpellEffectInfo::IsUnitOwnedAuraEffect() const { return IsAreaAuraEffect() || Effect == SPELL_EFFECT_APPLY_AURA || Effect == SPELL_EFFECT_APPLY_AURA_ON_PET; @@ -3740,14 +3728,14 @@ int32 SpellInfo::CalcDuration(Unit* caster /*= nullptr*/) const int32 SpellInfo::GetDuration() const { if (!DurationEntry) - return 0; + return IsPassive() ? -1 : 0; return (DurationEntry->Duration == -1) ? -1 : abs(DurationEntry->Duration); } int32 SpellInfo::GetMaxDuration() const { if (!DurationEntry) - return 0; + return IsPassive() ? -1 : 0; return (DurationEntry->MaxDuration == -1) ? -1 : abs(DurationEntry->MaxDuration); } diff --git a/src/server/game/Spells/SpellInfo.h b/src/server/game/Spells/SpellInfo.h index 2d50ac0a8d1..54ec93a8471 100644 --- a/src/server/game/Spells/SpellInfo.h +++ b/src/server/game/Spells/SpellInfo.h @@ -312,8 +312,6 @@ public: bool IsAura(AuraType aura) const; bool IsTargetingArea() const; bool IsAreaAuraEffect() const; - bool IsFarUnitTargetEffect() const; - bool IsFarDestTargetEffect() const; bool IsUnitOwnedAuraEffect() const; int32 CalcValue(Unit const* caster = nullptr, int32 const* basePoints = nullptr, Unit const* target = nullptr, float* variance = nullptr, uint32 castItemId = 0, int32 itemLevel = -1) const; diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 78fe53bb051..1e6d3cbd5f5 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -3894,20 +3894,6 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->MaxAffectedTargets = 1; }); - // Boom (XT-002) - ApplySpellFix({ 62834 }, [](SpellInfo* spellInfo) - { - // This hack is here because we suspect our implementation of spell effect execution on targets - // is done in the wrong order. We suspect that EFFECT_0 needs to be applied on all targets, - // then EFFECT_1, etc - instead of applying each effect on target1, then target2, etc. - // The above situation causes the visual for this spell to be bugged, so we remove the instakill - // effect and implement a script hack for that. - ApplySpellEffectFix(spellInfo, EFFECT_1, [](SpellEffectInfo* spellEffectInfo) - { - spellEffectInfo->Effect = 0; - }); - }); - ApplySpellFix({ 64386, // Terrifying Screech (Auriaya) 64389, // Sentinel Blast (Auriaya) diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp index 236353877e2..d962c6c7fb8 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_xt002.cpp @@ -631,29 +631,6 @@ class npc_pummeller : public CreatureScript * XE-321 BOOMBOT * *///---------------------------------------------------- -class BoomEvent : public BasicEvent -{ - public: - BoomEvent(Creature* me) : _me(me) - { - } - - bool Execute(uint64 /*time*/, uint32 /*diff*/) override - { - // This hack is here because we suspect our implementation of spell effect execution on targets - // is done in the wrong order. We suspect that EFFECT_0 needs to be applied on all targets, - // then EFFECT_1, etc - instead of applying each effect on target1, then target2, etc. - // The above situation causes the visual for this spell to be bugged, so we remove the instakill - // effect and implement a script hack for that. - - _me->CastSpell(_me, SPELL_BOOM, false); - return true; - } - - private: - Creature* _me; -}; - class npc_boombot : public CreatureScript { public: @@ -699,12 +676,7 @@ class npc_boombot : public CreatureScript damage = 0; - // Visual only seems to work if the instant kill event is delayed or the spell itself is delayed - // Casting done from player and caster source has the same targetinfo flags, - // so that can't be the issue - // See BoomEvent class - // Schedule 1s delayed - me->m_Events.AddEvent(new BoomEvent(me), me->m_Events.CalculateTime(1*IN_MILLISECONDS)); + DoCastAOE(SPELL_BOOM); } } -- cgit v1.2.3