diff --git a/src/scenes/UIScene.ts b/src/scenes/UIScene.ts index c029523..c03db28 100644 --- a/src/scenes/UIScene.ts +++ b/src/scenes/UIScene.ts @@ -59,6 +59,18 @@ export class UIScene extends Phaser.Scene { /** True while the zone-edit tool is active (shown in ESC priority stack). */ private inForesterZoneEdit = false + // ── Action Bar ──────────────────────────────────────────────────────────── + private static readonly BAR_H = 48 + private static readonly TRAY_H = 68 + private actionBarBg!: Phaser.GameObjects.Rectangle + private actionBuildBtn!: Phaser.GameObjects.Rectangle + private actionBuildLabel!: Phaser.GameObjects.Text + private actionNisseBtn!: Phaser.GameObjects.Rectangle + private actionNisseLabel!: Phaser.GameObjects.Text + private actionTrayGroup!: Phaser.GameObjects.Group + private actionTrayVisible = false + private activeCategory: 'build' | 'nisse' | null = null + constructor() { super({ key: 'UI' }) } /** @@ -74,6 +86,7 @@ export class UIScene extends Phaser.Scene { this.createBuildModeIndicator() this.createFarmToolIndicator() this.createDebugPanel() + this.createActionBar() const gameScene = this.scene.get('Game') gameScene.events.on('buildModeChanged', (a: boolean, b: BuildingType) => this.onBuildModeChanged(a, b)) @@ -99,6 +112,7 @@ export class UIScene extends Phaser.Scene { this.nisseInfoGroup = this.add.group() this.settingsGroup = this.add.group() this.foresterPanelGroup = this.add.group() + this.actionTrayGroup = this.add.group() gameScene.events.on('foresterHutClicked', (id: string) => this.openForesterPanel(id)) gameScene.events.on('foresterZoneEditEnded', () => this.onForesterEditEnded()) @@ -171,7 +185,7 @@ export class UIScene extends Phaser.Scene { /** Creates the centered hint text element near the bottom of the screen. */ private createHintText(): void { - this.hintText = this.add.text(this.scale.width / 2, this.scale.height - 40, '', { + this.hintText = this.add.text(this.scale.width / 2, this.scale.height - UIScene.BAR_H - 24, '', { fontSize: '14px', color: '#ffff88', fontFamily: 'monospace', backgroundColor: '#00000099', padding: { x: 10, y: 5 }, }).setOrigin(0.5).setScrollFactor(0).setDepth(100).setVisible(false) @@ -266,6 +280,10 @@ export class UIScene extends Phaser.Scene { this.villagerPanelVisible = false this.villagerPanelGroup?.destroy(true) this.scene.get('Game').events.emit('uiMenuClose') + if (this.activeCategory === 'nisse') { + this.activeCategory = null + this.updateCategoryHighlights() + } } /** @@ -525,6 +543,7 @@ export class UIScene extends Phaser.Scene { if (this.foresterPanelVisible) { this.closeForesterPanel(); return } if (this.contextMenuVisible) { this.hideContextMenu(); return } if (this.buildMenuVisible) { this.closeBuildMenu(); return } + if (this.actionTrayVisible) { this.closeActionTray(); return } if (this.villagerPanelVisible) { this.closeVillagerPanel(); return } if (this.nisseInfoVisible) { this.closeNisseInfoPanel(); return } if (this.settingsVisible) { this.closeSettings(); return } @@ -1137,6 +1156,152 @@ export class UIScene extends Phaser.Scene { } } + // ─── Action Bar ─────────────────────────────────────────────────────────── + + /** + * Creates the persistent bottom action bar with Build and Nisse category buttons. + * The bar is always visible; individual button highlights change with the active category. + */ + private createActionBar(): void { + const { width, height } = this.scale + const barY = height - UIScene.BAR_H + + this.actionBarBg = this.add.rectangle(0, barY, width, UIScene.BAR_H, 0x080808, 0.92) + .setOrigin(0, 0).setScrollFactor(0).setDepth(300) + + this.actionBuildBtn = this.add.rectangle(8, barY + 8, 88, 32, 0x1a3a1a, 0.9) + .setOrigin(0, 0).setScrollFactor(0).setDepth(301).setInteractive() + this.actionBuildBtn.on('pointerover', () => { + if (this.activeCategory !== 'build') this.actionBuildBtn.setFillStyle(0x2a5a2a, 0.9) + }) + this.actionBuildBtn.on('pointerout', () => { + if (this.activeCategory !== 'build') this.actionBuildBtn.setFillStyle(0x1a3a1a, 0.9) + }) + this.actionBuildBtn.on('pointerdown', () => this.toggleCategory('build')) + + this.actionBuildLabel = this.add.text(52, barY + UIScene.BAR_H / 2, '🔨 Build', { + fontSize: '12px', color: '#cccccc', fontFamily: 'monospace', + }).setOrigin(0.5, 0.5).setScrollFactor(0).setDepth(302) + + this.actionNisseBtn = this.add.rectangle(104, barY + 8, 88, 32, 0x1a1a3a, 0.9) + .setOrigin(0, 0).setScrollFactor(0).setDepth(301).setInteractive() + this.actionNisseBtn.on('pointerover', () => { + if (this.activeCategory !== 'nisse') this.actionNisseBtn.setFillStyle(0x2a2a5a, 0.9) + }) + this.actionNisseBtn.on('pointerout', () => { + if (this.activeCategory !== 'nisse') this.actionNisseBtn.setFillStyle(0x1a1a3a, 0.9) + }) + this.actionNisseBtn.on('pointerdown', () => this.toggleCategory('nisse')) + + this.actionNisseLabel = this.add.text(148, barY + UIScene.BAR_H / 2, '👥 Nisse', { + fontSize: '12px', color: '#cccccc', fontFamily: 'monospace', + }).setOrigin(0.5, 0.5).setScrollFactor(0).setDepth(302) + } + + /** + * Toggles the given action bar category on or off. + * Selecting the active category deselects it; selecting a new one closes the previous. + * @param cat - The category to toggle ('build' or 'nisse') + */ + private toggleCategory(cat: 'build' | 'nisse'): void { + if (this.activeCategory === cat) { + this.deactivateCategory() + return + } + // Close whatever was open before + if (this.activeCategory === 'build') this.closeActionTray() + if (this.activeCategory === 'nisse' && this.villagerPanelVisible) this.closeVillagerPanel() + + this.activeCategory = cat + this.updateCategoryHighlights() + + if (cat === 'build') { + this.openActionTray() + } else { + this.openVillagerPanel() + } + } + + /** + * Deactivates the currently active category, closing its associated panel or tray. + */ + private deactivateCategory(): void { + if (this.activeCategory === 'build') this.closeActionTray() + if (this.activeCategory === 'nisse' && this.villagerPanelVisible) this.closeVillagerPanel() + this.activeCategory = null + this.updateCategoryHighlights() + } + + /** + * Updates the visual highlight of the Build and Nisse buttons + * to reflect the current active category. + */ + private updateCategoryHighlights(): void { + this.actionBuildBtn.setFillStyle(this.activeCategory === 'build' ? 0x3d7a3d : 0x1a3a1a, 0.9) + this.actionNisseBtn.setFillStyle(this.activeCategory === 'nisse' ? 0x3d3d7a : 0x1a1a3a, 0.9) + } + + /** + * Builds and shows the building tool tray above the action bar. + * Each building is shown as a clickable tile with emoji and name. + */ + private openActionTray(): void { + if (this.actionTrayVisible) return + this.actionTrayVisible = true + this.actionTrayGroup.destroy(true) + this.actionTrayGroup = this.add.group() + + const { width, height } = this.scale + const trayY = height - UIScene.BAR_H - UIScene.TRAY_H + + const bg = this.add.rectangle(0, trayY, width, UIScene.TRAY_H, 0x0d0d0d, 0.88) + .setOrigin(0, 0).setScrollFactor(0).setDepth(300) + this.actionTrayGroup.add(bg) + + const buildings: { kind: BuildingType; emoji: string; label: string }[] = [ + { kind: 'floor', emoji: '🪵', label: 'Floor' }, + { kind: 'wall', emoji: '🧱', label: 'Wall' }, + { kind: 'chest', emoji: '📦', label: 'Chest' }, + { kind: 'bed', emoji: '🛏', label: 'Bed' }, + { kind: 'stockpile_zone', emoji: '📦', label: 'Stockpile' }, + { kind: 'forester_hut', emoji: '🌲', label: 'Forester' }, + ] + + const itemW = 84 + buildings.forEach((b, i) => { + const bx = 8 + i * (itemW + 4) + const btn = this.add.rectangle(bx, trayY + 4, itemW, UIScene.TRAY_H - 8, 0x1a2a1a, 0.9) + .setOrigin(0, 0).setScrollFactor(0).setDepth(301).setInteractive() + btn.on('pointerover', () => btn.setFillStyle(0x2d4a2d, 0.9)) + btn.on('pointerout', () => btn.setFillStyle(0x1a2a1a, 0.9)) + btn.on('pointerdown', () => { + this.closeActionTray() + this.deactivateCategory() + this.scene.get('Game').events.emit('selectBuilding', b.kind) + }) + this.actionTrayGroup.add(btn) + this.actionTrayGroup.add( + this.add.text(bx + itemW / 2, trayY + 18, b.emoji, { fontSize: '18px' }) + .setOrigin(0.5, 0.5).setScrollFactor(0).setDepth(302) + ) + this.actionTrayGroup.add( + this.add.text(bx + itemW / 2, trayY + 44, b.label, { + fontSize: '10px', color: '#cccccc', fontFamily: 'monospace', + }).setOrigin(0.5, 0.5).setScrollFactor(0).setDepth(302) + ) + }) + } + + /** + * Hides and destroys the building tool tray. + */ + private closeActionTray(): void { + if (!this.actionTrayVisible) return + this.actionTrayVisible = false + this.actionTrayGroup.destroy(true) + this.actionTrayGroup = this.add.group() + } + // ─── Resize ─────────────────────────────────────────────────────────────── /** @@ -1157,8 +1322,16 @@ export class UIScene extends Phaser.Scene { } // Bottom elements - this.hintText.setPosition(width / 2, height - 40) + this.hintText.setPosition(width / 2, height - UIScene.BAR_H - 24) this.toastText.setPosition(width / 2, 60) + + // Action bar — reposition persistent elements + this.actionBarBg.setPosition(0, height - UIScene.BAR_H).setSize(width, UIScene.BAR_H) + this.actionBuildBtn.setPosition(8, height - UIScene.BAR_H + 8) + this.actionBuildLabel.setPosition(48, height - UIScene.BAR_H + UIScene.BAR_H / 2) + this.actionNisseBtn.setPosition(104, height - UIScene.BAR_H + 8) + this.actionNisseLabel.setPosition(144, height - UIScene.BAR_H + UIScene.BAR_H / 2) + if (this.actionTrayVisible) this.closeActionTray() // Close centered panels — their position is calculated on open, so they // would be off-center if left open during a resize if (this.buildMenuVisible) this.closeBuildMenu()