Sesión 18: Refinado de spawning, ajustes de pasillo inicial y mejoras de UX/UI

This commit is contained in:
2026-01-11 18:33:19 +01:00
parent da4c93bf98
commit 2046aa7117
11 changed files with 328 additions and 141 deletions

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -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) {

View File

@@ -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
*/

View File

@@ -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 }]
}
};
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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',