From bb7753d363067fc3ab673f8ca34210285c4ec5af Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Tue, 2 Dec 2025 09:44:23 -0600 Subject: [PATCH] fix(Core/SAI): SMARTCAST_COMBAT_MOVE prevents movement on successful cast (#23913) --- src/server/game/AI/SmartScripts/SmartAI.cpp | 47 ++++++++++++++++++- src/server/game/AI/SmartScripts/SmartAI.h | 1 + .../game/AI/SmartScripts/SmartScript.cpp | 8 +--- src/server/game/AI/SmartScripts/SmartScript.h | 2 + .../game/AI/SmartScripts/SmartScriptMgr.h | 21 +++++---- 5 files changed, 61 insertions(+), 18 deletions(-) diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index f88840349..3c952a42d 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -858,7 +858,7 @@ void SmartAI::AttackStart(Unit* who) return; } - if (who && me->Attack(who, me->IsWithinMeleeRange(who))) + if (who && me->Attack(who, me->IsWithinMeleeRange(who) || _currentRangeMode)) { if (!me->HasUnitState(UNIT_STATE_NO_COMBAT_MOVEMENT)) { @@ -870,7 +870,7 @@ void SmartAI::AttackStart(Unit* who) me->GetMotionMaster()->Clear(false); } - me->GetMotionMaster()->MoveChase(who); + me->GetMotionMaster()->MoveChase(who, _attackDistance); } } } @@ -941,6 +941,35 @@ void SmartAI::PassengerBoarded(Unit* who, int8 seatId, bool apply) void SmartAI::InitializeAI() { GetScript()->OnInitialize(me); + + for (SmartScriptHolder const& event : GetScript()->GetEvents()) + { + if (event.GetActionType() != SMART_ACTION_CAST) + continue; + + if (!(event.action.cast.castFlags & SMARTCAST_MAIN_SPELL)) + continue; + + SetMainSpell(event.action.cast.spell); + break; + } + + // Fallback: use first SMARTCAST_COMBAT_MOVE if no MAIN_SPELL found + if (!_currentRangeMode) + { + for (SmartScriptHolder const& event : GetScript()->GetEvents()) + { + if (event.GetActionType() != SMART_ACTION_CAST) + continue; + + if (!(event.action.cast.castFlags & SMARTCAST_COMBAT_MOVE)) + continue; + + SetMainSpell(event.action.cast.spell); + break; + } + } + if (!me->isDead()) { mJustReset = true; @@ -1083,6 +1112,20 @@ void SmartAI::SetCurrentRangeMode(bool on, float range) me->GetMotionMaster()->MoveChase(victim, _attackDistance); } +void SmartAI::SetMainSpell(uint32 spellId) +{ + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo) + return; + + float maxRange = spellInfo->GetMaxRange(false); + if (maxRange <= NOMINAL_MELEE_RANGE) + return; + + _attackDistance = std::max(maxRange - NOMINAL_MELEE_RANGE, 0.0f); + _currentRangeMode = true; +} + void SmartAI::DistanceYourself(float range) { Unit* victim = me->GetVictim(); diff --git a/src/server/game/AI/SmartScripts/SmartAI.h b/src/server/game/AI/SmartScripts/SmartAI.h index d6b957cc0..087633645 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.h +++ b/src/server/game/AI/SmartScripts/SmartAI.h @@ -67,6 +67,7 @@ public: void SetAutoAttack(bool on) { mCanAutoAttack = on; } void SetCombatMovement(bool on, bool stopOrStartMovement); void SetCurrentRangeMode(bool on, float range = 0.f); + void SetMainSpell(uint32 spellId); 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); diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index e7a5c4fd8..2f66b4ac1 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -707,7 +707,6 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u continue; } - // Let us not try to cast spell if we know it is going to fail anyway. Stick to chasing and continue. if (distanceToTarget > spellMaxRange && isWithinLOSInMap) { failedSpellCast = true; @@ -745,12 +744,9 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u 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. + if (result == SPELL_FAILED_OUT_OF_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. + else if (result != SPELL_CAST_OK) CAST_AI(SmartAI, me->AI())->SetCurrentRangeMode(false, 0.f); } diff --git a/src/server/game/AI/SmartScripts/SmartScript.h b/src/server/game/AI/SmartScripts/SmartScript.h index e0f32438c..250fdd775 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.h +++ b/src/server/game/AI/SmartScripts/SmartScript.h @@ -207,6 +207,8 @@ public: void AddCreatureSummon(ObjectGuid const& guid); void RemoveCreatureSummon(ObjectGuid const& guid); + SmartAIEventList const& GetEvents() const { return mEvents; } + private: void IncPhase(uint32 p); void DecPhase(uint32 p); diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.h b/src/server/game/AI/SmartScripts/SmartScriptMgr.h index 5e8e5e9e0..8dd177dfa 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.h +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.h @@ -1932,16 +1932,17 @@ enum SmartEventFlags enum SmartCastFlags { - SMARTCAST_INTERRUPT_PREVIOUS = 0x001, // Interrupt any spell casting - SMARTCAST_TRIGGERED = 0x002, // Triggered (this makes spell cost zero mana and have no cast time) - //CAST_FORCE_CAST = 0x004, // Forces cast even if creature is out of mana or out of range - //CAST_NO_MELEE_IF_OOM = 0x008, // Prevents creature from entering melee if out of mana or out of range - //CAST_FORCE_TARGET_SELF = 0x010, // Forces the target to cast this spell on itself - 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_ENABLE_COMBAT_MOVE_ON_LOS = 0x200, + SMARTCAST_INTERRUPT_PREVIOUS = 0x001, // Interrupt any spell casting + SMARTCAST_TRIGGERED = 0x002, // Triggered (this makes spell cost zero mana and have no cast time) + //CAST_FORCE_CAST = 0x004, // Forces cast even if creature is out of mana or out of range + //CAST_NO_MELEE_IF_OOM = 0x008, // Prevents creature from entering melee if out of mana or out of range + //CAST_FORCE_TARGET_SELF = 0x010, // Forces the target to cast this spell on itself + 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_ENABLE_COMBAT_MOVE_ON_LOS = 0x200, // Allows combat movement when not in line of sight + SMARTCAST_MAIN_SPELL = 0x400, // Sets this spell's max range as the creature's chase distance on spawn }; enum SmartFollowType