From 0cf48ded616c23e1a4627841c1c2b3f5e2c97e4c Mon Sep 17 00:00:00 2001 From: root Date: Sat, 7 Mar 2026 04:33:42 +0000 Subject: [PATCH] feat: add NPC name generator and broadcast names in entity state Co-Authored-By: Claude Opus 4.6 --- server/src/game/spawner.ts | 2 ++ server/src/network/stateSerializer.ts | 1 + .../spawner/__tests__/nameGenerator.test.ts | 19 +++++++++++++++++++ server/src/spawner/nameGenerator.ts | 19 +++++++++++++++++++ shared/src/types.ts | 1 + 5 files changed, 42 insertions(+) create mode 100644 server/src/spawner/__tests__/nameGenerator.test.ts create mode 100644 server/src/spawner/nameGenerator.ts diff --git a/server/src/game/spawner.ts b/server/src/game/spawner.ts index 544044e..94c2c3e 100644 --- a/server/src/game/spawner.ts +++ b/server/src/game/spawner.ts @@ -1,6 +1,7 @@ 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 } from '@dflike/shared'; export function spawnNPC(world: World, map: GameMap): EntityId { @@ -24,6 +25,7 @@ export function spawnNPC(world: World, map: GameMap): EntityId { goalQueue: [], }); world.addComponent(entity, 'appearance', generateRandomAppearance()); + world.addComponent(entity, 'name', generateName()); return entity; } diff --git a/server/src/network/stateSerializer.ts b/server/src/network/stateSerializer.ts index aa2625f..7e2def3 100644 --- a/server/src/network/stateSerializer.ts +++ b/server/src/network/stateSerializer.ts @@ -15,6 +15,7 @@ export function serializeEntity(world: World, entityId: number): EntityState { needs: world.getComponent(entityId, 'needs'), npcBrain: world.getComponent(entityId, 'npcBrain'), playerControlled: world.getComponent(entityId, 'playerControlled'), + name: world.getComponent(entityId, 'name'), }; } diff --git a/server/src/spawner/__tests__/nameGenerator.test.ts b/server/src/spawner/__tests__/nameGenerator.test.ts new file mode 100644 index 0000000..70fd5c6 --- /dev/null +++ b/server/src/spawner/__tests__/nameGenerator.test.ts @@ -0,0 +1,19 @@ +import { describe, it, expect } from 'vitest'; +import { generateName } from '../nameGenerator.js'; + +describe('generateName', () => { + it('returns a non-empty string', () => { + const name = generateName(); + expect(name).toBeTruthy(); + expect(typeof name).toBe('string'); + expect(name.length).toBeGreaterThan(0); + }); + + it('generates varied names over many calls', () => { + const names = new Set(); + for (let i = 0; i < 30; i++) { + names.add(generateName()); + } + expect(names.size).toBeGreaterThan(5); + }); +}); diff --git a/server/src/spawner/nameGenerator.ts b/server/src/spawner/nameGenerator.ts new file mode 100644 index 0000000..91663be --- /dev/null +++ b/server/src/spawner/nameGenerator.ts @@ -0,0 +1,19 @@ +const PREFIXES = [ + 'Urist', 'Kor', 'Bel', 'Thar', 'Grim', 'Hed', 'Mog', 'Zan', + 'Dur', 'Fal', 'Nor', 'Vim', 'Erd', 'Sig', 'Bor', 'Tul', + 'Arn', 'Hild', 'Dag', 'Sven', 'Brun', 'Kel', 'Ral', 'Tor', +]; + +const SUFFIXES = [ + 'mund', 'rik', 'mir', 'gard', 'sten', 'vald', 'dak', 'fen', + 'grim', 'lok', 'nar', 'thas', 'borg', 'din', 'run', 'vik', + 'gar', 'horn', 'mar', 'wulf', 'ban', 'dor', 'kel', 'ton', +]; + +function pick(arr: T[]): T { + return arr[Math.floor(Math.random() * arr.length)]; +} + +export function generateName(): string { + return pick(PREFIXES) + pick(SUFFIXES); +} diff --git a/shared/src/types.ts b/shared/src/types.ts index aaf2be2..f8cabd8 100644 --- a/shared/src/types.ts +++ b/shared/src/types.ts @@ -55,6 +55,7 @@ export interface EntityState { needs?: Needs; npcBrain?: NPCBrain; playerControlled?: PlayerControlled; + name?: string; // NPC display name } export interface WorldState {