Improve splash damage falloff calculation to account for obstruction size.

Splash falloff was calculated using centre-to-point distance, where
nearest-edge to nearest-edge ought to have been used.
Use DistanceToPoint to correct for that.

Make sure the damage multiplier cannot go negative.
Remove GetUnitSize in favour of GetSize.

Reviewed By: bb
Differential Revision: https://code.wildfiregames.com/D2963
This was SVN commit r24010.
This commit is contained in:
wraitii 2020-08-31 15:01:04 +00:00
parent 32886202a3
commit e916b8cc01
11 changed files with 91 additions and 31 deletions

View File

@ -54,7 +54,7 @@ Builder.prototype.GetRange = function()
let max = 2;
let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
if (cmpObstruction)
max += cmpObstruction.GetUnitRadius();
max += cmpObstruction.GetSize();
return { "max": max, "min": 0 };
};

View File

@ -296,7 +296,7 @@ GarrisonHolder.prototype.PerformEject = function(entities, forced)
if (failedRadius !== undefined)
{
let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction);
radius = cmpObstruction ? cmpObstruction.GetUnitRadius() : 0;
radius = cmpObstruction ? cmpObstruction.GetSize() : 0;
if (radius >= failedRadius)
continue;
}
@ -315,7 +315,7 @@ GarrisonHolder.prototype.PerformEject = function(entities, forced)
else
{
let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction);
failedRadius = cmpObstruction ? cmpObstruction.GetUnitRadius() : 0;
failedRadius = cmpObstruction ? cmpObstruction.GetSize() : 0;
}
}
}

View File

@ -298,7 +298,7 @@ Trader.prototype.GetRange = function()
let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
let max = 1;
if (cmpObstruction)
max += cmpObstruction.GetUnitRadius()*1.5;
max += cmpObstruction.GetSize() * 1.5;
return { "min": 0, "max": max };
};

View File

@ -77,7 +77,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmpBuilder.GetEntitiesList(), ["structures/athen_civil_c
TS_ASSERT_UNEVAL_EQUALS(cmpBuilder.GetRange(), { "max": 2, "min": 0 });
AddMock(builderId, IID_Obstruction, {
"GetUnitRadius": () => 1.0
"GetSize": () => 1
});
TS_ASSERT_UNEVAL_EQUALS(cmpBuilder.GetRange(), { "max": 3, "min": 0 });

View File

@ -196,8 +196,16 @@ function TestLinearSplashDamage()
"ExecuteQueryAroundPos": () => [60, 61, 62],
});
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
"DistanceToPoint": (ent) => ({
"60": Math.sqrt(9.25),
"61": 0,
"62": Math.sqrt(29)
}[ent])
});
AddMock(60, IID_Position, {
"GetPosition2D": () => new Vector2D(2.2, -0.4),
"GetPosition2D": () => new Vector2D(3, -0.5),
});
AddMock(61, IID_Position, {
@ -211,7 +219,7 @@ function TestLinearSplashDamage()
AddMock(60, IID_Health, {
"TakeDamage": (amount, __, ___) => {
hitEnts.add(60);
TS_ASSERT_EQUALS(amount, 100 * fallOff(2.2, -0.4));
TS_ASSERT_EQUALS(amount, 100 * fallOff(3, -0.5));
return { "healthChange": -amount };
}
});
@ -227,7 +235,8 @@ function TestLinearSplashDamage()
AddMock(62, IID_Health, {
"TakeDamage": (amount, __, ___) => {
hitEnts.add(62);
TS_ASSERT_EQUALS(amount, 0);
// Minor numerical precision issues make this necessary
TS_ASSERT(amount < 0.00001);
return { "healthChange": -amount };
}
});
@ -280,7 +289,18 @@ function TestCircularSplashDamage()
});
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
"ExecuteQueryAroundPos": () => [60, 61, 62, 64],
"ExecuteQueryAroundPos": () => [60, 61, 62, 64, 65],
});
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
"DistanceToPoint": (ent, x, z) => ({
"60": 0,
"61": 5,
"62": 1,
"63": Math.sqrt(85),
"64": 10,
"65": 2
}[ent])
});
AddMock(60, IID_Position, {
@ -299,11 +319,16 @@ function TestCircularSplashDamage()
"GetPosition2D": () => new Vector2D(10, -10),
});
// Target on the frontier of the shape
// Target on the frontier of the shape (see distance above).
AddMock(64, IID_Position, {
"GetPosition2D": () => new Vector2D(9, -4),
});
// Big target far away (see distance above).
AddMock(65, IID_Position, {
"GetPosition2D": () => new Vector2D(23, 4),
});
AddMock(60, IID_Health, {
"TakeDamage": (amount, __, ___) => {
TS_ASSERT_EQUALS(amount, 100 * fallOff(0));
@ -331,13 +356,21 @@ function TestCircularSplashDamage()
}
});
let cmphealth = AddMock(64, IID_Health, {
let cmphealth64 = AddMock(64, IID_Health, {
"TakeDamage": (amount, __, ___) => {
TS_ASSERT_EQUALS(amount, 0);
return { "healthChange": -amount };
}
});
let spy = new Spy(cmphealth, "TakeDamage");
let spy64 = new Spy(cmphealth64, "TakeDamage");
let cmpHealth65 = AddMock(65, IID_Health, {
"TakeDamage": (amount, __, ___) => {
TS_ASSERT_EQUALS(amount, 100 * fallOff(2));
return { "healthChange": -amount };
}
});
let spy65 = new Spy(cmpHealth65, "TakeDamage");
Attacking.CauseDamageOverArea({
"type": "Ranged",
@ -350,7 +383,8 @@ function TestCircularSplashDamage()
"friendlyFire": false,
});
TS_ASSERT_EQUALS(spy._called, 1);
TS_ASSERT_EQUALS(spy64._called, 1);
TS_ASSERT_EQUALS(spy65._called, 1);
}
TestCircularSplashDamage();
@ -437,6 +471,10 @@ function Test_MissileHit()
"GetParent": () => 61
});
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
"DistanceToPoint": (ent) => 0
});
AddMock(61, IID_Position, {
"GetPosition": () => targetPos,
"GetPreviousPosition": () => targetPos,
@ -500,6 +538,13 @@ function Test_MissileHit()
"ExecuteQueryAroundPos": () => [61, 62]
});
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
"DistanceToPoint": (ent) => ({
"61": 0,
"62": 5
}[ent])
});
let dealtDamage = 0;
AddMock(61, IID_Health, {
"TakeDamage": (amount, __, ___) => {
@ -542,6 +587,10 @@ function Test_MissileHit()
"ExecuteQueryAroundPos": () => [61]
});
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
"DistanceToPoint": (ent) => 0
});
let bonus= { "BonusCav": { "Classes": "Cavalry", "Multiplier": 400 } };
let splashBonus = { "BonusCav": { "Classes": "Cavalry", "Multiplier": 10000 } };
@ -596,6 +645,13 @@ function Test_MissileHit()
"ExecuteQueryAroundPos": () => [61, 62]
});
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
"DistanceToPoint": (ent) => ({
"61": 0,
"62": 5
}[ent])
});
dealtDamage = 0;
AddMock(61, IID_Health, {
"TakeDamage": (amount, __, ___) => {

View File

@ -293,16 +293,22 @@ Attacking.prototype.CauseDamageOverArea = function(data)
this.GetPlayersToDamage(data.attackerOwner, data.friendlyFire));
let damageMultiplier = 1;
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
// Cycle through all the nearby entities and damage it appropriately based on its distance from the origin.
for (let ent of nearEnts)
{
let entityPosition = Engine.QueryInterface(ent, IID_Position).GetPosition2D();
// Correct somewhat for the entity's obstruction radius.
// TODO: linear falloff should arguably use something cleverer.
let distance = cmpObstructionManager.DistanceToPoint(ent, data.origin.x, data.origin.y);
if (data.shape == 'Circular') // circular effect with quadratic falloff in every direction
damageMultiplier = 1 - data.origin.distanceToSquared(entityPosition) / (data.radius * data.radius);
damageMultiplier = 1 - distance * distance / (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.
let relativePos = entityPosition.sub(data.origin);
// The entity has a position here since it was returned by the range manager.
let entityPosition = Engine.QueryInterface(ent, IID_Position).GetPosition2D();
let relativePos = entityPosition.sub(data.origin).normalize().mult(distance);
// Get the position relative to the missile direction.
let direction = Vector2D.from3D(data.direction);
@ -324,6 +330,9 @@ Attacking.prototype.CauseDamageOverArea = function(data)
{
warn("The " + data.shape + " splash damage shape is not implemented!");
}
// The RangeManager can return units that are too far away (due to approximations there)
// so the multiplier can end up below 0.
damageMultiplier = Math.max(0, damageMultiplier);
this.HandleAttackEffects(ent, data.type + ".Splash", data.attackData, data.attacker, data.attackerOwner, damageMultiplier);
}

View File

@ -169,7 +169,7 @@ public:
CmpPtr<ICmpObstruction> cmpSpawnedObstruction(GetSimContext(), spawned);
if (cmpSpawnedObstruction)
{
spawnedRadius = cmpSpawnedObstruction->GetUnitRadius();
spawnedRadius = cmpSpawnedObstruction->GetSize();
spawnedTag = cmpSpawnedObstruction->GetObstruction();
}
@ -292,7 +292,7 @@ public:
CmpPtr<ICmpObstruction> cmpSpawnedObstruction(GetSimContext(), spawned);
if (cmpSpawnedObstruction)
{
spawnedRadius = cmpSpawnedObstruction->GetUnitRadius();
spawnedRadius = cmpSpawnedObstruction->GetSize();
spawnedTag = cmpSpawnedObstruction->GetObstruction();
}
// else use zero radius

View File

@ -503,14 +503,6 @@ public:
return true;
}
virtual entity_pos_t GetUnitRadius() const
{
if (m_Type == UNIT)
return m_Clearance;
else
return entity_pos_t::Zero();
}
virtual entity_pos_t GetSize() const
{
if (m_Type == UNIT)

View File

@ -46,7 +46,7 @@ std::string ICmpObstruction::CheckFoundation_wrapper(const std::string& classNam
}
BEGIN_INTERFACE_WRAPPER(Obstruction)
DEFINE_INTERFACE_METHOD_CONST_0("GetUnitRadius", entity_pos_t, ICmpObstruction, GetUnitRadius)
DEFINE_INTERFACE_METHOD_CONST_0("GetSize", entity_pos_t, ICmpObstruction, GetSize)
DEFINE_INTERFACE_METHOD_CONST_0("CheckShorePlacement", bool, ICmpObstruction, CheckShorePlacement)
DEFINE_INTERFACE_METHOD_CONST_2("CheckFoundation", std::string, ICmpObstruction, CheckFoundation_wrapper, std::string, bool)
DEFINE_INTERFACE_METHOD_CONST_0("CheckDuplicateFoundation", bool, ICmpObstruction, CheckDuplicateFoundation)

View File

@ -58,12 +58,16 @@ public:
*/
virtual bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare& out) const = 0;
/**
* @return the size of the obstruction (either the clearance or a circumscribed circle).
*/
virtual entity_pos_t GetSize() const = 0;
/**
* @return the size of the static obstruction or (0,0) for a unit shape.
*/
virtual CFixedVector2D GetStaticSize() const = 0;
virtual entity_pos_t GetUnitRadius() const = 0;
virtual EObstructionType GetObstructionType() const = 0;
virtual void SetUnitClearance(const entity_pos_t& clearance) = 0;

View File

@ -31,7 +31,6 @@ public:
virtual bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare& UNUSED(out)) const { return true; }
virtual entity_pos_t GetSize() const { return entity_pos_t::Zero(); }
virtual CFixedVector2D GetStaticSize() const { return CFixedVector2D(); }
virtual entity_pos_t GetUnitRadius() const { return entity_pos_t::Zero(); }
virtual EObstructionType GetObstructionType() const { return ICmpObstruction::STATIC; }
virtual void SetUnitClearance(const entity_pos_t& UNUSED(clearance)) { }
virtual bool IsControlPersistent() const { return true; }