Catalogues des ensembles de règles

Utilise cette page pour distribuer des entrées préparées (sorts, équipement, capacités de classe) que l'éditeur de fiche propose dans un sélecteur, pour que les joueurs n'aient pas à saisir de longues listes à la main. Elle couvre l'en-tête du catalogue, les entrées, les valeurs maintenues par l'ensemble de règles, les commandes de fiche et la commande d'utilisation. Elle s'adresse aux auteurs d'ensembles de règles. Avant de commencer, lis Écrire des ensembles de règles Game Mode.

Saisir ligne par ligne une longue liste de sorts ou un tableau d'équipement prend du temps. Un catalogue est une collection nommée d'entrées préparées que tu distribues avec l'ensemble. L'éditeur les propose dans un sélecteur sur chaque liste alimentée par le catalogue ; un choix remplit la ligne.

Les catalogues sont facultatifs. Un ensemble peut en avoir douze ; le moteur ne connaît pas leur sujet : chaque identifiant, colonne, filtre et mot vient de ton fichier.

L'en-tête

L'en-tête va dans catalogs, au premier niveau du fichier, à côté de gm.

"catalogs": [
  {
    "id": "knacks",
    "label": "Knacks",
    "feeds": ["knacks", "tricks"],
    "filters": [
      { "id": "grit", "label": "Grit cost", "type": "number" },
      { "id": "road", "label": "Road", "type": "text" },
      { "id": "callings", "label": "Calling", "type": "tags", "startFrom": { "field": "calling" } }
    ],
    "units": { "distance": { "label": "paces", "perCell": 2 } },
    "entries": []
  }
]

Une entrée

{
  "id": "road-sense",
  "label": "Road Sense",
  "summary": "You read a road the way other people read a face.",
  "filters": { "grit": 0, "road": "Ash Flats", "callings": ["Scout", "Courier"] },
  "rows": [
    {
      "list": "knacks",
      "values": { "name": "Road Sense", "notes": "Sneak to notice where a road turns bad." }
    }
  ]
}

Chaque valeur est vérifiée contre les colonnes cibles. Un nom mal écrit ou un nombre hors limites est signalé avec son entrée d'origine. Les entrées dans le fichier de règles sont vérifiées à son chargement, donc à l'importation pour un fichier importé. Un catalogue séparé de package est vérifié lorsque le sélecteur le demande pour la première fois ; s'il contient une erreur, il montre les raisons plutôt que des entrées.

Une entrée, plusieurs listes

Une capacité à usages limités occupe deux lignes : la capacité et son compteur. Cela reste un seul choix.

{
  "id": "last-ember",
  "label": "Last Ember",
  "rows": [
    {
      "list": "knacks",
      "values": { "name": "Last Ember", "notes": "Spend 1 Grit to give a downed friend 3 Grit back." }
    },
    { "list": "tricks", "values": { "name": "Last Ember", "uses": 1, "recharge": "camp" } }
  ]
}

Valeurs maintenues à jour par l'ensemble

Les nombres d'une ligne appartiennent au joueur dès qu'il la choisit. Une exception est utile : un maximum dépendant du personnage, comme des usages égaux à un attribut ou une ressource de classe qui augmente avec le niveau. Une ligne peut nommer jusqu'à quatre de ses colonnes numériques dans scaled ; l'éditeur maintient ces cellules à jour.

{
  "list": "tricks",
  "values": { "name": "Last Ember", "uses": 1, "recharge": "camp" },
  "scaled": { "uses": { "from": { "abilityScore": "heart" } } }
}

La valeur est calculée à l'édition, jamais à la lecture : une ligne sauvegardée contient toujours le nombre qu'elle annonce. Elle est adaptée à la colonne : bornée par min et max, puis arrondie vers le bas si la colonne prend des entiers. Dans l'exemple, Heart 3 donne trois usages et Heart 0 ou moins n'en donne aucun. La ligne reste avec 0 usage ; un compteur au maximum de 0 n'étant pas une réserve, rien n'est dépensable en jeu.

Les colonnes à échelle nécessitent Capability API 1.23 pour les ensembles en package. Un ensemble communautaire importé est validé par le moteur qui le lit et n'a besoin de rien d'autre.

Les lignes choisies sont des copies

Chaque ligne choisie est copiée sur la fiche avec une clé supplémentaire, _catalog, contenant <catalog id>/<entry id>. Les identifiants de colonnes commencent par une lettre ; cette clé ne peut donc pas être l'une des tiennes.

La copie appartient au personnage. Le joueur peut tout modifier ensuite ; la fiche fonctionne sans l'ensemble installé et publier une version n'écrase jamais les personnages. La marque permet au sélecteur d'afficher ce que possède déjà la fiche et à l'actualisation décrite ci-dessous de le reconnaître.

Actualiser depuis l'ensemble de règles

Grâce à la marque, l'éditeur peut avertir le joueur si ton nouveau texte diffère de sa ligne. Une courte ligne sous la liste indique combien de lignes ont un texte nouveau ; Review (Examiner) montre le contenu de la fiche à côté de celui de l'ensemble, avec une case par ligne. Rien n'est écrit avant Update selected (Mettre à jour la sélection) ; seules les colonnes différentes des lignes cochées changent. Tout le reste est conservé, marque comprise.

La comparaison est volontairement étroite :

Reformuler ou renommer une entrée peut donc atteindre les personnages qui l'ont déjà choisie, s'ils l'acceptent. Changer le sens d'un nombre ne le peut pas et ne le fera pas : cette colonne appartient au joueur depuis qu'il possède la ligne.

Commandes de fiche : progression permanente

Un ensemble peut accepter des modifications de la construction permanente du personnage avec sheet.advancement :

"advancement": {
  "fields": ["xp", "level", "humanity", "discipline"],
  "abilities": true,
  "skills": true,
  "saves": true,
  "text": "Award XP at the end of a session; change other values only when earned."
}

N'inclus que des identifiants de champs que ta fiche déclare comme nombre ou texte (text ou longtext), 24 au plus. Les booléens autorisent respectivement tous les attributs, toutes les compétences ou toutes les sauvegardes ; omis ou faux, ils n'autorisent rien. Un ensemble d'autorisations vide, un champ inconnu, un type de champ non pris en charge ou une clé inconnue sont refusés à l'importation. text est une consigne facultative d'au plus 400 caractères destinés au prompt. Sans advancement, les commandes existantes se comportent comme avant et toute demande advance est refusée.

[sheet: op="advance" field="xp" by="250" who="party"]
[sheet: op="advance" field="xp" to="1200"]
[sheet: op="advance" field="humanity" by="-1"]
[sheet: op="advance" field="discipline" to="Auspex"]
[sheet: op="advance" ability="str" by="1"]
[sheet: op="advance" ability="str" to="16"]
[sheet: op="advance" skill="stealth" tier="expert"]
[sheet: op="advance" save="will" tier="trained"]

Nomme exactement une cible. Les champs et les attributs prennent exactement l'un de by ou to ; le texte ne prend que to. Les noms et les niveaux correspondent aux identifiants ou aux libellés, sans tenir compte de la casse. Les compétences et les sauvegardes acceptent tout niveau déclaré dans resolution.proficiencyTiers. Les champs numériques respectent integer, min et max ; les attributs respectent leurs bornes entières ; le texte respecte maxLength. Une valeur invalide est refusée avec une raison bad-amount qui indique la borne, jamais ramenée silencieusement dans les limites. Une progression désactivée, des cibles non autorisées, des noms inconnus et des niveaux inconnus sont aussi refusés. La balise réécrite porte result et la nouvelle valeur dans now. Omets who pour le joueur, ou utilise who="party" pour chaque fiche de l'équipe identifiée sans ambiguïté ; les refus partiels nomment les fiches qui n'ont pas pu progresser.

Le moteur enregistre la construction sur la fiche de personnage de la partie par le même stockage de métadonnées qu'une modification du joueur, en gardant sa version de fiche et en recalculant les valeurs dérivées à partir de la nouvelle construction. Les fiches de la bibliothèque ne sont pas modifiées. Comme pour les commandes vivantes, un swipe alternatif ou une réponse régénérée part d'avant les modifications de la réponse sortante. Chaque réponse enregistre la construction à laquelle elle aboutit, y compris les réponses sans progression. Sélectionner un swipe restaure son instantané ; supprimer la dernière réponse fait réapparaître l'instantané précédent. Supprimer une réponse plus ancienne laisse un instantané ultérieur en vigueur jusqu'à ce que cette réponse ultérieure soit supprimée. Une continuation met à jour l'instantané de la même réponse. Annuler ne modifie que les valeurs qui correspondent encore au résultat enregistré : les modifications ultérieures du joueur sont conservées, et des modifications sans rapport n'empêchent pas d'annuler une attribution d'XP.

La commande use : laisser le Game Master payer un prix que tu as écrit

Pendant la narration, le GM actualise les fiches avec [sheet: ...] : spend, restore (heal a le même sens), damage, temp, track, condition, note et rest. Un ensemble avec catalogues en reçoit une autre :

[sheet: who="Mira" op="use" name="Fireball"]
[sheet: who="Mira" op="use" name="Fireball" pool="3rd-level slots"]

op="cast" équivaut à op="use" et spell= à name= : l'expression choisie par le GM fonctionne sans que ton format connaisse le mot "spell".

Le nom est comparé sans tenir compte de la casse aux lignes du personnage issues de tes catalogues. Une ligne répond au nom montré au GM (colonne de nom sheetSummary, puis pools.nameColumn, puis première colonne de texte) et au label de l'entrée d'origine. Renommer une ligne ne fait donc pas perdre son accès. Un nom sans correspondance et un nom partagé par deux entrées distinctes sont tous deux refusés.

Ce qui est dépensé :

pool= représente l'incantation à niveau supérieur : le même prix unique, payé depuis une autre réserve du groupe. Il est accepté seulement si le coût a un terme unique et si la réserve nommée appartient au même groupe, ou paie pour ce groupe. Tout autre cas est refusé plutôt que réinterprété.

Une réserve qui paie pour un autre groupe (Capability API 1.171). Le pays d'une réserve vivante nomme jusqu'à quatre autres groupes dont elle peut aussi payer les coûts, à son propre niveau (son level, ou sa place dans son propre groupe), et jamais pour un coût d'un niveau supérieur au sien. Les emplacements de pacte de 5e paient pour des sorts dont le coût est exprimé en emplacements de sorts, comme peut le faire un occultiste multiclassé :

{
  "id": "pact_slots",
  "label": "Pact Slots",
  "group": "pact",
  "max": { "field": "pact_slot_count" },
  "level": { "field": "pact_slot_level" },
  "pays": ["slots"]
}

Un combat propose une telle réserve à côté des réserves supérieures du groupe propre au sort, et un paiement depuis elle fait grandir le sort d'autant de crans que son niveau dépasse celui du sort : un emplacement de pacte de niveau 3 paie Magic Missile comme le ferait un emplacement de niveau 3. Une utilisation dont la propre réserve est vide est toujours proposée, payée seulement depuis une réserve supérieure ou une réserve qui paie pour elle : un sort de niveau 1 se lance ainsi avec un emplacement de niveau 2 une fois les emplacements de niveau 1 épuisés. targetsPerCostStep (1.171, de 1 à 10) sur les mechanics d'une entrée ajoute autant de cibles pour chaque cran dont elle est payée au-dessus, jusqu'à 20 au plus : Hold Person s'écrit "targetCount": 1, "targetsPerCostStep": 1.

C'est tout ou rien. Si une partie du prix ne peut pas être payée, toute la commande est refusée, rien ne change et le joueur est averti. Une entrée gratuite, comme un tour de magie ou une capacité passive, est acceptée sans rien changer.

Dans le fichier, ou dans son propre fichier

Un petit catalogue est intégré à ruleset.json, dans les entries de son en-tête. Un long catalogue a son fichier, nommé par asset dans l'en-tête. Il possède exactement l'une de ces deux formes.

{ "id": "knacks", "label": "Knacks", "feeds": ["knacks"], "asset": "catalogs/knacks.json" }

Le chemin est toujours catalogs/<the catalog's id>.json. Le fichier ressemble à ceci :

{ "schemaVersion": 1, "catalog": "knacks", "entries": [] }

Les fichiers de catalogue séparés viennent dans un package : un package publié par le catalogue officiel, ou un fichier de package d'ensemble que tu importes toi-même (voir Partager ton ensemble de règles). Le package liste le fichier dans contributions.assets.paths avec ruleset.json et nécessite Capability API 1.21. Un catalogue de créatures, intégré ou séparé, nécessite Capability API 1.27, et un catalogue d'objets, 1.49. Un ensemble importé comme fichier unique ou partagé par GitHub contient ses catalogues dans le fichier, donc ils doivent tenir dans sa limite totale de 512 KB. Cela permet quelques centaines d'entrées courtes.

Les limites sont douze catalogues par ensemble, 2000 entrées par catalogue dans les deux cas et 2 MB par fichier de catalogue. Un package dont le ruleset.json dépasse 256 KB, ou dont un fichier de catalogue dépasse 1 MB, nécessite Capability API 1.340, qui a relevé ces deux limites.