import Phaser from 'phaser' import { TILE_SIZE } from '../config' import { TileType } from '../types' import { stateManager } from '../StateManager' import type { VillagerSystem } from './VillagerSystem' import type { WorldSystem } from './WorldSystem' /** All data collected each frame for the debug panel. */ export interface DebugData { fps: number mouseWorld: { x: number; y: number } mouseTile: { tileX: number; tileY: number } tileType: string resourcesOnTile: Array<{ kind: string; hp: number }> buildingsOnTile: string[] cropsOnTile: Array<{ kind: string; stage: number; maxStage: number }> nisseTotal: number nisseByState: { idle: number; walking: number; working: number; sleeping: number } jobsByType: { chop: number; mine: number; farm: number } activePaths: number } /** Human-readable names for TileType enum values. */ const TILE_NAMES: Record = { [TileType.DEEP_WATER]: 'DEEP_WATER', [TileType.SHALLOW_WATER]: 'SHALLOW_WATER', [TileType.SAND]: 'SAND', [TileType.GRASS]: 'GRASS', [TileType.DARK_GRASS]: 'DARK_GRASS', [TileType.FOREST]: 'FOREST', [TileType.ROCK]: 'ROCK', [TileType.FLOOR]: 'FLOOR', [TileType.WALL]: 'WALL', [TileType.TILLED_SOIL]: 'TILLED_SOIL', [TileType.WATERED_SOIL]: 'WATERED_SOIL', } export class DebugSystem { private scene: Phaser.Scene private villagerSystem: VillagerSystem private worldSystem: WorldSystem private pathGraphics!: Phaser.GameObjects.Graphics private active = false /** * @param scene - The Phaser scene this system belongs to * @param villagerSystem - Used to read active paths for visualization * @param worldSystem - Used to read tile types under the mouse */ constructor(scene: Phaser.Scene, villagerSystem: VillagerSystem, worldSystem: WorldSystem) { this.scene = scene this.villagerSystem = villagerSystem this.worldSystem = worldSystem } /** * Creates the world-space Graphics object used for pathfinding visualization. * Starts hidden until toggled on. */ create(): void { this.pathGraphics = this.scene.add.graphics().setDepth(50) this.pathGraphics.setVisible(false) } /** * Toggles debug mode on or off. * Shows or hides the pathfinding overlay graphics accordingly. */ toggle(): void { this.active = !this.active this.pathGraphics.setVisible(this.active) if (!this.active) this.pathGraphics.clear() } /** Returns whether debug mode is currently active. */ isActive(): boolean { return this.active } /** * Redraws pathfinding lines for all currently walking Nisse. * Should be called every frame while debug mode is active. */ update(): void { if (!this.active) return this.pathGraphics.clear() const paths = this.villagerSystem.getActivePaths() this.pathGraphics.lineStyle(1, 0x00ffff, 0.65) for (const entry of paths) { if (entry.path.length === 0) continue this.pathGraphics.beginPath() this.pathGraphics.moveTo(entry.x, entry.y) for (const step of entry.path) { this.pathGraphics.lineTo( (step.tileX + 0.5) * TILE_SIZE, (step.tileY + 0.5) * TILE_SIZE, ) } this.pathGraphics.strokePath() // Mark the destination tile const last = entry.path[entry.path.length - 1] this.pathGraphics.fillStyle(0x00ffff, 0.4) this.pathGraphics.fillRect( last.tileX * TILE_SIZE, last.tileY * TILE_SIZE, TILE_SIZE, TILE_SIZE, ) } } /** * Collects and returns all debug data for the current frame. * Called by UIScene to populate the debug panel. * @param ptr - The active pointer, used to resolve world position * @returns Snapshot of game state for display */ getDebugData(ptr: Phaser.Input.Pointer): DebugData { const state = stateManager.getState() const villagers = Object.values(state.world.villagers) const tileX = Math.floor(ptr.worldX / TILE_SIZE) const tileY = Math.floor(ptr.worldY / TILE_SIZE) const tileType = this.worldSystem.getTileType(tileX, tileY) const nisseByState = { idle: 0, walking: 0, working: 0, sleeping: 0 } const jobsByType = { chop: 0, mine: 0, farm: 0 } for (const v of villagers) { nisseByState[v.aiState as keyof typeof nisseByState]++ if (v.job && (v.aiState === 'working' || v.aiState === 'walking')) { jobsByType[v.job.type as keyof typeof jobsByType]++ } } const resourcesOnTile = Object.values(state.world.resources) .filter(r => r.tileX === tileX && r.tileY === tileY) .map(r => ({ kind: r.kind, hp: r.hp })) const buildingsOnTile = Object.values(state.world.buildings) .filter(b => b.tileX === tileX && b.tileY === tileY) .map(b => b.kind) const cropsOnTile = Object.values(state.world.crops) .filter(c => c.tileX === tileX && c.tileY === tileY) .map(c => ({ kind: c.kind, stage: c.stage, maxStage: c.maxStage })) return { fps: Math.round(this.scene.game.loop.actualFps), mouseWorld: { x: ptr.worldX, y: ptr.worldY }, mouseTile: { tileX, tileY }, tileType: TILE_NAMES[tileType] ?? `UNKNOWN(${tileType})`, resourcesOnTile, buildingsOnTile, cropsOnTile, nisseTotal: villagers.length, nisseByState, jobsByType, activePaths: this.villagerSystem.getActivePaths().length, } } }