Index: binaries/data/mods/public/simulation/components/Damage.js
===================================================================
--- binaries/data/mods/public/simulation/components/Damage.js
+++ binaries/data/mods/public/simulation/components/Damage.js
@@ -232,6 +232,63 @@
};
/**
+ * Damages units around a given entity.
+ * @param {Object} data - the data sent by the caller.
+ * @param {number} data.attacker - the entity id of the attacker.
+ * @param {Vector2D} data.origin - the origin of the attacker.
+ * @param {number} data.radius - the radius of the proximity damage.
+ * @param {string} data.shape - the shape of the radius.
+ * @param {Object} data.strengths - data of the form { 'hack': number, 'pierce': number, 'crush': number }.
+ * @param {string} data.type - the type of damage.
+ * @param {number} data.attackerOwner - the player id of the attacker.
+ * @param {Object} data.proximityBonus - the attack bonus template from the attacker.
+ * @param {number[]} data.playersToDamage - the array of player id's to damage.
+ */
+Damage.prototype.CauseProximityDamage = function(data)
+{
+ // Get nearby entities and define variables
+ let nearEnts = this.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage);
+ let cmpProximityDamage = Engine.QueryInterface(data.attacker, IID_ProximityDamage);
+ let restrictedClasses = cmpProximityDamage.GetRestrictedClasses();
+ let damageMultiplier = 1;
+
+ // Cycle through all the nearby entities and damage it appropriately based on its distance from the origin.
+ for (let ent of nearEnts)
+ {
+ // Do not damage the attacker itself
+ if (ent == data.attacker)
+ continue;
+
+ // Do not damage restricted classes
+ let cmpIdentity = QueryMiragedInterface(ent, IID_Identity);
+ let targetClasses = cmpIdentity.GetClassesList();
+ if (restrictedClasses.length && MatchesClassList(targetClasses, restrictedClasses))
+ continue;
+
+ // Calculate distance effects
+ let entityPosition = Engine.QueryInterface(ent, IID_Position).GetPosition2D();
+ if (data.shape == 'Circular')
+ damageMultiplier = 1 - data.origin.distanceToSquared(entityPosition) / (data.radius * data.radius);
+ else
+ warn("The " + data.shape + " proximity damage shape is not implemented!");
+
+ // Apply bonusses
+ if (data.proximityBonus)
+ damageMultiplier *= GetDamageBonus(ent, data.proximityBonus);
+
+ // Call CauseDamage which reduces the hitpoints, posts network command, plays sounds....
+ this.CauseDamage({
+ "strengths": data.strengths,
+ "target": ent,
+ "attacker": data.attacker,
+ "multiplier": damageMultiplier,
+ "type": data.type + ".Proximity",
+ "attackerOwner": data.attackerOwner
+ });
+ }
+};
+
+/**
* Causes damage on a given unit.
* @param {Object} data - the data passed by the caller.
* @param {Object} data.strengths - data in the form of { 'hack': number, 'pierce': number, 'crush': number }.
Index: binaries/data/mods/public/simulation/components/ProximityDamage.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/ProximityDamage.js
@@ -0,0 +1,160 @@
+function ProximityDamage() {}
+
+ProximityDamage.prototype.bonusesSchema =
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
+
+ProximityDamage.prototype.restrictedClassesSchema =
+ "" +
+ "" +
+ "" +
+ "tokens" +
+ "" +
+ "" +
+ "" +
+ "";
+
+ProximityDamage.prototype.Schema =
+ "Whether a unit or building inflicts damage to nearby units." +
+ "" +
+ "Circular" +
+ "10" +
+ "true" +
+ "0.0" +
+ "10.0" +
+ "50.0" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ DamageTypes.BuildSchema("damage strength") +
+ ProximityDamage.prototype.restrictedClassesSchema +
+ ProximityDamage.prototype.bonusesSchema;
+
+ProximityDamage.prototype.Init = function()
+{
+ this.CheckTimer();
+};
+
+/**
+ * Find the entities not to be damaged by this proximity damage
+ * @return {Array} classes not to be damaged by this proximity damage
+ */
+ProximityDamage.prototype.GetRestrictedClasses = function()
+{
+ if (this.template.RestrictedClasses &&
+ this.template.RestrictedClasses._string)
+ return this.template.RestrictedClasses._string.split(/\s+/);
+
+ return [];
+};
+
+ProximityDamage.prototype.Serialize = null; // We have no dynamic state to save
+
+/**
+ * Work out the damage values with technology effects
+ * @return {Array} modifications to the damage types
+ */
+ProximityDamage.prototype.GetProximityDamageStrengths = function()
+{
+ let applyMods = damageType =>
+ ApplyValueModificationsToEntity("ProximityDamage/" + damageType, +(this.template[damageType] || 0), this.entity);
+
+ let ret = {};
+ for (let damageType of DamageTypes.GetTypes())
+ ret[damageType] = applyMods(damageType);
+
+ return ret;
+};
+
+/**
+ * Handle any bonuses with this damage type
+ * @return {Array} bonusses to apply
+ */
+ProximityDamage.prototype.GetBonusTemplate = function()
+{
+ return this.template.Bonuses || null;
+};
+
+ProximityDamage.prototype.CauseProximityDamage = function()
+{
+ // Return when this entity is otherworldly
+ let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+ if (!cmpPosition || !cmpPosition.IsInWorld())
+ return;
+
+ let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+ let 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 cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
+ let playersToDamage = cmpDamage.GetPlayersToDamage(owner, this.template.FriendlyFire);
+
+ let radius = ApplyValueModificationsToEntity("ProximityDamage/Range", +this.template.Range, this.entity);
+
+ // Call the Damage-component to find out which entities to damage and damage them
+ cmpDamage.CauseProximityDamage({
+ "attacker": this.entity,
+ "origin": cmpPosition.GetPosition2D(),
+ "radius": radius,
+ "shape": this.template.Shape,
+ "strengths": this.GetProximityDamageStrengths(),
+ "proximityBonus": this.GetBonusTemplate(),
+ "playersToDamage": playersToDamage,
+ "type": "Proximity",
+ "attackerOwner": owner
+ });
+};
+
+// Start the damage timer when no timer exists.
+ProximityDamage.prototype.CheckTimer = function()
+{
+ // If the unit only causes proximity damage whilst moving, disable timer when not moving
+ if (this.template.OnlyWhenMoving == "true")
+ {
+ let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
+ if (cmpUnitAI && !cmpUnitAI.IsInMovingState())
+ {
+ // We don't need a timer, disable if one exists
+ if (this.proximityDamageTimer)
+ {
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ cmpTimer.CancelTimer(this.proximityDamageTimer);
+ delete this.proximityDamageTimer;
+ }
+ return;
+ }
+ }
+
+ // We need a timer, enable if one doesn't exist
+ if (this.proximityDamageTimer)
+ return;
+
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ this.proximityDamageTimer = cmpTimer.SetInterval(this.entity, IID_ProximityDamage, "CauseProximityDamage", 100, 100, undefined);
+};
+
+// If the entity starts or stops moving check the timer
+ProximityDamage.prototype.OnUnitAIStateChanged = function(msg)
+{
+ this.CheckTimer();
+};
+
+Engine.RegisterComponentType(IID_ProximityDamage, "ProximityDamage", ProximityDamage);
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
@@ -3321,6 +3321,12 @@
return (state == "WALKING");
};
+UnitAI.prototype.IsInMovingState = function()
+{
+ let state = ["WALKING", "APPROACHING", "RUNNING", "CHARGING", "FLEEING"];
+ return state.IndexOf(this.GetCurrentState().split(".").pop()) != -1;
+};
+
/**
* Return true if the current order is WalkAndFight or Patrol.
*/
Index: binaries/data/mods/public/simulation/components/interfaces/ProximityDamage.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/interfaces/ProximityDamage.js
@@ -0,0 +1 @@
+Engine.RegisterInterface("ProximityDamage");