Engine.LoadHelperScript("Player.js"); Engine.LoadComponentScript("interfaces/Diplomacy.js"); Engine.LoadComponentScript("interfaces/TurretHolder.js"); Engine.LoadComponentScript("interfaces/Turretable.js"); Engine.LoadComponentScript("interfaces/UnitAI.js"); Engine.LoadComponentScript("TurretHolder.js"); Engine.LoadComponentScript("Turretable.js"); AddMock(SYSTEM_ENTITY, IID_PlayerManager, { "GetPlayerByID": id => id }); const player = 1; const enemyPlayer = 2; const alliedPlayer = 3; const turretHolderID = 9; const entitiesToTest = [10, 11, 12, 13]; let entityID = 100; AddMock(turretHolderID, IID_Ownership, { "GetOwner": () => player }); AddMock(turretHolderID, IID_Position, { "GetPosition": () => new Vector3D(4, 3, 25), "GetRotation": () => new Vector3D(4, 0, 6), "IsInWorld": () => true }); for (const entity of entitiesToTest) { AddMock(entity, IID_Position, { "GetPosition": () => new Vector3D(4, 3, 25), "GetRotation": () => new Vector3D(4, 0, 6), "SetTurretParent": (parent, offset) => {}, "IsInWorld": () => true }); AddMock(entity, IID_Ownership, { "GetOwner": () => player }); } AddMock(player, IID_Diplomacy, { "IsAlly": id => id != enemyPlayer, "IsMutualAlly": id => id != enemyPlayer, }); AddMock(alliedPlayer, IID_Diplomacy, { "IsAlly": id => true, "IsMutualAlly": id => true, }); let cmpTurretHolder = ConstructComponent(turretHolderID, "TurretHolder", { "TurretPoints": { "archer1": { "X": "12.0", "Y": "5.", "Z": "6.0" }, "archer2": { "X": "15.0", "Y": "5.0", "Z": "6.0", "AllowedClasses": { "_string": "Siege Trader" } }, "archer3": { "X": "15.0", "Y": "5.0", "Z": "6.0", "AllowedClasses": { "_string": "Siege Infantry+Ranged Infantry+Cavalry" } } } }); const siegeEngineID = entitiesToTest[0]; AddMock(siegeEngineID, IID_Identity, { "GetClassesList": () => ["Siege"] }); const archerID = entitiesToTest[1]; AddMock(archerID, IID_Identity, { "GetClassesList": () => ["Infantry", "Ranged"] }); const cavID = entitiesToTest[2]; AddMock(cavID, IID_Identity, { "GetClassesList": () => ["Infantry", "Cavalry"] }); const infID = entitiesToTest[3]; AddMock(infID, IID_Identity, { "GetClassesList": () => ["Infantry"] }); // Test visible garrisoning restrictions. TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(siegeEngineID, cmpTurretHolder.turretPoints[0]), true); TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(siegeEngineID, cmpTurretHolder.turretPoints[1]), true); TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(siegeEngineID, cmpTurretHolder.turretPoints[2]), true); TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(archerID, cmpTurretHolder.turretPoints[0]), true); TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(archerID, cmpTurretHolder.turretPoints[1]), false); TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(archerID, cmpTurretHolder.turretPoints[2]), true); TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(cavID, cmpTurretHolder.turretPoints[0]), true); TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(cavID, cmpTurretHolder.turretPoints[1]), false); TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(cavID, cmpTurretHolder.turretPoints[2]), true); TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(infID, cmpTurretHolder.turretPoints[0]), true); TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(infID, cmpTurretHolder.turretPoints[1]), false); TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(infID, cmpTurretHolder.turretPoints[2]), false); // Test forReplacement parameter - empty turret points should allow both TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(siegeEngineID, cmpTurretHolder.turretPoints[0], false), true); TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(siegeEngineID, cmpTurretHolder.turretPoints[0], true), true); // Now occupy some turret points to test replacement logic TS_ASSERT(cmpTurretHolder.OccupyTurretPoint(archerID, cmpTurretHolder.turretPoints[0])); TS_ASSERT(cmpTurretHolder.OccupyTurretPoint(cavID, cmpTurretHolder.turretPoints[2])); // Test that occupied turrets block normal occupation but allow replacement TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(siegeEngineID, cmpTurretHolder.turretPoints[0], false), false); TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(siegeEngineID, cmpTurretHolder.turretPoints[0], true), true); TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(infID, cmpTurretHolder.turretPoints[2], false), false); TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(infID, cmpTurretHolder.turretPoints[2], true), false); // Still false due to class restriction // Test that class restrictions still apply even for replacement TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(infID, cmpTurretHolder.turretPoints[1], false), false); TS_ASSERT_EQUALS(cmpTurretHolder.AllowedToOccupyTurretPoint(infID, cmpTurretHolder.turretPoints[1], true), false); // Still false due to class restriction // Clean up for subsequent tests TS_ASSERT(cmpTurretHolder.LeaveTurretPoint(archerID)); TS_ASSERT(cmpTurretHolder.LeaveTurretPoint(cavID)); // Test that one cannot leave a turret that is not occupied. TS_ASSERT(!cmpTurretHolder.LeaveTurretPoint(archerID)); // Test occupying a turret. TS_ASSERT(!cmpTurretHolder.OccupiesTurretPoint(archerID)); TS_ASSERT(cmpTurretHolder.OccupyTurretPoint(archerID)); TS_ASSERT(cmpTurretHolder.OccupiesTurretPoint(archerID)); // We're not occupying a turret that we can't occupy. TS_ASSERT(!cmpTurretHolder.OccupiesTurretPoint(archerID, cmpTurretHolder.turretPoints[1])); TS_ASSERT(!cmpTurretHolder.OccupyTurretPoint(cavID, cmpTurretHolder.turretPoints[1])); TS_ASSERT(!cmpTurretHolder.OccupyTurretPoint(cavID, cmpTurretHolder.turretPoints[0])); TS_ASSERT(cmpTurretHolder.OccupyTurretPoint(cavID, cmpTurretHolder.turretPoints[2])); // Leave turrets. TS_ASSERT(cmpTurretHolder.LeaveTurretPoint(archerID)); TS_ASSERT(!cmpTurretHolder.LeaveTurretPoint(cavID, false, cmpTurretHolder.turretPoints[1])); TS_ASSERT(cmpTurretHolder.LeaveTurretPoint(cavID, false, cmpTurretHolder.turretPoints[2])); // Incremental Turret creation. cmpTurretHolder = ConstructComponent(turretHolderID, "TurretHolder", { "TurretPoints": { "Turret": { "X": "15.0", "Y": "5.0", "Z": "6.0", "Template": "units/iber/cavalry_javelineer_c" } } }); let spawned = 100; Engine.AddEntity = function() { ++spawned; if (spawned > 101) { ConstructComponent(spawned, "Turretable", {}); } if (spawned > 102) { AddMock(spawned, IID_Ownership, { "GetOwner": () => player, "SetOwner": () => {} }); } if (spawned > 103) { AddMock(spawned, IID_Position, { "GetPosition": () => new Vector3D(4, 3, 25), "GetRotation": () => new Vector3D(4, 0, 6), "SetTurretParent": () => {}, "IsInWorld": () => true }); } return spawned; }; const GetUpgradedTemplate = (_, template) => template === "units/iber/cavalry_javelineer_b" ? "units/iber/cavalry_javelineer_a" : template; Engine.RegisterGlobal("GetUpgradedTemplate", GetUpgradedTemplate); cmpTurretHolder.OnOwnershipChanged({ "to": 1, "from": INVALID_PLAYER }); TS_ASSERT(!cmpTurretHolder.OccupiesTurretPoint(spawned)); cmpTurretHolder.OnOwnershipChanged({ "to": 1, "from": INVALID_PLAYER }); TS_ASSERT(!cmpTurretHolder.OccupiesTurretPoint(spawned)); cmpTurretHolder.OnOwnershipChanged({ "to": 1, "from": INVALID_PLAYER }); TS_ASSERT(!cmpTurretHolder.OccupiesTurretPoint(spawned)); cmpTurretHolder.OnOwnershipChanged({ "to": 1, "from": INVALID_PLAYER }); TS_ASSERT(cmpTurretHolder.OccupiesTurretPoint(spawned)); // Normal turret creation. Engine.AddEntity = function(t) { ++spawned; // Check that we're using the upgraded template. TS_ASSERT(t, "units/iber/cavalry_javelineer_a"); ConstructComponent(spawned, "Turretable", {}); AddMock(spawned, IID_Ownership, { "GetOwner": () => player, "SetOwner": () => {} }); AddMock(spawned, IID_Position, { "GetPosition": () => new Vector3D(4, 3, 25), "GetRotation": () => new Vector3D(4, 0, 6), "SetTurretParent": () => {}, "IsInWorld": () => true }); return spawned; }; cmpTurretHolder = ConstructComponent(turretHolderID, "TurretHolder", { "TurretPoints": { "Turret": { "X": "15.0", "Y": "5.0", "Z": "6.0", "Template": "units/iber/cavalry_javelineer_b" } } }); cmpTurretHolder.OnOwnershipChanged({ "to": 1, "from": INVALID_PLAYER }); 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); }