Files
feuerwerk/src/core/model/Project.ts
T
admin 1e9d21cd5d
game-maker / suite (push) Failing after 11m6s
A game that is more than one scene needs a way from one to the next, so the graph can load one (#39)
Load scene sets a flag on the World like Stop and Restart do; the swap
happens after the tick, and the last wish of a tick wins. Exactly one
thing crosses over: the SharedState, so global variables and global
machines keep what they know while everything local is built anew.
Loading the scene that is already running rebuilds it, which is what
"reset the level" means when the score must survive.

A run starts either from the start scene (a new project field, set from
the asset panel and marked with an arrow in the list) or from whatever
scene is open. The toggle next to Play says which question is asked and
remembers the answer in localStorage: it is a way of working, not part
of the game.

Sounds no longer end because a scene ends. A voice belongs to the
behavior that started it and stops right after On destroy, so the scene
swap is no longer a special case at all. For the enemy that plays its
explosion and destroys itself in the same breath there is now a
detached input on Play sound.

The scene selector stays usable while a run plays, because a double
click in the asset panel already did the same thing unguarded. It moves
what the editor looks at, not what the run plays.
2026-09-15 13:24:26 +02:00

94 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<T extends Asset['type']>(
project: Project,
assetId: string,
type: T,
): Extract<Asset, { type: T }> | undefined {
const asset = findAsset(project, assetId);
return asset?.type === type ? (asset as Extract<Asset, { type: T }>) : 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;
}