forked from 0ad/0ad
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:
parent
32886202a3
commit
e916b8cc01
@ -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 };
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 };
|
||||
};
|
||||
|
||||
|
@ -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 });
|
||||
|
@ -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, __, ___) => {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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; }
|
||||
|
Loading…
Reference in New Issue
Block a user