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

Фреймворки на коде

Схемы фреймворков

Объявите входные данные, действия, производные значения, фреймы, контент и дочерние фреймворки для одного вида элементов.

Файл фреймворка

Каждый файл в schemas/ объявляет один фреймворк. На страницах справочника Keeper из быстрого старта растёт дальше. Теперь у него есть выбор Calling, производное значение Guard, готовый пресет, раздел и дочерний Lamp.

manifest/project.json
schemas/keeper.json
schemas/lamp.json
rules/keeper.ts
rules/lamp.ts
rules/hurt.ts
rules/heal.ts
rules/guard.ts
layouts/keeper.json
layouts/lamp.json
scenarios/new-keeper.json
scenarios/heal.json
scenarios/new-lamp.json

Основные поля

ПолеОбязательноЗначение
idДаУникальный идентификатор фреймворка. Его используют манифест и сценарии.
nameДаОтображаемое название.
elementTypeДаЧто он создаёт: character для игровых персонажей, item для предметов, spell для заклинаний. Можно использовать свои типы.
constructorДаПуть к файлу правил, который создаёт новый элемент.
inputsНетЧто игрок вводит при создании элемента. Каждый ключ соответствует параметру конструктора.
actionsНетЧто может делать элемент. У каждого действия есть идентификатор, название и файл правил.
ledgerFunctionsНетПроизводные значения. Каждое нацелено на хранимое поле и имеет файл правил.
framesДаОт одного до шестнадцати фреймов. Ровно у одного default равен true.
sectionsНетГруппирует поля ввода по шагам мастера создания.
contentНетГотовые пресеты. У каждого есть идентификатор, название и значения полей ввода.
childFrameworksНетСопоставляет имя дочернего слота с другим фреймворком той же игры.

В GAME входной фреймворк в манифесте должен быть игровым персонажем.

The entry framework must be a character constructor.

Поля ввода

Поле ввода настраивает один параметр конструктора, и его ключ должен совпадать с именем параметра. Большинству полей нужна только метка label. Остальные ключи сужают то, что может ввести игрок.

schemas/keeper.json
  "inputs": {
    "name": { "label": "Name" },
    "max_hp": {
      "label": "Maximum HP",
      "description": "How much damage a Keeper can take.",
      "defaultValue": 10,
      "min": 1,
      "max": 40
    },
    "calling": {
      "label": "Calling",
      "options": ["Guard", "Scout"],
      "defaultValue": "Guard"
    },
    "lamp": { "label": "Lamp", "defaultValue": "lamp", "hidden": true }
  },
  1. Строки 8-14 min и max ограничивают число. Сценарий или пресет за этими границами не пройдёт проверку.
  2. Строки 15-19 options задаёт фиксированный список вариантов. Значение по умолчанию должно быть одним из них.
  3. Строка 20 Входные данные hidden хранят значение, которого игрок не видит. Здесь оно подключает дочерний Lamp.
schemas/keeper.json
schemas/keeper.json
{
  "id": "keeper",
  "name": "Keeper",
  "elementType": "character",
  "constructor": "rules/keeper.ts",
  "inputs": {
    "name": { "label": "Name" },
    "max_hp": {
      "label": "Maximum HP",
      "description": "How much damage a Keeper can take.",
      "defaultValue": 10,
      "min": 1,
      "max": 40
    },
    "calling": {
      "label": "Calling",
      "options": ["Guard", "Scout"],
      "defaultValue": "Guard"
    },
    "lamp": { "label": "Lamp", "defaultValue": "lamp", "hidden": true }
  },
  "childFrameworks": { "lamp": "lamp" },
  "sections": [
    {
      "id": "basics",
      "title": "Basics",
      "inputs": ["name", "max_hp", "calling", "lamp"]
    }
  ],
  "actions": [
    { "id": "hurt", "name": "Take damage", "source": "rules/hurt.ts" },
    { "id": "heal", "name": "Heal", "source": "rules/heal.ts" }
  ],
  "ledgerFunctions": [
    {
      "id": "guard",
      "name": "Guard",
      "field": "guard",
      "source": "rules/guard.ts"
    }
  ],
  "frames": [
    { "id": "sheet", "source": "layouts/keeper.json", "default": true }
  ],
  "content": [
    {
      "id": "mira-scout",
      "name": "Mira the Scout",
      "parameters": {
        "name": "Mira",
        "max_hp": 8,
        "calling": "Scout",
        "lamp": "lamp"
      }
    }
  ]
}
КлючЗначение
labelТекст, который видит игрок.
descriptionТекст подсказки, до 1024 символов.
defaultValueЗначение, которое используется, если игрок не трогает поле.
optionsФиксированный список от 2 до 128 текстовых вариантов, каждый до 128 символов. Значение по умолчанию должно быть одним из них.
optionLabelsПодписи вариантов для игрока. Хранимым значением остаётся сам вариант.
min, maxГраницы для числа. min не должен быть больше max.
minLength, maxLengthГраницы длины текста.
requiredУкажите false, чтобы игрок мог оставить поле пустым.
hiddenУбирает поле из мастера. Скрытому полю нужен defaultValue.
visibleWhenПоказывает поле, только пока выполняется условие на других полях.

Значение по умолчанию, которого нет среди вариантов, отклоняется.

{
  "calling": {
    "label": "Calling",
    "options": ["Guard", "Scout"],
    "defaultValue": "Mage"
  }
}
Default for calling must be one of its options ("Guard", "Scout").

Сценарии и пресеты тоже должны укладываться в границы.

Parameter max_hp 99 is not between 1 and 40.

Скрытое поле без значения по умолчанию даёт ошибку:

Input name is hidden, so it must declare a defaultValue.

Действия и функции журнала

У каждого действия есть идентификатор, название и файл с кодом. У каждой функции журнала то же самое, плюс поле field. Это хранимое значение, которое функция вычисляет, и оно должно быть скалярным полем, которое возвращает конструктор. Функция журнала читает значения и возвращает производное. Она никогда не пишет.

schemas/keeper.json
  "ledgerFunctions": [
    {
      "id": "guard",
      "name": "Guard",
      "field": "guard",
      "source": "rules/guard.ts"
    }
  ],
  1. Строки 36-39 id и name определяют функцию, field это хранимое значение, которое она выводит, а source это её файл правил.
rules/guard.ts
function base_guard(calling: string): number {
  return calling === "Guard" ? 3 : 1;
}

export function guard(currentValue: number, self: ElementRef): number {
  const calling: string = element.var(self, "calling") as string;
  return base_guard(calling);
}
  1. Строки 1-3 Вспомогательная функция. У неё нет export, и функция журнала вызывает её по имени.
  2. Строка 5 Функция журнала получает своё текущее значение и элемент.
  3. Строка 6 Читайте хранимые значения через element.var.
  4. Строка 7 Верните производное значение.
Ledger function must target an existing scalar field.

Идентификаторы должны быть уникальными внутри Framework. Действия и функции журнала используют один общий набор идентификаторов.

Duplicate semantic identity or source path.

Две функции журнала могут использовать один исходный файл. Действие и функция журнала не могут:

action source does not match its owner contract

Входные данные действия

Входы действия являются параметрами его функции. Их объявляет сигнатура, а схема может добавить запись для каждого. Каждый вход представляет собой число, текст или значение «истина/ложь».

Когда игрок нажимает кнопку, диалог запрашивает каждый вход. Диалог называет вход по имени параметра, если запись не задаёт метку. Запись может также задать описание и значение по умолчанию. У числового входа могут быть минимум и максимум. У текстового входа может быть список вариантов или варианты из таблицы. Диалог показывает всё это, а сервер отклоняет значение вне границ или вариантов. Кнопка не может передать значение. Назовите каждый параметр так, чтобы игрок мог его прочитать: roll, а не r.

schemas/keeper.json
    {
      "id": "check_lore",
      "name": "Lore check",
      "source": "rules/check-lore.ts",
      "inputs": { "roll": { "label": "Roll", "min": 1, "max": 100 } }
    }
  1. Строка 24 Запись входа броска. Диалог показывает метку, а сервер обеспечивает минимум и максимум.

Минимум и максимум не мешают числу с дробной частью. Если правилу нужно целое число, оно проверяет это само, а шаг сценария это доказывает. Шаг сценария вне границ отклоняется.

rules/check-lore.ts
export function check_lore(self: ElementRef, roll: number): void {
  if (math.floor(roll) === roll) {
    const lore: number = element.var(self, "lore") as number;
    ledger.set(self, "last_target", lore);
    ledger.set(self, "last_roll", roll);
  }
}
  1. Строка 1 Бросок это входное значение, которое вводит игрок.
  2. Строка 2 Игнорируйте число с дробной частью. Границы входа уже удерживают его между 1 и 100.
  3. Строки 4-5 Запишите цель и бросок.

Текстовое входное значение может вместо этого предлагать список вариантов из таблицы правил. Страница «Таблицы правил» показывает, как.

Текстовый вход может вместо этого выбирать по имени одно из сохранённых полей Framework с помощью списка fields. Страница «Написание правил» показывает действие, которое его использует.

Числовой вход может попросить сервер бросить его. Добавьте ключ roll с выражением кубиков, например "roll": "1d100" или "roll": "2d6+1". Тогда диалог показывает кнопку «Бросить» вместо поля, в которое игрок вводит число. Сервер бросает, а диалог показывает сумму, которую игрок не может изменить. Бросать можно только числовой параметр. Компилятор отклоняет roll вместе с defaultValue и выражение кубиков, которое сервер не принимает.

Бросаемый вход тратится один раз: действие берёт бросок при выполнении, а новый бросок аннулирует прежние неиспользованные броски этого входа. Бросок привязан к своей кампании и истекает через 15 минут. Страница «Макеты и листы» показывает, что видит стол.

Строка журнала действия

Действие может опубликовать строку с результатом в журнале стола. Добавьте шаблон журнала в запись действия в схеме, например "log": "{name} takes {amount} damage, defense {defense}". Сервер подставляет каждое поле {name} как обычный текст после сохранения действия. Браузер эту строку никогда не отправляет.

Подстановка может называть входные данные действия или числовое, текстовое либо логическое поле того же Framework. Компилятор отклоняет скрытые входные данные, поле здоровья (hp, current_hp, hit_points, temp_hp, current_health, max_health, hitPoints, а также поля здоровья и состояний боевого блока), несуществующее имя, шаблон длиннее 200 символов, больше 16 подстановок, одиночную скобку и управляющий символ. Если входные данные и поле называются одинаково, побеждают входные данные. Значения берутся после выполнения действия, поэтому поле, которое записало действие, показывает новое значение. Вычисляемое поле не пересчитывается и показывает значение до действия.

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

Пресеты контента

Пресет это именованный набор значений полей ввода, например готовый персонаж. Fabled проверяет каждый пресет как сценарий, поэтому каждое значение должно подходить своему полю. В игре может быть до 512 пресетов на фреймворк.

schemas/keeper.json
  "content": [
    {
      "id": "mira-scout",
      "name": "Mira the Scout",
      "parameters": {
        "name": "Mira",
        "max_hp": 8,
        "calling": "Scout",
        "lamp": "lamp"
      }
    }
  ]
  1. Строки 47-48 У пресета есть id и name.
  2. Строки 49-54 Значения входных данных, которые заполняет пресет. Они должны укладываться в границы входных данных.

Разделы

Разделы группируют поля ввода по шагам мастера создания. Если фреймворк объявляет разделы, каждое поле должно входить в один из них, в том числе скрытые.

schemas/keeper.json
  "sections": [
    {
      "id": "basics",
      "title": "Basics",
      "inputs": ["name", "max_hp", "calling", "lamp"]
    }
  ],
  1. Строки 25-27 У раздела есть id, title и входные данные, которые он показывает. Каждое входное значение должно быть в одном из разделов.
Input lamp is not assigned to any section.

Дочерние фреймворки

Дочерний фреймворк это часть большего элемента, например Lamp, который носит Keeper. У дочернего есть своя схема, конструктор, фрейм и сценарий.

schemas/keeper.json
  "childFrameworks": { "lamp": "lamp" },
  1. Строка 22 Связывает дочерний слот lamp с фреймворком с id lamp.
schemas/lamp.json
{
  "id": "lamp",
  "name": "Lamp",
  "elementType": "item",
  "constructor": "rules/lamp.ts",
  "inputs": {
    "name": { "label": "Name" },
    "oil": { "label": "Oil", "defaultValue": 5 }
  },
  "frames": [{ "id": "card", "source": "layouts/lamp.json", "default": true }]
}
  1. Строки 6-9 Входные данные дочернего элемента. Дочернему элементу тоже нужен name.
  2. Строка 10 У дочернего элемента есть свой фрейм.

Родитель сопоставляет имя слота с идентификатором фреймворка в childFrameworks. Ссылку хранит скрытое поле ввода. Конструктор возвращает входные данные дочернего элемента рядом со своими значениями.

rules/keeper.ts
export function construct(
  name: string,
  max_hp: number,
  calling: string,
  lamp: FrameworkRef,
): {
  values: { name: string; max_hp: number; hp: number; calling: string; guard: number };
  children: { lamp: { framework: FrameworkRef; params: { name: string; oil: number } } };
} {
  return {
    values: { name: name, max_hp: max_hp, hp: max_hp, calling: calling, guard: 0 },
    children: { lamp: { framework: lamp, params: { name: "Lantern", oil: 5 } } },
  };
}
  1. Строка 5 lamp это FrameworkRef. Платформа превращает id фреймворка из входных данных в него.
  2. Строки 6-9 Тип результата перечисляет хранимые значения и дочерние слоты.
  3. Строка 11 Начальные значения Keeper.
  4. Строка 12 Дочерний слот: какой фреймворк построить и какие входные данные ему передать.

Каждый фреймворк, включая дочерние, должен возвращать name.

must define the required 'name' value, normally as a string constructor field.

Слот, который указывает на фреймворк вне игры, даёт ошибку:

Invalid child Framework constraint: lamp.

Сложные поля

ПолеНазначение
listsКонтент, который персонаж получает в игре.
contentChoicesМеста выбора, где игрок выбирает одну запись Framework с контентом.
slotsСредства выбора для повторяемых вариантов.
poolsОбщие пулы, из которых берутся числовые входные данные.
modifiersЧисла, которые платформа выводит из эффектов контента.
effectsКак Framework с контентом называет модификатор и величину.
levellingПовышение или понижение персонажа на один уровень после создания.
rebuildЧто перестройка делает с состоянием игры, например при смене уровня.
combatРоли, которые читают боевые инструменты стола.
catalogueЗаписи контента, которые хранятся вне исходников и добавляются частями.
tablesОбщие таблицы данных.
playReadsВыведенные значения, которые стол читает из хранимых атрибутов.
rulesAttacksАтаки, которые есть у каждого персонажа.
optionsFromБерёт варианты входного значения из другого источника.
rich, richTextОтмечает текстовое входное значение как форматированный текст.

Дальше