Refine FOW visuals, Turn Skipping logic, and UI Polish
This commit is contained in:
16
DEVLOG.md
16
DEVLOG.md
@@ -23,13 +23,23 @@
|
|||||||
- Se abandonó la idea de radio por celdas simples.
|
- Se abandonó la idea de radio por celdas simples.
|
||||||
- Nueva lógica: Se identifica la **Sección de Tablero (Tile)** del Líder.
|
- Nueva lógica: Se identifica la **Sección de Tablero (Tile)** del Líder.
|
||||||
- Se calculan las secciones conectadas físicamente (puertas/pasillos) mediante análisis de `canMoveBetween` en la rejilla.
|
- Se calculan las secciones conectadas físicamente (puertas/pasillos) mediante análisis de `canMoveBetween` en la rejilla.
|
||||||
- **Renderizado Dinámico**:
|
- **Renderizado Dinámico de Niebla**:
|
||||||
- `GameRenderer` ahora agrupa las losetas en `dungeonGroup`.
|
- `GameRenderer` ahora agrupa las losetas en `dungeonGroup`.
|
||||||
- Método `updateFogOfWar` que oculta/muestra losetas completas basándose en la lista de IDs visibles calculada por el motor.
|
- Método `updateFogOfWar` que oculta/muestra losetas y **Entidades** (héroes/monstruos) basándose en la visibilidad de su posición.
|
||||||
- La iluminación se actualiza en tiempo real con cada paso del portador de la lámpara.
|
- La iluminación se actualiza en tiempo real con cada paso del portador de la lámpara.
|
||||||
|
|
||||||
|
#### 3. UX de Combate
|
||||||
|
- **Feedback Visual de Objetivo**: Se ha añadido un **Anillo Azul** que señala al héroe objetivo de un monstruo *antes* de que se realice el ataque, permitiendo al jugador identificar la amenaza inmediatamente.
|
||||||
|
- **Limpieza de UI**: Al comenzar la fase de monstruos, se eliminan automáticamente todos los indicadores de selección (anillos verdes/amarillos) para limpiar la escena.
|
||||||
|
- **Persistencia de Héroe Activo**: El héroe activo ya no pierde su estado de selección al moverse o al hacer clic sobre sí mismo accidentalmente, mejorando la fluidez del turno.
|
||||||
|
|
||||||
|
#### 4. UX y Lógica de Juego
|
||||||
|
- **Ocultación de Entidades en Niebla**: Los héroes y monstruos que quedan fuera del alcance de la lámpara (losetas no visibles) ahora desaparecen completamente de la vista, aumentando la inmersión.
|
||||||
|
- **Salto de Turno Automático**: Si un héroe comienza su turno en una zona oscura (oculta por la Niebla de Guerra), pierde automáticamente su turno hasta que sea "rescatado" (iluminado de nuevo) por el Portador de la Lámpara.
|
||||||
|
- **Botones de Fase**: Se ha reorganizado la barra superior. El botón de "Acabar Fase" ahora comparte espacio con un nuevo botón "Acabar Turno" específico para cada héroe, facilitando el flujo de juego sin tener que buscar en la ficha del personaje.
|
||||||
|
|
||||||
### Estado Actual
|
### Estado Actual
|
||||||
El juego ahora respeta las reglas de visión y turno del juego de mesa original. La sensación de exploración es más tensa al ocultarse las zonas lejanas ("se perderán en la oscuridad"), y el orden táctico es crucial.
|
El juego ahora respeta las reglas de visión y turno del juego de mesa original con una fidelidad visual alta. La sensación de exploración es más tensa al ocultarse las zonas lejanas ("se perderán en la oscuridad"), y el orden táctico es crucial. La UI es más intuitiva y limpia durante el combate.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,8 @@
|
|||||||
- [x] UI Improvements (Spanish Stats, Tooltips)
|
- [x] UI Improvements (Spanish Stats, Tooltips)
|
||||||
- [x] Implement Turn Initiative System (Strict Order, Leader First)
|
- [x] Implement Turn Initiative System (Strict Order, Leader First)
|
||||||
- [x] Implement Fog of War (Lamp Rule based on Board Sections)
|
- [x] Implement Fog of War (Lamp Rule based on Board Sections)
|
||||||
|
- [x] Refine FOW (Entity Hiding, Turn Skipping)
|
||||||
|
- [x] UI Polish (End Turn placement, Target Rings, Clean Monster Phase)
|
||||||
|
|
||||||
## Phase 4: Campaign System
|
## Phase 4: Campaign System
|
||||||
- [ ] **Campaign Manager**
|
- [ ] **Campaign Manager**
|
||||||
|
|||||||
@@ -55,6 +55,12 @@ export class GameEngine {
|
|||||||
if (phase === 'hero') {
|
if (phase === 'hero') {
|
||||||
this.initializeTurnOrder();
|
this.initializeTurnOrder();
|
||||||
}
|
}
|
||||||
|
if (phase === 'monster') {
|
||||||
|
if (window.RENDERER && window.RENDERER.clearAllActiveRings) {
|
||||||
|
window.RENDERER.clearAllActiveRings();
|
||||||
|
}
|
||||||
|
this.deselectEntity();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// End of Turn Logic (Buffs, cooldowns, etc)
|
// End of Turn Logic (Buffs, cooldowns, etc)
|
||||||
@@ -211,9 +217,44 @@ export class GameEngine {
|
|||||||
|
|
||||||
nextHeroTurn() {
|
nextHeroTurn() {
|
||||||
this.currentTurnIndex++;
|
this.currentTurnIndex++;
|
||||||
if (this.currentTurnIndex < this.heroTurnOrder.length) {
|
|
||||||
this.activateHero(this.heroTurnOrder[this.currentTurnIndex]);
|
// Loop to find next VALID hero (visible)
|
||||||
|
while (this.currentTurnIndex < this.heroTurnOrder.length) {
|
||||||
|
const nextHero = this.heroTurnOrder[this.currentTurnIndex];
|
||||||
|
|
||||||
|
// Check visibility
|
||||||
|
// Exception: Leader (hasLantern) is ALWAYS visible.
|
||||||
|
if (nextHero.hasLantern) {
|
||||||
|
this.activateHero(nextHero);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if hero is in a visible tile
|
||||||
|
// Get hero tile ID
|
||||||
|
const heroTileId = this.dungeon.grid.occupiedCells.get(`${nextHero.x},${nextHero.y}`);
|
||||||
|
|
||||||
|
// If currentVisibleTileIds is defined, enforce it.
|
||||||
|
if (this.currentVisibleTileIds) {
|
||||||
|
if (heroTileId && this.currentVisibleTileIds.has(heroTileId)) {
|
||||||
|
this.activateHero(nextHero);
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
console.log(`Skipping turn for ${nextHero.name} (In Darkness)`);
|
||||||
|
if (this.onShowMessage) {
|
||||||
|
// Optional: Small notification or log
|
||||||
|
// this.onShowMessage("Perdido en la oscuridad", `${nextHero.name} pierde su turno.`);
|
||||||
|
}
|
||||||
|
this.currentTurnIndex++; // Skip and continue loop
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Should not happen if updateLighting runs, but fallback
|
||||||
|
this.activateHero(nextHero);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If loop finishes, no more heroes
|
||||||
|
if (this.currentTurnIndex >= this.heroTurnOrder.length) {
|
||||||
console.log("All heroes acted. Ending Phase sequence if auto?");
|
console.log("All heroes acted. Ending Phase sequence if auto?");
|
||||||
this.deselectEntity();
|
this.deselectEntity();
|
||||||
if (window.RENDERER) {
|
if (window.RENDERER) {
|
||||||
@@ -391,9 +432,23 @@ export class GameEngine {
|
|||||||
|
|
||||||
if (this.selectedEntity === clickedEntity) {
|
if (this.selectedEntity === clickedEntity) {
|
||||||
// Toggle Deselect
|
// Toggle Deselect
|
||||||
// Prevent deselecting active hero effectively? Or allow it but re-select him if trying to select others?
|
// EXCEPTION: In Hero Phase, if I click MYSELF (Active Hero), do NOT deselect.
|
||||||
// For now, allow deselect freely.
|
// It's annoying to lose the card.
|
||||||
|
const isHeroPhase = this.turnManager.currentPhase === 'hero';
|
||||||
|
let isActiveTurnHero = false;
|
||||||
|
if (isHeroPhase && this.heroTurnOrder && this.currentTurnIndex !== undefined) {
|
||||||
|
const activeHero = this.heroTurnOrder[this.currentTurnIndex];
|
||||||
|
if (activeHero && activeHero.id === clickedEntity.id) {
|
||||||
|
isActiveTurnHero = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isActiveTurnHero) {
|
||||||
|
// Do nothing (keep selected)
|
||||||
|
// Maybe blink the card or something?
|
||||||
|
} else {
|
||||||
this.deselectEntity();
|
this.deselectEntity();
|
||||||
|
}
|
||||||
} else if (this.selectedMonster === clickedMonster && clickedMonster) {
|
} else if (this.selectedMonster === clickedMonster && clickedMonster) {
|
||||||
// Clicking on already selected monster - deselect it
|
// Clicking on already selected monster - deselect it
|
||||||
const monsterId = this.selectedMonster.id;
|
const monsterId = this.selectedMonster.id;
|
||||||
@@ -503,6 +558,9 @@ export class GameEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store active visibility sets for Turn Logic
|
||||||
|
this.currentVisibleTileIds = visibleTileIds;
|
||||||
|
|
||||||
window.RENDERER.updateFogOfWar(Array.from(visibleTileIds));
|
window.RENDERER.updateFogOfWar(Array.from(visibleTileIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -720,8 +778,33 @@ export class GameEngine {
|
|||||||
if (entity.currentMoves < 0) entity.currentMoves = 0;
|
if (entity.currentMoves < 0) entity.currentMoves = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AUTO-DESELECT LOGIC
|
||||||
|
// In Hero Phase, we want to KEEP the active hero selected to avoid re-selecting.
|
||||||
|
const isHeroPhase = this.turnManager.currentPhase === 'hero';
|
||||||
|
// Check if entity is the currently active turn hero
|
||||||
|
let isActiveTurnHero = false;
|
||||||
|
if (isHeroPhase && this.heroTurnOrder && this.currentTurnIndex !== undefined) {
|
||||||
|
const activeHero = this.heroTurnOrder[this.currentTurnIndex];
|
||||||
|
if (activeHero && activeHero.id === entity.id) {
|
||||||
|
isActiveTurnHero = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isActiveTurnHero) {
|
||||||
|
// Do NOT deselect. Just clear path.
|
||||||
|
this.plannedPath = [];
|
||||||
|
if (this.onPathChange) this.onPathChange([]);
|
||||||
|
|
||||||
|
// Also force update UI/Card (stats changed)
|
||||||
|
if (this.onEntitySelect) {
|
||||||
|
// Re-trigger selection to ensure UI is fresh?
|
||||||
|
// UIManager listens to onEntityMove to update stats, so that should be covered.
|
||||||
|
// But purely being consistent:
|
||||||
|
}
|
||||||
|
} else {
|
||||||
this.deselectEntity();
|
this.deselectEntity();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -214,11 +214,22 @@ export class MonsterAI {
|
|||||||
|
|
||||||
performAttack(monster, hero) {
|
performAttack(monster, hero) {
|
||||||
// SEQUENCE:
|
// SEQUENCE:
|
||||||
// 1. Show green ring on monster
|
// 0. Show TARGET (Blue Ring) on Hero
|
||||||
// 2. Monster attack animation (we'll simulate with delay)
|
if (this.game.onRangedTarget) {
|
||||||
// 3. Show red ring + shake on hero
|
// Re-using onRangedTarget? Or directly calling renderer?
|
||||||
// 4. Remove both rings
|
// Better to use a specific callback or direct call if available, or just add a new callback.
|
||||||
// 5. Show combat result
|
// But let's check if we can access renderer directly or use a new callback.
|
||||||
|
// The user prompt specifically asked for this feature.
|
||||||
|
// I'll assume we can use game.onEntityTarget if defined, or direct renderer call if needed,
|
||||||
|
// but standard pattern here is callbacks.
|
||||||
|
// Let's add onEntityTarget to GameEngine callbacks if not present, but for now I will try to use global RENDERER if possible
|
||||||
|
// OR simply define a new callback `this.game.onEntityTarget(hero.id, true)`.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct renderer call is safest given current context if we don't want to modify GameEngine interface heavily right now.
|
||||||
|
if (window.RENDERER && window.RENDERER.setEntityTarget) {
|
||||||
|
window.RENDERER.setEntityTarget(hero.id, true);
|
||||||
|
}
|
||||||
|
|
||||||
const result = CombatMechanics.resolveMeleeAttack(monster, hero, this.game);
|
const result = CombatMechanics.resolveMeleeAttack(monster, hero, this.game);
|
||||||
|
|
||||||
@@ -229,9 +240,6 @@ export class MonsterAI {
|
|||||||
|
|
||||||
// Step 2: Attack animation delay (500ms)
|
// Step 2: Attack animation delay (500ms)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Step 3: Trigger hit visual on defender (if hit succeeded)
|
|
||||||
// Step 3: Trigger hit visual on defender REMOVED (Handled by onCombatResult)
|
|
||||||
|
|
||||||
|
|
||||||
// Step 4: Remove green ring after red ring appears (1200ms for red ring duration)
|
// Step 4: Remove green ring after red ring appears (1200ms for red ring duration)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -239,6 +247,11 @@ export class MonsterAI {
|
|||||||
this.game.onEntityActive(monster.id, false);
|
this.game.onEntityActive(monster.id, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove Target Ring
|
||||||
|
if (window.RENDERER && window.RENDERER.setEntityTarget) {
|
||||||
|
window.RENDERER.setEntityTarget(hero.id, false);
|
||||||
|
}
|
||||||
|
|
||||||
// Step 5: Show combat result after both rings are gone
|
// Step 5: Show combat result after both rings are gone
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.game.onCombatResult) {
|
if (this.game.onCombatResult) {
|
||||||
@@ -246,7 +259,7 @@ export class MonsterAI {
|
|||||||
}
|
}
|
||||||
}, 200); // Small delay after rings disappear
|
}, 200); // Small delay after rings disappear
|
||||||
}, 1200); // Wait for red ring to disappear
|
}, 1200); // Wait for red ring to disappear
|
||||||
}, 500); // Attack animation delay
|
}, 800); // Attack animation delay + focus time
|
||||||
}
|
}
|
||||||
|
|
||||||
getAdjacentHero(entity) {
|
getAdjacentHero(entity) {
|
||||||
|
|||||||
@@ -957,46 +957,40 @@ export class GameRenderer {
|
|||||||
if (!this.dungeonGroup) return;
|
if (!this.dungeonGroup) return;
|
||||||
|
|
||||||
const visibleSet = new Set(visibleTileIds);
|
const visibleSet = new Set(visibleTileIds);
|
||||||
|
const visibleCellKeys = new Set();
|
||||||
|
|
||||||
|
// 1. Update Tile Visibility & Collect Visible Cells
|
||||||
this.dungeonGroup.children.forEach(mesh => {
|
this.dungeonGroup.children.forEach(mesh => {
|
||||||
const isVisible = visibleSet.has(mesh.userData.tileId);
|
const isVisible = visibleSet.has(mesh.userData.tileId);
|
||||||
mesh.visible = isVisible;
|
mesh.visible = isVisible;
|
||||||
|
if (isVisible && mesh.userData.cells) {
|
||||||
|
mesh.userData.cells.forEach(cell => {
|
||||||
|
visibleCellKeys.add(`${cell.x},${cell.y}`);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Hide/Show Entities based on Tile Visibility
|
||||||
|
if (this.entities) {
|
||||||
|
this.entities.forEach((mesh, id) => {
|
||||||
|
// Get grid coords (World X, -Z)
|
||||||
|
const gx = Math.round(mesh.position.x);
|
||||||
|
const gy = Math.round(-mesh.position.z);
|
||||||
|
const key = `${gx},${gy}`;
|
||||||
|
|
||||||
|
// If the cell is visible, show the entity. Otherwise hide it.
|
||||||
|
if (visibleCellKeys.has(key)) {
|
||||||
|
mesh.visible = true;
|
||||||
|
} else {
|
||||||
|
mesh.visible = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Also update Doors (in exitGroup)
|
// Also update Doors (in exitGroup)
|
||||||
if (this.exitGroup) {
|
if (this.exitGroup) {
|
||||||
this.exitGroup.children.forEach(door => {
|
this.exitGroup.children.forEach(door => {
|
||||||
// If door connects to AT LEAST ONE visible tile, it should be visible?
|
|
||||||
// Or if it is part of a visible tile?
|
|
||||||
// Doors usually belong to the tile they were spawned with?
|
|
||||||
// Logic: Check if door is within/adjacent to visible cells?
|
|
||||||
// Door userData has `cells`.
|
|
||||||
// We need to map cells to tileIds? We don't have that map here.
|
|
||||||
// Simplified: If the door is physically close to a visible tile, show it.
|
|
||||||
|
|
||||||
// Better approach:
|
|
||||||
// Engine should pass visible cells?
|
|
||||||
// Using tileIds is robust for tiles.
|
|
||||||
// For doors: we can check if any of the door's cells are roughly inside a visible tile's bounding box?
|
|
||||||
// Or just show all doors for now? No, floating doors in darkness.
|
|
||||||
|
|
||||||
// Hack: Show door if its position is near a visible tile.
|
|
||||||
let doorVisible = false;
|
|
||||||
const dx = door.position.x;
|
|
||||||
const dy = -door.position.z; // World Z is -Grid Y
|
|
||||||
|
|
||||||
// Check against visible meshes
|
|
||||||
// This is O(N*M), slow.
|
|
||||||
// But N (visible tiles) is small (1-5).
|
|
||||||
|
|
||||||
// Let's assume doors connected to visible tiles are visible.
|
|
||||||
// I need to know which tile a door belongs to.
|
|
||||||
// I will skip door FOW for this iteration or make them always visible but dim?
|
|
||||||
// Let's hide them by default and check proximity.
|
|
||||||
|
|
||||||
door.visible = false;
|
door.visible = false;
|
||||||
// Can't easily check without grid.
|
|
||||||
// Engine will handle Door visibility via `setEntityVisibility`? No, they are meshes not entities.
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Re-iterate to show close doors
|
// Re-iterate to show close doors
|
||||||
@@ -1028,6 +1022,48 @@ export class GameRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setEntityTarget(entityId, isTarget) {
|
||||||
|
const mesh = this.entities.get(entityId);
|
||||||
|
if (!mesh) return;
|
||||||
|
|
||||||
|
// Remove existing target ring
|
||||||
|
const oldRing = mesh.getObjectByName("TargetRing");
|
||||||
|
if (oldRing) mesh.remove(oldRing);
|
||||||
|
|
||||||
|
if (isTarget) {
|
||||||
|
// Blue Ring logic
|
||||||
|
const ringGeom = new THREE.RingGeometry(0.3, 0.4, 32);
|
||||||
|
const ringMat = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0x00AADD, // Light Blue
|
||||||
|
side: THREE.DoubleSide,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.8
|
||||||
|
});
|
||||||
|
const ring = new THREE.Mesh(ringGeom, ringMat);
|
||||||
|
ring.rotation.x = -Math.PI / 2;
|
||||||
|
// Align with floor (relative to mesh center)
|
||||||
|
const h = 1.56;
|
||||||
|
ring.position.y = -h / 2 + 0.06; // Slightly above floor/selection
|
||||||
|
|
||||||
|
ring.name = "TargetRing";
|
||||||
|
mesh.add(ring);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAllActiveRings() {
|
||||||
|
if (!this.entities) return;
|
||||||
|
this.entities.forEach(mesh => {
|
||||||
|
const ring = mesh.getObjectByName("ActiveRing"); // Green ring
|
||||||
|
if (ring) mesh.remove(ring);
|
||||||
|
const ring2 = mesh.getObjectByName("SelectionRing"); // Yellow ring
|
||||||
|
if (ring2) ring2.visible = false;
|
||||||
|
|
||||||
|
// Also clear TargetRing if any
|
||||||
|
const ring3 = mesh.getObjectByName("TargetRing");
|
||||||
|
if (ring3) mesh.remove(ring3);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
openDoor(doorMesh) {
|
openDoor(doorMesh) {
|
||||||
if (!doorMesh || !doorMesh.userData.isDoor) return;
|
if (!doorMesh || !doorMesh.userData.isDoor) return;
|
||||||
if (doorMesh.userData.isOpen) return; // Already open
|
if (doorMesh.userData.isOpen) return; // Already open
|
||||||
|
|||||||
@@ -113,4 +113,5 @@ export class UIManager {
|
|||||||
showTemporaryMessage(t, m, d) { this.feedback.showTemporaryMessage(t, m, d); }
|
showTemporaryMessage(t, m, d) { this.feedback.showTemporaryMessage(t, m, d); }
|
||||||
showCombatLog(log) { this.feedback.showCombatLog(log); }
|
showCombatLog(log) { this.feedback.showCombatLog(log); }
|
||||||
showRangedAttackUI(monster) { this.cards.showRangedAttackUI(monster); }
|
showRangedAttackUI(monster) { this.cards.showRangedAttackUI(monster); }
|
||||||
|
hideMonsterCard() { this.cards.hideMonsterCard(); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,12 +44,24 @@ export class TurnStatusUI {
|
|||||||
`;
|
`;
|
||||||
this.statusPanel.appendChild(this.phaseInfo);
|
this.statusPanel.appendChild(this.phaseInfo);
|
||||||
|
|
||||||
// End Phase Button
|
// Button Container (Row for split buttons)
|
||||||
|
this.buttonContainer = document.createElement('div');
|
||||||
|
Object.assign(this.buttonContainer.style, {
|
||||||
|
marginTop: '10px',
|
||||||
|
width: '300px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: '4px', // Space between buttons
|
||||||
|
justifyItems: 'center',
|
||||||
|
pointerEvents: 'none' // Container checks pointer events safely? inner btns will be auto.
|
||||||
|
});
|
||||||
|
this.statusPanel.appendChild(this.buttonContainer);
|
||||||
|
|
||||||
|
// End Phase Button (Left)
|
||||||
this.endPhaseBtn = document.createElement('button');
|
this.endPhaseBtn = document.createElement('button');
|
||||||
this.endPhaseBtn.textContent = 'ACABAR FASE AVENTUREROS';
|
this.endPhaseBtn.textContent = 'ACABAR FASE AVENTUREROS';
|
||||||
Object.assign(this.endPhaseBtn.style, {
|
Object.assign(this.endPhaseBtn.style, {
|
||||||
marginTop: '10px',
|
flex: '1', // Take available space (50% if shared)
|
||||||
width: '300px',
|
|
||||||
padding: '8px',
|
padding: '8px',
|
||||||
backgroundColor: '#daa520',
|
backgroundColor: '#daa520',
|
||||||
color: '#000',
|
color: '#000',
|
||||||
@@ -59,7 +71,7 @@ export class TurnStatusUI {
|
|||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
display: 'none',
|
display: 'none',
|
||||||
fontFamily: '"Cinzel", serif',
|
fontFamily: '"Cinzel", serif',
|
||||||
fontSize: '12px',
|
fontSize: '11px', // Slightly smaller text for split
|
||||||
pointerEvents: 'auto'
|
pointerEvents: 'auto'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -67,10 +79,38 @@ export class TurnStatusUI {
|
|||||||
this.endPhaseBtn.onmouseout = () => { this.endPhaseBtn.style.backgroundColor = '#daa520'; };
|
this.endPhaseBtn.onmouseout = () => { this.endPhaseBtn.style.backgroundColor = '#daa520'; };
|
||||||
|
|
||||||
this.endPhaseBtn.onclick = () => {
|
this.endPhaseBtn.onclick = () => {
|
||||||
|
// Only if visible!
|
||||||
console.log('[TurnStatusUI] End Phase Button Clicked', this.game.turnManager.currentPhase);
|
console.log('[TurnStatusUI] End Phase Button Clicked', this.game.turnManager.currentPhase);
|
||||||
this.game.turnManager.nextPhase();
|
this.game.turnManager.nextPhase();
|
||||||
};
|
};
|
||||||
this.statusPanel.appendChild(this.endPhaseBtn);
|
this.buttonContainer.appendChild(this.endPhaseBtn);
|
||||||
|
|
||||||
|
// End Turn Button (Right - Hero only)
|
||||||
|
this.endTurnBtn = document.createElement('button');
|
||||||
|
this.endTurnBtn.textContent = 'ACABAR TURNO';
|
||||||
|
Object.assign(this.endTurnBtn.style, {
|
||||||
|
flex: '1', // 50% width
|
||||||
|
padding: '8px',
|
||||||
|
backgroundColor: '#8B4513', // Different color (Dark Red/Wood)
|
||||||
|
color: '#FFD700',
|
||||||
|
border: '1px solid #DAA520',
|
||||||
|
borderRadius: '3px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'none',
|
||||||
|
fontFamily: '"Cinzel", serif',
|
||||||
|
fontSize: '11px',
|
||||||
|
pointerEvents: 'auto'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.endTurnBtn.onmouseover = () => { this.endTurnBtn.style.backgroundColor = '#A0522D'; };
|
||||||
|
this.endTurnBtn.onmouseout = () => { this.endTurnBtn.style.backgroundColor = '#8B4513'; };
|
||||||
|
this.endTurnBtn.onclick = () => {
|
||||||
|
if (this.game.nextHeroTurn) {
|
||||||
|
this.game.nextHeroTurn();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.buttonContainer.appendChild(this.endTurnBtn);
|
||||||
|
|
||||||
// Notification Area (Power Roll)
|
// Notification Area (Power Roll)
|
||||||
this.notificationArea = document.createElement('div');
|
this.notificationArea = document.createElement('div');
|
||||||
@@ -109,17 +149,31 @@ export class TurnStatusUI {
|
|||||||
|
|
||||||
this.phaseInfo.innerHTML = content;
|
this.phaseInfo.innerHTML = content;
|
||||||
|
|
||||||
if (this.endPhaseBtn) {
|
if (this.buttonContainer) {
|
||||||
|
this.buttonContainer.style.display = 'flex'; // Default
|
||||||
|
|
||||||
|
if (this.endPhaseBtn) this.endPhaseBtn.style.display = 'none';
|
||||||
|
if (this.endTurnBtn) this.endTurnBtn.style.display = 'none';
|
||||||
|
|
||||||
if (phase === 'hero') {
|
if (phase === 'hero') {
|
||||||
|
// Split Mode
|
||||||
|
if (this.endPhaseBtn) {
|
||||||
this.endPhaseBtn.style.display = 'block';
|
this.endPhaseBtn.style.display = 'block';
|
||||||
this.endPhaseBtn.textContent = 'ACABAR FASE AVENTUREROS';
|
this.endPhaseBtn.textContent = 'ACABAR FASE'; // Shorter text
|
||||||
this.endPhaseBtn.title = "Pasar a la Fase de Monstruos";
|
this.endPhaseBtn.title = "Pasar a la Fase de Monstruos";
|
||||||
|
}
|
||||||
|
if (this.endTurnBtn) {
|
||||||
|
this.endTurnBtn.style.display = 'block'; // Show right button
|
||||||
|
}
|
||||||
} else if (phase === 'exploration') {
|
} else if (phase === 'exploration') {
|
||||||
|
// Full Width Mode for End Phase (used as End Turn in exp)
|
||||||
|
if (this.endPhaseBtn) {
|
||||||
this.endPhaseBtn.style.display = 'block';
|
this.endPhaseBtn.style.display = 'block';
|
||||||
this.endPhaseBtn.textContent = 'ACABAR TURNO';
|
this.endPhaseBtn.textContent = 'ACABAR TURNO';
|
||||||
this.endPhaseBtn.title = "Finalizar turno y comenzar Fase de Poder";
|
this.endPhaseBtn.title = "Finalizar turno y comenzar Fase de Poder";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.endPhaseBtn.style.display = 'none';
|
// Nothing visible
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,11 +237,13 @@ export class UnitCardManager {
|
|||||||
Object.assign(invBtn.style, {
|
Object.assign(invBtn.style, {
|
||||||
width: '100%', padding: '8px', marginTop: '8px', backgroundColor: '#444',
|
width: '100%', padding: '8px', marginTop: '8px', backgroundColor: '#444',
|
||||||
color: '#fff', border: '1px solid #777', borderRadius: '4px',
|
color: '#fff', border: '1px solid #777', borderRadius: '4px',
|
||||||
fontFamily: '"Cinzel", serif', fontSize: '12px', cursor: 'not-allowed'
|
fontFamily: '"Cinzel", serif', fontSize: '12px', cursor: 'pointer' // Changed cursor to pointer for feel, though functionality implies future
|
||||||
});
|
});
|
||||||
invBtn.title = 'Inventario (Próximamente)';
|
invBtn.title = 'Inventario (Próximamente)';
|
||||||
card.appendChild(invBtn);
|
card.appendChild(invBtn);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Wizard Spells
|
// Wizard Spells
|
||||||
if (hero.key === 'wizard') {
|
if (hero.key === 'wizard') {
|
||||||
const spellsBtn = document.createElement('button');
|
const spellsBtn = document.createElement('button');
|
||||||
|
|||||||
Reference in New Issue
Block a user