🐛 Stockpile opacity + layout overlap + ESC menu padding #21
@@ -49,6 +49,9 @@ export const VILLAGER_NAMES = [
|
|||||||
export const SAVE_KEY = 'tg_save_v5'
|
export const SAVE_KEY = 'tg_save_v5'
|
||||||
export const AUTOSAVE_INTERVAL = 30_000
|
export const AUTOSAVE_INTERVAL = 30_000
|
||||||
|
|
||||||
|
/** localStorage key for UI settings (opacity etc.) — separate from the game save. */
|
||||||
|
export const UI_SETTINGS_KEY = 'tg_ui_settings'
|
||||||
|
|
||||||
/** Milliseconds for one tree-seedling stage to advance (two stages = full tree). */
|
/** Milliseconds for one tree-seedling stage to advance (two stages = full tree). */
|
||||||
export const TREE_SEEDLING_STAGE_MS = 60_000 // 1 min per stage → 2 min total
|
export const TREE_SEEDLING_STAGE_MS = 60_000 // 1 min per stage → 2 min total
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { BuildingType, JobPriorities } from '../types'
|
|||||||
import type { FarmingTool } from '../systems/FarmingSystem'
|
import type { FarmingTool } from '../systems/FarmingSystem'
|
||||||
import type { DebugData } from '../systems/DebugSystem'
|
import type { DebugData } from '../systems/DebugSystem'
|
||||||
import { stateManager } from '../StateManager'
|
import { stateManager } from '../StateManager'
|
||||||
|
import { UI_SETTINGS_KEY } from '../config'
|
||||||
|
|
||||||
const ITEM_ICONS: Record<string, string> = {
|
const ITEM_ICONS: Record<string, string> = {
|
||||||
wood: '🪵', stone: '🪨', wheat_seed: '🌱', carrot_seed: '🥕',
|
wood: '🪵', stone: '🪨', wheat_seed: '🌱', carrot_seed: '🥕',
|
||||||
@@ -46,6 +47,11 @@ export class UIScene extends Phaser.Scene {
|
|||||||
logTexts: Phaser.GameObjects.Text[]
|
logTexts: Phaser.GameObjects.Text[]
|
||||||
} | null = null
|
} | null = null
|
||||||
|
|
||||||
|
/** Current overlay background opacity (0.4–1.0, default 0.8). Persisted in localStorage. */
|
||||||
|
private uiOpacity = 0.8
|
||||||
|
private settingsGroup!: Phaser.GameObjects.Group
|
||||||
|
private settingsVisible = false
|
||||||
|
|
||||||
constructor() { super({ key: 'UI' }) }
|
constructor() { super({ key: 'UI' }) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,6 +59,7 @@ export class UIScene extends Phaser.Scene {
|
|||||||
* keyboard shortcuts (B, V, F3, ESC).
|
* keyboard shortcuts (B, V, F3, ESC).
|
||||||
*/
|
*/
|
||||||
create(): void {
|
create(): void {
|
||||||
|
this.loadUISettings()
|
||||||
this.createStockpilePanel()
|
this.createStockpilePanel()
|
||||||
this.createHintText()
|
this.createHintText()
|
||||||
this.createToast()
|
this.createToast()
|
||||||
@@ -85,6 +92,7 @@ export class UIScene extends Phaser.Scene {
|
|||||||
this.escMenuGroup = this.add.group()
|
this.escMenuGroup = this.add.group()
|
||||||
this.confirmGroup = this.add.group()
|
this.confirmGroup = this.add.group()
|
||||||
this.nisseInfoGroup = this.add.group()
|
this.nisseInfoGroup = this.add.group()
|
||||||
|
this.settingsGroup = this.add.group()
|
||||||
|
|
||||||
this.input.on('pointerdown', (ptr: Phaser.Input.Pointer) => {
|
this.input.on('pointerdown', (ptr: Phaser.Input.Pointer) => {
|
||||||
if (ptr.rightButtonDown()) {
|
if (ptr.rightButtonDown()) {
|
||||||
@@ -196,7 +204,7 @@ export class UIScene extends Phaser.Scene {
|
|||||||
{ kind: 'stockpile_zone', label: '📦 Stockpile', cost: 'free (workers deliver here)' },
|
{ kind: 'stockpile_zone', label: '📦 Stockpile', cost: 'free (workers deliver here)' },
|
||||||
]
|
]
|
||||||
const menuX = this.scale.width / 2 - 150, menuY = this.scale.height / 2 - 140
|
const menuX = this.scale.width / 2 - 150, menuY = this.scale.height / 2 - 140
|
||||||
const bg = this.add.rectangle(menuX, menuY, 300, 280, 0x000000, 0.88).setOrigin(0,0).setScrollFactor(0).setDepth(200)
|
const bg = this.add.rectangle(menuX, menuY, 300, 280, 0x000000, this.uiOpacity).setOrigin(0,0).setScrollFactor(0).setDepth(200)
|
||||||
this.buildMenuGroup.add(bg)
|
this.buildMenuGroup.add(bg)
|
||||||
this.buildMenuGroup.add(this.add.text(menuX + 150, menuY + 14, 'BUILD MENU [B/ESC]', { fontSize: '11px', color: '#aaaaaa', fontFamily: 'monospace' }).setOrigin(0.5,0).setScrollFactor(0).setDepth(201))
|
this.buildMenuGroup.add(this.add.text(menuX + 150, menuY + 14, 'BUILD MENU [B/ESC]', { fontSize: '11px', color: '#aaaaaa', fontFamily: 'monospace' }).setOrigin(0.5,0).setScrollFactor(0).setDepth(201))
|
||||||
|
|
||||||
@@ -263,7 +271,7 @@ export class UIScene extends Phaser.Scene {
|
|||||||
const px = this.scale.width / 2 - panelW / 2
|
const px = this.scale.width / 2 - panelW / 2
|
||||||
const py = this.scale.height / 2 - panelH / 2
|
const py = this.scale.height / 2 - panelH / 2
|
||||||
|
|
||||||
const bg = this.add.rectangle(px, py, panelW, panelH, 0x0a0a0a, 0.92).setOrigin(0,0).setScrollFactor(0).setDepth(210)
|
const bg = this.add.rectangle(px, py, panelW, panelH, 0x0a0a0a, this.uiOpacity).setOrigin(0,0).setScrollFactor(0).setDepth(210)
|
||||||
this.villagerPanelGroup.add(bg)
|
this.villagerPanelGroup.add(bg)
|
||||||
|
|
||||||
this.villagerPanelGroup.add(
|
this.villagerPanelGroup.add(
|
||||||
@@ -383,10 +391,11 @@ export class UIScene extends Phaser.Scene {
|
|||||||
|
|
||||||
/** Creates the debug panel text object (initially hidden). */
|
/** Creates the debug panel text object (initially hidden). */
|
||||||
private createDebugPanel(): void {
|
private createDebugPanel(): void {
|
||||||
|
const hexAlpha = Math.round(this.uiOpacity * 255).toString(16).padStart(2, '0')
|
||||||
this.debugPanelText = this.add.text(10, 80, '', {
|
this.debugPanelText = this.add.text(10, 80, '', {
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
color: '#cccccc',
|
color: '#cccccc',
|
||||||
backgroundColor: '#000000cc',
|
backgroundColor: `#000000${hexAlpha}`,
|
||||||
padding: { x: 8, y: 6 },
|
padding: { x: 8, y: 6 },
|
||||||
lineSpacing: 2,
|
lineSpacing: 2,
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
@@ -463,7 +472,7 @@ export class UIScene extends Phaser.Scene {
|
|||||||
const mx = Math.min(x, this.scale.width - menuW - 4)
|
const mx = Math.min(x, this.scale.width - menuW - 4)
|
||||||
const my = Math.min(y, this.scale.height - menuH - 4)
|
const my = Math.min(y, this.scale.height - menuH - 4)
|
||||||
|
|
||||||
const bg = this.add.rectangle(mx, my, menuW, menuH, 0x000000, 0.88)
|
const bg = this.add.rectangle(mx, my, menuW, menuH, 0x000000, this.uiOpacity)
|
||||||
.setOrigin(0, 0).setScrollFactor(0).setDepth(300)
|
.setOrigin(0, 0).setScrollFactor(0).setDepth(300)
|
||||||
this.contextMenuGroup.add(bg)
|
this.contextMenuGroup.add(bg)
|
||||||
|
|
||||||
@@ -521,6 +530,7 @@ export class UIScene extends Phaser.Scene {
|
|||||||
if (this.buildMenuVisible) { this.closeBuildMenu(); return }
|
if (this.buildMenuVisible) { this.closeBuildMenu(); return }
|
||||||
if (this.villagerPanelVisible){ this.closeVillagerPanel(); return }
|
if (this.villagerPanelVisible){ this.closeVillagerPanel(); return }
|
||||||
if (this.nisseInfoVisible) { this.closeNisseInfoPanel(); return }
|
if (this.nisseInfoVisible) { this.closeNisseInfoPanel(); return }
|
||||||
|
if (this.settingsVisible) { this.closeSettings(); return }
|
||||||
if (this.escMenuVisible) { this.closeEscMenu(); return }
|
if (this.escMenuVisible) { this.closeEscMenu(); return }
|
||||||
// Build/farm mode: let BuildingSystem / FarmingSystem handle their own ESC key.
|
// Build/farm mode: let BuildingSystem / FarmingSystem handle their own ESC key.
|
||||||
// We only skip opening the ESC menu while those modes are active.
|
// We only skip opening the ESC menu while those modes are active.
|
||||||
@@ -564,7 +574,7 @@ export class UIScene extends Phaser.Scene {
|
|||||||
const mx = this.scale.width / 2 - menuW / 2
|
const mx = this.scale.width / 2 - menuW / 2
|
||||||
const my = this.scale.height / 2 - menuH / 2
|
const my = this.scale.height / 2 - menuH / 2
|
||||||
|
|
||||||
const bg = this.add.rectangle(mx, my, menuW, menuH, 0x0a0a0a, 0.95)
|
const bg = this.add.rectangle(mx, my, menuW, menuH, 0x0a0a0a, this.uiOpacity)
|
||||||
.setOrigin(0, 0).setScrollFactor(0).setDepth(400)
|
.setOrigin(0, 0).setScrollFactor(0).setDepth(400)
|
||||||
this.escMenuGroup.add(bg)
|
this.escMenuGroup.add(bg)
|
||||||
this.escMenuGroup.add(
|
this.escMenuGroup.add(
|
||||||
@@ -602,10 +612,153 @@ export class UIScene extends Phaser.Scene {
|
|||||||
window.location.reload()
|
window.location.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Opens an empty Settings panel (placeholder). */
|
/** Opens the Settings overlay. */
|
||||||
private doSettings(): void {
|
private doSettings(): void {
|
||||||
this.closeEscMenu()
|
this.closeEscMenu()
|
||||||
this.showToast('Settings — coming soon')
|
this.openSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Settings overlay ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Opens the settings overlay if it is not already open. */
|
||||||
|
private openSettings(): void {
|
||||||
|
if (this.settingsVisible) return
|
||||||
|
this.settingsVisible = true
|
||||||
|
this.scene.get('Game').events.emit('uiMenuOpen')
|
||||||
|
this.buildSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Closes and destroys the settings overlay. */
|
||||||
|
private closeSettings(): void {
|
||||||
|
if (!this.settingsVisible) return
|
||||||
|
this.settingsVisible = false
|
||||||
|
this.settingsGroup.destroy(true)
|
||||||
|
this.settingsGroup = this.add.group()
|
||||||
|
this.scene.get('Game').events.emit('uiMenuClose')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the settings overlay with an overlay-opacity row (step buttons).
|
||||||
|
* Destroying and recreating this method is used to refresh the displayed value.
|
||||||
|
*/
|
||||||
|
private buildSettings(): void {
|
||||||
|
if (this.settingsGroup) this.settingsGroup.destroy(true)
|
||||||
|
this.settingsGroup = this.add.group()
|
||||||
|
|
||||||
|
const panelW = 280
|
||||||
|
const panelH = 130
|
||||||
|
const px = this.scale.width / 2 - panelW / 2
|
||||||
|
const py = this.scale.height / 2 - panelH / 2
|
||||||
|
|
||||||
|
// Background
|
||||||
|
const bg = this.add.rectangle(px, py, panelW, panelH, 0x0a0a0a, this.uiOpacity)
|
||||||
|
.setOrigin(0, 0).setScrollFactor(0).setDepth(450)
|
||||||
|
this.settingsGroup.add(bg)
|
||||||
|
|
||||||
|
// Title
|
||||||
|
this.settingsGroup.add(
|
||||||
|
this.add.text(px + panelW / 2, py + 14, '⚙️ SETTINGS [ESC close]', {
|
||||||
|
fontSize: '11px', color: '#666666', fontFamily: 'monospace',
|
||||||
|
}).setOrigin(0.5, 0).setScrollFactor(0).setDepth(451)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Opacity label
|
||||||
|
this.settingsGroup.add(
|
||||||
|
this.add.text(px + 16, py + 58, 'Overlay opacity:', {
|
||||||
|
fontSize: '13px', color: '#cccccc', fontFamily: 'monospace',
|
||||||
|
}).setOrigin(0, 0.5).setScrollFactor(0).setDepth(451)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Minus button
|
||||||
|
const minusBtn = this.add.rectangle(px + 170, py + 47, 26, 22, 0x1a1a2e, 0.9)
|
||||||
|
.setOrigin(0, 0).setScrollFactor(0).setDepth(451).setInteractive()
|
||||||
|
minusBtn.on('pointerover', () => minusBtn.setFillStyle(0x2a2a4e, 0.9))
|
||||||
|
minusBtn.on('pointerout', () => minusBtn.setFillStyle(0x1a1a2e, 0.9))
|
||||||
|
minusBtn.on('pointerdown', () => {
|
||||||
|
this.uiOpacity = Math.max(0.4, Math.round((this.uiOpacity - 0.1) * 10) / 10)
|
||||||
|
this.saveUISettings()
|
||||||
|
this.updateDebugPanelBackground()
|
||||||
|
this.buildSettings()
|
||||||
|
})
|
||||||
|
this.settingsGroup.add(minusBtn)
|
||||||
|
this.settingsGroup.add(
|
||||||
|
this.add.text(px + 183, py + 58, '−', {
|
||||||
|
fontSize: '15px', color: '#ffffff', fontFamily: 'monospace',
|
||||||
|
}).setOrigin(0.5, 0.5).setScrollFactor(0).setDepth(452)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Value display
|
||||||
|
this.settingsGroup.add(
|
||||||
|
this.add.text(px + 215, py + 58, `${Math.round(this.uiOpacity * 100)}%`, {
|
||||||
|
fontSize: '13px', color: '#aaaaaa', fontFamily: 'monospace',
|
||||||
|
}).setOrigin(0.5, 0.5).setScrollFactor(0).setDepth(451)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Plus button
|
||||||
|
const plusBtn = this.add.rectangle(px + 242, py + 47, 26, 22, 0x1a1a2e, 0.9)
|
||||||
|
.setOrigin(0, 0).setScrollFactor(0).setDepth(451).setInteractive()
|
||||||
|
plusBtn.on('pointerover', () => plusBtn.setFillStyle(0x2a2a4e, 0.9))
|
||||||
|
plusBtn.on('pointerout', () => plusBtn.setFillStyle(0x1a1a2e, 0.9))
|
||||||
|
plusBtn.on('pointerdown', () => {
|
||||||
|
this.uiOpacity = Math.min(1.0, Math.round((this.uiOpacity + 0.1) * 10) / 10)
|
||||||
|
this.saveUISettings()
|
||||||
|
this.updateDebugPanelBackground()
|
||||||
|
this.buildSettings()
|
||||||
|
})
|
||||||
|
this.settingsGroup.add(plusBtn)
|
||||||
|
this.settingsGroup.add(
|
||||||
|
this.add.text(px + 255, py + 58, '+', {
|
||||||
|
fontSize: '15px', color: '#ffffff', fontFamily: 'monospace',
|
||||||
|
}).setOrigin(0.5, 0.5).setScrollFactor(0).setDepth(452)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Close button
|
||||||
|
const closeBtnRect = this.add.rectangle(px + panelW / 2 - 50, py + 92, 100, 28, 0x1a1a2e, 0.9)
|
||||||
|
.setOrigin(0, 0).setScrollFactor(0).setDepth(451).setInteractive()
|
||||||
|
closeBtnRect.on('pointerover', () => closeBtnRect.setFillStyle(0x2a2a4e, 0.9))
|
||||||
|
closeBtnRect.on('pointerout', () => closeBtnRect.setFillStyle(0x1a1a2e, 0.9))
|
||||||
|
closeBtnRect.on('pointerdown', () => this.closeSettings())
|
||||||
|
this.settingsGroup.add(closeBtnRect)
|
||||||
|
this.settingsGroup.add(
|
||||||
|
this.add.text(px + panelW / 2, py + 106, 'Close', {
|
||||||
|
fontSize: '13px', color: '#dddddd', fontFamily: 'monospace',
|
||||||
|
}).setOrigin(0.5, 0.5).setScrollFactor(0).setDepth(452)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads UI settings from localStorage and applies the stored opacity value.
|
||||||
|
* Falls back to the default (0.8) if no setting is found.
|
||||||
|
*/
|
||||||
|
private loadUISettings(): void {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(UI_SETTINGS_KEY)
|
||||||
|
if (raw) {
|
||||||
|
const parsed = JSON.parse(raw) as { opacity?: number }
|
||||||
|
if (typeof parsed.opacity === 'number') {
|
||||||
|
this.uiOpacity = Math.max(0.4, Math.min(1.0, parsed.opacity))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists the current UI settings (opacity) to localStorage.
|
||||||
|
* Stored separately from the game save so New Game does not wipe it.
|
||||||
|
*/
|
||||||
|
private saveUISettings(): void {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(UI_SETTINGS_KEY, JSON.stringify({ opacity: this.uiOpacity }))
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the current uiOpacity to the debug panel text background.
|
||||||
|
* Called whenever uiOpacity changes so the debug panel stays in sync.
|
||||||
|
*/
|
||||||
|
private updateDebugPanelBackground(): void {
|
||||||
|
const hexAlpha = Math.round(this.uiOpacity * 255).toString(16).padStart(2, '0')
|
||||||
|
this.debugPanelText.setStyle({ backgroundColor: `#000000${hexAlpha}` })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Shows a confirmation dialog before starting a new game. */
|
/** Shows a confirmation dialog before starting a new game. */
|
||||||
@@ -634,7 +787,7 @@ export class UIScene extends Phaser.Scene {
|
|||||||
const dx = this.scale.width / 2 - dialogW / 2
|
const dx = this.scale.width / 2 - dialogW / 2
|
||||||
const dy = this.scale.height / 2 - dialogH / 2
|
const dy = this.scale.height / 2 - dialogH / 2
|
||||||
|
|
||||||
const bg = this.add.rectangle(dx, dy, dialogW, dialogH, 0x0a0a0a, 0.97)
|
const bg = this.add.rectangle(dx, dy, dialogW, dialogH, 0x0a0a0a, this.uiOpacity)
|
||||||
.setOrigin(0, 0).setScrollFactor(0).setDepth(500)
|
.setOrigin(0, 0).setScrollFactor(0).setDepth(500)
|
||||||
this.confirmGroup.add(bg)
|
this.confirmGroup.add(bg)
|
||||||
|
|
||||||
@@ -728,7 +881,7 @@ export class UIScene extends Phaser.Scene {
|
|||||||
|
|
||||||
// Background
|
// Background
|
||||||
this.nisseInfoGroup.add(
|
this.nisseInfoGroup.add(
|
||||||
this.add.rectangle(px, py, panelW, panelH, 0x050510, 0.93)
|
this.add.rectangle(px, py, panelW, panelH, 0x050510, this.uiOpacity)
|
||||||
.setOrigin(0, 0).setScrollFactor(0).setDepth(250)
|
.setOrigin(0, 0).setScrollFactor(0).setDepth(250)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -881,6 +1034,7 @@ export class UIScene extends Phaser.Scene {
|
|||||||
if (this.villagerPanelVisible) this.closeVillagerPanel()
|
if (this.villagerPanelVisible) this.closeVillagerPanel()
|
||||||
if (this.contextMenuVisible) this.hideContextMenu()
|
if (this.contextMenuVisible) this.hideContextMenu()
|
||||||
if (this.escMenuVisible) this.closeEscMenu()
|
if (this.escMenuVisible) this.closeEscMenu()
|
||||||
|
if (this.settingsVisible) this.closeSettings()
|
||||||
if (this.confirmVisible) this.hideConfirm()
|
if (this.confirmVisible) this.hideConfirm()
|
||||||
if (this.nisseInfoVisible) this.closeNisseInfoPanel()
|
if (this.nisseInfoVisible) this.closeNisseInfoPanel()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user