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>
This commit is contained in:
@@ -1,7 +1,13 @@
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
import { applyFieldEffectEraserStroke } from '../../shared/fieldEffectEraser';
|
||||
import type { EffectsEvent, EffectsState, EffectToolState } from '../../shared/types';
|
||||
import {
|
||||
isPersistentFieldEffect,
|
||||
type EffectInstance,
|
||||
type EffectsEvent,
|
||||
type EffectsState,
|
||||
type EffectToolState,
|
||||
} from '../../shared/types';
|
||||
|
||||
function nowMs(): number {
|
||||
return Date.now();
|
||||
@@ -23,6 +29,9 @@ export class EffectsStore {
|
||||
tool: defaultTool(),
|
||||
instances: [],
|
||||
};
|
||||
/** Постоянные эффекты по карточке (graphNodeId или sceneId) на время показа. */
|
||||
private cache = new Map<string, EffectInstance[]>();
|
||||
private currentKey: string | null = null;
|
||||
|
||||
getState(): EffectsState {
|
||||
// Всегда обновляем serverNowMs при чтении — это наш "таймкод" для рендереров.
|
||||
@@ -36,13 +45,70 @@ export class EffectsStore {
|
||||
serverNowMs: nowMs(),
|
||||
instances: [],
|
||||
};
|
||||
this.commitCurrent();
|
||||
return this.state;
|
||||
}
|
||||
|
||||
/** Новый показ: и текущая карточка, и кэш остальных. */
|
||||
resetSession(): EffectsState {
|
||||
this.cache.clear();
|
||||
this.currentKey = null;
|
||||
this.state = {
|
||||
revision: this.state.revision + 1,
|
||||
serverNowMs: nowMs(),
|
||||
tool: this.state.tool,
|
||||
instances: [],
|
||||
};
|
||||
return this.getState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Смена карточки: туман/огонь/дождь/вода/лёд/пятно остаются на ушедшей
|
||||
* и поднимаются на новой. Разовые эффекты действий сбрасываются.
|
||||
*/
|
||||
switchScene(cacheKey: string | null): EffectsState {
|
||||
this.commitCurrent();
|
||||
this.currentKey = cacheKey;
|
||||
const instances = cacheKey ? [...(this.cache.get(cacheKey) ?? [])] : [];
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
serverNowMs: nowMs(),
|
||||
instances,
|
||||
};
|
||||
return this.getState();
|
||||
}
|
||||
|
||||
exportByCard(): Record<string, EffectInstance[]> {
|
||||
this.commitCurrent();
|
||||
const out: Record<string, EffectInstance[]> = {};
|
||||
for (const [key, instances] of this.cache) {
|
||||
const kept = instances.filter((i) => isPersistentFieldEffect(i.type));
|
||||
if (kept.length > 0) out[key] = kept.map((i) => ({ ...i }));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
importByCard(byCard: Record<string, EffectInstance[]>): void {
|
||||
this.cache.clear();
|
||||
this.currentKey = null;
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
serverNowMs: nowMs(),
|
||||
instances: [],
|
||||
};
|
||||
for (const [key, instances] of Object.entries(byCard)) {
|
||||
const kept = instances.filter((i) => isPersistentFieldEffect(i.type));
|
||||
if (kept.length > 0) this.cache.set(key, kept.map((i) => ({ ...i })));
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(event: EffectsEvent): EffectsState {
|
||||
const s = this.state;
|
||||
const next: EffectsState = applyEvent(s, event, (prefix) => this.makeId(prefix));
|
||||
this.state = next;
|
||||
this.commitCurrent();
|
||||
return next;
|
||||
}
|
||||
|
||||
@@ -78,9 +144,18 @@ export class EffectsStore {
|
||||
serverNowMs: now,
|
||||
instances: kept,
|
||||
};
|
||||
this.commitCurrent();
|
||||
return true;
|
||||
}
|
||||
|
||||
private commitCurrent(): void {
|
||||
if (this.currentKey === null) return;
|
||||
this.cache.set(
|
||||
this.currentKey,
|
||||
this.state.instances.filter((i) => isPersistentFieldEffect(i.type)).map((i) => ({ ...i })),
|
||||
);
|
||||
}
|
||||
|
||||
makeId(prefix: string): string {
|
||||
return `${prefix}_${crypto.randomBytes(6).toString('hex')}_${String(nowMs())}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user