feat: Sistema de combate completo con tarjetas de personajes y animaciones
- Tarjetas de héroes y monstruos con tokens circulares - Sistema de selección: héroe + monstruo para atacar - Botón de ATACAR en tarjeta de monstruo - Animación de muerte: fade-out + hundimiento (1.5s) - Visualización de estadísticas completas (WS, BS, S, T, W, I, A, Mov) - Placeholder cuando no hay héroe seleccionado - Tokens de héroes y monstruos en formato circular - Deselección correcta de monstruos - Fix: paso de gameEngine a CombatMechanics para callbacks de muerte
This commit is contained in:
@@ -20,7 +20,7 @@ export class CombatMechanics {
|
||||
* @param {Object} defender
|
||||
* @returns {Object} Result log
|
||||
*/
|
||||
static resolveMeleeAttack(attacker, defender) {
|
||||
static resolveMeleeAttack(attacker, defender, gameEngine = null) {
|
||||
const log = {
|
||||
attackerId: attacker.id,
|
||||
defenderId: defender.id,
|
||||
@@ -88,7 +88,7 @@ export class CombatMechanics {
|
||||
}
|
||||
|
||||
// 6. Apply Damage to Defender State
|
||||
this.applyDamage(defender, wounds);
|
||||
this.applyDamage(defender, wounds, gameEngine);
|
||||
|
||||
if (defender.isDead) {
|
||||
log.defenderDied = true;
|
||||
@@ -110,7 +110,7 @@ export class CombatMechanics {
|
||||
return 6; // Fallback
|
||||
}
|
||||
|
||||
static applyDamage(entity, amount) {
|
||||
static applyDamage(entity, amount, gameEngine = null) {
|
||||
if (!entity.stats) entity.stats = {};
|
||||
|
||||
// If entity doesn't have current wounds tracked, init it from max
|
||||
@@ -135,6 +135,10 @@ export class CombatMechanics {
|
||||
if (entity.currentWounds <= 0) {
|
||||
entity.currentWounds = 0;
|
||||
entity.isDead = true;
|
||||
// Trigger death callback if available
|
||||
if (gameEngine && gameEngine.onEntityDeath) {
|
||||
gameEngine.onEntityDeath(entity.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ export class GameEngine {
|
||||
this.onEntitySelect = null;
|
||||
this.onEntityActive = null; // New: When entity starts/ends turn
|
||||
this.onEntityHit = null; // New: When entity takes damage
|
||||
this.onEntityDeath = null; // New: When entity dies
|
||||
this.onPathChange = null;
|
||||
}
|
||||
|
||||
@@ -149,29 +150,42 @@ export class GameEngine {
|
||||
const clickedHero = this.heroes.find(h => h.x === x && h.y === y);
|
||||
const clickedMonster = this.monsters ? this.monsters.find(m => m.x === x && m.y === y && !m.isDead) : null;
|
||||
|
||||
// COMBAT: Hero Attack Check
|
||||
if (clickedMonster && this.selectedEntity && this.selectedEntity.type === 'hero') {
|
||||
const attackResult = this.performHeroAttack(clickedMonster.id);
|
||||
if (attackResult && attackResult.success) {
|
||||
// Attack performed, do not deselect hero
|
||||
return;
|
||||
}
|
||||
// If attack failed (e.g. not adjacent), proceeds to select the monster
|
||||
}
|
||||
|
||||
const clickedEntity = clickedHero || clickedMonster;
|
||||
|
||||
if (clickedEntity) {
|
||||
if (this.selectedEntity === clickedEntity) {
|
||||
// Toggle Deselect
|
||||
this.deselectEntity();
|
||||
} else {
|
||||
// Select new entity
|
||||
if (this.selectedEntity) this.deselectEntity();
|
||||
|
||||
this.selectedEntity = clickedEntity;
|
||||
} else if (this.selectedMonster === clickedMonster && clickedMonster) {
|
||||
// Clicking on already selected monster - deselect it
|
||||
const monsterId = this.selectedMonster.id;
|
||||
this.selectedMonster = null;
|
||||
if (this.onEntitySelect) {
|
||||
this.onEntitySelect(clickedEntity.id, true);
|
||||
this.onEntitySelect(monsterId, false);
|
||||
}
|
||||
} else {
|
||||
// Select new entity (don't deselect hero if clicking monster)
|
||||
if (clickedMonster && this.selectedEntity && this.selectedEntity.type === 'hero') {
|
||||
// Deselect previous monster if any
|
||||
if (this.selectedMonster) {
|
||||
const prevMonsterId = this.selectedMonster.id;
|
||||
if (this.onEntitySelect) {
|
||||
this.onEntitySelect(prevMonsterId, false);
|
||||
}
|
||||
}
|
||||
// Keep hero selected, also select monster
|
||||
this.selectedMonster = clickedMonster;
|
||||
if (this.onEntitySelect) {
|
||||
this.onEntitySelect(clickedMonster.id, true);
|
||||
}
|
||||
} else {
|
||||
// Normal selection (deselect previous)
|
||||
if (this.selectedEntity) this.deselectEntity();
|
||||
|
||||
this.selectedEntity = clickedEntity;
|
||||
if (this.onEntitySelect) {
|
||||
this.onEntitySelect(clickedEntity.id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -201,7 +215,7 @@ export class GameEngine {
|
||||
if (hero.hasAttacked) return { success: false, reason: 'cooldown' };
|
||||
|
||||
// Execute Attack
|
||||
const result = CombatMechanics.resolveMeleeAttack(hero, monster);
|
||||
const result = CombatMechanics.resolveMeleeAttack(hero, monster, this);
|
||||
hero.hasAttacked = true;
|
||||
|
||||
if (this.onCombatResult) this.onCombatResult(result);
|
||||
@@ -216,6 +230,13 @@ export class GameEngine {
|
||||
this.plannedPath = [];
|
||||
if (this.onEntitySelect) this.onEntitySelect(id, false);
|
||||
if (this.onPathChange) this.onPathChange([]);
|
||||
|
||||
// Also deselect monster if selected
|
||||
if (this.selectedMonster) {
|
||||
const monsterId = this.selectedMonster.id;
|
||||
this.selectedMonster = null;
|
||||
if (this.onEntitySelect) this.onEntitySelect(monsterId, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Alias for legacy calls if any
|
||||
|
||||
@@ -220,7 +220,7 @@ export class MonsterAI {
|
||||
// 4. Remove both rings
|
||||
// 5. Show combat result
|
||||
|
||||
const result = CombatMechanics.resolveMeleeAttack(monster, hero);
|
||||
const result = CombatMechanics.resolveMeleeAttack(monster, hero, this.game);
|
||||
|
||||
// Step 1: Green ring on attacker
|
||||
if (this.game.onEntityActive) {
|
||||
|
||||
Reference in New Issue
Block a user