diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 3e2cb36dd..8a0eebffc 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -1753,6 +1753,56 @@ float WorldObject::GetSightRange(WorldObject const* target) const return 0.0f; } +float WorldObject::GetLeewayBonusRangeForTargets(Player const* player, Unit const* target) +{ + if (!player || !target) + return 0.0f; + + constexpr uint32 leewayMoveFlags = MOVEMENTFLAG_FORWARD | MOVEMENTFLAG_STRAFE_LEFT | MOVEMENTFLAG_STRAFE_RIGHT | MOVEMENTFLAG_FALLING; + if (player->HasUnitMovementFlag(leewayMoveFlags) && !player->IsWalking() && target->HasUnitMovementFlag(leewayMoveFlags) && !target->IsWalking()) + return LEEWAY_BONUS_RANGE; + + return 0.0f; +} + +float WorldObject::GetLeewayBonusRange(Unit const* target) const +{ + if (!target) + return 0.0f; + + if (Player const* player = ToPlayer()) + return GetLeewayBonusRangeForTargets(player, target); + + if (Player const* playerTarget = target->ToPlayer()) + return GetLeewayBonusRangeForTargets(playerTarget, ToUnit()); + + return 0.0f; +} + +float WorldObject::GetLeewayBonusRadius() const +{ + if (Player const* player = ToPlayer()) + { + bool hasLeewayMovement = false; + + if (player->HasUnitState(UNIT_STATE_JUMPING) || player->HasUnitMovementFlag(MOVEMENTFLAG_FALLING)) + hasLeewayMovement = true; + else + { + float speedXY = (player->m_movementInfo.jump.xyspeed > 0.0f) + ? player->m_movementInfo.jump.xyspeed + : player->GetSpeed(player->IsWalking() ? MOVE_WALK : MOVE_RUN); + + hasLeewayMovement = speedXY > LEEWAY_MIN_MOVE_SPEED; + } + + if (hasLeewayMovement) + return LEEWAY_BONUS_RANGE; + } + + return 0.0f; +} + bool WorldObject::CanSeeOrDetect(WorldObject const* obj, bool ignoreStealth, bool distanceCheck, bool checkAlert) const { if (this == obj) diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index 4a635175d..454537353 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -605,6 +605,11 @@ public: [[nodiscard]] float GetGridActivationRange() const; [[nodiscard]] float GetVisibilityRange() const; virtual float GetSightRange(WorldObject const* target = nullptr) const; + + [[nodiscard]] static float GetLeewayBonusRangeForTargets(Player const* player, Unit const* target); + [[nodiscard]] float GetLeewayBonusRange(Unit const* target) const; + [[nodiscard]] float GetLeewayBonusRadius() const; + //bool CanSeeOrDetect(WorldObject const* obj, bool ignoreStealth = false, bool distanceCheck = false) const; bool CanSeeOrDetect(WorldObject const* obj, bool ignoreStealth = false, bool distanceCheck = false, bool checkAlert = false) const; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index d8fe891f0..009c33c9e 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -792,8 +792,7 @@ bool Unit::IsWithinMeleeRange(Unit const* obj, float dist) const float maxdist = dist + GetMeleeRange(obj); - if ((IsPlayer() || obj->IsPlayer()) && HasLeewayMovement() && obj->HasLeewayMovement()) - maxdist += LEEWAY_BONUS_RANGE; + maxdist += GetLeewayBonusRange(obj); return distsq < maxdist * maxdist; } diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 0700f1adc..13654929d 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1729,12 +1729,6 @@ public: UNIT_STATE_ROOT | UNIT_STATE_STUNNED | UNIT_STATE_DISTRACTED) && !GetOwnerGUID(); } - [[nodiscard]] bool HasLeewayMovement() const - { - return m_movementInfo.HasMovementFlag(MOVEMENTFLAG_FORWARD | MOVEMENTFLAG_STRAFE_LEFT | MOVEMENTFLAG_STRAFE_RIGHT | MOVEMENTFLAG_FALLING) - && !IsWalking(); - } - void KnockbackFrom(float x, float y, float speedXY, float speedZ); void JumpTo(float speedXY, float speedZ, bool forward = true); void JumpTo(WorldObject* obj, float speedZ); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 68f028a95..a5f8bcc1d 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -1248,6 +1248,9 @@ void Spell::SelectImplicitConeTargets(SpellEffIndex effIndex, SpellImplicitTarge } float radius = m_spellInfo->Effects[effIndex].CalcRadius(m_caster) * m_spellValue->RadiusMod; + + radius += m_caster->GetLeewayBonusRadius(); + if (uint32 containerTypeMask = GetSearcherTypeMask(objectType, condList)) { Acore::WorldObjectSpellConeTargetCheck check(coneAngle, radius, m_caster, m_spellInfo, selectionType, condList); @@ -1335,6 +1338,17 @@ void Spell::SelectImplicitAreaTargets(SpellEffIndex effIndex, SpellImplicitTarge // Xinef: the distance should be increased by caster size, it is neglected in latter calculations std::list targets; float radius = m_spellInfo->Effects[effIndex].CalcRadius(m_caster) * m_spellValue->RadiusMod; + switch (targetType.GetTarget()) + { + case TARGET_UNIT_SRC_AREA_ENEMY: + case TARGET_UNIT_CASTER_AREA_PARTY: + case TARGET_UNIT_CASTER_AREA_RAID: + radius += m_caster->GetLeewayBonusRadius(); + break; + default: + break; + } + SearchAreaTargets(targets, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), m_spellInfo->Effects[effIndex].ImplicitTargetConditions, Acore::WorldObjectSpellAreaTargetSearchReason::Area); CallScriptObjectAreaTargetSelectHandlers(targets, effIndex, targetType); @@ -7088,8 +7102,9 @@ SpellCastResult Spell::CheckRange(bool strict) if (range_type == SPELL_RANGE_MELEE) { float real_max_range = max_range; - if (!m_caster->IsCreature() && m_caster->HasLeewayMovement() && target->HasLeewayMovement()) - real_max_range -= MIN_MELEE_REACH; // Because of lag, we can not check too strictly here (is only used if both caster and target are moving) + + if (m_caster->GetLeewayBonusRange(target) > 0.0f) + real_max_range -= MIN_MELEE_REACH; // less strict when leeway applies else real_max_range -= 2 * MIN_MELEE_REACH; @@ -7124,7 +7139,7 @@ SpellCastResult Spell::CheckRange(bool strict) if (m_targets.HasDst() && !m_targets.HasTraj()) { - if (!m_caster->IsWithinDist3d(m_targets.GetDstPos(), max_range)) + if (!m_caster->IsWithinDist3d(m_targets.GetDstPos(), max_range + m_caster->GetLeewayBonusRadius())) return SPELL_FAILED_OUT_OF_RANGE; if (min_range && m_caster->IsWithinDist3d(m_targets.GetDstPos(), min_range)) return SPELL_FAILED_TOO_CLOSE;