Compare commits
14 Commits
16852c42e7
...
feature/es
| Author | SHA1 | Date | |
|---|---|---|---|
| 41097b4765 | |||
| 0c636ed5ec | |||
| 4c41dc9205 | |||
| 01e57df6a6 | |||
| 1feeff215d | |||
| 1ba38cc23e | |||
| 793ab430e4 | |||
| 6f0d8a866f | |||
| 71aee058b5 | |||
| 3fdf621966 | |||
| 7f0ef0554e | |||
| d83b97a447 | |||
| a93e8a2c5d | |||
| 7c130763b5 |
@@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"permissions": {
|
|
||||||
"allow": [
|
|
||||||
"Bash(curl:)",
|
|
||||||
"Bash(curl -s \"https://git.zally.dev/api/v1/repos/tekki/nissefolk/issues/1\" -H \"Authorization: token de54ccf9eadd5950a6ea5fa264b6404acdecc732\")",
|
|
||||||
"Bash(python3 -m json.tool)",
|
|
||||||
"Bash(curl -s \"https://git.zally.dev/api/v1/repos/tekki/nissefolk/issues/1/timeline\" -H \"Authorization: token de54ccf9eadd5950a6ea5fa264b6404acdecc732\")",
|
|
||||||
"Bash(curl:*)",
|
|
||||||
"Bash(python3 -c \":*)",
|
|
||||||
"Bash(git checkout:*)",
|
|
||||||
"Bash(npx tsc:*)",
|
|
||||||
"Bash(npm run:*)",
|
|
||||||
"Bash(/usr/local/bin/npm run:*)",
|
|
||||||
"Bash(/home/tekki/.nvm/versions/node/v24.14.0/bin/npm run:*)",
|
|
||||||
"Bash(export PATH=\"/home/tekki/.nvm/versions/node/v24.14.0/bin:$PATH\")",
|
|
||||||
"Bash(git add:*)",
|
|
||||||
"Bash(git commit:*)",
|
|
||||||
"Bash(git push:*)",
|
|
||||||
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\(''''html_url'''', d.get\\(''''message'''',''''''''\\)\\)\\)\")",
|
|
||||||
"Bash(git pull:*)",
|
|
||||||
"Bash(for id:*)",
|
|
||||||
"Bash(do echo:*)",
|
|
||||||
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\(''''html_url'''',''''''''\\)\\)\")",
|
|
||||||
"Bash(TOKEN=\"de54ccf9eadd5950a6ea5fa264b6404acdecc732\" BASE=\"https://git.zally.dev/api/v1/repos/tekki/nissefolk\" __NEW_LINE_2bc8ebfb809e4939__ for id in 5 6 7 9)",
|
|
||||||
"Bash(TOKEN=\"de54ccf9eadd5950a6ea5fa264b6404acdecc732\")",
|
|
||||||
"Bash(BASE=\"https://git.zally.dev/api/v1/repos/tekki/nissefolk\")",
|
|
||||||
"Bash(__NEW_LINE_5d5fe245d6f316dc__ for:*)",
|
|
||||||
"Bash(do)",
|
|
||||||
"Bash(done)",
|
|
||||||
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\(''''html_url'''',''''''''\\), d.get\\(''''number'''',''''''''\\), d.get\\(''''message'''',''''''''\\)\\)\")",
|
|
||||||
"Bash(git remote:*)",
|
|
||||||
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\(''''login'''',''''''''\\), d.get\\(''''message'''',''''''''\\)\\)\")"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,4 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
|
game-test.log
|
||||||
|
.claude/
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **ESC Menu**: pressing ESC when no overlay is open shows a pause menu with Save Game, Load Game, Settings (placeholder), and New Game; New Game requires confirmation before wiping the save
|
||||||
|
- ESC key now follows a priority stack: confirmation dialog → context menu → build menu → villager panel → ESC menu → (build/farm mode handled by their systems) → open ESC menu
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- **F3 Debug View**: toggleable overlay showing FPS, tile type and contents under the cursor, Nisse count by AI state, active jobs by type, and pathfinding visualization (cyan lines in world space)
|
- **F3 Debug View**: toggleable overlay showing FPS, tile type and contents under the cursor, Nisse count by AI state, active jobs by type, and pathfinding visualization (cyan lines in world space)
|
||||||
|
|
||||||
|
|||||||
11
CLAUDE.md
11
CLAUDE.md
@@ -1,5 +1,16 @@
|
|||||||
# CLAUDE.md — Game Project
|
# CLAUDE.md — Game Project
|
||||||
|
|
||||||
|
## ⚠️ Important: Session Start Location
|
||||||
|
|
||||||
|
**Claude Code must be started from `~` (home directory), NOT from `~/game`.**
|
||||||
|
|
||||||
|
If you are reading this and the working directory is `/home/tekki/game`, please let the user know:
|
||||||
|
> "Heads up: you've started me from inside `~/game`. Please exit and restart from your home directory (`~`) so that `.claude/` settings and memory stay outside the repo."
|
||||||
|
|
||||||
|
`.claude/` directories inside `~/game` are gitignored and must stay that way — no settings, tokens, or memory files belong in the project repo.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
A browser-based top-down game built with **Phaser 3** and **TypeScript**, bundled via **Vite**.
|
A browser-based top-down game built with **Phaser 3** and **TypeScript**, bundled via **Vite**.
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
{"t":1774091984264,"event":"snapshot","zoom":1,"scrollX":321,"scrollY":562,"vpScreen":{"w":958,"h":475},"vpWorld":{"w":958,"h":475},"vpTiles":{"w":29.938,"h":14.844},"centerWorld":{"x":800,"y":799.5},"mouse":{"screen":{"x":0,"y":0},"world":{"x":0,"y":0}}}
|
|
||||||
{"t":1774091986280,"event":"snapshot","zoom":1,"scrollX":321,"scrollY":562,"vpScreen":{"w":958,"h":475},"vpWorld":{"w":958,"h":475},"vpTiles":{"w":29.938,"h":14.844},"centerWorld":{"x":800,"y":799.5},"mouse":{"screen":{"x":288,"y":400},"world":{"x":609,"y":961.5}}}
|
|
||||||
{"t":1774091988280,"event":"snapshot","zoom":1,"scrollX":321,"scrollY":562,"vpScreen":{"w":958,"h":475},"vpWorld":{"w":958,"h":475},"vpTiles":{"w":29.938,"h":14.844},"centerWorld":{"x":800,"y":799.5},"mouse":{"screen":{"x":288,"y":400},"world":{"x":609,"y":961.5}}}
|
|
||||||
{"t":1774091990281,"event":"snapshot","zoom":1,"scrollX":321,"scrollY":562,"vpScreen":{"w":958,"h":475},"vpWorld":{"w":958,"h":475},"vpTiles":{"w":29.938,"h":14.844},"centerWorld":{"x":800,"y":799.5},"mouse":{"screen":{"x":288,"y":400},"world":{"x":609,"y":961.5}}}
|
|
||||||
{"t":1774091992281,"event":"snapshot","zoom":1,"scrollX":321,"scrollY":562,"vpScreen":{"w":958,"h":475},"vpWorld":{"w":958,"h":475},"vpTiles":{"w":29.938,"h":14.844},"centerWorld":{"x":800,"y":799.5},"mouse":{"screen":{"x":288,"y":400},"world":{"x":609,"y":961.5}}}
|
|
||||||
@@ -31,6 +31,10 @@ export class UIScene extends Phaser.Scene {
|
|||||||
private inFarmMode = false
|
private inFarmMode = false
|
||||||
private debugPanelText!: Phaser.GameObjects.Text
|
private debugPanelText!: Phaser.GameObjects.Text
|
||||||
private debugActive = false
|
private debugActive = false
|
||||||
|
private escMenuGroup!: Phaser.GameObjects.Group
|
||||||
|
private escMenuVisible = false
|
||||||
|
private confirmGroup!: Phaser.GameObjects.Group
|
||||||
|
private confirmVisible = false
|
||||||
|
|
||||||
constructor() { super({ key: 'UI' }) }
|
constructor() { super({ key: 'UI' }) }
|
||||||
|
|
||||||
@@ -66,6 +70,8 @@ export class UIScene extends Phaser.Scene {
|
|||||||
|
|
||||||
this.input.mouse!.disableContextMenu()
|
this.input.mouse!.disableContextMenu()
|
||||||
this.contextMenuGroup = this.add.group()
|
this.contextMenuGroup = this.add.group()
|
||||||
|
this.escMenuGroup = this.add.group()
|
||||||
|
this.confirmGroup = 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()) {
|
||||||
@@ -78,7 +84,7 @@ export class UIScene extends Phaser.Scene {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.input.keyboard!.addKey(Phaser.Input.Keyboard.KeyCodes.ESC)
|
this.input.keyboard!.addKey(Phaser.Input.Keyboard.KeyCodes.ESC)
|
||||||
.on('down', () => this.hideContextMenu())
|
.on('down', () => this.handleEsc())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -488,6 +494,179 @@ export class UIScene extends Phaser.Scene {
|
|||||||
this.scene.get('Game').events.emit('uiMenuClose')
|
this.scene.get('Game').events.emit('uiMenuClose')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── ESC key handler ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles ESC key presses with a priority stack:
|
||||||
|
* confirm dialog → context menu → build menu → villager panel →
|
||||||
|
* esc menu → build/farm mode (handled by their own systems) → open ESC menu.
|
||||||
|
*/
|
||||||
|
private handleEsc(): void {
|
||||||
|
if (this.confirmVisible) { this.hideConfirm(); return }
|
||||||
|
if (this.contextMenuVisible) { this.hideContextMenu(); return }
|
||||||
|
if (this.buildMenuVisible) { this.closeBuildMenu(); return }
|
||||||
|
if (this.villagerPanelVisible){ this.closeVillagerPanel(); return }
|
||||||
|
if (this.escMenuVisible) { this.closeEscMenu(); return }
|
||||||
|
// Build/farm mode: let BuildingSystem / FarmingSystem handle their own ESC key.
|
||||||
|
// We only skip opening the ESC menu while those modes are active.
|
||||||
|
if (this.inBuildMode || this.inFarmMode) return
|
||||||
|
this.openEscMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── ESC Menu ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Opens the ESC pause menu (New Game / Save / Load / Settings). */
|
||||||
|
private openEscMenu(): void {
|
||||||
|
if (this.escMenuVisible) return
|
||||||
|
this.escMenuVisible = true
|
||||||
|
this.scene.get('Game').events.emit('uiMenuOpen')
|
||||||
|
this.buildEscMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Closes and destroys the ESC menu. */
|
||||||
|
private closeEscMenu(): void {
|
||||||
|
if (!this.escMenuVisible) return
|
||||||
|
this.escMenuVisible = false
|
||||||
|
this.escMenuGroup.destroy(true)
|
||||||
|
this.escMenuGroup = this.add.group()
|
||||||
|
this.scene.get('Game').events.emit('uiMenuClose')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Builds the ESC menu UI elements. */
|
||||||
|
private buildEscMenu(): void {
|
||||||
|
if (this.escMenuGroup) this.escMenuGroup.destroy(true)
|
||||||
|
this.escMenuGroup = this.add.group()
|
||||||
|
|
||||||
|
const menuW = 240
|
||||||
|
const btnH = 40
|
||||||
|
const entries: { label: string; action: () => void }[] = [
|
||||||
|
{ label: '💾 Save Game', action: () => this.doSaveGame() },
|
||||||
|
{ label: '📂 Load Game', action: () => this.doLoadGame() },
|
||||||
|
{ label: '⚙️ Settings', action: () => this.doSettings() },
|
||||||
|
{ label: '🆕 New Game', action: () => this.doNewGame() },
|
||||||
|
]
|
||||||
|
const menuH = 16 + entries.length * (btnH + 8) + 8
|
||||||
|
const mx = this.scale.width / 2 - menuW / 2
|
||||||
|
const my = this.scale.height / 2 - menuH / 2
|
||||||
|
|
||||||
|
const bg = this.add.rectangle(mx, my, menuW, menuH, 0x0a0a0a, 0.95)
|
||||||
|
.setOrigin(0, 0).setScrollFactor(0).setDepth(400)
|
||||||
|
this.escMenuGroup.add(bg)
|
||||||
|
this.escMenuGroup.add(
|
||||||
|
this.add.text(mx + menuW / 2, my + 12, 'MENU [ESC] close', {
|
||||||
|
fontSize: '11px', color: '#666666', fontFamily: 'monospace',
|
||||||
|
}).setOrigin(0.5, 0).setScrollFactor(0).setDepth(401)
|
||||||
|
)
|
||||||
|
|
||||||
|
entries.forEach((entry, i) => {
|
||||||
|
const by = my + 32 + i * (btnH + 8)
|
||||||
|
const btn = this.add.rectangle(mx + 12, by, menuW - 24, btnH, 0x1a1a2e, 0.9)
|
||||||
|
.setOrigin(0, 0).setScrollFactor(0).setDepth(401).setInteractive()
|
||||||
|
btn.on('pointerover', () => btn.setFillStyle(0x2a2a4e, 0.9))
|
||||||
|
btn.on('pointerout', () => btn.setFillStyle(0x1a1a2e, 0.9))
|
||||||
|
btn.on('pointerdown', entry.action)
|
||||||
|
this.escMenuGroup.add(btn)
|
||||||
|
this.escMenuGroup.add(
|
||||||
|
this.add.text(mx + 24, by + btnH / 2, entry.label, {
|
||||||
|
fontSize: '14px', color: '#dddddd', fontFamily: 'monospace',
|
||||||
|
}).setOrigin(0, 0.5).setScrollFactor(0).setDepth(402)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Saves the game and shows a toast confirmation. */
|
||||||
|
private doSaveGame(): void {
|
||||||
|
stateManager.save()
|
||||||
|
this.closeEscMenu()
|
||||||
|
this.showToast('Game saved!')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Reloads the page to load the last save from localStorage. */
|
||||||
|
private doLoadGame(): void {
|
||||||
|
this.closeEscMenu()
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Opens an empty Settings panel (placeholder). */
|
||||||
|
private doSettings(): void {
|
||||||
|
this.closeEscMenu()
|
||||||
|
this.showToast('Settings — coming soon')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Shows a confirmation dialog before starting a new game. */
|
||||||
|
private doNewGame(): void {
|
||||||
|
this.closeEscMenu()
|
||||||
|
this.showConfirm(
|
||||||
|
'Start a new game?\nAll progress will be lost.',
|
||||||
|
() => { stateManager.reset(); window.location.reload() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Confirm dialog ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a modal confirmation dialog with OK and Cancel buttons.
|
||||||
|
* @param message - Message to display (newlines supported)
|
||||||
|
* @param onConfirm - Callback invoked when the user confirms
|
||||||
|
*/
|
||||||
|
private showConfirm(message: string, onConfirm: () => void): void {
|
||||||
|
this.hideConfirm()
|
||||||
|
this.confirmVisible = true
|
||||||
|
this.scene.get('Game').events.emit('uiMenuOpen')
|
||||||
|
|
||||||
|
const dialogW = 280
|
||||||
|
const dialogH = 130
|
||||||
|
const dx = this.scale.width / 2 - dialogW / 2
|
||||||
|
const dy = this.scale.height / 2 - dialogH / 2
|
||||||
|
|
||||||
|
const bg = this.add.rectangle(dx, dy, dialogW, dialogH, 0x0a0a0a, 0.97)
|
||||||
|
.setOrigin(0, 0).setScrollFactor(0).setDepth(500)
|
||||||
|
this.confirmGroup.add(bg)
|
||||||
|
|
||||||
|
this.confirmGroup.add(
|
||||||
|
this.add.text(dx + dialogW / 2, dy + 20, message, {
|
||||||
|
fontSize: '13px', color: '#cccccc', fontFamily: 'monospace',
|
||||||
|
align: 'center', wordWrap: { width: dialogW - 32 },
|
||||||
|
}).setOrigin(0.5, 0).setScrollFactor(0).setDepth(501)
|
||||||
|
)
|
||||||
|
|
||||||
|
const btnY = dy + dialogH - 44
|
||||||
|
// Cancel button
|
||||||
|
const cancelBtn = this.add.rectangle(dx + 16, btnY, 110, 30, 0x333333, 0.9)
|
||||||
|
.setOrigin(0, 0).setScrollFactor(0).setDepth(501).setInteractive()
|
||||||
|
cancelBtn.on('pointerover', () => cancelBtn.setFillStyle(0x555555, 0.9))
|
||||||
|
cancelBtn.on('pointerout', () => cancelBtn.setFillStyle(0x333333, 0.9))
|
||||||
|
cancelBtn.on('pointerdown', () => this.hideConfirm())
|
||||||
|
this.confirmGroup.add(cancelBtn)
|
||||||
|
this.confirmGroup.add(
|
||||||
|
this.add.text(dx + 71, btnY + 15, 'Cancel', {
|
||||||
|
fontSize: '13px', color: '#aaaaaa', fontFamily: 'monospace',
|
||||||
|
}).setOrigin(0.5, 0.5).setScrollFactor(0).setDepth(502)
|
||||||
|
)
|
||||||
|
|
||||||
|
// OK button
|
||||||
|
const okBtn = this.add.rectangle(dx + dialogW - 126, btnY, 110, 30, 0x4a1a1a, 0.9)
|
||||||
|
.setOrigin(0, 0).setScrollFactor(0).setDepth(501).setInteractive()
|
||||||
|
okBtn.on('pointerover', () => okBtn.setFillStyle(0x8a2a2a, 0.9))
|
||||||
|
okBtn.on('pointerout', () => okBtn.setFillStyle(0x4a1a1a, 0.9))
|
||||||
|
okBtn.on('pointerdown', () => { this.hideConfirm(); onConfirm() })
|
||||||
|
this.confirmGroup.add(okBtn)
|
||||||
|
this.confirmGroup.add(
|
||||||
|
this.add.text(dx + dialogW - 71, btnY + 15, 'OK', {
|
||||||
|
fontSize: '13px', color: '#ff8888', fontFamily: 'monospace',
|
||||||
|
}).setOrigin(0.5, 0.5).setScrollFactor(0).setDepth(502)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Closes and destroys the confirmation dialog. */
|
||||||
|
private hideConfirm(): void {
|
||||||
|
if (!this.confirmVisible) return
|
||||||
|
this.confirmVisible = false
|
||||||
|
this.confirmGroup.destroy(true)
|
||||||
|
this.confirmGroup = this.add.group()
|
||||||
|
this.scene.get('Game').events.emit('uiMenuClose')
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Resize ───────────────────────────────────────────────────────────────
|
// ─── Resize ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -518,5 +697,7 @@ export class UIScene extends Phaser.Scene {
|
|||||||
if (this.buildMenuVisible) this.closeBuildMenu()
|
if (this.buildMenuVisible) this.closeBuildMenu()
|
||||||
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.confirmVisible) this.hideConfirm()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user