Index: binaries/data/mods/public/globalscripts/Templates.js
===================================================================
--- binaries/data/mods/public/globalscripts/Templates.js
+++ binaries/data/mods/public/globalscripts/Templates.js
@@ -386,6 +386,17 @@
"time": getEntityValue("Pack/Time"),
};
+ if (template.ProximityAttack)
+ {
+ ret.proximityAttack = {
+ "friendlyFire": template.ProximityAttack.FriendlyFire != "false",
+ "minRange": template.ProximityAttack.MinRange,
+ "maxRange": template.ProximityAttack.MaxRange,
+ "rate": template.ProximityAttack.RepeatTime
+ };
+ Object.assign(ret.proximityAttack, getAttackEffects(template.ProximityAttack, "ProximityAttack"));
+ }
+
if (template.Health)
ret.health = Math.round(getEntityValue("Health/Max"));
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
@@ -328,6 +328,39 @@
return tooltips.join("\n");
}
+function getProximityAttackTooltip(template)
+{
+// Can very well be improved (after D2138).
+ if (!template.proximityAttack)
+ return "";
+
+ let proximityAttackTooltip;
+ if (template.proximityAttack.minRange > 0)
+ proximityAttackTooltip = sprintf(translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(minRange)s to %(maxRange)s %(rangeUnit)s"), {
+ "attackLabel": headerFont("Proximity Attack:"),
+ "damageTypes": damageTypesToText(template.proximityAttack.strengths.Damage),
+ "rangeLabel": headerFont(translate("Range:")),
+ "minRange": template.proximityAttack.minRange,
+ "maxRange": template.proximityAttack.maxRange,
+ "rangeUnit": unitFont(translatePlural("meter", "meters", +template.proximityAttack.maxRange))
+ });
+ else
+ proximityAttackTooltip = sprintf(translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(maxRange)s %(rangeUnit)s"), {
+ "attackLabel": headerFont("Proximity Attack:"),
+ "damageTypes": damageTypesToText(template.proximityAttack.strengths.Damage),
+ "rangeLabel": headerFont(translate("Radius:")),
+ "maxRange": template.proximityAttack.maxRange,
+ "rangeUnit": unitFont(translatePlural("meter", "meters", +template.proximityAttack.maxRange))
+ });
+
+ if (g_AlwaysDisplayFriendlyFire || template.proximityAttack.friendlyFire)
+ proximityAttackTooltip += commaFont(translate(", ")) + sprintf(translate("Friendly Fire: %(enabled)s"), {
+ "enabled": template.proximityAttack.friendlyFire ? translate("Yes") : translate("No")
+ });
+
+ return proximityAttackTooltip;
+}
+
function getGarrisonTooltip(template)
{
if (!template.garrisonHolder)
Index: binaries/data/mods/public/gui/reference/common/draw.js
===================================================================
--- binaries/data/mods/public/gui/reference/common/draw.js
+++ binaries/data/mods/public/gui/reference/common/draw.js
@@ -14,6 +14,7 @@
getHealerTooltip,
getAttackTooltip,
getSplashDamageTooltip,
+ getProximityAttackTooltip,
getArmorTooltip,
getGarrisonTooltip,
getProjectilesTooltip,
Index: binaries/data/mods/public/gui/session/selection_details.js
===================================================================
--- binaries/data/mods/public/gui/session/selection_details.js
+++ binaries/data/mods/public/gui/session/selection_details.js
@@ -286,6 +286,7 @@
Engine.GetGUIObjectByName("attackAndArmorStats").tooltip = [
getAttackTooltip,
getSplashDamageTooltip,
+ getProximityAttackTooltip,
getHealerTooltip,
getArmorTooltip,
getGatherTooltip,
Index: binaries/data/mods/public/maps/random/polar_sea_triggers.js
===================================================================
--- binaries/data/mods/public/maps/random/polar_sea_triggers.js
+++ binaries/data/mods/public/maps/random/polar_sea_triggers.js
@@ -51,7 +51,7 @@
continue;
// The returned entities are sorted by RangeManager already
- let targets = Attack.EntitiesNearPoint(attackerPos, 200, players).filter(ent => {
+ let targets = Attack.EntitiesNearPoint(attackerPos, 0, 200, players).filter(ent => {
let cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), targetClasses);
});
Index: binaries/data/mods/public/simulation/components/DeathDamage.js
===================================================================
--- binaries/data/mods/public/simulation/components/DeathDamage.js
+++ binaries/data/mods/public/simulation/components/DeathDamage.js
@@ -68,7 +68,8 @@
"attacker": this.entity,
"attackerOwner": owner,
"origin": pos,
- "radius": radius,
+ "minRange": 0,
+ "maxRange": radius,
"shape": this.template.Shape,
"playersToDamage": playersToDamage,
});
Index: binaries/data/mods/public/simulation/components/DelayedDamage.js
===================================================================
--- binaries/data/mods/public/simulation/components/DelayedDamage.js
+++ binaries/data/mods/public/simulation/components/DelayedDamage.js
@@ -44,7 +44,8 @@
"attacker": data.attacker,
"attackerOwner": data.attackerOwner,
"origin": Vector2D.from3D(data.position),
- "radius": data.splash.radius,
+ "minRange": 0,
+ "maxRange": data.splash.radius,
"shape": data.splash.shape,
"direction": data.direction,
"playersToDamage": Attacking.GetPlayersToDamage(data.attackerOwner, data.splash.friendlyFire)
@@ -69,7 +70,7 @@
// If we didn't hit the main target look for nearby units
let cmpPlayer = QueryPlayerIDInterface(data.attackerOwner);
- let ents = Attacking.EntitiesNearPoint(Vector2D.from3D(data.position), targetPosition.horizDistanceTo(data.position) * 2, cmpPlayer.GetEnemies());
+ let ents = Attacking.EntitiesNearPoint(Vector2D.from3D(data.position), 0, targetPosition.horizDistanceTo(data.position) * 2, cmpPlayer.GetEnemies());
for (let ent of ents)
{
if (!Attacking.TestCollision(ent, data.position, lateness))
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
@@ -432,6 +432,16 @@
}
}
+ let cmpProximityAttack = Engine.QueryInterface(ent, IID_ProximityAttack);
+ if (cmpProximityAttack)
+ ret.proximityAttack = {
+ "friendlyFire": cmpProximityAttack.GetFriendlyFire() != "false",
+ "strengths": cmpProximityAttack.GetAttackEffectsData(),
+ "minRange": cmpProximityAttack.GetRange().min,
+ "maxRange": cmpProximityAttack.GetRange().max,
+ "rate": cmpProximityAttack.GetTimers().repeat
+ };
+
let cmpArmour = Engine.QueryInterface(ent, IID_Resistance);
if (cmpArmour)
ret.armour = cmpArmour.GetArmourStrengths("Damage");
Index: binaries/data/mods/public/simulation/components/ProximityAttack.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/ProximityAttack.js
@@ -0,0 +1,194 @@
+function ProximityAttack() {}
+
+ProximityAttack.prototype.Schema =
+ "Whether a unit or building inflicts damage to nearby units." +
+ "" +
+ "" +
+ "0.0" +
+ "10.0" +
+ "50.0" +
+ "" +
+ "0" +
+ "10" +
+ "true" +
+ "Circular" +
+ "true" +
+ "0" +
+ "200" +
+ "1000" +
+ "" +
+ "" +
+ "Support" +
+ "2" +
+ "" +
+ "" +
+ "" +
+ Attacking.BuildAttackEffectsSchema() +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
+
+ProximityAttack.prototype.Init = function()
+{
+};
+
+ProximityAttack.prototype.Serialize = function()
+{
+ return { "ProximityAttackTimer": this.ProximityAttackTimer };
+};
+
+ProximityAttack.prototype.Deserialize = function(data)
+{
+ this.ProximityAttackTimer = data.ProximityAttackTimer;
+};
+
+/**
+ * Work out the range value with technology effects.
+ * @return {object} - The min/max range values of the proximity damage.
+ */
+ProximityAttack.prototype.GetRange = function()
+{
+ return {
+ "min": ApplyValueModificationsToEntity("ProximityAttack/MinRange", +(this.template.MinRange || 0), this.entity),
+ "max": ApplyValueModificationsToEntity("ProximityAttack/MaxRange", +this.template.MaxRange, this.entity)
+ };
+};
+
+/**
+ * Work out the timer value with technology effects.
+ * @return {object} - The prepare/repeat timers of the proximity damage.
+ */
+ProximityAttack.prototype.GetTimers = function()
+{
+ return {
+ "prepare": ApplyValueModificationsToEntity("ProximityAttack/PrepareTime", +this.template.PrepareTime, this.entity),
+ "repeat": ApplyValueModificationsToEntity("ProximityAttack/RepeatTime", +this.template.RepeatTime, this.entity)
+ };
+};
+
+ProximityAttack.prototype.GetAttackEffectsData = function()
+{
+ return Attacking.GetAttackEffectsData("ProximityAttack/", this.template, this.entity);
+};
+
+/**
+ * Returns whether friendly fire is enabled for this proximity attack.
+ * This function is needed for the GUI.
+ * @return {boolean} - Whether friendly fire is enabled for this proximity attack.
+ */
+ProximityAttack.prototype.GetFriendlyFire = function()
+{
+ return this.template.FriendlyFire;
+};
+
+ProximityAttack.prototype.CauseProximityAttack = function()
+{
+ // Return when this entity is otherworldly or garrisoned
+ let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+ if (!cmpPosition || !cmpPosition.IsInWorld())
+ return;
+
+ let owner;
+ let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+ if (cmpOwnership)
+ owner = cmpOwnership.GetOwner();
+ if (owner == INVALID_PLAYER)
+ warn("Unit causing proximity damage does not have any owner.");
+
+ // Get an array of the players which ought to be damaged.
+ let playersToDamage = Attacking.GetPlayersToDamage(owner, this.template.FriendlyFire != "false");
+
+ let radii = this.GetRange();
+
+ // Call the Damage component to find out which entities to damage and damage them.
+ Attacking.CauseDamageOverArea({
+ "type": "Proximity",
+ "attackData": this.GetAttackEffectsData(),
+ "origin": cmpPosition.GetPosition2D(),
+ "attacker": this.entity,
+ "attackerOwner": owner,
+ "minRange": radii.min,
+ "maxRange": radii.max,
+ "shape": this.template.Shape,
+ "playersToDamage": playersToDamage
+ });
+};
+
+// Start the proximity damage timer when no timer exists.
+ProximityAttack.prototype.CheckTimer = function()
+{
+ // If the unit only causes proximity damage whilst moving, disable timer when not moving.
+ if (this.template.OnlyWhenMoving != "false")
+ {
+ let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+ if (!cmpPosition || cmpPosition.IsInWorld() && !Vector3D.isEqualTo(cmpPosition.GetPreviousPosition(), cmpPosition.GetPosition()))
+ {
+ // We don't need a timer, disable if one exists.
+ if (this.ProximityAttackTimer)
+ {
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ cmpTimer.CancelTimer(this.ProximityAttackTimer);
+ delete this.ProximityAttackTimer;
+ }
+ return;
+ }
+ }
+ // We need a timer, enable if one doesn't exist.
+ if (this.ProximityAttackTimer)
+ return;
+
+ let timers = this.GetTimers();
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ this.ProximityAttackTimer = cmpTimer.SetInterval(this.entity, IID_ProximityAttack, "CauseProximityAttack", timers.prepare, timers.repeat, undefined);
+};
+
+/**
+ * Check timer when a unit changes motion.
+ * @param {{ "msg.to": string }} - The state to which the unit has changed.
+ */
+ProximityAttack.prototype.OnUnitAIStateChanged = function(msg)
+{
+ this.CheckTimer();
+};
+
+/**
+ * Check timer when a unit changes ownership.
+ * @param {{ "msg.from": number, "msg.to": number }} - From which player to which player the ownership is changed.
+ */
+ProximityAttack.prototype.OnOwnershipChanged = function(msg)
+{
+ if (msg.to == INVALID_PLAYER)
+ {
+ if (this.ProximityAttackTimer)
+ {
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ cmpTimer.CancelTimer(this.ProximityAttackTimer);
+ delete this.ProximityAttackTimer;
+ }
+ return;
+ }
+
+ this.CheckTimer();
+};
+
+Engine.RegisterComponentType(IID_ProximityAttack, "ProximityAttack", ProximityAttack);
Index: binaries/data/mods/public/simulation/components/interfaces/ProximityAttack.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/interfaces/ProximityAttack.js
@@ -0,0 +1 @@
+Engine.RegisterInterface("ProximityAttack");
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
@@ -166,7 +166,8 @@
"attacker": attacker,
"attackerOwner": attackerOwner,
"origin": origin,
- "radius": 10,
+ "minRange": 0,
+ "maxRange": 10,
"shape": "Linear",
"direction": new Vector3D(1, 747, 0),
"playersToDamage": [2],
@@ -174,7 +175,7 @@
let fallOff = function(x, y)
{
- return (1 - x * x / (data.radius * data.radius)) * (1 - 25 * y * y / (data.radius * data.radius));
+ return (1 - x * x / (data.maxRange * data.maxRange)) * (1 - 25 * y * y / (data.maxRange * data.maxRange));
};
let hitEnts = new Set();
@@ -185,14 +186,17 @@
AddMock(60, IID_Position, {
"GetPosition2D": () => new Vector2D(2.2, -0.4),
+ "IsInWorld": () => true
});
AddMock(61, IID_Position, {
"GetPosition2D": () => new Vector2D(0, 0),
+ "IsInWorld": () => true
});
AddMock(62, IID_Position, {
"GetPosition2D": () => new Vector2D(5, 2),
+ "IsInWorld": () => true
});
AddMock(60, IID_Health, {
@@ -262,23 +266,28 @@
AddMock(60, IID_Position, {
"GetPosition2D": () => new Vector2D(3, 4),
+ "IsInWorld": () => true
});
AddMock(61, IID_Position, {
"GetPosition2D": () => new Vector2D(0, 0),
+ "IsInWorld": () => true
});
AddMock(62, IID_Position, {
"GetPosition2D": () => new Vector2D(3.6, 3.2),
+ "IsInWorld": () => true
});
AddMock(63, IID_Position, {
"GetPosition2D": () => new Vector2D(10, -10),
+ "IsInWorld": () => true
});
// Target on the frontier of the shape
AddMock(64, IID_Position, {
"GetPosition2D": () => new Vector2D(9, -4),
+ "IsInWorld": () => true
});
AddMock(60, IID_Resistance, {
@@ -321,7 +330,8 @@
"attacker": 50,
"attackerOwner": 1,
"origin": new Vector2D(3, 4),
- "radius": radius,
+ "minRange": 0,
+ "maxRange": radius,
"shape": "Circular",
"playersToDamage": [2],
});
Index: binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js
+++ binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js
@@ -45,7 +45,8 @@
"attacker": deadEnt,
"attackerOwner": player,
"origin": pos,
- "radius": template.Range,
+ "minRange": 0,
+ "maxRange": template.Range,
"shape": template.Shape,
"playersToDamage": playersToDamage
};
Index: binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
+++ binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
@@ -22,6 +22,7 @@
Engine.LoadComponentScript("interfaces/Pack.js");
Engine.LoadComponentScript("interfaces/ProductionQueue.js");
Engine.LoadComponentScript("interfaces/Promotion.js");
+Engine.LoadComponentScript("interfaces/ProximityAttack.js");
Engine.LoadComponentScript("interfaces/Repairable.js");
Engine.LoadComponentScript("interfaces/ResourceDropsite.js");
Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
Index: binaries/data/mods/public/simulation/components/tests/test_ProximityAttack.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/tests/test_ProximityAttack.js
@@ -0,0 +1,106 @@
+Engine.LoadHelperScript("DamageBonus.js");
+Engine.LoadHelperScript("Attacking.js");
+Engine.LoadHelperScript("ValueModification.js");
+Engine.LoadComponentScript("interfaces/ProximityAttack.js");
+Engine.LoadComponentScript("interfaces/TechnologyManager.js");
+Engine.LoadComponentScript("interfaces/Timer.js");
+Engine.LoadComponentScript("ProximityAttack.js");
+Engine.LoadComponentScript("Timer.js");
+
+let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer");
+cmpTimer.OnUpdate({ "turnLength": 100 });
+
+let damagingEnt = 42;
+let player = 1;
+let playersToDamage = [1, 2, 3, 7];
+let pos = new Vector2D(3, 4.2);
+let pos3D = new Vector3D(3, 0, 4.2);
+
+ApplyValueModificationsToEntity = function(value, stat, ent)
+{
+ return stat;
+};
+
+let template = {
+ "Damage": {
+ "Pierce": 10.0,
+ "Crush": 50.0,
+ },
+ "MinRange": 4,
+ "MaxRange": 9,
+ "PrepareTime": 0,
+ "RepeatTime": 100,
+ "Shape": "Circular",
+ "FriendlyFire": true,
+ "OnlyWhenMoving": false
+};
+
+let effects = {
+ "Damage": {
+ "Pierce": 10.0,
+ "Crush": 50.0,
+ },
+};
+
+let cmpProximityAttack = ConstructComponent(damagingEnt, "ProximityAttack", template);
+
+AddMock(damagingEnt, IID_Position, {
+ "GetPosition": () => pos3D,
+ "GetPreviousPosition": () => pos3D,
+ "GetPosition2D": () => pos,
+ "IsInWorld": () => true
+});
+
+AddMock(damagingEnt, IID_Ownership, {
+ "GetOwner": () => player
+});
+
+let result = {
+ "type": "Proximity",
+ "attackData": effects,
+ "origin": pos,
+ "attacker": damagingEnt,
+ "attackerOwner": player,
+ "minRange": template.MinRange,
+ "maxRange": template.MaxRange,
+ "shape": template.Shape,
+ "playersToDamage": playersToDamage
+};
+
+Attacking.CauseDamageOverArea = data => TS_ASSERT_UNEVAL_EQUALS(data, result);
+Attacking.GetPlayersToDamage = (owner, friendlyFire) => playersToDamage;
+
+TS_ASSERT_UNEVAL_EQUALS(cmpProximityAttack.GetAttackEffectsData().Damage, template.Damage);
+cmpProximityAttack.CauseProximityAttack();
+
+// Test timer.
+/*
+// Timer needed because we do not need to move to cause damage.
+cmpProximityAttack.CheckTimer();
+TS_ASSERT(cmpProximityAttack.ProximityAttackTimer != null);
+
+// Timer not needed because we need to move to cause damage.
+template.OnlyWhenMoving = true;
+cmpProximityAttack = ConstructComponent(damagingEnt, "ProximityAttack", template);
+cmpProximityAttack.CheckTimer();
+TS_ASSERT(cmpProximityAttack.ProximityAttackTimer == null);
+
+// Timer needed because we need to move to cause damage.
+let newPos3D = new Vector3D(3, 0, 4.3);
+AddMock(damagingEnt, IID_Position, {
+ "GetPosition": () => pos3D,
+ "GetPreviousPosition": () => newPos3D,
+ "IsInWorld": () => true
+});
+cmpProximityAttack.CheckTimer();
+TS_ASSERT(cmpProximityAttack.ProximityAttackTimer != null);
+
+// Timer not needed because we stopped moving.
+AddMock(damagingEnt, IID_Position, {
+ "GetPosition": () => pos3D,
+ "GetPreviousPosition": () => pos3D,
+ "IsInWorld": () => true
+});
+cmpProximityAttack.CheckTimer();
+TS_ASSERT(cmpProximityAttack.ProximityAttackTimer == null);
+*/
Index: binaries/data/mods/public/simulation/helpers/Attacking.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Attacking.js
+++ binaries/data/mods/public/simulation/helpers/Attacking.js
@@ -179,7 +179,8 @@
* @param {number} data.attacker - The entity id of the attacker.
* @param {number} data.attackerOwner - The player id of the attacker.
* @param {Vector2D} data.origin - The origin of the projectile hit.
- * @param {number} data.radius - The radius of the splash damage.
+ * @param {number} data.minRange - The minimal radius of the area damage.
+ * @param {number} data.maxRange - The maximal radius of the area damage.
* @param {string} data.shape - The shape of the radius.
* @param {Vector3D} [data.direction] - The unit vector defining the direction. Needed for linear splash damage.
* @param {number[]} data.playersToDamage - The array of player id's to damage.
@@ -187,16 +188,29 @@
Attacking.prototype.CauseDamageOverArea = function(data)
{
// Get nearby entities and define variables
- let nearEnts = this.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage);
+ let nearEnts = this.EntitiesNearPoint(data.origin, data.minRange, data.maxRange, data.playersToDamage);
let damageMultiplier = 1;
+ // Radius used for calculations.
+ let radius = data.maxRange;
+
// Cycle through all the nearby entities and damage it appropriately based on its distance from the origin.
for (let ent of nearEnts)
{
- let entityPosition = Engine.QueryInterface(ent, IID_Position).GetPosition2D();
- if (data.shape == 'Circular') // circular effect with quadratic falloff in every direction
- damageMultiplier = 1 - data.origin.distanceToSquared(entityPosition) / (data.radius * data.radius);
- else if (data.shape == 'Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles)
+ // Do not damage self when causing proximity damage.
+ if (ent == data.attacker && data.type == "Proximity")
+ continue;
+
+ let cmpPosition = Engine.QueryInterface(ent, IID_Position);
+ if (!cmpPosition || !cmpPosition.IsInWorld())
+ continue;
+ let entityPosition = cmpPosition.GetPosition2D();
+
+ // Circular effect with quadratic falloff in every direction.
+ if (data.shape == 'Circular')
+ damageMultiplier = 1 - data.origin.distanceToSquared(entityPosition) / (radius * radius);
+ // Linear effect with quadratic falloff in two directions (only used for certain missiles).
+ else if (data.shape == 'Linear' && data.direction)
{
// Get position of entity relative to splash origin.
let relativePos = entityPosition.sub(data.origin);
@@ -207,17 +221,17 @@
let perpPos = relativePos.cross(direction);
// The width of linear splash is one fifth of the normal splash radius.
- let width = data.radius / 5;
+ let width = radius / 5;
// Check that the unit is within the distance splash width of the line starting at the missile's
// landing point which extends in the direction of the missile for length splash radius.
if (parallelPos >= 0 && Math.abs(perpPos) < width) // If in radius, quadratic falloff in both directions
- damageMultiplier = (1 - parallelPos * parallelPos / (data.radius * data.radius)) *
+ damageMultiplier = (1 - parallelPos * parallelPos / (radius * radius)) *
(1 - perpPos * perpPos / (width * width));
else
damageMultiplier = 0;
}
- else // In case someone calls this function with an invalid shape.
+ else
{
warn("The " + data.shape + " splash damage shape is not implemented!");
}
@@ -265,17 +279,18 @@
/**
* Gets entities near a give point for given players.
* @param {Vector2D} origin - The point to check around.
- * @param {number} radius - The radius around the point to check.
+ * @param {number} minRange - The maximum distance the entities may have from the point to check.
+ * @param {number} maxRange - The minumum distance the entities may have from the point to check.
* @param {number[]} players - The players of which we need to check entities.
* @return {number[]} The id's of the entities in range of the given point.
*/
-Attacking.prototype.EntitiesNearPoint = function(origin, radius, players)
+Attacking.prototype.EntitiesNearPoint = function(origin, minRange, maxRange, players)
{
// If there is insufficient data return an empty array.
- if (!origin || !radius || !players)
+ if (!origin || !maxRange || !players)
return [];
- return Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).ExecuteQueryAroundPos(origin, 0, radius, players, IID_Resistance);
+ return Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).ExecuteQueryAroundPos(origin, minRange || 0, maxRange, players, IID_Resistance);
};
/**
Index: binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml
@@ -57,6 +57,25 @@
150
+
+
+ 10.0
+
+ 0
+ 10
+ true
+ Circular
+ true
+ 0
+ 100
+ 0
+
+
+ Cavalry
+ 0
+
+
+
2.0
1.0