feat: add support for stat templates

Introduces a new `template` stat type that allows for table-based
lookups via CSV blocks in markdown files. When a template stat is
rolled, it uses a dice expression defined in the template to select
an entry, applies a label, and automatically updates associated
modifier stats using relative values.
This commit is contained in:
2026-07-09 10:26:54 +08:00
parent 19c66640b5
commit 1c69bf394c
10 changed files with 384 additions and 15 deletions
+36 -5
View File
@@ -15,10 +15,14 @@ import {
getPathsByExtension,
getIndexedData,
} from "../../data-loader/file-index";
import { parseStatYaml, parseStatCsv } from "../../cli/completions/stat-parser";
import type { StatDef } from "../../cli/completions/stat-parser";
import {
parseStatYaml,
parseStatCsv,
parseTemplateCsv,
} from "../../cli/completions/stat-parser";
import type { StatDef, StatTemplate } from "../../cli/completions/stat-parser";
export type { StatDef };
export type { StatDef, StatTemplate };
// ------------------- Types (mirrors CLI) -------------------
@@ -47,6 +51,7 @@ export interface JournalCompletions {
links: LinkCompletion[];
sparkTables: SparkTableCompletion[];
stats: StatDef[];
statTemplates: StatTemplate[];
}
export type CompletionsState =
@@ -73,6 +78,9 @@ async function tryServer(): Promise<JournalCompletions | null> {
links: Array.isArray(data.links) ? data.links : [],
sparkTables: Array.isArray(data.sparkTables) ? data.sparkTables : [],
stats: Array.isArray(data.stats) ? data.stats : [],
statTemplates: Array.isArray(data.statTemplates)
? data.statTemplates
: [],
};
} catch {
return null;
@@ -173,7 +181,24 @@ async function scanClientSide(): Promise<JournalCompletions> {
}
}
return { dice, links, sparkTables, stats };
// Stat template scan
const statTemplates: StatTemplate[] = [];
const templateBlockRegex =
/```csv\s+role=stat-template\s+file=(\S+)\s*\n([\s\S]*?)```/gi;
for (const filePath of paths) {
const content = await getIndexedData(filePath);
if (!content) continue;
templateBlockRegex.lastIndex = 0;
let tMatch: RegExpExecArray | null;
while ((tMatch = templateBlockRegex.exec(content)) !== null) {
const name = tMatch[1];
const csv = tMatch[2];
statTemplates.push(parseTemplateCsv(csv, filePath, name));
}
}
return { dice, links, sparkTables, stats, statTemplates };
}
function splitTableRow(line: string): string[] | null {
@@ -244,6 +269,12 @@ export function useJournalCompletions(): {
}
return {
state: s,
data: { dice: [], links: [], sparkTables: [], stats: [] },
data: {
dice: [],
links: [],
sparkTables: [],
stats: [],
statTemplates: [],
},
};
}