diff options
| -rw-r--r-- | src/server/game/AI/CreatureAI.cpp | 9 | ||||
| -rw-r--r-- | src/server/game/Entities/Creature/Creature.cpp | 117 | ||||
| -rw-r--r-- | src/server/game/Entities/Creature/Creature.h | 6 | ||||
| -rw-r--r-- | src/server/game/Entities/Unit/Unit.cpp | 23 | ||||
| -rw-r--r-- | src/server/game/Entities/Unit/Unit.h | 4 | ||||
| -rw-r--r-- | src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp | 3 | ||||
| -rw-r--r-- | src/server/game/Spells/Spell.cpp | 52 | ||||
| -rw-r--r-- | src/server/game/Spells/Spell.h | 1 | 
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 19d64907065..3e67f709816 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -200,6 +200,7 @@ m_originalEntry(0), m_homePosition(), m_transportHomePosition(), m_creatureInfo(      TriggerJustRespawned = false;      m_isTempWorldObject = false;      _focusSpell = NULL; +    _focusDelay = 0;  }  Creature::~Creature() @@ -1569,7 +1570,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 @@ -2688,39 +2691,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) > 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()->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() diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 92522a6a770..b0318ed33e2 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -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); @@ -745,6 +746,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;  }; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 06ff5d527eb..24ddcd358ea 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -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;      } @@ -17561,7 +17572,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 90d312f422a..c64667feeca 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -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(); @@ -2153,6 +2154,7 @@ class 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 100644 --- 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 aed7c0db043..84bff4a69d6 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -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() diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index e9fde234889..696d2801645 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -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;  | 
