feat: wire stats into NPC spawner and game loop

Add stats and statModifiers components to spawnNPC, and run
statModifierSystem as the first system in the GameLoop update cycle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-03-07 14:20:43 +00:00
parent e90e69ce35
commit c5d75fa0b0
3 changed files with 20 additions and 2 deletions
+2
View File
@@ -5,6 +5,7 @@ import { needsDecaySystem } from '../systems/needsDecaySystem.js';
import { npcBrainSystem } from '../systems/npcBrainSystem.js';
import { movementSystem } from '../systems/movementSystem.js';
import { socialSystem } from '../systems/socialSystem.js';
import { statModifierSystem } from '../systems/statModifierSystem.js';
import { spawnNPC } from './spawner.js';
export class GameLoop {
@@ -61,6 +62,7 @@ export class GameLoop {
this.tick++;
// Run systems in order
statModifierSystem(this.world);
needsDecaySystem(this.world);
npcBrainSystem(this.world, this.map);
socialSystem(this.world);
+14 -1
View File
@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest';
import { World } from '../../ecs/World.js';
import { GameMap } from '../../map/GameMap.js';
import { spawnNPC } from '../spawner.js';
import type { Position } from '@dflike/shared';
import type { Position, Stats } from '@dflike/shared';
describe('spawnNPC', () => {
it('spawns at random position when no hint given', () => {
@@ -44,5 +44,18 @@ describe('spawnNPC', () => {
expect(world.getComponent(entity, 'appearance')).toBeDefined();
expect(world.getComponent(entity, 'name')).toBeDefined();
expect(world.getComponent(entity, 'socialState')).toBeDefined();
expect(world.getComponent(entity, 'stats')).toBeDefined();
expect(world.getComponent(entity, 'statModifiers')).toBeDefined();
});
it('generates stats in 3-18 range', () => {
const world = new World();
const map = new GameMap();
const entity = spawnNPC(world, map);
const stats = world.getComponent<Stats>(entity, 'stats')!;
for (const key of Object.keys(stats) as (keyof Stats)[]) {
expect(stats[key]).toBeGreaterThanOrEqual(3);
expect(stats[key]).toBeLessThanOrEqual(18);
}
});
});
+4 -1
View File
@@ -2,7 +2,8 @@ import type { World } from '../ecs/World.js';
import type { GameMap } from '../map/GameMap.js';
import { generateRandomAppearance } from '../spawner/appearanceGenerator.js';
import { generateName } from '../spawner/nameGenerator.js';
import type { EntityId, Position, Needs, Movement, NPCBrain, Appearance, SocialState } from '@dflike/shared';
import { generateStats } from '../spawner/statGenerator.js';
import type { EntityId, Position, Needs, Movement, NPCBrain, Appearance, SocialState, Stats, StatModifiers } from '@dflike/shared';
export function spawnNPC(world: World, map: GameMap, positionHint?: Position): EntityId {
const entity = world.createEntity();
@@ -36,6 +37,8 @@ export function spawnNPC(world: World, map: GameMap, positionHint?: Position): E
globalCooldown: 0,
pairCooldowns: new Map(),
});
world.addComponent<Stats>(entity, 'stats', generateStats());
world.addComponent<StatModifiers>(entity, 'statModifiers', { modifiers: [] });
return entity;
}