コードフレームワーク
ルールの書き方
コンストラクタ、アクション、レジャー関数を、小さく安全な TypeScript のサブセットで書きます。
3 種類のルールファイル
| 種類 | 形 | できること |
|---|---|---|
| コンストラクタ | export function construct(...inputs): { fields } | 入力を読み取り、初期値を返します。書き込みはしません。 |
| アクション | export function id(self: ElementRef, ...inputs): void | ledger.set と ledger.add で値を変更します。 |
| レジャー関数 | export function id(currentValue: T, self: ElementRef): T | 値を読み取り、派生した値を返します。書き込みはしません。 |
コンストラクタのファイルは関数を 1 つエクスポートします。アクションまたはレジャーのファイルは、各エクスポート名がそのファイルに宣言された ID(アクション ID またはレジャー関数 ID)であれば、複数をエクスポートできます。保存されるフィールド名には、小文字、数字、アンダースコアを使います。
レジャー関数はテキストも返せます。currentValue と結果を string にし、シナリオのレジャーエントリには文字列の 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";
}- 1 行目 テキストを受け取り、テキストを返します。
export function hurt(self: ElementRef, amount: number): void {
ledger.add(self, "hp", 0 - amount);
}- 1 行目 最初にエレメントが self として渡され、次にアクション自身の入力が続きます。アクションは void を返します。
ルールファイルには補助関数も置けます。export を付けずに書き、同じファイル内で名前を呼び出します。ファイルがエクスポートするのは、引き続き関数 1 つだけ、または宣言したアクションやレジャー関数だけです。
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-3 行目 ヘルパーです。export はなく、同じファイル内から名前で呼び出されます。
- 7 行目 ヘルパーの呼び出しです。
ルールファイルはコードをインポートできません。アクションファイルは、複数のアクションが共有する場合を除き、関数を 1 つだけエクスポートします。余分なエクスポートが単独である場合は、次のエラーになります。
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補助関数をアクション間で共有するには、それらのアクションで 1 つのファイルを共有します。各エクスポートには実行するアクション名を付け、すべてのエクスポートをアクション ID として宣言し、補助関数には export を付けません。アクションは別のフレームワークに属していてもかまいませんが、アクションから別のアクションは呼び出せません。各関数はコンパイル後のゲームでファイル全体のコピーを持つため、共有ファイルは短く保ってください。
補助関数は、計算して値を返すことしかできません。ledger.set や ledger.add は呼び出せません。書き込む補助関数は、アクションが共有するファイルの中でも拒否されます。
function wound(self: ElementRef): void {
ledger.add(self, "hp", -1);
}effectful action helpers are not supported until call-target provenance is enforced補助関数には値を返させ、書き込みは各アクションが行います。次の 2 つのアクションは、1 つの純粋な補助関数を共有しています。
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-3 行目 純粋な補助関数です。export はなく、値を返すだけです。
- 8 行目 アクションが、補助関数の返した値を書き込みます。
- 14 行目 2 つ目のアクションも同じ補助関数を使います。
子スロットを埋めるコンストラクタは、単純なレコードではなく { values, children } を返します。クイックスタートでは単純なレコードを示し、フレームワークスキーマのページで子の形を示します。
TypeScript のサブセット
ルールはどこでも同じように実行され、必ず終了します。そのため、言語は小さくなっています。
| 使えるもの | 使えないもの |
|---|---|
| 明示的な型、const と let | import、class、アロー関数、new、this |
| if と else、for of、return | while ループ、for (;;)、switch、break、continue、async のコード、例外 |
| 数値、文字列、真偽値、レコード、配列のリテラル | テンプレート文字列、スプレッド、オプショナルチェーン、??、ブロックコメント(// を使ってください) |
| 算術、厳密比較(=== と !==)、論理演算子、三項演算子 | == と !=、**、++ と +=、typeof、オブジェクトの直接変更、パラメータのデフォルト値 |
| ホスト関数の呼び出し、同じファイルのヘルパー関数の呼び出し、それらが返す値への as キャスト | "a".toUpperCase() のようなメソッド呼び出し、Math、Date、JSON、fetch などのグローバル |
すべての文はセミコロンで終えてください。次のような間違いは、プロジェクトを検証するときに検出されます。
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 allowed戻り値の型を持つ関数は、すべての経路で値を返す必要があります。
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チェックで見つかるその他の間違い
各行は、メッセージの一部を引用し、代わりに何を書くべきかを示します。
| メッセージ | 代わりに書くもの |
|---|---|
| block comments are not allowed | // の行コメントを使ってください。 |
| switch is not allowed | if と else を使ってください。 |
| break is not allowed | ループを書き換えるか、本体の残りを if で囲んでください。 |
| continue is not allowed | ループ本体の残りを if で囲んでください。 |
| classic for loops are not allowed | for (const item of list) を使ってください。 |
| loose equality is not allowed | === を使ってください。 |
| loose inequality is not allowed | !== を使ってください。 |
| exponentiation (**) is not allowed | math.pow を使ってください。 |
| increment (++) is not allowed | count = count + 1 と書いてください。 |
| compound assignment is not allowed | count = count + value と書いてください。 |
| typeof is not allowed | リテラルと === で比較してください。 |
| class is not allowed | 単純な関数を使ってください。 |
| arrow functions are not allowed | ファイルの先頭にヘルパー関数を書いてください。 |
| async is not allowed | async を削除してください。ルールは 1 ステップで実行されます。 |
| template literals are not allowed | 文字列リテラルを使ってください。 |
| spread/rest (...) is not allowed | 項目を 1 つずつ書き出してください。 |
| optional chaining (?.) is not allowed | 先に null と比較してください。 |
| nullish coalescing (??) is not allowed | null と比較する三項演算子を使ってください。 |
| throw is not allowed | 何も書き込まずにルールを終了してください。 |
| try/catch is not allowed | try と catch を削除してください。 |
| new is not allowed | 代わりにリテラルを書いてください。 |
| this is not allowed | self パラメータを使ってください。 |
| method calls are not allowed | 呼び出せるのは、ホスト関数と同じファイルのヘルパーだけです。 |
| default parameter values (=) are not allowed | ルールを使う場所で、すべてのパラメータに値を渡してください。 |
| assignment targets must be local variables | オブジェクトを変更せず、新しい値を作ってください。 |
| functions but only the action | ヘルパー関数から export を削除してください。 |
| constructors must be pure and cannot read or write host state | 読み書きは、アクションとレジャー関数で行ってください。 |
| is not available until | レジャー関数は書き込めません。アクションを使ってください。 |
| effectful action helpers are not supported | 補助関数に値を返させ、書き込みはアクションで行います。 |
ホスト呼び出し
ルールは、ホスト呼び出しを通じて Fabled にアクセスします。ledger.add(...) のようにそのまま書きます。インポートはしません。
これらのシグネチャで、HostValue はホスト呼び出しが返しうる任意の値です。使う前に as でキャストしてください。AbiValue は、数値、テキスト、真偽値、null、またはそれらのリストやレコードです。省略可能な sticky パラメータは Fabled の内部用なので、指定しないでください。
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;| 呼び出し | 使える場所 | 動作 |
|---|---|---|
| element.var | アクション、レジャー関数 | エレメントの保存値を読み取ります。結果は as number、as string、as boolean でキャストします。 |
| ledger.set | アクション | 保存フィールドに値を設定します。 |
| ledger.add | アクション | 保存フィールドに数値を加えます。マイナスの数を加えると減ります。 |
| math.floor | どこでも | 切り捨てます。 |
| math.ceil | どこでも | 切り上げます。 |
| math.round | どこでも | 最も近い整数に丸めます。 |
| math.max | どこでも | 渡された数値のうち最大のものを返します。 |
| math.min | どこでも | 渡された数値のうち最小のものを返します。 |
| math.abs | どこでも | 符号を除いた数値の大きさを返します。 |
| math.pow | どこでも | 底を指数で累乗します。 |
| math.sqrt | どこでも | 平方根を返します。 |
| list.add | アクション | 選ばれたエントリをリストに追加します。追加できるものはオファーが決めます。NOT_OFFERED、ALREADY_IN_LIST、LIST_FULL で拒否されます。 |
| list.remove | アクション | 選ばれたエントリをリストから削除します。NOT_IN_LIST で拒否されます。 |
| list.count | アクション | リストが持つエントリ数を返します。list.add の前のガードで使います。 |
| offer.where | アクション | list.add 用のオファーを作ります。リテラルで書いた条件にフィールドが合うエントリが対象です。 |
リスト呼び出しは、フレームワークのリストをリテラルのキーで指定します。エントリは ContentRef で、プレイヤーが選ぶエントリを保持する入力です。オファーが取れるのは、offer.where({ weight: { lte: 3 } }) のようなリテラルの条件だけです。各フィールドには eq、ne、gte、lte、in、has を使えます。オファーはエレメントを読めません。キャラクターの値に応じて変えたいときは、list.add の前に if で確認します。
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 } }));
}
}- 2 行目 パックがすでに 2 つのエントリを持っているなら、止めます。
- 3 行目 オファーが許可するのは、weight が 3 以下のエントリだけです。
export function unpack_trinket(self: ElementRef, trinket: ContentRef): void {
list.remove(self, "pack", trinket);
}- 2 行目 選ばれたエントリを削除します。削除だけを行うリストアクションには、受け入れられるステップが 1 つ必要です。
フィールド名はそのまま書きます
アクションでは、フィールドの名前を、コードに書かれた "hp" のような文字列にする必要があります。値から作ることはできません。次のようなルールは失敗します。
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.そのため、フィールドの一覧がない限り、1 つのアクションで任意のスキルを名前から選んで判定することはできません。スキルごとにアクションを 1 つ書き、それぞれが自分のフィールドを読み取ります。「スキルに対してロールする」レシピに、コピーできる短いアクションがあります。 例外は、フィールドを一覧にした入力です。アクションの入力に ["lore", "library_use"] のような "fields" の一覧を指定すると、ledger.add(self, skill, amount) のように、その入力を名前として渡せます。チェックはアクションを一覧のすべてのフィールドに触れるものとして扱い、サーバーはプレイヤーが選んだフィールドへの書き込みだけを受け付けます。
このアクションは、プレイヤーが選んだ技能を上げます。
{
"id": "improve",
"name": "Improve a skill",
"source": "rules/improve.ts",
"inputs": {
"skill": { "label": "Skill", "fields": ["lore", "library_use"] }
}
}- 25 行目 skill という入力は、これらのフィールドのうち 1 つを名前で選びます。一覧には 2 つ以上が必要です。
export function improve(self: ElementRef, skill: string): void {
ledger.add(self, skill, 1);
}- 2 行目 skill には、プレイヤーが選んだフィールドの名前が入ります。
fields の各名前は、アクションが書き込める、そのフレームワークの保存フィールドでなければなりません。レジャー関数が導出するフィールドや修正値は拒否されます。フィールドと同名のスキーマ入力に label がある場合、ダイアログはそのラベルを表示します。ない場合は library_use のようなフィールド ID を表示します。たとえば inputs.library_use.label で選択肢を「図書館利用」と表示できます。選択した値、シナリオのパラメーター、{skill} のようなログのプレースホルダーには、引き続きフィールド ID を使います。 ネイティブゲームの文字列の選択肢がすべて書き込み可能なスカラーフィールドの名前である場合も、同じラベルを使います。
fields "skill_total" is not a writable state field宣言済みのフィールドのみ
アクションが書き込めるのは、コンストラクタが返すフィールドだけです。検証では、見つからないフィールドの名前が示されます。
Action "keeper.heal" calls ledger.set(self, "mana", ...), and "mana" is not declared on this schema. Declared scalar fields: hp, max_hp, name.
