Muse owns muse-shaped files now

This commit is contained in:
Endeavorance 2025-03-17 17:20:30 -04:00
parent 3e2ab6ec73
commit bf444ccb1a
23 changed files with 1288 additions and 201 deletions

361
src/define/index.ts Normal file
View file

@ -0,0 +1,361 @@
import {
type Ability,
type Blueprint,
type Item,
type Method,
type Resource,
type Rule,
type Species,
parseAbility,
parseBlueprint,
parseGlossary,
parseItem,
parseMethod,
parseResource,
parseRule,
parseSpecies,
} from "@proscenium/playbill";
import { z } from "zod";
const Base = z.object({
$define: z.string(),
});
const Info = Base.extend({
id: z.string(), // TODO: Validate ID shapes
name: z.string().default("Unnamed Component"),
description: z.string().default("No description provided"),
categories: z.array(z.string()).default([]),
});
const AbilitySchema = Info.extend({
$define: z.literal("ability"),
type: z.string().default("action"),
ap: z.number().int().default(0),
hp: z.number().int().default(0),
ep: z.number().int().default(0),
xp: z.number().int().default(0),
damage: z.number().int().default(0),
damageType: z.string().default("phy"),
roll: z.string().default("none"),
});
const BlueprintSchema = Info.extend({
$define: z.literal("blueprint"),
species: z.string(),
items: z.array(z.string()).default([]),
abilities: z.array(z.string()).default([]),
ap: z.number().int().default(0),
hp: z.number().int().default(0),
ep: z.number().int().default(0),
xp: z.number().int().default(0),
muscle: z.string().default("novice"),
focus: z.string().default("novice"),
knowledge: z.string().default("novice"),
charm: z.string().default("novice"),
cunning: z.string().default("novice"),
spark: z.string().default("novice"),
});
const GlossarySchema = Base.extend({
$define: z.literal("glossary"),
terms: z.record(z.string(), z.string()),
});
const BaseItem = Info.extend({
$define: z.literal("item"),
rarity: z.string().default("common"),
affinity: z.string().default("none"),
damage: z.number().int().default(0),
damageType: z.string().default("phy"),
});
const ItemSchema = z.discriminatedUnion("type", [
BaseItem.extend({
type: z.literal("wielded"),
hands: z.number().int().default(1),
}),
BaseItem.extend({
type: z.literal("worn"),
hands: z.number().int().default(1),
}),
BaseItem.extend({
type: z.literal("trinket"),
}),
]);
const MethodSchema = Info.extend({
$define: z.literal("method"),
curator: z.string().default("Someone"),
abilities: z.array(z.array(z.string())).default([]),
});
const ResourceSchema = z.discriminatedUnion("type", [
Info.extend({
$define: z.literal("resource"),
type: z.literal("text"),
}),
Info.extend({
$define: z.literal("resource"),
type: z.literal("image"),
url: z.string(),
}),
Info.extend({
$define: z.literal("resource"),
type: z.literal("table"),
data: z.array(z.array(z.string())),
}),
]);
const RuleSchema = Info.extend({
$define: z.literal("rule"),
overrule: z.nullable(z.string()).default(null),
order: z.number().int().default(Number.POSITIVE_INFINITY),
});
const SpeciesSchema = Info.extend({
$define: z.literal("species"),
hands: z.number().int().default(2),
abilities: z.array(z.string()).default([]),
ap: z.number().int().default(0),
hp: z.number().int().default(0),
ep: z.number().int().default(0),
xp: z.number().int().default(0),
muscle: z.string().default("novice"),
focus: z.string().default("novice"),
knowledge: z.string().default("novice"),
charm: z.string().default("novice"),
cunning: z.string().default("novice"),
spark: z.string().default("novice"),
});
const SchemaMapping = {
ability: AbilitySchema,
blueprint: BlueprintSchema,
glossary: GlossarySchema,
item: ItemSchema,
method: MethodSchema,
resource: ResourceSchema,
rule: RuleSchema,
species: SpeciesSchema,
} as const;
type ComponentType = keyof typeof SchemaMapping;
const parseComponentType = (val: unknown): ComponentType => {
if (typeof val !== "string") {
throw new Error("Component type must be a string");
}
if (!(val in SchemaMapping)) {
throw new Error(`Unknown component type: ${val}`);
}
return val as ComponentType;
};
export type ParsedComponent =
| {
type: "ability";
component: Ability;
}
| {
type: "blueprint";
component: Blueprint;
}
| {
type: "glossary";
component: Record<string, string[]>;
}
| {
type: "item";
component: Item;
}
| {
type: "method";
component: Method;
}
| {
type: "resource";
component: Resource;
}
| {
type: "rule";
component: Rule;
}
| {
type: "species";
component: Species;
};
function parseAbilityDefinition(obj: unknown): ParsedComponent {
const parsed = AbilitySchema.parse(obj);
const ability = parseAbility({
id: parsed.id,
name: parsed.name,
description: parsed.description,
categories: parsed.categories,
type: parsed.type,
ap: parsed.ap,
ep: parsed.ep,
hp: parsed.hp,
xp: parsed.xp,
damage:
parsed.damage > 0
? {
amount: parsed.damage,
type: parsed.damageType,
}
: null,
roll: parsed.roll,
boons: [],
banes: [],
});
return {
type: "ability",
component: ability,
};
}
function parseBlueprintDefinition(obj: unknown): ParsedComponent {
const parsed = BlueprintSchema.parse(obj);
const blueprint = parseBlueprint({
id: parsed.id,
name: parsed.name,
description: parsed.description,
categories: parsed.categories,
species: parsed.species,
items: parsed.items,
abilities: parsed.abilities,
baggage: [],
ap: parsed.ap,
hp: parsed.hp,
ep: parsed.ep,
xp: parsed.xp,
muscle: parsed.muscle,
focus: parsed.focus,
knowledge: parsed.knowledge,
charm: parsed.charm,
cunning: parsed.cunning,
spark: parsed.spark,
});
return {
type: "blueprint",
component: blueprint,
};
}
function parseGlossaryDefinition(obj: unknown): ParsedComponent {
const { terms } = GlossarySchema.parse(obj);
// Create a new object with each definition as an array
const curatedTerms: Record<string, string[]> = {};
for (const [term, definition] of Object.entries(terms)) {
curatedTerms[term] = [definition];
}
const glossary = parseGlossary(curatedTerms);
return {
type: "glossary",
component: glossary,
};
}
function parseItemDefinition(obj: unknown): ParsedComponent {
const parsed = ItemSchema.parse(obj);
return {
type: "item",
component: parseItem({
...parsed,
damage:
parsed.damage > 0
? {
amount: parsed.damage,
type: parsed.damageType,
}
: null,
tweak: "",
temper: "",
}),
};
}
function parseMethodDefinition(obj: unknown): ParsedComponent {
const parsed = MethodSchema.parse(obj);
const method = {
id: parsed.id,
name: parsed.name,
description: parsed.description,
categories: parsed.categories,
curator: parsed.curator,
abilities: parsed.abilities,
};
return {
type: "method",
component: parseMethod(method),
};
}
function parseResourceDefinition(obj: unknown): ParsedComponent {
const parsed = ResourceSchema.parse(obj);
return {
type: "resource",
component: parseResource(parsed),
};
}
function parseRuleDefinition(obj: unknown): ParsedComponent {
const parsed = RuleSchema.parse(obj);
return {
type: "rule",
component: parseRule(parsed),
};
}
function parseSpeciesDefinition(obj: unknown): ParsedComponent {
const parsed = SpeciesSchema.parse(obj);
return {
type: "species",
component: parseSpecies(parsed),
};
}
export function parsePlaybillComponent(obj: unknown): ParsedComponent {
const baseParse = Base.parse(obj);
const type = parseComponentType(baseParse.$define);
const schema = SchemaMapping[type];
const component = schema.parse(obj);
switch (type) {
case "ability":
return parseAbilityDefinition(component);
case "blueprint":
return parseBlueprintDefinition(component);
case "glossary":
return parseGlossaryDefinition(component);
case "item":
return parseItemDefinition(component);
case "method":
return parseMethodDefinition(component);
case "resource":
return parseResourceDefinition(component);
case "rule":
return parseRuleDefinition(component);
case "species":
return parseSpeciesDefinition(component);
default:
throw new Error(`Unknown component type: ${type}`);
}
}