Index: ps/trunk/binaries/data/mods/public/globalscripts/Templates.js
===================================================================
--- ps/trunk/binaries/data/mods/public/globalscripts/Templates.js
+++ ps/trunk/binaries/data/mods/public/globalscripts/Templates.js
@@ -178,6 +178,16 @@
}
}
+ if (template.DeathDamage)
+ {
+ ret.deathDamage = {
+ "hack": getEntityValue("DeathDamage/Hack"),
+ "pierce": getEntityValue("DeathDamage/Pierce"),
+ "crush": getEntityValue("DeathDamage/Crush"),
+ "friendlyFire": template.DeathDamage.FriendlyFire != "false"
+ };
+ }
+
if (template.Auras)
{
ret.auras = {};
Index: ps/trunk/binaries/data/mods/public/simulation/components/Damage.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Damage.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/Damage.js
@@ -65,6 +65,27 @@
};
/**
+ * Get the list of players affected by the damage.
+ * @param {number} attackerOwner - the player id of the attacker.
+ * @param {boolean} friendlyFire - a flag indicating if allied entities are also damaged.
+ * @return {number[]} - the ids of players need to be damaged
+ */
+Damage.prototype.GetPlayersToDamage = function(attackerOwner, friendlyFire)
+{
+ let cmpPlayer = QueryPlayerIDInterface(attackerOwner);
+
+ if (!friendlyFire)
+ return cmpPlayer.GetEnemies();
+
+ let playersToDamage = [];
+ let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
+ for (let i = 0; i < numPlayers; ++i)
+ playersToDamage.push(i)
+
+ return playersToDamage;
+}
+
+/**
* Handles hit logic after the projectile travel time has passed.
* @param {Object} data - the data sent by the caller.
* @param {number} data.attacker - the entity id of the attacker.
@@ -90,16 +111,6 @@
// Do this first in case the direct hit kills the target
if (data.isSplash)
{
- let playersToDamage = [];
- if (!data.friendlyFire)
- playersToDamage = QueryPlayerIDInterface(data.attackerOwner).GetEnemies();
- else
- {
- let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
- for (let i = 0; i < numPlayers; ++i)
- playersToDamage.push(i);
- }
-
this.CauseSplashDamage({
"attacker": data.attacker,
"origin": Vector2D.from3D(data.position),
@@ -107,7 +118,7 @@
"shape": data.shape,
"strengths": data.splashStrengths,
"direction": data.direction,
- "playersToDamage": playersToDamage,
+ "playersToDamage": this.GetPlayersToDamage(data.attackerOwner, data.friendlyFire),
"type": data.type,
"attackerOwner": data.attackerOwner
});
Index: ps/trunk/binaries/data/mods/public/simulation/components/DeathDamage.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/DeathDamage.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/DeathDamage.js
@@ -0,0 +1,87 @@
+function DeathDamage() {}
+
+DeathDamage.prototype.bonusesSchema =
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
+
+DeathDamage.prototype.Schema =
+ "When a unit or building is destroyed, it inflicts damage to nearby units." +
+ "" +
+ "Circular" +
+ "20" +
+ "false" +
+ "0.0" +
+ "10.0" +
+ "50.0" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ DeathDamage.prototype.bonusesSchema;
+
+DeathDamage.prototype.Init = function()
+{
+};
+
+DeathDamage.prototype.Serialize = null; // we have no dynamic state to save
+
+DeathDamage.prototype.GetDeathDamageStrengths = function(type)
+{
+ // Work out the damage values with technology effects
+ let applyMods = damageType =>
+ ApplyValueModificationsToEntity("DeathDamage/" + damageType, +(this.template[damageType] || 0), this.entity);
+
+ return {
+ "hack": applyMods("Hack"),
+ "pierce": applyMods("Pierce"),
+ "crush": applyMods("Crush")
+ };
+};
+
+DeathDamage.prototype.CauseDeathDamage = function()
+{
+ let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+ if (!cmpPosition || !cmpPosition.IsInWorld())
+ return;
+ let pos = cmpPosition.GetPosition2D();
+
+ let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+ let owner = cmpOwnership.GetOwner();
+ if (owner == -1)
+ warn("Unit causing death damage does not have any owner.");
+
+ let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
+ let playersToDamage = cmpDamage.GetPlayersToDamage(owner, this.template.FriendlyFire);
+
+ let radius = ApplyValueModificationsToEntity("DeathDamage/Range", +this.template.Range, this.entity);
+
+ cmpDamage.CauseSplashDamage({
+ "attacker": this.entity,
+ "origin": pos,
+ "radius": radius,
+ "shape": this.template.Shape,
+ "strengths": this.GetDeathDamageStrengths("Death"),
+ "playersToDamage": playersToDamage,
+ "type": "Death",
+ "attackerOwner": owner
+ });
+};
+
+Engine.RegisterComponentType(IID_DeathDamage, "DeathDamage", DeathDamage);
Index: ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -434,6 +434,7 @@
"armour": null,
"attack": null,
"buildingAI": null,
+ "deathDamage": null,
"heal": null,
"isBarterMarket": null,
"loot": null,
@@ -521,6 +522,14 @@
"arrowCount": cmpBuildingAI.GetArrowCount()
};
+ let cmpDeathDamage = Engine.QueryInterface(ent, IID_DeathDamage);
+ if (cmpDeathDamage)
+ ret.deathDeath = {
+ "hack": cmpDeathDamage.GetDeathDamageStrengths("hack"),
+ "pierce": cmpDeathDamage.GetDeathDamageStrengths("pierce"),
+ "crush": cmpDeathDamage.GetDeathDamageStrengths("crush")
+ };
+
let cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
if (cmpObstruction)
ret.obstruction = {
Index: ps/trunk/binaries/data/mods/public/simulation/components/Health.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Health.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/Health.js
@@ -217,33 +217,41 @@
// might get called multiple times)
if (this.hitpoints)
{
+ this.hitpoints = 0;
+ this.RegisterHealthChanged(oldHitpoints);
state.killed = true;
+ let cmpDeathDamage = Engine.QueryInterface(this.entity, IID_DeathDamage);
+ if (cmpDeathDamage)
+ cmpDeathDamage.CauseDeathDamage();
+
PlaySound("death", this.entity);
- // If SpawnEntityOnDeath is set, spawn the entity
- if(this.template.SpawnEntityOnDeath)
+ if (this.template.SpawnEntityOnDeath)
this.CreateDeathSpawnedEntity();
- if (this.template.DeathType == "corpse")
+ switch (this.template.DeathType)
{
+ case "corpse":
this.CreateCorpse();
- Engine.DestroyEntity(this.entity);
- }
- else if (this.template.DeathType == "vanish")
- {
- Engine.DestroyEntity(this.entity);
- }
- else if (this.template.DeathType == "remain")
+ break;
+
+ case "remain":
{
- var resource = this.CreateCorpse(true);
+ let resource = this.CreateCorpse(true);
if (resource != INVALID_ENTITY)
Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: resource });
- Engine.DestroyEntity(this.entity);
}
- this.hitpoints = 0;
- this.RegisterHealthChanged(oldHitpoints);
+ case "vanish":
+ break;
+
+ default:
+ error("Invalid template.DeathType: " + this.template.DeathType);
+ break;
+ }
+
+ Engine.DestroyEntity(this.entity);
}
}
Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/DeathDamage.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/DeathDamage.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/interfaces/DeathDamage.js
@@ -0,0 +1 @@
+Engine.RegisterInterface("DeathDamage");
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Attack.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Attack.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Attack.js
@@ -31,7 +31,8 @@
AddMock(attacker, IID_Position, {
"IsInWorld": () => true,
- "GetHeightOffset": () => 5
+ "GetHeightOffset": () => 5,
+ "GetPosition2D": () => new Vector2D(1, 2)
});
AddMock(attacker, IID_Ownership, {
@@ -67,11 +68,21 @@
"MaxRange": 80,
"PrepareTime": 300,
"RepeatTime": 500,
+ "ProjectileSpeed": 50,
+ "Spread": 2.5,
"PreferredClasses": {
"_string": "Archer"
},
"RestrictedClasses": {
"_string": "Elephant"
+ },
+ "Splash" : {
+ "Shape": "Circular",
+ "Range": 10,
+ "FriendlyFire": "false",
+ "Hack": 0.0,
+ "Pierce": 15.0,
+ "Crush": 35.0
}
},
"Capture" : {
@@ -134,6 +145,8 @@
"prepare": 0,
"repeat": 1000
});
+
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetSplashDamage("Ranged"), { "hack": 0, "pierce": 15, "crush": 35, "friendlyFire": false, "shape": "Circular" });
});
for (let className of ["Infantry", "Cavalry"])
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Damage.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Damage.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Damage.js
@@ -48,11 +48,12 @@
};
AddMock(atkPlayerEntity, IID_Player, {
- GetEnemies: () => [targetOwner],
+ GetEnemies: () => [targetOwner]
});
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
GetPlayerByID: (id) => atkPlayerEntity,
+ GetNumPlayers: () => 5
});
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
@@ -122,3 +123,10 @@
cmpAttack.PerformAttack("Ranged", target);
Engine.DestroyEntity(attacker);
TestDamage();
+
+atkPlayerEntity = 1;
+AddMock(atkPlayerEntity, IID_Player, {
+ GetEnemies: () => [2, 3]
+});
+TS_ASSERT_UNEVAL_EQUALS(cmpDamage.GetPlayersToDamage(atkPlayerEntity, true), [0, 1, 2, 3, 4]);
+TS_ASSERT_UNEVAL_EQUALS(cmpDamage.GetPlayersToDamage(atkPlayerEntity, false), [2, 3]);
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
@@ -7,6 +7,7 @@
Engine.LoadComponentScript("interfaces/Capturable.js");
Engine.LoadComponentScript("interfaces/CeasefireManager.js");
Engine.LoadComponentScript("interfaces/DamageReceiver.js");
+Engine.LoadComponentScript("interfaces/DeathDamage.js");
Engine.LoadComponentScript("interfaces/EndGameManager.js");
Engine.LoadComponentScript("interfaces/EntityLimits.js");
Engine.LoadComponentScript("interfaces/Foundation.js");
@@ -644,6 +645,7 @@
armour: null,
attack: null,
buildingAI: null,
+ deathDamage:null,
heal: null,
isBarterMarket: true,
loot: null,
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_fire.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_fire.xml
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_fire.xml
@@ -10,6 +10,14 @@
100
+
+ Circular
+ 30
+ true
+ 300.0
+ 300.0
+ 300.0
+
30
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/theb_mechanical_siege_fireraiser.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/theb_mechanical_siege_fireraiser.xml
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/theb_mechanical_siege_fireraiser.xml
@@ -13,6 +13,14 @@
2.0
+
+ Circular
+ 20
+ true
+ 200.0
+ 200.0
+ 200.0
+
4.5