Files
dflike/server/src/network/stateSerializer.ts
T
2026-03-08 22:50:38 +00:00

98 lines
4.4 KiB
TypeScript

import type {
EntityState, WorldState, StateUpdate,
Position, Movement, Appearance, Needs, NPCBrain, PlayerControlled, SocialState, Stats, Relationships,
} from '@dflike/shared';
import { TILE_SIZE } from '@dflike/shared';
import type { World } from '../ecs/World.js';
import type { GameMap } from '../map/GameMap.js';
import { getEffectiveStat } from '../systems/statHelpers.js';
import { getEffectiveClassification } from '../systems/relationshipHelpers.js';
import type { BondRegistry } from '../systems/bondRegistry.js';
import { hasBond } from '../systems/bondRegistry.js';
export function serializeEntity(world: World, entityId: number): EntityState {
const socialState = world.getComponent<SocialState>(entityId, 'socialState');
const statsComponent = world.getComponent<Stats>(entityId, 'stats');
const stats = statsComponent ? {
strength: getEffectiveStat(world, entityId, 'strength'),
dexterity: getEffectiveStat(world, entityId, 'dexterity'),
constitution: getEffectiveStat(world, entityId, 'constitution'),
intelligence: getEffectiveStat(world, entityId, 'intelligence'),
perception: getEffectiveStat(world, entityId, 'perception'),
sociability: getEffectiveStat(world, entityId, 'sociability'),
courage: getEffectiveStat(world, entityId, 'courage'),
curiosity: getEffectiveStat(world, entityId, 'curiosity'),
empathy: getEffectiveStat(world, entityId, 'empathy'),
temperament: getEffectiveStat(world, entityId, 'temperament'),
} : undefined;
const relationshipsComponent = world.getComponent<Relationships>(entityId, 'relationships');
const registry = world.getSingleton<BondRegistry>('bondRegistry');
const relationships = relationshipsComponent && relationshipsComponent.size > 0
? [...relationshipsComponent.entries()].map(([otherId, rel]) => {
const sociability = statsComponent ? getEffectiveStat(world, entityId, 'sociability') : 10;
const empathy = statsComponent ? getEffectiveStat(world, entityId, 'empathy') : 10;
const classification = getEffectiveClassification(
otherId, rel.value, relationshipsComponent, sociability, empathy,
registry, entityId,
);
let bond: string | null = null;
if (registry) {
if (hasBond(registry, entityId, otherId, 'partner')) bond = 'partner';
else if (hasBond(registry, entityId, otherId, 'partner', 'former')) bond = 'former_partner';
}
return {
entityId: otherId,
name: world.getComponent<string>(otherId, 'name') ?? `NPC #${otherId}`,
value: Math.round(rel.value * 10) / 10,
classification,
status: rel.status,
bond,
};
})
: undefined;
const inventoryComponent = world.getComponent<Map<string, number>>(entityId, 'inventory');
const inventory = inventoryComponent && inventoryComponent.size > 0
? Object.fromEntries(inventoryComponent)
: undefined;
return {
id: entityId,
position: world.getComponent<Position>(entityId, 'position')!,
movement: world.getComponent<Movement>(entityId, 'movement')!,
appearance: world.getComponent<Appearance>(entityId, 'appearance')!,
needs: world.getComponent<Needs>(entityId, 'needs'),
npcBrain: world.getComponent<NPCBrain>(entityId, 'npcBrain'),
playerControlled: world.getComponent<PlayerControlled>(entityId, 'playerControlled'),
name: world.getComponent<string>(entityId, 'name'),
backstory: world.getComponent<string>(entityId, 'backstory') || undefined,
socialState: socialState ? {
phase: socialState.phase,
partnerId: socialState.partnerId,
outcome: socialState.outcome,
} : undefined,
stats,
relationships,
inventory,
};
}
export function serializeWorldState(world: World, map: GameMap): WorldState {
const entities = world.query('position').map(id => serializeEntity(world, id));
return {
entities,
worldWidth: map.width,
worldHeight: map.height,
tileSize: TILE_SIZE,
obstacles: map.getObstacles(),
pointsOfInterest: map.getPointsOfInterest(),
terrain: map.terrain,
decorations: map.decorations,
trunkDecorations: map.trunkDecorations,
resourceTiles: map.resourceTiles,
};
}
export function serializeStateUpdate(world: World, tick: number, gameTime: number): StateUpdate {
const entities = world.query('position').map(id => serializeEntity(world, id));
return { entities, tick, gameTime };
}