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

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

Пулы и модификаторы

Разделите бюджет очков между входными данными и позвольте контенту менять числа персонажа.

Пулы

Пул ограничивает группу числовых входных данных, которые игрок заполняет вместе, например три черты с общим бюджетом очков. Сервер проверяет каждый пул, когда персонажа создают или меняют.

schemas/keeper.json
  "pools": {
    "traits": {
      "label": "Traits",
      "inputs": ["might", "wit", "grace"],
      "methods": {
        "pointBuy": {
          "budget": 6,
          "min": 1,
          "max": 5,
          "costs": { "1": 0, "2": 1, "3": 2, "4": 4, "5": 6 }
        }
      }
    }
  }
  1. Строка 23 Числовые входные данные пула. Каждому нужна запись в inputs.
  2. Строка 24 Методы, которые может использовать игрок.
  3. Строка 26 Общая цена значений не должна превышать бюджет.
  4. Строка 27 Каждое значение не меньше этого.
  5. Строка 28 Каждое значение не больше этого.
  6. Строка 29 Цена каждого значения. Она нужна для каждого значения от min до max.
МетодЧто проверяет
standardArrayЗначения это перечисленные числа в любом порядке.
pointBuyКаждое значение лежит между min и max, а сумма цен не больше бюджета.
manualКаждое значение лежит между min и max.
rollСервер бросает count значений кубиками dice, прибавляет необязательный bonus, умножает на необязательный multiplier, а игрок распределяет итоги по полям.
budgetКаждое значение лежит между min и max, а сумма значений не больше total.

Пул должен объявлять хотя бы один метод. Пул с несколькими методами называет в methodInput входное значение, которое выбирает метод. Пресеты и сценарии должны удовлетворять пулу, и сценарий это показывает:

scenarios/new-keeper.json
{
  "id": "new-keeper",
  "framework": "keeper",
  "parameters": {
    "name": "Mira",
    "max_hp": 10,
    "might": 3,
    "wit": 2,
    "grace": 1
  },
  "expectedValues": {
    "name": "Mira",
    "max_hp": 10,
    "hp": 10,
    "might": 3,
    "wit": 2,
    "grace": 1
  }
}
  1. Строки 7-9 Эти значения стоят 2, 1 и 0, поэтому сумма равна 3, а бюджет равен 6.
is not satisfied: pointBuy.budget
pointBuy costs has no entry for 5
A pool declares at least one method.
ОграничениеЗначение
Пулов на Framework4
Входных данных в пуле64
Значений между min и max64
Бюджет1000

Метод budget принимает total, min и max. Каждое значение это целое число от min до max, а сумма значений не больше total. У значений нет цены, поэтому метод подходит для бюджета очков навыков и не требует стоимостей.

Брошенные значения

Метод roll заставляет сервер бросать значения, так что игрок не может ввести свои. У него есть dice и count, а также два необязательных ключа, bonus и multiplier. dice это одна группа кубиков, например "3d6" или "4d6dl1". count это сколько значений бросает сервер, и оно равно числу полей пула. bonus (от -20 до 100) прибавляется к каждой сумме кубиков, затем multiplier (от 1 до 10) её умножает, так что каждое значение равно (сумма кубиков + bonus) × multiplier. Характеристика 3d6 × 5, как в «Зове Ктулху», это dice "3d6" с multiplier 5.

schemas/keeper.json
  "pools": {
    "traits": {
      "label": "Traits",
      "inputs": ["might", "wit", "grace"],
      "methodInput": "method",
      "methods": {
        "manual": { "min": 15, "max": 90 },
        "roll": { "dice": "3d6", "count": 3, "multiplier": 5 }
      }
    }
  }
  1. Строка 29 Пул с несколькими методами называет вход, который выбирает метод.
  2. Строка 31 Игрок может вписать значения сам.
  3. Строка 32 Либо сервер бросает 3 значения по 3d6, по одному на каждый вход.

methodInput называет текстовое поле, которое выбирает метод пула. Выбор игрока в этом поле решает, какой метод проверяет сервер, поэтому одни и те же значения можно проверить по стандартному набору, по покупке очков или по броску. Пулу с одним методом methodInput не нужен, и он всегда использует этот метод. При нескольких методах поле обязательно, а options поля должны в точности совпадать с названиями методов пула. Сценарий или пресет задаёт поле одним из этих названий. Отсутствующее значение или значение, не являющееся названием метода, приводит к ошибке method.missing или method.unknown.

schemas/keeper.json
    "method": {
      "label": "Scores by",
      "options": ["manual", "roll"],
      "defaultValue": "manual"
    },
  1. Строка 11 Ровно методы пула и ничего больше.

Каждая сумма, которую может дать бросок, с учётом bonus и multiplier, должна укладываться в min и max каждого поля. 3d6 × 5 даёт от 15 до 90, поэтому каждая черта допускает от 15 до 90. Кубики составляют одну группу: до 10 кубиков не более чем со 100 гранями, а dl, dh, kl или kh отбрасывает или оставляет от 1 до 9 из них, причём остаётся хотя бы один. Множитель записывайте ключом multiplier, а не внутри кубиков: 3d6x5 отклоняется.

The roll dice must be one group such as
outside the min/max of input
give exactly one roll per input.

Отсутствующий ключ показывается как список JSON. Путь в списке называет ключ, здесь pools, traits, methods, roll, dice, а сообщение гласит Required.

Required

В форме создания персонажа пул с roll показывает вместо входов кнопку «Бросить значения», а после первого броска «Перебросить всё». Бросает сервер и хранит запись, поэтому браузер никогда не бросает. Затем игрок распределяет суммы по входам в любом порядке. Бросок доступен только в форме создания.

Сценарий, выбирающий метод roll, сам задаёт броски в poolRolls, потому что настоящие броски есть только на сервере. poolRolls сопоставляет идентификатору пула по одному слоту на каждое брошенное значение. У слота есть total (итоговое число после bonus и multiplier) и необязательные dice и dropped, которые нужны только для чтения. Проверка пула тогда считает слоты записью сервера, так что значения сценария должны быть этими суммами в любом порядке. Каждая сумма должна быть такой, которую может дать бросок. Без poolRolls сценарий, выбирающий roll, завершается ошибкой roll.missing. Пресет не может использовать метод roll. Форма создания проверяет настоящий бросок.

scenarios/new-keeper.json
{
  "id": "new-keeper",
  "framework": "keeper",
  "parameters": {
    "name": "Mira",
    "max_hp": 10,
    "method": "roll",
    "might": 60,
    "wit": 45,
    "grace": 75
  },
  "expectedValues": {
    "name": "Mira",
    "max_hp": 10,
    "hp": 10,
    "might": 60,
    "wit": 45,
    "grace": 75,
    "method": "roll"
  },
  "poolRolls": {
    "traits": [
      { "total": 45, "dice": [3, 4, 2] },
      { "total": 60, "dice": [5, 4, 3] },
      { "total": 75, "dice": [6, 5, 4] }
    ]
  }
}
  1. Строка 7 Сценарий выбирает метод roll.
  2. Строки 21-27 Записанные броски. Каждый total это сумма кубиков, умноженная на multiplier, здесь 9 × 5 = 45.
is not satisfied: roll.missing

Модификаторы и эффекты

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

Framework персонажа объявляет модификатор.

schemas/keeper.json
  "modifiers": {
    "mod_guard": {
      "label": "Guard",
      "stack": "sum",
      "each": { "min": -3, "max": 3 },
      "total": { "min": -5, "max": 5 },
      "appliesTo": ["guard"]
    }
  },
  1. Строка 25 Название, которое видят игроки.
  2. Строка 26 sum складывает все вклады. max берёт наибольший.
  3. Строка 27 Диапазон одного вклада.
  4. Строка 28 Диапазон суммы.
  5. Строка 29 Поле журнала, которое питает модификатор. Это подпись для людей. Расчёты делают ваши правила.

Список сообщает, что его записи применяют эффекты, и может их ограничивать.

schemas/keeper.json
  "lists": {
    "worn": {
      "label": "Worn",
      "of": "charm",
      "max": 3,
      "effects": true,
      "distinctBy": "name",
      "caps": [
        { "label": "Rings", "where": { "slot": { "eq": "Ring" } }, "max": 1 }
      ]
    }
  },
  1. Строка 37 Записи этого списка меняют модификаторы.
  2. Строка 38 Запись с тем же именем, что у записи в списке, отклоняется с кодом SAME_NAME.
  3. Строки 39-41 Не больше 1 записи со слотом Ring. Следующая отклоняется с кодом LIST_CAP.

Framework с контентом называет свои входные данные эффекта. Не указывайте их варианты и диапазоны, потому что компилятор заполняет их из модификаторов персонажа.

schemas/charm.json
{
  "id": "charm",
  "name": "Charm",
  "elementType": "item",
  "constructor": "rules/charm.ts",
  "inputs": {
    "name": { "label": "Name" },
    "slot": {
      "label": "Slot",
      "options": ["Ring", "Cloak"],
      "defaultValue": "Ring"
    },
    "effect_1": { "label": "Effect" },
    "amount_1": { "label": "Amount" }
  },
  "effects": [{ "target": "effect_1", "amount": "amount_1" }],
  "frames": [
    { "id": "charm-card", "source": "layouts/charm.json", "default": true }
  ],
  "content": [
    {
      "id": "ring-of-guard",
      "name": "Ring of Guard",
      "parameters": {
        "name": "Ring of Guard",
        "slot": "Ring",
        "effect_1": "Guard",
        "amount_1": 2
      }
    },
    {
      "id": "ring-of-calm",
      "name": "Ring of Calm",
      "parameters": {
        "name": "Ring of Calm",
        "slot": "Ring",
        "effect_1": "None",
        "amount_1": 0
      }
    },
    {
      "id": "cloak-of-guard",
      "name": "Cloak of Guard",
      "parameters": {
        "name": "Cloak of Guard",
        "slot": "Cloak",
        "effect_1": "Guard",
        "amount_1": 1
      }
    },
    {
      "id": "cloak-spare",
      "name": "Cloak of Guard",
      "parameters": {
        "name": "Cloak of Guard",
        "slot": "Cloak",
        "effect_1": "Guard",
        "amount_1": 1
      }
    }
  ]
}
  1. Строки 13-14 У эффекта и его величины нет вариантов и диапазона.
  2. Строка 16 Один эффект: какое входное значение называет модификатор и какое хранит величину.
  3. Строка 27 Пресет задаёт метку модификатора и величину.

Функция журнала читает модификатор как любое другое поле.

rules/guard.ts
export function guard(currentValue: number, self: ElementRef): number {
  return 10 + (element.var(self, "mod_guard") as number);
}
  1. Строка 2 mod_guard объявлен в Framework и не возвращается конструктором.

Сценарий проверяет каждый случай. expectedValuesAfter проверяет выведенное значение после шага, а expectedModifiers проверяет модификаторы в конце.

scenarios/wear.json
{
  "id": "wear-charms",
  "framework": "keeper",
  "parameters": { "name": "Mira", "max_hp": 10 },
  "expectedValues": { "name": "Mira", "max_hp": 10, "hp": 10, "guard": 10 },
  "actions": [
    {
      "action": "wear",
      "parameters": {},
      "choose": { "charm": "ring-of-guard" },
      "expectedWrites": [
        { "op": "list.add", "key": "worn", "value": "ring-of-guard" }
      ],
      "expectedValuesAfter": { "guard": 12 }
    },
    {
      "action": "wear",
      "parameters": {},
      "choose": { "charm": "ring-of-calm" },
      "expectedRefusal": "LIST_CAP",
      "expectedWrites": []
    },
    {
      "action": "wear",
      "parameters": {},
      "choose": { "charm": "cloak-of-guard" },
      "expectedWrites": [
        { "op": "list.add", "key": "worn", "value": "cloak-of-guard" }
      ],
      "expectedValuesAfter": { "guard": 13 }
    },
    {
      "action": "wear",
      "parameters": {},
      "choose": { "charm": "cloak-spare" },
      "expectedRefusal": "SAME_NAME",
      "expectedWrites": []
    },
    {
      "action": "remove_charm",
      "parameters": {},
      "choose": { "charm": "ring-of-guard" },
      "expectedWrites": [
        { "op": "list.remove", "key": "worn", "value": "ring-of-guard" }
      ],
      "expectedValuesAfter": { "guard": 11 }
    }
  ],
  "expectedLists": { "worn": ["cloak-of-guard"] },
  "expectedModifiers": { "mod_guard": 1 }
}
  1. Строка 14 Guard 12: 10 плюс 2 от кольца.
  2. Строка 20 Второе кольцо отклонено: лимит допускает 1.
  3. Строка 30 Guard 13 с 1 от плаща.
  4. Строка 36 Второй плащ с тем же именем отклонён.
  5. Строка 46 Guard 11 после того, как кольцо снято.
  6. Строка 50 Остаётся сумма: 1 от плаща.
Поле модификатораЗначение
labelНазвание, которое появляется в вариантах эффекта контента.
stacksum или max.
each, totalДиапазон одного вклада и суммы.
appliesToДо 8 названий полей, которые питает модификатор.
appliesWhenУсловие в виде подписи. Выполнение обеспечивают ваши правила.

Пока не описано

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

Дальше