import { expect } from "@esm-bundle/chai"; import { GameLoop } from "../../../src/core/GameLoop.js"; import { createGameLoopSetup, cleanupGameLoop, createRunData, createMockGameStateManagerForCombat, setupCombatUnits, cleanupTurnSystem, } from "./helpers.js"; describe("Core: GameLoop - Combat Movement Execution", function () { this.timeout(30000); let gameLoop; let container; let playerUnit; beforeEach(async () => { const setup = createGameLoopSetup(); gameLoop = setup.gameLoop; container = setup.container; if (gameLoop.turnSystemAbortController) { gameLoop.turnSystemAbortController.abort(); } gameLoop.stop(); if ( gameLoop.turnSystem && typeof gameLoop.turnSystem.reset === "function" ) { gameLoop.turnSystem.reset(); } gameLoop.init(container); const mockGameStateManager = createMockGameStateManagerForCombat(); gameLoop.gameStateManager = mockGameStateManager; const runData = createRunData({ squad: [{ id: "u1", classId: "CLASS_VANGUARD" }], }); await gameLoop.startLevel(runData, { startAnimation: false }); gameLoop.updateCombatState = async () => Promise.resolve(); const units = setupCombatUnits(gameLoop); playerUnit = units.playerUnit; }); afterEach(async () => { // Ensure combat is ended before cleanup if ( gameLoop.turnSystem && gameLoop.turnSystem.phase !== "INIT" && gameLoop.turnSystem.phase !== "COMBAT_END" ) { try { gameLoop.turnSystem.endCombat(); } catch (e) { // Ignore errors during cleanup } } gameLoop.clearMovementHighlights(); gameLoop.clearSpawnZoneHighlights(); cleanupTurnSystem(gameLoop); cleanupGameLoop(gameLoop, container); await new Promise((resolve) => setTimeout(resolve, 50)); }); it("CoA 9: should move player unit in combat when clicking valid position", async () => { playerUnit.chargeMeter = 100; playerUnit.baseStats.speed = 20; const allUnits = [playerUnit]; gameLoop.turnSystem.startCombat(allUnits); const activeUnit = gameLoop.turnSystem.getActiveUnit(); expect(activeUnit).to.equal(playerUnit); const initialPos = { ...playerUnit.position }; const targetPos = { x: initialPos.x + 1, y: initialPos.y, z: initialPos.z, }; const initialAP = playerUnit.currentAP; await gameLoop.handleCombatMovement(targetPos); if ( playerUnit.position.x !== initialPos.x || playerUnit.position.z !== initialPos.z ) { expect(playerUnit.position.x).to.equal(targetPos.x); expect(playerUnit.position.z).to.equal(targetPos.z); expect(playerUnit.currentAP).to.be.lessThan(initialAP); } else { expect(playerUnit.currentAP).to.be.at.most(initialAP); } }); it("CoA 10: should not move unit if target is not reachable", async () => { playerUnit.chargeMeter = 100; playerUnit.baseStats.speed = 20; const allUnits = [playerUnit]; gameLoop.turnSystem.startCombat(allUnits); expect(gameLoop.turnSystem.getActiveUnit()).to.equal(playerUnit); const initialPos = { ...playerUnit.position }; const targetPos = { x: 20, y: 1, z: 20 }; gameLoop.isRunning = false; gameLoop.inputManager = { getCursorPosition: () => targetPos, update: () => {}, isKeyPressed: () => false, setCursor: () => {}, }; await gameLoop.handleCombatMovement(targetPos); expect(playerUnit.position.x).to.equal(initialPos.x); expect(playerUnit.position.z).to.equal(initialPos.z); }); it("CoA 11: should not move unit if not enough AP", async () => { playerUnit.chargeMeter = 100; playerUnit.baseStats.speed = 20; const allUnits = [playerUnit]; gameLoop.turnSystem.startCombat(allUnits); expect(gameLoop.turnSystem.getActiveUnit()).to.equal(playerUnit); playerUnit.currentAP = 0; const initialPos = { ...playerUnit.position }; const targetPos = { x: 6, y: 1, z: 5 }; gameLoop.isRunning = false; gameLoop.inputManager = { getCursorPosition: () => targetPos, update: () => {}, isKeyPressed: () => false, setCursor: () => {}, }; await gameLoop.handleCombatMovement(targetPos); expect(playerUnit.position.x).to.equal(initialPos.x); }); });