Index: binaries/data/mods/public/globalscripts/Templates.js =================================================================== --- binaries/data/mods/public/globalscripts/Templates.js +++ binaries/data/mods/public/globalscripts/Templates.js @@ -220,11 +220,11 @@ }, "minRange": getAttackStat("MinRange"), "maxRange": getAttackStat("MaxRange"), - "elevationBonus": getAttackStat("ElevationBonus") + "yOrigin": getAttackStat("Origin/Y") }; ret.attack[type].elevationAdaptedRange = Math.sqrt(ret.attack[type].maxRange * - (2 * ret.attack[type].elevationBonus + ret.attack[type].maxRange)); + (2 * ret.attack[type].yOrigin + ret.attack[type].maxRange)); ret.attack[type].repeatTime = getAttackStat("RepeatTime"); if (template.Attack[type].Projectile) Index: binaries/data/mods/public/gui/session/input.js =================================================================== --- binaries/data/mods/public/gui/session/input.js +++ binaries/data/mods/public/gui/session/input.js @@ -180,14 +180,14 @@ if (placementSupport.attack && placementSupport.attack.Ranged) { - let cmd = { + const cmd = { "x": placementSupport.position.x, "z": placementSupport.position.z, "range": placementSupport.attack.Ranged.maxRange, - "elevationBonus": placementSupport.attack.Ranged.elevationBonus + "origin": placementSupport.attack.Ranged.yOrigin }; - let averageRange = Math.round(Engine.GuiInterfaceCall("GetAverageRangeForBuildings", cmd) - cmd.range); - let range = Math.round(cmd.range); + const averageRange = Math.round(Engine.GuiInterfaceCall("GetAverageRangeForBuildings", cmd) - cmd.range); + const range = Math.round(cmd.range); placementSupport.tooltipMessage = sprintf(translatePlural("Basic range: %(range)s meter", "Basic range: %(range)s meters", range), { "range": range }) + "\n" + sprintf(translatePlural("Average bonus range: %(range)s meter", "Average bonus range: %(range)s meters", averageRange), { "range": averageRange }); } 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 @@ -57,10 +57,14 @@ "" + "44.0" + "20.0" + - "15.0" + + "" + + "0" + + "10.0" + + "0" + + "" + "800" + "1600" + - "1000" + + "1000" + "" + "" + "Cavalry" + @@ -115,7 +119,19 @@ "" + "" + ""+ - "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + "" + "" + "" + @@ -135,7 +151,7 @@ "" + "" + "" + - "" + + "" + "" + "" + "" + @@ -438,10 +454,14 @@ let min = +(this.template[type].MinRange || 0); min = ApplyValueModificationsToEntity("Attack/" + type + "/MinRange", min, this.entity); - let elevationBonus = +(this.template[type].ElevationBonus || 0); - elevationBonus = ApplyValueModificationsToEntity("Attack/" + type + "/ElevationBonus", elevationBonus, this.entity); + return { "max": max, "min": min }; +}; - return { "max": max, "min": min, "elevationBonus": elevationBonus }; +Attack.prototype.GetAttackYOrigin = function(type) +{ + if (!this.template[type].Origin) + return 0; + return ApplyValueModificationsToEntity("Attack/" + type + "/Origin/Y", +this.template[type].Origin.Y, this.entity); }; /** @@ -604,7 +624,7 @@ "target": target, }; - let delay = +(this.template[type].Delay || 0); + let delay = +(this.template[type].EffectDelay || 0); if (this.template[type].Projectile) { @@ -733,7 +753,7 @@ let positionSelf = cmpPositionSelf.GetPosition(); let positionTarget = cmpPositionTarget.GetPosition(); - let heightDifference = positionSelf.y + range.elevationBonus - positionTarget.y; + 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) 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 @@ -123,10 +123,11 @@ if (!enemies.length) return; - var range = cmpAttack.GetRange(attackType); + const range = cmpAttack.GetRange(attackType); + const yOriginAttack = cmpAttack.GetAttackYOrigin(attackType); // This takes entity sizes into accounts, so no need to compensate for structure size. this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( - this.entity, range.min, range.max, range.elevationBonus, + this.entity, range.min, range.max, yOriginAttack, enemies, IID_Resistance, cmpRangeManager.GetEntityFlagMask("normal")); cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery); @@ -151,12 +152,13 @@ if (!cmpPlayer || !cmpPlayer.IsEnemy(0)) return; - var range = cmpAttack.GetRange(attackType); + const range = cmpAttack.GetRange(attackType); + const yOriginAttack = cmpAttack.GetAttackYOrigin(attackType); // This query is only interested in Gaia entities that can attack. // This takes entity sizes into accounts, so no need to compensate for structure size. this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( - this.entity, range.min, range.max, range.elevationBonus, + this.entity, range.min, range.max, yOriginAttack, [0], IID_Attack, cmpRangeManager.GetEntityFlagMask("normal")); cmpRangeManager.EnableActiveQuery(this.gaiaUnitsQuery); @@ -331,12 +333,13 @@ // 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 range = cmpAttack.GetRange(attackType); + const range = cmpAttack.GetRange(attackType); + const yOriginAttack = cmpAttack.GetAttackYOrigin(attackType); let thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position); if (!thisCmpPosition.IsInWorld()) return; - let s = thisCmpPosition.GetPosition(); + const y = thisCmpPosition.GetPosition().y + yOriginAttack; let firedArrows = 0; while (firedArrows < arrowsToFire && targets.length()) @@ -348,7 +351,7 @@ { // 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; + const h = y - targetCmpPosition.GetPosition().y; if (h > -range.max / 2 && cmpObstructionManager.IsInTargetRange( this.entity, selectedTarget, Index: binaries/data/mods/public/simulation/components/GuiInterface.js =================================================================== --- binaries/data/mods/public/simulation/components/GuiInterface.js +++ binaries/data/mods/public/simulation/components/GuiInterface.js @@ -474,6 +474,7 @@ let range = cmpAttack.GetRange(type); ret.attack[type].minRange = range.min; ret.attack[type].maxRange = range.max; + ret.attack[type].yOrigin = cmpAttack.GetAttackYOrigin(type); let timers = cmpAttack.GetTimers(type); ret.attack[type].prepareTime = timers.prepare; @@ -481,18 +482,14 @@ if (type != "Ranged") { - // Not a ranged attack, set some defaults. - ret.attack[type].elevationBonus = 0; ret.attack[type].elevationAdaptedRange = ret.attack.maxRange; continue; } - ret.attack[type].elevationBonus = range.elevationBonus; - if (cmpPosition && cmpPosition.IsInWorld()) // For units, take the range in front of it, no spread, so angle = 0, // else, take the average elevation around it: angle = 2 * pi. - ret.attack[type].elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(cmpPosition.GetPosition(), cmpPosition.GetRotation(), range.max, range.elevationBonus, cmpUnitAI ? 0 : 2 * Math.PI); + ret.attack[type].elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(cmpPosition.GetPosition(), cmpPosition.GetRotation(), range.max, ret.attack[type].yOrigin, cmpUnitAI ? 0 : 2 * Math.PI); else // Not in world, set a default? ret.attack[type].elevationAdaptedRange = ret.attack.maxRange; @@ -616,10 +613,10 @@ "z": cmd.z }; - let elevationBonus = cmd.elevationBonus || 0; + const yOrigin = cmd.yOrigin || 0; let range = cmd.range; - return cmpRangeManager.GetElevationAdaptedRange(pos, rot, range, elevationBonus, 2 * Math.PI); + return cmpRangeManager.GetElevationAdaptedRange(pos, rot, range, yOrigin, 2 * Math.PI); }; GuiInterface.prototype.GetTemplateData = function(player, data) 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 @@ -4728,7 +4728,7 @@ // 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; + const h = s.y - t.y + Engine.QueryInterface(this.entity, IID_Attack).GetAttackYOrigin(type); let parabolicMaxRange = Math.sqrt(Math.square(range.max) + 2 * range.max * h); // No negative roots please @@ -6215,7 +6215,6 @@ * @param {string} type - [Optional] * @return {Object | undefined} - The range in the form * { "min": number, "max": number } - * Object."elevationBonus": number may be present when iid == IID_Attack. * Returns undefined when the entity does not have the requested component. */ UnitAI.prototype.GetRange = function(iid, type, target) Index: binaries/data/mods/public/simulation/components/tests/test_Damage.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Damage.js +++ binaries/data/mods/public/simulation/components/tests/test_Damage.js @@ -44,7 +44,7 @@ }, "MaxRange": 50, "MinRange": 0, - "Delay": 0, + "EffectDelay": 0, "Projectile": { "Speed": 75.0, "Spread": 0.5, Index: binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_artillery.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_artillery.xml +++ binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_artillery.xml @@ -9,7 +9,11 @@ 80 40 - 15 + + 0 + 15 + 0 + 4500 5000 Index: binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_bolt.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_bolt.xml +++ binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_bolt.xml @@ -8,7 +8,11 @@ 90 30 - 15 + + 0 + 15 + 0 + 500 4000 Index: binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_sentry.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_sentry.xml +++ binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_sentry.xml @@ -2,7 +2,11 @@ - 9 + + 0 + 9 + 0 + Index: binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_stone.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_stone.xml +++ binaries/data/mods/public/simulation/templates/template_structure_defensive_tower_stone.xml @@ -2,7 +2,11 @@ - 15 + + 0 + 15 + 0 + Index: binaries/data/mods/public/simulation/templates/template_unit_siege_tower.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_unit_siege_tower.xml +++ binaries/data/mods/public/simulation/templates/template_unit_siege_tower.xml @@ -9,7 +9,11 @@ 55 10 - 10 + + 0 + 10 + 0 + 1200 2000