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

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

Рецепт: бросок против навыка

Проверьте процентный бросок против значения навыка и покажите полосу успеха.

Цель

Пусть игрок бросает d100 против навыка Lore и видит полосу: Flawless, Strong, Pass или Miss. Чем ниже бросок, тем лучше.

Как бросок попадает в правило

Правила не умеют бросать кубики. Игрок бросает на листе и вводит число в действие. Правило проверяет число и записывает его. Игровой цикл состоит из четырёх шагов.

  1. Нажмите на значок кубиков

    На листе написано Roll: [[1d100]]. Нажатие бросает d100.

  2. Прочитайте бросок в журнале

    Результат появляется в журнале бросков стола и в чате. В персонажа пока ничего не записано.

  3. Запустите действие и введите бросок

    Нажмите кнопку Lore check. Fabled запросит бросок в диалоговом окне. Введите число из журнала.

  4. Полоса обновляется

    Правило записывает бросок и цель. Функция журнала выводит полосу, и лист её показывает.

Что менять

Добавьте в схему поле ввода Lore, действие и функцию журнала. У поля ввода конструктора могут быть границы.

schemas/keeper.json
    "lore": {
      "label": "Lore",
      "description": "The percentile a Lore check must roll under.",
      "defaultValue": 40,
      "min": 1,
      "max": 99
    }
  1. Строки 13-14 Эти границы действуют при создании персонажа. На бросок они не действуют.
schemas/keeper.json
    {
      "id": "check_lore",
      "name": "Lore check",
      "source": "rules/check-lore.ts",
      "inputs": { "roll": { "label": "Roll", "min": 1, "max": 100 } }
    }
  1. Строки 21-23 Действие проверки Lore и его файл правил.
  2. Строка 24 Вход Roll: метка и границы. Диалог показывает их, а сервер обеспечивает их соблюдение.
schemas/keeper.json
  "ledgerFunctions": [
    {
      "id": "last_band",
      "name": "Last band",
      "field": "last_band",
      "source": "rules/band.ts"
    }
  ]
  1. Строки 32-35 Функция журнала выводит last_band из двух полей, которые пишет действие.

Конструктор возвращает новые поля с временными значениями. Функция журнала заменяет last_band, когда элемент строится.

rules/keeper.ts
export function construct(
  name: string,
  max_hp: number,
  lore: number,
): {
  name: string;
  max_hp: number;
  hp: number;
  lore: number;
  last_roll: number;
  last_target: number;
  last_band: string;
} {
  return {
    name: name,
    max_hp: max_hp,
    hp: max_hp,
    lore: lore,
    last_roll: 0,
    last_target: 0,
    last_band: "",
  };
}
  1. Строки 10-12 Поля, которые пишет действие и читает функция журнала.
  2. Строки 19-21 Начальные значения: броска ещё не было.

Границы входа удерживают бросок между 1 и 100. Действие игнорирует число с дробной частью, а иначе записывает бросок и цель.

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 Запишите цель и бросок. Полоса следует из них.

Одна функция журнала превращает их в полосу. Её может использовать каждый навык, потому что каждое действие навыка пишет те же два поля.

rules/band.ts
export function last_band(currentValue: string, self: ElementRef): string {
  const roll: number = element.var(self, "last_roll") as number;
  const target: number = element.var(self, "last_target") as number;
  if (roll === 0) {
    return "No roll yet";
  }
  if (roll <= math.floor(target / 5)) {
    return "Flawless";
  }
  if (roll <= math.floor(target / 2)) {
    return "Strong";
  }
  if (roll <= target) {
    return "Pass";
  }
  return "Miss";
}
  1. Строки 2-3 Прочитайте два поля, которые записало действие.
  2. Строки 4-6 До первого броска сообщите об этом.
  3. Строки 7-15 Чем ниже бросок, тем лучше. Каждая полоса это доля цели.
  4. Строка 16 Выше цели это промах.

Для другого навыка скопируйте check-lore.ts, измените имя функции и поле в вызове element.var. Каждому навыку нужно своё действие, потому что имя поля пишется в коде явно.

Покажите на листе кубики, полосу и кнопку. Укажите полосу в requiredBindings, а действие в псевдонимах.

layouts/keeper.json
      { "binding": "element.attributes.last_band", "expectedType": "string" }
  1. Строка 15 Полоса это текст.
layouts/keeper.json
      { "name": "check_lore", "functionRef": "check_lore" }
  1. Строка 21 Псевдоним, который использует кнопка.
layouts/keeper.json
      { "kind": "text", "text": "Roll: [[1d100]]" },
      {
        "kind": "text",
        "text": "Last check: {{element.attributes.last_band}}"
      },
      { "kind": "button", "label": "Lore check", "actionRef": "check_lore" }
  1. Строка 35 Значок кубиков. Нажатие бросает кубики и показывает результат в журнале бросков.
  2. Строки 36-39 Полоса, которую вывела функция журнала.
  3. Строка 40 Кнопка, которая открывает диалог для броска.

Проверьте

Проверьте каждую полосу и бросок вне диапазона. Каждый шаг перечисляет две записи. expectedValuesAfter проверяет полосу после действия.

scenarios/lore.json
    {
      "action": "check_lore",
      "parameters": { "roll": 6 },
      "expectedWrites": [
        { "op": "ledger.set", "key": "last_target", "value": 40 },
        { "op": "ledger.set", "key": "last_roll", "value": 6 }
      ],
      "expectedValuesAfter": { "last_band": "Flawless" }
    },
  1. Строка 17 Число, которое ввёл игрок.
  2. Строки 18-21 Две записи по порядку.
  3. Строка 22 Производная полоса после действия.

Последним шагом идёт бросок 40,5. Он ничего не записывает, поэтому защита от дробей доказана. Бросок 500 не может быть шагом: он вне границ входа, и сценарий отклоняется.

scenarios/lore.json
    {
      "action": "check_lore",
      "parameters": { "roll": 40.5 },
      "expectedWrites": [],
      "expectedValuesAfter": { "last_band": "Miss" }
    }
  1. Строка 53 Число с дробной частью.
  2. Строка 54 Записей нет.
  3. Строка 55 Полоса остаётся такой, какой её оставил прежний шаг.

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

manifest/project.json
{
  "formatVersion": "fabled.game-project/v1",
  "projectId": "game-your-game-id",
  "projectKind": "GAME",
  "compilerProfile": "native-game-v1",
  "name": "Lantern Keepers",
  "entryFramework": "keeper",
  "frameworks": ["schemas/keeper.json"],
  "scenarios": [
    "scenarios/new-keeper.json",
    "scenarios/heal.json",
    "scenarios/lore.json"
  ]
}
  1. Строка 12 Новый файл сценария.

Дальше