diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index d732e90aa4f5c..68b450b9c441f 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -3445,21 +3445,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/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 26c824cace589..073b0f8c7169c 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -5882,18 +5882,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; @@ -5911,8 +5911,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) diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index b70597d6f3609..d39c7d9d51f27 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -3315,6 +3315,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/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 3a9fde754f145..0d802cb0a8daf 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -488,7 +488,7 @@ class CreatureGameObjectScriptRegistrySwapHooks } }; - AIFunctionMapWorker::type> worker(std::move(evaluator)); + AIFunctionMapWorker> worker(std::move(evaluator)); TypeContainerVisitor containerVisitor(worker); containerVisitor.Visit(map->GetObjectsStore()); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index f269c8b7aefd7..e8b5b2bd4bd5d 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" @@ -3244,19 +3245,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)) @@ -3269,6 +3260,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(); @@ -3922,21 +3931,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) { @@ -6600,6 +6597,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 056e02dedccf0..46b7926a29820 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 2e447f1066297..267723e96f3b2 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -4526,10 +4526,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;