feat: berry bush depletion on gather and regrowth over time
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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<Position>(e, 'position', { x, y });
|
||||
world.addComponent<Needs>(e, 'needs', { hunger: 80, energy: 80, productivity: overrides?.productivity ?? 30 });
|
||||
world.addComponent<Needs>(e, 'needs', { hunger: 80, thirst: 80, energy: 80, productivity: overrides?.productivity ?? 30 });
|
||||
world.addComponent<Movement>(e, 'movement', { state: 'idle', target: null, path: [], direction: 0, moveProgress: 0 });
|
||||
world.addComponent<NPCBrain>(e, 'npcBrain', { currentGoal: 'gather', goalQueue: [] });
|
||||
world.addComponent<Map<string, number>>(e, 'inventory', new Map());
|
||||
@@ -191,4 +192,44 @@ describe('gatheringSystem', () => {
|
||||
const needs = world.getComponent<Needs>(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<GatheringState>(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<GatheringState>(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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<NPCBrain>(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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user