From 8ed67313a80cb60cf7130f5ec21de6fe99b7b50c Mon Sep 17 00:00:00 2001 From: tekki mariani Date: Fri, 20 Mar 2026 12:19:57 +0000 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=90=9B=20fix=20UI=20repositioning=20a?= =?UTF-8?q?nd=20mouse=20coords=20after=20window=20resize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 5 +++++ src/scenes/UIScene.ts | 33 +++++++++++++++++++++++++++++---- src/systems/BuildingSystem.ts | 10 +++++----- src/systems/FarmingSystem.ts | 5 ++--- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 276024b..b831616 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Fixed +- UI elements (stockpile panel, controls hint) now reposition correctly after window resize +- Centered overlay panels (build menu, villager panel) close on resize so they reopen at the correct position +- Mouse world coordinates now use `ptr.worldX`/`ptr.worldY` in BuildingSystem and FarmingSystem, fixing misalignment after resize or zoom + ### Added - Right-click context menu: suppresses browser default, shows Build and Folks actions in the game world - Initial project setup: Phaser 3 + TypeScript + Vite diff --git a/src/scenes/UIScene.ts b/src/scenes/UIScene.ts index b34f428..509dd9f 100644 --- a/src/scenes/UIScene.ts +++ b/src/scenes/UIScene.ts @@ -21,7 +21,9 @@ export class UIScene extends Phaser.Scene { private buildModeText!: Phaser.GameObjects.Text private farmToolText!: Phaser.GameObjects.Text private coordsText!: Phaser.GameObjects.Text + private controlsHintText!: Phaser.GameObjects.Text private popText!: Phaser.GameObjects.Text + private stockpileTitleText!: Phaser.GameObjects.Text private contextMenuGroup!: Phaser.GameObjects.Group private contextMenuVisible = false private inBuildMode = false @@ -80,7 +82,7 @@ export class UIScene extends Phaser.Scene { private createStockpilePanel(): void { const x = this.scale.width - 178, y = 10 this.stockpilePanel = this.add.rectangle(x, y, 168, 165, 0x000000, 0.72).setOrigin(0, 0).setScrollFactor(0).setDepth(100) - 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','wheat','carrot'] as const 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) @@ -286,7 +288,7 @@ export class UIScene extends Phaser.Scene { private createCoordsDisplay(): void { this.coordsText = this.add.text(10, this.scale.height - 24, '', { fontSize: '11px', color: '#666666', fontFamily: 'monospace' }).setScrollFactor(0).setDepth(100) - this.add.text(10, this.scale.height - 42, '[WASD] Pan [Scroll] Zoom [F] Farm [B] Build [V] Villagers', { + this.controlsHintText = this.add.text(10, this.scale.height - 42, '[WASD] Pan [Scroll] Zoom [F] Farm [B] Build [V] Villagers', { fontSize: '10px', color: '#444444', fontFamily: 'monospace', backgroundColor: '#00000066', padding: { x: 4, y: 2 } }).setScrollFactor(0).setDepth(100) } @@ -359,10 +361,33 @@ export class UIScene extends Phaser.Scene { // ─── Resize ─────────────────────────────────────────────────────────────── + /** + * Repositions all fixed UI elements after a canvas resize. + * Open overlay panels are closed so they reopen correctly centered. + */ private repositionUI(): void { const { width, height } = this.scale - this.hintText.setPosition(width/2, height - 40) - this.toastText.setPosition(width/2, 60) + + // Stockpile panel — anchored to top-right; move all elements by the delta + const newPanelX = width - 178 + const deltaX = newPanelX - this.stockpilePanel.x + if (deltaX !== 0) { + this.stockpilePanel.setX(newPanelX) + this.stockpileTitleText.setX(this.stockpileTitleText.x + deltaX) + this.stockpileTexts.forEach(t => t.setX(t.x + deltaX)) + this.popText.setX(this.popText.x + deltaX) + } + + // Bottom elements + this.hintText.setPosition(width / 2, height - 40) + this.toastText.setPosition(width / 2, 60) this.coordsText.setPosition(10, height - 24) + this.controlsHintText.setPosition(10, height - 42) + + // 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() + if (this.villagerPanelVisible) this.closeVillagerPanel() + if (this.contextMenuVisible) this.hideContextMenu() } } diff --git a/src/systems/BuildingSystem.ts b/src/systems/BuildingSystem.ts index df1b25e..3e52d8a 100644 --- a/src/systems/BuildingSystem.ts +++ b/src/systems/BuildingSystem.ts @@ -92,9 +92,9 @@ export class BuildingSystem { if (!this.active) return // Update ghost to follow mouse (snapped to tile grid) - const ptr = this.scene.input.activePointer - const worldX = this.scene.cameras.main.scrollX + ptr.x - const worldY = this.scene.cameras.main.scrollY + ptr.y + const ptr = this.scene.input.activePointer + const worldX = ptr.worldX + const worldY = ptr.worldY const tileX = Math.floor(worldX / TILE_SIZE) const tileY = Math.floor(worldY / TILE_SIZE) const snapX = tileX * TILE_SIZE + TILE_SIZE / 2 @@ -142,8 +142,8 @@ export class BuildingSystem { } private tryPlace(ptr: Phaser.Input.Pointer): void { - const worldX = this.scene.cameras.main.scrollX + ptr.x - const worldY = this.scene.cameras.main.scrollY + ptr.y + const worldX = ptr.worldX + const worldY = ptr.worldY const tileX = Math.floor(worldX / TILE_SIZE) const tileY = Math.floor(worldY / TILE_SIZE) diff --git a/src/systems/FarmingSystem.ts b/src/systems/FarmingSystem.ts index 79ece7b..ea30a1a 100644 --- a/src/systems/FarmingSystem.ts +++ b/src/systems/FarmingSystem.ts @@ -80,9 +80,8 @@ export class FarmingSystem { // ─── Tool actions ───────────────────────────────────────────────────────── private useToolAt(ptr: Phaser.Input.Pointer): void { - const cam = this.scene.cameras.main - const worldX = cam.scrollX + ptr.x - const worldY = cam.scrollY + ptr.y + const worldX = ptr.worldX + const worldY = ptr.worldY const tileX = Math.floor(worldX / TILE_SIZE) const tileY = Math.floor(worldY / TILE_SIZE) const state = stateManager.getState() From 787ada7cb459483ac997b57b57bee7ce5a921647 Mon Sep 17 00:00:00 2001 From: tekki mariani Date: Fri, 20 Mar 2026 17:07:34 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=90=9B=20fix=20Nisse=20stuck=20idle?= =?UTF-8?q?=20after=20stockpile=20deposit;=20rename=20Villager=20=E2=86=92?= =?UTF-8?q?=20Nisse=20in=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 8 +++++++- src/StateManager.ts | 4 ++-- src/scenes/UIScene.ts | 12 ++++++------ src/systems/VillagerSystem.ts | 11 +++++++++-- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b831616..b59d2f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,18 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Fixed +- Nisse no longer get stuck idle after depositing items at the stockpile +- Working Nisse now reset to idle on game load (like walking ones), preventing stale AI state +- Stale jobs with empty carry are now cleared after work completes, avoiding a false "haul to stockpile" loop - UI elements (stockpile panel, controls hint) now reposition correctly after window resize - Centered overlay panels (build menu, villager panel) close on resize so they reopen at the correct position - Mouse world coordinates now use `ptr.worldX`/`ptr.worldY` in BuildingSystem and FarmingSystem, fixing misalignment after resize or zoom +### Changed +- Villagers are now called **Nisse** throughout the UI (panel, controls hint, stockpile display, context menu, spawn message) + ### Added -- Right-click context menu: suppresses browser default, shows Build and Folks actions in the game world +- Right-click context menu: suppresses browser default, shows Build and Nisse actions in the game world - Initial project setup: Phaser 3 + TypeScript + Vite - Core scenes: `BootScene`, `GameScene`, `UIScene` - Systems: `BuildingSystem`, `CameraSystem`, `FarmingSystem`, `PlayerSystem`, diff --git a/src/StateManager.ts b/src/StateManager.ts index 35211c3..f1fc7e6 100644 --- a/src/StateManager.ts +++ b/src/StateManager.ts @@ -176,9 +176,9 @@ class StateManager { if (!p.world.crops) p.world.crops = {} if (!p.world.villagers) p.world.villagers = {} if (!p.world.stockpile) p.world.stockpile = {} - // Reset walking villagers to idle on load + // Reset in-flight AI states to idle on load so runtime timers start fresh for (const v of Object.values(p.world.villagers)) { - if (v.aiState === 'walking') v.aiState = 'idle' + if (v.aiState === 'walking' || v.aiState === 'working') v.aiState = 'idle' } return p } catch (_) { return null } diff --git a/src/scenes/UIScene.ts b/src/scenes/UIScene.ts index 509dd9f..fa94dd0 100644 --- a/src/scenes/UIScene.ts +++ b/src/scenes/UIScene.ts @@ -88,7 +88,7 @@ export class UIScene extends Phaser.Scene { 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.popText = this.add.text(x + 10, y + 145, '👥 Pop: 0 / 0', { fontSize: '11px', color: '#aaaaaa', fontFamily: 'monospace' }).setScrollFactor(0).setDepth(101) + this.popText = this.add.text(x + 10, y + 145, '👥 Nisse: 0 / 0', { fontSize: '11px', color: '#aaaaaa', fontFamily: 'monospace' }).setScrollFactor(0).setDepth(101) } private updateStockpile(): void { @@ -104,7 +104,7 @@ export class UIScene extends Phaser.Scene { const state = stateManager.getState() const beds = Object.values(state.world.buildings).filter(b => b.kind === 'bed').length const current = Object.keys(state.world.villagers).length - this.popText?.setText(`👥 Pop: ${current} / ${beds} [V] manage`) + this.popText?.setText(`👥 Nisse: ${current} / ${beds} [V]`) } // ─── Hint ───────────────────────────────────────────────────────────────── @@ -204,13 +204,13 @@ export class UIScene extends Phaser.Scene { this.villagerPanelGroup.add(bg) this.villagerPanelGroup.add( - this.add.text(px + panelW/2, py + 12, '👥 VILLAGERS [V] close', { fontSize: '12px', color: '#aaaaaa', fontFamily: 'monospace' }) + this.add.text(px + panelW/2, py + 12, '👥 NISSE [V] close', { fontSize: '12px', color: '#aaaaaa', fontFamily: 'monospace' }) .setOrigin(0.5, 0).setScrollFactor(0).setDepth(211) ) if (villagers.length === 0) { this.villagerPanelGroup.add( - this.add.text(px + panelW/2, py + panelH/2, 'No villagers yet.\nBuild a 🛏 Bed first!', { + this.add.text(px + panelW/2, py + panelH/2, 'No Nisse yet.\nBuild a 🛏 Bed first!', { fontSize: '13px', color: '#666666', fontFamily: 'monospace', align: 'center' }).setOrigin(0.5).setScrollFactor(0).setDepth(211) ) @@ -288,7 +288,7 @@ export class UIScene extends Phaser.Scene { private createCoordsDisplay(): void { this.coordsText = this.add.text(10, this.scale.height - 24, '', { fontSize: '11px', color: '#666666', fontFamily: 'monospace' }).setScrollFactor(0).setDepth(100) - this.controlsHintText = this.add.text(10, this.scale.height - 42, '[WASD] Pan [Scroll] Zoom [F] Farm [B] Build [V] Villagers', { + this.controlsHintText = this.add.text(10, this.scale.height - 42, '[WASD] Pan [Scroll] Zoom [F] Farm [B] Build [V] Nisse', { fontSize: '10px', color: '#444444', fontFamily: 'monospace', backgroundColor: '#00000066', padding: { x: 4, y: 2 } }).setScrollFactor(0).setDepth(100) } @@ -324,7 +324,7 @@ export class UIScene extends Phaser.Scene { action: () => { this.hideContextMenu(); this.scene.get('Game').events.emit('uiRequestBuildMenu') }, }, { - label: '👥 Folks', + label: '👥 Nisse', action: () => { this.hideContextMenu(); this.toggleVillagerPanel() }, }, ] diff --git a/src/systems/VillagerSystem.ts b/src/systems/VillagerSystem.ts index 4c3ebfd..f6eb302 100644 --- a/src/systems/VillagerSystem.ts +++ b/src/systems/VillagerSystem.ts @@ -170,6 +170,7 @@ export class VillagerSystem { case 'stockpile': this.adapter.send({ type: 'VILLAGER_DEPOSIT', villagerId: v.id }) this.adapter.send({ type: 'VILLAGER_SET_AI', villagerId: v.id, aiState: 'idle' }) + rt.idleScanTimer = 0 // scan for a new job immediately after deposit break case 'bed': @@ -217,7 +218,13 @@ export class VillagerSystem { } } - // Back to idle so decideAction handles depositing + // If the harvest produced nothing (resource already gone), clear the stale job + // so tickIdle does not try to walk to a stockpile with nothing to deposit. + if (!v.job?.carrying || !Object.values(v.job.carrying).some(n => (n ?? 0) > 0)) { + this.adapter.send({ type: 'VILLAGER_SET_JOB', villagerId: v.id, job: null }) + } + + // Back to idle — tickIdle will handle hauling to stockpile if carrying items this.adapter.send({ type: 'VILLAGER_SET_AI', villagerId: v.id, aiState: 'idle' }) } @@ -342,7 +349,7 @@ export class VillagerSystem { this.adapter.send({ type: 'SPAWN_VILLAGER', villager }) this.spawnSprite(villager) - this.onMessage?.(`${name} has joined the settlement! 🏘`) + this.onMessage?.(`${name} the Nisse has arrived! 🏘`) } // ─── Sprite management ──────────────────────────────────────────────────── From c9c8e45b0c410a370f35d39579691b856edb8a36 Mon Sep 17 00:00:00 2001 From: tekki mariani Date: Fri, 20 Mar 2026 17:39:25 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=90=9B=20clear=20FOREST/ROCK=20tile?= =?UTF-8?q?=20after=20harvest=20so=20Nisse=20can=20access=20deeper=20resou?= =?UTF-8?q?rces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/systems/VillagerSystem.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/systems/VillagerSystem.ts b/src/systems/VillagerSystem.ts index f6eb302..68efa98 100644 --- a/src/systems/VillagerSystem.ts +++ b/src/systems/VillagerSystem.ts @@ -1,5 +1,6 @@ import Phaser from 'phaser' import { TILE_SIZE, VILLAGER_SPEED, VILLAGER_SPAWN_INTERVAL, VILLAGER_WORK_TIMES, VILLAGER_NAMES } from '../config' +import { TileType } from '../types' import type { VillagerState, VillagerJob, JobType, AIState, ItemId } from '../types' import { stateManager } from '../StateManager' import { findPath } from '../utils/pathfinding' @@ -200,13 +201,19 @@ export class VillagerSystem { const state = stateManager.getState() if (job.type === 'chop') { - if (state.world.resources[job.targetId]) { + const res = state.world.resources[job.targetId] + if (res) { this.adapter.send({ type: 'VILLAGER_HARVEST_RESOURCE', villagerId: v.id, resourceId: job.targetId }) + // Clear the FOREST tile so the area becomes passable for future pathfinding + this.adapter.send({ type: 'CHANGE_TILE', tileX: res.tileX, tileY: res.tileY, tile: TileType.DARK_GRASS }) this.resourceSystem.removeResource(job.targetId) } } else if (job.type === 'mine') { - if (state.world.resources[job.targetId]) { + const res = state.world.resources[job.targetId] + if (res) { this.adapter.send({ type: 'VILLAGER_HARVEST_RESOURCE', villagerId: v.id, resourceId: job.targetId }) + // Clear the ROCK tile so the area becomes passable for future pathfinding + this.adapter.send({ type: 'CHANGE_TILE', tileX: res.tileX, tileY: res.tileY, tile: TileType.GRASS }) this.resourceSystem.removeResource(job.targetId) } } else if (job.type === 'farm') { From 6385872dd1975f6138cd29b131e95df8380c06f3 Mon Sep 17 00:00:00 2001 From: tekki mariani Date: Fri, 20 Mar 2026 18:54:58 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=93=9D=20update=20changelog=20with=20?= =?UTF-8?q?tile-clearing=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b59d2f2..b1eee5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Fixed +- Nisse now clear the FOREST/ROCK tile after harvesting, opening paths to deeper resources - Nisse no longer get stuck idle after depositing items at the stockpile - Working Nisse now reset to idle on game load (like walking ones), preventing stale AI state - Stale jobs with empty carry are now cleared after work completes, avoiding a false "haul to stockpile" loop