import { DungeonGenerator } from '../dungeon/DungeonGenerator.js'; import { TurnManager } from './TurnManager.js'; import { GAME_PHASES } from './GameConstants.js'; import { Entity } from './Entity.js'; export class GameEngine { constructor() { this.dungeon = new DungeonGenerator(); this.turnManager = new TurnManager(); this.missionConfig = null; this.player = null; this.selectedPath = []; this.selectedEntityId = null; // Simple event callbacks (external systems can assign these) this.onPathChange = null; // (path) => {} this.onEntityUpdate = null; // (entity) => {} this.onEntityMove = null; // (entity, path) => {} this.onEntitySelect = null; // (entityId, isSelected) => {} this.setupEventHooks(); } setupEventHooks() { this.turnManager.on('phase_changed', (phase) => this.handlePhaseChange(phase)); } startMission(missionConfig) { this.missionConfig = missionConfig; console.log(`[GameEngine] Starting mission: ${missionConfig.name}`); // 1. Initialize Dungeon (Places the first room) this.dungeon.startDungeon(missionConfig); // 2. Initialize Player // Find a valid starting spot (first occupied cell) const startingSpot = this.dungeon.grid.occupiedCells.keys().next().value; const [startX, startY] = startingSpot ? startingSpot.split(',').map(Number) : [0, 0]; console.log(`[GameEngine] Spawning player at valid spot: ${startX}, ${startY}`); this.player = new Entity('p1', 'Barbaro', 'hero', startX, startY, '/assets/images/dungeon1/standees/barbaro.png'); if (this.onEntityUpdate) this.onEntityUpdate(this.player); // 3. Start the Turn Sequence this.turnManager.startGame(); } update(deltaTime) { // Continuous logic } handlePhaseChange(phase) { console.log(`[GameEngine] Phase Changed to: ${phase}`); } // --- Interaction --- onCellClick(x, y) { if (this.turnManager.currentPhase !== GAME_PHASES.HERO) return; console.log(`Cell Click: ${x},${y}`); // 1. Selector Check (Click on Player?) if (this.player.x === x && this.player.y === y) { if (this.selectedEntityId === this.player.id) { // Deselect this.selectedEntityId = null; console.log("Player Deselected"); if (this.onEntitySelect) this.onEntitySelect(this.player.id, false); // Clear path too this.selectedPath = []; this._notifyPath(); } else { // Select this.selectedEntityId = this.player.id; console.log("Player Selected"); if (this.onEntitySelect) this.onEntitySelect(this.player.id, true); } return; } // If nothing selected, ignore floor clicks if (!this.selectedEntityId) return; // 2. Check if valid Floor (Occupied Cell) if (!this.dungeon.grid.occupiedCells.has(`${x},${y}`)) { console.log("Invalid cell: Void"); return; } // 3. Logic: Path Building (Only if Selected) // A. If clicking on last selected -> Deselect (Remove last step) if (this.selectedPath.length > 0) { const last = this.selectedPath[this.selectedPath.length - 1]; if (last.x === x && last.y === y) { this.selectedPath.pop(); this._notifyPath(); return; } } // B. Determine Previous Point (Player or Last Path Node) let prevX = this.player.x; let prevY = this.player.y; if (this.selectedPath.length > 0) { const last = this.selectedPath[this.selectedPath.length - 1]; prevX = last.x; prevY = last.y; } // Note: Manhattan distance 1 = Adjacency (No diagonals) const dist = Math.abs(x - prevX) + Math.abs(y - prevY); if (dist === 1) { // Check Path Length Limit (Speed) if (this.selectedPath.length < this.player.stats.move) { this.selectedPath.push({ x, y }); this._notifyPath(); } else { console.log("Max movement reached"); } } else { // Restart path if clicking adjacent to player const distFromPlayer = Math.abs(x - this.player.x) + Math.abs(y - this.player.y); if (distFromPlayer === 1) { this.selectedPath = [{ x, y }]; this._notifyPath(); } } } onCellRightClick(x, y) { if (this.turnManager.currentPhase !== GAME_PHASES.HERO) return; if (!this.selectedEntityId) return; // Must satisfy selection rule // Must be clicking the last tile of the path to confirm if (this.selectedPath.length === 0) return; const last = this.selectedPath[this.selectedPath.length - 1]; if (last.x === x && last.y === y) { console.log("Confirming Move..."); this.confirmMove(); } } confirmMove() { if (this.selectedPath.length === 0) return; const target = this.selectedPath[this.selectedPath.length - 1]; const pathCopy = [...this.selectedPath]; // 1. Trigger Animation Sequence if (this.onEntityMove) this.onEntityMove(this.player, pathCopy); // 2. Update Logical Position this.player.setPosition(target.x, target.y); // 3. Cleanup this.selectedPath = []; this._notifyPath(); // Note: Exploration is now manual via door interaction } _notifyPath() { if (this.onPathChange) this.onPathChange(this.selectedPath); } exploreExit(exitCell) { console.log('[GameEngine] Exploring exit:', exitCell); // Find this exit in pendingExits const exit = this.dungeon.pendingExits.find(ex => ex.x === exitCell.x && ex.y === exitCell.y); if (exit) { // Prioritize this exit const idx = this.dungeon.pendingExits.indexOf(exit); if (idx > -1) { this.dungeon.pendingExits.splice(idx, 1); this.dungeon.pendingExits.unshift(exit); } // Trigger exploration this.turnManager.triggerExploration(); this.dungeon.step(); this.turnManager.setPhase(GAME_PHASES.HERO); } else { console.warn('[GameEngine] Exit not found in pendingExits'); } } }