Zum Hauptinhalt springen

Code-Frameworks

Framework-Schemas

Deklariere Eingaben, Aktionen, abgeleitete Werte, Frames, Inhalte und untergeordnete Frameworks für eine Art von Element.

Eine Framework-Datei

Jede Datei in schemas/ deklariert ein Framework. Die Referenzseiten bauen den Keeper aus dem Schnellstart weiter aus. Er hat jetzt eine Calling-Auswahl, einen abgeleiteten Guard-Wert, ein fertiges Preset, einen Abschnitt und eine untergeordnete Lamp.

manifest/project.json
schemas/keeper.json
schemas/lamp.json
rules/keeper.ts
rules/lamp.ts
rules/hurt.ts
rules/heal.ts
rules/guard.ts
layouts/keeper.json
layouts/lamp.json
scenarios/new-keeper.json
scenarios/heal.json
scenarios/new-lamp.json

Kernfelder

FeldPflichtBedeutung
idJaDie eindeutige ID des Frameworks. Das Manifest und die Szenarien nutzen sie.
nameJaDer Anzeigename.
elementTypeJaWas es erstellt: character für spielbare Charaktere, item für Objekte, spell für Zauber. Eigene Typen sind erlaubt.
constructorJaDer Pfad der Regeldatei, die ein neues Element erstellt.
inputsNeinWas der Spieler beim Erstellen eines Elements eingibt. Jeder Schlüssel passt zu einem Konstruktor-Parameter.
actionsNeinDinge, die ein Element tun kann. Jede hat eine ID, einen Namen und eine Regeldatei.
ledgerFunctionsNeinAbgeleitete Werte. Jeder zielt auf ein gespeichertes Feld und hat eine Regeldatei.
framesJaEin bis sechzehn Frames. Genau einer hat default auf true.
sectionsNeinGruppiert Eingaben zu Schritten des Erstellungsassistenten.
contentNeinFertige Presets. Jedes hat eine ID, einen Namen und Eingabewerte.
childFrameworksNeinOrdnet einem Namen für einen untergeordneten Platz ein anderes Framework desselben Spiels zu.

In einem GAME muss das Einstiegs-Framework im Manifest ein spielbarer Charakter sein.

The entry framework must be a character constructor.

Eingaben

Eine Eingabe passt einen Konstruktor-Parameter an, und ihr Schlüssel muss zum Namen des Parameters passen. Die meisten Eingaben brauchen nur ein label. Die anderen Schlüssel schränken ein, was ein Spieler eingeben kann.

schemas/keeper.json
  "inputs": {
    "name": { "label": "Name" },
    "max_hp": {
      "label": "Maximum HP",
      "description": "How much damage a Keeper can take.",
      "defaultValue": 10,
      "min": 1,
      "max": 40
    },
    "calling": {
      "label": "Calling",
      "options": ["Guard", "Scout"],
      "defaultValue": "Guard"
    },
    "lamp": { "label": "Lamp", "defaultValue": "lamp", "hidden": true }
  },
  1. Zeilen 8-14 min und max begrenzen eine Zahl. Ein Szenario oder Preset außerhalb der Grenzen schlägt fehl.
  2. Zeilen 15-19 options gibt eine feste Liste von Auswahlmöglichkeiten vor. Der Standardwert muss eine davon sein.
  3. Zeile 20 Eine hidden-Eingabe hält einen Wert, den der Spieler nie sieht. Hier verbindet sie die untergeordnete Lamp.
schemas/keeper.json
schemas/keeper.json
{
  "id": "keeper",
  "name": "Keeper",
  "elementType": "character",
  "constructor": "rules/keeper.ts",
  "inputs": {
    "name": { "label": "Name" },
    "max_hp": {
      "label": "Maximum HP",
      "description": "How much damage a Keeper can take.",
      "defaultValue": 10,
      "min": 1,
      "max": 40
    },
    "calling": {
      "label": "Calling",
      "options": ["Guard", "Scout"],
      "defaultValue": "Guard"
    },
    "lamp": { "label": "Lamp", "defaultValue": "lamp", "hidden": true }
  },
  "childFrameworks": { "lamp": "lamp" },
  "sections": [
    {
      "id": "basics",
      "title": "Basics",
      "inputs": ["name", "max_hp", "calling", "lamp"]
    }
  ],
  "actions": [
    { "id": "hurt", "name": "Take damage", "source": "rules/hurt.ts" },
    { "id": "heal", "name": "Heal", "source": "rules/heal.ts" }
  ],
  "ledgerFunctions": [
    {
      "id": "guard",
      "name": "Guard",
      "field": "guard",
      "source": "rules/guard.ts"
    }
  ],
  "frames": [
    { "id": "sheet", "source": "layouts/keeper.json", "default": true }
  ],
  "content": [
    {
      "id": "mira-scout",
      "name": "Mira the Scout",
      "parameters": {
        "name": "Mira",
        "max_hp": 8,
        "calling": "Scout",
        "lamp": "lamp"
      }
    }
  ]
}
SchlüsselBedeutung
labelDer Text, der dem Spieler angezeigt wird.
descriptionHilfetext, bis zu 1024 Zeichen.
defaultValueDer Wert, der gilt, wenn der Spieler die Eingabe nicht anfasst.
optionsEine feste Liste von 2 bis 128 Textoptionen, jede bis zu 128 Zeichen lang. Der Standardwert muss eine davon sein.
optionLabelsBeschriftungen der Optionen für den Spieler. Gespeichert wird weiterhin die Option.
min, maxGrenzen für eine Zahl. min darf max nicht übersteigen.
minLength, maxLengthGrenzen für die Länge eines Textes.
requiredSetze es auf false, damit der Spieler die Eingabe leer lassen darf.
hiddenHält die Eingabe aus dem Assistenten heraus. Eine versteckte Eingabe braucht einen defaultValue.
visibleWhenZeigt die Eingabe nur, solange eine Bedingung für andere Eingaben gilt.

Ein Standardwert, der keine der Optionen ist, wird abgelehnt.

{
  "calling": {
    "label": "Calling",
    "options": ["Guard", "Scout"],
    "defaultValue": "Mage"
  }
}
Default for calling must be one of its options ("Guard", "Scout").

Auch Szenarien und Presets müssen in die Grenzen passen.

Parameter max_hp 99 is not between 1 and 40.

Eine versteckte Eingabe ohne Standardwert schlägt fehl mit:

Input name is hidden, so it must declare a defaultValue.

Aktionen und Ledger-Funktionen

Jede Aktion hat eine ID, einen Namen und eine Quelldatei. Jede Ledger-Funktion hat dasselbe, dazu ein field. Das Feld ist der gespeicherte Wert, den die Funktion ableitet, und es muss ein skalares Feld sein, das der Konstruktor zurückgibt. Eine Ledger-Funktion liest Werte und gibt den abgeleiteten zurück. Sie schreibt nie.

schemas/keeper.json
  "ledgerFunctions": [
    {
      "id": "guard",
      "name": "Guard",
      "field": "guard",
      "source": "rules/guard.ts"
    }
  ],
  1. Zeilen 36-39 id und name bezeichnen die Funktion, field ist der gespeicherte Wert, den sie ableitet, und source ist ihre Regeldatei.
rules/guard.ts
function base_guard(calling: string): number {
  return calling === "Guard" ? 3 : 1;
}

export function guard(currentValue: number, self: ElementRef): number {
  const calling: string = element.var(self, "calling") as string;
  return base_guard(calling);
}
  1. Zeilen 1-3 Eine Hilfsfunktion. Sie hat kein export, und die Ledger-Funktion ruft sie beim Namen auf.
  2. Zeile 5 Eine Ledger-Funktion erhält ihren aktuellen Wert und das Element.
  3. Zeile 6 Lies gespeicherte Werte mit element.var.
  4. Zeile 7 Gib den abgeleiteten Wert zurück.
Ledger function must target an existing scalar field.

IDs müssen innerhalb eines Frameworks eindeutig sein. Aktionen und Ledger-Funktionen teilen sich einen Satz von IDs.

Duplicate semantic identity or source path.

Zwei Ledger-Funktionen dürfen eine Quelldatei teilen. Eine Aktion und eine Ledger-Funktion dürfen das nicht:

action source does not match its owner contract

Aktions-Eingaben

Die Eingaben einer Aktion sind die Parameter ihrer Funktion. Die Signatur deklariert sie, und das Schema kann für jede einen Eintrag ergänzen. Jede Eingabe ist eine Zahl, ein Text oder ein Wahr/Falsch-Wert.

Der Spieler wird in einem Dialog nach jeder Eingabe gefragt, wenn er die Schaltfläche benutzt. Der Dialog nennt eine Eingabe nach ihrem Parameternamen, sofern der Eintrag kein Label vergibt. Ein Eintrag kann außerdem eine Beschreibung und einen Standardwert festlegen. Eine Zahleneingabe kann ein Minimum und ein Maximum haben. Eine Texteingabe kann eine Liste von Optionen oder Optionen aus einer Tabelle haben. Der Dialog zeigt alles an, und der Server lehnt einen Wert außerhalb der Grenzen oder Optionen ab. Eine Schaltfläche kann keinen Wert übergeben. Benenne jeden Parameter so, dass ein Spieler ihn lesen kann: roll statt r.

schemas/keeper.json
    {
      "id": "check_lore",
      "name": "Lore check",
      "source": "rules/check-lore.ts",
      "inputs": { "roll": { "label": "Roll", "min": 1, "max": 100 } }
    }
  1. Zeile 24 Der Eintrag für die Wurf-Eingabe. Der Dialog zeigt das Label, und der Server erzwingt Minimum und Maximum.

Minimum und Maximum verhindern keine Zahl mit Dezimalstellen. Braucht die Regel eine ganze Zahl, prüft sie das selbst, und ein Szenarioschritt belegt es. Ein Szenarioschritt außerhalb der Grenzen wird abgelehnt.

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. Zeile 1 Der Wurf ist eine Eingabe, die der Spieler tippt.
  2. Zeile 2 Ignoriere eine Zahl mit Dezimalstellen. Die Grenzen der Eingabe halten sie bereits zwischen 1 und 100.
  3. Zeilen 4-5 Schreibe das Ziel und den Wurf.

Eine Text-Eingabe kann stattdessen eine Auswahl aus einer Regelwerk-Tabelle bieten. Die Seite Regelwerk-Tabellen zeigt, wie.

Eine Texteingabe kann stattdessen mit einer fields-Liste eines der gespeicherten Felder des Frameworks nach Namen wählen. Die Seite Regeln schreiben zeigt eine Aktion, die das nutzt.

Eine Zahleneingabe kann den Server bitten, sie zu würfeln. Füge einen Schlüssel roll mit einem Würfelausdruck hinzu, etwa "roll": "1d100" oder "roll": "2d6+1". Der Dialog zeigt dann eine Schaltfläche Würfeln statt eines Feldes zum Tippen. Der Server würfelt, und der Dialog zeigt die Summe, die der Spieler nicht ändern kann. Nur ein Zahlenparameter lässt sich würfeln. Der Compiler lehnt roll zusammen mit defaultValue ab und lehnt einen Würfelausdruck ab, den der Server nicht akzeptiert.

Eine gewürfelte Eingabe wird nur einmal verbraucht: Die Aktion nimmt den Wurf beim Ausführen, und ein neuer Wurf macht frühere unverbrauchte Würfe dieser Eingabe ungültig. Ein Wurf ist an seine Kampagne gebunden und läuft nach 15 Minuten ab. Die Seite Layouts und Bögen zeigt, was der Tisch sieht.

Protokollzeile einer Aktion

Eine Aktion kann eine Ergebniszeile im Tischprotokoll veröffentlichen. Füge dem Eintrag der Aktion im Schema eine Protokollvorlage hinzu, zum Beispiel "log": "{name} takes {amount} damage, defense {defense}". Der Server füllt jeden Platzhalter {name} als reinen Text, nachdem die Aktion gespeichert wurde. Ein Browser sendet die Zeile nie.

Ein Platzhalter darf eine Eingabe der Aktion oder ein Zahlen-, Text- oder Wahr/Falsch-Feld desselben Frameworks benennen. Der Compiler lehnt eine versteckte Eingabe ab, ein Gesundheitsfeld (hp, current_hp, hit_points, temp_hp, current_health, max_health, hitPoints sowie die Gesundheits- und Zustandsfelder des Kampfblocks), einen Namen, der nicht existiert, eine Vorlage mit mehr als 200 Zeichen, mehr als 16 Platzhalter, eine einzelne Klammer und ein Steuerzeichen. Teilen eine Eingabe und ein Feld einen Namen, gewinnt die Eingabe. Die Werte stammen von nach der Aktion, ein von der Aktion geschriebenes Feld zeigt also seinen neuen Wert. Ein berechnetes Feld wird nicht neu berechnet und zeigt den Wert von vor der Aktion.

Wer mit der eigenen Figur handelt, sieht die Zeile im selben Publikum wie die eigenen Würfelwürfe: alle, nur er oder sie selbst, oder er oder sie und die SL. In jedem anderen Fall, auch wenn die SL mit einer beliebigen Figur handelt, erreicht die Zeile nur die Kampagnenmitglieder, die diese Figur lesen dürfen. Ist das Publikum „alle“, aber ein Mitglied darf die Figur nicht lesen, geht die Zeile als private Nachricht an die Mitglieder, die es dürfen. Der Tisch zeigt die Zeile unter dem Label Spiel, damit sie niemand für die Nachricht einer Spielerin oder eines Spielers hält.

Inhalts-Presets

Ein Preset ist ein benannter Satz von Eingabewerten, zum Beispiel ein fertiger Charakter. Fabled prüft jedes Preset wie ein Szenario, daher muss jeder Wert zu seiner Eingabe passen. Ein Spiel kann bis zu 512 Presets pro Framework enthalten.

schemas/keeper.json
  "content": [
    {
      "id": "mira-scout",
      "name": "Mira the Scout",
      "parameters": {
        "name": "Mira",
        "max_hp": 8,
        "calling": "Scout",
        "lamp": "lamp"
      }
    }
  ]
  1. Zeilen 47-48 Ein Preset hat eine id und einen name.
  2. Zeilen 49-54 Die Eingabewerte, die das Preset ausfüllt. Sie müssen in die Grenzen der Eingaben passen.

Abschnitte

Abschnitte gruppieren Eingaben zu Schritten des Erstellungsassistenten. Deklariert ein Framework Abschnitte, muss jede Eingabe in einem davon stehen, versteckte Eingaben eingeschlossen.

schemas/keeper.json
  "sections": [
    {
      "id": "basics",
      "title": "Basics",
      "inputs": ["name", "max_hp", "calling", "lamp"]
    }
  ],
  1. Zeilen 25-27 Ein Abschnitt hat eine id, einen title und die Eingaben, die er zeigt. Jede Eingabe muss in einem Abschnitt stehen.
Input lamp is not assigned to any section.

Untergeordnete Frameworks

Ein untergeordnetes Framework ist ein Teil eines größeren Elements, zum Beispiel die Lamp, die ein Keeper trägt. Das untergeordnete Framework hat ein eigenes Schema, einen eigenen Konstruktor, einen eigenen Frame und ein eigenes Szenario.

schemas/keeper.json
  "childFrameworks": { "lamp": "lamp" },
  1. Zeile 22 Ordnet dem untergeordneten Platz lamp das Framework mit der id lamp zu.
schemas/lamp.json
{
  "id": "lamp",
  "name": "Lamp",
  "elementType": "item",
  "constructor": "rules/lamp.ts",
  "inputs": {
    "name": { "label": "Name" },
    "oil": { "label": "Oil", "defaultValue": 5 }
  },
  "frames": [{ "id": "card", "source": "layouts/lamp.json", "default": true }]
}
  1. Zeilen 6-9 Die Eingaben des untergeordneten Elements. Auch ein untergeordnetes Element braucht einen name.
  2. Zeile 10 Das untergeordnete Element hat seinen eigenen Frame.

Das übergeordnete Framework ordnet in childFrameworks einem Platznamen eine Framework-ID zu. Eine versteckte Eingabe hält den Verweis. Der Konstruktor gibt die Eingaben des untergeordneten Frameworks neben seinen eigenen Werten zurück.

rules/keeper.ts
export function construct(
  name: string,
  max_hp: number,
  calling: string,
  lamp: FrameworkRef,
): {
  values: { name: string; max_hp: number; hp: number; calling: string; guard: number };
  children: { lamp: { framework: FrameworkRef; params: { name: string; oil: number } } };
} {
  return {
    values: { name: name, max_hp: max_hp, hp: max_hp, calling: calling, guard: 0 },
    children: { lamp: { framework: lamp, params: { name: "Lantern", oil: 5 } } },
  };
}
  1. Zeile 5 lamp ist eine FrameworkRef. Die Plattform macht aus der Framework-id in der Eingabe eine solche.
  2. Zeilen 6-9 Der Rückgabetyp listet die gespeicherten Werte und die untergeordneten Plätze auf.
  3. Zeile 11 Die Startwerte des Keeper.
  4. Zeile 12 Der untergeordnete Platz: welches Framework gebaut wird und welche Eingaben es bekommt.

Jedes Framework, auch untergeordnete, muss einen name zurückgeben.

must define the required 'name' value, normally as a string constructor field.

Ein Platz, der auf ein Framework außerhalb des Spiels zeigt, schlägt fehl mit:

Invalid child Framework constraint: lamp.

Fortgeschrittene Felder

FeldZweck
listsInhalte, die ein Charakter im Spiel erhält.
contentChoicesAuswahlpunkte, an denen der Spieler einen Eintrag eines Inhalts-Frameworks wählt.
slotsAuswahlfelder für wiederholbare Entscheidungen.
poolsGemeinsame Pools, aus denen Zahlen-Eingaben schöpfen.
modifiersZahlen, die die Plattform aus den Effekten von Inhalten ableitet.
effectsWie ein Inhalts-Framework einen Modifikator und einen Betrag nennt.
levellingEinen Charakter nach der Erstellung eine Stufe hoch- oder runterstufen.
rebuildWas ein Umbau mit dem Spielzustand macht, zum Beispiel bei einem Stufenwechsel.
combatDie Rollen, die die Kampfwerkzeuge des Tabletops lesen.
catalogueInhaltsdatensätze, die außerhalb der Quelle liegen und in Teilen hinzugefügt werden.
tablesGemeinsame Datentabellen.
playReadsAbgeleitete Werte, die das Tabletop aus gespeicherten Attributen liest.
rulesAttacksAngriffe, die jeder Charakter hat.
optionsFromNimmt die Optionen einer Eingabe aus einer anderen Quelle.
rich, richTextMarkiert eine Text-Eingabe als Rich-Text.

Weiter