aether-shards/test/core/GameLoop/helpers.js

279 lines
6.2 KiB
JavaScript
Raw Permalink Normal View History

import sinon from "sinon";
import { GameLoop } from "../../../src/core/GameLoop.js";
/**
* Mock Data for Assets
*/
const MOCK_MANIFEST = {
classes: ["classes/vanguard.json"],
units: ["enemies/default.json"],
};
const MOCK_VANGUARD = {
id: "CLASS_VANGUARD",
name: "Vanguard",
base_stats: {
health: 100,
maxHealth: 100,
ap: 3,
maxAp: 3,
movement: 5,
defense: 10,
resistance: 5,
speed: 10,
accuracy: 85,
evasion: 5,
critRate: 5,
critDamage: 1.5,
},
growth_rates: {
health: 1.0,
ap: 0,
movement: 0,
defense: 0.5,
resistance: 0.3,
speed: 0.4,
accuracy: 0.4,
evasion: 0.2,
critRate: 0.2,
},
equipment: {
mainHand: "SWORD_BASIC",
armor: "ARMOR_BASIC",
},
skills: ["STRIKE", "GUARD"],
};
const MOCK_ENEMY = {
id: "ENEMY_DEFAULT",
name: "Drone",
baseStats: {
health: 50,
maxHealth: 50,
ap: 3,
maxAp: 3,
movement: 4,
defense: 2,
resistance: 0,
speed: 8,
accuracy: 80,
evasion: 10,
critRate: 5,
critDamage: 1.5,
},
equipment: {
mainHand: "BEAM_RIFLE",
},
};
// Keep track of the stub to restore it
let fetchStub = null;
/**
* Mocks window.fetch to return game assets.
*/
export function mockFetchAssets() {
if (fetchStub) return; // Already mocked
fetchStub = sinon.stub(window, "fetch");
fetchStub.callsFake(async (url) => {
if (url.includes("manifest.json")) {
return {
ok: true,
json: async () => MOCK_MANIFEST,
};
}
if (url.includes("classes/vanguard.json")) {
return {
ok: true,
json: async () => MOCK_VANGUARD,
};
}
if (url.includes("enemies/default.json")) {
return {
ok: true,
json: async () => MOCK_ENEMY,
};
}
// Return empty/default for others to prevent crashes
return {
ok: false,
status: 404,
json: async () => ({}),
text: async () => "Not Found",
};
});
}
/**
* Restores window.fetch.
*/
export function restoreFetchAssets() {
if (fetchStub) {
fetchStub.restore();
fetchStub = null;
}
}
/**
* Creates a basic GameLoop setup for tests.
* @returns {{ gameLoop: GameLoop; container: HTMLElement }}
*/
export function createGameLoopSetup() {
mockFetchAssets(); // Auto-mock assets
const container = document.createElement("div");
document.body.appendChild(container);
const gameLoop = new GameLoop();
return { gameLoop, container };
}
/**
* Cleans up GameLoop after tests.
* @param {GameLoop} gameLoop
* @param {HTMLElement} container
*/
export function cleanupGameLoop(gameLoop, container) {
gameLoop.stop();
if (container.parentNode) {
container.parentNode.removeChild(container);
}
// Cleanup Three.js resources if possible to avoid context loss limits
if (gameLoop.renderer) {
gameLoop.renderer.dispose();
gameLoop.renderer.forceContextLoss();
}
restoreFetchAssets(); // Restore fetch
}
/**
* Creates a mock game state manager for deployment phase.
* @returns {Object}
*/
export function createMockGameStateManagerForDeployment() {
return {
currentState: "STATE_DEPLOYMENT",
transitionTo: sinon.stub(),
setCombatState: sinon.stub(),
getCombatState: sinon.stub().returns(null),
rosterManager: {
roster: [],
},
};
}
/**
* Creates a mock game state manager for combat phase.
* @returns {Object}
*/
export function createMockGameStateManagerForCombat() {
return {
currentState: "STATE_COMBAT",
transitionTo: sinon.stub(),
setCombatState: sinon.stub(),
getCombatState: sinon.stub(),
rosterManager: {
roster: [],
},
};
}
/**
* Creates a mock mission manager with enemy spawns.
* @param {Array} enemySpawns
* @returns {Object}
*/
export function createMockMissionManager(enemySpawns = []) {
const mockMissionDef = {
id: "MISSION_TEST",
config: { title: "Test Mission" },
enemy_spawns: enemySpawns,
objectives: { primary: [] },
};
return {
getActiveMission: sinon.stub().returns(mockMissionDef),
setGridContext: sinon.stub(),
populateZoneCoordinates: sinon.stub(),
resolveMissionObjectPositions: sinon.stub().returns([]),
};
}
/**
* Creates basic run data for tests.
* @param {Object} overrides
* @returns {Object}
*/
export function createRunData(overrides = {}) {
return {
seed: 12345,
depth: 1,
squad: [],
...overrides,
};
}
/**
* Sets up combat test units.
* @param {GameLoop} gameLoop
* @returns {{ playerUnit: Object; enemyUnit: Object }}
*/
export function setupCombatUnits(gameLoop) {
const playerUnit = gameLoop.unitManager.createUnit(
"CLASS_VANGUARD",
"PLAYER"
);
playerUnit.baseStats.movement = 4;
playerUnit.baseStats.speed = 10;
playerUnit.currentAP = 10;
playerUnit.chargeMeter = 100;
playerUnit.position = { x: 5, y: 1, z: 5 };
gameLoop.grid.placeUnit(playerUnit, playerUnit.position);
gameLoop.createUnitMesh(playerUnit, playerUnit.position);
const enemyUnit = gameLoop.unitManager.createUnit("ENEMY_DEFAULT", "ENEMY");
enemyUnit.baseStats.speed = 8;
enemyUnit.chargeMeter = 80;
enemyUnit.position = { x: 15, y: 1, z: 15 };
gameLoop.grid.placeUnit(enemyUnit, enemyUnit.position);
gameLoop.createUnitMesh(enemyUnit, enemyUnit.position);
return { playerUnit, enemyUnit };
}
/**
* Cleans up turn system state.
* @param {GameLoop} gameLoop
*/
export function cleanupTurnSystem(gameLoop) {
if (gameLoop.turnSystem) {
try {
// First, try to end combat immediately to stop any ongoing turn advancement
if (
gameLoop.turnSystem.phase !== "INIT" &&
gameLoop.turnSystem.phase !== "COMBAT_END"
) {
// End combat first to stop any loops
gameLoop.turnSystem.endCombat();
}
// Then reset the turn system
if (typeof gameLoop.turnSystem.reset === "function") {
gameLoop.turnSystem.reset();
} else {
// Fallback: manually reset state
gameLoop.turnSystem.globalTick = 0;
gameLoop.turnSystem.activeUnitId = null;
gameLoop.turnSystem.phase = "INIT";
gameLoop.turnSystem.round = 1;
gameLoop.turnSystem.turnQueue = [];
}
} catch (e) {
// Ignore errors during cleanup
console.warn("Error during turn system cleanup:", e);
}
}
}