diff options
author | killerwife <killerwife@gmail.com> | 2025-09-24 01:45:48 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-09-23 20:45:48 -0300 |
commit | 8e6d35c9b2642c296b0c3a5e257843859a45468f (patch) | |
tree | c3c167530228a7e42002703f2fba8edc5f537b25 /src/server | |
parent | bc30a6fba6273b0a4a9b24ec942468fc8a7bd376 (diff) |
fix(Core/SAI): idle casters (#23005)
Diffstat (limited to 'src/server')
-rw-r--r-- | src/server/game/AI/CreatureAI.h | 3 | ||||
-rw-r--r-- | src/server/game/AI/SmartScripts/SmartAI.cpp | 92 | ||||
-rw-r--r-- | src/server/game/AI/SmartScripts/SmartAI.h | 16 | ||||
-rw-r--r-- | src/server/game/AI/SmartScripts/SmartScript.cpp | 66 | ||||
-rw-r--r-- | src/server/game/AI/SmartScripts/SmartScriptMgr.h | 3 | ||||
-rw-r--r-- | src/server/game/Entities/Unit/Unit.cpp | 14 | ||||
-rw-r--r-- | src/server/game/Entities/Unit/Unit.h | 3 | ||||
-rw-r--r-- | src/server/game/Entities/Unit/UnitDefines.h | 2 | ||||
-rw-r--r-- | src/server/game/Movement/MotionMaster.cpp | 37 | ||||
-rw-r--r-- | src/server/game/Movement/MotionMaster.h | 2 | ||||
-rw-r--r-- | src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp | 158 | ||||
-rw-r--r-- | src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h | 21 |
12 files changed, 269 insertions, 148 deletions
diff --git a/src/server/game/AI/CreatureAI.h b/src/server/game/AI/CreatureAI.h index 5374858a4d..cadd3b9f4c 100644 --- a/src/server/game/AI/CreatureAI.h +++ b/src/server/game/AI/CreatureAI.h @@ -231,6 +231,9 @@ public: // Called when an aura is removed or expires. virtual void OnAuraRemove(AuraApplication* /*aurApp*/, AuraRemoveMode /*mode*/) { } + virtual void DistancingStarted() {} + virtual void DistancingEnded() {} + protected: virtual void MoveInLineOfSight(Unit* /*who*/); diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index db446525d6..cb4067e0c8 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -49,7 +49,6 @@ SmartAI::SmartAI(Creature* c) : CreatureAI(c) mEvadeDisabled = false; mCanAutoAttack = true; - mCanCombatMove = true; mForcedPaused = false; @@ -80,6 +79,9 @@ SmartAI::SmartAI(Creature* c) : CreatureAI(c) m_ConditionsTimer = 0; if (me->GetVehicleKit()) conditions = sConditionMgr->GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, me->GetEntry()); + + _currentRangeMode = false; + _attackDistance = 0.f; } bool SmartAI::IsAIControlled() const @@ -846,7 +848,7 @@ void SmartAI::AttackStart(Unit* who) if (who && me->Attack(who, me->IsWithinMeleeRange(who))) { - if (mCanCombatMove) + if (!me->HasUnitState(UNIT_STATE_NO_COMBAT_MOVEMENT)) { SetRun(mRun); MovementGeneratorType type = me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE); @@ -1047,37 +1049,49 @@ void SmartAI::sQuestReward(Player* player, Quest const* quest, uint32 opt) GetScript()->ProcessEventsFor(SMART_EVENT_REWARD_QUEST, player, quest->GetQuestId(), opt); } -void SmartAI::SetCombatMove(bool on, float chaseRange) +void SmartAI::SetCombatMovement(bool on, bool stopOrStartMovement) { - if (mCanCombatMove == on) - return; - - mCanCombatMove = on; + if (on) + me->ClearUnitState(UNIT_STATE_NO_COMBAT_MOVEMENT); + else + me->AddUnitState(UNIT_STATE_NO_COMBAT_MOVEMENT); - if (!IsAIControlled()) + if (!IsAIControlled() || HasEscortState(SMART_ESCORT_ESCORTING)) return; - if (!HasEscortState(SMART_ESCORT_ESCORTING)) + if (stopOrStartMovement && me->GetVictim()) // Only change current movement while in combat { - if (on && me->GetVictim()) - { - if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == IDLE_MOTION_TYPE) - { - SetRun(mRun); - me->GetMotionMaster()->MoveChase(me->GetVictim(), chaseRange); - me->CastStop(); - } - } - else + if (!me->IsCrowdControlled()) { - me->StopMoving(); - if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE) - me->GetMotionMaster()->Clear(false); - me->GetMotionMaster()->MoveIdle(); + if (on) + me->GetMotionMaster()->MoveChase(me->GetVictim()); + else if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE) + me->StopMoving(); } } } +void SmartAI::SetCurrentRangeMode(bool on, float range) +{ + _currentRangeMode = on; + _attackDistance = range; + + if (Unit* victim = me->GetVictim()) + me->GetMotionMaster()->MoveChase(victim, _attackDistance); +} + +void SmartAI::DistanceYourself(float range) +{ + Unit* victim = me->GetVictim(); + if (!victim || !victim->IsWithinMeleeRange(me)) + return; + + float combatReach = me->GetMeleeRange(victim); + float distance = DISTANCING_CONSTANT + std::max(combatReach * 1.5f, combatReach + range); + me->GetMotionMaster()->DistanceYourself(distance); + _pendingDistancing = distance; +} + void SmartAI::SetFollow(Unit* target, float dist, float angle, uint32 credit, uint32 end, uint32 creditType, bool aliveState) { if (!target) @@ -1130,32 +1144,6 @@ void SmartAI::StopFollow(bool complete) GetScript()->ProcessEventsFor(SMART_EVENT_FOLLOW_COMPLETED, player); } -void SmartAI::MoveAway(float distance) -{ - if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == POINT_MOTION_TYPE) - return; - - mCanCombatMove = false; - - if (!IsAIControlled()) - return; - - if (!HasEscortState(SMART_ESCORT_ESCORTING)) - { - if (me->GetVictim()) - { - me->StopMoving(); - if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE) - me->GetMotionMaster()->Clear(false); - - float x, y, z; - me->GetClosePoint(x, y, z, me->GetObjectSize(), distance, M_PI); - if (me->GetVictim()->IsWithinLOS(x, y, z)) - me->GetMotionMaster()->MovePoint(SMART_RANDOM_POINT, x, y, z); - } - } -} - void SmartAI::SetScript9(SmartScriptHolder& e, uint32 entry, WorldObject* invoker) { if (invoker) @@ -1183,6 +1171,12 @@ void SmartAI::PathEndReached(uint32 /*pathId*/) me->LoadPath(0); } +void SmartAI::DistancingEnded() +{ + SetCurrentRangeMode(true, _pendingDistancing); + _pendingDistancing = 0.f; +} + void SmartGameObjectAI::SummonedCreatureDies(Creature* summon, Unit* /*killer*/) { GetScript()->ProcessEventsFor(SMART_EVENT_SUMMONED_UNIT_DIES, summon); diff --git a/src/server/game/AI/SmartScripts/SmartAI.h b/src/server/game/AI/SmartScripts/SmartAI.h index 7b3a367729..5bd9fcd8b3 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.h +++ b/src/server/game/AI/SmartScripts/SmartAI.h @@ -40,6 +40,8 @@ enum SmartEscortVars SMART_MAX_AID_DIST = SMART_ESCORT_MAX_PLAYER_DIST / 2, }; +#define DISTANCING_CONSTANT 1.f // buffer for better functionality of distancing + class SmartAI : public CreatureAI { public: @@ -63,11 +65,11 @@ public: bool IsEscorted() override { return (mEscortState & SMART_ESCORT_ESCORTING); } void RemoveEscortState(uint32 uiEscortState) { mEscortState &= ~uiEscortState; } void SetAutoAttack(bool on) { mCanAutoAttack = on; } - void SetCombatMove(bool on, float chaseRange = 0.0f); - bool CanCombatMove() { return mCanCombatMove; } + void SetCombatMovement(bool on, bool stopOrStartMovement); + void SetCurrentRangeMode(bool on, float range = 0.f); + void DistanceYourself(float range); void SetFollow(Unit* target, float dist = 0.0f, float angle = 0.0f, uint32 credit = 0, uint32 end = 0, uint32 creditType = 0, bool aliveState = true); void StopFollow(bool complete); - void MoveAway(float distance); void SetScript9(SmartScriptHolder& e, uint32 entry, WorldObject* invoker); SmartScript* GetScript() { return &mScript; } @@ -212,8 +214,7 @@ public: // Xinef void SetWPPauseTimer(uint32 time) { mWPPauseTimer = time; } - void SetChaseOnInterrupt(bool apply) { _chaseOnInterrupt = apply; } - [[nodiscard]] bool CanChaseOnInterrupt() const { return _chaseOnInterrupt; } + void DistancingEnded() override; private: bool mIsCharmed; @@ -242,7 +243,6 @@ private: bool mRun; bool mEvadeDisabled; bool mCanAutoAttack; - bool mCanCombatMove; bool mForcedPaused; uint32 mInvincibilityHpLevel; @@ -263,6 +263,10 @@ private: bool _chaseOnInterrupt; std::unordered_map<uint32, uint32> aiDataSet; + + bool _currentRangeMode; + float _attackDistance; + float _pendingDistancing; }; class SmartGameObjectAI : public GameObjectAI diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index ffedcdcf65..f4f9e82da9 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -686,7 +686,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u float spellMinRange = me->GetSpellMinRangeForTarget(target->ToUnit(), spellInfo); float meleeRange = me->GetMeleeRange(target->ToUnit()); - bool isWithinLOSInMap = me->IsWithinLOSInMap(target->ToUnit()); + bool isWithinLOSInMap = me->IsWithinLOSInMap(target->ToUnit(), VMAP::ModelIgnoreFlags::M2); bool isWithinMeleeRange = distanceToTarget <= meleeRange; bool isRangedAttack = spellMaxRange > NOMINAL_MELEE_RANGE; bool isTargetRooted = target->ToUnit()->HasUnitState(UNIT_STATE_ROOT); @@ -703,7 +703,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u continue; float minDistance = std::max(meleeRange, spellMinRange) - distanceToTarget + NOMINAL_MELEE_RANGE; - CAST_AI(SmartAI, me->AI())->MoveAway(std::min(minDistance, spellMaxRange)); + CAST_AI(SmartAI, me->AI())->DistanceYourself(std::min(minDistance, spellMaxRange)); continue; } @@ -715,7 +715,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (me->IsRooted()) // Rooted inhabit type, never move/reposition continue; - CAST_AI(SmartAI, me->AI())->SetCombatMove(true, std::max(spellMaxRange - NOMINAL_MELEE_RANGE, 0.0f)); + CAST_AI(SmartAI, me->AI())->SetCurrentRangeMode(true, std::max(spellMaxRange - NOMINAL_MELEE_RANGE, 0.0f)); continue; } else if (distanceToTarget < spellMinRange || !(isWithinLOSInMap || isSpellIgnoreLOS)) @@ -725,7 +725,9 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (me->IsRooted()) // Rooted inhabit type, never move/reposition continue; - CAST_AI(SmartAI, me->AI())->SetCombatMove(true); + CAST_AI(SmartAI, me->AI())->SetCurrentRangeMode(true, 0.f); + if (e.action.cast.castFlags & SMARTCAST_ENABLE_COMBAT_MOVE_ON_LOS) + CAST_AI(SmartAI, me->AI())->SetCombatMovement(true, true); continue; } @@ -743,18 +745,13 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (e.action.cast.castFlags & SMARTCAST_COMBAT_MOVE) { - CAST_AI(SmartAI, me->AI())->SetChaseOnInterrupt(true); - - if (!me->isMoving()) // Don't try to reposition while we are moving - { - // If cast flag SMARTCAST_COMBAT_MOVE is set combat movement will not be allowed unless target is outside spell range, out of mana, or LOS. - if (result == SPELL_FAILED_OUT_OF_RANGE) - // if we are just out of range, we only chase until we are back in spell range. - CAST_AI(SmartAI, me->AI())->SetCombatMove(true, std::max(spellMaxRange - NOMINAL_MELEE_RANGE, 0.0f)); - else - // if spell fail for any other reason, we chase to melee range, or stay where we are if spellcast was successful. - CAST_AI(SmartAI, me->AI())->SetCombatMove(spellCastFailed); - } + // If cast flag SMARTCAST_COMBAT_MOVE is set combat movement will not be allowed unless target is outside spell range, out of mana, or LOS. + if (result == SPELL_FAILED_OUT_OF_RANGE || result == SPELL_CAST_OK) + // if we are just out of range, we only chase until we are back in spell range. + CAST_AI(SmartAI, me->AI())->SetCurrentRangeMode(true, std::max(spellMaxRange - NOMINAL_MELEE_RANGE, 0.0f)); + else // move into melee on any other fail + // if spell fail for any other reason, we chase to melee range, or stay where we are if spellcast was successful. + CAST_AI(SmartAI, me->AI())->SetCurrentRangeMode(false, 0.f); } if (spellCastFailed) @@ -989,7 +986,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u break; bool move = e.action.combatMove.move; - CAST_AI(SmartAI, me->AI())->SetCombatMove(move); + CAST_AI(SmartAI, me->AI())->SetCombatMovement(move, true); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_ALLOW_COMBAT_MOVEMENT: Creature {} bool on = {}", me->GetGUID().ToString(), e.action.combatMove.move); break; @@ -2061,7 +2058,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u for (WorldObject* target : targets) if (Creature* creature = target->ToCreature()) if (IsSmart(creature) && creature->GetVictim()) - if (CAST_AI(SmartAI, creature->AI())->CanCombatMove()) + if (!creature->HasUnitState(UNIT_STATE_NO_COMBAT_MOVEMENT)) creature->GetMotionMaster()->MoveChase(creature->GetVictim(), attackDistance, attackAngle); break; @@ -2730,26 +2727,9 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u me->InterruptNonMeleeSpells(false); } - if (e.action.castCustom.flags & SMARTCAST_COMBAT_MOVE) - { - // If cast flag SMARTCAST_COMBAT_MOVE is set combat movement will not be allowed - // unless target is outside spell range, out of mana, or LOS. - - bool _allowMove = false; - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(e.action.castCustom.spell); // AssertSpellInfo? - int32 mana = me->GetPower(POWER_MANA); - - if (me->GetDistance(target->ToUnit()) > spellInfo->GetMaxRange(true) || - me->GetDistance(target->ToUnit()) < spellInfo->GetMinRange(true) || - !me->IsWithinLOSInMap(target->ToUnit()) || - mana < spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask())) - _allowMove = true; - - CAST_AI(SmartAI, me->AI())->SetCombatMove(_allowMove); - } - if (!(e.action.castCustom.flags & SMARTCAST_AURA_NOT_PRESENT) || !target->ToUnit()->HasAura(e.action.castCustom.spell)) { + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(e.action.castCustom.spell); CustomSpellValues values; if (e.action.castCustom.bp1) values.AddSpellMod(SPELLVALUE_BASE_POINT0, e.action.castCustom.bp1); @@ -2757,7 +2737,19 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u values.AddSpellMod(SPELLVALUE_BASE_POINT1, e.action.castCustom.bp2); if (e.action.castCustom.bp3) values.AddSpellMod(SPELLVALUE_BASE_POINT2, e.action.castCustom.bp3); - me->CastCustomSpell(e.action.castCustom.spell, values, target->ToUnit(), (e.action.castCustom.flags & SMARTCAST_TRIGGERED) ? TRIGGERED_FULL_MASK : TRIGGERED_NONE); + SpellCastResult result = me->CastCustomSpell(spellInfo, values, target->ToUnit(), (e.action.castCustom.flags & SMARTCAST_TRIGGERED) ? TRIGGERED_FULL_MASK : TRIGGERED_NONE); + + float spellMaxRange = me->GetSpellMaxRangeForTarget(target->ToUnit(), spellInfo); + if (e.action.cast.castFlags & SMARTCAST_COMBAT_MOVE) + { + // If cast flag SMARTCAST_COMBAT_MOVE is set combat movement will not be allowed unless target is outside spell range, out of mana, or LOS. + if (result == SPELL_FAILED_OUT_OF_RANGE || result == SPELL_CAST_OK) + // if we are just out of range, we only chase until we are back in spell range. + CAST_AI(SmartAI, me->AI())->SetCurrentRangeMode(true, std::max(spellMaxRange - NOMINAL_MELEE_RANGE, 0.0f)); + else // move into melee on any other fail + // if spell fail for any other reason, we chase to melee range, or stay where we are if spellcast was successful. + CAST_AI(SmartAI, me->AI())->SetCurrentRangeMode(false, 0.f); + } } } } diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.h b/src/server/game/AI/SmartScripts/SmartScriptMgr.h index c8b96a9268..e589d7bf1a 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.h +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.h @@ -1957,7 +1957,8 @@ enum SmartCastFlags SMARTCAST_AURA_NOT_PRESENT = 0x020, // Only casts the spell if the target does not have an aura from the spell SMARTCAST_COMBAT_MOVE = 0x040, // Prevents combat movement if cast successful. Allows movement on range, OOM, LOS SMARTCAST_THREATLIST_NOT_SINGLE = 0x080, // Only cast if the source's threatlist is higher than one. This includes pets (see Skeram's True Fulfillment) - SMARTCAST_TARGET_POWER_MANA = 0x100 // Only cast if the target has power type mana (e.g. Mana Drain) + SMARTCAST_TARGET_POWER_MANA = 0x100, // Only cast if the target has power type mana (e.g. Mana Drain) + SMARTCAST_ENABLE_COMBAT_MOVE_ON_LOS = 0x200, }; enum SmartFollowType diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index fc48264958..7963773799 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -1283,6 +1283,11 @@ SpellCastResult Unit::CastCustomSpell(uint32 spellId, CustomSpellValues const& v return SPELL_FAILED_SPELL_UNAVAILABLE; } + return CastCustomSpell(spellInfo, value, victim, triggerFlags, castItem, triggeredByAura, originalCaster); +} + +SpellCastResult Unit::CastCustomSpell(SpellInfo const* spellInfo, CustomSpellValues const& value, Unit* victim, TriggerCastFlags triggerFlags, Item* castItem, AuraEffect const* triggeredByAura, ObjectGuid originalCaster) +{ SpellCastTargets targets; targets.SetUnitTarget(victim); @@ -4130,15 +4135,6 @@ void Unit::InterruptSpell(CurrentSpellTypes spellType, bool withDelayed, bool wi spell->SetReferencedFromCurrent(false); } - // SAI creatures only - // Start chasing victim if they are spell casters (at least one SMC spell) if interrupted/silenced. - if (IsCreature()) - { - if (SmartAI* ai = dynamic_cast<SmartAI*>(ToCreature()->AI())) - if (ai->CanChaseOnInterrupt()) - ai->SetCombatMove(true); - } - if (IsCreature() && IsAIEnabled) ToCreature()->AI()->OnSpellCastFinished(spell->GetSpellInfo(), SPELL_FINISHED_CANCELED); } diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 302f882531..2fcccdb905 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -736,6 +736,8 @@ public: [[nodiscard]] uint16 GetExtraUnitMovementFlags() const { return m_movementInfo.flags2; } void SetExtraUnitMovementFlags(uint16 f) { m_movementInfo.flags2 = f; } + inline bool IsCrowdControlled() const { return HasFlag(UNIT_FIELD_FLAGS, (UNIT_FLAG_CONFUSED | UNIT_FLAG_FLEEING | UNIT_FLAG_STUNNED)); } + /*********************************************************/ /*** UNIT TYPES, CLASSES, RACES... ***/ /*********************************************************/ @@ -1595,6 +1597,7 @@ public: SpellCastResult CastCustomSpell(uint32 spellId, SpellValueMod mod, int32 value, Unit* victim, bool triggered, Item* castItem = nullptr, AuraEffect const* triggeredByAura = nullptr, ObjectGuid originalCaster = ObjectGuid::Empty); SpellCastResult CastCustomSpell(uint32 spellId, SpellValueMod mod, int32 value, Unit* victim = nullptr, TriggerCastFlags triggerFlags = TRIGGERED_NONE, Item* castItem = nullptr, AuraEffect const* triggeredByAura = nullptr, ObjectGuid originalCaster = ObjectGuid::Empty); SpellCastResult CastCustomSpell(uint32 spellId, CustomSpellValues const& value, Unit* victim = nullptr, TriggerCastFlags triggerFlags = TRIGGERED_NONE, Item* castItem = nullptr, AuraEffect const* triggeredByAura = nullptr, ObjectGuid originalCaster = ObjectGuid::Empty); + SpellCastResult CastCustomSpell(SpellInfo const* spellInfo, CustomSpellValues const& value, Unit* victim = nullptr, TriggerCastFlags triggerFlags = TRIGGERED_NONE, Item* castItem = nullptr, AuraEffect const* triggeredByAura = nullptr, ObjectGuid originalCaster = ObjectGuid::Empty); /*********************************************************/ /*** METHODS RELATED TO GAMEOBJECT & DYNOBEJCTS ***/ diff --git a/src/server/game/Entities/Unit/UnitDefines.h b/src/server/game/Entities/Unit/UnitDefines.h index c0a39a03c5..e6885fe162 100644 --- a/src/server/game/Entities/Unit/UnitDefines.h +++ b/src/server/game/Entities/Unit/UnitDefines.h @@ -197,6 +197,8 @@ enum UnitState UNIT_STATE_IGNORE_PATHFINDING = 0x10000000, // do not use pathfinding in any MovementGenerator UNIT_STATE_NO_ENVIRONMENT_UPD = 0x20000000, + UNIT_STATE_NO_COMBAT_MOVEMENT, // serverside only - should never be changed outside of core and hence shouldnt have a defined static value and be at the end + UNIT_STATE_ALL_STATE_SUPPORTED = UNIT_STATE_DIED | UNIT_STATE_MELEE_ATTACKING | UNIT_STATE_STUNNED | UNIT_STATE_ROAMING | UNIT_STATE_CHASE | UNIT_STATE_FLEEING | UNIT_STATE_IN_FLIGHT | UNIT_STATE_FOLLOW | UNIT_STATE_ROOT | UNIT_STATE_CONFUSED | UNIT_STATE_DISTRACTED | UNIT_STATE_ISOLATED | UNIT_STATE_ATTACK_PLAYER | UNIT_STATE_CASTING diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp index 8189fd5058..2b2aba6ef9 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -310,12 +310,29 @@ void MotionMaster::MoveConfused() /** * @brief Force the unit to chase this target. Doesn't work with UNIT_FLAG_DISABLE_MOVE */ -void MotionMaster::MoveChase(Unit* target, std::optional<ChaseRange> dist, std::optional<ChaseAngle> angle) +void MotionMaster::MoveChase(Unit* target, std::optional<ChaseRange> dist, std::optional<ChaseAngle> angle) { // ignore movement request if target not exist if (!target || target == _owner || _owner->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE)) return; + if (GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE) + { + if (_owner->IsPlayer()) + { + ChaseMovementGenerator<Player>* gen = (ChaseMovementGenerator<Player>*)top(); + gen->SetOffsetAndAngle(dist, angle); + gen->SetNewTarget(target); + } + else + { + ChaseMovementGenerator<Creature>* gen = (ChaseMovementGenerator<Creature>*)top(); + gen->SetOffsetAndAngle(dist, angle); + gen->SetNewTarget(target); + } + return; + } + //_owner->ClearUnitState(UNIT_STATE_FOLLOW); if (_owner->IsPlayer()) { @@ -331,6 +348,24 @@ void MotionMaster::MoveChase(Unit* target, std::optional<ChaseRange> dist, std: } } +void MotionMaster::DistanceYourself(float dist) +{ + if (GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE) + { + if (_owner->IsPlayer()) + { + ChaseMovementGenerator<Player>* gen = (ChaseMovementGenerator<Player>*)top(); + gen->DistanceYourself((Player*)_owner, dist); + } + else + { + ChaseMovementGenerator<Creature>* gen = (ChaseMovementGenerator<Creature>*)top(); + gen->DistanceYourself((Creature*)_owner, dist); + } + return; + } +} + void MotionMaster::MoveBackwards(Unit* target, float dist) { if (!target) diff --git a/src/server/game/Movement/MotionMaster.h b/src/server/game/Movement/MotionMaster.h index 248263d7ba..0fcf5abae8 100644 --- a/src/server/game/Movement/MotionMaster.h +++ b/src/server/game/Movement/MotionMaster.h @@ -247,6 +247,8 @@ public: void ReinitializeMovement(); bool GetDestination(float& x, float& y, float& z); + + void DistanceYourself(float range); private: void Mutate(MovementGenerator* m, MovementSlot slot); // use Move* functions instead diff --git a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp index 76fb0840a4..4dfc688282 100644 --- a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp @@ -63,6 +63,81 @@ bool ChaseMovementGenerator<T>::PositionOkay(T* owner, Unit* target, Optional<fl } template<class T> +void ChaseMovementGenerator<T>::SetOffsetAndAngle(std::optional<ChaseRange> dist, std::optional<ChaseAngle> angle) +{ + _range = dist; + _angle = angle; + _lastTargetPosition.reset(); +} + +template<class T> +void ChaseMovementGenerator<T>::SetNewTarget(Unit* target) +{ + i_target.link(target, this); + _lastTargetPosition.reset(); +} + +template<class T> +void ChaseMovementGenerator<T>::DistanceYourself(T* owner, float distance) +{ + // make a new path if we have to... + if (!i_path) + i_path = std::make_unique<PathGenerator>(owner); + + float x, y, z; + i_target->GetNearPoint(owner, x, y, z, owner->GetBoundaryRadius(), distance, i_target->GetAngle(owner)); + if (DispatchSplineToPosition(owner, x, y, z, false, false, 0.f, false, false)) + { + m_currentMode = CHASE_MODE_DISTANCING; + if constexpr (!std::is_same_v<T, Player>) + { + owner->AI()->DistancingStarted(); + } + } +} + +template<class T> +bool ChaseMovementGenerator<T>::DispatchSplineToPosition(T* owner, float x, float y, float z, bool walk, bool cutPath, float maxTarget, bool forceDest, bool target) +{ + Creature* cOwner = owner->ToCreature(); + + if (owner->IsHovering()) + owner->UpdateAllowedPositionZ(x, y, z); + + bool success = i_path->CalculatePath(x, y, z, forceDest); + if (!success || i_path->GetPathType() & PATHFIND_NOPATH) + { + if (cOwner) + { + cOwner->SetCannotReachTarget(i_target.getTarget()->GetGUID()); + } + + owner->StopMoving(); + return true; + } + + if (cutPath) + i_path->ShortenPathUntilDist(G3D::Vector3(x, y, z), maxTarget); + + if (cOwner) + { + cOwner->SetCannotReachTarget(); + } + + owner->AddUnitState(UNIT_STATE_CHASE_MOVE); + i_recalculateTravel = true; + + Movement::MoveSplineInit init(owner); + init.MovebyPath(i_path->GetPath()); + if (target) + init.SetFacing(i_target.getTarget()); + init.SetWalk(walk); + init.Launch(); + + return false; +} + +template<class T> bool ChaseMovementGenerator<T>::DoUpdate(T* owner, uint32 time_diff) { if (!i_target.isValid() || !i_target->IsInWorld() || !owner->IsInMap(i_target.getTarget())) @@ -71,6 +146,13 @@ bool ChaseMovementGenerator<T>::DoUpdate(T* owner, uint32 time_diff) if (!owner || !owner->IsAlive()) return false; + if (owner->HasUnitState(UNIT_STATE_NO_COMBAT_MOVEMENT)) // script paused combat movement + { + owner->StopMoving(); + _lastTargetPosition.reset(); + return true; + } + Creature* cOwner = owner->ToCreature(); bool isStoppedBecauseOfCasting = cOwner && cOwner->IsMovementPreventedByCasting(); @@ -243,53 +325,23 @@ bool ChaseMovementGenerator<T>::DoUpdate(T* owner, uint32 time_diff) shortenPath = false; } - if (owner->IsHovering()) - owner->UpdateAllowedPositionZ(x, y, z); - - bool success = i_path->CalculatePath(x, y, z, forceDest); - if (!success || i_path->GetPathType() & PATHFIND_NOPATH) - { - if (cOwner) - { - cOwner->SetCannotReachTarget(target->GetGUID()); - } - - owner->StopMoving(); - return true; - } - - if (shortenPath) - i_path->ShortenPathUntilDist(G3D::Vector3(x, y, z), maxTarget); - - if (cOwner) - { - cOwner->SetCannotReachTarget(); - } - bool walk = false; if (cOwner && !cOwner->IsPet()) { switch (cOwner->GetMovementTemplate().GetChase()) { - case CreatureChaseMovementType::CanWalk: - walk = owner->IsWalking(); - break; - case CreatureChaseMovementType::AlwaysWalk: - walk = true; - break; - default: - break; + case CreatureChaseMovementType::CanWalk: + walk = owner->IsWalking(); + break; + case CreatureChaseMovementType::AlwaysWalk: + walk = true; + break; + default: + break; } } - owner->AddUnitState(UNIT_STATE_CHASE_MOVE); - i_recalculateTravel = true; - - Movement::MoveSplineInit init(owner); - init.MovebyPath(i_path->GetPath()); - init.SetFacing(target); - init.SetWalk(walk); - init.Launch(); + DispatchSplineToPosition(owner, x, y, z, walk, shortenPath, maxTarget, forceDest, true); } } @@ -339,9 +391,24 @@ void ChaseMovementGenerator<T>::MovementInform(T* owner) if (!owner->IsCreature()) return; - // Pass back the GUIDLow of the target. If it is pet's owner then PetAI will handle - if (CreatureAI* AI = owner->ToCreature()->AI()) - AI->MovementInform(CHASE_MOTION_TYPE, i_target.getTarget()->GetGUID().GetCounter()); + switch (m_currentMode) + { + default: + { + // Pass back the GUIDLow of the target. If it is pet's owner then PetAI will handle + if (CreatureAI* AI = owner->ToCreature()->AI()) + AI->MovementInform(CHASE_MOTION_TYPE, i_target.getTarget()->GetGUID().GetCounter()); + break; + } + case CHASE_MODE_DISTANCING: + { + if (CreatureAI* AI = owner->ToCreature()->AI()) + AI->DistancingEnded(); + break; + } + } + + m_currentMode = CHASE_MODE_NORMAL; } //-----------------------------------------------// @@ -604,6 +671,13 @@ template bool ChaseMovementGenerator<Player>::DoUpdate(Player*, uint32); template bool ChaseMovementGenerator<Creature>::DoUpdate(Creature*, uint32); template void ChaseMovementGenerator<Unit>::MovementInform(Unit*); +template void ChaseMovementGenerator<Creature>::SetOffsetAndAngle(std::optional<ChaseRange>, std::optional<ChaseAngle>); +template void ChaseMovementGenerator<Creature>::SetNewTarget(Unit*); +template void ChaseMovementGenerator<Creature>::DistanceYourself(Creature*, float); +template void ChaseMovementGenerator<Player>::SetOffsetAndAngle(std::optional<ChaseRange>, std::optional<ChaseAngle>); +template void ChaseMovementGenerator<Player>::SetNewTarget(Unit*); +template void ChaseMovementGenerator<Player>::DistanceYourself(Player*, float); + template void FollowMovementGenerator<Player>::DoInitialize(Player*); template void FollowMovementGenerator<Creature>::DoInitialize(Creature*); template void FollowMovementGenerator<Player>::DoFinalize(Player*); diff --git a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h index 82ba2df472..e5e87f845b 100644 --- a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h +++ b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.h @@ -34,12 +34,20 @@ protected: FollowerReference i_target; }; +enum ChaseMovementMode +{ + CHASE_MODE_NORMAL, // chasing target + CHASE_MODE_BACKPEDAL, // collision movement + CHASE_MODE_DISTANCING, // running away from melee + CHASE_MODE_FANNING, // mob collision movement +}; + template<class T> class ChaseMovementGenerator : public MovementGeneratorMedium<T, ChaseMovementGenerator<T>>, public TargetedMovementGeneratorBase { public: ChaseMovementGenerator(Unit* target, Optional<ChaseRange> range = {}, Optional<ChaseAngle> angle = {}) - : TargetedMovementGeneratorBase(target), i_leashExtensionTimer(5000), i_path(nullptr), i_recheckDistance(0), i_recalculateTravel(true), _range(range), _angle(angle) {} + : TargetedMovementGeneratorBase(target), i_leashExtensionTimer(5000), i_path(nullptr), i_recheckDistance(0), i_recalculateTravel(true), _range(range), _angle(angle), m_currentMode(CHASE_MODE_NORMAL) {} ~ChaseMovementGenerator() { } MovementGeneratorType GetMovementGeneratorType() { return CHASE_MOTION_TYPE; } @@ -58,6 +66,11 @@ public: bool EnableWalking() const { return false; } bool HasLostTarget(Unit* unit) const { return unit->GetVictim() != this->GetTarget(); } + void SetOffsetAndAngle(std::optional<ChaseRange> dist, std::optional<ChaseAngle> angle); + void SetNewTarget(Unit* target); + + void DistanceYourself(T* owner, float distance); + bool DispatchSplineToPosition(T* owner, float x, float y, float z, bool walk, bool cutPath, float maxTarget, bool forceDest, bool target = false); private: TimeTrackerSmall i_leashExtensionTimer; std::unique_ptr<PathGenerator> i_path; @@ -65,10 +78,12 @@ private: bool i_recalculateTravel; Optional<Position> _lastTargetPosition; - Optional<ChaseRange> const _range; - Optional<ChaseAngle> const _angle; + Optional<ChaseRange> _range; + Optional<ChaseAngle> _angle; bool _movingTowards = true; bool _mutualChase = true; + + ChaseMovementMode m_currentMode; }; template<class T> |