🐛 Skip unreachable job targets in pickJob #23
@@ -351,12 +351,17 @@ export class VillagerSystem {
|
|||||||
if (p.chop > 0) {
|
if (p.chop > 0) {
|
||||||
for (const res of Object.values(state.world.resources)) {
|
for (const res of Object.values(state.world.resources)) {
|
||||||
if (res.kind !== 'tree' || this.claimed.has(res.id)) continue
|
if (res.kind !== 'tree' || this.claimed.has(res.id)) continue
|
||||||
|
// Skip trees with no reachable neighbour — A* cannot enter an impassable goal
|
||||||
|
// tile unless at least one passable neighbour exists to jump from.
|
||||||
|
if (!this.hasAdjacentPassable(res.tileX, res.tileY)) continue
|
||||||
candidates.push({ type: 'chop', targetId: res.id, tileX: res.tileX, tileY: res.tileY, dist: dist(res.tileX, res.tileY), pri: p.chop })
|
candidates.push({ type: 'chop', targetId: res.id, tileX: res.tileX, tileY: res.tileY, dist: dist(res.tileX, res.tileY), pri: p.chop })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (p.mine > 0) {
|
if (p.mine > 0) {
|
||||||
for (const res of Object.values(state.world.resources)) {
|
for (const res of Object.values(state.world.resources)) {
|
||||||
if (res.kind !== 'rock' || this.claimed.has(res.id)) continue
|
if (res.kind !== 'rock' || this.claimed.has(res.id)) continue
|
||||||
|
// Same reachability guard for rock tiles.
|
||||||
|
if (!this.hasAdjacentPassable(res.tileX, res.tileY)) continue
|
||||||
candidates.push({ type: 'mine', targetId: res.id, tileX: res.tileX, tileY: res.tileY, dist: dist(res.tileX, res.tileY), pri: p.mine })
|
candidates.push({ type: 'mine', targetId: res.id, tileX: res.tileX, tileY: res.tileY, dist: dist(res.tileX, res.tileY), pri: p.mine })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -397,7 +402,7 @@ export class VillagerSystem {
|
|||||||
this.claimed.delete(v.job.targetId)
|
this.claimed.delete(v.job.targetId)
|
||||||
this.adapter.send({ type: 'VILLAGER_SET_JOB', villagerId: v.id, job: null })
|
this.adapter.send({ type: 'VILLAGER_SET_JOB', villagerId: v.id, job: null })
|
||||||
}
|
}
|
||||||
rt.idleScanTimer = 1500 // longer delay after failed pathfind
|
rt.idleScanTimer = 4000 // longer delay after failed pathfind to avoid tight retry loops
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,6 +439,22 @@ export class VillagerSystem {
|
|||||||
return this.nearestBuilding(v, 'bed') as any
|
return this.nearestBuilding(v, 'bed') as any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if at least one of the 8 neighbours of the given tile is passable.
|
||||||
|
* Used to pre-filter job targets that are fully enclosed by impassable terrain —
|
||||||
|
* such as trees deep inside a dense forest cluster where A* can never reach the goal
|
||||||
|
* tile because no passable tile is adjacent to it.
|
||||||
|
* @param tileX - Target tile X
|
||||||
|
* @param tileY - Target tile Y
|
||||||
|
*/
|
||||||
|
private hasAdjacentPassable(tileX: number, tileY: number): boolean {
|
||||||
|
const DIRS = [[1,0],[-1,0],[0,1],[0,-1],[1,1],[1,-1],[-1,1],[-1,-1]] as const
|
||||||
|
for (const [dx, dy] of DIRS) {
|
||||||
|
if (this.worldSystem.isPassable(tileX + dx, tileY + dy)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Spawning ─────────────────────────────────────────────────────────────
|
// ─── Spawning ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user