Index: binaries/data/mods/public/simulation/components/Attack.js =================================================================== --- binaries/data/mods/public/simulation/components/Attack.js +++ binaries/data/mods/public/simulation/components/Attack.js @@ -739,28 +739,14 @@ */ Attack.prototype.IsTargetInRange = function(target, type) { - let range = this.GetRange(type); - if (type == "Ranged") - { - let cmpPositionTarget = Engine.QueryInterface(target, IID_Position); - if (!cmpPositionTarget || !cmpPositionTarget.IsInWorld()) - return false; - - let cmpPositionSelf = Engine.QueryInterface(this.entity, IID_Position); - if (!cmpPositionSelf || !cmpPositionSelf.IsInWorld()) - return false; - - let positionSelf = cmpPositionSelf.GetPosition(); - let positionTarget = cmpPositionTarget.GetPosition(); - - const heightDifference = positionSelf.y + this.GetAttackYOrigin(type) - positionTarget.y; - range.max = Math.sqrt(Math.square(range.max) + 2 * range.max * heightDifference); - - if (range.max < 0) - return false; - } - let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); - return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, false); + const range = this.GetRange(type); + return Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager).IsInTargetParabolicRange( + this.entity, + target, + range.min, + range.max, + range.elevationBonus, + false); }; Attack.prototype.OnValueModification = function(msg) Index: binaries/data/mods/public/simulation/components/BuildingAI.js =================================================================== --- binaries/data/mods/public/simulation/components/BuildingAI.js +++ binaries/data/mods/public/simulation/components/BuildingAI.js @@ -332,36 +332,25 @@ // The obstruction manager performs approximate range checks. // so we need to verify them here. // TODO: perhaps an optional 'precise' mode to range queries would be more performant. - let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); + const cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); const range = cmpAttack.GetRange(attackType); - let thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position); - if (!thisCmpPosition.IsInWorld()) - return; - const y = thisCmpPosition.GetPosition().y + cmpAttack.GetAttackYOrigin(attackType); - let firedArrows = 0; while (firedArrows < arrowsToFire && targets.length()) { - let selectedTarget = targets.randomItem(); - - let targetCmpPosition = Engine.QueryInterface(selectedTarget, IID_Position); - if (targetCmpPosition && targetCmpPosition.IsInWorld() && this.CheckTargetVisible(selectedTarget)) + const selectedTarget = targets.randomItem(); + if (this.CheckTargetVisible(selectedTarget) && cmpObstructionManager.IsInTargetParabolicRange( + this.entity, + selectedTarget, + range.min, + range.max, + range.elevationBonus, + false)) { - // Parabolic range compuation is the same as in UnitAI's MoveToTargetAttackRange. - // h is positive when I'm higher than the target. - const h = y - targetCmpPosition.GetPosition().y; - if (h > -range.max / 2 && cmpObstructionManager.IsInTargetRange( - this.entity, - selectedTarget, - range.min, - Math.sqrt(Math.square(range.max) + 2 * range.max * h), false)) - { - cmpAttack.PerformAttack(attackType, selectedTarget); - PlaySound("attack_" + attackType.toLowerCase(), this.entity); - ++firedArrows; - continue; - } + cmpAttack.PerformAttack(attackType, selectedTarget); + PlaySound("attack_" + attackType.toLowerCase(), this.entity); + ++firedArrows; + continue; } // Could not attack target, try a different target. Index: binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/UnitAI.js +++ binaries/data/mods/public/simulation/components/UnitAI.js @@ -4698,11 +4698,11 @@ return false; } - let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); + const cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); if (!this.AbleToMove(cmpUnitMotion)) return false; - let cmpFormation = Engine.QueryInterface(target, IID_Formation); + const cmpFormation = Engine.QueryInterface(target, IID_Formation); if (cmpFormation) target = cmpFormation.GetClosestMember(this.entity); @@ -4717,28 +4717,11 @@ return false; const range = cmpAttack.GetRange(type); - let thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position); - if (!thisCmpPosition.IsInWorld()) - return false; - let s = thisCmpPosition.GetPosition(); - - let targetCmpPosition = Engine.QueryInterface(target, IID_Position); - if (!targetCmpPosition || !targetCmpPosition.IsInWorld()) - return false; - - // Parabolic range compuation is the same as in BuildingAI's FireArrows. - let t = targetCmpPosition.GetPosition(); - // h is positive when I'm higher than the target - const h = s.y - t.y + cmpAttack.GetAttackYOrigin(type); - - let parabolicMaxRange = Math.sqrt(Math.square(range.max) + 2 * range.max * h); - // No negative roots please - if (h <= -range.max / 2) - // return false? Or hope you come close enough? - parabolicMaxRange = 0; + // In case the range returns negative, we are probably too high compared to the target. Hope we come close enough. + const parabolicMaxRange = Math.max(0, Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).GetEffectiveParabolicRange(this.entity, target, range.max, range.elevationBonus)); // The parabole changes while walking so be cautious: - let guessedMaxRange = parabolicMaxRange > range.max ? (range.max + parabolicMaxRange) / 2 : parabolicMaxRange; + const guessedMaxRange = parabolicMaxRange > range.max ? (range.max + parabolicMaxRange) / 2 : parabolicMaxRange; return cmpUnitMotion && cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange); }; Index: source/simulation2/components/CCmpObstructionManager.cpp =================================================================== --- source/simulation2/components/CCmpObstructionManager.cpp +++ source/simulation2/components/CCmpObstructionManager.cpp @@ -21,6 +21,7 @@ #include "ICmpObstructionManager.h" #include "ICmpPosition.h" +#include "ICmpRangeManager.h" #include "simulation2/MessageTypes.h" #include "simulation2/helpers/Geometry.h" @@ -475,6 +476,7 @@ virtual bool IsInPointRange(entity_id_t ent, entity_pos_t px, entity_pos_t pz, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const; virtual bool IsInTargetRange(entity_id_t ent, entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const; + virtual bool IsInTargetParabolicRange(entity_id_t ent, entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus, bool opposite) const; virtual bool IsPointInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t px, entity_pos_t pz, entity_pos_t minRange, entity_pos_t maxRange) const; virtual bool AreShapesInRange(const ObstructionSquare& source, const ObstructionSquare& target, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const; @@ -816,6 +818,7 @@ * to set the opposite bool false and use the edge to egde distance. * * We don't use squares because the are likely to overflow. + * TODO Avoid the overflows and use squares instead. * We use a 0.0001 margin to avoid rounding errors. */ bool CCmpObstructionManager::IsInPointRange(entity_id_t ent, entity_pos_t px, entity_pos_t pz, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const @@ -835,6 +838,21 @@ (dist <= (maxRange + fixed::FromFloat(0.0001f)) || maxRange < fixed::Zero()) && (opposite ? MaxDistanceToTarget(ent, target) : dist) >= minRange - fixed::FromFloat(0.0001f); } + +bool CCmpObstructionManager::IsInTargetParabolicRange(entity_id_t ent, entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus, bool opposite) const +{ + // If the maxRange is finite adapt it to the parabolic query. + if (maxRange >= fixed::Zero()) + { + CmpPtr cmpRangeManager(GetSystemEntity()); + maxRange = cmpRangeManager->GetEffectiveParabolicRange(ent, target, maxRange, elevationBonus); + // GetEffectiveParabolicRange returns -1 if we will never be in range. + if (maxRange == entity_pos_t::FromInt(-1)) + return false; + } + return IsInTargetRange(ent, target, minRange, maxRange, opposite); +} + bool CCmpObstructionManager::IsPointInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t px, entity_pos_t pz, entity_pos_t minRange, entity_pos_t maxRange) const { entity_pos_t distance = (CFixedVector2D(x, z) - CFixedVector2D(px, pz)).Length(); Index: source/simulation2/components/CCmpRangeManager.cpp =================================================================== --- source/simulation2/components/CCmpRangeManager.cpp +++ source/simulation2/components/CCmpRangeManager.cpp @@ -1279,6 +1279,25 @@ } } + virtual entity_pos_t GetEffectiveParabolicRange(entity_id_t source, entity_id_t target, entity_pos_t range, entity_pos_t elevationBonus) const + { + CmpPtr cmpSourcePosition(GetSimContext(), source); + if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld()) + return entity_pos_t::FromInt(-1); + + CmpPtr cmpTargetPosition(GetSimContext(), target); + if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld()) + return entity_pos_t::FromInt(-1); + + entity_pos_t h = cmpSourcePosition->GetHeightOffset() - cmpTargetPosition->GetHeightOffset() + elevationBonus; + if (h < -range / 2) + return entity_pos_t::FromInt(-1); + + entity_pos_t r; + r.SetInternalValue(static_cast(isqrt64(SQUARE_U64_FIXED(range) + static_cast(h.GetInternalValue()) * static_cast(range.GetInternalValue()) * 2))); + return r; + } + virtual entity_pos_t GetElevationAdaptedRange(const CFixedVector3D& pos1, const CFixedVector3D& rot, entity_pos_t range, entity_pos_t elevationBonus, entity_pos_t angle) const { entity_pos_t r = entity_pos_t::Zero(); Index: source/simulation2/components/ICmpObstructionManager.h =================================================================== --- source/simulation2/components/ICmpObstructionManager.h +++ source/simulation2/components/ICmpObstructionManager.h @@ -219,6 +219,12 @@ virtual bool IsInTargetRange(entity_id_t ent, entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const = 0; /** + * Check if the given entity is in parabolic range of the target given those parameters. + * @param maxRange - if -1, treated as infinite. + */ + virtual bool IsInTargetParabolicRange(entity_id_t ent, entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus, bool opposite) const = 0; + + /** * Check if the given point is in range of the other point given those parameters. * @param maxRange - if -1, treated as infinite. */ Index: source/simulation2/components/ICmpObstructionManager.cpp =================================================================== --- source/simulation2/components/ICmpObstructionManager.cpp +++ source/simulation2/components/ICmpObstructionManager.cpp @@ -30,5 +30,6 @@ DEFINE_INTERFACE_METHOD("MaxDistanceToTarget", ICmpObstructionManager, MaxDistanceToTarget) DEFINE_INTERFACE_METHOD("IsInPointRange", ICmpObstructionManager, IsInPointRange) DEFINE_INTERFACE_METHOD("IsInTargetRange", ICmpObstructionManager, IsInTargetRange) +DEFINE_INTERFACE_METHOD("IsInTargetParabolicRange", ICmpObstructionManager, IsInTargetParabolicRange) DEFINE_INTERFACE_METHOD("IsPointInPointRange", ICmpObstructionManager, IsPointInPointRange) END_INTERFACE_WRAPPER(ObstructionManager) Index: source/simulation2/components/ICmpRangeManager.h =================================================================== --- source/simulation2/components/ICmpRangeManager.h +++ source/simulation2/components/ICmpRangeManager.h @@ -169,6 +169,16 @@ /** + * Get the effective range in a parablic range query. + * @param source The entity id at the origin of the query. + * @param target A target entity id. + * @param range The distance to compare terrain height with. + * @param elevationBonus Height the source gains over the target by default. + * @return a fixed number representing the effective range correcting parabolicly for the height difference. Returns -1 when the target is too high compared to the source to be in range. + */ + virtual entity_pos_t GetEffectiveParabolicRange(entity_id_t source, entity_id_t target, entity_pos_t range, entity_pos_t elevationBonus) const = 0; + + /** * Get the average elevation over 8 points on distance range around the entity * @param id the entity id to look around * @param range the distance to compare terrain height with Index: source/simulation2/components/ICmpRangeManager.cpp =================================================================== --- source/simulation2/components/ICmpRangeManager.cpp +++ source/simulation2/components/ICmpRangeManager.cpp @@ -64,6 +64,7 @@ DEFINE_INTERFACE_METHOD("ExploreTerritories", ICmpRangeManager, ExploreTerritories) DEFINE_INTERFACE_METHOD("SetLosRevealAll", ICmpRangeManager, SetLosRevealAll) DEFINE_INTERFACE_METHOD("GetLosRevealAll", ICmpRangeManager, GetLosRevealAll) +DEFINE_INTERFACE_METHOD("GetEffectiveParabolicRange", ICmpRangeManager, GetEffectiveParabolicRange) DEFINE_INTERFACE_METHOD("GetElevationAdaptedRange", ICmpRangeManager, GetElevationAdaptedRange) DEFINE_INTERFACE_METHOD("ActivateScriptedVisibility", ICmpRangeManager, ActivateScriptedVisibility) DEFINE_INTERFACE_METHOD("GetLosVisibility", ICmpRangeManager, GetLosVisibility_wrapper)