mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-09-21 20:06:40 +00:00
Prevent targeting unreachable turreted entities
Introduce CanEverReachTarget in the Attack component to check whether a target is geometrically reachable at all, accounting for height offsets and turreted units inside buildings. For non-parabolic attacks (e.g. melee), a simple 3D distance check from the closest approach point is used. For parabolic attacks (e.g. ranged), the parabolic range formula is used to determine if the height difference is surmountable from the closest horizontal distance to the target. Add GetClosestApproachDistanceToTurretPoint to TurretHolder to estimate the minimum horizontal distance to a turret point, using its local offset and the holder's obstruction size. Passable buildings are not considered obstacles. Fixes units on the ground trying to attack unreachable units on walls or towers when the projectile's arc cannot reach the required height.
This commit is contained in:
@@ -1394,6 +1394,22 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute effective horizontal range given a reference range and height difference.
|
||||
*/
|
||||
static entity_pos_t ComputeParabolicRange(entity_pos_t range, entity_pos_t heightDiff)
|
||||
{
|
||||
if (heightDiff < -range / 2)
|
||||
return NEVER_IN_RANGE;
|
||||
|
||||
entity_pos_t effectiveRange;
|
||||
effectiveRange.SetInternalValue(static_cast<i32>(isqrt64(
|
||||
SQUARE_U64_FIXED(range) +
|
||||
static_cast<i64>(heightDiff.GetInternalValue()) * static_cast<i64>(range.GetInternalValue()) * 2
|
||||
)));
|
||||
return effectiveRange;
|
||||
}
|
||||
|
||||
entity_pos_t GetEffectiveParabolicRange(entity_id_t source, entity_id_t target, entity_pos_t range, entity_pos_t yOrigin) const override
|
||||
{
|
||||
// For non-positive ranges, just return the range.
|
||||
@@ -1412,14 +1428,28 @@ public:
|
||||
CFixedVector3D sourcePos = cmpSourcePosition->GetPosition();
|
||||
CFixedVector3D targetPos = cmpTargetPosition->GetPosition();
|
||||
|
||||
entity_pos_t heightDifference = sourcePos.Y - targetPos.Y + yOrigin;
|
||||
entity_pos_t heightDiff = sourcePos.Y - targetPos.Y + yOrigin;
|
||||
return ComputeParabolicRange(range, heightDiff);
|
||||
}
|
||||
|
||||
if (heightDifference < -range / 2)
|
||||
return NEVER_IN_RANGE;
|
||||
entity_pos_t GetMaxReachableParabolicHeight(entity_pos_t range, entity_pos_t yOrigin, entity_pos_t horizDistance) const override
|
||||
{
|
||||
// EffectiveRange² = range² + 2 * range * heightDiff
|
||||
// Solve for heightDiff when effectiveRange = horizDistance:
|
||||
// heightDiff = (horizDistance² - range²) / (2 * range)
|
||||
// Max target height above source = yOrigin - heightDiff
|
||||
// = yOrigin + (range² - horizDistance²) / (2 * range)
|
||||
//
|
||||
// If horizDistance > range, the result is less than yOrigin (can be negative),
|
||||
// meaning the source must be above the target to compensate for the extra horizontal distance.
|
||||
// The caller can decide if that's acceptable.
|
||||
i64 rangeSq = SQUARE_U64_FIXED(range);
|
||||
i64 distSq = SQUARE_U64_FIXED(horizDistance);
|
||||
i64 numerator = rangeSq - distSq;
|
||||
|
||||
entity_pos_t effectiveRange;
|
||||
effectiveRange.SetInternalValue(static_cast<i32>(isqrt64(SQUARE_U64_FIXED(range) + static_cast<i64>(heightDifference.GetInternalValue()) * static_cast<i64>(range.GetInternalValue()) * 2)));
|
||||
return effectiveRange;
|
||||
entity_pos_t result;
|
||||
result.SetInternalValue(static_cast<i32>(numerator / static_cast<i64>(range.GetInternalValue() * 2)));
|
||||
return yOrigin + result;
|
||||
}
|
||||
|
||||
entity_pos_t GetElevationAdaptedRange(const CFixedVector3D& pos1, const CFixedVector3D& rot, entity_pos_t range, entity_pos_t yOrigin, entity_pos_t angle) const override
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2025 Wildfire Games.
|
||||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@@ -45,6 +45,14 @@ std::string ICmpObstruction::CheckFoundation_wrapper(const std::string& classNam
|
||||
}
|
||||
}
|
||||
|
||||
CFixedVector2D ICmpObstruction::GetObstructionHalfSizes_wrapper() const
|
||||
{
|
||||
ICmpObstructionManager::ObstructionSquare square;
|
||||
if (!GetObstructionSquare(square))
|
||||
return CFixedVector2D(entity_pos_t::FromInt(-1), entity_pos_t::FromInt(-1));
|
||||
return CFixedVector2D(square.hw, square.hh);
|
||||
}
|
||||
|
||||
BEGIN_INTERFACE_WRAPPER(Obstruction)
|
||||
DEFINE_INTERFACE_METHOD("GetSize", ICmpObstruction, GetSize)
|
||||
DEFINE_INTERFACE_METHOD("CheckShorePlacement", ICmpObstruction, CheckShorePlacement)
|
||||
@@ -55,6 +63,7 @@ DEFINE_INTERFACE_METHOD("GetEntitiesBlockingConstruction", ICmpObstruction, GetE
|
||||
DEFINE_INTERFACE_METHOD("GetEntitiesDeletedUponConstruction", ICmpObstruction, GetEntitiesDeletedUponConstruction)
|
||||
DEFINE_INTERFACE_METHOD("SetActive", ICmpObstruction, SetActive)
|
||||
DEFINE_INTERFACE_METHOD("SetDisableBlockMovementPathfinding", ICmpObstruction, SetDisableBlockMovementPathfinding)
|
||||
DEFINE_INTERFACE_METHOD("GetObstructionHalfSizes", ICmpObstruction, GetObstructionHalfSizes_wrapper)
|
||||
DEFINE_INTERFACE_METHOD("GetBlockMovementFlag", ICmpObstruction, GetBlockMovementFlag)
|
||||
DEFINE_INTERFACE_METHOD("SetControlGroup", ICmpObstruction, SetControlGroup)
|
||||
DEFINE_INTERFACE_METHOD("GetControlGroup", ICmpObstruction, GetControlGroup)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2025 Wildfire Games.
|
||||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@@ -104,6 +104,12 @@ public:
|
||||
*/
|
||||
virtual std::string CheckFoundation_wrapper(const std::string& className, bool onlyCenterPoint) const;
|
||||
|
||||
/**
|
||||
* GetObstructionSquare wrapper for script calls.
|
||||
* @return [hw, hh] half-sizes of the obstruction square, or empty array on failure.
|
||||
*/
|
||||
virtual CFixedVector2D GetObstructionHalfSizes_wrapper() const;
|
||||
|
||||
/**
|
||||
* Test whether this entity is colliding with any obstructions that share its
|
||||
* control groups and block the creation of foundations.
|
||||
|
||||
@@ -68,6 +68,7 @@ DEFINE_INTERFACE_METHOD("GetLosRevealWholeMap", ICmpRangeManager, GetLosRevealWh
|
||||
DEFINE_INTERFACE_METHOD("SetLosRevealWholeMapForAll", ICmpRangeManager, SetLosRevealWholeMapForAll)
|
||||
DEFINE_INTERFACE_METHOD("GetLosRevealWholeMapForAll", ICmpRangeManager, GetLosRevealWholeMapForAll)
|
||||
DEFINE_INTERFACE_METHOD("GetEffectiveParabolicRange", ICmpRangeManager, GetEffectiveParabolicRange)
|
||||
DEFINE_INTERFACE_METHOD("GetMaxReachableParabolicHeight", ICmpRangeManager, GetMaxReachableParabolicHeight)
|
||||
DEFINE_INTERFACE_METHOD("GetElevationAdaptedRange", ICmpRangeManager, GetElevationAdaptedRange)
|
||||
DEFINE_INTERFACE_METHOD("ActivateScriptedVisibility", ICmpRangeManager, ActivateScriptedVisibility)
|
||||
DEFINE_INTERFACE_METHOD("GetLosVisibility", ICmpRangeManager, GetLosVisibility_wrapper)
|
||||
|
||||
@@ -195,6 +195,18 @@ public:
|
||||
*/
|
||||
virtual entity_pos_t GetEffectiveParabolicRange(entity_id_t source, entity_id_t target, entity_pos_t range, entity_pos_t yOrigin) const = 0;
|
||||
|
||||
/**
|
||||
* Get the max height (relative to the source) a parabolic projectile can reach
|
||||
* at a given horizontal distance.
|
||||
* @param source the entity at the origin.
|
||||
* @param range the maximum parabolic range on flat terrain.
|
||||
* @param yOrigin height bonus for the source.
|
||||
* @param horizDistance the horizontal distance to check.
|
||||
* @return the maximum reachable height difference (target height - source height),
|
||||
* or a very negative value if the horizontal distance exceeds the range.
|
||||
*/
|
||||
virtual entity_pos_t GetMaxReachableParabolicHeight(entity_pos_t range, entity_pos_t yOrigin, entity_pos_t horizDistance) const = 0;
|
||||
|
||||
/**
|
||||
* Get the average elevation over 8 points on distance range around the entity
|
||||
* @param id the entity id to look around
|
||||
|
||||
Reference in New Issue
Block a user