Aller au contenu principal

Frameworks de code

Réserves et modificateurs

Partagez un budget de points entre des entrées, et laissez le contenu changer des nombres sur le personnage.

Réserves

Une réserve limite un groupe d'entrées numériques qu'un joueur remplit ensemble, comme trois traits qui partagent un budget de points. Le serveur vérifie chaque réserve quand un personnage est créé ou modifié.

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. Ligne 23 Les entrées numériques de la réserve. Chacune a besoin d'une entrée sous inputs.
  2. Ligne 24 Les méthodes que le joueur peut utiliser.
  3. Ligne 26 Le coût total des scores ne peut pas dépasser le budget.
  4. Ligne 27 Chaque score est au moins égal à cette valeur.
  5. Ligne 28 Chaque score est au plus égal à cette valeur.
  6. Ligne 29 Le coût de chaque score. Chaque score de min à max en a besoin d'un.
MéthodeCe qu'elle vérifie
standardArrayLes scores sont les valeurs listées, dans n'importe quel ordre.
pointBuyChaque score est entre min et max, et la somme des coûts est au plus égale au budget.
manualChaque score est entre min et max.
rollLe serveur lance count scores avec dice, ajoute le bonus facultatif, multiplie par le multiplier facultatif, et le joueur attribue les totaux aux entrées.
budgetChaque score est entre min et max, et les scores totalisent au plus total.

Une réserve doit déclarer au moins une méthode. Une réserve avec plus d'une méthode nomme dans methodInput l'entrée qui choisit la méthode. Les préréglages et les scénarios doivent respecter la réserve, et un scénario le montre :

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. Lignes 7-9 Ces scores coûtent 2, 1 et 0, donc le total est 3 et le budget est 6.
is not satisfied: pointBuy.budget
pointBuy costs has no entry for 5
A pool declares at least one method.
LimiteValeur
Réserves par Framework4
Entrées par réserve64
Valeurs entre min et max64
Budget1000

La méthode budget prend total, min et max. Chaque score est un nombre entier de min à max, et les scores totalisent au plus total. Les scores n'ont pas de prix, donc elle convient à un budget de points de compétence et n'a besoin d'aucun coût.

Scores lancés

La méthode roll fait lancer les scores par le serveur, si bien qu'un joueur ne peut pas saisir les siens. Elle prend dice et count, et deux clés facultatives, bonus et multiplier. dice est un groupe de dés, comme "3d6" ou "4d6dl1". count est le nombre de scores que le serveur lance, et il égale le nombre d'entrées du pool. bonus (de -20 à 100) s'ajoute à chaque total de dés, puis multiplier (de 1 à 10) le multiplie, de sorte que chaque score vaut (total des dés + bonus) × multiplier. Une caractéristique 3d6 × 5, comme dans L'Appel de Cthulhu, s'écrit dice "3d6" avec 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. Ligne 29 Une réserve avec plus d'une méthode nomme l'entrée qui choisit la méthode.
  2. Ligne 31 Le joueur peut saisir les scores.
  3. Ligne 32 Ou le serveur lance 3 scores de 3d6, un pour chaque entrée.

methodInput nomme l'entrée de texte qui choisit la méthode d'un pool. Le choix du joueur dans cette entrée décide de la méthode que le serveur vérifie, de sorte que les mêmes scores peuvent être soumis au tableau standard, à l'achat de points ou à un lancer. Un pool à une seule méthode n'a pas besoin de methodInput et utilise toujours cette méthode. Avec plusieurs méthodes, il est obligatoire, et les options de l'entrée doivent être exactement les noms de méthode du pool. Un scénario ou un preset règle l'entrée sur l'un de ces noms. Une valeur absente ou qui n'est pas un nom de méthode échoue avec method.missing ou method.unknown.

schemas/keeper.json
    "method": {
      "label": "Scores by",
      "options": ["manual", "roll"],
      "defaultValue": "manual"
    },
  1. Ligne 11 Exactement les méthodes de la réserve, rien d'autre.

Tout total que le lancer peut donner, après le bonus et le multiplier, doit tenir dans min et max de chaque entrée. 3d6 × 5 donne de 15 à 90, donc chaque trait autorise de 15 à 90. Les dés forment un seul groupe : jusqu'à 10 dés de 100 faces au plus, et dl, dh, kl ou kh écarte ou garde de 1 à 9 d'entre eux, en gardant au moins un dé. Écris un multiplicateur avec la clé multiplier, jamais dans les dés : 3d6x5 est refusé.

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

Une clé manquante apparaît sous forme de liste JSON. Le chemin de la liste nomme la clé, ici pools, traits, methods, roll, dice, et le message dit Required.

Required

Dans le formulaire de création de personnage, une réserve avec roll affiche un bouton Lancer les scores à la place de ses entrées, puis Tout relancer après le premier jet. Le serveur lance et garde une trace, donc le navigateur ne lance jamais. Le joueur attribue ensuite les totaux aux entrées dans l'ordre qu'il veut. Le lancer n'est proposé que dans le formulaire de création.

Un scénario qui choisit la méthode roll fournit lui-même les lancers dans poolRolls, car les vrais lancers n'existent que sur le serveur. poolRolls associe l'identifiant d'un pool à un slot par score lancé. Un slot a un total, le nombre final après le bonus et le multiplier, et facultativement dice et dropped, qui servent seulement à la lecture. La vérification du pool traite alors les slots comme l'enregistrement du serveur : les scores du scénario doivent être ces totaux, dans n'importe quel ordre. Chaque total doit être un total que le lancer peut produire. Sans poolRolls, un scénario qui choisit roll échoue avec roll.missing. Un preset ne peut pas utiliser la méthode roll. Le formulaire de création teste le vrai lancer.

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. Ligne 7 Le scénario choisit la méthode roll.
  2. Lignes 21-27 Les lancers enregistrés. Chaque total est la somme des dés fois le multiplier, ici 9 × 5 = 45.
is not satisfied: roll.missing

Modificateurs et effets

Un modificateur est un nombre que la plateforme dérive du contenu qu'un personnage détient. Une liste qui applique des effets l'alimente. Vos règles le lisent comme un champ. Cet exemple laisse les charmes portés augmenter Guard.

Le Framework du personnage déclare le modificateur.

schemas/keeper.json
  "modifiers": {
    "mod_guard": {
      "label": "Guard",
      "stack": "sum",
      "each": { "min": -3, "max": 3 },
      "total": { "min": -5, "max": 5 },
      "appliesTo": ["guard"]
    }
  },
  1. Ligne 25 Le nom que les joueurs voient.
  2. Ligne 26 sum additionne chaque contribution. max prend la plus grande.
  3. Ligne 27 La plage d'une contribution.
  4. Ligne 28 La plage du total.
  5. Ligne 29 Le champ de registre que le modificateur alimente. C'est un libellé pour les gens. Vos règles font le calcul.

La liste dit que ses entrées appliquent des effets, et elle peut les limiter.

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. Ligne 37 Les entrées de cette liste changent des modificateurs.
  2. Ligne 38 Une entrée du même nom qu'une entrée de la liste est refusée avec SAME_NAME.
  3. Lignes 39-41 Au plus 1 entrée avec l'emplacement Ring. Une autre est refusée avec LIST_CAP.

Le Framework de contenu nomme ses entrées d'effet. Omettez leurs choix et leurs plages, car le compilateur les remplit à partir des modificateurs du personnage.

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. Lignes 13-14 L'effet et son montant n'ont ni options ni plage.
  2. Ligne 16 Un effet : quelle entrée nomme le modificateur, et laquelle contient le montant.
  3. Ligne 27 Le préréglage donne le libellé du modificateur et le montant.

Une fonction de registre lit le modificateur comme tout autre champ.

rules/guard.ts
export function guard(currentValue: number, self: ElementRef): number {
  return 10 + (element.var(self, "mod_guard") as number);
}
  1. Ligne 2 mod_guard est déclaré sur le Framework, pas retourné par le constructeur.

Le scénario prouve chaque cas. expectedValuesAfter vérifie la valeur dérivée après une étape, et expectedModifiers vérifie les modificateurs à la fin.

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. Ligne 14 Guard 12 : 10 plus les 2 de l'anneau.
  2. Ligne 20 Un deuxième anneau est refusé : le plafond en autorise 1.
  3. Ligne 30 Guard 13 avec le 1 de la cape.
  4. Ligne 36 Une deuxième cape du même nom est refusée.
  5. Ligne 46 Guard 11 après le retrait de l'anneau.
  6. Ligne 50 Le total qui reste est le 1 de la cape.
Champ du modificateurSignification
labelLe nom qui apparaît dans les choix d'effet du contenu.
stacksum ou max.
each, totalLa plage d'une contribution et celle du total.
appliesToJusqu'à 8 noms des champs que le modificateur alimente.
appliesWhenUne condition, sous forme de libellé. Vos règles l'appliquent.

Pas encore couvert

D'autres parties avancées existent et ne sont pas couvertes ici : les listes de lignes, les choix et emplacements de contenu, la montée de niveau et les reconstructions, les rôles de combat, et les catalogues d'enregistrements. Chacune a besoin de sa propre page.

Suite