Frameworki kodowe
Pisanie reguł
Pisz konstruktory, akcje i funkcje ledgera w małym, bezpiecznym podzbiorze TypeScript.
Trzy rodzaje plików reguł
| Rodzaj | Kształt | Co może robić |
|---|---|---|
| Konstruktor | export function construct(...inputs): { fields } | Czyta dane wejściowe i zwraca wartości startowe. Nigdy nie zapisuje. |
| Akcja | export function id(self: ElementRef, ...inputs): void | Zmienia wartości przez ledger.set i ledger.add. |
| Funkcja ledgera | export function id(currentValue: T, self: ElementRef): T | Czyta wartości i zwraca wartość pochodną. Nigdy nie zapisuje. |
Plik konstruktora eksportuje jedną funkcję. Plik akcji lub ledgera może wyeksportować kilka, jeśli każda nazwa eksportu jest identyfikatorem zadeklarowanym dla tego pliku: identyfikatorem akcji lub funkcji ledgera. Nazwy zapisanych pól używają małych liter, cyfr i podkreśleń.
Funkcja ledgera może też zwracać tekst. Zadeklaruj currentValue i wynik jako string, a we wpisie ledgera scenariusza podaj tekstowe currentValue.
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";
}- Wiersz 1 Tekst na wejściu, tekst na wyjściu.
export function hurt(self: ElementRef, amount: number): void {
ledger.add(self, "hp", 0 - amount);
}- Wiersz 1 Element jest pierwszy jako self, potem własne dane wejściowe akcji. Akcja zwraca void.
Plik reguł może też zawierać funkcje pomocnicze. Pisz je bez export i wywołuj po nazwie z tego samego pliku. Plik nadal eksportuje dokładnie jedną funkcję albo tylko akcje lub funkcje ledgera, które deklaruje.
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);
}- Wiersze 1-3 Funkcja pomocnicza: bez export, wywoływana po nazwie z tego samego pliku.
- Wiersz 7 Wywołanie funkcji pomocniczej.
Plik reguł nie może importować kodu. Plik akcji eksportuje jedną funkcję, chyba że współdzieli go kilka akcji. Dodatkowy eksport sam kończy się błędem:
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 itAby współdzielić funkcję pomocniczą między akcjami, niech te akcje współdzielą jeden plik. Nazwij każdy eksport nazwą akcji, którą uruchamia, zadeklaruj każdy eksport jako identyfikator akcji i zostaw funkcję pomocniczą bez export. Akcje mogą należeć do różnych Frameworków, a jedna akcja nie może wywołać innej. Każda funkcja niesie kopię całego pliku w skompilowanej grze, więc trzymaj pliki współdzielone krótkie.
Funkcja pomocnicza może tylko obliczać i zwracać wartość. Nie może wywoływać ledger.set ani ledger.add. Funkcja pomocnicza, która zapisuje, jest odrzucana, nawet w pliku współdzielonym przez akcje:
function wound(self: ElementRef): void {
ledger.add(self, "hp", -1);
}effectful action helpers are not supported until call-target provenance is enforcedNiech funkcja pomocnicza zwraca wartość, a każda akcja niech sama zapisuje. Te dwie akcje współdzielą jedną czystą funkcję pomocniczą:
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));
}- Wiersze 1-3 Czysta funkcja pomocnicza: bez export, tylko zwraca wartość.
- Wiersz 8 Akcja zapisuje wartość zwróconą przez funkcję pomocniczą.
- Wiersz 14 Druga akcja używa tej samej funkcji pomocniczej.
Konstruktor, który wypełnia miejsca podrzędne, zwraca { values, children } zamiast zwykłego rekordu. Szybki start pokazuje zwykły rekord, a strona o schematach frameworków pokazuje formę z elementami podrzędnymi.
Podzbiór TypeScript
Reguły działają wszędzie tak samo i zawsze się kończą. Dlatego język jest mały.
| Dozwolone | Niedozwolone |
|---|---|
| Jawne typy, const i let | import, klasy, funkcje strzałkowe, new, this |
| if i else, for of, return | pętle while, for (;;), switch, break, continue, kod async, wyjątki |
| Literały liczb, tekstów, wartości logicznych, rekordów i tablic | Szablony tekstowe, spread, optional chaining, ??, komentarze blokowe (użyj //) |
| Arytmetyka, ścisłe porównania (=== i !==), operatory logiczne, operatory trójargumentowe | == i !=, **, ++ i +=, typeof, zmiana obiektów w miejscu, domyślne wartości parametrów |
| Wywołania funkcji hosta, funkcji pomocniczych z tego samego pliku i rzutowania as ich wyników | Wywołania metod, np. "a".toUpperCase(), Math, Date, JSON, fetch i inne globalne obiekty |
Kończ każdą instrukcję średnikiem. Każdy z tych błędów zostanie wychwycony przy sprawdzaniu projektu.
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 allowedFunkcja z typem zwracanym musi zwracać wartość na każdej ścieżce.
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 pathWięcej błędów wychwytywanych przez sprawdzanie
Każdy wiersz cytuje fragment komunikatu i mówi, co napisać zamiast tego.
| Komunikat | Co napisać zamiast tego |
|---|---|
| block comments are not allowed | Użyj komentarzy liniowych //. |
| switch is not allowed | Użyj if i else. |
| break is not allowed | Zmień pętlę albo opakuj resztę jej treści w if. |
| continue is not allowed | Opakuj resztę treści pętli w if. |
| classic for loops are not allowed | Użyj for (const item of list). |
| loose equality is not allowed | Użyj ===. |
| loose inequality is not allowed | Użyj !==. |
| exponentiation (**) is not allowed | Użyj math.pow. |
| increment (++) is not allowed | Napisz count = count + 1. |
| compound assignment is not allowed | Napisz count = count + value. |
| typeof is not allowed | Porównaj przez === z literałem. |
| class is not allowed | Użyj zwykłych funkcji. |
| arrow functions are not allowed | Napisz funkcję pomocniczą na początku pliku. |
| async is not allowed | Usuń async. Reguły działają w jednym kroku. |
| template literals are not allowed | Użyj literałów tekstowych. |
| spread/rest (...) is not allowed | Wypisz elementy po kolei. |
| optional chaining (?.) is not allowed | Najpierw porównaj z null. |
| nullish coalescing (??) is not allowed | Użyj operatora trójargumentowego, który porównuje z null. |
| throw is not allowed | Wyjdź z reguły bez zapisywania. |
| try/catch is not allowed | Usuń try i catch. |
| new is not allowed | Napisz zamiast tego literały. |
| this is not allowed | Użyj parametru self. |
| method calls are not allowed | Wywołuj tylko funkcje hosta i funkcje pomocnicze z tego samego pliku. |
| default parameter values (=) are not allowed | Podaj wartość każdego parametru tam, gdzie reguła jest używana. |
| assignment targets must be local variables | Zbuduj nową wartość zamiast zmieniać obiekt. |
| functions but only the action | Usuń export z funkcji pomocniczych. |
| constructors must be pure and cannot read or write host state | Czytaj i zapisuj w akcjach i funkcjach ledgera. |
| is not available until | Funkcja ledgera nie może zapisywać. Użyj akcji. |
| effectful action helpers are not supported | Niech funkcja pomocnicza zwraca wartość, a zapisz ją w akcji. |
Wywołania hosta
Reguły łączą się z Fabled przez wywołania hosta. Piszesz je tak, jak są, na przykład ledger.add(...). Nigdy ich nie importujesz.
W tych sygnaturach HostValue to dowolna wartość, którą może zwrócić wywołanie hosta, więc zrzutuj ją przez as, zanim jej użyjesz. AbiValue to liczba, tekst, wartość logiczna lub null albo lista lub rekord takich wartości. Opcjonalny parametr sticky jest do użytku samego Fabled, więc go pomiń.
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;| Wywołanie | Gdzie | Co robi |
|---|---|---|
| element.var | Akcje, funkcje ledgera | Czyta zapisaną wartość elementu. Zrzutuj wynik przez as number, as string lub as boolean. |
| ledger.set | Akcje | Ustawia zapisane pole na wartość. |
| ledger.add | Akcje | Dodaje liczbę do zapisanego pola. Dodaj liczbę ujemną, aby odjąć. |
| math.floor | Wszędzie | Zaokrągla w dół. |
| math.ceil | Wszędzie | Zaokrągla w górę. |
| math.round | Wszędzie | Zaokrągla do najbliższej liczby całkowitej. |
| math.max | Wszędzie | Zwraca największą z podanych liczb. |
| math.min | Wszędzie | Zwraca najmniejszą z podanych liczb. |
| math.abs | Wszędzie | Zwraca wartość liczby bez znaku. |
| math.pow | Wszędzie | Podnosi podstawę do potęgi. |
| math.sqrt | Wszędzie | Zwraca pierwiastek kwadratowy. |
| list.add | Akcje | Dodaje wybrany wpis do listy. Oferta decyduje, co można dodać. Odmowa z NOT_OFFERED, ALREADY_IN_LIST lub LIST_FULL. |
| list.remove | Akcje | Usuwa wybrany wpis z listy. Odmowa z NOT_IN_LIST. |
| list.count | Akcje | Zwraca, ile wpisów zawiera lista. Użyj tego w warunku przed list.add. |
| offer.where | Akcje | Buduje ofertę dla list.add: wpisy, których pola spełniają warunki zapisane jako literały. |
Wywołanie listy podaje listę frameworka przez klucz zapisany wprost, a wpis to ContentRef, czyli dane wejściowe, które przechowują wpis wybrany przez gracza. Oferta przyjmuje tylko warunki zapisane jako literały, na przykład offer.where({ weight: { lte: 3 } }). Każde pole przyjmuje eq, ne, gte, lte, in lub has. Oferta nie może czytać elementu. Aby zależeć od wartości postaci, sprawdź ją przez if przed 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 } }));
}
}- Wiersz 2 Zatrzymaj się, gdy plecak ma już dwa wpisy.
- Wiersz 3 Oferta dopuszcza tylko wpisy o wadze 3 lub mniejszej.
export function unpack_trinket(self: ElementRef, trinket: ContentRef): void {
list.remove(self, "pack", trinket);
}- Wiersz 2 Usuń wybrany wpis. Akcja listy, która tylko usuwa, potrzebuje jednego kroku przyjętego.
Nazwy pól są zapisane wprost
W akcji nazwa pola musi być tekstem zapisanym w kodzie, na przykład "hp". Nie może pochodzić z wartości. Taka reguła kończy się błędem:
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.Bez listy pól jedna akcja nie może więc sprawdzać dowolnej umiejętności po nazwie. Napisz po jednej akcji dla każdej umiejętności i niech każda czyta swoje pole. Przepis Rzut przeciw umiejętności pokazuje krótką akcję do skopiowania. Wyjątkiem jest wejście, które wylicza swoje pola. Nadaj wejściu akcji listę "fields", na przykład ["lore", "library_use"], a akcja może przekazać to wejście jako nazwę, jak w ledger.add(self, skill, amount). Sprawdzanie traktuje akcję tak, jakby dotykała każdego pola z listy, a serwer przyjmuje zapis tylko do pola wybranego przez gracza.
Ta akcja podnosi umiejętność wybraną przez gracza:
{
"id": "improve",
"name": "Improve a skill",
"source": "rules/improve.ts",
"inputs": {
"skill": { "label": "Skill", "fields": ["lore", "library_use"] }
}
}- Wiersz 25 Dane wejściowe skill wybierają jedno z tych pól po nazwie. Lista wymaga co najmniej dwóch.
export function improve(self: ElementRef, skill: string): void {
ledger.add(self, skill, 1);
}- Wiersz 2 skill zawiera nazwę pola wybranego przez gracza.
Każda nazwa w fields musi wskazywać zapisane pole Frameworka, które akcja może zmieniać. Pole wyliczane przez funkcję rejestru lub modyfikator jest odrzucane. Jeśli pole ma wejście schematu o tej samej nazwie z label, okno pokazuje tę etykietę; w przeciwnym razie pokazuje identyfikator, na przykład library_use. Na przykład inputs.library_use.label może nazwać opcję Korzystanie z biblioteki. Wybrana wartość, parametry scenariuszy i symbole zastępcze w dzienniku, takie jak {skill}, nadal używają identyfikatora pola. Każda natywna lista opcji tekstowych, której wszystkie wartości są nazwami zapisywalnych pól skalarnych, używa tych samych etykiet.
fields "skill_total" is not a writable state fieldTylko zadeklarowane pola
Akcja może zapisywać tylko pola zwracane przez konstruktor. Sprawdzenie podaje nazwę pola, którego nie może znaleźć.
Action "keeper.heal" calls ledger.set(self, "mana", ...), and "mana" is not declared on this schema. Declared scalar fields: hp, max_hp, name.
