forked from mirrors/0ad
Fix a confusion with 2D and 3D vector for linear splash damage which silently appeared in 4b7ab63172. Add back a comment and slightly change the related damaged area. Add a test for linear splash damage. Patch by temple.
Differential Revision: https://code.wildfiregames.com/D767 This was SVN commit r19969.
This commit is contained in:
@@ -178,6 +178,8 @@ Damage.prototype.CauseSplashDamage = function(data)
|
||||
// Get nearby entities and define variables
|
||||
let nearEnts = this.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage);
|
||||
let damageMultiplier = 1;
|
||||
let direction = Vector2D.from3D(data.direction);
|
||||
|
||||
// Cycle through all the nearby entities and damage it appropriately based on its distance from the origin.
|
||||
for (let ent of nearEnts)
|
||||
{
|
||||
@@ -189,17 +191,18 @@ Damage.prototype.CauseSplashDamage = function(data)
|
||||
// Get position of entity relative to splash origin.
|
||||
let relativePos = entityPosition.sub(data.origin);
|
||||
|
||||
// Get the position relative to the missile direction.
|
||||
let parallelPos = relativePos.dot(direction);
|
||||
let perpPos = relativePos.cross(direction);
|
||||
|
||||
// The width of linear splash is one fifth of the normal splash radius.
|
||||
let width = data.radius / 5;
|
||||
|
||||
// Effectivly rotate the axis to align with the missile direction.
|
||||
let parallelDist = relativePos.dot(data.direction); // z axis
|
||||
let 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);
|
||||
// Check that the unit is within the distance splash width of the line starting at the missile's
|
||||
// landing point which extends in the direction of the missile for length splash radius.
|
||||
if (parallelPos >= 0 && Math.abs(perpPos) < width) // If in radius, quadratic falloff in both directions
|
||||
damageMultiplier = (1 - parallelPos * parallelPos / (data.radius * data.radius)) *
|
||||
(1 - perpPos * perpPos / (width * width));
|
||||
else
|
||||
damageMultiplier = 0;
|
||||
}
|
||||
|
||||
@@ -17,116 +17,206 @@ Engine.LoadComponentScript("Attack.js");
|
||||
Engine.LoadComponentScript("Damage.js");
|
||||
Engine.LoadComponentScript("Timer.js");
|
||||
|
||||
let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage");
|
||||
let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer");
|
||||
cmpTimer.OnUpdate({ turnLength: 1 });
|
||||
let attacker = 11;
|
||||
let atkPlayerEntity = 1;
|
||||
let attackerOwner = 6;
|
||||
let cmpAttack = ConstructComponent(attacker, "Attack", { "Ranged": { "ProjectileSpeed": 500, "Spread": 0.5, "MaxRange": 50, "MinRange": 0 } } );
|
||||
let damage = 5;
|
||||
let target = 21;
|
||||
let targetOwner = 7;
|
||||
let targetPos = new Vector3D(3, 0, 3);
|
||||
|
||||
let type = "Melee";
|
||||
let damageTaken = false;
|
||||
|
||||
cmpAttack.GetAttackStrengths = (type) => ({ "hack": 0, "pierce": 0, "crush": damage });
|
||||
cmpAttack.GetAttackBonus = (type, target) => 1.0;
|
||||
|
||||
let data = {
|
||||
"attacker": attacker,
|
||||
"target": target,
|
||||
"type": "Melee",
|
||||
"strengths": { "hack": 0, "pierce": 0, "crush": damage },
|
||||
"multiplier": 1.0,
|
||||
"attackerOwner": attackerOwner,
|
||||
"position": targetPos,
|
||||
"isSplash": false,
|
||||
"projectileId": 9
|
||||
};
|
||||
|
||||
AddMock(atkPlayerEntity, IID_Player, {
|
||||
GetEnemies: () => [targetOwner]
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
|
||||
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) => {},
|
||||
});
|
||||
|
||||
AddMock(target, IID_Position, {
|
||||
GetPosition: () => targetPos,
|
||||
GetPreviousPosition: () => targetPos,
|
||||
GetPosition2D: () => new Vector2D(3, 3),
|
||||
IsInWorld: () => true,
|
||||
});
|
||||
|
||||
AddMock(target, IID_Health, {});
|
||||
|
||||
AddMock(target, IID_DamageReceiver, {
|
||||
TakeDamage: (hack, pierce, crush) => { damageTaken = true; return { "killed": false, "change": -crush }; },
|
||||
});
|
||||
|
||||
Engine.PostMessage = function(ent, iid, message)
|
||||
let Test_Generic = function()
|
||||
{
|
||||
TS_ASSERT_UNEVAL_EQUALS({ "attacker": attacker, "target": target, "type": type, "damage": damage, "attackerOwner": attackerOwner }, message);
|
||||
};
|
||||
ResetState();
|
||||
|
||||
AddMock(target, IID_Footprint, {
|
||||
GetShape: () => ({ "type": "circle", "radius": 20 }),
|
||||
});
|
||||
|
||||
AddMock(attacker, IID_Ownership, {
|
||||
GetOwner: () => attackerOwner,
|
||||
});
|
||||
|
||||
AddMock(attacker, IID_Position, {
|
||||
GetPosition: () => new Vector3D(2, 0, 3),
|
||||
GetRotation: () => new Vector3D(1, 2, 3),
|
||||
IsInWorld: () => true,
|
||||
});
|
||||
|
||||
function TestDamage()
|
||||
{
|
||||
let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage");
|
||||
let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer");
|
||||
cmpTimer.OnUpdate({ turnLength: 1 });
|
||||
TS_ASSERT(damageTaken);
|
||||
damageTaken = false;
|
||||
let attacker = 11;
|
||||
let atkPlayerEntity = 1;
|
||||
let attackerOwner = 6;
|
||||
let cmpAttack = ConstructComponent(attacker, "Attack", { "Ranged": { "ProjectileSpeed": 500, "Spread": 0.5, "MaxRange": 50, "MinRange": 0 } } );
|
||||
let damage = 5;
|
||||
let target = 21;
|
||||
let targetOwner = 7;
|
||||
let targetPos = new Vector3D(3, 0, 3);
|
||||
|
||||
let type = "Melee";
|
||||
let damageTaken = false;
|
||||
|
||||
cmpAttack.GetAttackStrengths = (type) => ({ "hack": 0, "pierce": 0, "crush": damage });
|
||||
cmpAttack.GetAttackBonus = (type, target) => 1.0;
|
||||
|
||||
let data = {
|
||||
"attacker": attacker,
|
||||
"target": target,
|
||||
"type": "Melee",
|
||||
"strengths": { "hack": 0, "pierce": 0, "crush": damage },
|
||||
"multiplier": 1.0,
|
||||
"attackerOwner": attackerOwner,
|
||||
"position": targetPos,
|
||||
"isSplash": false,
|
||||
"projectileId": 9,
|
||||
"direction": new Vector3D(1,0,0)
|
||||
};
|
||||
|
||||
AddMock(atkPlayerEntity, IID_Player, {
|
||||
GetEnemies: () => [targetOwner]
|
||||
});
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
|
||||
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) => {},
|
||||
});
|
||||
|
||||
AddMock(target, IID_Position, {
|
||||
GetPosition: () => targetPos,
|
||||
GetPreviousPosition: () => targetPos,
|
||||
GetPosition2D: () => new Vector2D(3, 3),
|
||||
IsInWorld: () => true,
|
||||
});
|
||||
|
||||
AddMock(target, IID_Health, {});
|
||||
|
||||
AddMock(target, IID_DamageReceiver, {
|
||||
TakeDamage: (hack, pierce, crush) => { damageTaken = true; return { "killed": false, "change": -crush }; },
|
||||
});
|
||||
|
||||
Engine.PostMessage = function(ent, iid, message)
|
||||
{
|
||||
TS_ASSERT_UNEVAL_EQUALS({ "attacker": attacker, "target": target, "type": type, "damage": damage, "attackerOwner": attackerOwner }, message);
|
||||
};
|
||||
|
||||
AddMock(target, IID_Footprint, {
|
||||
GetShape: () => ({ "type": "circle", "radius": 20 }),
|
||||
});
|
||||
|
||||
AddMock(attacker, IID_Ownership, {
|
||||
GetOwner: () => attackerOwner,
|
||||
});
|
||||
|
||||
AddMock(attacker, IID_Position, {
|
||||
GetPosition: () => new Vector3D(2, 0, 3),
|
||||
GetRotation: () => new Vector3D(1, 2, 3),
|
||||
IsInWorld: () => true,
|
||||
});
|
||||
|
||||
function TestDamage()
|
||||
{
|
||||
cmpTimer.OnUpdate({ turnLength: 1 });
|
||||
TS_ASSERT(damageTaken);
|
||||
damageTaken = false;
|
||||
}
|
||||
|
||||
cmpDamage.CauseDamage(data);
|
||||
TestDamage();
|
||||
|
||||
type = data.type = "Ranged";
|
||||
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);
|
||||
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]);
|
||||
}
|
||||
|
||||
cmpDamage.CauseDamage(data);
|
||||
TestDamage();
|
||||
Test_Generic();
|
||||
|
||||
type = data.type = "Ranged";
|
||||
cmpDamage.CauseDamage(data);
|
||||
TestDamage();
|
||||
let TestLinearSplashDamage = function()
|
||||
{
|
||||
ResetState();
|
||||
Engine.PostMessage = (ent, iid, message) => {};
|
||||
|
||||
data.friendlyFire = false;
|
||||
data.range = 10;
|
||||
data.shape = "Circular";
|
||||
data.isSplash = true;
|
||||
cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", 1000, data);
|
||||
TestDamage();
|
||||
const attacker = 50;
|
||||
const attackerOwner = 1;
|
||||
|
||||
// Check for damage still being dealt if the attacker dies
|
||||
cmpAttack.PerformAttack("Ranged", target);
|
||||
Engine.DestroyEntity(attacker);
|
||||
TestDamage();
|
||||
const origin = new Vector2D(0, 0);
|
||||
|
||||
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]);
|
||||
let data = {
|
||||
"attacker": attacker,
|
||||
"origin": origin,
|
||||
"radius": 10,
|
||||
"shape": "Linear",
|
||||
"strengths": { "hack" : 100, "pierce" : 0, "crush": 0 },
|
||||
"direction": new Vector3D(1, 747, 0),
|
||||
"playersToDamage": [2],
|
||||
"type": "Ranged",
|
||||
"attackerOwner": attackerOwner
|
||||
};
|
||||
|
||||
let fallOff = function(x,y)
|
||||
{
|
||||
return (1 - x * x / (data.radius * data.radius)) * (1 - 25 * y * y / (data.radius * data.radius));
|
||||
}
|
||||
|
||||
let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage");
|
||||
|
||||
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
|
||||
ExecuteQueryAroundPos: () => [60, 61, 62],
|
||||
});
|
||||
|
||||
AddMock(60, IID_Position, {
|
||||
GetPosition2D: () => new Vector2D(2.2, -0.4),
|
||||
});
|
||||
|
||||
AddMock(61, IID_Position, {
|
||||
GetPosition2D: () => new Vector2D(0, 0),
|
||||
});
|
||||
|
||||
AddMock(62, IID_Position, {
|
||||
GetPosition2D: () => new Vector2D(5, 2),
|
||||
});
|
||||
|
||||
AddMock(60, IID_DamageReceiver, {
|
||||
TakeDamage: (hack, pierce, crush) => {
|
||||
TS_ASSERT_EQUALS(hack + pierce + crush, 100 * fallOff(2.2, -0.4));
|
||||
return { "killed": false, "change": -(hack + pierce + crush) };
|
||||
}
|
||||
});
|
||||
|
||||
AddMock(61, IID_DamageReceiver, {
|
||||
TakeDamage: (hack, pierce, crush) => {
|
||||
TS_ASSERT_EQUALS(hack + pierce + crush, 100 * fallOff(0, 0));
|
||||
return { "killed": false, "change": -(hack + pierce + crush) };
|
||||
}
|
||||
});
|
||||
|
||||
AddMock(62, IID_DamageReceiver, {
|
||||
TakeDamage: (hack, pierce, crush) => {
|
||||
TS_ASSERT_EQUALS(hack + pierce + crush, 0);
|
||||
return { "killed": false, "change": -(hack + pierce + crush) };
|
||||
}
|
||||
});
|
||||
|
||||
cmpDamage.CauseSplashDamage(data);
|
||||
|
||||
data.direction = new Vector3D(0.6, 747, 0.8);
|
||||
|
||||
AddMock(60, IID_DamageReceiver, {
|
||||
TakeDamage: (hack, pierce, crush) => {
|
||||
TS_ASSERT_EQUALS(hack + pierce + crush, 100 * fallOff(1, 2));
|
||||
return { "killed": false, "change": -(hack + pierce + crush) };
|
||||
}
|
||||
});
|
||||
|
||||
cmpDamage.CauseSplashDamage(data);
|
||||
};
|
||||
|
||||
TestLinearSplashDamage();
|
||||
|
||||
Reference in New Issue
Block a user