Code frameworks
Escribir reglas
Escribe constructores, acciones y funciones de ledger en un subconjunto pequeño y seguro de TypeScript.
Tres tipos de archivo de reglas
| Tipo | Forma | Qué puede hacer |
|---|---|---|
| Constructor | export function construct(...inputs): { fields } | Lee sus entradas y devuelve los valores iniciales. Nunca escribe. |
| Acción | export function id(self: ElementRef, ...inputs): void | Cambia valores con ledger.set y ledger.add. |
| Función del ledger | export function id(currentValue: T, self: ElementRef): T | Lee valores y devuelve un valor derivado. Nunca escribe. |
Un archivo de constructor exporta una función. Un archivo de acción o de ledger puede exportar varias, si cada nombre exportado es un id declarado para ese archivo: un id de acción o un id de función de ledger. Los nombres de campos guardados usan minúsculas, dígitos y guiones bajos.
Una función del ledger también puede devolver texto. Declara currentValue y el resultado como string, y da a la entrada de ledger del escenario un currentValue de texto.
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";
}- Línea 1 Entra texto, sale texto.
export function hurt(self: ElementRef, amount: number): void {
ledger.add(self, "hp", 0 - amount);
}- Línea 1 El Elemento va primero como self, y después las entradas propias de la acción. Una acción devuelve void.
Un archivo de reglas también puede contener funciones auxiliares. Escríbelas sin export y llámalas por su nombre desde el mismo archivo. El archivo sigue exportando exactamente una función, o solo las acciones o funciones de ledger que declara.
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);
}- Líneas 1-3 Una función auxiliar: sin export, y se llama por su nombre desde el mismo archivo.
- Línea 7 La llamada a la función auxiliar.
Un archivo de reglas no puede importar código. Un archivo de acción exporta una función, salvo que varias acciones lo compartan. Una exportación de más por sí sola falla 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 itPara compartir una función auxiliar entre acciones, haz que esas acciones compartan un archivo. Nombra cada exportación como la acción que ejecuta, declara cada exportación como id de acción y deja la función auxiliar sin export. Las acciones pueden pertenecer a distintos Frameworks, y una acción no puede llamar a otra. Cada función lleva una copia de todo su archivo en el juego compilado, así que mantén cortos los archivos compartidos.
Una función auxiliar solo puede calcular y devolver un valor. No puede llamar a ledger.set ni a ledger.add. Una función auxiliar que escribe se rechaza, incluso en un archivo que comparten acciones:
function wound(self: ElementRef): void {
ledger.add(self, "hp", -1);
}effectful action helpers are not supported until call-target provenance is enforcedHaz que la función auxiliar devuelva el valor y que cada acción haga su propia escritura. Estas dos acciones comparten una función auxiliar 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));
}- Líneas 1-3 Una función auxiliar pura: sin export, y solo devuelve un valor.
- Línea 8 La acción escribe el valor que devolvió la función auxiliar.
- Línea 14 La segunda acción reutiliza la función auxiliar.
Un constructor que rellena ranuras hijas devuelve { values, children } en lugar de un registro simple. El inicio rápido muestra el registro simple, y la página de esquemas de Framework muestra la forma con hijos.
El subconjunto de TypeScript
Las reglas se ejecutan igual en todas partes y siempre terminan. Por eso el lenguaje es pequeño.
| Permitido | No permitido |
|---|---|
| Tipos explícitos, const y let | import, clases, funciones flecha, new, this |
| if y else, for of, return | bucles while, for (;;), switch, break, continue, código async, excepciones |
| Literales de número, cadena, booleano, registro y arreglo | Plantillas de texto, spread, encadenamiento opcional, ??, comentarios de bloque (usa //) |
| Aritmética, comparación estricta (=== y !==), operadores booleanos, ternarios | == y !=, **, ++ y +=, typeof, cambiar objetos en el sitio, valores por defecto en parámetros |
| Llamadas a funciones del host, funciones auxiliares del mismo archivo y casts con as de los valores que devuelven | Llamadas a métodos como "a".toUpperCase(), Math, Date, JSON, fetch y otros globales |
Termina cada instrucción con punto y coma. Cada uno de estos errores se detecta cuando compruebas el proyecto.
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 función con tipo de retorno debe devolver un valor en todos los caminos.
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 pathMás errores que detecta la comprobación
Cada fila cita parte de un mensaje y dice qué escribir en su lugar.
| Mensaje | Qué escribir en su lugar |
|---|---|
| block comments are not allowed | Usa comentarios de línea con //. |
| switch is not allowed | Usa if y else. |
| break is not allowed | Cambia el bucle, o envuelve el resto de su cuerpo en un if. |
| continue is not allowed | Envuelve el resto del cuerpo del bucle en 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 | Escribe count = count + 1. |
| compound assignment is not allowed | Escribe count = count + value. |
| typeof is not allowed | Compara con === contra un literal. |
| class is not allowed | Usa funciones simples. |
| arrow functions are not allowed | Escribe una función auxiliar al principio del archivo. |
| async is not allowed | Quita async. Las reglas se ejecutan en un solo paso. |
| template literals are not allowed | Usa cadenas literales. |
| spread/rest (...) is not allowed | Escribe los elementos uno a uno. |
| optional chaining (?.) is not allowed | Compara primero con null. |
| nullish coalescing (??) is not allowed | Usa un ternario que compare con null. |
| throw is not allowed | Sal de la regla sin escribir. |
| try/catch is not allowed | Quita try y catch. |
| new is not allowed | Escribe literales en su lugar. |
| this is not allowed | Usa el parámetro self. |
| method calls are not allowed | Llama solo a funciones del host y a funciones auxiliares del mismo archivo. |
| default parameter values (=) are not allowed | Da un valor a cada parámetro donde se use la regla. |
| assignment targets must be local variables | Crea un valor nuevo en lugar de cambiar un objeto. |
| functions but only the action | Quita export de las funciones auxiliares. |
| constructors must be pure and cannot read or write host state | Lee y escribe en acciones y funciones de ledger. |
| is not available until | Una función de ledger no puede escribir. Usa una acción. |
| effectful action helpers are not supported | Haz que la función auxiliar devuelva un valor y escríbelo en la acción. |
Llamadas del host
Las reglas llegan a Fabled mediante llamadas del host. Las escribes tal cual, como ledger.add(...). Nunca las importas.
En estas firmas, HostValue es cualquier valor que una llamada del host puede devolver, así que conviértelo con as antes de usarlo. AbiValue es un número, un texto, un booleano o null, o una lista o un registro de esos valores. Un parámetro opcional sticky es de uso interno de Fabled, así que omítelo.
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;| Llamada | Dónde | Qué hace |
|---|---|---|
| element.var | Acciones, funciones del ledger | Lee un valor guardado de un Elemento. Convierte el resultado con as number, as string o as boolean. |
| ledger.set | Acciones | Establece un campo guardado en un valor. |
| ledger.add | Acciones | Suma un número a un campo guardado. Suma un número negativo para restar. |
| math.floor | En cualquier lugar | Redondea hacia abajo. |
| math.ceil | En cualquier lugar | Redondea hacia arriba. |
| math.round | En cualquier lugar | Redondea al número entero más cercano. |
| math.max | En cualquier lugar | Devuelve el mayor de sus números. |
| math.min | En cualquier lugar | Devuelve el menor de sus números. |
| math.abs | En cualquier lugar | Devuelve el tamaño de un número sin su signo. |
| math.pow | En cualquier lugar | Eleva una base a un exponente. |
| math.sqrt | En cualquier lugar | Devuelve la raíz cuadrada. |
| list.add | Acciones | Añade la entrada elegida a una lista. La oferta decide qué se puede añadir. Se rechaza con NOT_OFFERED, ALREADY_IN_LIST o LIST_FULL. |
| list.remove | Acciones | Quita la entrada elegida de una lista. Se rechaza con NOT_IN_LIST. |
| list.count | Acciones | Devuelve cuántas entradas tiene una lista. Úsala en una guarda antes de list.add. |
| offer.where | Acciones | Construye la oferta para list.add: las entradas cuyos campos cumplen condiciones que escribes como literales. |
Una llamada de lista nombra una lista del Framework con una clave literal, y la entrada es un ContentRef, una entrada que guarda la entrada que elige el jugador. Una oferta solo admite condiciones literales, como offer.where({ weight: { lte: 3 } }). Cada campo admite eq, ne, gte, lte, in o has. Una oferta no puede leer el Elemento. Para depender de un valor del personaje, compruébalo con if antes de 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 } }));
}
}- Línea 2 Detente cuando la mochila ya tenga dos entradas.
- Línea 3 La oferta solo permite entradas que pesen 3 o menos.
export function unpack_trinket(self: ElementRef, trinket: ContentRef): void {
list.remove(self, "pack", trinket);
}- Línea 2 Quita la entrada elegida. Una acción de lista que solo quita necesita un paso aceptado.
Los nombres de campo se escriben tal cual
En una acción, el nombre de un campo debe ser una cadena escrita en el código, como "hp". No puede venir de un valor. Una regla como esta falla:
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.Por eso, sin una lista de campos, una acción no puede comprobar cualquier habilidad por su nombre. Escribe una acción para cada habilidad y deja que cada una lea su propio campo. La receta Tirar contra una habilidad muestra una acción corta que puedes copiar. La excepción es una entrada que enumera sus campos. Dale a la entrada de la acción una lista "fields", como ["lore", "library_use"], y la acción podrá pasar esa entrada como nombre, como en ledger.add(self, skill, amount). La comprobación trata la acción como si tocara todos los campos de la lista, y el servidor solo acepta una escritura en el campo que eligió el jugador.
Esta acción sube la habilidad que elija el jugador:
{
"id": "improve",
"name": "Improve a skill",
"source": "rules/improve.ts",
"inputs": {
"skill": { "label": "Skill", "fields": ["lore", "library_use"] }
}
}- Línea 25 La entrada skill elige uno de estos campos por su nombre. Una lista necesita al menos dos.
export function improve(self: ElementRef, skill: string): void {
ledger.add(self, skill, 1);
}- Línea 2 skill contiene el nombre del campo que eligió el jugador.
Cada nombre de fields debe ser un campo almacenado del Framework que una acción pueda escribir. Un campo que deriva una función del ledger, o un modificador, se rechaza. Si el campo tiene una entrada del esquema con el mismo nombre y un label, el diálogo muestra esa etiqueta; de lo contrario, muestra el identificador, como library_use. Por ejemplo, inputs.library_use.label puede llamar a la opción Uso de biblioteca. El valor seleccionado, los parámetros de los escenarios y los marcadores del registro como {skill} siguen usando el identificador del campo. Toda lista nativa de opciones de texto cuyos valores sean nombres de campos escalares modificables usa las mismas etiquetas.
fields "skill_total" is not a writable state fieldSolo campos declarados
Una acción solo puede escribir los campos que devuelve el constructor. La comprobación nombra el campo que no encuentra.
Action "keeper.heal" calls ledger.set(self, "mana", ...), and "mana" is not declared on this schema. Declared scalar fields: hp, max_hp, name.
