feat(npcs): add groups, storyline bindings, and Foundry import

Nested NPC groups with color, graph filter, and scene/storyline binding; Foundry worlds/modules import actors into groups; storyline merge asks on NPC name conflicts and reports NPC counts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-20 13:19:22 +08:00
parent 37ba855faf
commit f4c0ac1438
41 changed files with 5210 additions and 418 deletions
+36 -2
View File
@@ -1,6 +1,15 @@
import type { AssetId, GraphNodeId, MaterialId, NpcId, NpcRelationId, ProjectId, SceneId } from './ids';
import type {
AssetId,
GraphNodeId,
MaterialId,
NpcGroupId,
NpcId,
NpcRelationId,
ProjectId,
SceneId,
} from './ids';
export const PROJECT_SCHEMA_VERSION = 8 as const;
export const PROJECT_SCHEMA_VERSION = 9 as const;
/** Материал кампании: изображение, показываемое поверх сцены во время игры. */
export type ProjectMaterial = {
@@ -10,6 +19,26 @@ export type ProjectMaterial = {
rotationDeg: 0 | 90 | 180 | 270;
};
/** Группа НПС (дерево через parentId). */
export type ProjectNpcGroup = {
id: NpcGroupId;
name: string;
/** Hex `#rrggbb`. */
color: string;
parentId: NpcGroupId | null;
};
/** Привязка НПС к сюжетной линии. */
export type NpcStorylineRef =
| { kind: 'main' }
| { kind: 'side'; startGraphNodeId: GraphNodeId };
/** Привязка НПС к линии или сцене; `none` — без привязки. */
export type NpcBinding =
| { kind: 'none' }
| { kind: 'storyline'; storyline: NpcStorylineRef }
| { kind: 'scene'; sceneId: SceneId };
/** НПС кампании: персонаж с аватаром, описанием и связями на графе. */
export type ProjectNpc = {
id: NpcId;
@@ -20,6 +49,9 @@ export type ProjectNpc = {
/** Позиция карточки на графе взаимосвязей. */
x: number;
y: number;
/** `null` — системная секция «Без группы». */
groupId: NpcGroupId | null;
binding: NpcBinding;
};
/** Однонаправленная связь: от `sourceNpcId` к `targetNpcId`; подпись на линии. */
@@ -167,6 +199,8 @@ export type Project = {
materials: ProjectMaterial[];
/** НПС кампании (порядок = порядок в списке редактора/пульта). */
npcs: ProjectNpc[];
/** Группы НПС (порядок среди siblings = порядок в массиве). */
npcGroups: ProjectNpcGroup[];
/** Связи между НПС (однонаправленные; между одной парой направлений может быть несколько). */
npcRelations: ProjectNpcRelation[];
currentSceneId: SceneId | null;
+5
View File
@@ -7,6 +7,7 @@ export type GraphNodeId = Brand<string, 'GraphNodeId'>;
export type MaterialId = Brand<string, 'MaterialId'>;
export type NpcId = Brand<string, 'NpcId'>;
export type NpcRelationId = Brand<string, 'NpcRelationId'>;
export type NpcGroupId = Brand<string, 'NpcGroupId'>;
export function asProjectId(value: string): ProjectId {
return value as ProjectId;
@@ -35,3 +36,7 @@ export function asNpcId(value: string): NpcId {
export function asNpcRelationId(value: string): NpcRelationId {
return value as NpcRelationId;
}
export function asNpcGroupId(value: string): NpcGroupId {
return value as NpcGroupId;
}