diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index 61420e02e7..a7be872a2e 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -312,7 +312,7 @@ bool CreatureAI::_EnterEvadeMode(EvadeReason /*why*/) me->ResetPlayerDamageReq(); me->SetLastDamagedTime(0); me->SetCannotReachTarget(false); - me->DoNotReacquireSpellFocusTarget(); + me->ResetSpellFocusInfo(); me->GetSpellHistory()->ResetAllCooldowns(); me->SetTarget(ObjectGuid::Empty); EngagementOver(); diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index ef83937d31..6f622a9eaf 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -766,12 +766,12 @@ void Creature::Update(uint32 diff) break; GetThreatManager().Update(diff); - if (_spellFocusInfo.delay) + if (_spellFocusInfo.ReacquiringTargetDelay) { - if (_spellFocusInfo.delay <= diff) + if (_spellFocusInfo.ReacquiringTargetDelay <= diff) ReacquireSpellFocusTarget(); else - _spellFocusInfo.delay -= diff; + _spellFocusInfo.ReacquiringTargetDelay -= diff; } // periodic check to see if the creature has passed an evade boundary @@ -1903,9 +1903,9 @@ void Creature::setDeathState(DeathState s) SaveRespawnTime(); - ReleaseSpellFocus(nullptr, false); // remove spellcast focus - DoNotReacquireSpellFocusTarget(); // cancel delayed re-target - SetTarget(ObjectGuid::Empty); // drop target - dead mobs shouldn't ever target things + ReleaseSpellFocus(nullptr, false); // remove spellcast focus + _spellFocusInfo.Reset(); // cancel pending reacquisition of targets post spell focusing. + SetTarget(ObjectGuid::Empty); // drop target - dead mobs shouldn't ever target things SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); @@ -3064,15 +3064,19 @@ void Creature::SetDisplayId(uint32 modelId) void Creature::SetTarget(ObjectGuid guid) { if (HasSpellFocus()) - _spellFocusInfo.target = guid; + _spellFocusInfo.OriginalUnitTarget = guid; else SetGuidValue(UNIT_FIELD_TARGET, guid); } void Creature::SetSpellFocus(Spell const* focusSpell, WorldObject const* target) { - // Pointer validation and checking for a already existing focus - if (_spellFocusInfo.spell || !focusSpell) + // Pointer validation + if (!focusSpell || !target) + return; + + // Creature already has a spell focus + if (_spellFocusInfo.FocusSpell) return; // Prevent dead / feign death creatures from setting a focus target @@ -3093,33 +3097,58 @@ void Creature::SetSpellFocus(Spell const* focusSpell, WorldObject const* target) if (spellInfo->HasAura(SPELL_AURA_CONTROL_VEHICLE)) return; - // instant non-channeled casts and non-target spells don't need facing updates - if (!target && (!focusSpell->GetCastTime() && !spellInfo->IsChanneled())) + // Instant casts and non-channeled spells do not need a spell focus + if (!focusSpell->GetCastTime() && !spellInfo->IsChanneled()) return; - // store pre-cast values for target and orientation (used to later restore) - if (!_spellFocusInfo.delay) + // Store original orientation and target values to restore them when releasing the spell focus + if (!_spellFocusInfo.ReacquiringTargetDelay) { // only overwrite these fields if we aren't transitioning from one spell focus to another - _spellFocusInfo.target = GetGuidValue(UNIT_FIELD_TARGET); - _spellFocusInfo.orientation = GetOrientation(); + _spellFocusInfo.OriginalUnitTarget = GetGuidValue(UNIT_FIELD_TARGET); + _spellFocusInfo.OriginalOrientation = GetOrientation(); } else // don't automatically reacquire target for the previous spellcast - _spellFocusInfo.delay = 0; + _spellFocusInfo.ReacquiringTargetDelay = 0; - _spellFocusInfo.spell = focusSpell; + _spellFocusInfo.FocusSpell = focusSpell; + // Focusing behaves different based on the given spell attributes. Tracking channel spells only face the target's direction and stay there. Normal spell casts fixate on their target instead bool const noTurnDuringCast = spellInfo->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST); bool const turnDisabled = HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_DISABLE_TURN); - // set target, then force send update packet to players if it changed to provide appropriate facing - ObjectGuid newTarget = (target && !noTurnDuringCast && !turnDisabled) ? target->GetGUID() : ObjectGuid::Empty; - if (GetGuidValue(UNIT_FIELD_TARGET) != newTarget) + + ObjectGuid newTarget = [&]() + { + // Spells that do not allow turning or channeled spells that will track their channel target do not set a unit field target + if (turnDisabled || noTurnDuringCast || spellInfo->HasAttribute(SPELL_ATTR1_CHANNEL_TRACK_TARGET) || target == this) + return ObjectGuid::Empty; + + return target->GetGUID(); + }(); + + if (GetTarget() != newTarget) + { SetGuidValue(UNIT_FIELD_TARGET, newTarget); - // If we are not allowed to turn during cast but have a focus target, face the target - if (!turnDisabled && noTurnDuringCast && target) - SetFacingToObject(target, false); + // Sniffs indicate that UNIT_FIELD_TARGET is being updated outside of the regular update object interval so we squeeze the packet out as well + std::vector playersNearby; + GetPlayerListInGrid(playersNearby, GetVisibilityRange()); + for (Player* player : playersNearby) + { + // only update players that are known to the client (have already been created) + if (player->HaveAtClient(this)) + SendUpdateToPlayer(player); + } + } - if (noTurnDuringCast) + // Spells that do not face via UNIT_FIELD_TARGET and can turn, will face their target one last time + if (newTarget.IsEmpty() && !turnDisabled && target != this) + { + SetFacingTo(GetAngle(target)); + // We to manually update the orientation as the spline does so on the next update tick while we need a updated position now for further spell target selection + SetOrientationTowards(target); + } + + if (noTurnDuringCast || (!turnDisabled && newTarget.IsEmpty())) AddUnitState(UNIT_STATE_FOCUSING); } @@ -3127,30 +3156,30 @@ bool Creature::HasSpellFocus(Spell const* focusSpell) const { if (isDead()) // dead creatures cannot focus { - if (_spellFocusInfo.spell || _spellFocusInfo.delay) + if (_spellFocusInfo.FocusSpell || _spellFocusInfo.ReacquiringTargetDelay) { TC_LOG_WARN("entities.unit", "Creature '%s' (entry %u) has spell focus (spell id %u, delay %ums) despite being dead.", - GetName().c_str(), GetEntry(), _spellFocusInfo.spell ? _spellFocusInfo.spell->GetSpellInfo()->Id : 0, _spellFocusInfo.delay); + GetName().c_str(), GetEntry(), _spellFocusInfo.FocusSpell ? _spellFocusInfo.FocusSpell->GetSpellInfo()->Id : 0, _spellFocusInfo.ReacquiringTargetDelay); } return false; } if (focusSpell) - return (focusSpell == _spellFocusInfo.spell); + return (focusSpell == _spellFocusInfo.FocusSpell); else - return (_spellFocusInfo.spell || _spellFocusInfo.delay); + return (_spellFocusInfo.FocusSpell || _spellFocusInfo.ReacquiringTargetDelay); } void Creature::ReleaseSpellFocus(Spell const* focusSpell, bool withDelay) { - if (!_spellFocusInfo.spell) + if (!_spellFocusInfo.FocusSpell) return; // focused to something else - if (focusSpell && focusSpell != _spellFocusInfo.spell) + if (focusSpell && focusSpell != _spellFocusInfo.FocusSpell) return; - if (_spellFocusInfo.spell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST)) + if (_spellFocusInfo.FocusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST)) ClearUnitState(UNIT_STATE_FOCUSING); if (IsPet()) // player pets do not use delay system @@ -3159,9 +3188,9 @@ void Creature::ReleaseSpellFocus(Spell const* focusSpell, bool withDelay) ReacquireSpellFocusTarget(); } else // don't allow re-target right away to prevent visual bugs - _spellFocusInfo.delay = withDelay ? 1000 : 1; + _spellFocusInfo.ReacquiringTargetDelay = withDelay ? 1000 : 1; - _spellFocusInfo.spell = nullptr; + _spellFocusInfo.FocusSpell = nullptr; } void Creature::ReacquireSpellFocusTarget() @@ -3172,25 +3201,19 @@ void Creature::ReacquireSpellFocusTarget() return; } - SetGuidValue(UNIT_FIELD_TARGET, _spellFocusInfo.target); + SetGuidValue(UNIT_FIELD_TARGET, _spellFocusInfo.OriginalUnitTarget); if (!HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_DISABLE_TURN)) { - if (_spellFocusInfo.target) + if (_spellFocusInfo.OriginalUnitTarget) { - if (WorldObject const* objTarget = ObjectAccessor::GetWorldObject(*this, _spellFocusInfo.target)) + if (WorldObject const* objTarget = ObjectAccessor::GetWorldObject(*this, _spellFocusInfo.OriginalUnitTarget)) SetFacingToObject(objTarget, false); } else - SetFacingTo(_spellFocusInfo.orientation, false); + SetFacingTo(_spellFocusInfo.OriginalOrientation, false); } - _spellFocusInfo.delay = 0; -} - -void Creature::DoNotReacquireSpellFocusTarget() -{ - _spellFocusInfo.delay = 0; - _spellFocusInfo.spell = nullptr; + _spellFocusInfo.ReacquiringTargetDelay = 0; } bool Creature::IsMovementPreventedByCasting() const diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 8bc1b894a7..cdef4b17bf 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -343,11 +343,10 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma // Handling caster facing during spellcast void SetTarget(ObjectGuid guid) override; void ReacquireSpellFocusTarget(); - void DoNotReacquireSpellFocusTarget(); void SetSpellFocus(Spell const* focusSpell, WorldObject const* target); bool HasSpellFocus(Spell const* focusSpell = nullptr) const override; void ReleaseSpellFocus(Spell const* focusSpell = nullptr, bool withDelay = true); - void ResetSpellFocusReacquisition() { _spellFocusInfo.delay = 0; } + void ResetSpellFocusInfo() { _spellFocusInfo.Reset(); } bool IsMovementPreventedByCasting() const override; @@ -452,14 +451,8 @@ class TC_GAME_API Creature : public Unit, public GridObject, public Ma bool m_triggerJustAppeared; bool m_respawnCompatibilityMode; - /* Spell focus system */ - struct - { - Spell const* spell = nullptr; - uint32 delay = 0; // ms until the creature's target should snap back (0 = no snapback scheduled) - ObjectGuid target; // the creature's "real" target while casting - float orientation = 0.0f; // the creature's "real" orientation while casting - } _spellFocusInfo; + // Spell Focusing + CreatureSpellFocusData _spellFocusInfo; CreatureTextRepeatGroup m_textRepeat; diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index c366068a91..e886eb89b7 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -28,6 +28,7 @@ #include #include +class Spell; class WorldPacket; struct ItemTemplate; enum class VisibilityDistanceType : uint8; @@ -394,5 +395,19 @@ struct CreatureMovementInfo bool HasWalkSpeedOverriden = false; }; +struct CreatureSpellFocusData +{ + Spell const* FocusSpell = nullptr; + ObjectGuid OriginalUnitTarget; + uint32 ReacquiringTargetDelay = 0; + float OriginalOrientation = 0.f; + +public: + void Reset() + { + FocusSpell = nullptr; + ReacquiringTargetDelay = 0; + } +}; #endif // CreatureData_h__ diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index 9f48640415..05c257db76 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -391,11 +391,11 @@ enum SpellAttr1 { SPELL_ATTR1_DISMISS_PET = 0x00000001, // 0 for spells without this flag client doesn't allow to summon pet if caster has a pet SPELL_ATTR1_DRAIN_ALL_POWER = 0x00000002, // 1 use all power (Only paladin Lay of Hands and Bunyanize) - SPELL_ATTR1_CHANNELED_1 = 0x00000004, // 2 clientside checked? cancelable? + SPELL_ATTR1_CHANNELED = 0x00000004, // 2 Is Channeled SPELL_ATTR1_CANT_BE_REDIRECTED = 0x00000008, // 3 SPELL_ATTR1_UNK4 = 0x00000010, // 4 stealth and whirlwind SPELL_ATTR1_NOT_BREAK_STEALTH = 0x00000020, // 5 Not break stealth - SPELL_ATTR1_CHANNELED_2 = 0x00000040, // 6 + SPELL_ATTR1_SELF_CHANNELED = 0x00000040, // 6 Is Self Channeled SPELL_ATTR1_CANT_BE_REFLECTED = 0x00000080, // 7 SPELL_ATTR1_CANT_TARGET_IN_COMBAT = 0x00000100, // 8 can target only out of combat units SPELL_ATTR1_MELEE_COMBAT_START = 0x00000200, // 9 player starts melee combat after this spell is cast diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp index 7ffdb56652..90cc7df954 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -652,7 +652,7 @@ void MotionMaster::MoveSeekAssistance(float x, float y, float z) TC_LOG_DEBUG("movement.motionmaster", "MotionMaster::MoveSeekAssistance: '%s', seeks assistance (X: %f, Y: %f, Z: %f)", creature->GetGUID().ToString().c_str(), x, y, z); creature->AttackStop(); creature->CastStop(); - creature->DoNotReacquireSpellFocusTarget(); + creature->ResetSpellFocusInfo(); creature->SetReactState(REACT_PASSIVE); Mutate(new AssistanceMovementGenerator(x, y, z), MOTION_SLOT_ACTIVE); } diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 32c550b081..2bcef69af0 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -3357,15 +3357,8 @@ void Spell::prepare(SpellCastTargets const& targets, AuraEffect const* triggered } // Creatures focus their target when possible - if (m_casttime && m_caster->IsCreature() && !m_spellInfo->IsNextMeleeSwingSpell() && !IsAutoRepeat() && !m_caster->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED)) - { - // Channeled spells and some triggered spells do not focus a cast target. They face their target later on via channel object guid and via spell attribute or not at all - bool const focusTarget = !m_spellInfo->IsChanneled() && !(_triggeredCastFlags & TRIGGERED_IGNORE_SET_FACING); - if (focusTarget && m_targets.GetObjectTarget() && m_caster != m_targets.GetObjectTarget()) - m_caster->ToCreature()->SetSpellFocus(this, m_targets.GetObjectTarget()); - else - m_caster->ToCreature()->SetSpellFocus(this, nullptr); - } + if (m_casttime && m_caster->IsCreature() && !m_spellInfo->IsNextMeleeSwingSpell() && !IsAutoRepeat() && !m_caster->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_POSSESSED) && !(_triggeredCastFlags & TRIGGERED_IGNORE_SET_FACING)) + m_caster->ToCreature()->SetSpellFocus(this, m_targets.GetObjectTarget()); // set timer base at cast time ReSetTimer(); @@ -3658,6 +3651,10 @@ void Spell::_cast(bool skipCheck) TakeReagents(); } + if (!m_spellInfo->IsChanneled()) + if (Creature* creatureCaster = m_caster->ToCreature()) + creatureCaster->ReleaseSpellFocus(this); + // CAST SPELL SendSpellCooldown(); @@ -3668,10 +3665,6 @@ void Spell::_cast(bool skipCheck) // we must send smsg_spell_go packet before m_castItem delete in TakeCastItem()... SendSpellGo(); - if (!m_spellInfo->IsChanneled()) - if (Creature* creatureCaster = m_caster->ToCreature()) - creatureCaster->ReleaseSpellFocus(this); - // Okay, everything is prepared. Now we need to distinguish between immediate and evented delayed spells if ((m_spellInfo->Speed > 0.0f && !m_spellInfo->IsChanneled()) || m_spellInfo->HasAttribute(SPELL_ATTR4_UNK4)) { @@ -4893,7 +4886,14 @@ void Spell::SendChannelUpdate(uint32 time) void Spell::SendChannelStart(uint32 duration) { - ObjectGuid channelTarget = m_targets.HasDst() ? m_caster->GetGUID() : m_targets.GetObjectTargetGUID(); + ObjectGuid channelTarget = [&]() + { + if (m_spellInfo->HasAttribute(SPELL_ATTR1_SELF_CHANNELED)) + return m_caster->GetGUID(); + + return m_targets.GetObjectTargetGUID(); + }(); + if (!channelTarget && !m_spellInfo->NeedsExplicitUnitTarget()) { // this is for TARGET_SELECT_CATEGORY_NEARBY diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index 1ffe36e63a..bf41b91125 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -1447,7 +1447,7 @@ bool SpellInfo::IsPositiveEffect(uint8 effIndex) const bool SpellInfo::IsChanneled() const { - return HasAttribute(SpellAttr1(SPELL_ATTR1_CHANNELED_1 | SPELL_ATTR1_CHANNELED_2)); + return HasAttribute(SpellAttr1(SPELL_ATTR1_CHANNELED | SPELL_ATTR1_SELF_CHANNELED)); } bool SpellInfo::IsMoveAllowedChannel() const diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 7168ffa955..8bf6029984 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -4848,7 +4848,7 @@ void SpellMgr::LoadSpellInfoCorrections() // Spore Cloud ApplySpellFix({ 75701 }, [](SpellInfo* spellInfo) { - spellInfo->AttributesEx &= ~SPELL_ATTR1_CHANNELED_1; + spellInfo->AttributesEx &= ~SPELL_ATTR1_CHANNELED; }); // Noxious Spores @@ -4921,7 +4921,7 @@ void SpellMgr::LoadSpellInfoCorrections() // Gaze of Occu'thar ApplySpellFix({ 96942, 101009 }, [](SpellInfo* spellInfo) { - spellInfo->AttributesEx &= ~SPELL_ATTR1_CHANNELED_1; + spellInfo->AttributesEx &= ~SPELL_ATTR1_CHANNELED; }); // Meteor Slash @@ -5173,7 +5173,7 @@ void SpellMgr::LoadSpellInfoCorrections() // Nurture ApplySpellFix({ 85425 }, [](SpellInfo* spellInfo) { - spellInfo->AttributesEx &= ~SPELL_ATTR1_CHANNELED_2; + spellInfo->AttributesEx &= ~SPELL_ATTR1_SELF_CHANNELED; }); // Soothing Breeze diff --git a/src/server/scripts/Kalimdor/ThroneOfTheFourWinds/boss_conclave_of_wind.cpp b/src/server/scripts/Kalimdor/ThroneOfTheFourWinds/boss_conclave_of_wind.cpp index 964d92b65a..ceef076764 100644 --- a/src/server/scripts/Kalimdor/ThroneOfTheFourWinds/boss_conclave_of_wind.cpp +++ b/src/server/scripts/Kalimdor/ThroneOfTheFourWinds/boss_conclave_of_wind.cpp @@ -1042,7 +1042,7 @@ struct npc_conclave_of_wind_ravenous_creeper : public ScriptedAI { me->SetLastDamagedTime(0); me->SetCannotReachTarget(false); - me->DoNotReacquireSpellFocusTarget(); + me->ResetSpellFocusInfo(); me->GetMotionMaster()->MoveTargetedHome(); }