Aller au contenu principal

Frameworks de code

Schémas de Framework

Déclarez les entrées, les actions, les valeurs dérivées, les Cadres, le contenu et les Frameworks enfants pour un type d'Élément.

Un fichier de Framework

Chaque fichier de schemas/ déclare un Framework. Les pages de référence font évoluer le Keeper du démarrage rapide. Il a maintenant un choix de Calling, une valeur dérivée Guard, un préréglage prêt à l'emploi, une section et un Lamp enfant.

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

Champs principaux

ChampObligatoireSignification
idOuiL'id unique du Framework. Le manifeste et les scénarios l'utilisent.
nameOuiLe nom affiché.
elementTypeOuiCe qu'il construit : character pour les personnages jouables, item pour les objets, spell pour les sorts. Les types personnalisés sont autorisés.
constructorOuiLe chemin du fichier de règles qui construit un nouvel Élément.
inputsNonCe que le joueur saisit en créant un Élément. Chaque clé correspond à un paramètre du constructeur.
actionsNonCe qu'un Élément peut faire. Chaque action a un id, un nom et un fichier de règles.
ledgerFunctionsNonDes valeurs dérivées. Chacune cible un champ stocké et a un fichier de règles.
framesOuiDe un à seize Cadres. Un seul a default défini à true.
sectionsNonRegroupe les entrées en étapes de l'assistant de création.
contentNonDes préréglages prêts à l'emploi. Chacun a un id, un nom et des valeurs d'entrée.
childFrameworksNonAssocie le nom d'un emplacement enfant à un autre Framework du même jeu.

Dans un JEU, le Framework d'entrée du manifeste doit être un personnage jouable.

The entry framework must be a character constructor.

Entrées

Une entrée personnalise un paramètre du constructeur, et sa clé doit correspondre au nom du paramètre. La plupart des entrées n'ont besoin que d'un label. Les autres clés limitent ce qu'un joueur peut saisir.

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. Lignes 8-14 min et max bornent un nombre. Un scénario ou un préréglage hors des bornes échoue.
  2. Lignes 15-19 options donne une liste fixe de choix. La valeur par défaut doit en faire partie.
  3. Ligne 20 Une entrée hidden contient une valeur que le joueur ne voit jamais. Ici, elle relie l'enfant 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"
      }
    }
  ]
}
CléSignification
labelLe texte montré au joueur.
descriptionUn texte d'aide, jusqu'à 1024 caractères.
defaultValueLa valeur utilisée quand le joueur ne touche pas à l'entrée.
optionsUne liste fixe de 2 à 128 choix de texte, chacun jusqu'à 128 caractères. La valeur par défaut doit en faire partie.
optionLabelsLes labels montrés au joueur pour les options. La valeur stockée reste l'option.
min, maxLes bornes d'un nombre. min ne doit pas dépasser max.
minLength, maxLengthLes bornes de la longueur d'un texte.
requiredMettez false pour laisser le joueur laisser l'entrée vide.
hiddenGarde l'entrée hors de l'assistant. Une entrée cachée a besoin d'un defaultValue.
visibleWhenMontre l'entrée seulement tant qu'une condition sur d'autres entrées est vraie.

Une valeur par défaut qui n'est pas l'une des options est refusée.

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

Les scénarios et les préréglages doivent aussi respecter les bornes.

Parameter max_hp 99 is not between 1 and 40.

Une entrée cachée sans valeur par défaut échoue avec :

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

Actions et fonctions de registre

Chaque action a un id, un nom et un fichier source. Chaque fonction de registre a les mêmes, plus un champ. Le champ est la valeur stockée que la fonction dérive, et ce doit être un champ scalaire que le constructeur retourne. Une fonction de registre lit des valeurs et retourne la valeur dérivée. Elle n'écrit jamais.

schemas/keeper.json
  "ledgerFunctions": [
    {
      "id": "guard",
      "name": "Guard",
      "field": "guard",
      "source": "rules/guard.ts"
    }
  ],
  1. Lignes 36-39 id et name identifient la fonction, field est la valeur stockée qu'elle dérive, et source est son fichier de règles.
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. Lignes 1-3 Une fonction d'aide. Elle n'a pas d'export, et la fonction de registre l'appelle par son nom.
  2. Ligne 5 Une fonction de registre reçoit sa valeur actuelle et l'Élément.
  3. Ligne 6 Lisez les valeurs stockées avec element.var.
  4. Ligne 7 Retournez la valeur dérivée.
Ledger function must target an existing scalar field.

Les ids doivent être uniques dans un Framework. Les actions et les fonctions de registre partagent un seul ensemble d'ids.

Duplicate semantic identity or source path.

Deux fonctions de registre peuvent partager un fichier source. Une action et une fonction de registre ne le peuvent pas :

action source does not match its owner contract

Entrées d'action

Les entrées d'une action sont les paramètres de sa fonction. La signature les déclare, et le schéma peut ajouter une entrée pour chacune. Chaque entrée est un nombre, un texte ou une valeur vrai/faux.

Le joueur se voit demander chaque entrée dans une boîte de dialogue quand il utilise le bouton. La boîte de dialogue nomme une entrée par le nom de son paramètre, sauf si l'entrée lui donne un libellé. Une entrée peut aussi donner une description et une valeur par défaut. Une entrée numérique peut avoir un minimum et un maximum. Une entrée de texte peut avoir une liste d'options ou des options issues d'une table. La boîte de dialogue affiche le tout, et le serveur refuse une valeur hors des bornes ou des options. Un bouton ne peut pas fournir de valeur. Nommez chaque paramètre pour qu'un joueur puisse le lire : roll, pas r.

schemas/keeper.json
    {
      "id": "check_lore",
      "name": "Lore check",
      "source": "rules/check-lore.ts",
      "inputs": { "roll": { "label": "Roll", "min": 1, "max": 100 } }
    }
  1. Ligne 24 L'entrée du jet. La boîte de dialogue affiche le libellé et le serveur impose le minimum et le maximum.

Un minimum et un maximum n'empêchent pas un nombre à décimales. Si la règle a besoin d'un entier, elle le vérifie elle-même, et une étape de scénario le prouve. Une étape de scénario hors des bornes est refusée.

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. Ligne 1 Le jet est une entrée que le joueur saisit.
  2. Ligne 2 Ignorer un nombre à décimales. Les bornes de l'entrée le gardent déjà entre 1 et 100.
  3. Lignes 4-5 Écrivez la cible et le jet.

Une entrée de texte peut plutôt proposer une liste de choix tirée d'une table du livre de règles. La page Tables du livre de règles montre comment.

Une entrée de texte peut aussi choisir par son nom l'un des champs stockés du Framework, avec une liste fields. La page Écrire des règles montre une action qui l'utilise.

Une entrée numérique peut demander au serveur de la lancer. Ajoutez une clé roll avec une expression de dés, comme "roll": "1d100" ou "roll": "2d6+1". La boîte de dialogue affiche alors un bouton Lancer à la place d'un champ à saisir. Le serveur lance les dés et la boîte de dialogue affiche le total, que le joueur ne peut pas modifier. Seul un paramètre numérique peut être lancé. Le compilateur refuse un roll accompagné d'un defaultValue, et refuse une expression de dés que le serveur n'accepte pas.

Une entrée lancée n'est dépensée qu'une fois : l'action prend le jet quand elle s'exécute, et un nouveau jet annule les précédents non dépensés de cette entrée. Un jet est lié à sa campagne et expire au bout de 15 minutes. La page Dispositions et fiches montre ce que voit la table.

Ligne de journal d'une action

Une action peut publier une ligne de résultat dans le journal de la table. Ajoutez un modèle de journal à l'entrée de l'action dans le schéma, par exemple "log": "{name} takes {amount} damage, defense {defense}". Le serveur remplit chaque champ {name} en texte brut une fois l'action enregistrée. Un navigateur n'envoie jamais la ligne.

Un champ réservé peut nommer une entrée de l'action, ou un champ numérique, texte ou vrai/faux du même Framework. Le compilateur refuse une entrée masquée, un champ de santé (hp, current_hp, hit_points, temp_hp, current_health, max_health, hitPoints, ainsi que les champs de santé et d'état du bloc de combat), un nom qui n'existe pas, un modèle de plus de 200 caractères, plus de 16 champs réservés, une accolade isolée et un caractère de contrôle. Quand une entrée et un champ portent le même nom, l'entrée l'emporte. Les valeurs sont celles d'après l'action : un champ que l'action a écrit affiche donc sa nouvelle valeur. Un champ calculé n'est pas recalculé et affiche la valeur qu'il avait avant l'action.

Un joueur qui agit sur son propre personnage voit la ligne auprès du même public que ses jets de dés : tout le monde, lui seul, ou lui et les MJ. Dans tous les autres cas, y compris un MJ qui agit sur un personnage quelconque, la ligne n'atteint que les membres de la campagne qui peuvent lire ce personnage. Si le public est tout le monde mais qu'un membre ne peut pas lire le personnage, la ligne part en message privé aux membres qui le peuvent. La table affiche la ligne sous l'étiquette Jeu, pour que personne ne la prenne pour le message d'un joueur.

Préréglages de contenu

Un préréglage est un ensemble nommé de valeurs d'entrée, comme un personnage prêt à jouer. Fabled vérifie chaque préréglage comme un scénario : chaque valeur doit donc correspondre à son entrée. Un jeu peut contenir jusqu'à 512 préréglages par Framework.

schemas/keeper.json
  "content": [
    {
      "id": "mira-scout",
      "name": "Mira the Scout",
      "parameters": {
        "name": "Mira",
        "max_hp": 8,
        "calling": "Scout",
        "lamp": "lamp"
      }
    }
  ]
  1. Lignes 47-48 Un préréglage a un id et un name.
  2. Lignes 49-54 Les valeurs d'entrée que le préréglage remplit. Elles doivent respecter les bornes des entrées.

Sections

Les sections regroupent les entrées en étapes de l'assistant de création. Quand un Framework déclare des sections, chaque entrée doit figurer dans l'une d'elles, entrées cachées comprises.

schemas/keeper.json
  "sections": [
    {
      "id": "basics",
      "title": "Basics",
      "inputs": ["name", "max_hp", "calling", "lamp"]
    }
  ],
  1. Lignes 25-27 Une section a un id, un title et les inputs qu'elle montre. Chaque entrée doit être dans une section.
Input lamp is not assigned to any section.

Frameworks enfants

Un Framework enfant est une partie d'un Élément plus grand, comme le Lamp que porte un Keeper. L'enfant a son propre schéma, son constructeur, son Cadre et son scénario.

schemas/keeper.json
  "childFrameworks": { "lamp": "lamp" },
  1. Ligne 22 Associe l'emplacement enfant lamp au Framework dont l'id est lamp.
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. Lignes 6-9 Les entrées de l'enfant. Un enfant a aussi besoin d'un name.
  2. Ligne 10 L'enfant a son propre Cadre.

Le parent associe un nom d'emplacement à un id de Framework dans childFrameworks. Une entrée cachée contient la référence. Le constructeur retourne les entrées de l'enfant à côté de ses propres valeurs.

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. Ligne 5 lamp est un FrameworkRef. La plateforme transforme l'id de Framework de l'entrée en un FrameworkRef.
  2. Lignes 6-9 Le type de retour liste les valeurs stockées et les emplacements enfants.
  3. Ligne 11 Les valeurs de départ du Keeper.
  4. Ligne 12 L'emplacement enfant : quel Framework construire et quelles entrées lui donner.

Chaque Framework, enfants compris, doit retourner un nom.

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

Un emplacement qui pointe vers un Framework hors du jeu échoue avec :

Invalid child Framework constraint: lamp.

Champs avancés

ChampRôle
listsLe contenu qu'un personnage gagne en jouant.
contentChoicesLes points de choix où le joueur prend une entrée d'un Framework de contenu.
slotsLes sélecteurs pour les choix répétables.
poolsLes réserves partagées dont les entrées numériques sont tirées.
modifiersLes nombres que la plateforme dérive des effets du contenu.
effectsLa façon dont un Framework de contenu nomme un modificateur et un montant.
levellingMonter ou descendre un personnage d'un niveau après sa création.
rebuildCe qu'une reconstruction fait à l'état de jeu, comme un changement de niveau.
combatLes rôles que lisent les outils de combat de la table.
catalogueDes enregistrements de contenu gardés hors de la source et ajoutés par morceaux.
tablesDes tables de données partagées.
playReadsDes valeurs dérivées que la table lit à partir des attributs stockés.
rulesAttacksLes attaques que chaque personnage possède.
optionsFromPrend les options d'une entrée dans une autre source.
rich, richTextMarque une entrée de texte comme texte riche.

Suite