import Phaser from 'phaser' import { AUTOSAVE_INTERVAL, TILE_SIZE } from '../config' import { TileType } from '../types' import type { BuildingType } from '../types' import { stateManager } from '../StateManager' import { LocalAdapter } from '../NetworkAdapter' import { WorldSystem } from '../systems/WorldSystem' import { CameraSystem } from '../systems/CameraSystem' import { ResourceSystem } from '../systems/ResourceSystem' import { BuildingSystem } from '../systems/BuildingSystem' import { FarmingSystem } from '../systems/FarmingSystem' import { VillagerSystem } from '../systems/VillagerSystem' import { DebugSystem } from '../systems/DebugSystem' import { TreeSeedlingSystem } from '../systems/TreeSeedlingSystem' import { ForesterZoneSystem } from '../systems/ForesterZoneSystem' export class GameScene extends Phaser.Scene { private adapter!: LocalAdapter private worldSystem!: WorldSystem private cameraSystem!: CameraSystem private resourceSystem!: ResourceSystem private buildingSystem!: BuildingSystem private farmingSystem!: FarmingSystem villagerSystem!: VillagerSystem debugSystem!: DebugSystem private treeSeedlingSystem!: TreeSeedlingSystem foresterZoneSystem!: ForesterZoneSystem private autosaveTimer = 0 private menuOpen = false constructor() { super({ key: 'Game' }) } /** * Initialises all game systems, wires up inter-system events, * launches the UI scene overlay, and starts the autosave timer. */ create(): void { this.adapter = new LocalAdapter() this.worldSystem = new WorldSystem(this) this.cameraSystem = new CameraSystem(this, this.adapter) this.resourceSystem = new ResourceSystem(this, this.adapter) this.buildingSystem = new BuildingSystem(this, this.adapter) this.farmingSystem = new FarmingSystem(this, this.adapter) this.villagerSystem = new VillagerSystem(this, this.adapter, this.worldSystem) this.villagerSystem.init(this.resourceSystem, this.farmingSystem) this.treeSeedlingSystem = new TreeSeedlingSystem(this, this.adapter, this.worldSystem) this.foresterZoneSystem = new ForesterZoneSystem(this, this.adapter) this.debugSystem = new DebugSystem(this, this.villagerSystem, this.worldSystem, this.adapter) this.worldSystem.create() this.renderPersistentObjects() this.cameraSystem.create() this.resourceSystem.create() this.resourceSystem.onHarvest = (msg) => this.events.emit('toast', msg) this.buildingSystem.create() this.buildingSystem.onModeChange = (active, building) => this.events.emit('buildModeChanged', active, building) this.buildingSystem.onPlaced = (msg) => { this.events.emit('toast', msg) this.renderPersistentObjects() } this.farmingSystem.create() this.farmingSystem.onMessage = (msg) => this.events.emit('toast', msg) this.farmingSystem.onToolChange = (tool, label) => this.events.emit('farmToolChanged', tool, label) this.farmingSystem.onPlantTreeSeed = (tileX, tileY, tile) => this.treeSeedlingSystem.plantSeedling(tileX, tileY, tile) this.treeSeedlingSystem.create() this.foresterZoneSystem.create() this.foresterZoneSystem.refreshOverlay() this.foresterZoneSystem.onEditEnded = () => this.events.emit('foresterZoneEditEnded') this.foresterZoneSystem.onZoneChanged = (id, tiles) => this.events.emit('foresterZoneChanged', id, tiles) this.villagerSystem.create() this.villagerSystem.onMessage = (msg) => this.events.emit('toast', msg) this.villagerSystem.onNisseClick = (id) => this.events.emit('nisseClicked', id) this.villagerSystem.onPlantSeedling = (tileX, tileY, tile) => this.treeSeedlingSystem.plantSeedling(tileX, tileY, tile) this.debugSystem.create() // Sync tile changes and building visuals through adapter this.adapter.onAction = (action) => { if (action.type === 'CHANGE_TILE') { this.worldSystem.setTile(action.tileX, action.tileY, action.tile) this.worldSystem.refreshTerrainTile(action.tileX, action.tileY, action.tile) } else if (action.type === 'SPAWN_RESOURCE') { this.resourceSystem.spawnResourcePublic(action.resource) this.worldSystem.addResourceTile(action.resource.tileX, action.resource.tileY) } else if (action.type === 'FORESTER_ZONE_UPDATE') { this.foresterZoneSystem.refreshOverlay() } } // Detect left-clicks on forester huts to open the zone panel this.input.on('pointerdown', (ptr: Phaser.Input.Pointer) => { if (ptr.rightButtonDown() || this.menuOpen) return if (this.buildingSystem.isActive()) return const tileX = Math.floor(ptr.worldX / TILE_SIZE) const tileY = Math.floor(ptr.worldY / TILE_SIZE) const state = stateManager.getState() const hut = Object.values(state.world.buildings).find( b => b.kind === 'forester_hut' && b.tileX === tileX && b.tileY === tileY ) if (hut) { this.events.emit('foresterHutClicked', hut.id) } }) this.scene.launch('UI') this.events.on('selectBuilding', (kind: BuildingType) => this.buildingSystem.selectBuilding(kind)) this.events.on('uiMenuOpen', () => { this.menuOpen = true }) this.events.on('uiMenuClose', () => { this.menuOpen = false }) this.events.on('uiRequestBuildMenu', () => { if (!this.buildingSystem.isActive()) this.events.emit('openBuildMenu') }) this.events.on('updatePriorities', (villagerId: string, priorities: { chop: number; mine: number; farm: number; forester: number }) => { this.adapter.send({ type: 'UPDATE_PRIORITIES', villagerId, priorities }) }) this.events.on('foresterZoneEditStart', (buildingId: string) => { this.foresterZoneSystem.startEditMode(buildingId) this.menuOpen = false // keep game ticking while zone editor is open }) this.events.on('foresterZoneEditStop', () => { this.foresterZoneSystem.exitEditMode() }) this.events.on('debugToggle', () => this.debugSystem.toggle()) this.autosaveTimer = AUTOSAVE_INTERVAL } /** * Main game loop: updates all systems and emits the cameraMoved event for the UI. * Skips system updates while a menu is open. * @param _time - Total elapsed time (unused) * @param delta - Frame delta in milliseconds */ update(_time: number, delta: number): void { if (this.menuOpen) return // Advance the in-game clock first so all tick methods see the updated time stateManager.advanceTime(delta) this.cameraSystem.update(delta) this.resourceSystem.update(delta) this.farmingSystem.update(delta) this.treeSeedlingSystem.update(delta) this.villagerSystem.update(delta) this.debugSystem.update() // Drain tile-recovery queue; refresh canvas for any tiles that reverted to GRASS const recovered = stateManager.tickTileRecovery() for (const key of recovered) { const [tx, ty] = key.split(',').map(Number) this.worldSystem.refreshTerrainTile(tx, ty, TileType.GRASS) } this.events.emit('cameraMoved', this.cameraSystem.getCenterTile()) this.buildingSystem.update() this.autosaveTimer -= delta if (this.autosaveTimer <= 0) { this.autosaveTimer = AUTOSAVE_INTERVAL stateManager.save() } } /** Render game objects that persist across sessions (buildings + crop sprites etc.) */ private renderPersistentObjects(): void { const state = stateManager.getState() for (const building of Object.values(state.world.buildings)) { const wx = building.tileX * TILE_SIZE + TILE_SIZE / 2 const wy = building.tileY * TILE_SIZE + TILE_SIZE / 2 const name = `bobj_${building.id}` if (this.children.getByName(name)) continue const worldDepth = building.tileY + 5 if (building.kind === 'chest') { const g = this.add.graphics().setName(name).setDepth(worldDepth) g.fillStyle(0x8B4513); g.fillRect(wx - 10, wy - 7, 20, 14) g.fillStyle(0xCD853F); g.fillRect(wx - 9, wy - 6, 18, 6) g.lineStyle(1, 0x5C3317); g.strokeRect(wx - 10, wy - 7, 20, 14) } else if (building.kind === 'bed') { this.add.image(wx, wy, 'bed_obj').setName(name).setDepth(worldDepth) } else if (building.kind === 'stockpile_zone') { this.add.image(wx, wy, 'stockpile_obj').setName(name).setDepth(4).setAlpha(0.8) } else if (building.kind === 'forester_hut') { // Draw a simple log-cabin silhouette for the forester hut const g = this.add.graphics().setName(name).setDepth(worldDepth) // Body g.fillStyle(0x6B3F16); g.fillRect(wx - 12, wy - 9, 24, 18) // Roof g.fillStyle(0x4a2800); g.fillTriangle(wx - 14, wy - 9, wx + 14, wy - 9, wx, wy - 22) // Door g.fillStyle(0x2a1500); g.fillRect(wx - 4, wy + 1, 8, 8) // Tree symbol on the roof g.fillStyle(0x228B22); g.fillTriangle(wx - 6, wy - 11, wx + 6, wy - 11, wx, wy - 20) } } } /** Saves game state and destroys all systems cleanly on scene shutdown. */ shutdown(): void { stateManager.save() this.worldSystem.destroy() this.resourceSystem.destroy() this.buildingSystem.destroy() this.farmingSystem.destroy() this.treeSeedlingSystem.destroy() this.foresterZoneSystem.destroy() this.villagerSystem.destroy() } }