Core/Creatures: several cleanups and improvements to spell focusing to reduce edge case issues
This commit is contained in:
1 parent
1d5bbfc144
commit
b10f962ba3
10 files changed
+108
-77
No files matched your search
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user