877fbaa8e7
Persist show state to local saves; author map darkness in scene editor with bake-to-scene and help updates. Co-authored-by: Cursor <cursoragent@cursor.com>
121 lines
4.0 KiB
TypeScript
121 lines
4.0 KiB
TypeScript
import type { SceneDarknessEvent, SceneDarknessRevealStroke, SceneDarknessState } from '../../shared/types';
|
|
|
|
function emptyState(): SceneDarknessState {
|
|
return {
|
|
revision: 1,
|
|
enabled: false,
|
|
cacheKey: null,
|
|
strokes: [],
|
|
draft: null,
|
|
};
|
|
}
|
|
|
|
export class SceneDarknessStore {
|
|
private state: SceneDarknessState = emptyState();
|
|
/** Кэш раскрытых областей по ключу (graphNodeId или sceneId) на время сессии показа. */
|
|
private cache = new Map<string, SceneDarknessRevealStroke[]>();
|
|
private currentKey: string | null = null;
|
|
|
|
getState(): SceneDarknessState {
|
|
return { ...this.state, strokes: [...this.state.strokes] };
|
|
}
|
|
|
|
/** Сброс кэша при новом запуске показа (кнопка «Запустить»). */
|
|
resetSession(): void {
|
|
this.cache.clear();
|
|
this.currentKey = null;
|
|
this.state = emptyState();
|
|
}
|
|
|
|
/**
|
|
* Переключение сцены: сохраняем текущие штрихи в кэш и загружаем состояние новой сцены.
|
|
* Если ключа ещё не было в сессии — берём seed (база из файла партии).
|
|
*/
|
|
switchScene(
|
|
cacheKey: string | null,
|
|
enabled: boolean,
|
|
seedStrokes: readonly SceneDarknessRevealStroke[] = [],
|
|
): SceneDarknessState {
|
|
if (this.currentKey !== null) {
|
|
this.cache.set(this.currentKey, this.state.strokes.map(cloneStroke));
|
|
}
|
|
this.currentKey = cacheKey;
|
|
let strokes: SceneDarknessRevealStroke[] = [];
|
|
if (cacheKey && enabled) {
|
|
if (this.cache.has(cacheKey)) {
|
|
strokes = (this.cache.get(cacheKey) ?? []).map(cloneStroke);
|
|
} else {
|
|
strokes = seedStrokes.map(cloneStroke);
|
|
this.cache.set(cacheKey, strokes.map(cloneStroke));
|
|
}
|
|
}
|
|
this.state = {
|
|
revision: this.state.revision + 1,
|
|
enabled,
|
|
cacheKey,
|
|
strokes,
|
|
draft: null,
|
|
};
|
|
return this.getState();
|
|
}
|
|
|
|
dispatch(event: SceneDarknessEvent): SceneDarknessState {
|
|
if (!this.state.enabled) return this.getState();
|
|
switch (event.kind) {
|
|
case 'draft.set':
|
|
this.state = {
|
|
...this.state,
|
|
revision: this.state.revision + 1,
|
|
draft: event.draft,
|
|
};
|
|
return this.getState();
|
|
case 'stroke.add': {
|
|
const strokes = [...this.state.strokes, event.stroke];
|
|
if (this.currentKey) {
|
|
this.cache.set(this.currentKey, strokes);
|
|
}
|
|
this.state = {
|
|
...this.state,
|
|
revision: this.state.revision + 1,
|
|
strokes,
|
|
draft: null,
|
|
};
|
|
return this.getState();
|
|
}
|
|
default: {
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const _x: never = event;
|
|
return this.getState();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Кэш по карточкам, включая текущую. Пустые карточки не входят. */
|
|
exportByCard(): Record<string, SceneDarknessRevealStroke[]> {
|
|
if (this.currentKey !== null) {
|
|
this.cache.set(this.currentKey, this.state.strokes.map(cloneStroke));
|
|
}
|
|
const out: Record<string, SceneDarknessRevealStroke[]> = {};
|
|
for (const [key, strokes] of this.cache) {
|
|
if (strokes.length === 0) continue;
|
|
out[key] = strokes.map(cloneStroke);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** Кладёт кэш, не переключая карточку. Дальше вызывают switchScene. */
|
|
importByCard(byCard: Record<string, SceneDarknessRevealStroke[]>): void {
|
|
this.cache.clear();
|
|
this.currentKey = null;
|
|
this.state = emptyState();
|
|
for (const [key, strokes] of Object.entries(byCard)) {
|
|
if (!Array.isArray(strokes) || strokes.length === 0) continue;
|
|
this.cache.set(key, strokes.map(cloneStroke));
|
|
}
|
|
}
|
|
}
|
|
|
|
function cloneStroke(stroke: SceneDarknessRevealStroke): SceneDarknessRevealStroke {
|
|
return { ...stroke, points: stroke.points.map((p) => ({ ...p })) };
|
|
}
|