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
| Tipo | Forma | Cosa può fare |
|---|---|---|
| Costruttore | export function construct(...inputs): { fields } | Legge i suoi input e restituisce i valori iniziali. Non scrive mai. |
| Azione | export function id(self: ElementRef, ...inputs): void | Modifica i valori con ledger.set e ledger.add. |
| Funzione del registro | export function id(currentValue: T, self: ElementRef): T | Legge 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.
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";
}- Riga 1 Testo in ingresso, testo in uscita.
export function hurt(self: ElementRef, amount: number): void {
ledger.add(self, "hp", 0 - amount);
}- 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.
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);
}- Righe 1-3 Una funzione di supporto: senza export, chiamata per nome dallo stesso file.
- 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 itPer 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 enforcedLascia 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:
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));
}- Righe 1-3 Una funzione di supporto pura: senza export, restituisce soltanto un valore.
- Riga 8 L'azione scrive il valore restituito dalla funzione di supporto.
- 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.
| Ammesso | Non ammesso |
|---|---|
| Tipi espliciti, const e let | import, classi, arrow function, new, this |
| if ed else, for of, return | cicli while, for (;;), switch, break, continue, codice async, eccezioni |
| Letterali di numero, stringa, booleano, record e array | Template 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 restituiscono | Chiamate 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 allowedexport 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 subsetimport { clamp } from "./clamp";import is not allowedUna 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 pathAltri errori che la verifica rileva
Ogni riga cita una parte di un messaggio e dice cosa scrivere al suo posto.
| Messaggio | Cosa scrivere al suo posto |
|---|---|
| block comments are not allowed | Usa commenti di riga //. |
| switch is not allowed | Usa if ed else. |
| break is not allowed | Cambia il ciclo, oppure racchiudi il resto del suo corpo in un if. |
| continue is not allowed | Racchiudi il resto del corpo del ciclo in un if. |
| classic for loops are not allowed | Usa for (const item of list). |
| loose equality is not allowed | Usa ===. |
| loose inequality is not allowed | Usa !==. |
| exponentiation (**) is not allowed | Usa math.pow. |
| increment (++) is not allowed | Scrivi count = count + 1. |
| compound assignment is not allowed | Scrivi count = count + value. |
| typeof is not allowed | Confronta con === rispetto a un letterale. |
| class is not allowed | Usa funzioni semplici. |
| arrow functions are not allowed | Scrivi una funzione di supporto all'inizio del file. |
| async is not allowed | Rimuovi async. Le regole girano in un solo passaggio. |
| template literals are not allowed | Usa letterali stringa. |
| spread/rest (...) is not allowed | Scrivi gli elementi uno per uno. |
| optional chaining (?.) is not allowed | Confronta prima con null. |
| nullish coalescing (??) is not allowed | Usa un ternario che confronta con null. |
| throw is not allowed | Esci dalla regola senza scrivere. |
| try/catch is not allowed | Rimuovi try e catch. |
| new is not allowed | Scrivi invece dei letterali. |
| this is not allowed | Usa il parametro self. |
| method calls are not allowed | Chiama solo funzioni host e funzioni di supporto dello stesso file. |
| default parameter values (=) are not allowed | Dai un valore a ogni parametro dove la regola viene usata. |
| assignment targets must be local variables | Costruisci un nuovo valore invece di modificare un oggetto. |
| functions but only the action | Rimuovi export dalle funzioni di supporto. |
| constructors must be pure and cannot read or write host state | Leggi e scrivi nelle azioni e nelle funzioni del registro. |
| is not available until | Una funzione del registro non può scrivere. Usa un'azione. |
| effectful action helpers are not supported | Fai 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;| Chiamata | Dove | Cosa fa |
|---|---|---|
| element.var | Azioni, funzioni del registro | Legge un valore salvato di un Elemento. Converti il risultato con as number, as string o as boolean. |
| ledger.set | Azioni | Imposta un campo salvato su un valore. |
| ledger.add | Azioni | Aggiunge un numero a un campo salvato. Aggiungi un numero negativo per sottrarre. |
| math.floor | Ovunque | Arrotonda per difetto. |
| math.ceil | Ovunque | Arrotonda per eccesso. |
| math.round | Ovunque | Arrotonda al numero intero più vicino. |
| math.max | Ovunque | Restituisce il più grande dei suoi numeri. |
| math.min | Ovunque | Restituisce il più piccolo dei suoi numeri. |
| math.abs | Ovunque | Restituisce la grandezza di un numero senza il segno. |
| math.pow | Ovunque | Eleva una base a un esponente. |
| math.sqrt | Ovunque | Restituisce la radice quadrata. |
| list.add | Azioni | Aggiunge la voce scelta a un elenco. L'offerta decide cosa si può aggiungere. Rifiutata con NOT_OFFERED, ALREADY_IN_LIST o LIST_FULL. |
| list.remove | Azioni | Rimuove la voce scelta da un elenco. Rifiutata con NOT_IN_LIST. |
| list.count | Azioni | Restituisce quante voci contiene un elenco. Usala in una condizione prima di list.add. |
| offer.where | Azioni | Costruisce 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.
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 } }));
}
}- Riga 2 Fermati quando lo zaino contiene già due voci.
- Riga 3 L'offerta ammette solo voci che pesano 3 o meno.
export function unpack_trinket(self: ElementRef, trinket: ContentRef): void {
list.remove(self, "pack", trinket);
}- 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:
{
"id": "improve",
"name": "Improve a skill",
"source": "rules/improve.ts",
"inputs": {
"skill": { "label": "Skill", "fields": ["lore", "library_use"] }
}
}- Riga 25 L'input skill sceglie uno di questi campi per nome. Un elenco ne richiede almeno due.
export function improve(self: ElementRef, skill: string): void {
ledger.add(self, skill, 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 fieldSolo 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.
