Перейти к содержимому

Формат персонажа

Формат персонажа для генерации и импорта в лист Long Story Short.

Артефакт URL
Схема содержимого data https://longstoryshort.app/schema/v1/character.json
Готовый пример персонажа (валиден по схеме) https://longstoryshort.app/schema/v1/character-example.json
Заклинания SRD 5.1: слаг, id, имя, круг, классы https://longstoryshort.app/datasets/srd-spell-slugs-2014.json
Заклинания SRD 5.2: то же https://longstoryshort.app/datasets/srd-spell-slugs-2024.json

JSON Schema — draft-07.

Персонаж — это объект с несколькими полями верхнего уровня, один из которых (data) — вложенный JSON, закодированный строкой:

Поле Тип Описание
jsonType "character" Константа, тип документа.
version "2" Версия формата файла.
edition "2014" | "2024" Механическая редакция правил. По умолчанию "2024".
sheetEdition "2014" | "2024" Косметическая редакция — вёрстка классического листа (печатная форма). Независима от edition.
spells объект Настройки книги заклинаний: mode ("cards" | "text"), prepared и book — массивы заклинаний: id из базы Long Story Short или слаг ("cure-wounds"), см. «Заклинания»; pending — слаги, которые ещё не нашлись ({ slug, list }[], заполняет сервер, писать самим не нужно); granted — заклинания, выданные датасетом ({ id, source }[], вне лимита подготовленных, пишет только визард); slotless — id заклинаний, за которые нельзя заплатить ячейкой (string[]).
disabledBlocks объект Какие блоки листа скрыты, по спискам ключей блоков: info-left, info-right, subinfo-left, subinfo-right, notes-left, notes-right. Пустые массивы — ничего не скрыто.
data строка (JSON) Всё содержимое листа персонажа — имя, характеристики, класс, снаряжение, заклинания, заметки. Описано схемой ниже.

Пример верхнего уровня:

{
"jsonType": "character",
"version": "2",
"edition": "2024",
"sheetEdition": "2024",
"spells": { "mode": "cards", "prepared": [], "book": [], "granted": [], "slotless": [] },
"disabledBlocks": { "info-left": [], "info-right": [], "subinfo-left": [], "subinfo-right": [], "notes-left": [], "notes-right": [] },
"data": "{ ...см. ниже... }"
}

data — это отдельный JSON-объект, закодированный строкой (значение поля — строка, а не вложенный объект: соберите объект по описанной ниже модели, затем примените к нему JSON.stringify). Полная схема со всеми полями, типами и допустимыми значениями — character.json (ссылка выше); ниже — обзор структуры.

Поле Что это
name Имя персонажа, { value }.
template Визуальная тема листа (default, aime, …).
info Класс, подкласс, уровень, предыстория, раса (+ опционально subrace), мировоззрение, опыт, размер. У charClass/charSubclass/background/race есть необязательный id — ссылка на датасет, голый id без префикса источника (cleric, не srd-2024:cleric); то же для spellsInfo.available.classes ниже. Это отдельный формат от bonuses[*].source.refId, который как раз с префиксом (srd-2024:cleric) — тот адресует контент датасета, а не classId.
subInfo Возраст, рост, вес, глаза, кожа, волосы.
spellsInfo Блок каста: базовая характеристика, кастомные DC/атака; charges — привязка «заклинание → счётчик» ({ [spellId]: resourceId }, id ресурса из resources), abilities — заклинательная характеристика конкретного заклинания ({ [spellId]: "int" | "wis" | "cha" }). Последние два — свойства этого персонажа, а не заклинания: коллекция заклинаний дедуплицируется между игроками, и там таким полям не место.
spells / spellsPact Счётчик слотов заклинаний по кругам. У персонажа из визарда это база свёртки, а не итог: поверх неё лежат выданные классом ячейки и правка игрока, и на листе видно другое число. У листа, собранного руками, слоёв нет и напечатанное — единственный ответ.
bonuses Массив числовых бонусов (единственный канал — см. ниже).
proficiency / proficiencyCustom Бонус мастерства и его переопределение. Переопределение читается как proficiencyCustom || proficiency — 0 фолсиев и трактуется как «не задано», а не как принудительный ноль; форсировать бонус мастерства в 0 сейчас нечем.
stats Характеристики (str/dex/con/int/wis/cha), поле score.
saves Спасброски, владение по характеристикам.
skills 18 навыков SRD, владение/экспертиза по каждому.
vitality HP, кости хитов, AC, скорость, щит, инициатива, состояние умирания.
attunementsList Слоты присоединения к магическим предметам.
weaponsList Список оружия: урон, характеристика атаки, владение.
text Текстовые блоки (черты, снаряжение, заметки, личность и т.д.).
coins Монеты по номиналам.
resources Именованные счётчики (использования способностей и т.п.) — устройство виджета описано в «Ресурс».
conditions Активные состояния.
exhaustion, inspiration, avatar, prof Истощение, вдохновение, портрет, владения бронёй/оружием.

Все обычные редактируемые поля обёрнуты в { value: ... }.

Не путайте — в модели три независимых сущности с похожим именем:

Где Что это
spells (верхний уровень документа) настройки книги заклинаний: режим отображения, сами заклинания (id или слаги), выданные датасетом (granted) и то, за что нельзя заплатить ячейкой (slotless)
data.spellsInfo блок каста: базовая характеристика, кастомные DC/атака
data.spells / data.spellsPact счётчик слотов заклинаний по кругам, не содержимое книги

Содержимое книги заклинаний (какие заклинания известны/подготовлены) — ссылки на заклинания в базе Long Story Short, не их текст. Как на них сослаться — ниже.

В spells.prepared и spells.book можно класть и id заклинания, и его слаг — английское имя в нижнем регистре через дефис:

"spells": { "mode": "cards", "prepared": ["guidance", "cure-wounds", "silvery-barbs"], "book": [] }

Слаг проще и годится для любого заклинания. Вместо него можно написать и английское имя ("Cure Wounds"), и имя в том виде, в каком оно хранится в базе ("Исцеление ран [Cure Wounds]") — сервер сведёт их к слагу сам. Имя только по-русски свести не к чему.

Что происходит дальше. При создании персонажа сервер оставляет в списках только id, а слаги откладывает в spells.pending. Лист ищет их при открытии: сначала среди заклинаний SRD редакции персонажа, затем в гримуаре владельца. Найденное встаёт в свой список как обычное заклинание. Ненайденное видно в настройках заклинаний — «2 заклинания не найдены: silvery-barbs, …» — и убирается кнопкой «Очистить». Поиск повторяется при каждом открытии листа: стоит игроку загрузить заклинание в гримуар, и оно появится само.

Чтобы модель выбирала из того, что точно найдётся, дайте ей справочник SRD той же редакции (ссылки в «Артефактах») — по записи на заклинание:

{ "slug": "cure-wounds", "id": "698197c2a5dc6923c7028c8c", "name": "Исцеление ран [Cure Wounds]", "level": 1, "classes": ["artificer", "bard", "cleric", "druid", "paladin", "ranger"] }

Отфильтруйте по classes и level, отдайте модели список. classes — «в чьём списке есть», без подклассов: Мистическому рыцарю берите по wizard.

  • Куда класть. На листе показываются prepared — известные или подготовленные заклинания, заговоры тоже здесь. book — только книга волшебника: в неё всё, что в книге, а в prepared — подготовленное из неё. Остальным классам book не нужен. granted пишет только визард, слаги там не принимаются.
  • Заклинание не из SRD найдётся, только если оно есть в гримуаре того, кто импортирует персонажа. Общего каталога у нас нет: у каждого игрока свои заклинания. Раздавая персонажа с такими заклинаниями, отдайте вместе с ним файл заклинаний.
  • Редакция. Слаг от редакции не зависит — заклинание ищется под edition персонажа. id зависит: у SRD 5.1 и SRD 5.2 разные документы, и персонаж 2024 не увидит id заклинания 2014 — оно попадёт в те же «не найдены».
  • Слаг и id любого заклинания, в том числе своего, видны в его карточке: внизу, в свёрнутой «Сервисной информации».

Единственный канал числовых бонусов — массив data.bonuses. У каждого — цель (target, закрытый список в схеме), величина или формула, источник. Как слои складываются (mode/priority, порядок применения) — в статье «Система бонусов».

Текстовые блоки (text.<ключ>.value.data) — документ форматированного редактора, не HTML-строка. Минимально валидный:

{ "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "..." }] }] }

Кроме paragraph/text/bulletList/orderedList/listItem редактор поддерживает roller, formula, resource, divider, spoiler/spoilerSummary/spoilerContent, taskList/taskItem — их атрибуты не валидируются схемой, ориентируйтесь на реальные экспорты. blockquote не поддерживается, несмотря на то что раньше значился в схеме как «безопасный» тип — уберите его из генератора, если он там есть. Любой нераспознанный тип узла — это не мягкий пропуск блока, а хард-креш рендера всего текстового блока целиком (в редакторе нет error boundary), так что незнакомый тип узла — самая дорогая ошибка формата для генератора.

Дайс/формула-нотация: <количество>d<грани> или <количество>к<грани> (кириллица наравне с латиницей), переменные в [КВАДРАТНЫХ_СКОБКАХ]: [STR]/[DEX]/… (МОДИФИКАТОР характеристики, не значение), плюс [PROF]/[LVL]. Только латиница. Полный список переменных и функций — в статье «Поля с формулами».

Регистр переменной важен не везде: в большинстве формульных полей (ac, speed, shield.mod, initiative, кастомные DC/атака каста, переопределения бонуса навыка/спасброска, формулы ресурсов, expr у бонуса) переменную нужно писать строго заглавными — 10+[DEX], не 10+[dex]. Единственное исключение — урон оружия (weaponsList[*].dmg), там регистр не важен. Если сомневаетесь — пишите везде заглавными, это работает во всех полях.

Схема проверяет форму, а не смысл.

Схема поймает Схема не поймает
Вычисляемое поле там, где должно быть только «сырое» (например, модификатор характеристики, итоговый урон) info.charClass.id, не указывающий на существующий класс датасета
Бонус на несуществующий или неживой таргет Бонус на оружие с id, которого нет в weaponsList
Отсутствие обязательного навыка/характеристики/спасброска Ресурс, чей location не указывает на реальный текстовый блок
Устаревший канал бонусов на верхнем уровне Формулу, которая не вычисляется
Опечатку в имени поля Дубли id внутри списков

Схема пишется с позиции генератора: что можно и нужно писать, создавая персонажа с нуля. Валидатор, который сверяет уже существующий экспорт из приложения, легитимно увидит в нём чуть больше — это не расхождение схемы с приложением, а разная аудитория:

  • data.bonusesSkills / data.bonusesStats — deprecated канал бонусов, ещё не заменённый: пара переключателей в интерфейсе (например, чекбокс «Мастер на все руки» у барда) всё ещё пишет туда. Не авторизуйте их сами; валидатор должен их принимать.
  • data.createdAt — бэкенд подставляет дату создания персонажа в data при отдаче по GET /character/:id, так что она попадает в любой скачанный экспорт. Ни на что не влияет при сохранении обратно.
  • data.wizardStep/choices — мёртвые ключи на персонажах, созданных до переноса состояния визарда в отдельную колонку; ничего их не читает.

Это не то же самое, что «схема не описывает баг»: resources.*.resolvedMax/ resolvedShortRegain и id внутри text.<ключ>.value были утечкой вычисленного/сессионного состояния в персистентные данные и починены в коде — но уже сохранённые до фикса персонажи могут донести старое значение до следующего редактирования соответствующего поля.