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:
fatherbushido
2017-08-10 08:10:09 +00:00
parent ced0a71782
commit 1fc4f55e38
2 changed files with 205 additions and 112 deletions
@@ -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();