fix: sleep emoji, invention history race condition, and gathering stuck state

- Add persistent 💤 emoji over sleeping NPCs (tracks sprite position)
- Buffer history events (inventions, narration, stockpile) in SocketClient
  to fix race condition where data arrived before GameScene handlers registered
- Fix NPCs getting stuck on 'gather' goal: tree trunks are both obstacles
  and resource tiles, so pathfinding always failed. Now findNearestResource
  returns adjacent walkable tiles for non-walkable resources, and
  gatheringSystem checks adjacent tiles. Added wander fallback when
  pathfinding to any gather target fails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-03-09 18:57:50 +00:00
parent 3294879bab
commit 34aca29082
4 changed files with 110 additions and 12 deletions
+5 -2
View File
@@ -57,10 +57,13 @@ export function gatheringSystem(world: World, map: GameMap): void {
continue;
}
// Not yet gathering — check if at resource tile and idle
// Not yet gathering — check if at or adjacent to resource tile and idle
if (movement.state !== 'idle') continue;
const resourceTile = map.resourceTiles.find(r => r.x === pos.x && r.y === pos.y);
const resourceTile = map.resourceTiles.find(r => r.x === pos.x && r.y === pos.y)
?? map.resourceTiles.find(r =>
Math.abs(r.x - pos.x) + Math.abs(r.y - pos.y) === 1 && !map.isWalkable(r.x, r.y)
);
if (!resourceTile) continue;
// Start gathering
+26 -6
View File
@@ -16,13 +16,22 @@ import type { PickupState } from './pickupSystem.js';
function findNearestResource(map: GameMap, from: Position): Position | null {
const tiles = map.resourceTiles;
if (tiles.length === 0) return null;
let best = tiles[0];
let bestDist = Math.abs(from.x - best.x) + Math.abs(from.y - best.y);
for (let i = 1; i < tiles.length; i++) {
const d = Math.abs(from.x - tiles[i].x) + Math.abs(from.y - tiles[i].y);
if (d < bestDist) { best = tiles[i]; bestDist = d; }
// Sort by distance so we try closest first
const sorted = [...tiles].sort((a, b) =>
(Math.abs(from.x - a.x) + Math.abs(from.y - a.y)) -
(Math.abs(from.x - b.x) + Math.abs(from.y - b.y))
);
for (const tile of sorted) {
if (map.isWalkable(tile.x, tile.y)) {
return { x: tile.x, y: tile.y };
}
// Resource is on a non-walkable tile (e.g. tree trunk) — find adjacent walkable tile
const adjacent = map.findNearestWalkable(tile.x, tile.y, 1);
if (adjacent) return adjacent;
}
return { x: best.x, y: best.y };
return null;
}
function isNighttime(gameTime: number): boolean {
@@ -196,6 +205,17 @@ export function npcBrainSystem(world: World, map: GameMap, gameTime: number, eve
movement.target = target;
movement.path = path;
movement.direction = directionTo(pos, path[0]);
} else if (goal === 'gather') {
// Can't reach any resource — fall back to wander
brain.currentGoal = 'wander';
const wanderTarget = map.getRandomWalkable();
const wanderPath = findPath(map, pos, wanderTarget);
if (wanderPath && wanderPath.length > 0) {
movement.state = 'walking';
movement.target = wanderTarget;
movement.path = wanderPath;
movement.direction = directionTo(pos, wanderPath[0]);
}
}
}
}