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 @@ -344,30 +344,24 @@ // 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); + let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 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 selectedIndex = targets.randomIndex(); let selectedTarget = targets.itemAt(selectedIndex); - let targetCmpPosition = Engine.QueryInterface(selectedTarget, IID_Position); - if (targetCmpPosition && targetCmpPosition.IsInWorld() && this.CheckTargetVisible(selectedTarget)) + if (this.CheckTargetVisible(selectedTarget)) { - // 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( + let parabolicRange = cmpRangeManager.GetEffectiveParabolicRange(this.entity, selectedTarget, range.max, range.elevationBonus); + if (parabolicRange >= 0 && cmpObstructionManager.IsInTargetRange( this.entity, selectedTarget, range.min, - Math.sqrt(Math.square(range.max) + 2 * range.max * h), false)) + parabolicRange, + false)) { cmpAttack.PerformAttack(attackType, selectedTarget); PlaySound("attack_" + attackType.toLowerCase(), this.entity); 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 @@ -4614,25 +4614,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. + let 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; @@ -4757,22 +4740,12 @@ if (!range) return false; - let thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position); - if (!thisCmpPosition.IsInWorld()) - return false; - - let s = thisCmpPosition.GetPosition(); - - let t = targetCmpPosition.GetPosition(); - - let h = s.y - t.y + range.elevationBonus; - let maxRange = Math.sqrt(Math.square(range.max) + 2 * range.max * h); - - if (maxRange < 0) + let parabolicMaxRange = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).GetEffectiveParabolicRange(this.entity, target, range.max, range.elevationBonus); + if (parabolicMaxRange < 0) return false; let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); - return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, maxRange, false); + return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, parabolicMaxRange, false); }; UnitAI.prototype.CheckTargetRangeExplicit = function(target, min, max) Index: source/simulation2/components/CCmpRangeManager.cpp =================================================================== --- source/simulation2/components/CCmpRangeManager.cpp +++ source/simulation2/components/CCmpRangeManager.cpp @@ -1259,6 +1259,23 @@ } } + 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); + + return (range.Square() + range.Multiply(h).Multiply(entity_pos_t::FromInt(2))).Sqrt(); + } + 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/ICmpRangeManager.h =================================================================== --- source/simulation2/components/ICmpRangeManager.h +++ source/simulation2/components/ICmpRangeManager.h @@ -165,6 +165,15 @@ /** + * 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. + */ + 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_0("ExploreTerritories", void, ICmpRangeManager, ExploreTerritories) DEFINE_INTERFACE_METHOD_2("SetLosRevealAll", void, ICmpRangeManager, SetLosRevealAll, player_id_t, bool) DEFINE_INTERFACE_METHOD_CONST_1("GetLosRevealAll", bool, ICmpRangeManager, GetLosRevealAll, player_id_t) +DEFINE_INTERFACE_METHOD_CONST_4("GetEffectiveParabolicRange", entity_pos_t, ICmpRangeManager, GetEffectiveParabolicRange, entity_id_t, entity_id_t, entity_pos_t, entity_pos_t) DEFINE_INTERFACE_METHOD_CONST_5("GetElevationAdaptedRange", entity_pos_t, ICmpRangeManager, GetElevationAdaptedRange, CFixedVector3D, CFixedVector3D, entity_pos_t, entity_pos_t, entity_pos_t) DEFINE_INTERFACE_METHOD_2("ActivateScriptedVisibility", void, ICmpRangeManager, ActivateScriptedVisibility, entity_id_t, bool) DEFINE_INTERFACE_METHOD_CONST_2("GetLosVisibility", std::string, ICmpRangeManager, GetLosVisibility_wrapper, entity_id_t, player_id_t)