🐛 fix Nisse stuck idle after stockpile deposit; rename Villager → Nisse in UI
This commit is contained in:
@@ -8,12 +8,18 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Fixed
|
### 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
|
- 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
|
- 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
|
- 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
|
### 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
|
- Initial project setup: Phaser 3 + TypeScript + Vite
|
||||||
- Core scenes: `BootScene`, `GameScene`, `UIScene`
|
- Core scenes: `BootScene`, `GameScene`, `UIScene`
|
||||||
- Systems: `BuildingSystem`, `CameraSystem`, `FarmingSystem`, `PlayerSystem`,
|
- Systems: `BuildingSystem`, `CameraSystem`, `FarmingSystem`, `PlayerSystem`,
|
||||||
|
|||||||
@@ -176,9 +176,9 @@ class StateManager {
|
|||||||
if (!p.world.crops) p.world.crops = {}
|
if (!p.world.crops) p.world.crops = {}
|
||||||
if (!p.world.villagers) p.world.villagers = {}
|
if (!p.world.villagers) p.world.villagers = {}
|
||||||
if (!p.world.stockpile) p.world.stockpile = {}
|
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)) {
|
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
|
return p
|
||||||
} catch (_) { return null }
|
} catch (_) { return null }
|
||||||
|
|||||||
@@ -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)
|
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)
|
||||||
})
|
})
|
||||||
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 {
|
private updateStockpile(): void {
|
||||||
@@ -104,7 +104,7 @@ export class UIScene extends Phaser.Scene {
|
|||||||
const state = stateManager.getState()
|
const state = stateManager.getState()
|
||||||
const beds = Object.values(state.world.buildings).filter(b => b.kind === 'bed').length
|
const beds = Object.values(state.world.buildings).filter(b => b.kind === 'bed').length
|
||||||
const current = Object.keys(state.world.villagers).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 ─────────────────────────────────────────────────────────────────
|
// ─── Hint ─────────────────────────────────────────────────────────────────
|
||||||
@@ -204,13 +204,13 @@ export class UIScene extends Phaser.Scene {
|
|||||||
this.villagerPanelGroup.add(bg)
|
this.villagerPanelGroup.add(bg)
|
||||||
|
|
||||||
this.villagerPanelGroup.add(
|
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)
|
.setOrigin(0.5, 0).setScrollFactor(0).setDepth(211)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (villagers.length === 0) {
|
if (villagers.length === 0) {
|
||||||
this.villagerPanelGroup.add(
|
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'
|
fontSize: '13px', color: '#666666', fontFamily: 'monospace', align: 'center'
|
||||||
}).setOrigin(0.5).setScrollFactor(0).setDepth(211)
|
}).setOrigin(0.5).setScrollFactor(0).setDepth(211)
|
||||||
)
|
)
|
||||||
@@ -288,7 +288,7 @@ export class UIScene extends Phaser.Scene {
|
|||||||
|
|
||||||
private createCoordsDisplay(): void {
|
private createCoordsDisplay(): void {
|
||||||
this.coordsText = this.add.text(10, this.scale.height - 24, '', { fontSize: '11px', color: '#666666', fontFamily: 'monospace' }).setScrollFactor(0).setDepth(100)
|
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 }
|
fontSize: '10px', color: '#444444', fontFamily: 'monospace', backgroundColor: '#00000066', padding: { x: 4, y: 2 }
|
||||||
}).setScrollFactor(0).setDepth(100)
|
}).setScrollFactor(0).setDepth(100)
|
||||||
}
|
}
|
||||||
@@ -324,7 +324,7 @@ export class UIScene extends Phaser.Scene {
|
|||||||
action: () => { this.hideContextMenu(); this.scene.get('Game').events.emit('uiRequestBuildMenu') },
|
action: () => { this.hideContextMenu(); this.scene.get('Game').events.emit('uiRequestBuildMenu') },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '👥 Folks',
|
label: '👥 Nisse',
|
||||||
action: () => { this.hideContextMenu(); this.toggleVillagerPanel() },
|
action: () => { this.hideContextMenu(); this.toggleVillagerPanel() },
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ export class VillagerSystem {
|
|||||||
case 'stockpile':
|
case 'stockpile':
|
||||||
this.adapter.send({ type: 'VILLAGER_DEPOSIT', villagerId: v.id })
|
this.adapter.send({ type: 'VILLAGER_DEPOSIT', villagerId: v.id })
|
||||||
this.adapter.send({ type: 'VILLAGER_SET_AI', villagerId: v.id, aiState: 'idle' })
|
this.adapter.send({ type: 'VILLAGER_SET_AI', villagerId: v.id, aiState: 'idle' })
|
||||||
|
rt.idleScanTimer = 0 // scan for a new job immediately after deposit
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'bed':
|
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' })
|
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.adapter.send({ type: 'SPAWN_VILLAGER', villager })
|
||||||
this.spawnSprite(villager)
|
this.spawnSprite(villager)
|
||||||
this.onMessage?.(`${name} has joined the settlement! 🏘`)
|
this.onMessage?.(`${name} the Nisse has arrived! 🏘`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Sprite management ────────────────────────────────────────────────────
|
// ─── Sprite management ────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user