import Phaser from 'phaser' import { TILE_SIZE, WORLD_TILES } from '../config' import { TileType } from '../types' import { generateTerrain, findSpawn } from '../utils/noise' import { stateManager } from '../StateManager' const TOTAL_TILES = 11 // 0-10 in the tileset strip export class BootScene extends Phaser.Scene { constructor() { super({ key: 'Boot' }) } preload(): void { this.createLoadingBar() } create(): void { this.buildTileset() this.buildResourceTextures() this.buildPlayerTexture() this.buildCropTextures() this.buildUITextures() this.buildVillagerAndBuildingTextures() this.generateWorldIfNeeded() this.scene.start('Game') } // ─── Loading bar ────────────────────────────────────────────────────────── private createLoadingBar(): void { const { width, height } = this.scale const barW = 400, barH = 20 const x = width / 2 - barW / 2 const y = height / 2 const border = this.add.graphics() border.lineStyle(2, 0xffffff) border.strokeRect(x - 2, y - 2, barW + 4, barH + 4) const bar = this.add.graphics() this.load.on('progress', (v: number) => { bar.clear() bar.fillStyle(0x4CAF50) bar.fillRect(x, y, barW * v, barH) }) this.add.text(width / 2, y - 40, 'Loading...', { fontSize: '20px', color: '#ffffff', fontFamily: 'monospace' }).setOrigin(0.5) } // ─── Tileset (TOTAL_TILES × TILE_SIZE wide, TILE_SIZE tall) ────────────── private buildTileset(): void { const T = TILE_SIZE const g = this.add.graphics() const drawTile = (idx: number, cb: (g: Phaser.GameObjects.Graphics) => void) => { g.save(); g.translateCanvas(idx * T, 0); cb(g); g.restore() } // 0 – Deep water drawTile(TileType.DEEP_WATER, g => { g.fillStyle(0x1565C0); g.fillRect(0, 0, T, T) g.fillStyle(0x1976D2, 0.6) for (let i = 0; i < 3; i++) g.fillRect(4 + i * 10, 8 + i * 8, 14, 3) }) // 1 – Shallow water drawTile(TileType.SHALLOW_WATER, g => { g.fillStyle(0x42A5F5); g.fillRect(0, 0, T, T) g.fillStyle(0x64B5F6, 0.7) g.fillRect(5, 12, 22, 3); g.fillRect(8, 20, 16, 3) }) // 2 – Sand drawTile(TileType.SAND, g => { g.fillStyle(0xF5DEB3); g.fillRect(0, 0, T, T) g.fillStyle(0xDEB887, 0.5) g.fillRect(4, 4, 6, 6); g.fillRect(18, 14, 8, 8); g.fillRect(10, 22, 5, 5) }) // 3 – Grass drawTile(TileType.GRASS, g => { g.fillStyle(0x66BB6A); g.fillRect(0, 0, T, T) g.fillStyle(0x4CAF50, 0.6) g.fillRect(3, 8, 4, 6); g.fillRect(14, 4, 4, 8); g.fillRect(24, 16, 3, 7) }) // 4 – Dark grass drawTile(TileType.DARK_GRASS, g => { g.fillStyle(0x43A047); g.fillRect(0, 0, T, T) g.fillStyle(0x388E3C, 0.6) g.fillRect(2, 6, 5, 8); g.fillRect(16, 3, 5, 10); g.fillRect(22, 18, 4, 8) }) // 5 – Forest floor (under trees) drawTile(TileType.FOREST, g => { g.fillStyle(0x33691E); g.fillRect(0, 0, T, T) g.fillStyle(0x2E7D32, 0.5); g.fillRect(0, 0, T, T) }) // 6 – Rock ground drawTile(TileType.ROCK, g => { g.fillStyle(0x616161); g.fillRect(0, 0, T, T) g.fillStyle(0x757575, 0.6) g.fillRect(4, 4, 10, 10); g.fillRect(18, 16, 8, 8) }) // 7 – Built floor (wood plank) drawTile(TileType.FLOOR, g => { g.fillStyle(0xD2A679); g.fillRect(0, 0, T, T) g.lineStyle(1, 0xB8895A) g.strokeRect(1, 1, T - 2, T - 2) g.strokeRect(1, T / 2, T - 2, 1) }) // 8 – Built wall drawTile(TileType.WALL, g => { g.fillStyle(0x9E9E9E); g.fillRect(0, 0, T, T) g.fillStyle(0x757575) for (let row = 0; row < 4; row++) { for (let col = 0; col < 2; col++) { const ox = col * 16 + (row % 2 === 0 ? 0 : 8) g.fillRect(ox + 1, row * 8 + 1, 14, 6) } } }) // 9 – Tilled soil drawTile(TileType.TILLED_SOIL, g => { g.fillStyle(0x5D3A1A); g.fillRect(0, 0, T, T) g.fillStyle(0x4A2E0E) for (let row = 0; row < 4; row++) { g.fillRect(2, 4 + row * 8, T - 4, 2) } g.fillStyle(0x7B4F2A, 0.5) g.fillRect(3, 5, T - 6, 1) g.fillRect(3, 13, T - 6, 1) }) // 10 – Watered soil drawTile(TileType.WATERED_SOIL, g => { g.fillStyle(0x3E2208); g.fillRect(0, 0, T, T) g.fillStyle(0x2C5F8A, 0.25); g.fillRect(0, 0, T, T) g.fillStyle(0x2A1505) for (let row = 0; row < 4; row++) { g.fillRect(2, 4 + row * 8, T - 4, 2) } g.fillStyle(0x4A90D9, 0.3) g.fillRect(3, 5, T - 6, 1) g.fillRect(3, 13, T - 6, 1) }) g.generateTexture('tiles', TOTAL_TILES * T, T) g.destroy() } // ─── Tree and rock textures ─────────────────────────────────────────────── private buildResourceTextures(): void { // Tree (32 × 52) const tg = this.add.graphics() tg.fillStyle(0x000000, 0.18); tg.fillEllipse(16, 44, 22, 8) tg.fillStyle(0x6D4C41); tg.fillRect(11, 28, 10, 18) tg.fillStyle(0x2E7D32); tg.fillCircle(16, 20, 15) tg.fillStyle(0x388E3C); tg.fillCircle(10, 25, 11); tg.fillCircle(22, 25, 11) tg.fillStyle(0x43A047); tg.fillCircle(16, 14, 10) tg.generateTexture('tree', 32, 52) tg.destroy() // Rock (40 × 34) const rg = this.add.graphics() rg.fillStyle(0x000000, 0.18); rg.fillEllipse(20, 30, 36, 8) rg.fillStyle(0x78909C); rg.fillEllipse(20, 20, 36, 26) rg.fillStyle(0x90A4AE); rg.fillEllipse(14, 14, 20, 16) rg.fillStyle(0xB0BEC5, 0.6); rg.fillEllipse(12, 10, 10, 8) rg.generateTexture('rock', 40, 34) rg.destroy() } // ─── Player texture (placeholder – no player body in game) ─────────────── private buildPlayerTexture(): void { const T = TILE_SIZE const dirs = ['down', 'up', 'left', 'right'] as const dirs.forEach(dir => { const g = this.add.graphics() g.fillStyle(0x000000, 0.2); g.fillEllipse(T / 2, T - 4, 20, 8) g.fillStyle(0xE53935); g.fillCircle(T / 2, T / 2 - 2, 11) g.fillStyle(0x3949AB) g.fillRect(T / 2 - 8, T / 2 + 5, 7, 10) g.fillRect(T / 2 + 1, T / 2 + 5, 7, 10) g.fillStyle(0xFFFFFF) if (dir === 'down') g.fillTriangle(T/2, T/2+2, T/2-5, T/2-4, T/2+5, T/2-4) if (dir === 'up') g.fillTriangle(T/2, T/2-5, T/2-5, T/2+3, T/2+5, T/2+3) if (dir === 'left') g.fillTriangle(T/2-5, T/2, T/2+3, T/2-5, T/2+3, T/2+5) if (dir === 'right') g.fillTriangle(T/2+5, T/2, T/2-3, T/2-5, T/2-3, T/2+5) g.generateTexture(`player_${dir}`, T, T) g.destroy() }) } // ─── Crop textures ──────────────────────────────────────────────────────── private buildCropTextures(): void { this.buildWheatTextures() this.buildCarrotTextures() } private buildWheatTextures(): void { const W = 32, H = 40 const g0 = this.add.graphics() g0.fillStyle(0x8BC34A); g0.fillRect(14, 26, 4, 12) g0.fillStyle(0x9CCC65); g0.fillEllipse(16, 24, 10, 8) g0.generateTexture('crop_wheat_0', W, H); g0.destroy() const g1 = this.add.graphics() g1.fillStyle(0x7CB342) for (let i = 0; i < 3; i++) { g1.fillRect(8 + i * 8, 16 + i * 2, 3, 22 - i * 2) } g1.fillStyle(0x9CCC65) g1.fillEllipse(10, 14, 8, 6); g1.fillEllipse(18, 12, 8, 6); g1.fillEllipse(26, 16, 8, 6) g1.generateTexture('crop_wheat_1', W, H); g1.destroy() const g2 = this.add.graphics() g2.fillStyle(0x558B2F) for (let i = 0; i < 4; i++) { g2.fillRect(5 + i * 7, 8 + (i % 2) * 4, 3, 30 - (i % 2) * 4) } g2.fillStyle(0x689F38) g2.fillEllipse(7, 6, 7, 5); g2.fillEllipse(14, 4, 7, 5) g2.fillEllipse(21, 7, 7, 5); g2.fillEllipse(28, 5, 7, 5) g2.generateTexture('crop_wheat_2', W, H); g2.destroy() const g3 = this.add.graphics() g3.fillStyle(0x795548) for (let i = 0; i < 5; i++) { g3.fillRect(3 + i * 6, 14 + (i % 2) * 2, 2, 24) } g3.fillStyle(0xFDD835) for (let i = 0; i < 5; i++) { g3.fillEllipse(4 + i * 6, 10 + (i % 2) * 2, 6, 12) } g3.fillStyle(0xF9A825, 0.7) for (let i = 0; i < 5; i++) { g3.fillRect(3 + i * 6, 4 + (i % 2) * 2, 2, 8) } g3.generateTexture('crop_wheat_3', W, H); g3.destroy() } private buildCarrotTextures(): void { const W = 32, H = 40 const g0 = this.add.graphics() g0.fillStyle(0x4CAF50); g0.fillRect(14, 26, 4, 12) g0.fillStyle(0x66BB6A); g0.fillEllipse(16, 24, 10, 8) g0.generateTexture('crop_carrot_0', W, H); g0.destroy() const g1 = this.add.graphics() g1.fillStyle(0x388E3C) g1.fillRect(14, 22, 4, 16) g1.fillEllipse(11, 18, 10, 8); g1.fillEllipse(21, 18, 10, 8) g1.fillEllipse(16, 14, 8, 10) g1.generateTexture('crop_carrot_1', W, H); g1.destroy() const g2 = this.add.graphics() g2.fillStyle(0x2E7D32) g2.fillRect(14, 18, 4, 20) g2.fillEllipse(9, 14, 12, 10); g2.fillEllipse(23, 14, 12, 10) g2.fillEllipse(16, 8, 10, 12); g2.fillEllipse(13, 10, 8, 8); g2.fillEllipse(19, 10, 8, 8) g2.fillStyle(0xE65100, 0.6); g2.fillEllipse(16, 36, 8, 6) g2.generateTexture('crop_carrot_2', W, H); g2.destroy() const g3 = this.add.graphics() g3.fillStyle(0x1B5E20) g3.fillRect(14, 14, 4, 16) g3.fillEllipse(8, 10, 12, 10); g3.fillEllipse(24, 10, 12, 10) g3.fillEllipse(16, 4, 10, 12); g3.fillEllipse(13, 6, 8, 8); g3.fillEllipse(19, 6, 8, 8) g3.fillStyle(0xFF6F00); g3.fillEllipse(16, 30, 12, 10) g3.fillStyle(0xFF8F00) g3.fillTriangle(10, 28, 22, 28, 16, 40) g3.fillStyle(0xFFCC02, 0.4); g3.fillEllipse(13, 27, 5, 4) g3.generateTexture('crop_carrot_3', W, H); g3.destroy() } // ─── UI panel texture ───────────────────────────────────────────────────── private buildUITextures(): void { const pg = this.add.graphics() pg.fillStyle(0x000000, 0.65) pg.fillRoundedRect(0, 0, 200, 100, 8) pg.generateTexture('panel', 200, 100) pg.destroy() } // ─── Villager + building object textures ────────────────────────────────── private buildVillagerAndBuildingTextures(): void { // ── Villager gnome (24 × 28) ────────────────────────────────────────── const vg = this.add.graphics() vg.fillStyle(0x000000, 0.15); vg.fillEllipse(12, 26, 18, 6) vg.fillStyle(0x1A237E); vg.fillRect(7, 16, 5, 9) vg.fillRect(12, 16, 5, 9) vg.fillStyle(0x3949AB); vg.fillRect(5, 9, 14, 10) vg.fillStyle(0xFFCC80); vg.fillCircle(12, 7, 6) vg.fillStyle(0x4E342E); vg.fillCircle(10, 6, 1); vg.fillCircle(14, 6, 1) vg.generateTexture('villager', 24, 28) vg.destroy() // ── Bed (32 × 32) ───────────────────────────────────────────────────── const bg = this.add.graphics() bg.fillStyle(0x6D4C41); bg.fillRect(1, 1, 30, 30) bg.fillStyle(0xEFEBE9); bg.fillRect(3, 3, 26, 10) bg.fillStyle(0x5C6BC0); bg.fillRect(3, 15, 26, 14) bg.lineStyle(1, 0x4E342E, 0.8); bg.strokeRect(1, 1, 30, 30) bg.fillStyle(0xBCAAA4); bg.fillRect(3, 13, 26, 3) bg.generateTexture('bed_obj', 32, 32) bg.destroy() // ── Stockpile zone (32 × 32) ────────────────────────────────────────── const sg = this.add.graphics() sg.fillStyle(0xFFF9C4, 0.5); sg.fillRect(0, 0, 32, 32) sg.lineStyle(2, 0xF9A825, 0.9) for (let i = 0; i < 32; i += 6) { sg.strokeRect(i, 0, 6, 32) } sg.fillStyle(0x8D6E63); sg.fillRect(6, 10, 9, 9); sg.fillRect(17, 10, 9, 9) sg.fillRect(6, 20, 9, 9); sg.fillRect(17, 20, 9, 9) sg.lineStyle(1, 0x5D4037) sg.strokeRect(6, 10, 9, 9); sg.strokeRect(17, 10, 9, 9) sg.strokeRect(6, 20, 9, 9); sg.strokeRect(17, 20, 9, 9) sg.generateTexture('stockpile_obj', 32, 32) sg.destroy() } // ─── Terrain generation ─────────────────────────────────────────────────── private generateWorldIfNeeded(): void { const state = stateManager.getState() if (Object.keys(state.world.resources).length === 0) { const tiles = generateTerrain(state.world.seed) const mutableTiles = state.world.tiles as number[] for (let i = 0; i < tiles.length; i++) mutableTiles[i] = tiles[i] for (let y = 0; y < WORLD_TILES; y++) { for (let x = 0; x < WORLD_TILES; x++) { const tile = tiles[y * WORLD_TILES + x] if (tile === TileType.FOREST && Math.random() < 0.7) { const id = `tree_${x}_${y}` state.world.resources[id] = { id, tileX: x, tileY: y, kind: 'tree', hp: 3 } } else if (tile === TileType.ROCK && Math.random() < 0.5) { const id = `rock_${x}_${y}` state.world.resources[id] = { id, tileX: x, tileY: y, kind: 'rock', hp: 5 } } } } const spawn = findSpawn(tiles) ;(state.player as { x: number; y: number }).x = (spawn.tileX + 0.5) * 32 ;(state.player as { x: number; y: number }).y = (spawn.tileY + 0.5) * 32 } } }