Index: ps/trunk/binaries/data/mods/public/simulation/components/Builder.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Builder.js +++ ps/trunk/binaries/data/mods/public/simulation/components/Builder.js @@ -54,7 +54,7 @@ let max = 2; let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); if (cmpObstruction) - max += cmpObstruction.GetUnitRadius(); + max += cmpObstruction.GetSize(); return { "max": max, "min": 0 }; }; Index: ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js +++ ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js @@ -296,7 +296,7 @@ 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 @@ else { let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction); - failedRadius = cmpObstruction ? cmpObstruction.GetUnitRadius() : 0; + failedRadius = cmpObstruction ? cmpObstruction.GetSize() : 0; } } } Index: ps/trunk/binaries/data/mods/public/simulation/components/Trader.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Trader.js +++ ps/trunk/binaries/data/mods/public/simulation/components/Trader.js @@ -298,7 +298,7 @@ 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 }; }; Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Builder.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Builder.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Builder.js @@ -77,7 +77,7 @@ 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 }); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Damage.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Damage.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Damage.js @@ -196,8 +196,16 @@ "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 @@ 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 @@ 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 @@ }); 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 @@ "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 @@ } }); - 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 @@ "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 @@ "GetParent": () => 61 }); + AddMock(SYSTEM_ENTITY, IID_ObstructionManager, { + "DistanceToPoint": (ent) => 0 + }); + AddMock(61, IID_Position, { "GetPosition": () => targetPos, "GetPreviousPosition": () => targetPos, @@ -500,6 +538,13 @@ "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 @@ "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 @@ "ExecuteQueryAroundPos": () => [61, 62] }); + AddMock(SYSTEM_ENTITY, IID_ObstructionManager, { + "DistanceToPoint": (ent) => ({ + "61": 0, + "62": 5 + }[ent]) + }); + dealtDamage = 0; AddMock(61, IID_Health, { "TakeDamage": (amount, __, ___) => { Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Attacking.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/helpers/Attacking.js +++ ps/trunk/binaries/data/mods/public/simulation/helpers/Attacking.js @@ -293,16 +293,22 @@ 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 @@ { 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); } Index: ps/trunk/source/simulation2/components/CCmpFootprint.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpFootprint.cpp +++ ps/trunk/source/simulation2/components/CCmpFootprint.cpp @@ -169,7 +169,7 @@ CmpPtr cmpSpawnedObstruction(GetSimContext(), spawned); if (cmpSpawnedObstruction) { - spawnedRadius = cmpSpawnedObstruction->GetUnitRadius(); + spawnedRadius = cmpSpawnedObstruction->GetSize(); spawnedTag = cmpSpawnedObstruction->GetObstruction(); } @@ -292,7 +292,7 @@ CmpPtr cmpSpawnedObstruction(GetSimContext(), spawned); if (cmpSpawnedObstruction) { - spawnedRadius = cmpSpawnedObstruction->GetUnitRadius(); + spawnedRadius = cmpSpawnedObstruction->GetSize(); spawnedTag = cmpSpawnedObstruction->GetObstruction(); } // else use zero radius Index: ps/trunk/source/simulation2/components/CCmpObstruction.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpObstruction.cpp +++ ps/trunk/source/simulation2/components/CCmpObstruction.cpp @@ -503,14 +503,6 @@ 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) Index: ps/trunk/source/simulation2/components/ICmpObstruction.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpObstruction.h +++ ps/trunk/source/simulation2/components/ICmpObstruction.h @@ -58,12 +58,16 @@ */ 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; Index: ps/trunk/source/simulation2/components/ICmpObstruction.cpp =================================================================== --- ps/trunk/source/simulation2/components/ICmpObstruction.cpp +++ ps/trunk/source/simulation2/components/ICmpObstruction.cpp @@ -46,7 +46,7 @@ } 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) Index: ps/trunk/source/simulation2/components/tests/test_ObstructionManager.h =================================================================== --- ps/trunk/source/simulation2/components/tests/test_ObstructionManager.h +++ ps/trunk/source/simulation2/components/tests/test_ObstructionManager.h @@ -31,7 +31,6 @@ 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; }