import type { AnimationDef, Engine, IntentSystem, MapDef, ParticleDef, ShaderDef, ShopDef, SoundDef, SpriteDef, TimeSystem } from "@nengine/core"; import type { RenderSnapshot, ViewportHandle } from "../viewport.js"; import type { SoundPlayer } from "../sound.js"; import { arrowDirection } from "../keys.js"; export interface LocalSyncHandle { setTimeLabel(fn: (label: string) => void): void; refresh(): void; intent(action: string, args: Record): void; input(input: string): void; destroy(): void; } export interface LocalSyncOptions { sound?: SoundPlayer; } export function connectLocal(engine: Engine, viewport: ViewportHandle, opts?: LocalSyncOptions): LocalSyncHandle { const time = engine.systems.get("time"); const sound = opts?.sound; let label: ((label: string) => void) | undefined; function refresh(): void { const map = engine.registry.list("map")[0]; if (map) { const snapshot: RenderSnapshot = { map, world: engine.world.snapshot(), sprites: engine.registry.list("sprite"), sounds: engine.registry.list("sound"), particles: engine.registry.list("particle"), shaders: engine.registry.list("shader"), shops: engine.registry.list("shop"), animations: engine.registry.list("animation"), assets: Object.fromEntries(engine.assets), }; sound?.setCatalog(snapshot.sounds ?? []); sound?.setAssets(snapshot.assets ?? {}); viewport.render(snapshot); } else { viewport.render(); } const state = time?.get(); if (state && label) { label(`Day ${state.day}, ${state.season}`); } } refresh(); const unsubscribeSound = engine.events.on("sound-play", (payload) => { sound?.onEvent({ event: "sound-play", payload }); }); const unsubscribeBgmPlay = engine.events.on("bgm-play", (payload) => { sound?.onEvent({ event: "bgm-play", payload }); }); const unsubscribeBgmStop = engine.events.on("bgm-stop", (payload) => { sound?.onEvent({ event: "bgm-stop", payload }); }); const unsubscribeParticle = engine.events.on("particle", (payload) => { viewport.onEvent({ event: "particle", payload }); }); return { setTimeLabel(fn: (label: string) => void): void { label = fn; refresh(); }, refresh, intent(action: string, args: Record): void { engine.systems.get("intent")?.handle(action, args); refresh(); }, input(input: string): void { const direction = arrowDirection(input); if (!direction) return; const player = engine.world.get("player"); if (!player) return; const x = (typeof player.x === "number" ? player.x : 0) + direction.dx; const y = (typeof player.y === "number" ? player.y : 0) + direction.dy; engine.systems.get("intent")?.handle("move", { tile: { x, y } }); refresh(); }, destroy(): void { unsubscribeSound(); unsubscribeBgmPlay(); unsubscribeBgmStop(); unsubscribeParticle(); label = undefined; }, }; }