Implement Initiative Turn System and Fog of War
This commit is contained in:
@@ -52,12 +52,18 @@ export class GameEngine {
|
||||
if (phase === 'hero' || phase === 'exploration') {
|
||||
this.resetHeroMoves();
|
||||
}
|
||||
if (phase === 'hero') {
|
||||
this.initializeTurnOrder();
|
||||
}
|
||||
});
|
||||
|
||||
// End of Turn Logic (Buffs, cooldowns, etc)
|
||||
this.turnManager.on('turn_ended', (turn) => {
|
||||
this.handleEndTurn();
|
||||
});
|
||||
|
||||
// Initial Light Update
|
||||
setTimeout(() => this.updateLighting(), 500);
|
||||
}
|
||||
|
||||
resetHeroMoves() {
|
||||
@@ -161,6 +167,70 @@ export class GameEngine {
|
||||
this.player = this.heroes[0];
|
||||
}
|
||||
|
||||
initializeTurnOrder() {
|
||||
console.log("[GameEngine] Initializing Turn Order...");
|
||||
|
||||
// 1. Identify Leader
|
||||
const leader = this.getLeader();
|
||||
|
||||
// 2. Sort Rest by Initiative (Descending)
|
||||
// Note: Sort is stable or we rely on index? Array.sort is stable in modern JS.
|
||||
const others = this.heroes.filter(h => h !== leader);
|
||||
others.sort((a, b) => b.stats.init - a.stats.init);
|
||||
|
||||
// 3. Construct Order
|
||||
this.heroTurnOrder = [leader, ...others];
|
||||
this.currentTurnIndex = 0;
|
||||
|
||||
console.log("Turn Order:", this.heroTurnOrder.map(h => `${h.name} (${h.stats.init})`));
|
||||
|
||||
// 4. Activate First
|
||||
this.activateHero(this.heroTurnOrder[0]);
|
||||
}
|
||||
|
||||
activateHero(hero) {
|
||||
this.selectedEntity = hero;
|
||||
// Update selection UI
|
||||
if (this.onEntitySelect) {
|
||||
// Deselect all keys first?
|
||||
this.heroes.forEach(h => this.onEntitySelect(h.id, false));
|
||||
this.onEntitySelect(hero.id, true);
|
||||
}
|
||||
|
||||
// Notify UI about active turn
|
||||
if (this.onShowMessage) {
|
||||
this.onShowMessage(`Turno de ${hero.name}`, "Mueve y Ataca.");
|
||||
}
|
||||
|
||||
// Mark as active in renderer (Green Ring vs Yellow Selection)
|
||||
if (window.RENDERER) {
|
||||
this.heroes.forEach(h => window.RENDERER.setEntityActive(h.id, false));
|
||||
window.RENDERER.setEntityActive(hero.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
nextHeroTurn() {
|
||||
this.currentTurnIndex++;
|
||||
if (this.currentTurnIndex < this.heroTurnOrder.length) {
|
||||
this.activateHero(this.heroTurnOrder[this.currentTurnIndex]);
|
||||
} else {
|
||||
console.log("All heroes acted. Ending Phase sequence if auto?");
|
||||
this.deselectEntity();
|
||||
if (window.RENDERER) {
|
||||
this.heroes.forEach(h => window.RENDERER.setEntityActive(h.id, false));
|
||||
}
|
||||
if (this.onShowMessage) {
|
||||
this.onShowMessage("Fase de Aventureros Terminada", "Pasando a Monstruos...");
|
||||
}
|
||||
// Auto Advance Phase? Or Manual?
|
||||
// Usually manual "End Turn" button triggers nextHeroTurn.
|
||||
// When last hero ends, we trigger nextPhase.
|
||||
setTimeout(() => {
|
||||
this.turnManager.nextPhase();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
spawnMonster(monsterKey, x, y, options = {}) {
|
||||
const definition = MONSTER_DEFINITIONS[monsterKey];
|
||||
if (!definition) {
|
||||
@@ -309,8 +379,20 @@ export class GameEngine {
|
||||
const clickedEntity = clickedHero || clickedMonster;
|
||||
|
||||
if (clickedEntity) {
|
||||
|
||||
// STRICT TURN ORDER CHECK
|
||||
if (this.turnManager.currentPhase === 'hero' && clickedHero) {
|
||||
const currentActiveHero = this.heroTurnOrder ? this.heroTurnOrder[this.currentTurnIndex] : null;
|
||||
if (currentActiveHero && clickedHero.id !== currentActiveHero.id) {
|
||||
if (this.onShowMessage) this.onShowMessage("No es su turno", `Es el turno de ${currentActiveHero.name}.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.selectedEntity === clickedEntity) {
|
||||
// Toggle Deselect
|
||||
// Prevent deselecting active hero effectively? Or allow it but re-select him if trying to select others?
|
||||
// For now, allow deselect freely.
|
||||
this.deselectEntity();
|
||||
} else if (this.selectedMonster === clickedMonster && clickedMonster) {
|
||||
// Clicking on already selected monster - deselect it
|
||||
@@ -369,9 +451,61 @@ export class GameEngine {
|
||||
performHeroAttack(targetMonsterId) {
|
||||
const hero = this.selectedEntity;
|
||||
const monster = this.monsters.find(m => m.id === targetMonsterId);
|
||||
|
||||
// Attack ends turn logic could be here? Assuming user clicks "End Turn" manually for now.
|
||||
// Or if standard rules: 1 attack per turn.
|
||||
// For now, just attack.
|
||||
|
||||
return this.combatSystem.handleMeleeAttack(hero, monster);
|
||||
}
|
||||
|
||||
updateLighting() {
|
||||
if (!window.RENDERER) return;
|
||||
|
||||
const leader = this.getLeader();
|
||||
if (!leader) return;
|
||||
|
||||
// 1. Get Leader Tile ID
|
||||
const leaderTileId = this.dungeon.grid.occupiedCells.get(`${leader.x},${leader.y}`);
|
||||
if (!leaderTileId) return;
|
||||
|
||||
const visibleTileIds = new Set([leaderTileId]);
|
||||
|
||||
// 2. Find Neighbor Tiles (Connected Board Sections)
|
||||
// Iterate grid occupied cells to find cells belonging to leaderTileId
|
||||
// Then check their neighbors for DIFFERENT tile IDs that are connected.
|
||||
|
||||
// Optimization: We could cache this or iterate efficiently
|
||||
// For now, scan occupiedCells (Map)
|
||||
|
||||
for (const [key, tid] of this.dungeon.grid.occupiedCells.entries()) {
|
||||
if (tid === leaderTileId) {
|
||||
const [cx, cy] = key.split(',').map(Number);
|
||||
|
||||
// Check 4 neighbors
|
||||
const neighbors = [
|
||||
{ x: cx + 1, y: cy }, { x: cx - 1, y: cy },
|
||||
{ x: cx, y: cy + 1 }, { x: cx, y: cy - 1 }
|
||||
];
|
||||
|
||||
for (const n of neighbors) {
|
||||
const nKey = `${n.x},${n.y}`;
|
||||
const nTileId = this.dungeon.grid.occupiedCells.get(nKey);
|
||||
|
||||
if (nTileId && nTileId !== leaderTileId) {
|
||||
// Found a neighbor tile!
|
||||
// Check connectivity logic (Walls/Doors)
|
||||
if (this.dungeon.grid.canMoveBetween(cx, cy, n.x, n.y)) {
|
||||
visibleTileIds.add(nTileId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.RENDERER.updateFogOfWar(Array.from(visibleTileIds));
|
||||
}
|
||||
|
||||
performRangedAttack(targetMonsterId) {
|
||||
const hero = this.selectedEntity;
|
||||
const monster = this.monsters.find(m => m.id === targetMonsterId);
|
||||
@@ -517,6 +651,11 @@ export class GameEngine {
|
||||
entity.y = step.y;
|
||||
stepsTaken++;
|
||||
|
||||
// Update Light if Lantern Bearer
|
||||
if (entity.hasLantern) {
|
||||
this.updateLighting();
|
||||
}
|
||||
|
||||
// 2. Check for New Tile Entry
|
||||
const tileId = this.dungeon.grid.occupiedCells.get(`${step.x},${step.y}`);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user