Checkpoint current UI and ink integration state
This commit is contained in:
@@ -77,6 +77,8 @@ export function compileInkSource(sourcePath: string, outputPath: string): InkCom
|
||||
export class InkEngine {
|
||||
private story: Story | null = null;
|
||||
private nextTurnId = 1;
|
||||
private storyJson: any = null;
|
||||
private readonly choicePreviewTagKeys = new Set(['action', 'key', 'letter', 'optional', 'gated', 'sort']);
|
||||
|
||||
constructor(private readonly storyPath: string) {}
|
||||
|
||||
@@ -139,8 +141,8 @@ export class InkEngine {
|
||||
throw new Error(`Ink story file not found: ${resolvedPath}`);
|
||||
}
|
||||
|
||||
const storyJson = JSON.parse(readFileSync(resolvedPath, 'utf8'));
|
||||
return new Story(storyJson);
|
||||
this.storyJson = JSON.parse(readFileSync(resolvedPath, 'utf8'));
|
||||
return new Story(this.storyJson);
|
||||
}
|
||||
|
||||
private continueStory(): TurnResult {
|
||||
@@ -170,7 +172,7 @@ export class InkEngine {
|
||||
}
|
||||
|
||||
const choices = this.story.currentChoices.map((choice): ChoiceResult => {
|
||||
const tags = parseTags(choice.tags || []);
|
||||
const tags = this.getChoiceTags(choice);
|
||||
const category = getTagValue(tags, 'action');
|
||||
const letter = getTagValue(tags, 'letter') || getTagValue(tags, 'key');
|
||||
return {
|
||||
@@ -223,4 +225,72 @@ export class InkEngine {
|
||||
gameState: Object.keys(gameState).length > 0 ? gameState : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private getChoiceTags(choice: any): StoryTag[] {
|
||||
const directTags = parseTags(choice?.tags || []);
|
||||
const previewTags = this.extractChoicePreviewTags(choice);
|
||||
const merged = new Map<string, StoryTag>();
|
||||
[...previewTags, ...directTags].forEach((tag) => {
|
||||
merged.set(`${tag.key}:${tag.value || ''}:${tag.param || ''}`, tag);
|
||||
});
|
||||
return Array.from(merged.values());
|
||||
}
|
||||
|
||||
private extractChoicePreviewTags(choice: any): StoryTag[] {
|
||||
const pathString = String(choice?.pathStringOnChoice || choice?.targetPath?.toString?.() || '').trim();
|
||||
if (!pathString || !this.storyJson) return [];
|
||||
|
||||
const container = this.resolveInkPath(pathString);
|
||||
if (!Array.isArray(container)) return [];
|
||||
|
||||
const tags: StoryTag[] = [];
|
||||
for (let index = 0; index < container.length; index += 1) {
|
||||
const token = container[index];
|
||||
if (typeof token === 'string' && token.replace(/^\^/, '').trim() === '') continue;
|
||||
if (token === '\n') continue;
|
||||
if (token !== '#') break;
|
||||
|
||||
const rawParts: string[] = [];
|
||||
index += 1;
|
||||
while (index < container.length && container[index] !== '/#') {
|
||||
const part = container[index];
|
||||
if (typeof part === 'string') {
|
||||
rawParts.push(part.replace(/^\^/, ''));
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
|
||||
const tag = parseTags([rawParts.join('').trim()])[0];
|
||||
if (tag && this.choicePreviewTagKeys.has(tag.key)) {
|
||||
tags.push(tag);
|
||||
}
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
private resolveInkPath(pathString: string): any {
|
||||
const parts = pathString.split('.').filter(Boolean);
|
||||
let node: any = this.storyJson?.root;
|
||||
|
||||
for (const part of parts) {
|
||||
if (!node) return null;
|
||||
|
||||
if (Array.isArray(node) && node.length > 0 && this.isNamedContainerMap(node[node.length - 1]) && part in node[node.length - 1]) {
|
||||
node = node[node.length - 1][part];
|
||||
} else if (Array.isArray(node) && /^\d+$/.test(part)) {
|
||||
node = node[Number(part)];
|
||||
} else if (this.isNamedContainerMap(node) && part in node) {
|
||||
node = node[part];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private isNamedContainerMap(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user