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

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

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

Артефакт URL
Схема содержимого data https://longstoryshort.app/schema/v1/character.json
Готовый пример персонажа (валиден по схеме) https://longstoryshort.app/schema/v1/character-example.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 (эти id нельзя придумать, только взять реальные); 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 || proficiency0 фолсиев и трактуется как «не задано», а не как принудительный ноль; форсировать бонус мастерства в 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; сгенерировать их «из головы» нельзя.

Единственный канал числовых бонусов — массив 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 были утечкой вычисленного/сессионного состояния в персистентные данные и починены в коде — но уже сохранённые до фикса персонажи могут донести старое значение до следующего редактирования соответствующего поля.