import { type Asset, type ContainerAsset, type GraphAsset, isGraphAsset, isSceneAsset, type SceneAsset, } from './Asset'; /** 2: a placed container carries no copy of its template. */ export const PROJECT_FORMAT_VERSION = 2; export interface Project { formatVersion: number; id: string; name: string; assets: Asset[]; /** Empty string = none. */ activeSceneId: string; /** * Where the game begins – what a player would see first, as opposed to whatever scene happens to * be open in the editor. Empty (or dangling) means none has been named, and a run falls back to * the active scene; no format bump needed for that, since an older project reads as exactly that. */ startSceneId: string; } export function createEmptyProject(id: string, name: string): Project { return { formatVersion: PROJECT_FORMAT_VERSION, id, name, assets: [], activeSceneId: '', startSceneId: '', }; } /** The scene a real run starts from: the named one, or the active one while none is named. */ export function startScene(project: Project): SceneAsset | undefined { return findScene(project, project.startSceneId) ?? findScene(project, project.activeSceneId); } export function findAsset(project: Project, assetId: string): Asset | undefined { return project.assets.find((a) => a.id === assetId); } export function findAssetOf( project: Project, assetId: string, type: T, ): Extract | undefined { const asset = findAsset(project, assetId); return asset?.type === type ? (asset as Extract) : undefined; } export function findScene(project: Project, sceneId: string): SceneAsset | undefined { const asset = findAsset(project, sceneId); return asset && isSceneAsset(asset) ? asset : undefined; } export function listScenes(project: Project): SceneAsset[] { return project.assets.filter(isSceneAsset); } export function findGraphAsset(project: Project, assetId: string): GraphAsset | undefined { const asset = findAsset(project, assetId); return asset && isGraphAsset(asset) ? asset : undefined; } /** * Everything that holds placed objects. A container's `objects` are a template, so anything * walking "all placed objects" must cover both or it silently skips the templates. */ export type ObjectHolder = SceneAsset | ContainerAsset; export function isObjectHolder(asset: Asset): asset is ObjectHolder { return asset.type === 'scene' || asset.type === 'container'; } export function objectHolders(project: Project): ObjectHolder[] { return project.assets.filter(isObjectHolder); } /** * The scene or container asset an object belongs to. Editing a container's template is the same * work as editing a scene, so the object commands resolve their target through this rather than * through `findScene`. */ export function findObjectHolder(project: Project, holderId: string): ObjectHolder | undefined { const asset = findAsset(project, holderId); return asset && isObjectHolder(asset) ? asset : undefined; }