feat: scene traps, material legends, and scene editor window
Add trap overlays with session state, material legend editor/panel, and a dedicated scene editor window wired through IPC and project persistence. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
import {
|
||||
defaultTrapRuntime,
|
||||
type SceneTrapsEvent,
|
||||
type SceneTrapsState,
|
||||
type SceneTrapRuntime,
|
||||
} from '../../shared/types';
|
||||
|
||||
function emptyState(): SceneTrapsState {
|
||||
return {
|
||||
revision: 1,
|
||||
cacheKey: null,
|
||||
byId: {},
|
||||
lastActivation: null,
|
||||
};
|
||||
}
|
||||
|
||||
function cloneById(byId: Record<string, SceneTrapRuntime>): Record<string, SceneTrapRuntime> {
|
||||
const out: Record<string, SceneTrapRuntime> = {};
|
||||
for (const [k, v] of Object.entries(byId)) {
|
||||
out[k] = { ...v };
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export class SceneTrapsStore {
|
||||
private state: SceneTrapsState = emptyState();
|
||||
/** Кэш runtime по ключу сцены/ноды на время сессии показа. */
|
||||
private cache = new Map<string, Record<string, SceneTrapRuntime>>();
|
||||
private currentKey: string | null = null;
|
||||
private activationToken = 0;
|
||||
|
||||
getState(): SceneTrapsState {
|
||||
return {
|
||||
...this.state,
|
||||
byId: cloneById(this.state.byId),
|
||||
lastActivation: this.state.lastActivation ? { ...this.state.lastActivation } : null,
|
||||
};
|
||||
}
|
||||
|
||||
resetSession(): void {
|
||||
this.cache.clear();
|
||||
this.currentKey = null;
|
||||
this.state = emptyState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Переключение сцены: сохраняем runtime в кэш и подгружаем/инициализируем для новых trapIds.
|
||||
*/
|
||||
switchScene(cacheKey: string | null, trapIds: readonly string[]): SceneTrapsState {
|
||||
if (this.currentKey !== null) {
|
||||
this.cache.set(this.currentKey, cloneById(this.state.byId));
|
||||
}
|
||||
this.currentKey = cacheKey;
|
||||
const cached = cacheKey ? this.cache.get(cacheKey) : undefined;
|
||||
const byId: Record<string, SceneTrapRuntime> = {};
|
||||
for (const id of trapIds) {
|
||||
byId[id] = cached?.[id] ? { ...cached[id]! } : defaultTrapRuntime();
|
||||
}
|
||||
if (cacheKey) {
|
||||
this.cache.set(cacheKey, cloneById(byId));
|
||||
}
|
||||
this.state = {
|
||||
revision: this.state.revision + 1,
|
||||
cacheKey,
|
||||
byId,
|
||||
lastActivation: null,
|
||||
};
|
||||
return this.getState();
|
||||
}
|
||||
|
||||
dispatch(event: SceneTrapsEvent): SceneTrapsState {
|
||||
switch (event.kind) {
|
||||
case 'syncTrapIds': {
|
||||
const byId: Record<string, SceneTrapRuntime> = {};
|
||||
for (const id of event.trapIds) {
|
||||
byId[id] = this.state.byId[id] ? { ...this.state.byId[id]! } : defaultTrapRuntime();
|
||||
}
|
||||
if (this.currentKey) this.cache.set(this.currentKey, cloneById(byId));
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
byId,
|
||||
};
|
||||
return this.getState();
|
||||
}
|
||||
case 'reveal': {
|
||||
const cur = this.state.byId[event.trapId];
|
||||
if (!cur) return this.getState();
|
||||
const byId = cloneById(this.state.byId);
|
||||
byId[event.trapId] = { ...cur, revealed: true };
|
||||
if (this.currentKey) this.cache.set(this.currentKey, cloneById(byId));
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
byId,
|
||||
};
|
||||
return this.getState();
|
||||
}
|
||||
case 'activate': {
|
||||
const cur = this.state.byId[event.trapId];
|
||||
if (!cur) return this.getState();
|
||||
const byId = cloneById(this.state.byId);
|
||||
byId[event.trapId] = { status: 'active', revealed: true };
|
||||
this.activationToken += 1;
|
||||
if (this.currentKey) this.cache.set(this.currentKey, cloneById(byId));
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
byId,
|
||||
lastActivation: { trapId: event.trapId, token: this.activationToken },
|
||||
};
|
||||
return this.getState();
|
||||
}
|
||||
case 'disarm': {
|
||||
const cur = this.state.byId[event.trapId];
|
||||
if (!cur) return this.getState();
|
||||
const byId = cloneById(this.state.byId);
|
||||
byId[event.trapId] = { status: 'disarmed', revealed: true };
|
||||
if (this.currentKey) this.cache.set(this.currentKey, cloneById(byId));
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
byId,
|
||||
};
|
||||
return this.getState();
|
||||
}
|
||||
default: {
|
||||
const _x: never = event;
|
||||
void _x;
|
||||
return this.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user