Files
DndGamePlayer/app/main/effects/sceneDarknessStore.ts
T
Ivan Fontosh 877fbaa8e7 feat: save games and edit scene darkness
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>
2026-10-06 11:41:42 +08:00

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 })) };
}