diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index 52c1da79d..17daed82d 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -307,7 +307,7 @@ bool CreatureAI::_EnterEvadeMode(EvadeReason /*why*/) me->LoadCreaturesAddon(true); me->SetLootRecipient(nullptr); me->ResetPlayerDamageReq(); - me->SetLastDamagedTime(0); + me->ClearLastLeashExtensionTimePtr(); me->SetCannotReachTarget(); if (ZoneScript* zoneScript = me->GetZoneScript() ? me->GetZoneScript() : (ZoneScript*)me->GetInstanceScript()) diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 33a294531..d9e0dc574 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -220,9 +220,6 @@ bool AssistDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) { if (Unit* victim = ObjectAccessor::GetUnit(*m_owner, m_victim)) { - // Initialize last damage timer if it doesn't exist - m_owner->SetLastDamagedTime(GameTime::GetGameTime().count() + MAX_AGGRO_RESET_TIME); - while (!m_assistants.empty()) { Creature* assistant = ObjectAccessor::GetCreature(*m_owner, *m_assistants.begin()); @@ -233,9 +230,14 @@ bool AssistDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) assistant->SetNoCallAssistance(true); assistant->CombatStart(victim); if (assistant->IsAIEnabled) + { assistant->AI()->AttackStart(victim); - assistant->SetLastDamagedTimePtr(m_owner->GetLastDamagedTimePtr()); + // When nearby mobs aggro from another mob's initial call for assistance + // their leash timers become linked and attacking one will keep the rest from evading. + if (assistant->GetVictim()) + assistant->SetLastLeashExtensionTimePtr(m_owner->GetLastLeashExtensionTimePtr()); + } } } } @@ -272,7 +274,7 @@ Creature::Creature(bool isWorldObject): Unit(isWorldObject), MovableMapObject(), m_transportCheckTimer(1000), lootPickPocketRestoreTime(0), m_combatPulseTime(0), m_combatPulseDelay(0), m_reactState(REACT_AGGRESSIVE), m_defaultMovementType(IDLE_MOTION_TYPE), m_spawnId(0), m_equipmentId(0), m_originalEquipmentId(0), m_AlreadyCallAssistance(false), m_AlreadySearchedAssistance(false), m_regenHealth(true), m_regenPower(true), m_AI_locked(false), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), m_originalEntry(0), m_moveInLineOfSightDisabled(false), m_moveInLineOfSightStrictlyDisabled(false), - m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_detectionDistance(20.0f), m_waypointID(0), m_path_id(0), m_formation(nullptr), _lastDamagedTime(nullptr), m_cannotReachTimer(0), + m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_detectionDistance(20.0f), m_waypointID(0), m_path_id(0), m_formation(nullptr), m_lastLeashExtensionTime(nullptr), m_cannotReachTimer(0), _isMissingSwimmingFlagOutOfCombat(false), m_assistanceTimer(0), _playerDamageReq(0), _damagedByPlayer(false), _isCombatMovementAllowed(true) { m_regenTimer = CREATURE_REGEN_INTERVAL; @@ -1961,8 +1963,6 @@ void Creature::setDeathState(DeathState state, bool despawn) if (state == DeathState::JustDied) { - _lastDamagedTime.reset(); - m_corpseRemoveTime = GameTime::GetGameTime().count() + m_corpseDelay; m_respawnTime = GameTime::GetGameTime().count() + m_respawnDelay + m_corpseDelay; @@ -2640,7 +2640,7 @@ bool Creature::CanCreatureAttack(Unit const* victim, bool skipDistCheck) const return false; // cannot attack if is during 5 second grace period, unless being attacked - if (m_respawnedTime && (GameTime::GetGameTime().count() - m_respawnedTime) < 5 && !GetLastDamagedTime()) + if (m_respawnedTime && (GameTime::GetGameTime().count() - m_respawnedTime) < 5 && !IsEngagedBy(victim)) { return false; } @@ -2656,9 +2656,15 @@ bool Creature::CanCreatureAttack(Unit const* victim, bool skipDistCheck) const if (GetMap()->IsDungeon()) return true; + float visibility = std::max(GetVisibilityRange(), victim->GetVisibilityRange()); + + // if outside visibility + if (!IsWithinDist(victim, visibility)) + return false; + // pussywizard: don't check distance to home position if recently damaged (allow kiting away from spawnpoint!) // xinef: this should include taunt auras - if (!isWorldBoss() && (GetLastDamagedTime() > GameTime::GetGameTime().count() || HasAuraType(SPELL_AURA_MOD_TAUNT))) + if (!isWorldBoss() && (GetLastLeashExtensionTime() + 12 > GameTime::GetGameTime().count() || HasAuraType(SPELL_AURA_MOD_TAUNT))) return true; } @@ -2666,10 +2672,13 @@ bool Creature::CanCreatureAttack(Unit const* victim, bool skipDistCheck) const return true; // xinef: added size factor for huge npcs - float dist = std::min(GetMap()->GetVisibilityRange() + GetObjectSize() * 2, 150.0f); + float dist = std::min(GetDetectionRange() + GetObjectSize() * 2, 150.0f); if (Unit* unit = GetCharmerOrOwner()) + { + dist = std::min(GetMap()->GetVisibilityRange() + GetObjectSize() * 2, 150.0f); return victim->IsWithinDist(unit, dist); + } else { // to prevent creatures in air ignore attacks because distance is already too high... @@ -3666,35 +3675,31 @@ bool Creature::IsNotReachableAndNeedRegen() const return false; } -time_t Creature::GetLastDamagedTime() const +std::shared_ptr const& Creature::GetLastLeashExtensionTimePtr() const { - if (!_lastDamagedTime) - return time_t(0); - - return *_lastDamagedTime; + if (m_lastLeashExtensionTime == nullptr) + m_lastLeashExtensionTime = std::make_shared(time(nullptr)); + return m_lastLeashExtensionTime; } -std::shared_ptr const& Creature::GetLastDamagedTimePtr() const +void Creature::SetLastLeashExtensionTimePtr(std::shared_ptr const& timer) { - return _lastDamagedTime; + m_lastLeashExtensionTime = timer; } -void Creature::SetLastDamagedTime(time_t val) +void Creature::ClearLastLeashExtensionTimePtr() { - if (val > 0) - { - if (_lastDamagedTime) - *_lastDamagedTime = val; - else - _lastDamagedTime = std::make_shared(val); - } - else - _lastDamagedTime.reset(); + m_lastLeashExtensionTime.reset(); } -void Creature::SetLastDamagedTimePtr(std::shared_ptr const& val) +time_t Creature::GetLastLeashExtensionTime() const { - _lastDamagedTime = val; + return *GetLastLeashExtensionTimePtr(); +} + +void Creature::UpdateLeashExtensionTime() +{ + (*GetLastLeashExtensionTimePtr()) = time(nullptr); } bool Creature::CanPeriodicallyCallForAssistance() const diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 52f51dcdb..1c1aa3321 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -381,10 +381,11 @@ public: [[nodiscard]] bool IsMovementPreventedByCasting() const override; // Part of Evade mechanics - [[nodiscard]] time_t GetLastDamagedTime() const; - [[nodiscard]] std::shared_ptr const& GetLastDamagedTimePtr() const; - void SetLastDamagedTime(time_t val); - void SetLastDamagedTimePtr(std::shared_ptr const& val); + std::shared_ptr const& GetLastLeashExtensionTimePtr() const; + void SetLastLeashExtensionTimePtr(std::shared_ptr const& timer); + void ClearLastLeashExtensionTimePtr(); + time_t GetLastLeashExtensionTime() const; + void UpdateLeashExtensionTime(); bool IsFreeToMove(); static constexpr uint32 MOVE_CIRCLE_CHECK_INTERVAL = 3000; @@ -500,7 +501,9 @@ private: CreatureGroup* m_formation; bool TriggerJustRespawned; - mutable std::shared_ptr _lastDamagedTime; // Part of Evade mechanics + // Shared timer between mobs who assist another. + // Damaging one extends leash range on all of them. + mutable std::shared_ptr m_lastLeashExtensionTime; ObjectGuid m_cannotReachTarget; uint32 m_cannotReachTimer; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 5adb8a875..8d843c062 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -1041,10 +1041,6 @@ uint32 Unit::DealDamage(Unit* attacker, Unit* victim, uint32 damage, CleanDamage if (!victim->IsPlayer()) { - // Part of Evade mechanics. DoT's and Thorns / Retribution Aura do not contribute to this - if (damagetype != DOT && damage > 0 && !victim->GetOwnerGUID().IsPlayer() && (!spellProto || !spellProto->HasAura(SPELL_AURA_DAMAGE_SHIELD))) - victim->ToCreature()->SetLastDamagedTime(GameTime::GetGameTime().count() + MAX_AGGRO_RESET_TIME); - if (attacker) { if (spellProto && victim->CanHaveThreatList() && !victim->HasUnitState(UNIT_STATE_EVADE) && !victim->IsInCombatWith(attacker)) @@ -10387,6 +10383,8 @@ void Unit::CombatStop(bool includingCast) RemoveAllAttackers(); if (IsPlayer()) ToPlayer()->SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel + if (Creature* pCreature = ToCreature()) + pCreature->ClearLastLeashExtensionTimePtr(); ClearInCombat(); // xinef: just in case @@ -13539,6 +13537,9 @@ void Unit::SetInCombatWith(Unit* enemy, uint32 duration) return; } } + if (Creature* pCreature = ToCreature()) + pCreature->UpdateLeashExtensionTime(); + SetInCombatState(false, enemy, duration); } diff --git a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp index 59a97bc9e..4dc2f55bd 100644 --- a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp @@ -148,6 +148,15 @@ bool ChaseMovementGenerator::DoUpdate(T* owner, uint32 time_diff) owner->ClearUnitState(UNIT_STATE_CHASE_MOVE); owner->SetInFront(target); MovementInform(owner); + + // Mobs should chase you infinitely if you stop and wait every few seconds. + i_leashExtensionTimer.Update(time_diff); + if (i_leashExtensionTimer.Passed()) + { + i_leashExtensionTimer.Reset(5000); + if (Creature* creature = owner->ToCreature()) + creature->UpdateLeashExtensionTime(); + } } // if the target moved, we have to consider whether to adjust diff --git a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h index 41678c471..4e7df89db 100644 --- a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h +++ b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h @@ -39,7 +39,7 @@ class ChaseMovementGenerator : public MovementGeneratorMedium range = {}, Optional angle = {}) - : TargetedMovementGeneratorBase(target), i_path(nullptr), i_recheckDistance(0), i_recalculateTravel(true), _range(range), _angle(angle) {} + : TargetedMovementGeneratorBase(target), i_leashExtensionTimer(0), i_path(nullptr), i_recheckDistance(0), i_recalculateTravel(true), _range(range), _angle(angle) {} ~ChaseMovementGenerator() { } MovementGeneratorType GetMovementGeneratorType() { return CHASE_MOTION_TYPE; } @@ -59,6 +59,7 @@ public: bool HasLostTarget(Unit* unit) const { return unit->GetVictim() != this->GetTarget(); } private: + TimeTrackerSmall i_leashExtensionTimer; std::unique_ptr i_path; TimeTrackerSmall i_recheckDistance; bool i_recalculateTravel; diff --git a/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/pit_of_saron.cpp b/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/pit_of_saron.cpp index a4cdb0670..515476bfd 100644 --- a/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/pit_of_saron.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/pit_of_saron.cpp @@ -1130,7 +1130,7 @@ public: me->LoadCreaturesAddon(true); me->SetLootRecipient(nullptr); me->ResetPlayerDamageReq(); - me->SetLastDamagedTime(0); + me->UpdateLeashExtensionTime(); } }; diff --git a/src/server/scripts/Pet/pet_hunter.cpp b/src/server/scripts/Pet/pet_hunter.cpp index 5864a928b..3f973ba59 100644 --- a/src/server/scripts/Pet/pet_hunter.cpp +++ b/src/server/scripts/Pet/pet_hunter.cpp @@ -58,7 +58,7 @@ struct npc_pet_hunter_snake_trap : public ScriptedAI me->LoadCreaturesAddon(true); me->SetLootRecipient(nullptr); me->ResetPlayerDamageReq(); - me->SetLastDamagedTime(0); + me->ClearLastLeashExtensionTimePtr(); me->AddUnitState(UNIT_STATE_EVADE); me->GetMotionMaster()->MoveTargetedHome();