feat(server): add pickup system for NPC stockpile material retrieval

NPCs can now retrieve crafting materials from stockpiles instead of
always gathering raw resources. The industry system checks if a
stockpile has needed materials before falling back to gathering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-03-09 02:52:37 +00:00
parent 60c6d7808d
commit 9f77a84d6f
5 changed files with 129 additions and 1 deletions
+42
View File
@@ -7,6 +7,7 @@ import type { GameMap } from '../map/GameMap.js';
import type { GatheringState } from './gatheringSystem.js';
import type { CraftingState } from './craftingSystem.js';
import type { BuildingState, StructureData } from './buildingSystem.js';
import type { PickupState } from './pickupSystem.js';
import { RecipeRegistry } from '../industry/recipeRegistry.js';
import { getEffectiveStat } from './statHelpers.js';
import { industryConfig } from '../config/industryConfig.js';
@@ -56,6 +57,7 @@ export function industrySystem(world: World, map: GameMap): void {
if (brain.currentGoal === 'gather' && world.getComponent<GatheringState>(entity, 'gatheringState')) continue;
if (brain.currentGoal === 'gather' || brain.currentGoal === 'eat' || brain.currentGoal === 'rest') continue;
if (brain.currentGoal === 'dropoff') continue;
if (brain.currentGoal === 'pickup') continue;
const inv = world.getComponent<Map<string, number>>(entity, 'inventory')!;
const pos = world.getComponent<Position>(entity, 'position')!;
@@ -114,6 +116,46 @@ export function industrySystem(world: World, map: GameMap): void {
}
if (didBuild) continue;
// Try pickup from stockpile for crafting
let didPickup = false;
const allCraftRecipes = registry.getAll().filter(r => !r.id.startsWith('build_'));
for (const recipe of allCraftRecipes) {
const neededItems: { itemId: string; quantity: number }[] = [];
for (const input of recipe.inputs) {
const haveInInv = inv.get(input.itemId) ?? 0;
const stillNeed = input.quantity - haveInInv;
if (stillNeed > 0) {
neededItems.push({ itemId: input.itemId, quantity: stillNeed });
}
}
if (neededItems.length === 0) continue; // NPC can already craft — handled above
// Find a stockpile that has all needed items
let stockpileEntity: number | null = null;
for (const sEntity of world.query('structure', 'position')) {
const sData = world.getComponent<StructureData>(sEntity, 'structure')!;
if (sData.type !== 'stockpile' || sData.buildProgress !== undefined) continue;
const hasAll = neededItems.every(n => (sData.inventory.get(n.itemId) ?? 0) >= n.quantity);
if (hasAll) {
stockpileEntity = sEntity;
break;
}
}
if (stockpileEntity !== null) {
brain.currentGoal = 'pickup';
world.addComponent<PickupState>(entity, 'pickupState', {
stockpileEntityId: stockpileEntity,
items: neededItems,
});
didPickup = true;
break;
}
}
if (didPickup) continue;
// Fallback: gather
brain.currentGoal = 'gather';
}