import Phaser from 'phaser' import { TILE_SIZE, WORLD_TILES } from '../config' import { TileType, IMPASSABLE } from '../types' import { stateManager } from '../StateManager' const BIOME_COLORS: Record = { 0: '#1565C0', // DEEP_WATER 1: '#42A5F5', // SHALLOW_WATER 2: '#F5DEB3', // SAND 3: '#66BB6A', // GRASS 4: '#43A047', // DARK_GRASS 5: '#33691E', // FOREST 6: '#616161', // ROCK // Built types: show grass below 7: '#66BB6A', 8: '#66BB6A', 9: '#66BB6A', 10: '#66BB6A', } export class WorldSystem { private scene: Phaser.Scene private map!: Phaser.Tilemaps.Tilemap private tileset!: Phaser.Tilemaps.Tileset private bgImage!: Phaser.GameObjects.Image private builtLayer!: Phaser.Tilemaps.TilemapLayer constructor(scene: Phaser.Scene) { this.scene = scene } create(): void { const state = stateManager.getState() // --- Canvas background (1px per tile, scaled up, LINEAR filtered) --- const canvas = document.createElement('canvas') canvas.width = WORLD_TILES canvas.height = WORLD_TILES const ctx = canvas.getContext('2d')! for (let y = 0; y < WORLD_TILES; y++) { for (let x = 0; x < WORLD_TILES; x++) { const tile = state.world.tiles[y * WORLD_TILES + x] ctx.fillStyle = BIOME_COLORS[tile] ?? '#0a2210' ctx.fillRect(x, y, 1, 1) } } this.scene.textures.addCanvas('terrain_bg', canvas) this.bgImage = this.scene.add.image(0, 0, 'terrain_bg') .setOrigin(0, 0) .setScale(TILE_SIZE) .setDepth(0) this.scene.textures.get('terrain_bg').setFilter(Phaser.Textures.FilterMode.LINEAR) // --- Built tile layer (sparse — only FLOOR, WALL, TILLED_SOIL, WATERED_SOIL) --- this.map = this.scene.make.tilemap({ tileWidth: TILE_SIZE, tileHeight: TILE_SIZE, width: WORLD_TILES, height: WORLD_TILES, }) const ts = this.map.addTilesetImage('tiles', 'tiles', TILE_SIZE, TILE_SIZE, 0, 0, 0) if (!ts) throw new Error('Failed to add tileset') this.tileset = ts const layer = this.map.createBlankLayer('built', this.tileset, 0, 0) if (!layer) throw new Error('Failed to create built layer') this.builtLayer = layer this.builtLayer.setDepth(1) const BUILT_TILES = new Set([7, 8, 9, 10]) // FLOOR, WALL, TILLED_SOIL, WATERED_SOIL for (let y = 0; y < WORLD_TILES; y++) { for (let x = 0; x < WORLD_TILES; x++) { const t = state.world.tiles[y * WORLD_TILES + x] if (BUILT_TILES.has(t)) { this.builtLayer.putTileAt(t, x, y) } } } // Camera bounds this.scene.cameras.main.setBounds(0, 0, WORLD_TILES * TILE_SIZE, WORLD_TILES * TILE_SIZE) } getLayer(): Phaser.Tilemaps.TilemapLayer { return this.builtLayer } setTile(tileX: number, tileY: number, type: TileType): void { const BUILT_TILES = new Set([TileType.FLOOR, TileType.WALL, TileType.TILLED_SOIL, TileType.WATERED_SOIL]) if (BUILT_TILES.has(type)) { this.builtLayer.putTileAt(type, tileX, tileY) } else { // Reverting to natural: remove from built layer this.builtLayer.removeTileAt(tileX, tileY) } } isPassable(tileX: number, tileY: number): boolean { if (tileX < 0 || tileY < 0 || tileX >= WORLD_TILES || tileY >= WORLD_TILES) return false const state = stateManager.getState() const tile = state.world.tiles[tileY * WORLD_TILES + tileX] return !IMPASSABLE.has(tile) } worldToTile(worldX: number, worldY: number): { tileX: number; tileY: number } { return { tileX: Math.floor(worldX / TILE_SIZE), tileY: Math.floor(worldY / TILE_SIZE), } } tileToWorld(tileX: number, tileY: number): { x: number; y: number } { return { x: tileX * TILE_SIZE + TILE_SIZE / 2, y: tileY * TILE_SIZE + TILE_SIZE / 2, } } getTileType(tileX: number, tileY: number): TileType { const state = stateManager.getState() return state.world.tiles[tileY * WORLD_TILES + tileX] as TileType } destroy(): void { this.map.destroy() this.bgImage.destroy() } }