diff --git a/DEVLOG.md b/DEVLOG.md index 9461a9a..4cf51da 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1,4 +1,62 @@ -# Devlog - Warhammer Quest (Versión Web 3D) + +## Sesión 18: Refinado de Spawning y Ajustes de Corredores +**Fecha:** 11 de Enero de 2026 + +### Objetivos +- Corregir el spawn de monstruos en eventos de fase de poder (Minotauro). +- Ajustar la generación de salidas en la loseta inicial. +- Mejorar la visualización de notificaciones y la lógica de daño grupal. + +### Cambios Realizados + +#### 1. Spawn Hero-Centric para Eventos +- **Restricción de Área**: Modificada la lógica de `findSpawnPoints` en `GameEngine.js` para que los monstruos generados por Eventos de Poder solo aparezcan en losetas ocupadas por héroes, evitando que aparezcan en el vacío o en zonas inexploradas. +- **Fix Spawn Inicial**: Corregido bug de spawn inicial de héroes que causaba que aparecieran fuera de los límites (coordenada 0,0) si no se detectaban posiciones válidas inicialmente. + +#### 2. Ajuste de Salidas Iniciales +- **Regla del Inicio**: Los pasillos colocados como loseta inicial (`tile_0`) ahora habilitan exactamente **2 salidas aleatorias** (en lugar de 1 o 4), ofreciendo opciones estratégicas equilibradas desde el comienzo. +- **Continuidad**: El resto de pasillos mantienen la regla de 1 salida extra para mantener la estructura de la mazmorra. + +#### 3. Mejoras en EventInterpreter +- **Daño Grupal**: Implementada "herencia de objetivos" en `EventInterpreter.js`. Si un efecto afecta a "todos", ahora se aplica correctamente a todos los héroes seleccionados en la acción previa. +- **Logs Descriptivos**: Refinados los mensajes de log de eventos para ser más descriptivos: ahora detallan quién activó la trampa y a quién afecta exactamente el daño. + +#### 4. UX/UI y Estética +- **Posicionamiento de UI**: Desplazada la posición de los mensajes de notificación temporales (cartel rojo) a la zona inferior (70% de altura) para evitar solapamientos críticos con los botones de acción (`Acabar Turno`, etc.). +- **Actualización de Assets**: Vinculadas nuevas texturas específicas para las salas de laboratorio y la bóveda en `TileDefinitions.js` (`room_4x4_skull.png`, `room_4x4_clean.png`). + +--- + +## Sesión 17: Reglas de Mazmorra y Control de Pasillos +**Fecha:** 11 de Enero de 2026 + +### Objetivos +- Cumplir estrictamente con la composición del mazo de mazmorra (18 cartas originales). +- Implementar la mecánica de barajado de 13 cartas con el objetivo en la mitad inferior. +- Limitar el número de salidas en los pasillos para evitar laberintos infinitos. +- Establecer una regla de proximidad (4 celdas) para la generación de nuevas puertas. + +### Cambios Realizados + +#### 1. Fiel Composición del Mazo (Warhammer Quest 1995) +- **Mazo de 18 Cartas**: El pool inicial ahora contiene exactamente 6 salas de subterráneo, 7 pasillos, 3 cruces en T, 1 esquina y 1 escalera. +- **Estructura de 13 Cartas**: Al generar la misión, se crea un mazo de 13 cartas: + - 6 cartas aleatorias arriba. + - 1 sala de objetivo + 6 cartas aleatorias barajadas abajo. +- **Salas Específicas**: Se han definido 6 variantes de salas de subterráneo y 5 salas de objetivo únicas (`room_objective_1` a `5`) en `TileDefinitions.js`. +- **Assets de Backup**: Generadas texturas placeholder (`room_4x4_placeholder.png`, `room_4x8_placeholder.png`) para las nuevas salas para asegurar la carga visual. + +#### 2. Control de Salidas en Pasillos (Regla de las 2 Salidas) +- **Limitación de Ramificación**: Los pasillos, que lógicamente tienen 4 direcciones, ahora solo habilitan un máximo de **2 salidas** (la entrada utilizada + una salida extra aleatoria). +- **Control de Proximidad Táctico**: + - Antes de habilitar una salida extra en un pasillo, el sistema verifica que la celda de salida esté a una **distancia mínima de 4 celdas** de cualquier otra habitación (`ROOM` u `OBJECTIVE_ROOM`). + - Si una salida potencial está demasiado cerca de una estructura existente, se descarta para evitar colisiones visuales y mecánicas. +- **Visualización**: Durante la fase de "Placing Tile", el jugador sigue viendo todas las salidas en azul, pero al confirmar, el sistema activa solo las permitidas. + +### Tareas Pendientes / TODO +- **[Avanzado]** Implementar conectividad automática: si una pieza recién colocada queda adyacente a una pared con puerta de otra habitación, permitir la conexión física entre ambas. + +--- ## Sesión 16: Reglas de Exploración y Refinado de Eventos **Fecha:** 11 de Enero de 2026 diff --git a/public/assets/images/dungeon1/tiles/room_4x4_clean.png b/public/assets/images/dungeon1/tiles/room_4x4_clean.png new file mode 100644 index 0000000..4e65c72 Binary files /dev/null and b/public/assets/images/dungeon1/tiles/room_4x4_clean.png differ diff --git a/public/assets/images/dungeon1/tiles/room_4x4_placeholder.png b/public/assets/images/dungeon1/tiles/room_4x4_placeholder.png new file mode 100644 index 0000000..84dfa16 Binary files /dev/null and b/public/assets/images/dungeon1/tiles/room_4x4_placeholder.png differ diff --git a/public/assets/images/dungeon1/tiles/room_4x4_skull.png b/public/assets/images/dungeon1/tiles/room_4x4_skull.png new file mode 100644 index 0000000..9900b1a Binary files /dev/null and b/public/assets/images/dungeon1/tiles/room_4x4_skull.png differ diff --git a/public/assets/images/dungeon1/tiles/room_4x8_placeholder.png b/public/assets/images/dungeon1/tiles/room_4x8_placeholder.png new file mode 100644 index 0000000..c2e5d71 Binary files /dev/null and b/public/assets/images/dungeon1/tiles/room_4x8_placeholder.png differ diff --git a/src/engine/dungeon/DungeonDeck.js b/src/engine/dungeon/DungeonDeck.js index 8a23a03..b48766c 100644 --- a/src/engine/dungeon/DungeonDeck.js +++ b/src/engine/dungeon/DungeonDeck.js @@ -9,59 +9,53 @@ export class DungeonDeck { } generateMissionDeck(objectiveTileId) { - this.cards = []; - // 1. Create a "Pool" of standard dungeon tiles + // 1. Create a "Pool" of standard dungeon tiles (18 total) + // Rule: 6 Subterranean Rooms, 7 Corridors, 3 T-Junctions, 1 Corner, 1 Steps let pool = []; const composition = [ - { id: 'room_dungeon', count: 12 }, - { id: 'corridor_straight', count: 8 }, - { id: 'corridor_steps', count: 4 }, - { id: 'corridor_corner', count: 4 }, - { id: 'junction_t', count: 4 } + { id: 'room_subterranean_1', count: 1 }, + { id: 'room_subterranean_2', count: 1 }, + { id: 'room_subterranean_3', count: 1 }, + { id: 'room_subterranean_4', count: 1 }, + { id: 'room_subterranean_5', count: 1 }, + { id: 'room_subterranean_6', count: 1 }, + { id: 'corridor_straight', count: 7 }, + { id: 'junction_t', count: 3 }, + { id: 'corridor_corner', count: 1 }, + { id: 'corridor_steps', count: 1 } ]; composition.forEach(item => { - // FIXED: Access by Key string directly const tileDef = TILES[item.id]; - if (tileDef) { for (let i = 0; i < item.count; i++) { - pool.push(tileDef); + pool.push({ ...tileDef }); // Push a copy } } else { console.error(`❌ Missing Tile Definition for ID: ${item.id}`); } }); - const drawRandom = (source, count) => { - const drawn = []; - for (let i = 0; i < count; i++) { - if (source.length === 0) break; - const idx = Math.floor(Math.random() * source.length); - drawn.push(source[idx]); - source.splice(idx, 1); - } - return drawn; - }; + // Shuffle the initial pool + this.shuffleArray(pool); - // --- Step 1 & 2: Bottom Pool (6 random tiles + Objective) --- - const bottomPool = drawRandom(pool, 6); - const objectiveDef = TILES[objectiveTileId]; - if (objectiveDef) { - bottomPool.push(objectiveDef); - } - this.shuffleArray(bottomPool); + // --- Step 1: Bottom 6 + Objective (shuffled together) --- + const bottomHalf = pool.splice(0, 6); + const objectiveDef = TILES[objectiveTileId] || TILES['room_objective_1']; + bottomHalf.push({ ...objectiveDef }); + this.shuffleArray(bottomHalf); - // --- Step 3: Top Pool (All remaining tiles in the pool) --- - const topPool = [...pool]; // pool already has those 6 removed by drawRandom - this.shuffleArray(topPool); - - // --- Step 4: Final Stack --- - this.cards = [...topPool, ...bottomPool]; + // --- Step 2: Top 6 --- + const topHalf = pool.splice(0, 6); + this.shuffleArray(topHalf); + // --- Step 3: Final Stack (TopHalf on top of BottomHalf) --- + // Total 6 + 7 = 13 cards + this.cards = [...topHalf, ...bottomHalf]; + console.log(`✅ Dungeon Deck generated with ${this.cards.length} cards. Objective hidden in the bottom 7.`); } shuffleArray(array) { diff --git a/src/engine/dungeon/DungeonGenerator.js b/src/engine/dungeon/DungeonGenerator.js index def3d3e..a0ac1e3 100644 --- a/src/engine/dungeon/DungeonGenerator.js +++ b/src/engine/dungeon/DungeonGenerator.js @@ -1,7 +1,7 @@ - -import { DIRECTIONS } from './Constants.js'; +import { DIRECTIONS, TILE_TYPES } from './Constants.js'; import { GridSystem } from './GridSystem.js'; import { DungeonDeck } from './DungeonDeck.js'; +import { TILES } from './TileDefinitions.js'; const PLACEMENT_STATE = { WAITING_DOOR: 'WAITING_DOOR', @@ -31,7 +31,13 @@ export class DungeonGenerator { } startDungeon(missionConfig) { - const objectiveId = missionConfig?.type === 'quest' ? 'room_objective' : 'room_dungeon'; + // Warhammer Quest: Pick one of the 5 Objective Rooms + let objectiveId = missionConfig?.objectiveId; + if (!objectiveId) { + const objIds = ['room_objective_1', 'room_objective_2', 'room_objective_3', 'room_objective_4', 'room_objective_5']; + objectiveId = objIds[Math.floor(Math.random() * objIds.length)]; + } + this.deck.generateMissionDeck(objectiveId); // 1. Draw and place first card automatically at origin @@ -286,11 +292,17 @@ export class DungeonGenerator { /** * Update list of exits player can choose from + * Rule: Corridors only get ONE extra exit al azar, if it's far from OTHER rooms. */ updateAvailableExits(instance, variant, anchorX, anchorY) { + const card = TILES[instance.defId]; + const isCorridor = card.type === TILE_TYPES.CORRIDOR; + // Identify the "parent" room we are connecting from to ignore it in distance checks + const parentTileId = this.selectedExit ? this.selectedExit.tileId : null; - // Add new exits from this tile + // 1. Identify all potential exits (those not blocked by existing tiles) + let potentialExits = []; for (const ex of variant.exits) { const gx = anchorX + ex.x; const gy = anchorY + ex.y; @@ -298,28 +310,86 @@ export class DungeonGenerator { const leadingTo = this.neighbor(gx, gy, ex.direction); const isOccupied = this.grid.isOccupied(leadingTo.x, leadingTo.y); - - if (!isOccupied) { - this.availableExits.push({ + potentialExits.push({ x: gx, y: gy, direction: ex.direction, tileId: instance.id }); - } } + // 2. If it's a corridor, apply the "Semi-Random" exit rule + if (isCorridor && potentialExits.length > 0) { + const doorsByDir = {}; + potentialExits.forEach(e => { + if (!doorsByDir[e.direction]) doorsByDir[e.direction] = []; + doorsByDir[e.direction].push(e); + }); + const dirs = Object.keys(doorsByDir); - // Remove exits that are now blocked or connected + // Filter directions that are at least 4 cells away from any OTHER Room + const allowedDirs = dirs.filter(dir => { + const group = doorsByDir[dir]; + // Check proximity for every cell in this door group, IGNORE the room we just came from + return group.every(cell => this.isFarFromRooms(cell.x, cell.y, 4, parentTileId)); + }); + + if (allowedDirs.length > 0) { + // RULE: If it's the STARTING tile (tile_0), we want 2 exits. + // Otherwise, just 1 extra exit. + const numToSelect = (instance.id === 'tile_0') ? 2 : 1; + + // Shuffle and pick + const shuffledDirs = allowedDirs.sort(() => 0.5 - Math.random()); + const selectedDirs = shuffledDirs.slice(0, numToSelect); + + selectedDirs.forEach(dir => { + this.availableExits.push(...doorsByDir[dir]); + }); + + console.log(`[DungeonGenerator] Corridor ${instance.id} selected ${selectedDirs.length} exit directions: ${selectedDirs.join(', ')}`); + } else { + console.log(`[DungeonGenerator] Corridor ${instance.id} became a dead end.`); + } + } else { + // For Rooms and Junctions, we keep all valid exits + this.availableExits.push(...potentialExits); + } + + // 3. Global cleanup this.availableExits = this.availableExits.filter(exit => { const leadingTo = this.neighbor(exit.x, exit.y, exit.direction); return !this.grid.isOccupied(leadingTo.x, leadingTo.y); }); } + /** + * Checks if a coordinate is at least 'radius' distance away from any cell of a Room tile. + * @param {string} ignoreTileId - Optional ID of a room to ignore (usually the parent room) + */ + isFarFromRooms(gx, gy, radius, ignoreTileId = null) { + for (const tileInstance of this.grid.tiles) { + // Skip the room we are expanding from + if (ignoreTileId && tileInstance.id === ignoreTileId) continue; + + const card = TILES[tileInstance.defId]; + if (card.type === TILE_TYPES.ROOM || card.type === TILE_TYPES.OBJECTIVE_ROOM) { + const cells = this.grid.tileCells.get(tileInstance.id); + if (!cells) continue; + + for (const cellKey of cells) { + const [cx, cy] = cellKey.split(',').map(Number); + const dist = Math.abs(gx - cx) + Math.abs(gy - cy); + if (dist < radius) return false; + } + } + } + return true; + } + /** * Get current placement preview data for renderer */ diff --git a/src/engine/dungeon/TileDefinitions.js b/src/engine/dungeon/TileDefinitions.js index efee3c9..a3d04be 100644 --- a/src/engine/dungeon/TileDefinitions.js +++ b/src/engine/dungeon/TileDefinitions.js @@ -295,107 +295,150 @@ export const TILES = { }, // ------------------------------------------------------------------------- - // ROOM DUNGEON + // SUBTERRANEAN ROOMS (4x4) // ------------------------------------------------------------------------- - 'room_dungeon': { - id: 'room_dungeon', - name: 'Dungeon Room', + 'room_subterranean_1': { + id: 'room_subterranean_1', + name: 'Circle of Power', type: TILE_TYPES.ROOM, - textures: [ - '/assets/images/dungeon1/tiles/room_4x4_circle.png', - '/assets/images/dungeon1/tiles/room_4x4_orange.png', - '/assets/images/dungeon1/tiles/room_4x4_squeleton.png' - ], - variants: { - [DIRECTIONS.NORTH]: { - width: 4, height: 4, - layout: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], - exits: [ - { x: 1, y: 0, direction: DIRECTIONS.SOUTH }, { x: 2, y: 0, direction: DIRECTIONS.SOUTH }, - { x: 1, y: 3, direction: DIRECTIONS.NORTH }, { x: 2, y: 3, direction: DIRECTIONS.NORTH } - ] - }, - [DIRECTIONS.EAST]: { - width: 4, height: 4, - layout: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], - exits: [ - { x: 0, y: 1, direction: DIRECTIONS.WEST }, { x: 0, y: 2, direction: DIRECTIONS.WEST }, - { x: 3, y: 1, direction: DIRECTIONS.EAST }, { x: 3, y: 2, direction: DIRECTIONS.EAST } - ] - }, - [DIRECTIONS.SOUTH]: { - width: 4, height: 4, - layout: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], - exits: [ - { x: 1, y: 3, direction: DIRECTIONS.NORTH }, { x: 2, y: 3, direction: DIRECTIONS.NORTH }, - { x: 1, y: 0, direction: DIRECTIONS.SOUTH }, { x: 2, y: 0, direction: DIRECTIONS.SOUTH } - ] - }, - [DIRECTIONS.WEST]: { - width: 4, height: 4, - layout: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], - exits: [ - { x: 3, y: 1, direction: DIRECTIONS.EAST }, { x: 3, y: 2, direction: DIRECTIONS.EAST }, - { x: 0, y: 1, direction: DIRECTIONS.WEST }, { x: 0, y: 2, direction: DIRECTIONS.WEST } - ] - } - } + textures: ['/assets/images/dungeon1/tiles/room_4x4_circle.png'], + variants: getStandard4x4Variants() + }, + 'room_subterranean_2': { + id: 'room_subterranean_2', + name: 'Fighting Pit', + type: TILE_TYPES.ROOM, + textures: ['/assets/images/dungeon1/tiles/room_4x4_orange.png'], + variants: getStandard4x4Variants() + }, + 'room_subterranean_3': { + id: 'room_subterranean_3', + name: 'Guard Room', + type: TILE_TYPES.ROOM, + textures: ['/assets/images/dungeon1/tiles/room_4x4_squeleton.png'], + variants: getStandard4x4Variants() + }, + 'room_subterranean_4': { + id: 'room_subterranean_4', + name: 'Laboratory', + type: TILE_TYPES.ROOM, + textures: ['/assets/images/dungeon1/tiles/room_4x4_skull.png'], + variants: getStandard4x4Variants() + }, + 'room_subterranean_5': { + id: 'room_subterranean_5', + name: 'Vault', + type: TILE_TYPES.ROOM, + textures: ['/assets/images/dungeon1/tiles/room_4x4_clean.png'], + variants: getStandard4x4Variants() + }, + 'room_subterranean_6': { + id: 'room_subterranean_6', + name: 'Torture Chamber', + type: TILE_TYPES.ROOM, + textures: ['/assets/images/dungeon1/tiles/room_4x4_placeholder.png'], + variants: getStandard4x4Variants() }, - - // ------------------------------------------------------------------------- - // ROOM OBJECTIVE + // OBJECTIVE ROOMS (4x8) // ------------------------------------------------------------------------- - 'room_objective': { - id: 'room_objective', - name: 'Objective Room', + 'room_objective_1': { + id: 'room_objective_1', + name: 'Altar of Doom', type: TILE_TYPES.OBJECTIVE_ROOM, - textures: [ - '/assets/images/dungeon1/tiles/room_4x8_altar.png', - '/assets/images/dungeon1/tiles/room_4x8_tomb.png' - ], - variants: { - [DIRECTIONS.NORTH]: { - width: 4, height: 8, - layout: [ - [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], - [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1] - ], - exits: [ - { x: 1, y: 0, direction: DIRECTIONS.SOUTH }, { x: 2, y: 0, direction: DIRECTIONS.SOUTH } - ] - }, - [DIRECTIONS.EAST]: { - width: 8, height: 4, - layout: [ - [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1] - ], - exits: [ - { x: 0, y: 1, direction: DIRECTIONS.WEST }, { x: 0, y: 2, direction: DIRECTIONS.WEST } - ] - }, - [DIRECTIONS.SOUTH]: { - width: 4, height: 8, - layout: [ - [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], - [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1] - ], - exits: [ - { x: 1, y: 7, direction: DIRECTIONS.NORTH }, { x: 2, y: 7, direction: DIRECTIONS.NORTH } - ] - }, - [DIRECTIONS.WEST]: { - width: 8, height: 4, - layout: [ - [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1] - ], - exits: [ - { x: 7, y: 1, direction: DIRECTIONS.EAST }, { x: 7, y: 2, direction: DIRECTIONS.EAST } - ] - } - } + textures: ['/assets/images/dungeon1/tiles/room_4x8_altar.png'], + variants: getStandard4x8Variants() + }, + 'room_objective_2': { + id: 'room_objective_2', + name: 'Tomb of the Overlord', + type: TILE_TYPES.OBJECTIVE_ROOM, + textures: ['/assets/images/dungeon1/tiles/room_4x8_tomb.png'], + variants: getStandard4x8Variants() + }, + 'room_objective_3': { + id: 'room_objective_3', + name: 'The Dread Mill', + type: TILE_TYPES.OBJECTIVE_ROOM, + textures: ['/assets/images/dungeon1/tiles/room_4x8_placeholder.png'], + variants: getStandard4x8Variants() + }, + 'room_objective_4': { + id: 'room_objective_4', + name: 'Monster\'s Lair', + type: TILE_TYPES.OBJECTIVE_ROOM, + textures: ['/assets/images/dungeon1/tiles/room_4x8_placeholder.png'], + variants: getStandard4x8Variants() + }, + 'room_objective_5': { + id: 'room_objective_5', + name: 'Fire Chasm', + type: TILE_TYPES.OBJECTIVE_ROOM, + textures: ['/assets/images/dungeon1/tiles/room_4x8_placeholder.png'], + variants: getStandard4x8Variants() } }; + +// Helper functions to reduce redundancy +function getStandard4x4Variants() { + return { + [DIRECTIONS.NORTH]: { + width: 4, height: 4, + layout: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], + exits: [ + { x: 1, y: 0, direction: DIRECTIONS.SOUTH }, { x: 2, y: 0, direction: DIRECTIONS.SOUTH }, + { x: 1, y: 3, direction: DIRECTIONS.NORTH }, { x: 2, y: 3, direction: DIRECTIONS.NORTH } + ] + }, + [DIRECTIONS.EAST]: { + width: 4, height: 4, + layout: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], + exits: [ + { x: 0, y: 1, direction: DIRECTIONS.WEST }, { x: 0, y: 2, direction: DIRECTIONS.WEST }, + { x: 3, y: 1, direction: DIRECTIONS.EAST }, { x: 3, y: 2, direction: DIRECTIONS.EAST } + ] + }, + [DIRECTIONS.SOUTH]: { + width: 4, height: 4, + layout: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], + exits: [ + { x: 1, y: 3, direction: DIRECTIONS.NORTH }, { x: 2, y: 3, direction: DIRECTIONS.NORTH }, + { x: 1, y: 0, direction: DIRECTIONS.SOUTH }, { x: 2, y: 0, direction: DIRECTIONS.SOUTH } + ] + }, + [DIRECTIONS.WEST]: { + width: 4, height: 4, + layout: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], + exits: [ + { x: 3, y: 1, direction: DIRECTIONS.EAST }, { x: 3, y: 2, direction: DIRECTIONS.EAST }, + { x: 0, y: 1, direction: DIRECTIONS.WEST }, { x: 0, y: 2, direction: DIRECTIONS.WEST } + ] + } + }; +} + +function getStandard4x8Variants() { + return { + [DIRECTIONS.NORTH]: { + width: 4, height: 8, + layout: Array(8).fill([1, 1, 1, 1]), + exits: [{ x: 1, y: 0, direction: DIRECTIONS.SOUTH }, { x: 2, y: 0, direction: DIRECTIONS.SOUTH }] + }, + [DIRECTIONS.EAST]: { + width: 8, height: 4, + layout: Array(4).fill([1, 1, 1, 1, 1, 1, 1, 1]), + exits: [{ x: 0, y: 1, direction: DIRECTIONS.WEST }, { x: 0, y: 2, direction: DIRECTIONS.WEST }] + }, + [DIRECTIONS.SOUTH]: { + width: 4, height: 8, + layout: Array(8).fill([1, 1, 1, 1]), + exits: [{ x: 1, y: 7, direction: DIRECTIONS.NORTH }, { x: 2, y: 7, direction: DIRECTIONS.NORTH }] + }, + [DIRECTIONS.WEST]: { + width: 8, height: 4, + layout: Array(4).fill([1, 1, 1, 1, 1, 1, 1, 1]), + exits: [{ x: 7, y: 1, direction: DIRECTIONS.EAST }, { x: 7, y: 2, direction: DIRECTIONS.EAST }] + } + }; +} diff --git a/src/engine/events/EventInterpreter.js b/src/engine/events/EventInterpreter.js index 9199775..b4f023c 100644 --- a/src/engine/events/EventInterpreter.js +++ b/src/engine/events/EventInterpreter.js @@ -7,6 +7,7 @@ export class EventInterpreter { this.queue = []; this.isProcessing = false; this.currentContext = {}; // Store temporary variables (e.g. "victim") + this.lastSelectedTargets = []; // Store the results of the last SELECCION } /** @@ -174,6 +175,7 @@ export class EventInterpreter { } // Store result + this.lastSelectedTargets = targets; if (action.guardar_como) { this.currentContext[action.guardar_como] = targets[0]; } @@ -225,6 +227,9 @@ export class EventInterpreter { let targets = []; if (action.objetivo) { if (this.currentContext[action.objetivo]) targets = [this.currentContext[action.objetivo]]; + } else { + // Default to the last selected targets from the previous action + targets = this.lastSelectedTargets || []; } let amount = 0; diff --git a/src/engine/game/GameEngine.js b/src/engine/game/GameEngine.js index fc85a83..2bb24c3 100644 --- a/src/engine/game/GameEngine.js +++ b/src/engine/game/GameEngine.js @@ -892,11 +892,28 @@ export class GameEngine { findSpawnPoints(count, tileId = null) { // Collect all currently available cells (occupiedCells maps "x,y" => tileId) - // If tileId is provided, filter ONLY cells belonging to that tile. const candidates = []; + // If no specific tileId is provided (e.g., Power Event), + // restrict spawn to tiles currently occupied by heroes. + let allowedTileIds = null; + if (!tileId && this.heroes.length > 0) { + allowedTileIds = new Set(); + this.heroes.forEach(h => { + const hTileId = this.dungeon.grid.occupiedCells.get(`${h.x},${h.y}`); + if (hTileId) allowedTileIds.add(hTileId); + }); + console.log("[GameEngine] Power Event Spawn: Restricting to tiles with heroes:", Array.from(allowedTileIds)); + } + for (const [key, tid] of this.dungeon.grid.occupiedCells.entries()) { - if (tileId && tid !== tileId) continue; + // If tileId provided, must match it. + // If no tileId provided, must be one of the tiles with heroes. + if (tileId) { + if (tid !== tileId) continue; + } else if (allowedTileIds) { + if (!allowedTileIds.has(tid)) continue; + } const [x, y] = key.split(',').map(Number); diff --git a/src/view/ui/FeedbackUI.js b/src/view/ui/FeedbackUI.js index bb2dccc..d36cb3f 100644 --- a/src/view/ui/FeedbackUI.js +++ b/src/view/ui/FeedbackUI.js @@ -276,7 +276,7 @@ export class FeedbackUI { showTemporaryMessage(title, message, duration = 2000) { const modal = document.createElement('div'); Object.assign(modal.style, { - position: 'absolute', top: '25%', left: '50%', transform: 'translate(-50%, -50%)', + position: 'absolute', top: '70%', left: '50%', transform: 'translate(-50%, -50%)', backgroundColor: 'rgba(139, 0, 0, 0.9)', color: '#fff', padding: '15px 30px', borderRadius: '8px', border: '2px solid #ff4444', fontFamily: '"Cinzel", serif', fontSize: '20px', textShadow: '2px 2px 4px black', zIndex: '2000', pointerEvents: 'none',