Move playbill to its own repo
This commit is contained in:
parent
1c64a941cd
commit
879e508d95
8 changed files with 20 additions and 490 deletions
|
@ -7,10 +7,7 @@
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"ignoreUnknown": false,
|
"ignoreUnknown": false,
|
||||||
"ignore": [
|
"ignore": ["*.d.ts", "*.json"]
|
||||||
"*.d.ts",
|
|
||||||
"*.json"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|
13
bun.lock
13
bun.lock
|
@ -2,8 +2,9 @@
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "lang",
|
"name": "@proscenium/muse",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@proscenium/playbill": "0.0.1",
|
||||||
"chalk": "^5.4.1",
|
"chalk": "^5.4.1",
|
||||||
"yaml": "^2.7.0",
|
"yaml": "^2.7.0",
|
||||||
"zod": "^3.24.1",
|
"zod": "^3.24.1",
|
||||||
|
@ -36,13 +37,15 @@
|
||||||
|
|
||||||
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="],
|
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.2.2", "", { "dependencies": { "bun-types": "1.2.2" } }, "sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w=="],
|
"@proscenium/playbill": ["@proscenium/playbill@0.0.1", "https://git.astral.camp/api/packages/proscenium/npm/%40proscenium%2Fplaybill/-/0.0.1/playbill-0.0.1.tgz", { "peerDependencies": { "typescript": "^5" } }, "sha512-uvv2Bt1+rjzuXl5y2t2C4KGgVda3orTZMVzb6gNAzStzKutiR3xJZkw2ULukYWWGyZ9BzzkRClSKVYka2Z3Png=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="],
|
"@types/bun": ["@types/bun@1.2.3", "", { "dependencies": { "bun-types": "1.2.3" } }, "sha512-054h79ipETRfjtsCW9qJK8Ipof67Pw9bodFWmkfkaUaRiIQ1dIV2VTlheshlBx3mpKr0KeK8VqnMMCtgN9rQtw=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@22.13.5", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg=="],
|
||||||
|
|
||||||
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
|
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="],
|
"bun-types": ["bun-types@1.2.3", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-P7AeyTseLKAvgaZqQrvp3RqFM3yN9PlcLuSTe7SoJOfZkER73mLdT2vEQi8U64S1YvM/ldcNiQjn0Sn7H9lGgg=="],
|
||||||
|
|
||||||
"chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
"chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
||||||
|
|
||||||
|
@ -52,6 +55,6 @@
|
||||||
|
|
||||||
"yaml": ["yaml@2.7.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA=="],
|
"yaml": ["yaml@2.7.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA=="],
|
||||||
|
|
||||||
"zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="],
|
"zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.4.1",
|
"chalk": "^5.4.1",
|
||||||
"yaml": "^2.7.0",
|
"yaml": "^2.7.0",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1",
|
||||||
|
"@proscenium/playbill": "0.0.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"fmt": "bunx --bun biome check --fix",
|
"fmt": "bunx --bun biome check --fix",
|
||||||
|
|
11
src/index.ts
11
src/index.ts
|
@ -1,12 +1,13 @@
|
||||||
import { parseArgs } from "node:util";
|
import { parseArgs } from "node:util";
|
||||||
import chalk from "chalk";
|
|
||||||
import { loadBindingFile } from "./binding";
|
|
||||||
import { DirectiveError, processLang } from "./lang";
|
|
||||||
import {
|
import {
|
||||||
type AnyResource,
|
type AnyResource,
|
||||||
|
DirectiveError,
|
||||||
ValidatedPlaybillSchema,
|
ValidatedPlaybillSchema,
|
||||||
|
compileScript,
|
||||||
getEmptyPlaybill,
|
getEmptyPlaybill,
|
||||||
} from "./playbill-schema";
|
} from "@proscenium/playbill";
|
||||||
|
import chalk from "chalk";
|
||||||
|
import { loadBindingFile } from "./binding";
|
||||||
import { loadResourceFile } from "./resource";
|
import { loadResourceFile } from "./resource";
|
||||||
import { usage } from "./usage";
|
import { usage } from "./usage";
|
||||||
|
|
||||||
|
@ -148,7 +149,7 @@ async function main(): Promise<boolean> {
|
||||||
verboseLog("Processing descriptions");
|
verboseLog("Processing descriptions");
|
||||||
for (const resource of loadedResources) {
|
for (const resource of loadedResources) {
|
||||||
try {
|
try {
|
||||||
resource.description = await processLang(resource.description, playbill);
|
resource.description = compileScript(resource.description, playbill);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof DirectiveError) {
|
if (error instanceof DirectiveError) {
|
||||||
console.error(
|
console.error(
|
||||||
|
|
107
src/lang.ts
107
src/lang.ts
|
@ -1,107 +0,0 @@
|
||||||
import { z } from "zod";
|
|
||||||
import { StatsSchema, type Playbill } from "./playbill-schema";
|
|
||||||
|
|
||||||
const DirectiveSchema = z.enum(["dmg", "ability", "stat", "rule"] as const);
|
|
||||||
type DirectiveName = z.infer<typeof DirectiveSchema>;
|
|
||||||
type DirectiveHandler = (playbill: Playbill, ...args: string[]) => string;
|
|
||||||
|
|
||||||
export class DirectiveError extends Error { }
|
|
||||||
|
|
||||||
const ProsceniumLangDirectives: Record<DirectiveName, DirectiveHandler> = {
|
|
||||||
dmg: (_, amount = "1", type = "phy") => {
|
|
||||||
const DAMAGE_TYPES = ["phy", "arc", "physical", "arcane"];
|
|
||||||
if (!DAMAGE_TYPES.includes(type)) {
|
|
||||||
throw new DirectiveError(`Unknown damage type: ${type}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalizedType = type.slice(0, 3);
|
|
||||||
return `<em class='dmg ${finalizedType}'>${amount}</em>`;
|
|
||||||
},
|
|
||||||
|
|
||||||
ability: (playbill: Playbill, ref: string) => {
|
|
||||||
if (typeof ref !== "string") {
|
|
||||||
throw new DirectiveError("Invalid ability reference");
|
|
||||||
}
|
|
||||||
|
|
||||||
const foundAbility = playbill.abilities.find((a) => a.id === ref);
|
|
||||||
|
|
||||||
if (foundAbility === undefined) {
|
|
||||||
throw new DirectiveError(`Unknown ability reference: ${ref}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return `<em class="ref-ability">${foundAbility.name}</em>`;
|
|
||||||
},
|
|
||||||
|
|
||||||
rule: (playbill: Playbill, ref: string) => {
|
|
||||||
if (typeof ref !== "string") {
|
|
||||||
throw new DirectiveError("Invalid rule reference");
|
|
||||||
}
|
|
||||||
|
|
||||||
const foundRule = playbill.rules.find((r) => r.id === ref);
|
|
||||||
|
|
||||||
if (foundRule === undefined) {
|
|
||||||
throw new DirectiveError(`Unknown ability reference: ${ref}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return `<em class="ref-rule">${foundRule.name}</em>`;
|
|
||||||
},
|
|
||||||
stat: (_, stat: string, amount = "") => {
|
|
||||||
const lower = stat.toLowerCase();
|
|
||||||
const parsed = StatsSchema.safeParse(lower);
|
|
||||||
|
|
||||||
if (!parsed.success) {
|
|
||||||
throw new DirectiveError(`Unknown stat: ${stat}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amount !== "") {
|
|
||||||
const amountAsNum = Number.parseInt(amount, 10);
|
|
||||||
if (Number.isNaN(amountAsNum)) {
|
|
||||||
throw new DirectiveError(`Invalid amount: ${amount}`);
|
|
||||||
}
|
|
||||||
return `<em class="stat">${amount} ${stat.toUpperCase()}</em>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `<em class="stat">${stat.toUpperCase()}</em>`;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function processCommands(text: string, playbill: Playbill): string {
|
|
||||||
const commandPattern = /\{(\w+):([^}]+)\}/g;
|
|
||||||
|
|
||||||
return text.replace(commandPattern, (_match, command, args) => {
|
|
||||||
const directive = DirectiveSchema.parse(command);
|
|
||||||
const handler = ProsceniumLangDirectives[directive];
|
|
||||||
const parsedArgs = [];
|
|
||||||
let currentArg = "";
|
|
||||||
let inQuotes = false;
|
|
||||||
|
|
||||||
for (let i = 0; i < args.length; i++) {
|
|
||||||
const char = args[i];
|
|
||||||
if (char === '"' && (i === 0 || args[i - 1] !== "\\")) {
|
|
||||||
inQuotes = !inQuotes;
|
|
||||||
} else if (char === "," && !inQuotes) {
|
|
||||||
parsedArgs.push(currentArg.trim().replace(/^"|"$/g, ""));
|
|
||||||
currentArg = "";
|
|
||||||
} else {
|
|
||||||
currentArg += char;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parsedArgs.push(currentArg.trim().replace(/^"|"$/g, ""));
|
|
||||||
|
|
||||||
return handler(playbill, ...parsedArgs);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function processLang(
|
|
||||||
input: string,
|
|
||||||
playbill: Playbill,
|
|
||||||
): Promise<string> {
|
|
||||||
const lines = input.split("\n");
|
|
||||||
const processedLines: string[] = [];
|
|
||||||
for (const line of lines) {
|
|
||||||
const processed = processCommands(line, playbill);
|
|
||||||
processedLines.push(processed);
|
|
||||||
}
|
|
||||||
|
|
||||||
return processedLines.join("\n");
|
|
||||||
}
|
|
|
@ -1,365 +0,0 @@
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* TYPE REFERENCES
|
|
||||||
* ------------------------
|
|
||||||
* Ability N/A
|
|
||||||
* Tag N/A
|
|
||||||
* Lore N/A
|
|
||||||
* Rule N/A
|
|
||||||
* Method Ability
|
|
||||||
* Item Tag
|
|
||||||
* Species Ability
|
|
||||||
* Entity Ability, Species
|
|
||||||
*/
|
|
||||||
|
|
||||||
// String shapes
|
|
||||||
export const IDSchema = z.string().regex(/[a-z\-]+/);
|
|
||||||
export const NameSchema = z.string().max(40).default("Unnamed");
|
|
||||||
export const DescriptionSchema = z.string().default("No description");
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
export const STAT_ABBREVIATIONS = ["ap", "ep", "hp", "xp"] as const;
|
|
||||||
export const ITEM_TYPES = ["trinket", "worn", "wielded"] as const;
|
|
||||||
export const ROLL_TYPES = ["none", "attack", "fate"] as const;
|
|
||||||
export const TALENTS = [
|
|
||||||
"muscle",
|
|
||||||
"knowledge",
|
|
||||||
"focus",
|
|
||||||
"charm",
|
|
||||||
"cunning",
|
|
||||||
"spark",
|
|
||||||
] as const;
|
|
||||||
export const ABILITY_TYPES = ["action", "cue", "trait"] as const;
|
|
||||||
export const TALENT_PROWESS = ["novice", "adept", "master"] as const;
|
|
||||||
|
|
||||||
// Enums
|
|
||||||
export const StatsSchema = z.enum(STAT_ABBREVIATIONS);
|
|
||||||
export const ItemTypeSchema = z.enum(ITEM_TYPES);
|
|
||||||
export const RollTypeSchema = z.enum(ROLL_TYPES);
|
|
||||||
export const AbilityTypeSchema = z.enum(ABILITY_TYPES);
|
|
||||||
export const TalentSchema = z.enum(TALENTS);
|
|
||||||
export const TalentProwessSchema = z.enum(TALENT_PROWESS);
|
|
||||||
|
|
||||||
const ResourceTypes = [
|
|
||||||
"ability",
|
|
||||||
"species",
|
|
||||||
"entity",
|
|
||||||
"item",
|
|
||||||
"tag",
|
|
||||||
"lore",
|
|
||||||
"method",
|
|
||||||
"rule",
|
|
||||||
"playbill",
|
|
||||||
] as const;
|
|
||||||
const ResourceTypeSchema = z.enum(ResourceTypes);
|
|
||||||
type ResourceType = z.infer<typeof ResourceTypeSchema>;
|
|
||||||
|
|
||||||
// Inner utility shapes
|
|
||||||
export const StatSpreadSchema = z.object({
|
|
||||||
ap: z.number().default(0),
|
|
||||||
ep: z.number().default(0),
|
|
||||||
hp: z.number().default(0),
|
|
||||||
xp: z.number().default(0),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const TalentSpreadSchema = z.object({
|
|
||||||
muscle: TalentProwessSchema.default("novice"),
|
|
||||||
knowledge: TalentProwessSchema.default("novice"),
|
|
||||||
focus: TalentProwessSchema.default("novice"),
|
|
||||||
charm: TalentProwessSchema.default("novice"),
|
|
||||||
cunning: TalentProwessSchema.default("novice"),
|
|
||||||
spark: TalentProwessSchema.default("novice"),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const PlaybillResourceSchema = z.object({
|
|
||||||
$define: ResourceTypeSchema,
|
|
||||||
id: IDSchema,
|
|
||||||
name: NameSchema,
|
|
||||||
description: DescriptionSchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
// An object with optional properties to define passive effects
|
|
||||||
// offered from an ability. These are aggregated by the game
|
|
||||||
// engine at runtime to determine the final stats of a character.
|
|
||||||
export const AbilityEffectSchema = z.object({
|
|
||||||
bonusMaxHp: z.number().optional(),
|
|
||||||
bonusMaxAp: z.number().optional(),
|
|
||||||
bonusMaxEp: z.number().optional(),
|
|
||||||
muscleProwess: TalentProwessSchema.optional(),
|
|
||||||
knowledgeProwess: TalentProwessSchema.optional(),
|
|
||||||
focusProwess: TalentProwessSchema.optional(),
|
|
||||||
charmProwess: TalentProwessSchema.optional(),
|
|
||||||
cunningProwess: TalentProwessSchema.optional(),
|
|
||||||
sparkProwess: TalentProwessSchema.optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const BaggageSchema = z.object({
|
|
||||||
title: z.string(),
|
|
||||||
description: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Playbill component shapes
|
|
||||||
export const AbilitySchema = PlaybillResourceSchema.extend({
|
|
||||||
$define: z.literal("ability"),
|
|
||||||
name: NameSchema.default("Unnamed Ability"),
|
|
||||||
type: AbilityTypeSchema,
|
|
||||||
costs: StatSpreadSchema.default({}),
|
|
||||||
roll: RollTypeSchema.or(TalentSchema).default("none"),
|
|
||||||
banes: z.array(z.string()).default([]),
|
|
||||||
boons: z.array(z.string()).default([]),
|
|
||||||
effects: AbilityEffectSchema.default({}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type PlaybillAbility = z.infer<typeof AbilitySchema>;
|
|
||||||
|
|
||||||
export const EntitySchema = PlaybillResourceSchema.extend({
|
|
||||||
$define: z.literal("entity"),
|
|
||||||
name: NameSchema.default("Unnamed Species"),
|
|
||||||
species: z.string(IDSchema),
|
|
||||||
abilities: z.array(IDSchema),
|
|
||||||
stats: StatSpreadSchema,
|
|
||||||
talents: TalentSpreadSchema,
|
|
||||||
damage: z.number().default(0),
|
|
||||||
baggage: z.array(BaggageSchema).default([]),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ItemTagSchema = PlaybillResourceSchema.extend({
|
|
||||||
$define: z.literal("tag"),
|
|
||||||
name: NameSchema.default("Unnamed Item Tag"),
|
|
||||||
icon: z.string().url(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ItemSchema = PlaybillResourceSchema.extend({
|
|
||||||
$define: z.literal("item"),
|
|
||||||
name: NameSchema.default("Unnamed Item"),
|
|
||||||
type: ItemTypeSchema,
|
|
||||||
slots: z.number().min(0).max(5).default(0),
|
|
||||||
tags: z.array(IDSchema).default([]),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const LoreSchema = PlaybillResourceSchema.extend({
|
|
||||||
$define: z.literal("lore"),
|
|
||||||
name: NameSchema.default("Unnamed Lore Entry"),
|
|
||||||
categories: z.array(z.string()).default([]),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const MethodSchema = PlaybillResourceSchema.extend({
|
|
||||||
$define: z.literal("method"),
|
|
||||||
name: NameSchema.default("Unnamed Method"),
|
|
||||||
curator: NameSchema,
|
|
||||||
abilities: z.array(z.array(IDSchema)).default([]),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const SpeciesSchema = PlaybillResourceSchema.extend({
|
|
||||||
$define: z.literal("species"),
|
|
||||||
name: NameSchema.default("Unnamed Species"),
|
|
||||||
abilities: z.array(IDSchema).default([]),
|
|
||||||
bonuses: AbilityEffectSchema.default({}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const RuleSchema = PlaybillResourceSchema.extend({
|
|
||||||
$define: z.literal("rule"),
|
|
||||||
overrule: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Full Playbill Schema
|
|
||||||
export const PlaybillSchema = PlaybillResourceSchema.extend({
|
|
||||||
$define: z.literal("playbill"),
|
|
||||||
version: z.string().default("0"),
|
|
||||||
name: NameSchema.default("Unnamed Playbill"),
|
|
||||||
author: z.string().default("Anonymous"),
|
|
||||||
|
|
||||||
abilities: z.array(AbilitySchema).default([]),
|
|
||||||
entities: z.array(EntitySchema).default([]),
|
|
||||||
items: z.array(ItemSchema).default([]),
|
|
||||||
tags: z.array(ItemTagSchema).default([]),
|
|
||||||
lore: z.array(LoreSchema).default([]),
|
|
||||||
methods: z.array(MethodSchema).default([]),
|
|
||||||
species: z.array(SpeciesSchema).default([]),
|
|
||||||
rules: z.array(RuleSchema).default([]),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ValidatedPlaybillSchema = PlaybillSchema.superRefine(
|
|
||||||
(val, ctx) => {
|
|
||||||
// For each ability, ensure unique IDs
|
|
||||||
const abilityIds = new Set<string>();
|
|
||||||
for (const ability of val.abilities) {
|
|
||||||
if (abilityIds.has(ability.id)) {
|
|
||||||
ctx.addIssue({
|
|
||||||
message: `Ability ${ability.id} has a duplicate ID`,
|
|
||||||
code: z.ZodIssueCode.custom,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
abilityIds.add(ability.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For each method, ensure its abilities exist
|
|
||||||
// Also ensure unique IDs
|
|
||||||
const methodIds = new Set<string>();
|
|
||||||
for (const method of val.methods) {
|
|
||||||
if (methodIds.has(method.id)) {
|
|
||||||
ctx.addIssue({
|
|
||||||
message: `Method ${method.id} has a duplicate ID`,
|
|
||||||
code: z.ZodIssueCode.custom,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
methodIds.add(method.id);
|
|
||||||
const methodAbilityIds = method.abilities.flat();
|
|
||||||
|
|
||||||
for (const abilityId of methodAbilityIds) {
|
|
||||||
if (!val.abilities.some((a) => a.id === abilityId)) {
|
|
||||||
ctx.addIssue({
|
|
||||||
message: `Method ${method.id} references undefined ability ${abilityId}`,
|
|
||||||
code: z.ZodIssueCode.custom,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For each species, ensure its abilities and IDs
|
|
||||||
const speciesIds = new Set<string>();
|
|
||||||
for (const species of val.species) {
|
|
||||||
if (speciesIds.has(species.id)) {
|
|
||||||
ctx.addIssue({
|
|
||||||
message: `Species ${species.id} has a duplicate ID`,
|
|
||||||
code: z.ZodIssueCode.custom,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
speciesIds.add(species.id);
|
|
||||||
|
|
||||||
const speciesAbilityIds = species.abilities.flat();
|
|
||||||
|
|
||||||
for (const abilityId of speciesAbilityIds) {
|
|
||||||
if (!val.abilities.some((a) => a.id === abilityId)) {
|
|
||||||
ctx.addIssue({
|
|
||||||
message: `Species ${species.id} references undefined ability ${abilityId}`,
|
|
||||||
code: z.ZodIssueCode.custom,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For each entity, ensure its species and known abilities exist
|
|
||||||
const entityIds = new Set<string>();
|
|
||||||
for (const entity of val.entities) {
|
|
||||||
if (entityIds.has(entity.id)) {
|
|
||||||
ctx.addIssue({
|
|
||||||
message: `Entity ${entity.id} has a duplicate ID`,
|
|
||||||
code: z.ZodIssueCode.custom,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
entityIds.add(entity.id);
|
|
||||||
|
|
||||||
for (const abilityId of entity.abilities) {
|
|
||||||
if (!val.abilities.some((a) => a.id === abilityId)) {
|
|
||||||
ctx.addIssue({
|
|
||||||
message: `Entity ${entity.id} references undefined ability ${abilityId}`,
|
|
||||||
code: z.ZodIssueCode.custom,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!val.species.some((s) => s.id === entity.species)) {
|
|
||||||
ctx.addIssue({
|
|
||||||
message: `Entity ${entity.id} references undefined species ${entity.species}`,
|
|
||||||
code: z.ZodIssueCode.custom,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For each item, ensure its tags exist
|
|
||||||
const itemIds = new Set<string>();
|
|
||||||
for (const item of val.items) {
|
|
||||||
if (itemIds.has(item.id)) {
|
|
||||||
ctx.addIssue({
|
|
||||||
message: `Item ${item.id} has a duplicate ID`,
|
|
||||||
code: z.ZodIssueCode.custom,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
itemIds.add(item.id);
|
|
||||||
for (const tag of item.tags) {
|
|
||||||
if (!val.tags.some((t) => t.id === tag)) {
|
|
||||||
ctx.addIssue({
|
|
||||||
message: `Item ${item.id} references undefined tag ${tag}`,
|
|
||||||
code: z.ZodIssueCode.custom,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For each tag, ensure unique IDs
|
|
||||||
const tagIds = new Set<string>();
|
|
||||||
for (const tag of val.tags) {
|
|
||||||
if (tagIds.has(tag.id)) {
|
|
||||||
ctx.addIssue({
|
|
||||||
message: `Tag ${tag.id} has a duplicate ID`,
|
|
||||||
code: z.ZodIssueCode.custom,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
tagIds.add(tag.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For each rule, ensure unique IDs
|
|
||||||
const ruleIds = new Set<string>();
|
|
||||||
for (const rule of val.rules) {
|
|
||||||
if (ruleIds.has(rule.id)) {
|
|
||||||
ctx.addIssue({
|
|
||||||
message: `Rule ${rule.id} has a duplicate ID`,
|
|
||||||
code: z.ZodIssueCode.custom,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ruleIds.add(rule.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For each lore, ensure unique IDs
|
|
||||||
const loreIds = new Set<string>();
|
|
||||||
for (const lore of val.lore) {
|
|
||||||
if (loreIds.has(lore.id)) {
|
|
||||||
ctx.addIssue({
|
|
||||||
message: `Lore ${lore.id} has a duplicate ID`,
|
|
||||||
code: z.ZodIssueCode.custom,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
loreIds.add(lore.id);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
).brand("ValidatedPlaybill");
|
|
||||||
|
|
||||||
export type Playbill = z.infer<typeof PlaybillSchema>;
|
|
||||||
export type ValidatedPlaybill = z.infer<typeof ValidatedPlaybillSchema>;
|
|
||||||
|
|
||||||
export const ResourceMap = {
|
|
||||||
ability: AbilitySchema,
|
|
||||||
species: SpeciesSchema,
|
|
||||||
entity: EntitySchema,
|
|
||||||
item: ItemSchema,
|
|
||||||
tag: ItemTagSchema,
|
|
||||||
method: MethodSchema,
|
|
||||||
lore: LoreSchema,
|
|
||||||
rule: RuleSchema,
|
|
||||||
playbill: PlaybillSchema,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const AnyResourceSchema = z.discriminatedUnion("$define", [
|
|
||||||
AbilitySchema,
|
|
||||||
SpeciesSchema,
|
|
||||||
EntitySchema,
|
|
||||||
ItemSchema,
|
|
||||||
ItemTagSchema,
|
|
||||||
MethodSchema,
|
|
||||||
LoreSchema,
|
|
||||||
RuleSchema,
|
|
||||||
PlaybillSchema,
|
|
||||||
]);
|
|
||||||
|
|
||||||
export type AnyResource = z.infer<typeof AnyResourceSchema>;
|
|
||||||
|
|
||||||
export function getEmptyPlaybill(): Playbill {
|
|
||||||
return PlaybillSchema.parse({
|
|
||||||
$define: "playbill",
|
|
||||||
id: "empty",
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,9 +1,9 @@
|
||||||
import YAML from "yaml";
|
|
||||||
import {
|
import {
|
||||||
type AnyResource,
|
type AnyResource,
|
||||||
AnyResourceSchema,
|
AnyResourceSchema,
|
||||||
ResourceMap,
|
ResourceMap,
|
||||||
} from "./playbill-schema";
|
} from "@proscenium/playbill";
|
||||||
|
import YAML from "yaml";
|
||||||
import { extractFrontmatter, extractMarkdown } from "./markdown";
|
import { extractFrontmatter, extractMarkdown } from "./markdown";
|
||||||
|
|
||||||
type FileFormat = "yaml" | "markdown";
|
type FileFormat = "yaml" | "markdown";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { version } from "../package.json" with { type: "json" };
|
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
|
import { version } from "../package.json" with { type: "json" };
|
||||||
|
|
||||||
export const usage = `
|
export const usage = `
|
||||||
${chalk.bold("muse")} - Compile and validate Playbills for Proscenium
|
${chalk.bold("muse")} - Compile and validate Playbills for Proscenium
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue