Rewrite types using typebox
This commit is contained in:
parent
87837754f6
commit
2d4a59f2b3
|
|
@ -1,101 +1,79 @@
|
|||
import { Type, type Static } from '../typebox';
|
||||
|
||||
// ── Shared ────────────────────────────────────────────────────────────────────
|
||||
|
||||
const RPGActionScheme = Type.Object({
|
||||
type: Type.String(),
|
||||
arg: Type.Optional(Type.Union([Type.String(), Type.Number(), Type.Boolean()])),
|
||||
});
|
||||
|
||||
export type RPGCondition = string;
|
||||
export type RPGVariables = Record<string, string | number | boolean | undefined>;
|
||||
|
||||
export interface RPGAction {
|
||||
type: string;
|
||||
arg?: string | number | boolean | null;
|
||||
}
|
||||
|
||||
export type RPGActions = Record<string, (arg?: any) => unknown>;
|
||||
export type RPGAction = Static<typeof RPGActionScheme>;
|
||||
|
||||
export interface DialogChoice {
|
||||
text: string;
|
||||
nextNodeId?: string;
|
||||
}
|
||||
// ── Dialog ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface DialogNode {
|
||||
id: string;
|
||||
speaker: string;
|
||||
text: string;
|
||||
nextNodeId?: string;
|
||||
choices?: DialogChoice[];
|
||||
conditions?: RPGCondition[];
|
||||
actions?: RPGAction[];
|
||||
}
|
||||
const DialogChoiceScheme = Type.Object({
|
||||
text: Type.String(),
|
||||
nextNodeId: Type.Optional(Type.String()),
|
||||
});
|
||||
|
||||
export interface Dialog {
|
||||
nodes: DialogNode[];
|
||||
startNodeId: string;
|
||||
}
|
||||
const DialogNodeScheme = Type.Object({
|
||||
id: Type.String(),
|
||||
speaker: Type.String(),
|
||||
text: Type.String(),
|
||||
nextNodeId: Type.Optional(Type.String()),
|
||||
choices: Type.Optional(Type.Array(DialogChoiceScheme)),
|
||||
conditions: Type.Optional(Type.Array(Type.String())),
|
||||
actions: Type.Optional(Type.Array(RPGActionScheme)),
|
||||
});
|
||||
|
||||
export interface QuestObjective {
|
||||
id: string;
|
||||
description: string;
|
||||
condition: RPGCondition;
|
||||
}
|
||||
const DialogScheme = Type.Object({
|
||||
nodes: Type.Array(DialogNodeScheme),
|
||||
startNodeId: Type.String(),
|
||||
});
|
||||
|
||||
export interface QuestStage {
|
||||
id: string;
|
||||
description: string;
|
||||
objectives: QuestObjective[];
|
||||
actions: RPGAction[];
|
||||
}
|
||||
|
||||
export interface Quest {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
conditions?: RPGCondition[];
|
||||
stages: QuestStage[];
|
||||
}
|
||||
|
||||
function isRPGAction(v: unknown): v is RPGAction {
|
||||
if (typeof v !== 'object' || v === null) return false;
|
||||
return typeof (v as Record<string, unknown>).type === 'string';
|
||||
}
|
||||
|
||||
function isDialogChoice(v: unknown): v is DialogChoice {
|
||||
if (typeof v !== 'object' || v === null) return false;
|
||||
const c = v as Record<string, unknown>;
|
||||
return typeof c.text === 'string'
|
||||
&& (c.nextNodeId === undefined || typeof c.nextNodeId === 'string');
|
||||
}
|
||||
|
||||
function isDialogNode(v: unknown): v is DialogNode {
|
||||
if (typeof v !== 'object' || v === null) return false;
|
||||
const n = v as Record<string, unknown>;
|
||||
return typeof n.id === 'string'
|
||||
&& typeof n.speaker === 'string'
|
||||
&& typeof n.text === 'string'
|
||||
&& (n.nextNodeId === undefined || typeof n.nextNodeId === 'string')
|
||||
&& (n.choices === undefined || (Array.isArray(n.choices) && n.choices.every(isDialogChoice)))
|
||||
&& (n.conditions === undefined || (Array.isArray(n.conditions) && n.conditions.every(c => typeof c === 'string')))
|
||||
&& (n.actions === undefined || (Array.isArray(n.actions) && n.actions.every(isRPGAction)));
|
||||
}
|
||||
export type DialogChoice = Static<typeof DialogChoiceScheme>;
|
||||
export type DialogNode = Static<typeof DialogNodeScheme>;
|
||||
export type Dialog = Static<typeof DialogScheme>;
|
||||
|
||||
export function isDialog(v: unknown): v is Dialog {
|
||||
if (typeof v !== 'object' || v === null) return false;
|
||||
const d = v as Record<string, unknown>;
|
||||
return Array.isArray(d.nodes) && d.nodes.every(isDialogNode)
|
||||
&& typeof d.startNodeId === 'string';
|
||||
return Type.Is(DialogScheme, v);
|
||||
}
|
||||
|
||||
function isQuestObjective(v: unknown): v is QuestObjective {
|
||||
if (typeof v !== 'object' || v === null) return false;
|
||||
const o = v as Record<string, unknown>;
|
||||
return typeof o.id === 'string'
|
||||
&& typeof o.description === 'string'
|
||||
&& typeof o.condition === 'string';
|
||||
// ── Quest ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
const QuestObjectiveScheme = Type.Object({
|
||||
id: Type.String(),
|
||||
description: Type.String(),
|
||||
condition: Type.String(),
|
||||
});
|
||||
|
||||
const QuestStageScheme = Type.Object({
|
||||
id: Type.String(),
|
||||
description: Type.String(),
|
||||
objectives: Type.Array(QuestObjectiveScheme),
|
||||
actions: Type.Array(RPGActionScheme),
|
||||
});
|
||||
|
||||
const QuestScheme = Type.Object({
|
||||
id: Type.String(),
|
||||
title: Type.String(),
|
||||
description: Type.String(),
|
||||
conditions: Type.Optional(Type.Array(Type.String())),
|
||||
stages: Type.Array(QuestStageScheme),
|
||||
});
|
||||
|
||||
export type QuestObjective = Static<typeof QuestObjectiveScheme>;
|
||||
export type QuestStage = Static<typeof QuestStageScheme>;
|
||||
export type Quest = Static<typeof QuestScheme>;
|
||||
|
||||
export function isQuest(v: unknown): v is Quest {
|
||||
return Type.Is(QuestScheme, v);
|
||||
}
|
||||
|
||||
function isQuestStage(v: unknown): v is QuestStage {
|
||||
if (typeof v !== 'object' || v === null) return false;
|
||||
const s = v as Record<string, unknown>;
|
||||
return typeof s.id === 'string'
|
||||
&& typeof s.description === 'string'
|
||||
&& Array.isArray(s.objectives) && s.objectives.every(isQuestObjective)
|
||||
&& Array.isArray(s.actions) && s.actions.every(isRPGAction);
|
||||
}
|
||||
// ── Inventory ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export type SlotId = string | number;
|
||||
|
||||
|
|
@ -109,13 +87,3 @@ export type InventorySlotInput = SlotId | InventorySlotDefinition;
|
|||
export interface InventoryOptions {
|
||||
maxAmountPerItem?: Record<string, number>;
|
||||
}
|
||||
|
||||
export function isQuest(v: unknown): v is Quest {
|
||||
if (typeof v !== 'object' || v === null) return false;
|
||||
const q = v as Record<string, unknown>;
|
||||
return typeof q.id === 'string'
|
||||
&& typeof q.title === 'string'
|
||||
&& typeof q.description === 'string'
|
||||
&& Array.isArray(q.stages) && q.stages.every(isQuestStage)
|
||||
&& (q.conditions === undefined || (Array.isArray(q.conditions) && q.conditions.every(c => typeof c === 'string')));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,14 @@ export namespace Type {
|
|||
return result;
|
||||
}
|
||||
|
||||
export function Union<T extends TScheme[]>(anyOf: [...T], args: { description?: string } = {}): TUnion<T> {
|
||||
const result: TUnion<T> = { type: 'union', anyOf };
|
||||
if (args.description) {
|
||||
result.description = args.description;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function Optional<T extends TScheme = TScheme>(scheme: T): TOptional<T> {
|
||||
const result = { ...scheme };
|
||||
GlobalObject.defineProperty(result, optional, { value: true, enumerable: false, writable: false, configurable: false });
|
||||
|
|
@ -158,6 +166,13 @@ export namespace Type {
|
|||
}
|
||||
return errors;
|
||||
}
|
||||
case 'union': {
|
||||
const s = scheme as TUnion;
|
||||
for (const branch of s.anyOf) {
|
||||
if (check(branch, value, path).length === 0) return [];
|
||||
}
|
||||
return [{ path, message: `Expected union type at ${path}, got ${typeof value}` }];
|
||||
}
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
|
@ -224,17 +239,28 @@ export interface TOptionalObject<T extends TProperties = TProperties> extends TO
|
|||
[optional]: true;
|
||||
}
|
||||
|
||||
export interface TUnion<T extends TScheme[] = TScheme[]> {
|
||||
type: 'union';
|
||||
anyOf: T;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface TOptionalUnion<T extends TScheme[] = TScheme[]> extends TUnion<T> {
|
||||
[optional]: true;
|
||||
}
|
||||
|
||||
export type TOptional<T extends TScheme = TScheme> =
|
||||
T extends TString<infer S> ? TOptionalString<S> :
|
||||
T extends TNumber<infer N> ? TOptionalNumber<N> :
|
||||
T extends TBoolean ? TOptionalBoolean :
|
||||
T extends TArray<infer I> ? TOptionalArray<I> :
|
||||
T extends TObject<infer P> ? TOptionalObject<P> :
|
||||
T extends TUnion<infer U> ? TOptionalUnion<U> :
|
||||
never;
|
||||
|
||||
export type IsOptional<T> = T extends { [optional]: true } ? true : false;
|
||||
|
||||
export type TScheme = TString | TNumber | TBoolean | TArray | TObject | TOptionalString | TOptionalNumber | TOptionalBoolean | TOptionalArray | TOptionalObject;
|
||||
export type TScheme = TString | TNumber | TBoolean | TArray | TObject | TUnion | TOptionalString | TOptionalNumber | TOptionalBoolean | TOptionalArray | TOptionalObject | TOptionalUnion;
|
||||
|
||||
type Prettify<T> = { [K in keyof T]: T[K] } & {};
|
||||
|
||||
|
|
@ -251,10 +277,16 @@ type StaticObject<T extends TProperties> = Prettify<
|
|||
{ [K in OptionalKeys<T>]?: Static<T[K]> }
|
||||
>;
|
||||
|
||||
type StaticUnion<T extends TScheme[]> =
|
||||
T extends [infer First extends TScheme, ...infer Rest extends TScheme[]]
|
||||
? Static<First> | StaticUnion<Rest>
|
||||
: never;
|
||||
|
||||
export type Static<T extends TScheme> =
|
||||
T extends TString<infer S> ? S :
|
||||
T extends TNumber<infer N> ? N :
|
||||
T extends TBoolean ? boolean :
|
||||
T extends TArray<infer I> ? Static<I>[] :
|
||||
T extends TObject<infer P> ? StaticObject<P> :
|
||||
T extends TUnion<infer U> ? StaticUnion<U> :
|
||||
never;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { Stat } from "@common/rpg/components/stat";
|
|||
import { Variables } from "@common/rpg/components/variables";
|
||||
import { QuestManager } from "@common/rpg/quest";
|
||||
|
||||
|
||||
export default async function main() {
|
||||
const game = new RPGEntity();
|
||||
const player = new RPGEntity();
|
||||
|
|
@ -29,7 +28,7 @@ export default async function main() {
|
|||
amount: 1,
|
||||
slotId: 'head',
|
||||
});
|
||||
inventory.addItem({
|
||||
game.getActions()['player.inventory.addItem']({
|
||||
itemId: 'boots',
|
||||
amount: 2,
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue