Saltar al contenido principal

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

TipoFormaQué puede hacer
Constructorexport function construct(...inputs): { fields }Lee sus entradas y devuelve los valores iniciales. Nunca escribe.
Acciónexport function id(self: ElementRef, ...inputs): voidCambia valores con ledger.set y ledger.add.
Función del ledgerexport function id(currentValue: T, self: ElementRef): TLee 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.

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. Línea 1 Entra texto, sale texto.
rules/hurt.ts
export function hurt(self: ElementRef, amount: number): void {
  ledger.add(self, "hp", 0 - amount);
}
  1. 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.

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. Líneas 1-3 Una función auxiliar: sin export, y se llama por su nombre desde el mismo archivo.
  2. 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 it

Para 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 enforced

Haz 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:

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. Líneas 1-3 Una función auxiliar pura: sin export, y solo devuelve un valor.
  2. Línea 8 La acción escribe el valor que devolvió la función auxiliar.
  3. 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.

PermitidoNo permitido
Tipos explícitos, const y letimport, clases, funciones flecha, new, this
if y else, for of, returnbucles while, for (;;), switch, break, continue, código async, excepciones
Literales de número, cadena, booleano, registro y arregloPlantillas 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 devuelvenLlamadas 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 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 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 path

Más errores que detecta la comprobación

Cada fila cita parte de un mensaje y dice qué escribir en su lugar.

MensajeQué escribir en su lugar
block comments are not allowedUsa comentarios de línea con //.
switch is not allowedUsa if y else.
break is not allowedCambia el bucle, o envuelve el resto de su cuerpo en un if.
continue is not allowedEnvuelve el resto del cuerpo del bucle en 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 allowedEscribe count = count + 1.
compound assignment is not allowedEscribe count = count + value.
typeof is not allowedCompara con === contra un literal.
class is not allowedUsa funciones simples.
arrow functions are not allowedEscribe una función auxiliar al principio del archivo.
async is not allowedQuita async. Las reglas se ejecutan en un solo paso.
template literals are not allowedUsa cadenas literales.
spread/rest (...) is not allowedEscribe los elementos uno a uno.
optional chaining (?.) is not allowedCompara primero con null.
nullish coalescing (??) is not allowedUsa un ternario que compare con null.
throw is not allowedSal de la regla sin escribir.
try/catch is not allowedQuita try y catch.
new is not allowedEscribe literales en su lugar.
this is not allowedUsa el parámetro self.
method calls are not allowedLlama solo a funciones del host y a funciones auxiliares del mismo archivo.
default parameter values (=) are not allowedDa un valor a cada parámetro donde se use la regla.
assignment targets must be local variablesCrea un valor nuevo en lugar de cambiar un objeto.
functions but only the actionQuita export de las funciones auxiliares.
constructors must be pure and cannot read or write host stateLee y escribe en acciones y funciones de ledger.
is not available untilUna función de ledger no puede escribir. Usa una acción.
effectful action helpers are not supportedHaz 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;
LlamadaDóndeQué hace
element.varAcciones, funciones del ledgerLee un valor guardado de un Elemento. Convierte el resultado con as number, as string o as boolean.
ledger.setAccionesEstablece un campo guardado en un valor.
ledger.addAccionesSuma un número a un campo guardado. Suma un número negativo para restar.
math.floorEn cualquier lugarRedondea hacia abajo.
math.ceilEn cualquier lugarRedondea hacia arriba.
math.roundEn cualquier lugarRedondea al número entero más cercano.
math.maxEn cualquier lugarDevuelve el mayor de sus números.
math.minEn cualquier lugarDevuelve el menor de sus números.
math.absEn cualquier lugarDevuelve el tamaño de un número sin su signo.
math.powEn cualquier lugarEleva una base a un exponente.
math.sqrtEn cualquier lugarDevuelve la raíz cuadrada.
list.addAccionesAñ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.removeAccionesQuita la entrada elegida de una lista. Se rechaza con NOT_IN_LIST.
list.countAccionesDevuelve cuántas entradas tiene una lista. Úsala en una guarda antes de list.add.
offer.whereAccionesConstruye 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.

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. Línea 2 Detente cuando la mochila ya tenga dos entradas.
  2. Línea 3 La oferta solo permite entradas que pesen 3 o menos.
rules/unpack-trinket.ts
export function unpack_trinket(self: ElementRef, trinket: ContentRef): void {
  list.remove(self, "pack", trinket);
}
  1. 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:

schemas/keeper.json
    {
      "id": "improve",
      "name": "Improve a skill",
      "source": "rules/improve.ts",
      "inputs": {
        "skill": { "label": "Skill", "fields": ["lore", "library_use"] }
      }
    }
  1. Línea 25 La entrada skill elige uno de estos campos por su nombre. Una lista necesita al menos dos.
rules/improve.ts
export function improve(self: ElementRef, skill: string): void {
  ledger.add(self, skill, 1);
}
  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 field

Solo 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.

Siguiente