aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/server/game/AI/CreatureAI.cpp9
-rw-r--r--src/server/game/Entities/Creature/Creature.cpp117
-rw-r--r--src/server/game/Entities/Creature/Creature.h6
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp23
-rw-r--r--src/server/game/Entities/Unit/Unit.h4
-rwxr-xr-xsrc/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp3
-rw-r--r--src/server/game/Spells/Spell.cpp52
-rw-r--r--src/server/game/Spells/Spell.h1
8 files changed, 178 insertions, 37 deletions
diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp
index 8ef8940e89f..c254a9124c1 100644
--- a/src/server/game/AI/CreatureAI.cpp
+++ b/src/server/game/AI/CreatureAI.cpp
@@ -204,7 +204,8 @@ void CreatureAI::SetGazeOn(Unit* target)
{
if (me->IsValidAttackTarget(target))
{
- AttackStart(target);
+ if (!me->IsFocusing(nullptr, true))
+ AttackStart(target);
me->SetReactState(REACT_PASSIVE);
}
}
@@ -223,7 +224,8 @@ bool CreatureAI::UpdateVictimWithGaze()
}
if (Unit* victim = me->SelectVictim())
- AttackStart(victim);
+ if (!me->IsFocusing(nullptr, true))
+ AttackStart(victim);
return me->GetVictim() != nullptr;
}
@@ -236,7 +238,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;
}
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp
index 47ef22b2df0..4c1e7af2615 100644
--- a/src/server/game/Entities/Creature/Creature.cpp
+++ b/src/server/game/Entities/Creature/Creature.cpp
@@ -203,6 +203,7 @@ m_originalEntry(0), m_homePosition(), m_transportHomePosition(), m_creatureInfo(
TriggerJustRespawned = false;
m_isTempWorldObject = false;
_focusSpell = NULL;
+ _focusDelay = 0;
}
Creature::~Creature()
@@ -1596,7 +1597,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
+
SetUInt64Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, 0); // if creature is mounted on a virtual mount, remove it at death
@@ -2613,39 +2616,117 @@ void Creature::SetDisplayId(uint32 modelId)
void Creature::SetTarget(ObjectGuid const& 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()->AttributesEx5 & 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) > 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)
+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()->AttributesEx5 & 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()
diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h
index 96b04b32f84..73183b6f8dd 100644
--- a/src/server/game/Entities/Creature/Creature.h
+++ b/src/server/game/Entities/Creature/Creature.h
@@ -705,8 +705,9 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
// Handling caster facing during spellcast
void SetTarget(ObjectGuid const& 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);
@@ -779,6 +780,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
bool TriggerJustRespawned;
Spell const* _focusSpell; ///> Locks the target during spell cast for proper facing
+ uint32 _focusDelay;
CreatureTextRepeatGroup m_textRepeat;
};
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index 9a2a955d74d..6e6f8788201 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -262,7 +262,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;
if (GetTypeId() == TYPEID_PLAYER)
{
m_modMeleeHitChance = 7.5f;
@@ -285,7 +286,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();
@@ -1887,6 +1888,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();
@@ -7247,6 +7251,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)
@@ -7332,7 +7342,7 @@ bool Unit::AttackStop()
Unit* victim = m_attacking;
m_attacking->_removeAttacker(this);
- m_attacking = NULL;
+ m_attacking = nullptr;
// Clear our target
SetTarget(ObjectGuid::Empty);
@@ -10768,7 +10778,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())
@@ -10836,7 +10846,8 @@ Unit* Creature::SelectVictim()
if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
{
- SetInFront(target);
+ if(!IsFocusing())
+ SetInFront(target);
return target;
}
@@ -15781,7 +15792,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())
diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h
index d6e3d01b09a..7dcbdfcf13b 100644
--- a/src/server/game/Entities/Unit/Unit.h
+++ b/src/server/game/Entities/Unit/Unit.h
@@ -1370,6 +1370,7 @@ class TC_GAME_API 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();
@@ -1679,7 +1680,7 @@ class TC_GAME_API 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();
@@ -2253,6 +2254,7 @@ class TC_GAME_API Unit : public WorldObject
AttackerSet m_attackers;
Unit* m_attacking;
+ bool m_shouldReacquireTarget;
DeathState m_deathState;
diff --git a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp
index d0340a4df5f..b595059557a 100755
--- a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp
+++ b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp
@@ -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)
diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp
index 256600e4010..fb4a24a5e40 100644
--- a/src/server/game/Spells/Spell.cpp
+++ b/src/server/game/Spells/Spell.cpp
@@ -598,6 +598,8 @@ m_spellValue(new SpellValue(caster->GetMap()->GetDifficultyID(), m_spellInfo)),
//Auto Shot & Shoot (wand)
m_autoRepeat = m_spellInfo->IsAutoRepeatRangedSpell();
+
+ m_isDelayedInstantCast = false;
m_runesState = 0;
m_casttime = 0; // setup to correct value in Spell::prepare, must not be used before.
@@ -2986,6 +2988,27 @@ void Spell::prepare(SpellCastTargets const* targets, AuraEffect const* triggered
else
m_casttime = m_spellInfo->CalcCastTime(m_caster->getLevel(), 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)
// don't cancel spells which are affected by a SPELL_AURA_CAST_WHILE_WALKING effect
@@ -3023,18 +3046,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);
}
}
@@ -3043,6 +3062,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;
@@ -3112,6 +3134,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
@@ -3191,6 +3216,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
@@ -3297,6 +3332,9 @@ void Spell::cast(bool skipCheck)
}
SetExecutedCurrently(false);
+
+ if (Creature* creatureCaster = m_caster->ToCreature())
+ creatureCaster->ReleaseFocus(this);
}
void Spell::handle_immediate()
diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h
index dd6d39f6030..64818b06b2d 100644
--- a/src/server/game/Spells/Spell.h
+++ b/src/server/game/Spells/Spell.h
@@ -661,6 +661,7 @@ class TC_GAME_API 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;