Core/Creatures: several cleanups and improvements to spell focusing to reduce edge case issues

This commit is contained in:
Ovahlord committed 2021-10-21 13:18:24 +02:00
1 parent 1d5bbfc144
commit b10f962ba3
10 files changed
+108 -77

No files matched your search

+1 -1
View File
@@ -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();
+67 -44
View File
@@ -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<Player*> 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
+3 -10
View File
@@ -343,11 +343,10 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, 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<Creature>, 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;
@@ -28,6 +28,7 @@
#include <vector>
#include <cmath>
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__
@@ -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
+1 -1
View File
@@ -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);
}
+14 -14
View File
@@ -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
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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
@@ -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();
}