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");