Merge pull request 'Fix GC-Ruckler in pickJob und tickVillager' (#35) from fix/gc-alloc-picjob into master
Reviewed-on: #35 Reviewed-by: tekki <tekki.mariani@googlemail.com>
This commit is contained in:
@@ -7,6 +7,10 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- **Y-based depth sorting** (Issue #31): trees, rocks, seedlings and buildings now use `tileY + 5` as depth instead of fixed values — objects lower on screen always render in front of objects above them, regardless of spawn order; build ghost moved to depth 1000
|
||||
- **Nisse always visible** (Issue #33): Nisse sprites fixed at depth 900, always rendered above world objects
|
||||
|
||||
### Added
|
||||
- **Försterkreislauf** (Issue #25):
|
||||
- **Setzlinge beim Fällen**: Jeder gefällte Baum gibt 1–2 `tree_seed` in den Stockpile
|
||||
|
||||
@@ -13,6 +13,9 @@ const ARRIVAL_PX = 3
|
||||
|
||||
const WORK_LOG_MAX = 20
|
||||
|
||||
/** Job-type → display icon mapping; defined once at module level to avoid per-frame allocation. */
|
||||
const JOB_ICONS: Record<string, string> = { chop: '🪓', mine: '⛏', farm: '🌾', forester: '🌲', '': '' }
|
||||
|
||||
interface VillagerRuntime {
|
||||
sprite: Phaser.GameObjects.Image
|
||||
nameLabel: Phaser.GameObjects.Text
|
||||
@@ -126,8 +129,7 @@ export class VillagerSystem {
|
||||
this.drawEnergyBar(rt.energyBar, v.x, v.y, v.energy)
|
||||
|
||||
// Job icon
|
||||
const icons: Record<string, string> = { chop: '🪓', mine: '⛏', farm: '🌾', forester: '🌲', '': '' }
|
||||
const jobType = v.aiState === 'sleeping' ? '💤' : (v.job ? (icons[v.job.type] ?? '') : '')
|
||||
const jobType = v.aiState === 'sleeping' ? '💤' : (v.job ? (JOB_ICONS[v.job.type] ?? '') : '')
|
||||
rt.jobIcon.setText(jobType).setPosition(v.x + 10, v.y - 18)
|
||||
}
|
||||
|
||||
@@ -377,20 +379,27 @@ export class VillagerSystem {
|
||||
const vTY = Math.floor(v.y / TILE_SIZE)
|
||||
const dist = (tx: number, ty: number) => Math.abs(tx - vTX) + Math.abs(ty - vTY)
|
||||
|
||||
// Extract state collections once — avoids repeated Object.values() allocation per branch/loop.
|
||||
const resources = Object.values(state.world.resources)
|
||||
const buildings = Object.values(state.world.buildings)
|
||||
const crops = Object.values(state.world.crops)
|
||||
const seedlings = Object.values(state.world.treeSeedlings)
|
||||
const zones = Object.values(state.world.foresterZones)
|
||||
|
||||
type C = { type: JobType; targetId: string; tileX: number; tileY: number; dist: number; pri: number }
|
||||
const candidates: C[] = []
|
||||
|
||||
if (p.chop > 0) {
|
||||
// Build the set of all tiles belonging to forester zones for chop priority
|
||||
const zoneTiles = new Set<string>()
|
||||
for (const zone of Object.values(state.world.foresterZones)) {
|
||||
for (const zone of zones) {
|
||||
for (const key of zone.tiles) zoneTiles.add(key)
|
||||
}
|
||||
|
||||
const zoneChop: C[] = []
|
||||
const naturalChop: C[] = []
|
||||
|
||||
for (const res of Object.values(state.world.resources)) {
|
||||
for (const res of resources) {
|
||||
if (res.kind !== 'tree' || this.claimed.has(res.id)) continue
|
||||
// Skip trees with no reachable neighbour — A* cannot reach them.
|
||||
if (!this.hasAdjacentPassable(res.tileX, res.tileY)) continue
|
||||
@@ -406,7 +415,7 @@ export class VillagerSystem {
|
||||
}
|
||||
|
||||
if (p.mine > 0) {
|
||||
for (const res of Object.values(state.world.resources)) {
|
||||
for (const res of resources) {
|
||||
if (res.kind !== 'rock' || this.claimed.has(res.id)) continue
|
||||
// Same reachability guard for rock tiles.
|
||||
if (!this.hasAdjacentPassable(res.tileX, res.tileY)) continue
|
||||
@@ -415,7 +424,7 @@ export class VillagerSystem {
|
||||
}
|
||||
|
||||
if (p.farm > 0) {
|
||||
for (const crop of Object.values(state.world.crops)) {
|
||||
for (const crop of crops) {
|
||||
if (crop.stage < crop.maxStage || this.claimed.has(crop.id)) continue
|
||||
candidates.push({ type: 'farm', targetId: crop.id, tileX: crop.tileX, tileY: crop.tileY, dist: dist(crop.tileX, crop.tileY), pri: p.farm })
|
||||
}
|
||||
@@ -423,7 +432,7 @@ export class VillagerSystem {
|
||||
|
||||
if (p.forester > 0 && (state.world.stockpile.tree_seed ?? 0) > 0) {
|
||||
// Find empty plantable zone tiles to seed
|
||||
for (const zone of Object.values(state.world.foresterZones)) {
|
||||
for (const zone of zones) {
|
||||
for (const key of zone.tiles) {
|
||||
const [tx, ty] = key.split(',').map(Number)
|
||||
const targetId = `forester_tile_${tx}_${ty}`
|
||||
@@ -431,12 +440,12 @@ export class VillagerSystem {
|
||||
// Skip if tile is not plantable
|
||||
const tileType = state.world.tiles[ty * WORLD_TILES + tx] as TileType
|
||||
if (!PLANTABLE_TILES.has(tileType)) continue
|
||||
// Skip if something occupies this tile
|
||||
// Skip if something occupies this tile — reuse already-extracted arrays
|
||||
const occupied =
|
||||
Object.values(state.world.resources).some(r => r.tileX === tx && r.tileY === ty) ||
|
||||
Object.values(state.world.buildings).some(b => b.tileX === tx && b.tileY === ty) ||
|
||||
Object.values(state.world.crops).some(c => c.tileX === tx && c.tileY === ty) ||
|
||||
Object.values(state.world.treeSeedlings).some(s => s.tileX === tx && s.tileY === ty)
|
||||
resources.some(r => r.tileX === tx && r.tileY === ty) ||
|
||||
buildings.some(b => b.tileX === tx && b.tileY === ty) ||
|
||||
crops.some(c => c.tileX === tx && c.tileY === ty) ||
|
||||
seedlings.some(s => s.tileX === tx && s.tileY === ty)
|
||||
if (occupied) continue
|
||||
candidates.push({ type: 'forester', targetId, tileX: tx, tileY: ty, dist: dist(tx, ty), pri: p.forester })
|
||||
}
|
||||
@@ -445,8 +454,9 @@ export class VillagerSystem {
|
||||
|
||||
if (candidates.length === 0) return null
|
||||
|
||||
// Lowest priority number wins; ties broken by distance
|
||||
const bestPri = Math.min(...candidates.map(c => c.pri))
|
||||
// Lowest priority number wins; ties broken by distance — avoid spread+map allocation
|
||||
let bestPri = candidates[0].pri
|
||||
for (let i = 1; i < candidates.length; i++) if (candidates[i].pri < bestPri) bestPri = candidates[i].pri
|
||||
return candidates
|
||||
.filter(c => c.pri === bestPri)
|
||||
.sort((a, b) => a.dist - b.dist)[0] ?? null
|
||||
|
||||
Reference in New Issue
Block a user