Фреймворки на коде
Пулы и модификаторы
Разделите бюджет очков между входными данными и позвольте контенту менять числа персонажа.
Пулы
Пул ограничивает группу числовых входных данных, которые игрок заполняет вместе, например три черты с общим бюджетом очков. Сервер проверяет каждый пул, когда персонажа создают или меняют.
"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 }
}
}
}
}- Строка 23 Числовые входные данные пула. Каждому нужна запись в inputs.
- Строка 24 Методы, которые может использовать игрок.
- Строка 26 Общая цена значений не должна превышать бюджет.
- Строка 27 Каждое значение не меньше этого.
- Строка 28 Каждое значение не больше этого.
- Строка 29 Цена каждого значения. Она нужна для каждого значения от min до max.
| Метод | Что проверяет |
|---|---|
| standardArray | Значения это перечисленные числа в любом порядке. |
| pointBuy | Каждое значение лежит между min и max, а сумма цен не больше бюджета. |
| manual | Каждое значение лежит между min и max. |
| roll | Сервер бросает count значений кубиками dice, прибавляет необязательный bonus, умножает на необязательный multiplier, а игрок распределяет итоги по полям. |
| budget | Каждое значение лежит между min и max, а сумма значений не больше total. |
Пул должен объявлять хотя бы один метод. Пул с несколькими методами называет в methodInput входное значение, которое выбирает метод. Пресеты и сценарии должны удовлетворять пулу, и сценарий это показывает:
{
"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
}
}- Строки 7-9 Эти значения стоят 2, 1 и 0, поэтому сумма равна 3, а бюджет равен 6.
is not satisfied: pointBuy.budgetpointBuy costs has no entry for 5A pool declares at least one method.| Ограничение | Значение |
|---|---|
| Пулов на Framework | 4 |
| Входных данных в пуле | 64 |
| Значений между min и max | 64 |
| Бюджет | 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.
"pools": {
"traits": {
"label": "Traits",
"inputs": ["might", "wit", "grace"],
"methodInput": "method",
"methods": {
"manual": { "min": 15, "max": 90 },
"roll": { "dice": "3d6", "count": 3, "multiplier": 5 }
}
}
}- Строка 29 Пул с несколькими методами называет вход, который выбирает метод.
- Строка 31 Игрок может вписать значения сам.
- Строка 32 Либо сервер бросает 3 значения по 3d6, по одному на каждый вход.
methodInput называет текстовое поле, которое выбирает метод пула. Выбор игрока в этом поле решает, какой метод проверяет сервер, поэтому одни и те же значения можно проверить по стандартному набору, по покупке очков или по броску. Пулу с одним методом methodInput не нужен, и он всегда использует этот метод. При нескольких методах поле обязательно, а options поля должны в точности совпадать с названиями методов пула. Сценарий или пресет задаёт поле одним из этих названий. Отсутствующее значение или значение, не являющееся названием метода, приводит к ошибке method.missing или method.unknown.
"method": {
"label": "Scores by",
"options": ["manual", "roll"],
"defaultValue": "manual"
},- Строка 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 asoutside the min/max of inputgive 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. Форма создания проверяет настоящий бросок.
{
"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] }
]
}
}- Строка 7 Сценарий выбирает метод roll.
- Строки 21-27 Записанные броски. Каждый total это сумма кубиков, умноженная на multiplier, здесь 9 × 5 = 45.
is not satisfied: roll.missingМодификаторы и эффекты
Модификатор это число, которое платформа выводит из контента персонажа. Его питает список, который применяет эффекты. Ваши правила читают его как поле. В этом примере надетые амулеты повышают Guard.
Framework персонажа объявляет модификатор.
"modifiers": {
"mod_guard": {
"label": "Guard",
"stack": "sum",
"each": { "min": -3, "max": 3 },
"total": { "min": -5, "max": 5 },
"appliesTo": ["guard"]
}
},- Строка 25 Название, которое видят игроки.
- Строка 26 sum складывает все вклады. max берёт наибольший.
- Строка 27 Диапазон одного вклада.
- Строка 28 Диапазон суммы.
- Строка 29 Поле журнала, которое питает модификатор. Это подпись для людей. Расчёты делают ваши правила.
Список сообщает, что его записи применяют эффекты, и может их ограничивать.
"lists": {
"worn": {
"label": "Worn",
"of": "charm",
"max": 3,
"effects": true,
"distinctBy": "name",
"caps": [
{ "label": "Rings", "where": { "slot": { "eq": "Ring" } }, "max": 1 }
]
}
},- Строка 37 Записи этого списка меняют модификаторы.
- Строка 38 Запись с тем же именем, что у записи в списке, отклоняется с кодом SAME_NAME.
- Строки 39-41 Не больше 1 записи со слотом Ring. Следующая отклоняется с кодом LIST_CAP.
Framework с контентом называет свои входные данные эффекта. Не указывайте их варианты и диапазоны, потому что компилятор заполняет их из модификаторов персонажа.
{
"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
}
}
]
}- Строки 13-14 У эффекта и его величины нет вариантов и диапазона.
- Строка 16 Один эффект: какое входное значение называет модификатор и какое хранит величину.
- Строка 27 Пресет задаёт метку модификатора и величину.
Функция журнала читает модификатор как любое другое поле.
export function guard(currentValue: number, self: ElementRef): number {
return 10 + (element.var(self, "mod_guard") as number);
}- Строка 2 mod_guard объявлен в Framework и не возвращается конструктором.
Сценарий проверяет каждый случай. expectedValuesAfter проверяет выведенное значение после шага, а expectedModifiers проверяет модификаторы в конце.
{
"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 }
}- Строка 14 Guard 12: 10 плюс 2 от кольца.
- Строка 20 Второе кольцо отклонено: лимит допускает 1.
- Строка 30 Guard 13 с 1 от плаща.
- Строка 36 Второй плащ с тем же именем отклонён.
- Строка 46 Guard 11 после того, как кольцо снято.
- Строка 50 Остаётся сумма: 1 от плаща.
| Поле модификатора | Значение |
|---|---|
| label | Название, которое появляется в вариантах эффекта контента. |
| stack | sum или max. |
| each, total | Диапазон одного вклада и суммы. |
| appliesTo | До 8 названий полей, которые питает модификатор. |
| appliesWhen | Условие в виде подписи. Выполнение обеспечивают ваши правила. |
Пока не описано
Есть и другие сложные части, которые здесь не описаны: списки со строками, выбор контента и слоты, повышение уровня и перестройки, боевые роли и каталоги записей. Каждой нужна своя страница.

