Index: binaries/data/mods/public/globalscripts/Templates.js =================================================================== --- binaries/data/mods/public/globalscripts/Templates.js +++ binaries/data/mods/public/globalscripts/Templates.js @@ -187,7 +187,8 @@ ret.attack[type] = { "minRange": getAttackStat("MinRange"), "maxRange": getAttackStat("MaxRange"), - "elevationBonus": getAttackStat("ElevationBonus") + "elevationBonus": getAttackStat("ElevationBonus"), + "spread": getAttackStat("Spread") }; for (let damageType of damageTypes.GetTypes()) ret.attack[type][damageType] = getAttackStat(damageType); Index: binaries/data/mods/public/gui/common/tooltips.js =================================================================== --- binaries/data/mods/public/gui/common/tooltips.js +++ binaries/data/mods/public/gui/common/tooltips.js @@ -24,15 +24,15 @@ var g_RangeTooltipString = { "relative": { // Translation: For example: Ranged Attack: 12.0 Pierce, Range: 2 to 10 (+2) meters, Interval: 3 arrows / 2 seconds - "minRange": translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(minRange)s to %(maxRange)s (%(relativeRange)s) %(rangeUnit)s, %(rate)s"), + "minRange": translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(minRange)s to %(maxRange)s (%(relativeRange)s) %(rangeUnit)s, %(rate)s, %(spread)s"), // Translation: For example: Ranged Attack: 12.0 Pierce, Range: 10 (+2) meters, Interval: 3 arrows / 2 seconds - "no-minRange": translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(maxRange)s (%(relativeRange)s) %(rangeUnit)s, %(rate)s"), + "no-minRange": translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(maxRange)s (%(relativeRange)s) %(rangeUnit)s, %(rate)s, %(spread)s"), }, "non-relative": { // Translation: For example: Ranged Attack: 12.0 Pierce, Range: 2 to 10 meters, Interval: 3 arrows / 2 seconds - "minRange": translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(minRange)s to %(maxRange)s %(rangeUnit)s, %(rate)s"), + "minRange": translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(minRange)s to %(maxRange)s %(rangeUnit)s, %(rate)s, %(spread)s"), // Translation: For example: Ranged Attack: 12.0 Pierce, Range: 10 meters, Interval: 3 arrows / 2 seconds - "no-minRange": translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(maxRange)s %(rangeUnit)s, %(rate)s"), + "no-minRange": translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(maxRange)s %(rangeUnit)s, %(rate)s, %(spread)s"), } }; @@ -282,8 +282,9 @@ let minRange = Math.round(template.attack[type].minRange); let maxRange = Math.round(template.attack[type].maxRange); - let realRange = template.attack[type].elevationAdaptedRange; + let realRange = Math.round(template.attack[type].elevationAdaptedRange); let relativeRange = realRange ? Math.round(realRange - maxRange) : 0; + let spread = +(+template.attack[type].spread * realRange / 100).toFixed(1); tooltips.push(sprintf(g_RangeTooltipString[relativeRange ? "relative" : "non-relative"][minRange ? "minRange" : "no-minRange"], { "attackLabel": attackLabel, @@ -298,6 +299,13 @@ translate("meters") : translatePlural("meter", "meters", maxRange)), "rate": rate, + "spread": sprintf(translate("%(label)s %(val)s %(unitSpread)s at %(range)s %(unitRange)s"), { + "label": headerFont(translate("Spread Hit Radius:")), + "val": spread, + "unitSpread": unitFont(translatePlural("meter", "meters", spread)), + "range": realRange, + "unitRange": unitFont(translatePlural("meter", "meters", realRange)) + }) })); } return tooltips.join("\n"); 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 @@ -440,6 +440,14 @@ return ret; }; +Attack.prototype.GetSpread = function(type) +{ + if (type != "Ranged" || !this.template[type]) + return 0; + + return ApplyValueModificationsToEntity("Attack/" + type + "/Spread", +this.template[type].Spread, this.entity); +} + Attack.prototype.GetSplashDamage = function(type) { if (!this.template[type].Splash) @@ -513,8 +521,7 @@ let predictedPosition = (timeToTarget !== false) ? Vector3D.mult(targetVelocity, timeToTarget).add(targetPosition) : targetPosition; // Add inaccuracy based on spread. - let distanceModifiedSpread = ApplyValueModificationsToEntity("Attack/Ranged/Spread", +this.template.Ranged.Spread, this.entity) * - predictedPosition.horizDistanceTo(selfPosition) / 100; + let distanceModifiedSpread = this.GetSpread(type) * predictedPosition.horizDistanceTo(selfPosition) / 100; let randNorm = randomNormal2D(); let offsetX = randNorm[0] * distanceModifiedSpread; 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 @@ -389,6 +389,7 @@ for (let type of types) { ret.attack[type] = cmpAttack.GetAttackStrengths(type); + ret.attack[type].spread = cmpAttack.GetSpread(type); ret.attack[type].splash = cmpAttack.GetSplashDamage(type); let range = cmpAttack.GetRange(type);