feat: add social interaction guards to brain and movement systems
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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<SocialState>(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<NPCBrain>(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<NPCBrain>(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<Position>(e, 'position', { x: 0, y: 0 });
|
||||
world.addComponent<Movement>(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<Position>(e, 'position')!;
|
||||
expect(pos).toEqual({ x: 0, y: 0 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<Movement>(entity, 'movement')!;
|
||||
const pos = world.getComponent<Position>(entity, 'position')!;
|
||||
|
||||
const socialState = world.getComponent<SocialState>(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';
|
||||
|
||||
@@ -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<Movement>(entity, 'movement')!;
|
||||
const pos = world.getComponent<Position>(entity, 'position')!;
|
||||
|
||||
const socialState = world.getComponent<SocialState>(entity, 'socialState');
|
||||
if (socialState && socialState.phase !== 'none') continue;
|
||||
|
||||
// Skip if currently walking toward a goal
|
||||
if (movement.state === 'walking' && movement.path.length > 0) continue;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user