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
@@ -235,9 +235,22 @@ export const EDITOR_MESSAGES: Record<EditorLocale, Record<string, string>> = {
'projectMenu.home': 'Начальный экран',
'projectMenu.import': 'Импорт',
'projectMenu.importFoundry': 'Импорт из Foundry',
'projectMenu.export': 'Экспорт',
'projectMenu.noProjects': 'Нет сохранённых проектов',
'foundryImport.title': 'Импорт из Foundry',
'foundryImport.hint':
'Выберите папку или архив мира (.world) либо модуля Foundry VTT (версии 11+). Будет создан новый проект.',
'foundryImport.sourceType': 'ТИП ИСТОЧНИКА',
'foundryImport.folder': 'Папка',
'foundryImport.archive': 'Архив (.zip / .fvtt)',
'foundryImport.source': 'ИСТОЧНИК',
'foundryImport.chooseFolder': 'Выбрать папку',
'foundryImport.chooseArchive': 'Выбрать архив',
'foundryImport.noSourceSelected': 'Не выбрано',
'foundryImport.import': 'Импортировать',
'fileMenu.rename': 'Переименовать проект',
'scenes.search': 'Поиск сцен…',
@@ -301,11 +314,18 @@ export const EDITOR_MESSAGES: Record<EditorLocale, Record<string, string>> = {
'importStoryline.reportLines': 'Импортировано линий: {count}',
'importStoryline.reportScenesCreated': 'Создано новых сцен: {count}',
'importStoryline.reportScenesReused': 'Использовано существующих сцен: {count}',
'importStoryline.reportNpcsCreated': 'Создано новых НПС: {count}',
'importStoryline.reportNpcsReused': 'Использовано существующих НПС: {count}',
'importStoryline.reportNodes': 'Добавлено карточек на граф: {count}',
'importStoryline.reportEdges': 'Добавлено связей: {count}',
'importStoryline.reportAssetsCopied': 'Скопировано файлов материалов: {count}',
'importStoryline.reportAssetsReused': 'Повторно использовано материалов: {count}',
'importStoryline.reportRenamedSides': 'Переименованы побочные линии: {names}',
'importStoryline.npcConflictsTitle': 'Совпадение имён НПС',
'importStoryline.npcConflictsHint':
'В импортируемых линиях есть НПС с такими же именами, как в текущем проекте. Выберите действие для каждого.',
'importStoryline.createNewNpc': 'Создать нового НПС',
'importStoryline.useExistingNpc': 'Использовать «{name}»',
'confirmDelete.title': 'Удаление проекта',
'confirmDelete.body': 'Удалить проект «{name}» безвозвратно? Файл и кэш будут стёрты с диска.',
@@ -411,6 +431,28 @@ export const EDITOR_MESSAGES: Record<EditorLocale, Record<string, string>> = {
'npcs.zoomInHint': 'Кликните по аватару в предпросмотре пульта, чтобы увеличить.',
'npcs.zoomOutHint': 'Кликните по аватару в предпросмотре пульта, чтобы уменьшить.',
'npcs.zoomIdleHint': 'Выберите лупу, затем кликните по аватару в предпросмотре пульта.',
'npcs.ungrouped': 'Без группы',
'npcs.addGroup': 'Новая группа',
'npcs.editGroup': 'Изменить группу',
'npcs.deleteGroup': 'Удалить группу',
'npcs.groupName': 'Название группы',
'npcs.groupColor': 'Цвет',
'npcs.groupNameRequired': 'Укажите название группы.',
'npcs.groupNameDup': 'Группа с таким названием уже есть.',
'npcs.deleteGroupTitle': 'Удаление группы',
'npcs.deleteGroupConfirm':
'Удалить группу «{name}»? НПС станут без группы, вложенные группы будут подняты на уровень выше.',
'npcs.addSubgroup': 'Добавить подгруппу',
'npcs.group': 'ГРУППА',
'npcs.bindingEnable': 'Привязать…',
'npcs.bindingKind': 'ТИП ПРИВЯЗКИ',
'npcs.bindingStoryline': 'Сюжетная линия',
'npcs.bindingScene': 'Сцена',
'npcs.bindingMain': 'Основная линия',
'npcs.bindingSelect': 'ОБЪЕКТ',
'npcs.graphFilterAll': 'Все',
'npcs.graphFilterUngrouped': 'Без группы',
'npcs.graphFilter': 'Фильтр графа',
'scene.title': 'НАЗВАНИЕ СЦЕНЫ',
'scene.description': 'ОПИСАНИЕ',
@@ -727,9 +769,22 @@ export const EDITOR_MESSAGES: Record<EditorLocale, Record<string, string>> = {
'projectMenu.home': 'Home',
'projectMenu.import': 'Import',
'projectMenu.importFoundry': 'Import from Foundry',
'projectMenu.export': 'Export',
'projectMenu.noProjects': 'No saved projects',
'foundryImport.title': 'Import from Foundry',
'foundryImport.hint':
'Choose a Foundry VTT world or module folder or archive (version 11+). A new project will be created.',
'foundryImport.sourceType': 'SOURCE TYPE',
'foundryImport.folder': 'Folder',
'foundryImport.archive': 'Archive (.zip / .fvtt)',
'foundryImport.source': 'SOURCE',
'foundryImport.chooseFolder': 'Choose folder',
'foundryImport.chooseArchive': 'Choose archive',
'foundryImport.noSourceSelected': 'Nothing selected',
'foundryImport.import': 'Import',
'fileMenu.rename': 'Rename project',
'scenes.search': 'Search scenes…',
@@ -793,11 +848,18 @@ export const EDITOR_MESSAGES: Record<EditorLocale, Record<string, string>> = {
'importStoryline.reportLines': 'Storylines imported: {count}',
'importStoryline.reportScenesCreated': 'New scenes created: {count}',
'importStoryline.reportScenesReused': 'Existing scenes reused: {count}',
'importStoryline.reportNpcsCreated': 'New NPCs created: {count}',
'importStoryline.reportNpcsReused': 'Existing NPCs reused: {count}',
'importStoryline.reportNodes': 'Graph cards added: {count}',
'importStoryline.reportEdges': 'Connections added: {count}',
'importStoryline.reportAssetsCopied': 'Asset files copied: {count}',
'importStoryline.reportAssetsReused': 'Assets reused: {count}',
'importStoryline.reportRenamedSides': 'Renamed side storylines: {names}',
'importStoryline.npcConflictsTitle': 'Duplicate NPC names',
'importStoryline.npcConflictsHint':
'Imported storylines contain NPCs with the same names as in the current project. Choose what to do for each.',
'importStoryline.createNewNpc': 'Create new NPC',
'importStoryline.useExistingNpc': 'Use existing «{name}»',
'confirmDelete.title': 'Delete project',
'confirmDelete.body':
@@ -904,6 +966,28 @@ export const EDITOR_MESSAGES: Record<EditorLocale, Record<string, string>> = {
'npcs.zoomInHint': 'Click the avatar on the control preview to zoom in.',
'npcs.zoomOutHint': 'Click the avatar on the control preview to zoom out.',
'npcs.zoomIdleHint': 'Pick a magnifier, then click the avatar on the control preview.',
'npcs.ungrouped': 'Ungrouped',
'npcs.addGroup': 'New group',
'npcs.editGroup': 'Edit group',
'npcs.deleteGroup': 'Delete group',
'npcs.groupName': 'Group name',
'npcs.groupColor': 'Color',
'npcs.groupNameRequired': 'Group name is required.',
'npcs.groupNameDup': 'A group with this name already exists.',
'npcs.deleteGroupTitle': 'Delete group',
'npcs.deleteGroupConfirm':
'Delete group “{name}”? NPCs will become ungrouped; child groups will move up one level.',
'npcs.addSubgroup': 'Add subgroup',
'npcs.group': 'GROUP',
'npcs.bindingEnable': 'Bind…',
'npcs.bindingKind': 'BINDING TYPE',
'npcs.bindingStoryline': 'Storyline',
'npcs.bindingScene': 'Scene',
'npcs.bindingMain': 'Main storyline',
'npcs.bindingSelect': 'TARGET',
'npcs.graphFilterAll': 'All',
'npcs.graphFilterUngrouped': 'Ungrouped',
'npcs.graphFilter': 'Graph filter',
'scene.title': 'SCENE TITLE',
'scene.description': 'DESCRIPTION',