import type { SnapshotLayer, JsonValue } from '@rpg/engine';
/**
* Контентный слой снапшота агентного моста. Чистые функции — тестируются
* в Vitest без браузера; LocationScene/GameAgent подставляют живые данные.
*/
/** Герой. */
export interface HeroSnapshot {
tile: { x: number; y: number };
pos: { x: number; y: number };
hp: number;
maxHp: number;
facing: string;
moving: boolean;
invuln: boolean;
inHazard: string | null;
}
/** Враг. */
export interface EnemySnapshot {
kind: string;
state: string | null;
hp: number;
pos: { x: number; y: number };
asleep: boolean;
dead: boolean;
}
/** NPC. */
export interface NpcSnapshot {
id: string;
name: string;
tile: { x: number; y: number };
met: boolean;
}
/** Активный диалог (или null). */
export interface DialogueSnapshot {
id: string | null;
nodeId: string | null;
speaker: string | null;
text: string | null;
choices: string[];
waitingForChoice: boolean;
}
/** Переход (дверь/выход/портал) — из данных локации. */
export interface TransitionSnapshot {
tile: { x: number; y: number };
to: string;
label: string | null;
}
/** Контентный слой сцены локации. */
export interface LocationSnapshot {
scene: 'location';
area: string;
areaName: string;
hero: HeroSnapshot;
enemies: EnemySnapshot[];
npcs: NpcSnapshot[];
transitions: TransitionSnapshot[];
dialogue: DialogueSnapshot | null;
cutscene: { active: boolean } | null;
/** Последний тост (текст + тик) — единственный канал текста реакций. */
lastToast: { text: string; tick: number } | null;
}
/** Полный снапшот игры: движковый слой + слои игры. */
export interface GameSnapshot {
[k: string]: JsonValue | unknown;
/** Флаги прохождения. */
flags: string[];
/** Переменные прохождения. */
vars: Record<string, number | string | boolean>;
/** Сумка. */
inventory: { id: string; count: number }[];
}
/** Округление до 3 знаков — JSON читабельнее, точность в юнитах не теряется. */
function r(v: number): number {
return Math.round(v * 1000) / 1000;
}
/** Слой героя. */
export function heroLayer(o: HeroSnapshot): SnapshotLayer {
return {
hero: {
tile: { x: o.tile.x, y: o.tile.y },
pos: { x: r(o.pos.x), y: r(o.pos.y) },
hp: o.hp,
maxHp: o.maxHp,
facing: o.facing,
moving: o.moving,
invuln: o.invuln,
inHazard: o.inHazard
}
};
}
/** Слой врагов. */
export function enemiesLayer(enemies: EnemySnapshot[]): SnapshotLayer {
return {
enemies: enemies.map((e) => ({
kind: e.kind,
state: e.state,
hp: e.hp,
pos: { x: r(e.pos.x), y: r(e.pos.y) },
asleep: e.asleep,
dead: e.dead
})) as unknown as JsonValue[]
};
}
/** Слой NPC. */
export function npcsLayer(npcs: NpcSnapshot[]): SnapshotLayer {
return {
npcs: npcs.map((n) => ({
id: n.id,
name: n.name,
tile: { x: n.tile.x, y: n.tile.y },
met: n.met
})) as unknown as JsonValue[]
};
}
/** Слой диалога. */
export function dialogueLayer(d: DialogueSnapshot | null): SnapshotLayer {
return { dialogue: d ? (d as unknown as JsonValue) : null };
}
/** Слой уровня игры (вне сцен). */
export function gameLayer(o: {
flags: string[];
vars: Record<string, number | string | boolean>;
inventory: { id: string; count: number }[];
}): GameSnapshot {
return {
flags: [...o.flags],
vars: { ...o.vars },
inventory: o.inventory.map((s) => ({ id: s.id, count: s.count }))
};
}