Zum Hauptinhalt springen

Code-Frameworks

Pools und Modifikatoren

Teile ein Punktebudget zwischen Eingaben und lass Inhalte Zahlen am Charakter ändern.

Pools

Ein Pool begrenzt eine Gruppe von Zahlen-Eingaben, die ein Spieler gemeinsam füllt, zum Beispiel drei Eigenschaften, die ein Punktebudget teilen. Der Server prüft jeden Pool, wenn ein Charakter erstellt oder bearbeitet wird.

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. Zeile 23 Die Zahlen-Eingaben des Pools. Jede braucht einen Eintrag unter inputs.
  2. Zeile 24 Die Methoden, die der Spieler nutzen darf.
  3. Zeile 26 Die Gesamtkosten der Werte dürfen das Budget nicht übersteigen.
  4. Zeile 27 Jeder Wert ist mindestens so groß.
  5. Zeile 28 Jeder Wert ist höchstens so groß.
  6. Zeile 29 Die Kosten jedes Werts. Jeder Wert von min bis max braucht einen.
MethodeWas sie prüft
standardArrayDie Werte sind die aufgelisteten Werte, in beliebiger Reihenfolge.
pointBuyJeder Wert liegt zwischen min und max, und die Kosten ergeben höchstens das Budget.
manualJeder Wert liegt zwischen min und max.
rollDer Server würfelt count Werte mit dice, addiert den optionalen bonus, multipliziert mit dem optionalen multiplier, und der Spieler ordnet die Summen den Eingaben zu.
budgetJeder Wert liegt zwischen min und max, und die Werte ergeben zusammen höchstens total.

Ein Pool muss mindestens eine Methode deklarieren. Ein Pool mit mehr als einer Methode nennt in methodInput die Eingabe, die die Methode wählt. Presets und Szenarien müssen den Pool erfüllen, und ein Szenario zeigt es:

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. Zeilen 7-9 Diese Werte kosten 2, 1 und 0, die Summe ist also 3 und das Budget 6.
is not satisfied: pointBuy.budget
pointBuy costs has no entry for 5
A pool declares at least one method.
LimitWert
Pools pro Framework4
Eingaben pro Pool64
Werte zwischen min und max64
Budget1000

Die Methode budget nimmt total, min und max. Jeder Wert ist eine ganze Zahl von min bis max, und die Werte ergeben zusammen höchstens total. Die Werte haben keinen Preis, daher passt sie zu einem Budget aus Fertigkeitspunkten und braucht keine costs.

Gewürfelte Werte

Mit der Methode roll würfelt der Server die Werte, sodass ein Spieler keine eigenen eintippen kann. Sie hat dice und count sowie zwei optionale Schlüssel, bonus und multiplier. dice ist eine Würfelgruppe, etwa "3d6" oder "4d6dl1". count ist die Anzahl der Werte, die der Server würfelt, und entspricht der Anzahl der Eingaben im Pool. bonus (-20 bis 100) wird zu jeder Würfelsumme addiert, danach multipliziert multiplier (1 bis 10) sie, sodass jeder Wert (Würfelsumme + bonus) × multiplier ist. Ein Merkmal 3d6 × 5 wie in Call of Cthulhu ist dice "3d6" mit 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. Zeile 29 Ein Pool mit mehr als einer Methode nennt die Eingabe, die die Methode wählt.
  2. Zeile 31 Der Spieler darf die Werte eintippen.
  3. Zeile 32 Oder der Server würfelt 3 Werte mit 3d6, einen für jede Eingabe.

methodInput benennt die Texteingabe, die bestimmt, welche Methode ein Pool verwendet. Die Wahl des Spielers in dieser Eingabe entscheidet, welche Methode der Server prüft; dieselben Werte können also am Standard-Array, am Point Buy oder an einem Wurf gemessen werden. Ein Pool mit einer Methode braucht kein methodInput und verwendet immer diese Methode. Bei mehreren Methoden ist es Pflicht, und die options der Eingabe müssen genau die Methodennamen des Pools sein. Ein Szenario oder Preset setzt die Eingabe auf einen dieser Namen. Ein fehlender Wert oder ein Wert, der kein Methodenname ist, scheitert mit method.missing oder method.unknown.

schemas/keeper.json
    "method": {
      "label": "Scores by",
      "options": ["manual", "roll"],
      "defaultValue": "manual"
    },
  1. Zeile 11 Genau die Methoden des Pools, sonst nichts.

Jede Summe, die der Wurf nach bonus und multiplier ergeben kann, muss zu min und max jeder Eingabe passen. 3d6 × 5 ergibt 15 bis 90, also erlaubt jedes Merkmal 15 bis 90. Die Würfel sind eine Gruppe: bis zu 10 Würfel mit bis zu 100 Seiten, und dl, dh, kl oder kh streicht oder behält 1 bis 9 davon, wobei mindestens ein Würfel bleibt. Schreibe einen Multiplikator mit dem Schlüssel multiplier, niemals in den Würfeln: 3d6x5 wird abgelehnt.

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

Ein fehlender Schlüssel erscheint als JSON-Liste. Der Pfad in der Liste nennt den Schlüssel, hier pools, traits, methods, roll, dice, und die Meldung lautet Required.

Required

Im Formular zur Charaktererstellung zeigt ein roll-Pool statt seiner Eingaben die Schaltfläche Werte würfeln und nach dem ersten Wurf Alle neu würfeln. Der Server würfelt und führt Protokoll, der Browser würfelt also nie. Der Spieler weist die Summen dann in beliebiger Reihenfolge den Eingaben zu. Das Würfeln gibt es nur im Erstellungsformular.

Ein Szenario, das die Methode roll wählt, liefert die Würfe selbst in poolRolls, weil die echten Würfe nur auf dem Server existieren. poolRolls ordnet einer Pool-ID je einen Slot pro gewürfeltem Wert zu. Ein Slot hat ein total, die Endzahl nach bonus und multiplier, und optional dice und dropped, die nur zum Lesen dienen. Die Pool-Prüfung behandelt die Slots dann als Aufzeichnung des Servers, die Werte des Szenarios müssen also diese Summen sein, in beliebiger Reihenfolge. Jede Summe muss vom Wurf erzeugbar sein. Ohne poolRolls scheitert ein Szenario, das roll wählt, mit roll.missing. Ein Preset kann die Methode roll nicht verwenden. Das Erstellungsformular testet den echten Wurf.

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. Zeile 7 Das Szenario wählt die Methode roll.
  2. Zeilen 21-27 Die aufgezeichneten Würfe. Jedes total ist die Würfelsumme mal multiplier, hier 9 × 5 = 45.
is not satisfied: roll.missing

Modifikatoren und Effekte

Ein Modifikator ist eine Zahl, die die Plattform aus den Inhalten ableitet, die ein Charakter hält. Eine Liste, die Effekte anwendet, speist ihn. Deine Regeln lesen ihn wie ein Feld. In diesem Beispiel erhöhen getragene Amulette Guard.

Das Charakter-Framework deklariert den Modifikator.

schemas/keeper.json
  "modifiers": {
    "mod_guard": {
      "label": "Guard",
      "stack": "sum",
      "each": { "min": -3, "max": 3 },
      "total": { "min": -5, "max": 5 },
      "appliesTo": ["guard"]
    }
  },
  1. Zeile 25 Der Name, den Spieler sehen.
  2. Zeile 26 sum addiert jeden Beitrag. max nimmt den größten.
  3. Zeile 27 Der Bereich eines Beitrags.
  4. Zeile 28 Der Bereich der Summe.
  5. Zeile 29 Das Ledger-Feld, das der Modifikator speist. Das ist eine Beschriftung für Menschen. Deine Regeln rechnen.

Die Liste legt fest, dass ihre Einträge Effekte anwenden, und sie kann diese begrenzen.

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. Zeile 37 Die Einträge dieser Liste ändern Modifikatoren.
  2. Zeile 38 Ein Eintrag mit demselben Namen wie einer in der Liste wird mit SAME_NAME abgelehnt.
  3. Zeilen 39-41 Höchstens 1 Eintrag mit dem Slot Ring. Ein weiterer wird mit LIST_CAP abgelehnt.

Das Inhalts-Framework nennt seine Effekt-Eingaben. Lass ihre Optionen und Bereiche weg, denn der Compiler füllt sie aus den Modifikatoren des Charakters.

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. Zeilen 13-14 Der Effekt und sein Betrag haben keine Optionen und keinen Bereich.
  2. Zeile 16 Ein Effekt: Welche Eingabe den Modifikator nennt und welche den Betrag hält.
  3. Zeile 27 Das Preset gibt das Label des Modifikators und den Betrag an.

Eine Ledger-Funktion liest den Modifikator wie jedes andere Feld.

rules/guard.ts
export function guard(currentValue: number, self: ElementRef): number {
  return 10 + (element.var(self, "mod_guard") as number);
}
  1. Zeile 2 mod_guard wird im Framework deklariert und nicht vom Konstruktor zurückgegeben.

Das Szenario beweist jeden Fall. expectedValuesAfter prüft den abgeleiteten Wert nach einem Schritt, und expectedModifiers prüft die Modifikatoren am Ende.

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. Zeile 14 Guard 12: 10 plus die 2 des Rings.
  2. Zeile 20 Ein zweiter Ring wird abgelehnt: Die Obergrenze erlaubt 1.
  3. Zeile 30 Guard 13 mit der 1 des Umhangs.
  4. Zeile 36 Ein zweiter Umhang mit demselben Namen wird abgelehnt.
  5. Zeile 46 Guard 11, nachdem der Ring abgelegt ist.
  6. Zeile 50 Die verbleibende Summe ist die 1 des Umhangs.
Modifikator-FeldBedeutung
labelDer Name, der in den Effekt-Optionen des Inhalts erscheint.
stacksum oder max.
each, totalDer Bereich eines Beitrags und der Summe.
appliesToBis zu 8 Namen der Felder, die der Modifikator speist.
appliesWhenEine Bedingung als Beschriftung. Deine Regeln setzen sie durch.

Noch nicht beschrieben

Es gibt weitere fortgeschrittene Teile, die hier nicht beschrieben sind: Zeilenlisten, Inhaltsauswahl und Slots, Stufenaufstieg und Umbauten, Kampfrollen und Datensatz-Kataloge. Jeder braucht eine eigene Seite.

Weiter