implement unified tile system (Issue #14)

- Tree seedlings: plant tree_seed on grass via farming tool; two-stage
  growth (sprout → sapling → young tree, ~1 min/stage); matures into
  a harvestable FOREST resource tile
- Tile recovery: Nisse chops start a 5-min DARK_GRASS→GRASS timer;
  terrain canvas updated live via WorldSystem.refreshTerrainTile()
- New TreeSeedlingSystem manages sprites, growth ticking, maturation
- BootScene generates seedling_0/1/2 textures procedurally
- FarmingSystem adds tree_seed to tool cycle (F key)
- Stockpile panel shows tree_seed (default: 5); panel height adjusted
- StateManager v5: treeSeedlings + tileRecovery in WorldState
- WorldSystem uses CanvasTexture for live single-pixel updates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 16:15:21 +00:00
parent bbbb3e1f58
commit 18c8ccb644
12 changed files with 375 additions and 28 deletions

View File

@@ -5,15 +5,16 @@ import type { CropKind, CropState, ItemId } from '../types'
import { stateManager } from '../StateManager'
import type { LocalAdapter } from '../NetworkAdapter'
export type FarmingTool = 'none' | 'hoe' | 'wheat_seed' | 'carrot_seed' | 'water'
export type FarmingTool = 'none' | 'hoe' | 'wheat_seed' | 'carrot_seed' | 'tree_seed' | 'water'
const TOOL_CYCLE: FarmingTool[] = ['none', 'hoe', 'wheat_seed', 'carrot_seed', 'water']
const TOOL_CYCLE: FarmingTool[] = ['none', 'hoe', 'wheat_seed', 'carrot_seed', 'tree_seed', 'water']
const TOOL_LABELS: Record<FarmingTool, string> = {
none: '— None',
hoe: '⛏ Hoe (till grass)',
wheat_seed: '🌾 Wheat Seeds',
carrot_seed: '🥕 Carrot Seeds',
tree_seed: '🌲 Tree Seeds (plant on grass)',
water: '💧 Watering Can',
}
@@ -30,6 +31,14 @@ export class FarmingSystem {
onToolChange?: (tool: FarmingTool, label: string) => void
/** Emitted for toast notifications */
onMessage?: (msg: string) => void
/**
* Called when the player uses the tree_seed tool on a tile.
* @param tileX - Target tile column
* @param tileY - Target tile row
* @param underlyingTile - The tile type at that position
* @returns true if planting succeeded, false if validation failed
*/
onPlantTreeSeed?: (tileX: number, tileY: number, underlyingTile: TileType) => boolean
constructor(scene: Phaser.Scene, adapter: LocalAdapter) {
this.scene = scene
@@ -87,9 +96,27 @@ export class FarmingSystem {
const state = stateManager.getState()
const tile = state.world.tiles[tileY * 512 + tileX] as TileType
if (this.currentTool === 'hoe') this.tillSoil(tileX, tileY, tile)
else if (this.currentTool === 'water') this.waterTile(tileX, tileY, tile)
else this.plantCrop(tileX, tileY, tile, this.currentTool.replace('_seed', '') as CropKind)
if (this.currentTool === 'hoe') this.tillSoil(tileX, tileY, tile)
else if (this.currentTool === 'water') this.waterTile(tileX, tileY, tile)
else if (this.currentTool === 'tree_seed') this.plantTreeSeed(tileX, tileY, tile)
else this.plantCrop(tileX, tileY, tile, this.currentTool.replace('_seed', '') as CropKind)
}
/**
* Delegates tree-seedling planting to the registered callback (TreeSeedlingSystem).
* Only works on GRASS or DARK_GRASS tiles. Shows a toast on success or failure.
* @param tileX - Target tile column
* @param tileY - Target tile row
* @param tile - Current tile type at that position
*/
private plantTreeSeed(tileX: number, tileY: number, tile: TileType): void {
if (tile !== TileType.GRASS && tile !== TileType.DARK_GRASS) {
this.onMessage?.('Plant tree seeds on grass!')
return
}
const ok = this.onPlantTreeSeed?.(tileX, tileY, tile)
if (ok === false) this.onMessage?.('No tree seeds, or tile is occupied!')
else if (ok) this.onMessage?.('Tree seed planted! 🌱 (~2 min to grow)')
}
private tillSoil(tileX: number, tileY: number, tile: TileType): void {