メインコンテンツへスキップ

コードフレームワーク

プールと修正値

入力の間でポイント予算を共有し、コンテンツがキャラクターの数値を変えられるようにします。

プール

プールは、ポイント予算を共有する 3 つの特性のように、プレイヤーがまとめて埋める数値入力のグループを制限します。サーバーは、キャラクターを作成または編集するとき、すべてのプールを確認します。

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サーバーが dice で count 個の値をロールし、任意の bonus を足して任意の multiplier を掛け、プレイヤーがその合計を各入力に割り当てます。
budgetすべてのスコアが min と max の間にあり、スコアの合計が total 以下である。

プールは、方式を 1 つ以上宣言する必要があります。方式が複数あるプールでは、方式を選ぶ入力を 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.
上限値
1 つのフレームワークのプール数4
1 つのプールの入力数64
min と max の間の値の数64
予算1000

budget 方式は total、min、max を取ります。すべてのスコアは min から max までの整数で、スコアの合計は total 以下です。スコアに価格はないため、技能ポイントの予算に向いており、コストの定義は要りません。

ロールしたスコア

roll メソッドでは、サーバーが値をロールするため、プレイヤーが自分で値を入力することはできません。dice と count に加え、任意のキー bonus と multiplier を取ります。dice は "3d6" や "4d6dl1" のような 1 つのダイスグループです。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 行目 または、サーバーが 3d6 で 3 つのスコアをロールし、入力ごとに 1 つ使います。

methodInput は、プールが使うメソッドを選ぶ文字列入力を指定します。この入力でのプレイヤーの選択によって、サーバーがどのメソッドを検証するかが決まるため、同じ値を標準配列、ポイントバイ、ロールのいずれで検証することもできます。メソッドが 1 つのプールでは 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 を許可します。ダイスは 1 つのグループで、最大 10 個、最大 100 面です。dl、dh、kl、kh で 1 から 9 個を捨てるか残しますが、少なくとも 1 個は残します。倍率は 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 はプール ID を、ロールした値ごとに 1 つのスロットに対応付けます。スロットには、bonus と multiplier の適用後の最終値である total があり、任意で 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 を上げます。

キャラクターのフレームワークが修正値を宣言します。

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 行目 1 つの寄与の範囲です。
  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 行目 slot が Ring のエントリは最大 1 つです。もう 1 つは LIST_CAP で拒否されます。

コンテンツのフレームワークは、効果の入力を指定します。選択肢と範囲は書きません。コンパイラがキャラクターの修正値から埋めるためです。

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 行目 効果 1 つにつき、修正値を指定する入力と、量を持つ入力を指定します。
  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 はフレームワークで宣言され、コンストラクタが返すものではありません。

シナリオで各ケースを確認します。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 行目 2 つ目の指輪は拒否されます。上限は 1 つです。
  3. 30 行目 マントの 1 を足して、Guard は 13 です。
  4. 36 行目 同じ名前の 2 つ目のマントは拒否されます。
  5. 46 行目 指輪を外すと、Guard は 11 です。
  6. 50 行目 残る合計は、マントの 1 です。
修正値のフィールド意味
labelコンテンツの効果の選択肢に表示される名前です。
stacksum か max です。
each, total1 つの寄与の範囲と、合計の範囲です。
appliesTo修正値が反映されるフィールドの名前です。最大 8 つです。
appliesWhen条件をラベルとして示します。実際に守らせるのはルールです。

まだ説明していないこと

他にも高度な機能がありますが、ここでは説明しません。行リスト、コンテンツの選択とスロット、レベル変更と再構築、戦闘の役割、記録カタログです。それぞれに専用のページが必要です。

次へ