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
+63
View File
@@ -0,0 +1,63 @@
import type { FoundryCompatibility, FoundryPackageManifest } from './foundryTypes';
/** Актуальные major-версии Foundry, которые поддерживает импортёр. */
export const FOUNDRY_SUPPORTED_MAJORS = new Set([11, 12, 13]);
function parseMajor(version: string | undefined): number | null {
if (!version || typeof version !== 'string') return null;
const m = /^(\d+)/u.exec(version.trim());
if (!m) return null;
const n = Number(m[1]);
return Number.isFinite(n) ? n : null;
}
/**
* true — пакет выглядит как актуальный (v11+).
* Если версия не указана — разрешаем (попробуем прочитать данные).
* Если явно только старая (<11) — отклоняем.
*/
export function isSupportedFoundryPackage(
manifest: Pick<FoundryPackageManifest, 'compatibility' | 'coreVersion'>,
): {
ok: boolean;
reason?: string;
} {
const compat: FoundryCompatibility = manifest.compatibility ?? {};
const majors = [compat.minimum, compat.verified, compat.maximum, manifest.coreVersion]
.map(parseMajor)
.filter((n): n is number => n !== null);
if (majors.length === 0) return { ok: true };
const maxMajor = Math.max(...majors);
const minMajor = Math.min(...majors);
// Явно только до v10 и ниже.
if (typeof compat.maximum === 'string') {
const max = parseMajor(compat.maximum);
if (max !== null && max < 11) {
return {
ok: false,
reason: `Пакет Foundry слишком старый (maximum ${compat.maximum}). Нужна версия 11+.`,
};
}
}
if (maxMajor < 11) {
return {
ok: false,
reason: `Пакет Foundry слишком старый (обнаружена версия ${String(maxMajor)}). Нужна версия 11+.`,
};
}
// Если минимум уже далеко в будущем — всё равно пробуем, если пересекается с 11–13.
const overlapsSupported = majors.some((m) => FOUNDRY_SUPPORTED_MAJORS.has(m)) || minMajor <= 13;
if (!overlapsSupported) {
return {
ok: false,
reason: `Версия Foundry не поддерживается импортом (нужны 11–13).`,
};
}
return { ok: true };
}