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(entityId, 'socialState'); const statsComponent = world.getComponent(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(entityId, 'relationships'); const registry = world.getSingleton('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(otherId, 'name') ?? `NPC #${otherId}`, value: Math.round(rel.value * 10) / 10, classification, status: rel.status, bond, }; }) : undefined; const inventoryComponent = world.getComponent>(entityId, 'inventory'); const inventory = inventoryComponent && inventoryComponent.size > 0 ? Object.fromEntries(inventoryComponent) : undefined; return { id: entityId, position: world.getComponent(entityId, 'position')!, movement: world.getComponent(entityId, 'movement')!, appearance: world.getComponent(entityId, 'appearance')!, needs: world.getComponent(entityId, 'needs'), npcBrain: world.getComponent(entityId, 'npcBrain'), playerControlled: world.getComponent(entityId, 'playerControlled'), name: world.getComponent(entityId, 'name'), backstory: world.getComponent(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 }; }