feat(npcs): add campaign NPCs with relation graph and session overlay
Add a dedicated NPC editor window, directed relations, control/presentation avatar overlay, and ru/en help for the new section. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
clampNpcsLayout,
|
||||
DEFAULT_NPCS_OVERLAY_LAYOUT,
|
||||
type NpcId,
|
||||
type NpcsOverlayEvent,
|
||||
type NpcsOverlayLayout,
|
||||
type NpcsOverlayState,
|
||||
type NpcsZoomTool,
|
||||
zoomNpcsLayoutAt,
|
||||
} from '../../shared/types';
|
||||
|
||||
function emptyState(): NpcsOverlayState {
|
||||
return {
|
||||
revision: 1,
|
||||
activeNpcId: null,
|
||||
layout: { ...DEFAULT_NPCS_OVERLAY_LAYOUT },
|
||||
zoomTool: null,
|
||||
};
|
||||
}
|
||||
|
||||
export class NpcsOverlayStore {
|
||||
private state: NpcsOverlayState = emptyState();
|
||||
|
||||
getState(): NpcsOverlayState {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
clear(): NpcsOverlayState {
|
||||
if (this.state.activeNpcId === null && this.state.zoomTool === null) {
|
||||
return this.state;
|
||||
}
|
||||
this.state = {
|
||||
revision: this.state.revision + 1,
|
||||
activeNpcId: null,
|
||||
layout: { ...DEFAULT_NPCS_OVERLAY_LAYOUT },
|
||||
zoomTool: null,
|
||||
};
|
||||
return this.state;
|
||||
}
|
||||
|
||||
dispatch(event: NpcsOverlayEvent): NpcsOverlayState {
|
||||
switch (event.kind) {
|
||||
case 'hide':
|
||||
return this.clear();
|
||||
case 'show':
|
||||
this.state = {
|
||||
revision: this.state.revision + 1,
|
||||
activeNpcId: event.npcId,
|
||||
layout: { ...DEFAULT_NPCS_OVERLAY_LAYOUT },
|
||||
zoomTool: this.state.zoomTool,
|
||||
};
|
||||
return this.state;
|
||||
case 'toggle': {
|
||||
if (this.state.activeNpcId === event.npcId) {
|
||||
return this.clear();
|
||||
}
|
||||
this.state = {
|
||||
revision: this.state.revision + 1,
|
||||
activeNpcId: event.npcId,
|
||||
layout: { ...DEFAULT_NPCS_OVERLAY_LAYOUT },
|
||||
zoomTool: this.state.zoomTool,
|
||||
};
|
||||
return this.state;
|
||||
}
|
||||
case 'layout.set': {
|
||||
if (this.state.activeNpcId === null) return this.state;
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
layout: clampNpcsLayout(event.layout),
|
||||
};
|
||||
return this.state;
|
||||
}
|
||||
case 'zoomTool.set': {
|
||||
const tool: NpcsZoomTool = event.tool;
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
zoomTool: tool,
|
||||
};
|
||||
return this.state;
|
||||
}
|
||||
case 'zoomAt': {
|
||||
if (this.state.activeNpcId === null || !this.state.zoomTool) return this.state;
|
||||
const factor = this.state.zoomTool === 'zoomIn' ? 1.25 : 1 / 1.25;
|
||||
const layout: NpcsOverlayLayout = zoomNpcsLayoutAt(
|
||||
this.state.layout,
|
||||
event.nx,
|
||||
event.ny,
|
||||
factor,
|
||||
);
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
layout,
|
||||
};
|
||||
return this.state;
|
||||
}
|
||||
default:
|
||||
return this.state;
|
||||
}
|
||||
}
|
||||
|
||||
ensureNpcStillExists(npcIds: ReadonlySet<NpcId>): NpcsOverlayState {
|
||||
const active = this.state.activeNpcId;
|
||||
if (active === null || npcIds.has(active)) return this.state;
|
||||
return this.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user