Something went wrong. Try again.
A Social Deduction / Deception Game
Something went wrong. Try again.
4.5 kB · 104 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105// Resolve a game's map AT-URI (a world.tilemap.map record) into the engine's world layout: the// collision-derived nav grid, any stations authored on the map, and the spawn region. This is the// server-side half of// "the engine resolves the map" — the pure parsing lives in engine/map.ts; here we fetch the record// and hand it over. A map URI that can't be resolved is an error the caller surfaces (game creation// fails) rather than silently falling back to a default layout.
import { parseAtUri, getRecord, resolvePds } from './atproto.js'import { navFromMap, navFromExport, spawnFromMap, stationsFromMap, type MapRecord, type MapExportSubset,} from '../../engine/map.js'import type { StationSeed } from '../../engine/types.js'import type { ResolvedWorld } from './rooms.js'
const MAP_COLLECTION = 'world.tilemap.map'
export type ResolveWorldResult = | { ok: true; world: ResolvedWorld } /** 'bad-map-uri': not an at:// world.tilemap.map record URI. * 'map-unresolved': record missing, unreachable, or not a usable map. */ | { ok: false; reason: 'bad-map-uri' | 'map-unresolved' }
/** The `render` field a baked `world.tilemap.map` carries: the MapExport JSON lives in the `data` * blob (the same one the client's map-import fetches). */interface RenderField { data?: { ref?: { $link?: string } }}
/** * Fetch a map record's baked `MapExport` (the `render.data` blob) — the editor's self-contained * scene bake, which resolves every object's collision into world space. Returns null when the map * has no baked render or the blob can't be fetched, so the caller falls back to authored collision. */async function fetchBakedExport(did: string, value: Record<string, unknown>): Promise<MapExportSubset | null> { const cid = (value as { render?: RenderField }).render?.data?.ref?.$link if (!cid) return null const pds = await resolvePds(did) if (!pds) return null const url = `${pds.replace(/\/$/, '')}/xrpc/com.atproto.sync.getBlob` + `?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(cid)}` try { const res = await fetch(url) if (!res.ok) return null return (await res.json()) as MapExportSubset } catch { return null }}
/** All distinct resource AT-URIs a station seed references (collect ∪ deposit) — the in-play set. */function stationResources(stations: StationSeed[] | undefined): string[] { const out = new Set<string>() for (const s of stations ?? []) for (const r of [...s.collect, ...s.deposit]) out.add(r) return [...out]}
/** * Resolve a game's `world.tilemap.map` AT-URI into the engine's world layout. Station markers carry * resource AT-URIs (town.deception.resource records) directly — the in-play resource set is the * union of the stations' collect/deposit resources; the client fetches each record for its * name/sprite (presentation stays client-side). Records the resolved map URI for the client to * render. */export async function resolveGameWorld(uri: string): Promise<ResolveWorldResult> { const res = await resolveWorld(uri) return res.ok ? { ok: true, world: { ...res.world, tilemapUri: uri } } : res}
export async function resolveWorld(mapUri: string): Promise<ResolveWorldResult> { const parts = parseAtUri(mapUri) if (!parts || parts.collection !== MAP_COLLECTION) return { ok: false, reason: 'bad-map-uri' }
const value = await getRecord(parts.did, parts.collection, parts.rkey) if (!value || typeof value !== 'object') return { ok: false, reason: 'map-unresolved' } const map = value as unknown as MapRecord if (!map.grid || typeof map.grid.tileWidth !== 'number') return { ok: false, reason: 'map-unresolved' }
try { // Prefer the baked render for collision (it includes per-object colliders the authored nodes // only reference); fall back to the authored collision areas for maps with no baked render. const baked = await fetchBakedExport(parts.did, value) const nav = baked ? navFromExport(baked, map.grid.tileWidth) : navFromMap(map) const stations = stationsFromMap(map) const spawn = spawnFromMap(map) return { ok: true, world: { stations: stations.length ? stations : undefined, nav, spawn: spawn ?? undefined, resources: stations.length ? stationResources(stations) : undefined, }, } } catch (err) { console.warn(`[maps] could not resolve ${mapUri}:`, err instanceof Error ? err.message : err) return { ok: false, reason: 'map-unresolved' } }}