Index: ps/trunk/binaries/data/mods/public/simulation/components/Armour.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Armour.js (revision 16771) +++ ps/trunk/binaries/data/mods/public/simulation/components/Armour.js (revision 16772) @@ -1,122 +1,107 @@ function Armour() {} Armour.prototype.Schema = "Controls the damage resistance of the unit." + "" + "10.0" + "0.0" + "5.0" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; Armour.prototype.Init = function() { this.nextAlertTime = 0; this.invulnerable = false; }; Armour.prototype.SetInvulnerability = function(invulnerability) { this.invulnerable = invulnerability; }; /** * Take damage according to the entity's armor. * Returns object of the form { "killed": false, "change": -12 } */ -Armour.prototype.TakeDamage = function(hack, pierce, crush, source) +Armour.prototype.TakeDamage = function(hack, pierce, crush) { - // Alert target owner of attack - var cmpAttackDetection = QueryOwnerInterface(this.entity, IID_AttackDetection); - if (cmpAttackDetection) - { - var now = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime(); - if (now > this.nextAlertTime) - { - this.nextAlertTime = now + cmpAttackDetection.GetSuppressionTime(); - cmpAttackDetection.AttackAlert(this.entity, source); - } - } - if (this.invulnerable) return { "killed": false, "change": 0 }; // Adjust damage values based on armour; exponential armour: damage = attack * 0.9^armour var armourStrengths = this.GetArmourStrengths(); var adjHack = hack * Math.pow(0.9, armourStrengths.hack); var adjPierce = pierce * Math.pow(0.9, armourStrengths.pierce); var adjCrush = crush * Math.pow(0.9, armourStrengths.crush); // Total is sum of individual damages // Don't bother rounding, since HP is no longer integral. var total = adjHack + adjPierce + adjCrush; // Reduce health var cmpHealth = Engine.QueryInterface(this.entity, IID_Health); return cmpHealth.Reduce(total); }; Armour.prototype.GetArmourStrengths = function() { // Work out the armour values with technology effects - var self = this; - - var applyMods = function(type, foundation) - { + var applyMods = (type, foundation) => { var strength; if (foundation) { - strength = +self.template.Foundation[type]; + strength = +this.template.Foundation[type]; type = "Foundation/" + type; } else { - strength = +self.template[type]; + strength = +this.template[type]; } - strength = ApplyValueModificationsToEntity("Armour/" + type, strength, self.entity); + strength = ApplyValueModificationsToEntity("Armour/" + type, strength, this.entity); return strength; }; if (Engine.QueryInterface(this.entity, IID_Foundation) && this.template.Foundation) { return { hack: applyMods("Hack", true), pierce: applyMods("Pierce", true), crush: applyMods("Crush", true) }; } else { return { hack: applyMods("Hack"), pierce: applyMods("Pierce"), crush: applyMods("Crush") }; } }; Engine.RegisterComponentType(IID_DamageReceiver, "Armour", Armour); Index: ps/trunk/binaries/data/mods/public/simulation/components/AttackDetection.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/AttackDetection.js (revision 16771) +++ ps/trunk/binaries/data/mods/public/simulation/components/AttackDetection.js (revision 16772) @@ -1,161 +1,165 @@ function AttackDetection() {} AttackDetection.prototype.Schema = "Detects incoming attacks." + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; AttackDetection.prototype.Init = function() { this.suppressionTime = +this.template.SuppressionTime; // Use squared distance to avoid sqrts this.suppressionTransferRangeSquared = +this.template.SuppressionTransferRange * +this.template.SuppressionTransferRange; this.suppressionRangeSquared = +this.template.SuppressionRange * +this.template.SuppressionRange; this.suppressedList = []; }; AttackDetection.prototype.ActivateTimer = function() { Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).SetTimeout(this.entity, IID_AttackDetection, "HandleTimeout", this.suppressionTime); }; AttackDetection.prototype.AddSuppression = function(event) { this.suppressedList.push(event); this.ActivateTimer(); }; AttackDetection.prototype.UpdateSuppressionEvent = function(index, event) { this.suppressedList[index] = event; this.ActivateTimer(); }; //// Message handlers //// AttackDetection.prototype.OnGlobalAttacked = function(msg) { var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player); var cmpOwnership = Engine.QueryInterface(msg.target, IID_Ownership); - if (cmpOwnership.GetOwner() == cmpPlayer.GetPlayerID()) - Engine.PostMessage(msg.target, MT_MinimapPing); + if (cmpOwnership.GetOwner() != cmpPlayer.GetPlayerID()) + return; + + Engine.PostMessage(msg.target, MT_MinimapPing); + + this.AttackAlert(msg.target, msg.attacker); }; //// External interface //// AttackDetection.prototype.AttackAlert = function(target, attacker) { var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player); var cmpTargetOwnership = Engine.QueryInterface(target, IID_Ownership); // Don't register attacks dealt against other players if (cmpTargetOwnership.GetOwner() != cmpPlayer.GetPlayerID()) return; var cmpAttackerOwnership = Engine.QueryInterface(attacker, IID_Ownership); // Don't register attacks dealt by myself if (cmpAttackerOwnership.GetOwner() == cmpPlayer.GetPlayerID()) return; // Since livestock can be attacked/gathered by other players // and generally are not so valuable as other units/buildings, // we have a lower priority notification for it, which can be // overriden by a regular one. var cmpTargetIdentity = Engine.QueryInterface(target, IID_Identity); var targetIsDomesticAnimal = cmpTargetIdentity && cmpTargetIdentity.HasClass("Animal") && cmpTargetIdentity.HasClass("Domestic"); var cmpPosition = Engine.QueryInterface(target, IID_Position); if (!cmpPosition || !cmpPosition.IsInWorld()) return; var event = { "target": target, "position": cmpPosition.GetPosition(), "time": Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime(), "targetIsDomesticAnimal": targetIsDomesticAnimal }; // If we already have a low priority livestock event in suppressed list, // and now a more important target is attacked, we want to upgrade the // suppressed event and send the new notification var isPriorityIncreased = false; for (var i = 0; i < this.suppressedList.length; ++i) { var element = this.suppressedList[i]; // If the new attack is within suppression distance of this element, // then check if the element should be updated and return var dist = SquaredDistance(element.position, event.position); if (dist >= this.suppressionRangeSquared) continue; isPriorityIncreased = element.targetIsDomesticAnimal && !targetIsDomesticAnimal; var isPriorityDescreased = !element.targetIsDomesticAnimal && targetIsDomesticAnimal; if (isPriorityIncreased || (!isPriorityDescreased && dist < this.suppressionTransferRangeSquared)) this.UpdateSuppressionEvent(i, event); // If priority has increased, exit the loop to send the upgraded notification below if (isPriorityIncreased) break; return; } // If priority has increased for an existing event, then we already have it // in the suppression list if (!isPriorityIncreased) this.AddSuppression(event); Engine.PostMessage(this.entity, MT_AttackDetected, { "player": cmpPlayer.GetPlayerID(), "event": event }); Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({ "type": "attack", "players": [cmpPlayer.GetPlayerID()], "attacker": cmpAttackerOwnership.GetOwner(), "targetIsDomesticAnimal": targetIsDomesticAnimal }); PlaySound("attacked", target); }; AttackDetection.prototype.GetSuppressionTime = function() { return this.suppressionTime; }; AttackDetection.prototype.HandleTimeout = function() { var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); var now = cmpTimer.GetTime(); for (var i = 0; i < this.suppressedList.length; ++i) { var event = this.suppressedList[i]; // Check if this event has timed out if (now - event.time >= this.suppressionTime) { this.suppressedList.splice(i, 1); return; } } }; AttackDetection.prototype.GetIncomingAttacks = function() { return this.suppressedList; }; // Utility function for calculating the squared-distance between two attack events function SquaredDistance(pos1, pos2) { var xs = pos2.x - pos1.x; var zs = pos2.z - pos1.z; return xs*xs + zs*zs; }; Engine.RegisterComponentType(IID_AttackDetection, "AttackDetection", AttackDetection); Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Damage.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/helpers/Damage.js (revision 16771) +++ ps/trunk/binaries/data/mods/public/simulation/helpers/Damage.js (revision 16772) @@ -1,139 +1,139 @@ // Create global Damage object. var Damage = {}; /** * Damages units around a given origin. * data.attacker = * data.origin = * data.radius = * data.shape = * data.strengths = {'hack':, 'pierce':, 'crush':} * data.type = * ***Optional Variables*** * data.direction = * data.playersToDamage = */ Damage.CauseSplashDamage = function(data) { // Get nearby entities and define variables var nearEnts = Damage.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage); var damageMultiplier = 1; // Cycle through all the nearby entities and damage it appropriately based on its distance from the origin. for each (var entity in nearEnts) { var entityPosition = Engine.QueryInterface(entity, IID_Position).GetPosition2D(); if(data.shape == 'Circular') // circular effect with quadratic falloff in every direction { var squaredDistanceFromOrigin = data.origin.distanceToSquared(entityPosition); damageMultiplier = 1 - squaredDistanceFromOrigin / (data.radius * data.radius); } else if(data.shape == 'Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles) { // Get position of entity relative to splash origin. var relativePos = entityPosition.sub(data.origin); // The width of linear splash is one fifth of the normal splash radius. var width = data.radius/5; // Effectivly rotate the axis to align with the missile direction. var parallelDist = relativePos.dot(data.direction); // z axis var perpDist = Math.abs(relativePos.cross(data.direction)); // y axis // Check that the unit is within the distance at which it will get damaged. if (parallelDist > -width && perpDist < width) // If in radius, quadratic falloff in both directions damageMultiplier = (data.radius * data.radius - parallelDist * parallelDist) / (data.radius * data.radius) * (width * width - perpDist * perpDist) / (width * width); else damageMultiplier = 0; } else // In case someone calls this function with an invalid shape. { warn("The " + data.shape + " splash damage shape is not implemented!"); } // Call CauseDamage which reduces the hitpoints, posts network command, plays sounds.... Damage.CauseDamage({"strengths":data.strengths, "target":entity, "attacker":data.attacker, "multiplier":damageMultiplier, "type":data.type + ".Splash"}) } }; /** * Causes damage on a given unit * data.strengths = {'hack':, 'pierce':, 'crush':} * data.target = * data.attacker = * data.multiplier = * data.type = */ Damage.CauseDamage = function(data) { // Check the target can be damaged otherwise don't do anything. var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver); var cmpHealth = Engine.QueryInterface(data.target, IID_Health); if (!cmpDamageReceiver || !cmpHealth) return; // Damage the target - var targetState = cmpDamageReceiver.TakeDamage(data.strengths.hack * data.multiplier, data.strengths.pierce * data.multiplier, data.strengths.crush * data.multiplier, data.attacker); + var targetState = cmpDamageReceiver.TakeDamage(data.strengths.hack * data.multiplier, data.strengths.pierce * data.multiplier, data.strengths.crush * data.multiplier); // If the target was killed run some cleanup if (targetState.killed) Damage.TargetKilled(data.attacker, data.target); // Post the network command (make it work in multiplayer) Engine.PostMessage(data.target, MT_Attacked, {"attacker":data.attacker, "target":data.target, "type":data.type, "damage":-targetState.change}); // Play attacking sounds PlaySound("attack_impact", data.attacker); }; /** * Gets entities near a give point for given players. * origin = * radius = * players = * If players is not included, entities from all players are used. */ Damage.EntitiesNearPoint = function(origin, radius, players) { // If there is insufficient data return an empty array. if (!origin || !radius) return []; // If the players parameter is not specified use all players. if (!players) { var playerEntities = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayerEntities(); players = []; for each (var entity in playerEntities) players.push(Engine.QueryInterface(entity, IID_Player).GetPlayerID()); } // Call RangeManager with dummy entity and return the result. var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); var rangeQuery = rangeManager.ExecuteQueryAroundPos(origin, 0, radius, players, IID_DamageReceiver); return rangeQuery; }; /** * Called when some units kills something (another unit, building, animal etc) * killerEntity = * targetEntity = */ Damage.TargetKilled = function(killerEntity, targetEntity) { // Add to killer statistics. var cmpKillerPlayerStatisticsTracker = QueryOwnerInterface(killerEntity, IID_StatisticsTracker); if (cmpKillerPlayerStatisticsTracker) cmpKillerPlayerStatisticsTracker.KilledEntity(targetEntity); // Add to loser statistics. var cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(targetEntity, IID_StatisticsTracker); if (cmpTargetPlayerStatisticsTracker) cmpTargetPlayerStatisticsTracker.LostEntity(targetEntity); // If killer can collect loot, let's try to collect it. var cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter); if (cmpLooter) cmpLooter.Collect(targetEntity); }; Engine.RegisterGlobal("Damage", Damage);