From bdb5c72def57bfcf3edba988e9d7b1905d0bd4a6 Mon Sep 17 00:00:00 2001 From: Shauren Date: Sat, 18 May 2024 11:50:00 +0200 Subject: [PATCH 1/4] Core/Spells: Prevent creatures from being able to cast all their spells while moving Closes #26137 (cherry picked from commit f80f931e2bee9dbf08c3edee94d0c79dbbb64072) --- .../game/Entities/Creature/Creature.cpp | 17 +---- src/server/game/Entities/Unit/Unit.cpp | 5 ++ src/server/game/Movement/MovementDefines.h | 15 ++++ src/server/game/Spells/Spell.cpp | 71 +++++++++++-------- src/server/game/Spells/Spell.h | 1 + src/server/game/Spells/SpellEffects.cpp | 4 -- 6 files changed, 67 insertions(+), 46 deletions(-) diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 900ae1b9d165e..5f730c9c996ee 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -3246,21 +3246,10 @@ void Creature::DoNotReacquireSpellFocusTarget() bool Creature::IsMovementPreventedByCasting() const { - // first check if currently a movement allowed channel is active and we're not casting - if (Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL]) - { - if (spell->getState() != SPELL_STATE_FINISHED && spell->IsChannelActive()) - if (spell->GetSpellInfo()->IsMoveAllowedChannel()) - return false; - } - - if (HasSpellFocus()) - return true; - - if (HasUnitState(UNIT_STATE_CASTING)) - return true; + if (!Unit::IsMovementPreventedByCasting() && !HasSpellFocus()) + return false; - return false; + return true; } void Creature::StartPickPocketRefillTimer() diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 43c3446daf93c..d412b619be7d3 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -3123,6 +3123,11 @@ bool Unit::IsMovementPreventedByCasting() const if (!HasUnitState(UNIT_STATE_CASTING)) return false; + if (Spell* spell = m_currentSpells[CURRENT_GENERIC_SPELL]) + if (spell->getState() == SPELL_STATE_FINISHED || + !(spell->m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT)) + return false; + // channeled spells during channel stage (after the initial cast timer) allow movement with a specific spell attribute if (Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL]) if (spell->getState() != SPELL_STATE_FINISHED && spell->IsChannelActive()) diff --git a/src/server/game/Movement/MovementDefines.h b/src/server/game/Movement/MovementDefines.h index fd4cc0095f6c5..f4d35ec55d8e6 100644 --- a/src/server/game/Movement/MovementDefines.h +++ b/src/server/game/Movement/MovementDefines.h @@ -47,6 +47,21 @@ enum MovementGeneratorType : uint8 MAX_MOTION_TYPE // SKIP }; +constexpr bool CanStopMovementForSpellCasting(MovementGeneratorType type) +{ + // MovementGenerators that don't check Unit::IsMovementPreventedByCasting + switch (type) + { + case HOME_MOTION_TYPE: + case FLIGHT_MOTION_TYPE: + case EFFECT_MOTION_TYPE: // knockbacks, jumps, falling, land/takeoff transitions + return false; + default: + break; + } + return true; +} + enum MovementGeneratorMode : uint8 { MOTION_MODE_DEFAULT = 0, diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index f59e27da9c199..eaa4f1fad6518 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -36,6 +36,7 @@ #include "Item.h" #include "Log.h" #include "LootMgr.h" +#include "MotionMaster.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "Opcodes.h" @@ -3148,19 +3149,9 @@ SpellCastResult Spell::prepare(SpellCastTargets const& targets, AuraEffect const else m_casttime = m_spellInfo->CalcCastTime(this); - // don't allow channeled spells / spells with cast time to be cast while moving - // exception are only channeled spells that have no casttime and SPELL_ATTR5_CAN_CHANNEL_WHEN_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->ToPlayer()->IsCharmed() && m_caster->ToPlayer()->GetCharmerGUID().IsCreature()) && m_caster->ToPlayer()->isMoving() && (m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT)) - { - // 1. Has casttime, 2. Or doesn't have flag to allow movement during channel - if (m_casttime || !m_spellInfo->IsMoveAllowedChannel()) - { - SendCastResult(SPELL_FAILED_MOVING); - finish(false); - return SPELL_FAILED_MOVING; - } - } + SpellCastResult movementResult = SPELL_CAST_OK; + if (m_caster->IsUnit() && m_caster->ToUnit()->isMoving()) + movementResult = CheckMovement(); // Creatures focus their target when possible if (m_casttime && m_caster->IsCreature() && !m_spellInfo->IsNextMeleeSwingSpell() && !IsAutoRepeat() && !m_caster->ToUnit()->HasUnitFlag(UNIT_FLAG_POSSESSED)) @@ -3173,6 +3164,24 @@ SpellCastResult Spell::prepare(SpellCastTargets const& targets, AuraEffect const m_caster->ToCreature()->SetSpellFocus(this, nullptr); } + if (movementResult != SPELL_CAST_OK) + { + if (m_caster->ToUnit()->IsControlledByPlayer() || !CanStopMovementForSpellCasting(m_caster->ToUnit()->GetMotionMaster()->GetCurrentMovementGeneratorType())) + { + SendCastResult(movementResult); + finish(movementResult); + return movementResult; + } + else + { + // Creatures (not controlled) give priority to spell casting over movement. + // We assume that the casting is always valid and the current movement + // is stopped immediately (because spells are updated before movement, so next Unit::Update would cancel the spell before stopping movement) + // and future attempts are stopped by by Unit::IsMovementPreventedByCasting in movement generators to prevent casting interruption. + m_caster->ToUnit()->StopMoving(); + } + } + // set timer base at cast time ReSetTimer(); @@ -3805,21 +3814,9 @@ void Spell::update(uint32 difftime) return; } - // check if the player caster has moved before the spell finished - if (m_caster->GetTypeId() == TYPEID_PLAYER && m_timer != 0 && - m_caster->ToPlayer()->isMoving() && m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT && - (!m_spellInfo->HasEffect(SPELL_EFFECT_STUCK) || !m_caster->ToPlayer()->HasUnitMovementFlag(MOVEMENTFLAG_FALLING_FAR))) - { - // don't cancel for melee, autorepeat, triggered and instant spells - if (!m_spellInfo->IsNextMeleeSwingSpell() && !IsAutoRepeat() && !IsTriggered() && !(IsChannelActive() && m_spellInfo->IsMoveAllowedChannel())) - { - // if charmed by creature, trust the AI not to cheat and allow the cast to proceed - // @todo this is a hack, "creature" movesplines don't differentiate turning/moving right now - // however, checking what type of movement the spline is for every single spline would be really expensive - if (!m_caster->ToPlayer()->GetCharmerGUID().IsCreature()) - cancel(); - } - } + // check if the unit caster has moved before the spell finished + if (m_timer != 0 && m_caster->IsUnit() && m_caster->ToUnit()->isMoving() && CheckMovement() != SPELL_CAST_OK) + cancel(); switch (m_spellState) { @@ -6396,6 +6393,24 @@ int32 Spell::CalculateDamage(SpellEffectInfo const& spellEffectInfo) const return m_caster->CalculateSpellDamage(spellEffectInfo, m_spellValue->EffectBasePoints + spellEffectInfo.EffectIndex); } +SpellCastResult Spell::CheckMovement() const +{ + if (IsTriggered()) + return SPELL_CAST_OK; + + if (getState() == SPELL_STATE_PREPARING) + { + if (m_casttime > 0) + if (m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT) + return SPELL_FAILED_MOVING; + } + else if (getState() == SPELL_STATE_CASTING) + if (!m_spellInfo->IsMoveAllowedChannel()) + return SPELL_FAILED_MOVING; + + return SPELL_CAST_OK; +} + bool Spell::CanAutoCast(Unit* target) { if (!target) diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index 496dd23e6982b..29b5fb3b10583 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -338,6 +338,7 @@ class TC_GAME_API Spell SpellCastResult CheckPower() const; SpellCastResult CheckRuneCost(uint32 runeCostID) const; SpellCastResult CheckCasterAuras(uint32* param1) const; + SpellCastResult CheckMovement() const; bool CheckSpellCancelsAuraEffect(AuraType auraType, uint32* param1) const; bool CheckSpellCancelsCharm(uint32* param1) const; diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index b4cf72a7ac515..70eafbcb172be 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -4309,10 +4309,6 @@ void Spell::EffectKnockBack() if (unitTarget->HasUnitState(UNIT_STATE_ROOT)) return; - // Instantly interrupt non melee spells being cast - if (unitTarget->IsNonMeleeSpellCast(true)) - unitTarget->InterruptNonMeleeSpells(true); - float ratio = 0.1f; float speedxy = float(effectInfo->MiscValue) * ratio; float speedz = float(damage) * ratio; From dd40785b7f56f2c06861d709970f56f54d142002 Mon Sep 17 00:00:00 2001 From: Shauren Date: Sat, 18 May 2024 13:37:01 +0200 Subject: [PATCH 2/4] Core/Spells: Fix indent --- src/server/game/Spells/Spell.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index eaa4f1fad6518..9187a2411d8cc 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -6398,15 +6398,15 @@ SpellCastResult Spell::CheckMovement() const if (IsTriggered()) return SPELL_CAST_OK; - if (getState() == SPELL_STATE_PREPARING) - { - if (m_casttime > 0) - if (m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT) - return SPELL_FAILED_MOVING; - } - else if (getState() == SPELL_STATE_CASTING) - if (!m_spellInfo->IsMoveAllowedChannel()) + if (getState() == SPELL_STATE_PREPARING) + { + if (m_casttime > 0) + if (m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT) return SPELL_FAILED_MOVING; + } + else if (getState() == SPELL_STATE_CASTING) + if (!m_spellInfo->IsMoveAllowedChannel()) + return SPELL_FAILED_MOVING; return SPELL_CAST_OK; } From 95b83b54ee97be584ac3a7176a977b6b1785e95a Mon Sep 17 00:00:00 2001 From: Shauren Date: Wed, 22 May 2024 13:29:52 +0200 Subject: [PATCH 3/4] Core/Players: Fixed integer underflow in Player::UpdateCombatSkills Closes #29983 --- src/server/game/Entities/Player/Player.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 64f7e31888446..c68f2edf32cd7 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -5773,18 +5773,18 @@ void Player::UpdateWeaponSkill(Unit* victim, WeaponAttackType attType) void Player::UpdateCombatSkills(Unit* victim, WeaponAttackType attType, bool defense) { - uint8 plevel = GetLevel(); // if defense than victim == attacker - uint8 greylevel = Trinity::XP::GetGrayLevel(plevel); - uint8 moblevel = victim->GetLevelForTarget(this); + int32 plevel = GetLevel(); // if defense than victim == attacker + int32 greylevel = Trinity::XP::GetGrayLevel(plevel); + int32 moblevel = victim->GetLevelForTarget(this); if (moblevel > plevel + 5) moblevel = plevel + 5; - uint8 lvldif = moblevel - greylevel; + int32 lvldif = moblevel - greylevel; if (lvldif < 3) lvldif = 3; - uint32 skilldif = 5 * plevel - (defense ? GetBaseDefenseSkillValue() : GetBaseWeaponSkillValue(attType)); + int32 skilldif = 5 * plevel - int32(defense ? GetBaseDefenseSkillValue() : GetBaseWeaponSkillValue(attType)); if (skilldif <= 0) return; @@ -5802,8 +5802,6 @@ void Player::UpdateCombatSkills(Unit* victim, WeaponAttackType attType, bool def else UpdateWeaponSkill(victim, attType); } - else - return; } void Player::ModifySkillBonus(uint32 skillid, int32 val, bool talent) From d8240fb513fc18ee9bac067f8333aab1d96005d9 Mon Sep 17 00:00:00 2001 From: Shauren Date: Thu, 23 May 2024 11:19:55 +0200 Subject: [PATCH 4/4] Core/Misc: Fix msvc build with version 17.10 Closes #29999 --- src/server/game/Scripting/ScriptMgr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index b35aa9760b7de..d51b12bee8148 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -484,7 +484,7 @@ class CreatureGameObjectScriptRegistrySwapHooks } }; - AIFunctionMapWorker::type> worker(std::move(evaluator)); + AIFunctionMapWorker> worker(std::move(evaluator)); TypeContainerVisitor containerVisitor(worker); containerVisitor.Visit(map->GetObjectsStore());