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:
Ivan Fontosh
2026-07-17 13:13:58 +08:00
parent 61875be857
commit 37ba855faf
40 changed files with 3655 additions and 17 deletions
+86
View File
@@ -8,6 +8,10 @@ import type {
MaterialsOverlayEvent,
MaterialsOverlayState,
MediaAsset,
NpcId,
NpcRelationId,
NpcsOverlayEvent,
NpcsOverlayState,
Project,
ProjectId,
Scene,
@@ -54,6 +58,14 @@ export const ipcChannels = {
deleteMaterial: 'project.deleteMaterial',
setMaterialsOrder: 'project.setMaterialsOrder',
pickMaterialImage: 'project.pickMaterialImage',
upsertNpc: 'project.upsertNpc',
updateNpcFields: 'project.updateNpcFields',
updateNpcPosition: 'project.updateNpcPosition',
deleteNpc: 'project.deleteNpc',
setNpcsOrder: 'project.setNpcsOrder',
pickNpcAvatar: 'project.pickNpcAvatar',
upsertNpcRelation: 'project.upsertNpcRelation',
deleteNpcRelation: 'project.deleteNpcRelation',
importScenePreview: 'project.importScenePreview',
clearScenePreview: 'project.clearScenePreview',
assetFileUrl: 'project.assetFileUrl',
@@ -95,6 +107,10 @@ export const ipcChannels = {
sceneDescriptionContent: 'windows.sceneDescriptionContent',
openMaterials: 'windows.openMaterials',
closeMaterials: 'windows.closeMaterials',
openNpcsEditor: 'windows.openNpcsEditor',
closeNpcsEditor: 'windows.closeNpcsEditor',
openNpcs: 'windows.openNpcs',
closeNpcs: 'windows.closeNpcs',
},
session: {
stateChanged: 'session.stateChanged',
@@ -109,6 +125,11 @@ export const ipcChannels = {
dispatch: 'materialsOverlay.dispatch',
stateChanged: 'materialsOverlay.stateChanged',
},
npcsOverlay: {
getState: 'npcsOverlay.getState',
dispatch: 'npcsOverlay.dispatch',
stateChanged: 'npcsOverlay.stateChanged',
},
sceneDarkness: {
getState: 'sceneDarkness.getState',
dispatch: 'sceneDarkness.dispatch',
@@ -172,6 +193,7 @@ export type IpcEventMap = {
[ipcChannels.session.stateChanged]: { state: SessionState };
[ipcChannels.effects.stateChanged]: { state: EffectsState };
[ipcChannels.materialsOverlay.stateChanged]: { state: MaterialsOverlayState };
[ipcChannels.npcsOverlay.stateChanged]: { state: NpcsOverlayState };
[ipcChannels.sceneDarkness.stateChanged]: { state: SceneDarknessState };
[ipcChannels.video.stateChanged]: { state: VideoPlaybackState };
[ipcChannels.license.statusChanged]: { snapshot: LicenseSnapshot };
@@ -274,6 +296,45 @@ export type IpcInvokeMap = {
| { canceled: true }
| { canceled: false; filePath: string; previewDataUrl: string };
};
[ipcChannels.project.upsertNpc]: {
req: { npcId?: NpcId; name: string; description?: string; filePath?: string };
res: { project: Project };
};
[ipcChannels.project.updateNpcFields]: {
req: { npcId: NpcId; name?: string; description?: string };
res: { project: Project };
};
[ipcChannels.project.updateNpcPosition]: {
req: { npcId: NpcId; x: number; y: number };
res: { project: Project };
};
[ipcChannels.project.deleteNpc]: {
req: { npcId: NpcId };
res: { project: Project };
};
[ipcChannels.project.setNpcsOrder]: {
req: { npcIds: NpcId[] };
res: { project: Project };
};
[ipcChannels.project.pickNpcAvatar]: {
req: Record<string, never>;
res:
| { canceled: true }
| { canceled: false; filePath: string; previewDataUrl: string };
};
[ipcChannels.project.upsertNpcRelation]: {
req: {
relationId?: NpcRelationId;
sourceNpcId: NpcId;
targetNpcId: NpcId;
label: string;
};
res: { project: Project };
};
[ipcChannels.project.deleteNpcRelation]: {
req: { relationId: NpcRelationId };
res: { project: Project };
};
[ipcChannels.project.importScenePreview]: {
req: { sceneId: SceneId; filePath?: string };
res: { project: Project; assetId: AssetId | null; background: boolean };
@@ -436,6 +497,22 @@ export type IpcInvokeMap = {
req: Record<string, never>;
res: { ok: true };
};
[ipcChannels.windows.openNpcsEditor]: {
req: Record<string, never>;
res: { ok: true };
};
[ipcChannels.windows.closeNpcsEditor]: {
req: Record<string, never>;
res: { ok: true };
};
[ipcChannels.windows.openNpcs]: {
req: Record<string, never>;
res: { ok: true };
};
[ipcChannels.windows.closeNpcs]: {
req: Record<string, never>;
res: { ok: true };
};
[ipcChannels.materialsOverlay.getState]: {
req: Record<string, never>;
res: { state: MaterialsOverlayState };
@@ -444,6 +521,14 @@ export type IpcInvokeMap = {
req: { event: MaterialsOverlayEvent };
res: { ok: true };
};
[ipcChannels.npcsOverlay.getState]: {
req: Record<string, never>;
res: { state: NpcsOverlayState };
};
[ipcChannels.npcsOverlay.dispatch]: {
req: { event: NpcsOverlayEvent };
res: { ok: true };
};
[ipcChannels.effects.getState]: {
req: Record<string, never>;
res: { state: EffectsState };
@@ -495,6 +580,7 @@ export type LegacyIpcEventMap = {
[ipcChannels.session.stateChanged]: { state: SessionState };
[ipcChannels.effects.stateChanged]: { state: EffectsState };
[ipcChannels.materialsOverlay.stateChanged]: { state: MaterialsOverlayState };
[ipcChannels.npcsOverlay.stateChanged]: { state: NpcsOverlayState };
[ipcChannels.sceneDarkness.stateChanged]: { state: SceneDarknessState };
[ipcChannels.video.stateChanged]: { state: VideoPlaybackState };
[ipcChannels.license.statusChanged]: Record<string, never>;