102 lines
3.3 KiB
JavaScript
102 lines
3.3 KiB
JavaScript
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 };
|
|
}
|
|
}
|