diff --git a/server/src/systems/__tests__/gatheringSystem.test.ts b/server/src/systems/__tests__/gatheringSystem.test.ts index 18e0503..0017798 100644 --- a/server/src/systems/__tests__/gatheringSystem.test.ts +++ b/server/src/systems/__tests__/gatheringSystem.test.ts @@ -6,13 +6,14 @@ import type { Needs, Movement, NPCBrain, Position, Stats, StatModifiers } from ' import type { StructureData } from '../buildingSystem.js'; import { PRODUCTIVITY_RECOVERY_RATE } from '@dflike/shared'; import { createRuntimeConstants } from '../../config/runtimeConstants.js'; +import { industryConfig } from '../../config/industryConfig.js'; const rc = createRuntimeConstants(); function createGatheringNPC(world: World, x: number, y: number, overrides?: { strength?: number; productivity?: number }) { const e = world.createEntity(); world.addComponent(e, 'position', { x, y }); - world.addComponent(e, 'needs', { hunger: 80, energy: 80, productivity: overrides?.productivity ?? 30 }); + world.addComponent(e, 'needs', { hunger: 80, thirst: 80, energy: 80, productivity: overrides?.productivity ?? 30 }); world.addComponent(e, 'movement', { state: 'idle', target: null, path: [], direction: 0, moveProgress: 0 }); world.addComponent(e, 'npcBrain', { currentGoal: 'gather', goalQueue: [] }); world.addComponent>(e, 'inventory', new Map()); @@ -191,4 +192,44 @@ describe('gatheringSystem', () => { const needs = world.getComponent(e, 'needs')!; expect(needs.productivity).toBeGreaterThan(30); }); + + it('depletes berry bush on gather completion', () => { + const world = new World(); + const map = new GameMap(10, 10); + map.resourceTiles = [{ x: 3, y: 3, resourceType: 'berries' }]; + map.initBerryBush(3, 3, 5); + const e = createGatheringNPC(world, 3, 3); + // Start gathering + gatheringSystem(world, map, rc); + const gs = world.getComponent(e, 'gatheringState')!; + expect(gs).toBeDefined(); + // Fast-forward to completion + gs.ticksRemaining = 1; + gatheringSystem(world, map, rc); + expect(map.getBerryBush(3, 3)!.berryCount).toBe(4); + }); + + it('skips depleted berry bush when starting gather', () => { + const world = new World(); + const map = new GameMap(10, 10); + map.resourceTiles = [{ x: 3, y: 3, resourceType: 'berries' }]; + map.initBerryBush(3, 3, 5); + map.getBerryBush(3, 3)!.berryCount = 0; + const e = createGatheringNPC(world, 3, 3); + gatheringSystem(world, map, rc); + const gs = world.getComponent(e, 'gatheringState'); + expect(gs).toBeUndefined(); + }); + + it('regrows berries over time', () => { + const world = new World(); + const map = new GameMap(10, 10); + map.initBerryBush(3, 3, 5); + map.getBerryBush(3, 3)!.berryCount = 3; + // Simulate regrowth ticks + for (let i = 0; i < industryConfig.berryRegrowthTicks; i++) { + gatheringSystem(world, map, rc); + } + expect(map.getBerryBush(3, 3)!.berryCount).toBe(4); + }); }); diff --git a/server/src/systems/gatheringSystem.ts b/server/src/systems/gatheringSystem.ts index c8dec60..51d9775 100644 --- a/server/src/systems/gatheringSystem.ts +++ b/server/src/systems/gatheringSystem.ts @@ -13,6 +13,17 @@ export interface GatheringState { } export function gatheringSystem(world: World, map: GameMap, rc: RuntimeConstants): void { + // Berry bush regrowth + for (const [, bush] of map.berryBushes) { + if (bush.berryCount < bush.maxBerries) { + bush.ticksSinceLastRegrowth++; + if (bush.ticksSinceLastRegrowth >= industryConfig.berryRegrowthTicks) { + bush.berryCount++; + bush.ticksSinceLastRegrowth = 0; + } + } + } + for (const entity of world.query('npcBrain', 'position', 'needs', 'movement')) { const brain = world.getComponent(entity, 'npcBrain')!; @@ -34,6 +45,12 @@ export function gatheringSystem(world: World, map: GameMap, rc: RuntimeConstants if (inv) { addItem(inv, gs.resourceType, industryConfig.gatherYield); } + // Deplete berry bush + if (gs.resourceType === 'berries') { + const bush = map.getBerryBush(pos.x, pos.y); + if (bush) bush.berryCount = Math.max(0, bush.berryCount - 1); + } + world.removeComponent(entity, 'gatheringState'); // Decide next action: drop off if at carry capacity @@ -58,9 +75,14 @@ export function gatheringSystem(world: World, map: GameMap, rc: RuntimeConstants // 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 hasBerries = (tile: { x: number; y: number; resourceType: string }) => { + if (tile.resourceType !== 'berries') return true; + const bush = map.getBerryBush(tile.x, tile.y); + return bush != null && bush.berryCount > 0; + }; + const resourceTile = map.resourceTiles.find(r => r.x === pos.x && r.y === pos.y && hasBerries(r)) ?? map.resourceTiles.find(r => - Math.abs(r.x - pos.x) + Math.abs(r.y - pos.y) === 1 && !map.isWalkable(r.x, r.y) + Math.abs(r.x - pos.x) + Math.abs(r.y - pos.y) === 1 && !map.isWalkable(r.x, r.y) && hasBerries(r) ); if (!resourceTile) continue;