Core/Spells: Creature spellcast facing rework:
- Fixes creatures turning just before a spellcast finishes and smacking players with supposedly-unavoidable damage. Fixes and closes #15393, #10803, and probably others. - Fixes visual effects not lining up with the correct target for spells that have their visual aligned with the caster's orientation (examples: Anub'rekhan Impale, Ingvar's Smash/Dark Smash, etc.). Fixes and closes #2947 and probably a bunch of others, including the aforementioned #15393 and #10803. - Creatures' displayed target now properly matches the unit they are targeting with spells for a split second (blizzlike). This is necessary to get proper client-side orientation.
This commit is contained in:
1 parent
1d9d03b289
commit
f481ae1048
8 files changed
+179
-38
No files matched your search
@@ -203,7 +203,8 @@ void CreatureAI::SetGazeOn(Unit* target)
|
||||
{
|
||||
if (me->IsValidAttackTarget(target))
|
||||
{
|
||||
AttackStart(target);
|
||||
if (!me->IsFocusing(nullptr, true))
|
||||
AttackStart(target);
|
||||
me->SetReactState(REACT_PASSIVE);
|
||||
}
|
||||
}
|
||||
@@ -222,7 +223,8 @@ bool CreatureAI::UpdateVictimWithGaze()
|
||||
}
|
||||
|
||||
if (Unit* victim = me->SelectVictim())
|
||||
AttackStart(victim);
|
||||
if (!me->IsFocusing(nullptr, true))
|
||||
AttackStart(victim);
|
||||
|
||||
return me->GetVictim() != nullptr;
|
||||
}
|
||||
@@ -235,7 +237,8 @@ bool CreatureAI::UpdateVictim()
|
||||
if (!me->HasReactState(REACT_PASSIVE))
|
||||
{
|
||||
if (Unit* victim = me->SelectVictim())
|
||||
AttackStart(victim);
|
||||
if (!me->IsFocusing(nullptr, true))
|
||||
AttackStart(victim);
|
||||
|
||||
return me->GetVictim() != nullptr;
|
||||
}
|
||||
|
||||
@@ -200,6 +200,7 @@ m_originalEntry(0), m_homePosition(), m_transportHomePosition(), m_creatureInfo(
|
||||
TriggerJustRespawned = false;
|
||||
m_isTempWorldObject = false;
|
||||
_focusSpell = NULL;
|
||||
_focusDelay = 0;
|
||||
}
|
||||
|
||||
Creature::~Creature()
|
||||
@@ -1558,7 +1559,9 @@ void Creature::setDeathState(DeathState s)
|
||||
if (sWorld->getBoolConfig(CONFIG_SAVE_RESPAWN_TIME_IMMEDIATELY) || isWorldBoss())
|
||||
SaveRespawnTime();
|
||||
|
||||
SetTarget(ObjectGuid::Empty); // remove target selection in any cases (can be set at aura remove in Unit::setDeathState)
|
||||
ReleaseFocus(); // remove spellcast focus (this also clears unit target)
|
||||
SetTarget(ObjectGuid::Empty); // drop target - dead mobs shouldn't ever target things
|
||||
|
||||
SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
|
||||
|
||||
SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, 0); // if creature is mounted on a virtual mount, remove it at death
|
||||
@@ -2677,39 +2680,117 @@ void Creature::SetDisplayId(uint32 modelId)
|
||||
|
||||
void Creature::SetTarget(ObjectGuid guid)
|
||||
{
|
||||
if (!_focusSpell)
|
||||
if (!IsFocusing())
|
||||
SetGuidValue(UNIT_FIELD_TARGET, guid);
|
||||
}
|
||||
|
||||
void Creature::FocusTarget(Spell const* focusSpell, WorldObject const* target)
|
||||
bool Creature::FocusTarget(Spell const* focusSpell, WorldObject const* target)
|
||||
{
|
||||
// already focused
|
||||
if (_focusSpell)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if ((!target || target == this) && !focusSpell->GetCastTime()) // instant cast, untargeted (or self-targeted) spell doesn't need any facing updates
|
||||
return false;
|
||||
|
||||
_focusSpell = focusSpell;
|
||||
SetGuidValue(UNIT_FIELD_TARGET, target->GetGUID());
|
||||
if (focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST))
|
||||
AddUnitState(UNIT_STATE_ROTATING);
|
||||
|
||||
// Set serverside orientation if needed (needs to be after attribute check)
|
||||
SetInFront(target);
|
||||
// "instant" creature casts that require re-targeting will be delayed by a short moment to prevent facing bugs
|
||||
bool shouldDelay = false;
|
||||
|
||||
// set target, then force send update packet to players if it changed to provide appropriate facing
|
||||
ObjectGuid newTarget = target ? target->GetGUID() : ObjectGuid::Empty;
|
||||
if (GetGuidValue(UNIT_FIELD_TARGET) != newTarget)
|
||||
{
|
||||
SetGuidValue(UNIT_FIELD_TARGET, newTarget);
|
||||
if (target)
|
||||
SetFacingToObject(target);
|
||||
|
||||
if ( // here we determine if the (relatively expensive) forced update is worth it, or whether we can afford to wait until the scheduled update tick
|
||||
( // only require instant update for spells that actually have a visual
|
||||
focusSpell->GetSpellInfo()->SpellVisual[0] ||
|
||||
focusSpell->GetSpellInfo()->SpellVisual[1]
|
||||
) && (
|
||||
!focusSpell->GetCastTime() || // if the spell is instant cast
|
||||
focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST) // client gets confused if we attempt to turn at the regularly scheduled update packet
|
||||
)
|
||||
)
|
||||
{
|
||||
const MapRefManager& mapPlayers = GetMap()->GetPlayers();
|
||||
for (MapRefManager::const_iterator it = mapPlayers.begin(); it != mapPlayers.end(); ++it)
|
||||
if (Player* player = (*it).GetSource())
|
||||
{
|
||||
// only update players that can both see us, and are actually in combat with us (this is a performance tradeoff)
|
||||
if (player->CanSeeOrDetect(this, false, true) && IsInCombatWith(player))
|
||||
{
|
||||
SendUpdateToPlayer(player);
|
||||
shouldDelay = true;
|
||||
}
|
||||
}
|
||||
if (shouldDelay)
|
||||
shouldDelay = (!focusSpell->IsTriggered() && !focusSpell->GetCastTime());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// tell the creature that it should reacquire its current target after the cast is done (this is handled in ::Attack)
|
||||
MustReacquireTarget();
|
||||
|
||||
bool canTurnDuringCast = !focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST);
|
||||
// Face the target - we need to do this before the unit state is modified for no-turn spells
|
||||
if (target)
|
||||
SetInFront(target);
|
||||
else if (!canTurnDuringCast)
|
||||
if(Unit* victim = GetVictim())
|
||||
SetInFront(victim); // ensure server-side orientation is correct at beginning of cast
|
||||
|
||||
if (!canTurnDuringCast)
|
||||
AddUnitState(UNIT_STATE_CANNOT_TURN);
|
||||
|
||||
return shouldDelay;
|
||||
}
|
||||
|
||||
void Creature::ReleaseFocus(Spell const* focusSpell)
|
||||
bool Creature::IsFocusing(Spell const* focusSpell, bool withDelay)
|
||||
{
|
||||
// focused to something else
|
||||
if (focusSpell != _focusSpell)
|
||||
if (!IsAlive()) // dead creatures cannot focus
|
||||
{
|
||||
ReleaseFocus(nullptr, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (focusSpell && (focusSpell != _focusSpell))
|
||||
return false;
|
||||
|
||||
if (!_focusSpell)
|
||||
{
|
||||
if (!withDelay || !_focusDelay)
|
||||
return false;
|
||||
if (GetMSTimeDiffToNow(_focusDelay) > 1000) // @todo figure out if we can get rid of this magic number somehow
|
||||
{
|
||||
_focusDelay = 0; // save checks in the future
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Creature::ReleaseFocus(Spell const* focusSpell, bool withDelay)
|
||||
{
|
||||
if (!_focusSpell)
|
||||
return;
|
||||
|
||||
_focusSpell = NULL;
|
||||
if (Unit* victim = GetVictim())
|
||||
SetGuidValue(UNIT_FIELD_TARGET, victim->GetGUID());
|
||||
else
|
||||
SetGuidValue(UNIT_FIELD_TARGET, ObjectGuid::Empty);
|
||||
// focused to something else
|
||||
if (focusSpell && focusSpell != _focusSpell)
|
||||
return;
|
||||
|
||||
if (focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST))
|
||||
ClearUnitState(UNIT_STATE_ROTATING);
|
||||
SetGuidValue(UNIT_FIELD_TARGET, ObjectGuid::Empty);
|
||||
|
||||
if (_focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST))
|
||||
ClearUnitState(UNIT_STATE_CANNOT_TURN);
|
||||
|
||||
_focusSpell = nullptr;
|
||||
_focusDelay = withDelay ? getMSTime() : 0; // don't allow re-target right away to prevent visual bugs
|
||||
}
|
||||
|
||||
void Creature::StartPickPocketRefillTimer()
|
||||
|
||||
@@ -671,8 +671,9 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject
|
||||
|
||||
// Handling caster facing during spellcast
|
||||
void SetTarget(ObjectGuid guid) override;
|
||||
void FocusTarget(Spell const* focusSpell, WorldObject const* target);
|
||||
void ReleaseFocus(Spell const* focusSpell);
|
||||
bool FocusTarget(Spell const* focusSpell, WorldObject const* target);
|
||||
bool IsFocusing(Spell const* focusSpell = nullptr, bool withDelay = false);
|
||||
void ReleaseFocus(Spell const* focusSpell = nullptr, bool withDelay = true);
|
||||
|
||||
CreatureTextRepeatIds GetTextRepeatGroup(uint8 textGroup);
|
||||
void SetTextRepeatId(uint8 textGroup, uint8 id);
|
||||
@@ -744,6 +745,7 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject
|
||||
bool TriggerJustRespawned;
|
||||
|
||||
Spell const* _focusSpell; ///> Locks the target during spell cast for proper facing
|
||||
uint32 _focusDelay;
|
||||
|
||||
CreatureTextRepeatGroup m_textRepeat;
|
||||
};
|
||||
|
||||
@@ -223,7 +223,8 @@ Unit::Unit(bool isWorldObject) :
|
||||
for (uint8 i = 0; i < MAX_STATS; ++i)
|
||||
m_createStats[i] = 0.0f;
|
||||
|
||||
m_attacking = NULL;
|
||||
m_attacking = nullptr;
|
||||
m_shouldReacquireTarget = false;
|
||||
m_modMeleeHitChance = 0.0f;
|
||||
m_modRangedHitChance = 0.0f;
|
||||
m_modSpellHitChance = 0.0f;
|
||||
@@ -238,7 +239,7 @@ Unit::Unit(bool isWorldObject) :
|
||||
for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i)
|
||||
m_speed_rate[i] = 1.0f;
|
||||
|
||||
m_charmInfo = NULL;
|
||||
m_charmInfo = nullptr;
|
||||
|
||||
_redirectThreadInfo = RedirectThreatInfo();
|
||||
|
||||
@@ -1956,6 +1957,9 @@ void Unit::AttackerStateUpdate (Unit* victim, WeaponAttackType attType, bool ext
|
||||
if (attType != BASE_ATTACK && attType != OFF_ATTACK)
|
||||
return; // ignore ranged case
|
||||
|
||||
if (GetTypeId() == TYPEID_UNIT && !HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED))
|
||||
SetFacingToObject(victim); // update client side facing to face the target (prevents visual glitches when casting untargeted spells)
|
||||
|
||||
// melee attack spell cast at main hand attack only - no normal melee dmg dealt
|
||||
if (attType == BASE_ATTACK && m_currentSpells[CURRENT_MELEE_SPELL] && !extra)
|
||||
m_currentSpells[CURRENT_MELEE_SPELL]->cast();
|
||||
@@ -8956,6 +8960,12 @@ bool Unit::Attack(Unit* victim, bool meleeAttack)
|
||||
if (HasAuraType(SPELL_AURA_MOD_UNATTACKABLE))
|
||||
RemoveAurasByType(SPELL_AURA_MOD_UNATTACKABLE);
|
||||
|
||||
if (m_shouldReacquireTarget)
|
||||
{
|
||||
SetTarget(victim->GetGUID());
|
||||
m_shouldReacquireTarget = false;
|
||||
}
|
||||
|
||||
if (m_attacking)
|
||||
{
|
||||
if (m_attacking == victim)
|
||||
@@ -9041,7 +9051,7 @@ bool Unit::AttackStop()
|
||||
Unit* victim = m_attacking;
|
||||
|
||||
m_attacking->_removeAttacker(this);
|
||||
m_attacking = NULL;
|
||||
m_attacking = nullptr;
|
||||
|
||||
// Clear our target
|
||||
SetTarget(ObjectGuid::Empty);
|
||||
@@ -12705,7 +12715,7 @@ Unit* Creature::SelectVictim()
|
||||
// next-victim-selection algorithm and evade mode are called
|
||||
// threat list sorting etc.
|
||||
|
||||
Unit* target = NULL;
|
||||
Unit* target = nullptr;
|
||||
// First checking if we have some taunt on us
|
||||
AuraEffectList const& tauntAuras = GetAuraEffectsByType(SPELL_AURA_MOD_TAUNT);
|
||||
if (!tauntAuras.empty())
|
||||
@@ -12773,7 +12783,8 @@ Unit* Creature::SelectVictim()
|
||||
|
||||
if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
|
||||
{
|
||||
SetInFront(target);
|
||||
if(!IsFocusing())
|
||||
SetInFront(target);
|
||||
return target;
|
||||
}
|
||||
|
||||
@@ -17558,7 +17569,7 @@ void Unit::SetFacingTo(float ori)
|
||||
init.Launch();
|
||||
}
|
||||
|
||||
void Unit::SetFacingToObject(WorldObject* object)
|
||||
void Unit::SetFacingToObject(WorldObject const* object)
|
||||
{
|
||||
// never face when already moving
|
||||
if (!IsStopped())
|
||||
|
||||
@@ -1274,6 +1274,7 @@ class Unit : public WorldObject
|
||||
void _removeAttacker(Unit* pAttacker); // must be called only from Unit::AttackStop()
|
||||
Unit* getAttackerForHelper() const; // If someone wants to help, who to give them
|
||||
bool Attack(Unit* victim, bool meleeAttack);
|
||||
void MustReacquireTarget() { m_shouldReacquireTarget = true; } // flags the Unit for forced target reacquisition in the next ::Attack call
|
||||
void CastStop(uint32 except_spellid = 0);
|
||||
bool AttackStop();
|
||||
void RemoveAllAttackers();
|
||||
@@ -1584,7 +1585,7 @@ class Unit : public WorldObject
|
||||
|
||||
void SetInFront(WorldObject const* target);
|
||||
void SetFacingTo(float ori);
|
||||
void SetFacingToObject(WorldObject* object);
|
||||
void SetFacingToObject(WorldObject const* object);
|
||||
|
||||
void SendChangeCurrentVictimOpcode(HostileReference* pHostileReference);
|
||||
void SendClearThreatListOpcode();
|
||||
@@ -2152,6 +2153,7 @@ class Unit : public WorldObject
|
||||
|
||||
AttackerSet m_attackers;
|
||||
Unit* m_attacking;
|
||||
bool m_shouldReacquireTarget;
|
||||
|
||||
DeathState m_deathState;
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ void TargetedMovementGeneratorMedium<T, D>::_setTargetLocation(T* owner, bool up
|
||||
if (owner->GetTypeId() == TYPEID_UNIT && !i_target->isInAccessiblePlaceFor(owner->ToCreature()))
|
||||
return;
|
||||
|
||||
if (owner->GetTypeId() == TYPEID_UNIT && owner->ToCreature()->IsFocusing(nullptr, true))
|
||||
return;
|
||||
|
||||
float x, y, z;
|
||||
|
||||
if (updateDestination || !i_path)
|
||||
|
||||
@@ -602,6 +602,8 @@ m_caster((info->HasAttribute(SPELL_ATTR6_CAST_BY_CHARMER) && caster->GetCharmerO
|
||||
|
||||
//Auto Shot & Shoot (wand)
|
||||
m_autoRepeat = m_spellInfo->IsAutoRepeatRangedSpell();
|
||||
|
||||
m_isDelayedInstantCast = false;
|
||||
|
||||
m_runesState = 0;
|
||||
m_powerCost = 0; // setup to correct value in Spell::prepare, must not be used before.
|
||||
@@ -2936,6 +2938,27 @@ void Spell::prepare(SpellCastTargets const* targets, AuraEffect const* triggered
|
||||
else
|
||||
m_casttime = m_spellInfo->CalcCastTime(this);
|
||||
|
||||
if (m_caster->GetTypeId() == TYPEID_UNIT && !m_caster->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) // _UNIT actually means creature. for some reason.
|
||||
if (!(IsNextMeleeSwingSpell() || IsAutoRepeat() || _triggeredCastFlags & TRIGGERED_IGNORE_SET_FACING))
|
||||
{
|
||||
if (m_targets.GetObjectTarget() && m_caster != m_targets.GetObjectTarget())
|
||||
{
|
||||
if (m_caster->ToCreature()->FocusTarget(this, m_targets.GetObjectTarget()))
|
||||
{
|
||||
m_isDelayedInstantCast = true;
|
||||
m_timer = 100; // 100ms delay ensures client has updated creature orientation when cast goes off
|
||||
}
|
||||
}
|
||||
else if (m_spellInfo->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST))
|
||||
{
|
||||
if (m_caster->ToCreature()->FocusTarget(this, nullptr))
|
||||
{
|
||||
m_isDelayedInstantCast = true;
|
||||
m_timer = 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// don't allow channeled spells / spells with cast time to be cast while moving
|
||||
// (even if they are interrupted on moving, spells with almost immediate effect get to have their effect processed before movement interrupter kicks in)
|
||||
if ((m_spellInfo->IsChanneled() || m_casttime) && m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->isMoving() && m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT)
|
||||
@@ -2971,18 +2994,14 @@ void Spell::prepare(SpellCastTargets const* targets, AuraEffect const* triggered
|
||||
}
|
||||
|
||||
m_caster->SetCurrentCastSpell(this);
|
||||
SendSpellStart();
|
||||
|
||||
// set target for proper facing
|
||||
if ((m_casttime || m_spellInfo->IsChanneled()) && !(_triggeredCastFlags & TRIGGERED_IGNORE_SET_FACING))
|
||||
if (m_caster->GetTypeId() == TYPEID_UNIT && m_targets.GetObjectTarget() && m_caster != m_targets.GetObjectTarget())
|
||||
m_caster->ToCreature()->FocusTarget(this, m_targets.GetObjectTarget());
|
||||
if (!m_isDelayedInstantCast)
|
||||
SendSpellStart();
|
||||
|
||||
if (!(_triggeredCastFlags & TRIGGERED_IGNORE_GCD))
|
||||
TriggerGlobalCooldown();
|
||||
|
||||
//item: first cast may destroy item and second cast causes crash
|
||||
if (!m_casttime && !m_spellInfo->StartRecoveryTime && !m_castItemGUID && GetCurrentContainer() == CURRENT_GENERIC_SPELL)
|
||||
if (!m_casttime && !m_isDelayedInstantCast && !m_spellInfo->StartRecoveryTime && !m_castItemGUID && GetCurrentContainer() == CURRENT_GENERIC_SPELL)
|
||||
cast(true);
|
||||
}
|
||||
}
|
||||
@@ -2991,6 +3010,9 @@ void Spell::cancel()
|
||||
{
|
||||
if (m_spellState == SPELL_STATE_FINISHED)
|
||||
return;
|
||||
// delayed instant casts are used for client-side visual orientation; they are treated as instant for all intents and purposes server-side, and thus cannot be interrupted by another cast
|
||||
if (m_isDelayedInstantCast)
|
||||
return;
|
||||
|
||||
uint32 oldState = m_spellState;
|
||||
m_spellState = SPELL_STATE_FINISHED;
|
||||
@@ -3060,6 +3082,9 @@ void Spell::cast(bool skipCheck)
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_isDelayedInstantCast)
|
||||
SendSpellStart();
|
||||
|
||||
if (Player* playerCaster = m_caster->ToPlayer())
|
||||
{
|
||||
// now that we've done the basic check, now run the scripts
|
||||
@@ -3139,6 +3164,16 @@ void Spell::cast(bool skipCheck)
|
||||
}
|
||||
}
|
||||
|
||||
// if the spell allows the creature to turn while casting, then adjust server-side orientation to face the target now
|
||||
// client-side orientation is handled by the client itself, as the cast target is targeted due to Creature::FocusTarget
|
||||
if (m_caster->GetTypeId() == TYPEID_UNIT && !m_caster->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED))
|
||||
if (!m_spellInfo->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST))
|
||||
if (WorldObject* objTarget = m_targets.GetObjectTarget())
|
||||
{
|
||||
m_caster->SetInFront(objTarget);
|
||||
m_caster->SetFacingToObject(objTarget);
|
||||
}
|
||||
|
||||
SelectSpellTargets();
|
||||
|
||||
// Spell may be finished after target map check
|
||||
@@ -3242,6 +3277,9 @@ void Spell::cast(bool skipCheck)
|
||||
}
|
||||
|
||||
SetExecutedCurrently(false);
|
||||
|
||||
if (Creature* creatureCaster = m_caster->ToCreature())
|
||||
creatureCaster->ReleaseFocus(this);
|
||||
}
|
||||
|
||||
void Spell::handle_immediate()
|
||||
|
||||
@@ -524,6 +524,7 @@ class Spell
|
||||
int32 m_channeledDuration; // Calculated channeled spell duration in order to calculate correct pushback.
|
||||
bool m_canReflect; // can reflect this spell?
|
||||
bool m_autoRepeat;
|
||||
bool m_isDelayedInstantCast; // whether this is a creature's delayed instant cast
|
||||
uint8 m_runesState;
|
||||
|
||||
uint8 m_delayAtDamageCount;
|
||||
|
||||
Reference in new issue
Block a user