diff --git a/server/src/systems/__tests__/systems.test.ts b/server/src/systems/__tests__/systems.test.ts index d6ed3c8..4a9581f 100644 --- a/server/src/systems/__tests__/systems.test.ts +++ b/server/src/systems/__tests__/systems.test.ts @@ -4,7 +4,7 @@ import { GameMap } from '../../map/GameMap.js'; import { needsDecaySystem } from '../needsDecaySystem.js'; import { npcBrainSystem } from '../npcBrainSystem.js'; import { movementSystem } from '../movementSystem.js'; -import type { Needs, Movement, NPCBrain, Position } from '@dflike/shared'; +import type { Needs, Movement, NPCBrain, Position, SocialState } from '@dflike/shared'; import { HUNGER_DECAY_PER_TICK, ENERGY_DECAY_PER_TICK, MOVE_SPEED } from '@dflike/shared'; function createNPC(world: World, x: number, y: number, hunger = 80, energy = 80) { @@ -16,6 +16,17 @@ function createNPC(world: World, x: number, y: number, hunger = 80, energy = 80) return e; } +function addSocialState(world: World, entity: number, phase: SocialState['phase'] = 'none') { + world.addComponent(entity, 'socialState', { + phase, + partnerId: null, + phaseTimer: 10, + outcome: null, + globalCooldown: 0, + pairCooldowns: new Map(), + }); +} + describe('needsDecaySystem', () => { it('decays hunger and energy each tick', () => { const world = new World(); @@ -76,6 +87,17 @@ describe('npcBrainSystem', () => { const brain = world.getComponent(e, 'npcBrain')!; expect(brain.currentGoal).toBe('rest'); }); + + it('skips NPC when socialState phase is not none', () => { + const world = new World(); + const map = new GameMap(10, 10); + const e = createNPC(world, 5, 5, 80, 80); + addSocialState(world, e, 'facing'); + const brain = world.getComponent(e, 'npcBrain')!; + brain.currentGoal = null; + npcBrainSystem(world, map); + expect(brain.currentGoal).toBeNull(); + }); }); describe('movementSystem', () => { @@ -166,4 +188,21 @@ describe('movementSystem', () => { expect(mov.state).toBe('idle'); expect(mov.moveProgress).toBe(0); }); + + it('skips entity when socialState phase is not none', () => { + const world = new World(); + const e = world.createEntity(); + world.addComponent(e, 'position', { x: 0, y: 0 }); + world.addComponent(e, 'movement', { + state: 'walking', + target: { x: 3, y: 0 }, + path: [{ x: 1, y: 0 }, { x: 2, y: 0 }, { x: 3, y: 0 }], + direction: 2, + moveProgress: 0.75, + }); + addSocialState(world, e, 'pausing'); + movementSystem(world); + const pos = world.getComponent(e, 'position')!; + expect(pos).toEqual({ x: 0, y: 0 }); + }); }); diff --git a/server/src/systems/movementSystem.ts b/server/src/systems/movementSystem.ts index b20a375..4df3f41 100644 --- a/server/src/systems/movementSystem.ts +++ b/server/src/systems/movementSystem.ts @@ -1,4 +1,4 @@ -import { Direction, MOVE_SPEED, type Movement, type Position } from '@dflike/shared'; +import { Direction, MOVE_SPEED, type Movement, type Position, type SocialState } from '@dflike/shared'; import type { World } from '../ecs/World.js'; function directionFromDelta(dx: number, dy: number): number { @@ -11,6 +11,9 @@ export function movementSystem(world: World): void { const movement = world.getComponent(entity, 'movement')!; const pos = world.getComponent(entity, 'position')!; + const socialState = world.getComponent(entity, 'socialState'); + if (socialState && socialState.phase !== 'none') continue; + if (movement.state !== 'walking' || movement.path.length === 0) { if (movement.state === 'walking' && movement.path.length === 0) { movement.state = 'idle'; diff --git a/server/src/systems/npcBrainSystem.ts b/server/src/systems/npcBrainSystem.ts index 068ade7..edfe0ad 100644 --- a/server/src/systems/npcBrainSystem.ts +++ b/server/src/systems/npcBrainSystem.ts @@ -1,6 +1,6 @@ import { HUNGER_THRESHOLD, ENERGY_THRESHOLD, Direction, - type Needs, type Movement, type NPCBrain, type Position, + type Needs, type Movement, type NPCBrain, type Position, type SocialState, } from '@dflike/shared'; import type { World } from '../ecs/World.js'; import type { GameMap } from '../map/GameMap.js'; @@ -32,6 +32,9 @@ export function npcBrainSystem(world: World, map: GameMap): void { const movement = world.getComponent(entity, 'movement')!; const pos = world.getComponent(entity, 'position')!; + const socialState = world.getComponent(entity, 'socialState'); + if (socialState && socialState.phase !== 'none') continue; + // Skip if currently walking toward a goal if (movement.state === 'walking' && movement.path.length > 0) continue;