Skip to content

Commit

Permalink
Core/Spells: Creature spellcast facing rework:
Browse files Browse the repository at this point in the history
- Fixes creatures turning just before a spellcast finishes and smacking players with supposedly-unavoidable damage. Fixes and closes TrinityCore#15393, TrinityCore#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 TrinityCore#2947 and probably a bunch of others, including the aforementioned TrinityCore#15393 and TrinityCore#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.
  • Loading branch information
Treeston committed Dec 10, 2015
1 parent 80ed03a commit 4eaf64c
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 37 deletions.
9 changes: 6 additions & 3 deletions src/server/game/AI/CreatureAI.cpp
Expand Up @@ -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);
}
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down
117 changes: 99 additions & 18 deletions src/server/game/Entities/Creature/Creature.cpp
Expand Up @@ -158,6 +158,7 @@ m_originalEntry(0), m_homePosition(), m_transportHomePosition(), m_creatureInfo(
TriggerJustRespawned = false;
m_isTempWorldObject = false;
_focusSpell = NULL;
_focusDelay = 0;
}

Creature::~Creature()
Expand Down Expand Up @@ -1516,7 +1517,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
Expand Down Expand Up @@ -2635,39 +2638,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;
}

bool Creature::IsFocusing(Spell const* focusSpell, bool withDelay)
{
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) > 500) // @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)
void Creature::ReleaseFocus(Spell const* focusSpell, bool withDelay)
{
if (!_focusSpell)
return;

// focused to something else
if (focusSpell != _focusSpell)
if (focusSpell && focusSpell != _focusSpell)
return;

_focusSpell = NULL;
if (Unit* victim = GetVictim())
SetGuidValue(UNIT_FIELD_TARGET, victim->GetGUID());
else
SetGuidValue(UNIT_FIELD_TARGET, ObjectGuid::Empty);
SetGuidValue(UNIT_FIELD_TARGET, ObjectGuid::Empty);

if (_focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST))
ClearUnitState(UNIT_STATE_CANNOT_TURN);

if (focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST))
ClearUnitState(UNIT_STATE_ROTATING);
_focusSpell = nullptr;
_focusDelay = withDelay ? getMSTime() : 0; // don't allow re-target right away to prevent visual bugs
}

void Creature::StartPickPocketRefillTimer()
Expand Down
6 changes: 4 additions & 2 deletions src/server/game/Entities/Creature/Creature.h
Expand Up @@ -668,8 +668,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);
Expand Down Expand Up @@ -741,6 +742,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;
};
Expand Down
23 changes: 17 additions & 6 deletions src/server/game/Entities/Unit/Unit.cpp
Expand Up @@ -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;
Expand All @@ -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();

Expand Down Expand Up @@ -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 (this->GetTypeId() == TYPEID_UNIT) // why is this called _UNIT? It's a creature. Players are units too. Weird.
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();
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -12773,7 +12783,8 @@ Unit* Creature::SelectVictim()

if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
{
SetInFront(target);
if(!IsFocusing())
SetInFront(target);
return target;
}

Expand Down Expand Up @@ -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())
Expand Down
4 changes: 3 additions & 1 deletion src/server/game/Entities/Unit/Unit.h
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -2152,6 +2153,7 @@ class Unit : public WorldObject

AttackerSet m_attackers;
Unit* m_attacking;
bool m_shouldReacquireTarget;

DeathState m_deathState;

Expand Down

0 comments on commit 4eaf64c

Please sign in to comment.