@@ -3,7 +3,6 @@ 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 : '🥕' ,
@@ -47,11 +46,6 @@ 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' } ) }
/**
/**
@@ -59,7 +53,6 @@ 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 ( )
@@ -92,7 +85,6 @@ 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 ( ) ) {
@@ -127,16 +119,14 @@ export class UIScene extends Phaser.Scene {
/** Creates the stockpile panel in the top-right corner with item rows and population count. */
/** Creates the stockpile panel in the top-right corner with item rows and population count. */
private createStockpilePanel ( ) : void {
private createStockpilePanel ( ) : void {
const x = this . scale . width - 178 , y = 10
const x = this . scale . width - 178 , y = 10
// 7 items × 22px + 26px header + 12px gap + 18px popText row + 10px bottom = 210px
this . stockpilePanel = this . add . rectangle ( x , y , 168 , 187 , 0x000000 , 0.72 ) . setOrigin ( 0 , 0 ) . setScrollFactor ( 0 ) . setDepth ( 100 )
this . stockpilePanel = this . add . rectangle ( x , y , 168 , 210 , 0x000000 , this . uiOpacity ) . setOrigin ( 0 , 0 ) . setScrollFactor ( 0 ) . setDepth ( 100 )
this . stockpileTitleText = this . add . text ( x + 10 , y + 7 , '⚡ STOCKPILE' , { fontSize : '11px' , color : '#aaaaaa' , fontFamily : 'monospace' } ) . setScrollFactor ( 0 ) . setDepth ( 101 )
this . stockpileTitleText = this . add . text ( x + 10 , y + 7 , '⚡ STOCKPILE' , { fontSize : '11px' , color : '#aaaaaa' , fontFamily : 'monospace' } ) . setScrollFactor ( 0 ) . setDepth ( 101 )
const items = [ 'wood' , 'stone' , 'wheat_seed' , 'carrot_seed' , 'tree_seed' , 'wheat' , 'carrot' ] as const
const items = [ 'wood' , 'stone' , 'wheat_seed' , 'carrot_seed' , 'tree_seed' , 'wheat' , 'carrot' ] as const
items . forEach ( ( item , i ) = > {
items . forEach ( ( item , i ) = > {
const t = this . add . text ( x + 10 , y + 26 + i * 22 , ` ${ ITEM_ICONS [ item ] } ${ item } : 0 ` , { fontSize : '13px' , color : '#88dd88' , fontFamily : 'monospace' } ) . setScrollFactor ( 0 ) . setDepth ( 101 )
const t = this . add . text ( x + 10 , y + 26 + i * 22 , ` ${ ITEM_ICONS [ item ] } ${ item } : 0 ` , { fontSize : '13px' , color : '#88dd88' , fontFamily : 'monospace' } ) . setScrollFactor ( 0 ) . setDepth ( 101 )
this . stockpileTexts . set ( item , t )
this . stockpileTexts . set ( item , t )
} )
} )
// last item (i=6) bottom edge ≈ y+190 → popText starts at y+192 with 8px gap
this . popText = this . add . text ( x + 10 , y + 167 , '👥 Nisse: 0 / 0' , { fontSize : '11px' , color : '#aaaaaa' , fontFamily : 'monospace' } ) . setScrollFactor ( 0 ) . setDepth ( 101 )
this . popText = this . add . text ( x + 10 , y + 192 , '👥 Nisse: 0 / 0' , { fontSize : '11px' , color : '#aaaaaa' , fontFamily : 'monospace' } ) . setScrollFactor ( 0 ) . setDepth ( 101 )
}
}
/** Refreshes all item quantities and colors in the stockpile panel. */
/** Refreshes all item quantities and colors in the stockpile panel. */
@@ -206,7 +196,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 , this . uiOpacity ) . setOrigin ( 0 , 0 ) . setScrollFactor ( 0 ) . setDepth ( 200 )
const bg = this . add . rectangle ( menuX , menuY , 300 , 280 , 0x000000 , 0.88 ) . 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 ) )
@@ -273,7 +263,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 , this . uiOpacity ) . setOrigin ( 0 , 0 ) . setScrollFactor ( 0 ) . setDepth ( 210 )
const bg = this . add . rectangle ( px , py , panelW , panelH , 0x0a0a0a , 0.92 ) . setOrigin ( 0 , 0 ) . setScrollFactor ( 0 ) . setDepth ( 210 )
this . villagerPanelGroup . add ( bg )
this . villagerPanelGroup . add ( bg )
this . villagerPanelGroup . add (
this . villagerPanelGroup . add (
@@ -393,11 +383,10 @@ 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 : ` #000000${ hexAlpha } ` ,
backgroundColor : ' #000000cc' ,
padding : { x : 8 , y : 6 } ,
padding : { x : 8 , y : 6 } ,
lineSpacing : 2 ,
lineSpacing : 2 ,
fontFamily : 'monospace' ,
fontFamily : 'monospace' ,
@@ -474,7 +463,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 , this . uiOpacity )
const bg = this . add . rectangle ( mx , my , menuW , menuH , 0x000000 , 0.88 )
. setOrigin ( 0 , 0 ) . setScrollFactor ( 0 ) . setDepth ( 300 )
. setOrigin ( 0 , 0 ) . setScrollFactor ( 0 ) . setDepth ( 300 )
this . contextMenuGroup . add ( bg )
this . contextMenuGroup . add ( bg )
@@ -532,7 +521,6 @@ 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.
@@ -572,12 +560,11 @@ export class UIScene extends Phaser.Scene {
{ label : '⚙️ Settings' , action : ( ) = > this . doSettings ( ) } ,
{ label : '⚙️ Settings' , action : ( ) = > this . doSettings ( ) } ,
{ label : '🆕 New Game' , action : ( ) = > this . doNewGame ( ) } ,
{ label : '🆕 New Game' , action : ( ) = > this . doNewGame ( ) } ,
]
]
// 32px header + entries × (btnH + 8px gap) + 8px bottom padding
const menuH = 16 + entries . length * ( btnH + 8 ) + 8
const menuH = 32 + entries . length * ( btnH + 8 ) + 8
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 , this . uiOpacity )
const bg = this . add . rectangle ( mx , my , menuW , menuH , 0x0a0a0a , 0.95 )
. 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 (
@@ -615,155 +602,10 @@ export class UIScene extends Phaser.Scene {
window . location . reload ( )
window . location . reload ( )
}
}
/** Opens the Settings overlay . */
/** Opens an empty Settings panel (placeholder) . */
private doSettings ( ) : void {
private doSettings ( ) : void {
this . closeEscMenu ( )
this . closeEscMenu ( )
this . openSettings ( )
this . showToast ( 'Settings — coming soon' )
}
// ─── 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 . updateStaticPanelOpacity ( )
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 . updateStaticPanelOpacity ( )
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 all static UI elements that are not
* rebuilt on open (stockpile panel, debug panel background).
* Called whenever uiOpacity changes.
*/
private updateStaticPanelOpacity ( ) : void {
this . stockpilePanel . setAlpha ( this . uiOpacity )
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. */
@@ -792,7 +634,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 , this . uiOpacity )
const bg = this . add . rectangle ( dx , dy , dialogW , dialogH , 0x0a0a0a , 0.97 )
. setOrigin ( 0 , 0 ) . setScrollFactor ( 0 ) . setDepth ( 500 )
. setOrigin ( 0 , 0 ) . setScrollFactor ( 0 ) . setDepth ( 500 )
this . confirmGroup . add ( bg )
this . confirmGroup . add ( bg )
@@ -850,6 +692,7 @@ export class UIScene extends Phaser.Scene {
private openNisseInfoPanel ( villagerId : string ) : void {
private openNisseInfoPanel ( villagerId : string ) : void {
this . nisseInfoId = villagerId
this . nisseInfoId = villagerId
this . nisseInfoVisible = true
this . nisseInfoVisible = true
this . scene . get ( 'Game' ) . events . emit ( 'uiMenuOpen' )
this . buildNisseInfoPanel ( )
this . buildNisseInfoPanel ( )
}
}
@@ -860,6 +703,7 @@ export class UIScene extends Phaser.Scene {
this . nisseInfoId = null
this . nisseInfoId = null
this . nisseInfoGroup . destroy ( true )
this . nisseInfoGroup . destroy ( true )
this . nisseInfoGroup = this . add . group ( )
this . nisseInfoGroup = this . add . group ( )
this . scene . get ( 'Game' ) . events . emit ( 'uiMenuClose' )
}
}
/**
/**
@@ -886,7 +730,7 @@ export class UIScene extends Phaser.Scene {
// Background
// Background
this . nisseInfoGroup . add (
this . nisseInfoGroup . add (
this . add . rectangle ( px , py , panelW , panelH , 0x050510 , this . uiOpacity )
this . add . rectangle ( px , py , panelW , panelH , 0x050510 , 0.93 )
. setOrigin ( 0 , 0 ) . setScrollFactor ( 0 ) . setDepth ( 250 )
. setOrigin ( 0 , 0 ) . setScrollFactor ( 0 ) . setDepth ( 250 )
)
)
@@ -1039,7 +883,6 @@ 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 ( )
}
}