- Gefällter Baum → 1–2 tree_seed im Stockpile (zufällig) - Neues Gebäude forester_hut (50 wood): Log-Hütten-Grafik, Klick öffnet Info-Panel - Zonenmarkierung: Edit-Zone-Tool, Radius 5 Tiles, halbtransparente Overlay-Anzeige - Neuer JobType 'forester': Nisse pflanzen Setzlinge auf markierten Zonen-Tiles - Chop-Priorisierung: Zonen-Bäume werden vor natürlichen Bäumen gefällt - Nisse-Panel & Info-Panel zeigen forester-Priorität-Button Closes #25 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
195 lines
6.6 KiB
TypeScript
195 lines
6.6 KiB
TypeScript
import Phaser from 'phaser'
|
|
import { TILE_SIZE, FORESTER_ZONE_RADIUS } from '../config'
|
|
import { PLANTABLE_TILES } from '../types'
|
|
import type { TileType } from '../types'
|
|
import { stateManager } from '../StateManager'
|
|
import type { LocalAdapter } from '../NetworkAdapter'
|
|
|
|
/** Colors used for zone rendering. */
|
|
const COLOR_IN_RADIUS = 0x44aa44 // unselected tile within radius (edit mode only)
|
|
const COLOR_ZONE_TILE = 0x00ff44 // tile marked as part of the zone
|
|
const ALPHA_VIEW = 0.18 // always-on zone overlay
|
|
const ALPHA_RADIUS = 0.12 // in-radius tiles while editing
|
|
const ALPHA_ZONE_EDIT = 0.45 // zone tiles while editing
|
|
|
|
export class ForesterZoneSystem {
|
|
private scene: Phaser.Scene
|
|
private adapter: LocalAdapter
|
|
|
|
/** Graphics layer for the always-visible zone overlay. */
|
|
private zoneGraphics!: Phaser.GameObjects.Graphics
|
|
/** Graphics layer for the edit-mode radius/tile overlay. */
|
|
private editGraphics!: Phaser.GameObjects.Graphics
|
|
|
|
/** Building ID currently being edited, or null when not in edit mode. */
|
|
private editBuildingId: string | null = null
|
|
|
|
/**
|
|
* Callback invoked after a tile toggle so callers can react (e.g. refresh the panel).
|
|
* Receives the updated zone tiles array.
|
|
*/
|
|
onZoneChanged?: (buildingId: string, tiles: string[]) => void
|
|
|
|
/**
|
|
* Callback invoked when the user exits edit mode (right-click or programmatic close).
|
|
* UIScene listens to this to close the zone edit indicator.
|
|
*/
|
|
onEditEnded?: () => void
|
|
|
|
/**
|
|
* @param scene - The Phaser scene this system belongs to
|
|
* @param adapter - Network adapter for dispatching state actions
|
|
*/
|
|
constructor(scene: Phaser.Scene, adapter: LocalAdapter) {
|
|
this.scene = scene
|
|
this.adapter = adapter
|
|
}
|
|
|
|
/** Creates the graphics layers and registers the pointer listener. */
|
|
create(): void {
|
|
this.zoneGraphics = this.scene.add.graphics().setDepth(3)
|
|
this.editGraphics = this.scene.add.graphics().setDepth(4)
|
|
|
|
this.scene.input.on('pointerdown', (ptr: Phaser.Input.Pointer) => {
|
|
if (!this.editBuildingId) return
|
|
if (ptr.rightButtonDown()) {
|
|
this.exitEditMode()
|
|
return
|
|
}
|
|
this.handleTileClick(ptr.worldX, ptr.worldY)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Redraws all zone overlays for every forester hut in the current state.
|
|
* Should be called whenever the zone data changes.
|
|
*/
|
|
refreshOverlay(): void {
|
|
this.zoneGraphics.clear()
|
|
const state = stateManager.getState()
|
|
for (const zone of Object.values(state.world.foresterZones)) {
|
|
for (const key of zone.tiles) {
|
|
const [tx, ty] = key.split(',').map(Number)
|
|
this.zoneGraphics.fillStyle(COLOR_ZONE_TILE, ALPHA_VIEW)
|
|
this.zoneGraphics.fillRect(tx * TILE_SIZE, ty * TILE_SIZE, TILE_SIZE, TILE_SIZE)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Activates zone-editing mode for the given forester hut.
|
|
* Draws the radius indicator and zone tiles in edit colors.
|
|
* @param buildingId - ID of the forester_hut building to edit
|
|
*/
|
|
startEditMode(buildingId: string): void {
|
|
this.editBuildingId = buildingId
|
|
this.drawEditOverlay()
|
|
}
|
|
|
|
/**
|
|
* Deactivates zone-editing mode and clears the edit overlay.
|
|
* Triggers the onEditEnded callback.
|
|
*/
|
|
exitEditMode(): void {
|
|
if (!this.editBuildingId) return
|
|
this.editBuildingId = null
|
|
this.editGraphics.clear()
|
|
this.onEditEnded?.()
|
|
}
|
|
|
|
/** Returns true when the zone editor is currently active. */
|
|
isEditing(): boolean {
|
|
return this.editBuildingId !== null
|
|
}
|
|
|
|
/** Destroys all graphics objects. */
|
|
destroy(): void {
|
|
this.zoneGraphics.destroy()
|
|
this.editGraphics.destroy()
|
|
}
|
|
|
|
// ─── Private helpers ──────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Handles a left-click during edit mode.
|
|
* Toggles the clicked tile in the zone if it is within radius and plantable.
|
|
* @param worldX - World pixel X of the pointer
|
|
* @param worldY - World pixel Y of the pointer
|
|
*/
|
|
private handleTileClick(worldX: number, worldY: number): void {
|
|
const id = this.editBuildingId
|
|
if (!id) return
|
|
|
|
const state = stateManager.getState()
|
|
const building = state.world.buildings[id]
|
|
if (!building) { this.exitEditMode(); return }
|
|
|
|
const tileX = Math.floor(worldX / TILE_SIZE)
|
|
const tileY = Math.floor(worldY / TILE_SIZE)
|
|
|
|
// Chebyshev distance — must be within radius
|
|
const dx = Math.abs(tileX - building.tileX)
|
|
const dy = Math.abs(tileY - building.tileY)
|
|
if (Math.max(dx, dy) > FORESTER_ZONE_RADIUS) return
|
|
|
|
const zone = state.world.foresterZones[id]
|
|
if (!zone) return
|
|
|
|
const key = `${tileX},${tileY}`
|
|
const idx = zone.tiles.indexOf(key)
|
|
const tiles = idx >= 0
|
|
? zone.tiles.filter(t => t !== key) // remove
|
|
: [...zone.tiles, key] // add
|
|
|
|
this.adapter.send({ type: 'FORESTER_ZONE_UPDATE', buildingId: id, tiles })
|
|
this.refreshOverlay()
|
|
this.drawEditOverlay()
|
|
this.onZoneChanged?.(id, tiles)
|
|
}
|
|
|
|
/**
|
|
* Redraws the edit-mode overlay showing the valid radius and current zone tiles.
|
|
* Only called while editBuildingId is set.
|
|
*/
|
|
private drawEditOverlay(): void {
|
|
this.editGraphics.clear()
|
|
const id = this.editBuildingId
|
|
if (!id) return
|
|
|
|
const state = stateManager.getState()
|
|
const building = state.world.buildings[id]
|
|
if (!building) return
|
|
|
|
const zone = state.world.foresterZones[id]
|
|
const zoneSet = new Set(zone?.tiles ?? [])
|
|
const r = FORESTER_ZONE_RADIUS
|
|
|
|
for (let dy = -r; dy <= r; dy++) {
|
|
for (let dx = -r; dx <= r; dx++) {
|
|
const tx = building.tileX + dx
|
|
const ty = building.tileY + dy
|
|
const key = `${tx},${ty}`
|
|
|
|
// Only draw on plantable terrain
|
|
const tileType = state.world.tiles[ty * 512 + tx] as TileType
|
|
if (!PLANTABLE_TILES.has(tileType)) continue
|
|
|
|
if (zoneSet.has(key)) {
|
|
this.editGraphics.fillStyle(COLOR_ZONE_TILE, ALPHA_ZONE_EDIT)
|
|
this.editGraphics.strokeRect(tx * TILE_SIZE, ty * TILE_SIZE, TILE_SIZE, TILE_SIZE)
|
|
} else {
|
|
this.editGraphics.fillStyle(COLOR_IN_RADIUS, ALPHA_RADIUS)
|
|
}
|
|
this.editGraphics.fillRect(tx * TILE_SIZE, ty * TILE_SIZE, TILE_SIZE, TILE_SIZE)
|
|
}
|
|
}
|
|
|
|
// Draw a subtle border around the entire radius square
|
|
const bx = (building.tileX - r) * TILE_SIZE
|
|
const by = (building.tileY - r) * TILE_SIZE
|
|
const bw = (2 * r + 1) * TILE_SIZE
|
|
this.editGraphics.lineStyle(1, COLOR_ZONE_TILE, 0.4)
|
|
this.editGraphics.strokeRect(bx, by, bw, bw)
|
|
}
|
|
}
|