import { CombatMechanics } from './CombatMechanics.js'; export class CombatSystem { constructor(gameEngine) { this.game = gameEngine; } /** * Handles the complete flow of a Melee Attack request * @param {Object} attacker * @param {Object} defender * @returns {Object} Result object { success: boolean, result: logObject, reason: string } */ handleMeleeAttack(attacker, defender) { // 1. Validations if (!attacker || !defender) return { success: false, reason: 'invalid_target' }; // Check Phase (Hero Phase for heroes) // Note: Monsters use this too, but their phase check is in AI loop. // We might want to enforce "Monster Phase" check here later if we pass 'source' context. if (attacker.type === 'hero' && this.game.turnManager.currentPhase !== 'hero') { return { success: false, reason: 'phase' }; } // Check Action Economy (Cooldown) if (attacker.hasAttacked) { return { success: false, reason: 'cooldown' }; } // Check Adjacency (Melee Range) // Logic: Manhattan distance == 1 const dx = Math.abs(attacker.x - defender.x); const dy = Math.abs(attacker.y - defender.y); if (dx + dy !== 1) { return { success: false, reason: 'range' }; } // 2. Execution (Math) // Calls the pure math module const result = CombatMechanics.resolveMeleeAttack(attacker, defender, this.game); // 3. Update State attacker.hasAttacked = true; // 4. Side Effects (Sound, UI Events) if (window.SOUND_MANAGER) { // Logic to choose sound could be expanded here based on Weapon Type window.SOUND_MANAGER.playSound('sword'); } if (this.game.onCombatResult) { this.game.onCombatResult(result); } return { success: true, result }; } /** * Handles the complete flow of a Ranged Attack request * @param {Object} attacker * @param {Object} defender * @returns {Object} Result object */ handleRangedAttack(attacker, defender) { if (!attacker || !defender) return { success: false, reason: 'invalid_target' }; // 1. Validations if (attacker.type === 'hero' && this.game.turnManager.currentPhase !== 'hero') { return { success: false, reason: 'phase' }; } if (attacker.hasAttacked) { return { success: false, reason: 'cooldown' }; } // Check "Pinned" Status (Can't shoot if enemies are adjacent) // Using GameEngine's helper for now as it holds entity lists if (this.game.isEntityPinned(attacker)) { return { success: false, reason: 'pinned' }; } // Line of Sight is assumed checked by UI/Input, but we could enforce it here if strict. // 2. Execution (Math) const result = CombatMechanics.resolveRangedAttack(attacker, defender, this.game); // 3. Update State attacker.hasAttacked = true; // 4. Side Effects if (window.SOUND_MANAGER) { window.SOUND_MANAGER.playSound('arrow'); } if (this.game.onCombatResult) { this.game.onCombatResult(result); } return { success: true, result }; } }