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