mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-08-15 14:43:32 -07:00
Add tests for CanEverReachTarget and its helpers
This commit is contained in:
parent
a373c0e4ef
commit
41076070a6
2 changed files with 175 additions and 0 deletions
|
|
@ -429,3 +429,100 @@ function testAttackPreference()
|
||||||
}
|
}
|
||||||
testAttackPreference();
|
testAttackPreference();
|
||||||
|
|
||||||
|
function testCanEverReachTarget()
|
||||||
|
{
|
||||||
|
const attacker = ++entityID;
|
||||||
|
|
||||||
|
AddMock(attacker, IID_Position, {
|
||||||
|
"IsInWorld": () => true,
|
||||||
|
"GetHeightOffset": () => 0,
|
||||||
|
"GetPosition2D": () => new Vector2D(1, 2)
|
||||||
|
});
|
||||||
|
|
||||||
|
const cmpAttack = ConstructComponent(attacker, "Attack", {
|
||||||
|
"Melee": {
|
||||||
|
"Damage": { "Hack": 10, "Pierce": 0, "Crush": 0 },
|
||||||
|
"MaxRange": 5
|
||||||
|
},
|
||||||
|
"Ranged": {
|
||||||
|
"Damage": { "Hack": 0, "Pierce": 10, "Crush": 0 },
|
||||||
|
"MaxRange": 30,
|
||||||
|
"Projectile": { "Speed": 50, "Spread": 1, "Gravity": 1, "FriendlyFire": "false" }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Melee target within 3D range
|
||||||
|
{
|
||||||
|
const defender = ++entityID;
|
||||||
|
AddMock(defender, IID_Position, {
|
||||||
|
"IsInWorld": () => true,
|
||||||
|
"GetHeightOffset": () => 0
|
||||||
|
});
|
||||||
|
TS_ASSERT_EQUALS(cmpAttack.CanEverReachTarget(defender, "Melee"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Melee target too high
|
||||||
|
{
|
||||||
|
const defender = ++entityID;
|
||||||
|
AddMock(defender, IID_Position, {
|
||||||
|
"IsInWorld": () => true,
|
||||||
|
"GetHeightOffset": () => 10
|
||||||
|
});
|
||||||
|
TS_ASSERT_EQUALS(cmpAttack.CanEverReachTarget(defender, "Melee"), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Melee target at same height, within range (close distance)
|
||||||
|
{
|
||||||
|
const defender = ++entityID;
|
||||||
|
AddMock(defender, IID_Position, {
|
||||||
|
"IsInWorld": () => true,
|
||||||
|
"GetHeightOffset": () => 4
|
||||||
|
});
|
||||||
|
// sqrt(0² + 4²) = 4 <= 5
|
||||||
|
TS_ASSERT_EQUALS(cmpAttack.CanEverReachTarget(defender, "Melee"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ranged: target at same height — reachable from current position (check 1)
|
||||||
|
{
|
||||||
|
const defender = ++entityID;
|
||||||
|
AddMock(defender, IID_Position, {
|
||||||
|
"IsInWorld": () => true,
|
||||||
|
"GetHeightOffset": () => 0,
|
||||||
|
"GetPosition": () => new Vector3D(1, 0, 2)
|
||||||
|
});
|
||||||
|
// Need RangeManager mock for IsTargetInRange (check 1) to work
|
||||||
|
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
|
||||||
|
"GetEffectiveParabolicRange": () => 25,
|
||||||
|
"GetMaxReachableParabolicHeight": () => 15
|
||||||
|
});
|
||||||
|
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
|
||||||
|
"IsInTargetRange": () => true
|
||||||
|
});
|
||||||
|
TS_ASSERT_EQUALS(cmpAttack.CanEverReachTarget(defender, "Ranged"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ranged: target too high for parabolic arc even at closest approach (check 2)
|
||||||
|
{
|
||||||
|
const defender = ++entityID;
|
||||||
|
AddMock(defender, IID_Position, {
|
||||||
|
"IsInWorld": () => true,
|
||||||
|
"GetHeightOffset": () => 20,
|
||||||
|
"GetPosition": () => new Vector3D(1, 20, 2)
|
||||||
|
});
|
||||||
|
|
||||||
|
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
|
||||||
|
"GetEffectiveParabolicRange": () => -1, // out of range
|
||||||
|
"GetMaxReachableParabolicHeight": () => 10
|
||||||
|
});
|
||||||
|
|
||||||
|
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
|
||||||
|
"IsInTargetRange": () => false
|
||||||
|
});
|
||||||
|
|
||||||
|
// heightDiff = 20 - 0 = 20, maxReachableHeightDiff = 10 → unreachable
|
||||||
|
TS_ASSERT_EQUALS(cmpAttack.CanEverReachTarget(defender, "Ranged"), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testCanEverReachTarget();
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ const enemyPlayer = 2;
|
||||||
const alliedPlayer = 3;
|
const alliedPlayer = 3;
|
||||||
const turretHolderID = 9;
|
const turretHolderID = 9;
|
||||||
const entitiesToTest = [10, 11, 12, 13];
|
const entitiesToTest = [10, 11, 12, 13];
|
||||||
|
let entityID = 100;
|
||||||
|
|
||||||
AddMock(turretHolderID, IID_Ownership, {
|
AddMock(turretHolderID, IID_Ownership, {
|
||||||
"GetOwner": () => player
|
"GetOwner": () => player
|
||||||
|
|
@ -244,3 +245,80 @@ cmpTurretHolder.OnOwnershipChanged({
|
||||||
"from": INVALID_PLAYER
|
"from": INVALID_PLAYER
|
||||||
});
|
});
|
||||||
TS_ASSERT(cmpTurretHolder.OccupiesTurretPoint(spawned));
|
TS_ASSERT(cmpTurretHolder.OccupiesTurretPoint(spawned));
|
||||||
|
|
||||||
|
// Test GetClosestApproachDistanceToTurretPoint
|
||||||
|
{
|
||||||
|
const holder = ++entityID;
|
||||||
|
|
||||||
|
// Mock the holder's obstruction
|
||||||
|
AddMock(holder, IID_Obstruction, {
|
||||||
|
"GetBlockMovementFlag": () => true,
|
||||||
|
"GetObstructionHalfSizes": () => ({ "x": 10, "y": 15 })
|
||||||
|
});
|
||||||
|
|
||||||
|
const cmpHolder = ConstructComponent(holder, "TurretHolder", {
|
||||||
|
"TurretPoints": {
|
||||||
|
"center": {
|
||||||
|
"X": "0",
|
||||||
|
"Y": "5.0",
|
||||||
|
"Z": "0"
|
||||||
|
},
|
||||||
|
"edge": {
|
||||||
|
"X": "8.0",
|
||||||
|
"Y": "5.0",
|
||||||
|
"Z": "0"
|
||||||
|
},
|
||||||
|
"corner": {
|
||||||
|
"X": "10.0",
|
||||||
|
"Y": "5.0",
|
||||||
|
"Z": "15.0"
|
||||||
|
},
|
||||||
|
"outside": {
|
||||||
|
"X": "15.0",
|
||||||
|
"Y": "5.0",
|
||||||
|
"Z": "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Center point (0,0) in 20x30 building → min(10, 15) = 10
|
||||||
|
TS_ASSERT_EQUALS(cmpHolder.GetClosestApproachDistanceToTurretPoint("center"), 10);
|
||||||
|
|
||||||
|
// Edge point (8,0) in 20x30 building → min(10-8, 15-0) = 2
|
||||||
|
TS_ASSERT_EQUALS(cmpHolder.GetClosestApproachDistanceToTurretPoint("edge"), 2);
|
||||||
|
|
||||||
|
// Corner point (10,15) in 20x30 building → min(10-10, 15-15) = 0
|
||||||
|
TS_ASSERT_EQUALS(cmpHolder.GetClosestApproachDistanceToTurretPoint("corner"), 0);
|
||||||
|
|
||||||
|
// Outside point (15,0) in 20x30 building → min(10-15, 15-0) = -5 → clamped to 0
|
||||||
|
TS_ASSERT_EQUALS(cmpHolder.GetClosestApproachDistanceToTurretPoint("outside"), 0);
|
||||||
|
|
||||||
|
// Nonexistent turret point
|
||||||
|
TS_ASSERT_EQUALS(cmpHolder.GetClosestApproachDistanceToTurretPoint("nonexistent"), 0);
|
||||||
|
|
||||||
|
// Pass object directly
|
||||||
|
const turretPoint = cmpHolder.TurretPointByName("center");
|
||||||
|
TS_ASSERT_EQUALS(cmpHolder.GetClosestApproachDistanceToTurretPoint(turretPoint), 10);
|
||||||
|
|
||||||
|
// Passable building (no obstruction or doesn't block movement)
|
||||||
|
const passableHolder = ++entityID;
|
||||||
|
AddMock(passableHolder, IID_Obstruction, {
|
||||||
|
"GetBlockMovementFlag": () => false
|
||||||
|
});
|
||||||
|
const cmpHolderPassable = ConstructComponent(passableHolder, "TurretHolder", {
|
||||||
|
"TurretPoints": {
|
||||||
|
"center": { "X": "0", "Y": "5.0", "Z": "0" }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
TS_ASSERT_EQUALS(cmpHolderPassable.GetClosestApproachDistanceToTurretPoint("center"), 0);
|
||||||
|
|
||||||
|
// No obstruction component at all
|
||||||
|
++entityID;
|
||||||
|
const cmpHolderNoObst = ConstructComponent(entityID, "TurretHolder", {
|
||||||
|
"TurretPoints": {
|
||||||
|
"center": { "X": "0", "Y": "5.0", "Z": "0" }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
TS_ASSERT_EQUALS(cmpHolderNoObst.GetClosestApproachDistanceToTurretPoint("center"), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue