Fix issues with civ and classes bonuses for splash damage. Even if those bonuses were in splash schema they was not taken into account. Update, fix, clean and complete unit tests for those logic. Reviewed by Mate-86.

Differential Revision: https://code.wildfiregames.com/D792
This was SVN commit r20015.
This commit is contained in:
fatherbushido 2017-08-21 06:52:37 +00:00
parent 028ce1d9e9
commit ff90bb8490
9 changed files with 336 additions and 72 deletions

View file

@ -164,7 +164,6 @@ m.allowCapture = function(gameState, ent, target)
return capture > antiCapture + sumCapturePoints/80;
};
/** copy of GetAttackBonus from Attack.js */
m.getAttackBonus = function(ent, target, type)
{
let attackBonus = 1;

View file

@ -432,33 +432,14 @@ Attack.prototype.GetRange = function(type)
return { "max": max, "min": min, "elevationBonus": elevationBonus };
};
// Calculate the attack damage multiplier against a target
Attack.prototype.GetAttackBonus = function(type, target)
Attack.prototype.GetBonusTemplate = function(type)
{
let attackBonus = 1;
let template = this.template[type];
if (!template)
template = this.template[type.split(".")[0]].Splash;
if (template.Bonuses)
{
let cmpIdentity = Engine.QueryInterface(target, IID_Identity);
if (!cmpIdentity)
return 1;
// Multiply the bonuses for all matching classes
for (let key in template.Bonuses)
{
let bonus = template.Bonuses[key];
if (bonus.Civ && bonus.Civ !== cmpIdentity.GetCiv())
continue;
if (bonus.Classes && bonus.Classes.split(/\s+/).some(cls => !cmpIdentity.HasClass(cls)))
continue;
attackBonus *= bonus.Multiplier;
}
}
return attackBonus;
return clone(template.Bonuses);
return null;
};
/**
@ -528,7 +509,7 @@ Attack.prototype.PerformAttack = function(type, target)
"position": realTargetPosition,
"direction": missileDirection,
"projectileId": id,
"multiplier": this.GetAttackBonus(type, target),
"bonus": this.GetBonusTemplate(type),
"isSplash": false,
"attackerOwner": attackerOwner
};
@ -538,7 +519,8 @@ Attack.prototype.PerformAttack = function(type, target)
data.radius = +this.template.Ranged.Splash.Range;
data.shape = this.template.Ranged.Splash.Shape;
data.isSplash = true;
data.splashStrengths = this.GetAttackStrengths(type+".Splash");
data.splashStrengths = this.GetAttackStrengths(type + ".Splash");
data.splashBonus = this.GetBonusTemplate(type + ".Splash");
}
cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000, data);
}
@ -547,7 +529,7 @@ Attack.prototype.PerformAttack = function(type, target)
if (attackerOwner == -1)
return;
let multiplier = this.GetAttackBonus(type, target);
let multiplier = GetDamageBonus(target, this.GetBonusTemplate(type));
let cmpHealth = Engine.QueryInterface(target, IID_Health);
if (!cmpHealth || cmpHealth.GetHitpoints() == 0)
return;
@ -574,7 +556,7 @@ Attack.prototype.PerformAttack = function(type, target)
"strengths": this.GetAttackStrengths(type),
"target": target,
"attacker": this.entity,
"multiplier": this.GetAttackBonus(type, target),
"multiplier": GetDamageBonus(target, this.GetBonusTemplate(type)),
"type": type,
"attackerOwner": attackerOwner
});
@ -609,7 +591,7 @@ Attack.prototype.PredictTimeToTarget = function(selfPosition, horizSpeed, target
return c / (Math.sqrt(disc) - b);
return false;
}
};
Attack.prototype.OnValueModification = function(msg)
{

View file

@ -97,11 +97,14 @@ Damage.prototype.GetPlayersToDamage = function(attackerOwner, friendlyFire)
* @param {boolean} data.isSplash - a flag indicating if it's splash damage.
* @param {Vector3D} data.position - the expected position of the target.
* @param {number} data.projectileId - the id of the projectile.
* @param {Vector3D} data.direction - The unit vector defining the direction
* @param {Vector3D} data.direction - the unit vector defining the direction.
* @param {Object} data.bonus - the attack bonus template from the attacker.
* ***When splash damage***
* @param {boolean} data.friendlyFire - a flag indicating if allied entities are also damaged.
* @param {number} data.radius - the radius of the splash damage.
* @param {string} data.shape - the shape of the splash range.
* @param {Object} data.splashBonus - the attack bonus template from the attacker.
* @param {Object} data.splashStrengths - data of the form { 'hack': number, 'pierce': number, 'crush': number }.
*/
Damage.prototype.MissileHit = function(data, lateness)
{
@ -117,6 +120,7 @@ Damage.prototype.MissileHit = function(data, lateness)
"radius": data.radius,
"shape": data.shape,
"strengths": data.splashStrengths,
"splashBonus": data.splashBonus,
"direction": data.direction,
"playersToDamage": this.GetPlayersToDamage(data.attackerOwner, data.friendlyFire),
"type": data.type,
@ -131,6 +135,7 @@ Damage.prototype.MissileHit = function(data, lateness)
let cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);
if (cmpDamageReceiver && this.TestCollision(data.target, data.position, lateness))
{
data.multiplier = GetDamageBonus(data.target, data.bonus);
this.CauseDamage(data);
cmpProjectileManager.RemoveProjectile(data.projectileId);
return;
@ -147,11 +152,12 @@ Damage.prototype.MissileHit = function(data, lateness)
{
if (!this.TestCollision(ent, data.position, lateness))
continue;
this.CauseDamage({
"strengths": data.strengths,
"target": ent,
"attacker": data.attacker,
"multiplier": data.multiplier,
"multiplier": GetDamageBonus(ent, data.bonus),
"type": data.type,
"attackerOwner": data.attackerOwner
});
@ -170,7 +176,8 @@ Damage.prototype.MissileHit = function(data, lateness)
* @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 {Vector3D} [data.direction] - the unit vector defining the direction.
* @param {Vector3D} [data.direction] - the unit vector defining the direction. Needed for linear splash damage.
* @param {Object} data.splashBonus - the attack bonus template from the attacker.
* @param {number[]} data.playersToDamage - the array of player id's to damage.
*/
Damage.prototype.CauseSplashDamage = function(data)
@ -210,6 +217,10 @@ Damage.prototype.CauseSplashDamage = function(data)
{
warn("The " + data.shape + " splash damage shape is not implemented!");
}
if (data.splashBonus)
damageMultiplier *= GetDamageBonus(ent, data.splashBonus);
// Call CauseDamage which reduces the hitpoints, posts network command, plays sounds....
this.CauseDamage({
"strengths": data.strengths,

View file

@ -55,6 +55,13 @@ DeathDamage.prototype.GetDeathDamageStrengths = function(type)
};
};
DeathDamage.prototype.GetBonusTemplate = function()
{
if (this.template.Bonuses)
return clone(this.template.Bonuses);
return null;
};
DeathDamage.prototype.CauseDeathDamage = function()
{
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
@ -78,6 +85,7 @@ DeathDamage.prototype.CauseDeathDamage = function()
"radius": radius,
"shape": this.template.Shape,
"strengths": this.GetDeathDamageStrengths("Death"),
"splashBonus": this.GetBonusTemplate(),
"playersToDamage": playersToDamage,
"type": "Death",
"attackerOwner": owner

View file

@ -4684,14 +4684,6 @@ UnitAI.prototype.GetBestAttackAgainst = function(target, allowCapture)
return cmpAttack.GetBestAttackAgainst(target, allowCapture);
};
UnitAI.prototype.GetAttackBonus = function(type, target)
{
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
if (!cmpAttack)
return 1;
return cmpAttack.GetAttackBonus(type, target);
};
/**
* Try to find one of the given entities which can be attacked,
* and start attacking it.

View file

@ -1,3 +1,4 @@
Engine.LoadHelperScript("DamageBonus.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/Auras.js");
@ -86,7 +87,7 @@ function attackComponentTest(defenderClass, isEnemy, test_function)
"Bonuses": {
"BonusCav": {
"Classes": "Cavalry",
"Multiplier": 2
"Multiplier": 3
}
}
}
@ -163,11 +164,21 @@ attackComponentTest(undefined, true ,(attacker, cmpAttack, defender) => {
for (let className of ["Infantry", "Cavalry"])
attackComponentTest(className, true, (attacker, cmpAttack, defender) => {
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackBonus("Melee", defender), className == "Cavalry" ? 2 : 1);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackBonus("Ranged", defender), 1);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackBonus("Ranged.Splash", defender), className == "Cavalry" ? 2 : 1);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackBonus("Capture", defender), 1);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackBonus("Slaughter", defender), 1);
TS_ASSERT_EQUALS(cmpAttack.GetBonusTemplate("Melee").BonusCav.Multiplier, 2);
// Check that we don't leak data
let bonus = cmpAttack.GetBonusTemplate("Melee");
bonus.BonusCav.Multiplier = 2.7;
TS_ASSERT_EQUALS(cmpAttack.GetBonusTemplate("Melee").BonusCav.Multiplier, 2);
TS_ASSERT(cmpAttack.GetBonusTemplate("Capture") === null);
let getAttackBonus = (t, e) => GetDamageBonus(e, cmpAttack.GetBonusTemplate(t));
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus("Melee", defender), className == "Cavalry" ? 2 : 1);
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus("Ranged", defender), 1);
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus("Ranged.Splash", defender), className == "Cavalry" ? 3 : 1);
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus("Capture", defender), 1);
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus("Slaughter", defender), 1);
});
// CanAttack rejects elephant attack due to RestrictedClasses

View file

@ -1,3 +1,4 @@
Engine.LoadHelperScript("DamageBonus.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("Sound.js");
Engine.LoadHelperScript("ValueModification.js");
@ -36,8 +37,7 @@ function Test_Generic()
let type = "Melee";
let damageTaken = false;
cmpAttack.GetAttackStrengths = (type) => ({ "hack": 0, "pierce": 0, "crush": damage });
cmpAttack.GetAttackBonus = (type, target) => 1.0;
cmpAttack.GetAttackStrengths = attackType => ({ "hack": 0, "pierce": 0, "crush": damage });
let data = {
"attacker": attacker,
@ -57,24 +57,19 @@ function Test_Generic()
});
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": (id) => atkPlayerEntity,
"GetPlayerByID": id => atkPlayerEntity,
"GetNumPlayers": () => 5
});
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
"ExecuteQueryAroundPos": () => [target],
"GetElevationAdaptedRange": (pos, rot, max, bonus, a) => max,
});
AddMock(SYSTEM_ENTITY, IID_ProjectileManager, {
RemoveProjectile: () => {},
LaunchProjectileAtPoint: (ent, pos, speed, gravity) => {},
"RemoveProjectile": () => {},
"LaunchProjectileAtPoint": (ent, pos, speed, gravity) => {},
});
AddMock(target, IID_Position, {
"GetPosition": () => targetPos,
"GetPreviousPosition": () => targetPos,
"GetPosition2D": () => new Vector2D(3, 3),
"GetPosition2D": () => Vector2D.From(targetPos),
"IsInWorld": () => true,
});
@ -117,13 +112,6 @@ function Test_Generic()
cmpDamage.CauseDamage(data);
TestDamage();
data.friendlyFire = false;
data.range = 10;
data.shape = "Circular";
data.isSplash = true;
cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", 1000, data);
TestDamage();
// Check for damage still being dealt if the attacker dies
cmpAttack.PerformAttack("Ranged", target);
Engine.DestroyEntity(attacker);
@ -164,7 +152,9 @@ function TestLinearSplashDamage()
let fallOff = function(x,y)
{
return (1 - x * x / (data.radius * data.radius)) * (1 - 25 * y * y / (data.radius * data.radius));
}
};
let hitEnts = new Set();
let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage");
@ -186,6 +176,7 @@ function TestLinearSplashDamage()
AddMock(60, IID_DamageReceiver, {
"TakeDamage": (hack, pierce, crush) => {
hitEnts.add(60);
TS_ASSERT_EQUALS(hack + pierce + crush, 100 * fallOff(2.2, -0.4));
return { "killed": false, "change": -(hack + pierce + crush) };
}
@ -193,6 +184,7 @@ function TestLinearSplashDamage()
AddMock(61, IID_DamageReceiver, {
TakeDamage: (hack, pierce, crush) => {
hitEnts.add(61);
TS_ASSERT_EQUALS(hack + pierce + crush, 100 * fallOff(0, 0));
return { "killed": false, "change": -(hack + pierce + crush) };
}
@ -200,24 +192,34 @@ function TestLinearSplashDamage()
AddMock(62, IID_DamageReceiver, {
"TakeDamage": (hack, pierce, crush) => {
hitEnts.add(62);
TS_ASSERT_EQUALS(hack + pierce + crush, 0);
return { "killed": false, "change": -(hack + pierce + crush) };
}
});
cmpDamage.CauseSplashDamage(data);
TS_ASSERT(hitEnts.has(60));
TS_ASSERT(hitEnts.has(61));
TS_ASSERT(hitEnts.has(62));
hitEnts.clear();
data.direction = new Vector3D(0.6, 747, 0.8);
AddMock(60, IID_DamageReceiver, {
"TakeDamage": (hack, pierce, crush) => {
hitEnts.add(60);
TS_ASSERT_EQUALS(hack + pierce + crush, 100 * fallOff(1, 2));
return { "killed": false, "change": -(hack + pierce + crush) };
}
});
cmpDamage.CauseSplashDamage(data);
};
TS_ASSERT(hitEnts.has(60));
TS_ASSERT(hitEnts.has(61));
TS_ASSERT(hitEnts.has(62));
hitEnts.clear();
}
TestLinearSplashDamage();
@ -231,12 +233,12 @@ function TestCircularSplashDamage()
let fallOff = function(r)
{
return 1 - r * r / (radius * radius);
}
};
let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage");
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
"ExecuteQueryAroundPos": () => [60, 61, 62],
"ExecuteQueryAroundPos": () => [60, 61, 62, 64],
});
AddMock(60, IID_Position, {
@ -252,12 +254,12 @@ function TestCircularSplashDamage()
});
AddMock(63, IID_Position, {
"GetPosition2D": () => new Vector2D(5, -3),
"GetPosition2D": () => new Vector2D(10, -10),
});
// Target on the frontier of the shape
AddMock(64, IID_Position, {
"GetPosition2D": () => new Vector2D(4, -2),
"GetPosition2D": () => new Vector2D(9, -4),
});
AddMock(60, IID_DamageReceiver, {
@ -283,8 +285,7 @@ function TestCircularSplashDamage()
AddMock(63, IID_DamageReceiver, {
"TakeDamage": (hack, pierce, crush) => {
TS_ASSERT_EQUALS(hack + pierce + crush, 0);
return { "killed": false, "change": -(hack + pierce + crush) };
TS_ASSERT(false);
}
});
@ -305,6 +306,232 @@ function TestCircularSplashDamage()
"type": "Ranged",
"attackerOwner": 1
});
};
}
TestCircularSplashDamage();
function Test_MissileHit()
{
ResetState();
Engine.PostMessage = (ent, iid, message) => {};
let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage");
let target = 60;
let targetOwner = 1;
let targetPos = new Vector3D(3, 10, 0);
let hitEnts = new Set();
AddMock(SYSTEM_ENTITY, IID_Timer, {
"GetLatestTurnLength": () => 500
});
const radius = 10;
let data = {
"type": "Ranged",
"attacker": 70,
"target": 60,
"strengths": { "hack": 0, "pierce": 100, "crush": 0 },
"position": targetPos,
"direction": new Vector3D(1, 0, 0),
"projectileId": 9,
"bonus": undefined,
"isSplash": false,
"attackerOwner": 1
};
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": id => id == 1 ? 10 : 11,
"GetNumPlayers": () => 2
});
AddMock(SYSTEM_ENTITY, IID_ProjectileManager, {
"RemoveProjectile": () => {},
"LaunchProjectileAtPoint": (ent, pos, speed, gravity) => {},
});
AddMock(60, IID_Position, {
"GetPosition": () => targetPos,
"GetPreviousPosition": () => targetPos,
"GetPosition2D": () => Vector2D.From(targetPos),
"IsInWorld": () => true,
});
AddMock(60, IID_Health, {});
AddMock(60, IID_DamageReceiver, {
"TakeDamage": (hack, pierce, crush) => {
TS_ASSERT_EQUALS(hack + pierce + crush, 100);
hitEnts.add(60);
return { "killed": false, "change": -(hack + pierce + crush) };
}
});
AddMock(60, IID_Footprint, {
"GetShape": () => ({ "type": "circle", "radius": 20 }),
});
AddMock(70, IID_Ownership, {
"GetOwner": () => 1,
});
AddMock(70, IID_Position, {
"GetPosition": () => new Vector3D(0, 0, 0),
"GetRotation": () => new Vector3D(0, 0, 0),
"IsInWorld": () => true,
});
AddMock(10, IID_Player, {
"GetEnemies": () => [2]
});
cmpDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(60));
hitEnts.clear();
// The main target is not hit but another one is hit.
AddMock(60, IID_Position, {
"GetPosition": () => new Vector3D(900, 10, 0),
"GetPreviousPosition": () => new Vector3D(900, 10, 0),
"GetPosition2D": () => new Vector2D(900, 0),
"IsInWorld": () => true,
});
AddMock(60, IID_DamageReceiver, {
"TakeDamage": (hack, pierce, crush) => {
TS_ASSERT_EQUALS(false);
return { "killed": false, "change": -(hack + pierce + crush) };
}
});
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
"ExecuteQueryAroundPos": () => [61]
});
AddMock(61, IID_Position, {
"GetPosition": () => targetPos,
"GetPreviousPosition": () => targetPos,
"GetPosition2D": () => Vector2D.from3D(targetPos),
"IsInWorld": () => true,
});
AddMock(61, IID_Health, {});
AddMock(61, IID_DamageReceiver, {
"TakeDamage": (hack, pierce, crush) => {
TS_ASSERT_EQUALS(hack + pierce + crush, 100);
hitEnts.add(61);
return { "killed": false, "change": -(hack + pierce + crush) };
}
});
AddMock(61, IID_Footprint, {
"GetShape": () => ({ "type": "circle", "radius": 20 }),
});
cmpDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
hitEnts.clear();
// Add a splash damage.
data.friendlyFire = false;
data.radius = 10;
data.shape = "Circular";
data.isSplash = true;
data.splashStrengths = { "hack": 0, "pierce": 0, "crush": 200 };
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
"ExecuteQueryAroundPos": () => [61, 62]
});
let dealtDamage = 0;
AddMock(61, IID_DamageReceiver, {
"TakeDamage": (hack, pierce, crush) => {
dealtDamage += hack + pierce + crush;
hitEnts.add(61);
return { "killed": false, "change": -(hack + pierce + crush) };
}
});
AddMock(62, IID_Position, {
"GetPosition": () => new Vector3D(8, 10, 0),
"GetPreviousPosition": () => new Vector3D(8, 10, 0),
"GetPosition2D": () => new Vector2D(8, 0),
"IsInWorld": () => true,
});
AddMock(62, IID_Health, {});
AddMock(62, IID_DamageReceiver, {
"TakeDamage": (hack, pierce, crush) => {
TS_ASSERT_EQUALS(hack + pierce + crush, 200 * 0.75);
hitEnts.add(62);
return { "killed": false, "change": -(hack + pierce + crush) };
}
});
AddMock(62, IID_Footprint, {
"GetShape": () => ({ "type": "circle", "radius": 20 }),
});
cmpDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 100 + 200);
dealtDamage = 0;
TS_ASSERT(hitEnts.has(62));
hitEnts.clear();
// Add some hard counters bonus.
Engine.DestroyEntity(62);
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
"ExecuteQueryAroundPos": () => [61]
});
let bonus= { "BonusCav": { "Classes": "Cavalry", "Multiplier": 400 } };
let splashBonus = { "BonusCav": { "Classes": "Cavalry", "Multiplier": 10000 } };
AddMock(61, IID_Identity, {
"HasClass": cl => cl == "Cavalry"
});
data.bonus = bonus;
cmpDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 400 * 100 + 200);
dealtDamage = 0;
hitEnts.clear();
data.splashBonus = splashBonus;
cmpDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 400 * 100 + 10000 * 200);
dealtDamage = 0;
hitEnts.clear();
data.bonus = undefined;
cmpDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 100 + 10000 * 200);
dealtDamage = 0;
hitEnts.clear();
data.bonus = null;
cmpDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 100 + 10000 * 200);
dealtDamage = 0;
hitEnts.clear();
data.bonus = {};
cmpDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 100 + 10000 * 200);
dealtDamage = 0;
hitEnts.clear();
}
Test_MissileHit();

View file

@ -1,3 +1,4 @@
Engine.LoadHelperScript("DamageBonus.js");
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/Damage.js");
@ -41,6 +42,7 @@ let result = {
"radius": template.Range,
"shape": template.Shape,
"strengths": modifiedDamage,
"splashBonus": null,
"playersToDamage": playersToDamage,
"type": "Death",
"attackerOwner": player
@ -62,3 +64,9 @@ AddMock(deadEnt, IID_Ownership, {
TS_ASSERT_UNEVAL_EQUALS(cmpDeathDamage.GetDeathDamageStrengths(), modifiedDamage);
cmpDeathDamage.CauseDeathDamage();
let splashBonus = { "BonusCav": { "Classes": "Cavalry", "Multiplier": 3 } };
cmpDeathDamage.template.Bonuses = splashBonus;
result.splashBonus = splashBonus;
TS_ASSERT_UNEVAL_EQUALS(cmpDeathDamage.GetDeathDamageStrengths(), modifiedDamage);
cmpDeathDamage.CauseDeathDamage();

View file

@ -0,0 +1,26 @@
/**
* Calculate the attack damage multiplier against a target.
*/
function GetDamageBonus(target, template)
{
let attackBonus = 1;
let cmpIdentity = Engine.QueryInterface(target, IID_Identity);
if (!cmpIdentity)
return 1;
// Multiply the bonuses for all matching classes
for (let key in template)
{
let bonus = template[key];
if (bonus.Civ && bonus.Civ !== cmpIdentity.GetCiv())
continue;
if (bonus.Classes && bonus.Classes.split(/\s+/).some(cls => !cmpIdentity.HasClass(cls)))
continue;
attackBonus *= bonus.Multiplier;
}
return attackBonus;
}
Engine.RegisterGlobal("GetDamageBonus", GetDamageBonus);