Salta al contenuto principale

Framework in codice

Scrivere le regole

Scrivi constructor, azioni e funzioni del registro in un sottoinsieme piccolo e sicuro di TypeScript.

Tre tipi di file di regole

TipoFormaCosa può fare
Costruttoreexport function construct(...inputs): { fields }Legge i suoi input e restituisce i valori iniziali. Non scrive mai.
Azioneexport function id(self: ElementRef, ...inputs): voidModifica i valori con ledger.set e ledger.add.
Funzione del registroexport function id(currentValue: T, self: ElementRef): TLegge i valori e restituisce un valore derivato. Non scrive mai.

Un file di costruttore esporta una funzione. Un file di azione o di registro può esportarne più di una, se ogni nome esportato è un id dichiarato per quel file: un id di azione o un id di funzione di registro. I nomi dei campi salvati usano minuscole, cifre e trattini bassi.

Una funzione del registro può restituire anche testo. Tipizza currentValue e il risultato come string, e dai alla voce di registro dello scenario un currentValue di testo.

rules/band.ts
export function last_band(currentValue: string, self: ElementRef): string {
  const roll: number = element.var(self, "last_roll") as number;
  const target: number = element.var(self, "last_target") as number;
  if (roll === 0) {
    return "No roll yet";
  }
  if (roll <= math.floor(target / 5)) {
    return "Flawless";
  }
  if (roll <= math.floor(target / 2)) {
    return "Strong";
  }
  if (roll <= target) {
    return "Pass";
  }
  return "Miss";
}
  1. Riga 1 Testo in ingresso, testo in uscita.
rules/hurt.ts
export function hurt(self: ElementRef, amount: number): void {
  ledger.add(self, "hp", 0 - amount);
}
  1. Riga 1 L'Elemento viene per primo come self, poi gli input dell'azione. Un'azione restituisce void.

Un file di regole può contenere anche funzioni di supporto. Scrivile senza export e chiamale per nome dallo stesso file. Il file continua a esportare esattamente una funzione, o solo le azioni o le funzioni di registro che dichiara.

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. Righe 1-3 Una funzione di supporto: senza export, chiamata per nome dallo stesso file.
  2. Riga 7 La chiamata alla funzione di supporto.

Un file di regole non può importare codice. Un file di azione esporta una funzione, a meno che più azioni lo condividano. Un export in più da solo fallisce con:

export function hurt(self: ElementRef, amount: number): void {
  ledger.add(self, "hp", 0 - amount);
}
export function hurt_twice(self: ElementRef, amount: number): void {
  ledger.add(self, "hp", 0 - amount - amount);
}
functions but only the action hurt uses it

Per condividere una funzione di supporto tra azioni, fai condividere un file a quelle azioni. Dai a ogni export il nome dell'azione che esegue, dichiara ogni export come id di azione e lascia la funzione di supporto senza export. Le azioni possono appartenere a Framework diversi, e un'azione non può chiamarne un'altra. Ogni funzione porta una copia dell'intero file nel gioco compilato, quindi tieni brevi i file condivisi.

Una funzione di supporto può solo calcolare e restituire un valore. Non può chiamare ledger.set né ledger.add. Una funzione di supporto che scrive viene rifiutata, anche in un file condiviso da azioni:

function wound(self: ElementRef): void {
  ledger.add(self, "hp", -1);
}
effectful action helpers are not supported until call-target provenance is enforced

Lascia che la funzione di supporto restituisca il valore, e che ogni azione faccia la propria scrittura. Queste due azioni condividono una funzione di supporto pura:

rules/hp.ts
function clamp(value: number, low: number, high: number): number {
  return math.max(low, math.min(high, value));
}

export function hurt(self: ElementRef, amount: number): void {
  const hp: number = element.var(self, "hp") as number;
  const max_hp: number = element.var(self, "max_hp") as number;
  ledger.set(self, "hp", clamp(hp - amount, 0, max_hp));
}

export function heal(self: ElementRef, amount: number): void {
  const hp: number = element.var(self, "hp") as number;
  const max_hp: number = element.var(self, "max_hp") as number;
  ledger.set(self, "hp", clamp(hp + amount, 0, max_hp));
}
  1. Righe 1-3 Una funzione di supporto pura: senza export, restituisce soltanto un valore.
  2. Riga 8 L'azione scrive il valore restituito dalla funzione di supporto.
  3. Riga 14 La seconda azione riusa la funzione di supporto.

Un costruttore che riempie gli slot figli restituisce { values, children } invece di un semplice record. L'avvio rapido mostra il record semplice, e la pagina sugli schemi del Framework mostra la forma con i figli.

Il sottoinsieme di TypeScript

Le regole girano allo stesso modo ovunque e finiscono sempre. Per questo il linguaggio è piccolo.

AmmessoNon ammesso
Tipi espliciti, const e letimport, classi, arrow function, new, this
if ed else, for of, returncicli while, for (;;), switch, break, continue, codice async, eccezioni
Letterali di numero, stringa, booleano, record e arrayTemplate string, spread, optional chaining, ??, commenti a blocco (usa //)
Aritmetica, confronto stretto (=== e !==), operatori booleani, ternari== e !=, **, ++ e +=, typeof, modificare gli oggetti sul posto, valori predefiniti dei parametri
Chiamate a funzioni host, funzioni di supporto dello stesso file e cast con as dei valori che restituisconoChiamate a metodi come "a".toUpperCase(), Math, Date, JSON, fetch e altri globali

Termina ogni istruzione con un punto e virgola. Ognuno di questi errori viene rilevato quando verifichi il progetto.

export function heal(self: ElementRef, amount: number): void {
  while (amount > 0) {
    ledger.add(self, "hp", 1);
    amount = amount - 1;
  }
}
while loops are not allowed
export function heal(self: ElementRef, amount: number): void {
  ledger.set(self, "hp", Math.min(10, amount));
}
'Math' is a host global and not part of the TypeScript subset
import { clamp } from "./clamp";
import is not allowed

Una funzione con un tipo di ritorno deve restituire un valore su ogni percorso.

export function hurt(self: ElementRef, amount: number): number {
  ledger.add(self, "hp", 0 - amount);
}
a non-void function must return a value on every reachable path

Altri errori che la verifica rileva

Ogni riga cita una parte di un messaggio e dice cosa scrivere al suo posto.

MessaggioCosa scrivere al suo posto
block comments are not allowedUsa commenti di riga //.
switch is not allowedUsa if ed else.
break is not allowedCambia il ciclo, oppure racchiudi il resto del suo corpo in un if.
continue is not allowedRacchiudi il resto del corpo del ciclo in un if.
classic for loops are not allowedUsa for (const item of list).
loose equality is not allowedUsa ===.
loose inequality is not allowedUsa !==.
exponentiation (**) is not allowedUsa math.pow.
increment (++) is not allowedScrivi count = count + 1.
compound assignment is not allowedScrivi count = count + value.
typeof is not allowedConfronta con === rispetto a un letterale.
class is not allowedUsa funzioni semplici.
arrow functions are not allowedScrivi una funzione di supporto all'inizio del file.
async is not allowedRimuovi async. Le regole girano in un solo passaggio.
template literals are not allowedUsa letterali stringa.
spread/rest (...) is not allowedScrivi gli elementi uno per uno.
optional chaining (?.) is not allowedConfronta prima con null.
nullish coalescing (??) is not allowedUsa un ternario che confronta con null.
throw is not allowedEsci dalla regola senza scrivere.
try/catch is not allowedRimuovi try e catch.
new is not allowedScrivi invece dei letterali.
this is not allowedUsa il parametro self.
method calls are not allowedChiama solo funzioni host e funzioni di supporto dello stesso file.
default parameter values (=) are not allowedDai un valore a ogni parametro dove la regola viene usata.
assignment targets must be local variablesCostruisci un nuovo valore invece di modificare un oggetto.
functions but only the actionRimuovi export dalle funzioni di supporto.
constructors must be pure and cannot read or write host stateLeggi e scrivi nelle azioni e nelle funzioni del registro.
is not available untilUna funzione del registro non può scrivere. Usa un'azione.
effectful action helpers are not supportedFai restituire un valore alla funzione di supporto e scrivilo nell'azione.

Chiamate host

Le regole raggiungono Fabled tramite chiamate host. Le scrivi così come sono, ad esempio ledger.add(...). Non le importi mai.

In queste firme, HostValue è qualsiasi valore che una chiamata host può restituire, quindi convertilo con as prima di usarlo. AbiValue è un numero, un testo, un booleano o null, oppure un elenco o un record di questi. Un parametro sticky facoltativo è per uso interno di Fabled, quindi omettilo.

element.var(ref: ElementRef, name: HostValue, sticky?: AbiValue): HostValue;
ledger.set(ref: ElementRef, key: string, value: AbiValue): void;
ledger.add(ref: ElementRef, key: string, value: number): void;
math.floor(x: number): number;
math.ceil(x: number): number;
math.round(x: number): number;
math.max(...values: number[]): number;
math.min(...values: number[]): number;
math.abs(x: number): number;
math.pow(base: number, exponent: number): number;
math.sqrt(x: number): number;
list.add(ref: ElementRef, key: string, item: ContentRef, offer: ContentOffer): void;
list.remove(ref: ElementRef, key: string, item: ContentRef): void;
list.count(ref: ElementRef, key: string): number;
offer.where(where: ContentWhere, opts?: { maxItems?: number; flag?: { by: string; map: Record<string, string> } }): ContentOffer;
ChiamataDoveCosa fa
element.varAzioni, funzioni del registroLegge un valore salvato di un Elemento. Converti il risultato con as number, as string o as boolean.
ledger.setAzioniImposta un campo salvato su un valore.
ledger.addAzioniAggiunge un numero a un campo salvato. Aggiungi un numero negativo per sottrarre.
math.floorOvunqueArrotonda per difetto.
math.ceilOvunqueArrotonda per eccesso.
math.roundOvunqueArrotonda al numero intero più vicino.
math.maxOvunqueRestituisce il più grande dei suoi numeri.
math.minOvunqueRestituisce il più piccolo dei suoi numeri.
math.absOvunqueRestituisce la grandezza di un numero senza il segno.
math.powOvunqueEleva una base a un esponente.
math.sqrtOvunqueRestituisce la radice quadrata.
list.addAzioniAggiunge la voce scelta a un elenco. L'offerta decide cosa si può aggiungere. Rifiutata con NOT_OFFERED, ALREADY_IN_LIST o LIST_FULL.
list.removeAzioniRimuove la voce scelta da un elenco. Rifiutata con NOT_IN_LIST.
list.countAzioniRestituisce quante voci contiene un elenco. Usala in una condizione prima di list.add.
offer.whereAzioniCostruisce l'offerta per list.add: le voci i cui campi corrispondono a condizioni che scrivi come valori letterali.

Una chiamata a un elenco indica un elenco del Framework con una chiave letterale, e la voce è un ContentRef, un input che contiene la voce scelta dal giocatore. Un'offerta accetta solo condizioni letterali, come offer.where({ weight: { lte: 3 } }). Ogni campo accetta eq, ne, gte, lte, in o has. Un'offerta non può leggere l'Elemento. Per dipendere da un valore del personaggio, controllalo con if prima di list.add.

rules/pack-trinket.ts
export function pack_trinket(self: ElementRef, trinket: ContentRef): void {
  if (list.count(self, "pack") < 2) {
    list.add(self, "pack", trinket, offer.where({ weight: { lte: 3 } }));
  }
}
  1. Riga 2 Fermati quando lo zaino contiene già due voci.
  2. Riga 3 L'offerta ammette solo voci che pesano 3 o meno.
rules/unpack-trinket.ts
export function unpack_trinket(self: ElementRef, trinket: ContentRef): void {
  list.remove(self, "pack", trinket);
}
  1. Riga 2 Rimuovi la voce scelta. Un'azione su un elenco che si limita a rimuovere ha bisogno di un solo passo accettato.

I nomi dei campi si scrivono per esteso

In un'azione, il nome di un campo deve essere una stringa scritta nel codice, come "hp". Non può venire da un valore. Una regola come questa fallisce:

export function hurt(self: ElementRef, skill: string): void {
  const value: number = element.var(self, skill) as number;
  ledger.set(self, "hp", value);
}
Action accesses require literal keys on self.

Quindi, senza un elenco di campi, un'azione non può controllare un'abilità qualsiasi per nome. Scrivi un'azione per ogni abilità e lascia che ognuna legga il proprio campo. La ricetta Tiro contro un'abilità mostra una breve azione da copiare. L'eccezione è un input che elenca i suoi campi. Dai all'input dell'azione un elenco "fields", come ["lore", "library_use"], e l'azione può passare quell'input come nome, come in ledger.add(self, skill, amount). Il controllo tratta l'azione come se toccasse ogni campo dell'elenco, e il server accetta solo una scrittura nel campo scelto dal giocatore.

Questa azione aumenta l'abilità scelta dal giocatore:

schemas/keeper.json
    {
      "id": "improve",
      "name": "Improve a skill",
      "source": "rules/improve.ts",
      "inputs": {
        "skill": { "label": "Skill", "fields": ["lore", "library_use"] }
      }
    }
  1. Riga 25 L'input skill sceglie uno di questi campi per nome. Un elenco ne richiede almeno due.
rules/improve.ts
export function improve(self: ElementRef, skill: string): void {
  ledger.add(self, skill, 1);
}
  1. Riga 2 skill contiene il nome del campo scelto dal giocatore.

Ogni nome in fields deve essere un campo memorizzato del Framework che un'azione può scrivere. Un campo derivato da una funzione del registro, o un modificatore, viene rifiutato. Se il campo ha un input dello schema con lo stesso nome e un label, la finestra mostra quell'etichetta; altrimenti mostra l'identificatore, per esempio library_use. Ad esempio, inputs.library_use.label può chiamare la scelta Uso della biblioteca. Il valore selezionato, i parametri degli scenari e i segnaposto del registro come {skill} continuano a usare l'identificatore del campo. Qualsiasi elenco nativo di scelte testuali i cui valori sono tutti nomi di campi scalari modificabili usa le stesse etichette.

fields "skill_total" is not a writable state field

Solo campi dichiarati

Un'azione può scrivere solo i campi che il constructor restituisce. La verifica nomina il campo che non trova.

Action "keeper.heal" calls ledger.set(self, "mana", ...), and "mana" is not declared on this schema. Declared scalar fields: hp, max_hp, name.

Avanti