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 @@ -719,28 +719,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(); - - let heightDifference = positionSelf.y + range.elevationBonus - 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 @@ -333,33 +333,22 @@ let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); let range = cmpAttack.GetRange(attackType); - let thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position); - if (!thisCmpPosition.IsInWorld()) - return; - let s = thisCmpPosition.GetPosition(); - 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)) + 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. - let h = s.y - targetCmpPosition.GetPosition().y + range.elevationBonus; - 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 @@ -4716,25 +4716,8 @@ if (!range) return false; - 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 - let h = s.y - t.y + range.elevationBonus; - - 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; 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 < fixed::Zero()) + 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 considering the parbolic effects of the height difference. Returns is negative 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)