159 lines
5.8 KiB
JavaScript
159 lines
5.8 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.InkEngine = void 0;
|
|
exports.compileInkSource = compileInkSource;
|
|
const fs_1 = require("fs");
|
|
const path_1 = __importDefault(require("path"));
|
|
const inkjs_1 = require("inkjs");
|
|
const tag_parser_1 = require("../utils/tag-parser");
|
|
const { Compiler } = require('inkjs/full');
|
|
function compileInkSource(sourcePath, outputPath) {
|
|
const resolvedSource = path_1.default.resolve(sourcePath);
|
|
const resolvedOutput = path_1.default.resolve(outputPath);
|
|
if (!(0, fs_1.existsSync)(resolvedSource)) {
|
|
throw new Error(`Ink source file not found: ${resolvedSource}`);
|
|
}
|
|
const warnings = [];
|
|
const errors = [];
|
|
const source = (0, fs_1.readFileSync)(resolvedSource, 'utf8').replace(/^\uFEFF/, '');
|
|
const sourceDir = path_1.default.dirname(resolvedSource);
|
|
const fileHandler = {
|
|
ResolveInkFilename: (filename) => path_1.default.isAbsolute(filename) ? filename : path_1.default.resolve(sourceDir, filename),
|
|
LoadInkFileContents: (filename) => (0, fs_1.readFileSync)(path_1.default.isAbsolute(filename) ? filename : path_1.default.resolve(sourceDir, filename), 'utf8')
|
|
.replace(/^\uFEFF/, ''),
|
|
};
|
|
const compiler = new Compiler(source, {
|
|
sourceFilename: resolvedSource,
|
|
fileHandler,
|
|
errorHandler: (message, type) => {
|
|
if (type === 1) {
|
|
warnings.push(message);
|
|
}
|
|
else {
|
|
errors.push(message);
|
|
}
|
|
},
|
|
});
|
|
const story = compiler.Compile();
|
|
if (!story || errors.length > 0) {
|
|
throw new Error(`Ink compilation failed:\n${errors.join('\n')}`);
|
|
}
|
|
if (warnings.length > 0) {
|
|
warnings.forEach((warning) => console.warn(`[ink] ${warning}`));
|
|
}
|
|
(0, fs_1.mkdirSync)(path_1.default.dirname(resolvedOutput), { recursive: true });
|
|
(0, fs_1.writeFileSync)(resolvedOutput, story.ToJson(), 'utf8');
|
|
return {
|
|
sourcePath: resolvedSource,
|
|
outputPath: resolvedOutput,
|
|
warningCount: warnings.length,
|
|
};
|
|
}
|
|
class InkEngine {
|
|
constructor(storyPath) {
|
|
this.storyPath = storyPath;
|
|
this.story = null;
|
|
this.nextTurnId = 1;
|
|
}
|
|
isRunning() {
|
|
if (!this.story)
|
|
return false;
|
|
return this.story.canContinue || this.story.currentChoices.length > 0;
|
|
}
|
|
newGame() {
|
|
this.story = this.loadStory();
|
|
this.nextTurnId = 1;
|
|
return this.continueStory();
|
|
}
|
|
chooseChoice(choiceIndex) {
|
|
if (!this.story) {
|
|
throw new Error('No active Ink story');
|
|
}
|
|
const choice = this.story.currentChoices.find((item) => item.index === choiceIndex);
|
|
if (!choice) {
|
|
throw new Error(`Ink choice ${choiceIndex} is not available`);
|
|
}
|
|
this.story.ChooseChoiceIndex(choice.index);
|
|
return this.continueStory();
|
|
}
|
|
saveGame() {
|
|
if (!this.story) {
|
|
throw new Error('No active Ink story to save');
|
|
}
|
|
return JSON.stringify({
|
|
inkState: this.story.state.toJson(),
|
|
nextTurnId: this.nextTurnId,
|
|
});
|
|
}
|
|
loadGame(savedState) {
|
|
this.story = this.loadStory();
|
|
let inkState = savedState;
|
|
try {
|
|
const parsed = JSON.parse(savedState);
|
|
if (parsed && typeof parsed.inkState === 'string') {
|
|
inkState = parsed.inkState;
|
|
if (Number.isInteger(parsed.nextTurnId)) {
|
|
this.nextTurnId = Math.max(1, parsed.nextTurnId);
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
// Backward compatibility with raw Ink state JSON.
|
|
}
|
|
this.story.state.LoadJson(inkState);
|
|
return this.continueStory();
|
|
}
|
|
loadStory() {
|
|
const resolvedPath = path_1.default.resolve(this.storyPath);
|
|
if (!(0, fs_1.existsSync)(resolvedPath)) {
|
|
throw new Error(`Ink story file not found: ${resolvedPath}`);
|
|
}
|
|
const storyJson = JSON.parse((0, fs_1.readFileSync)(resolvedPath, 'utf8'));
|
|
return new inkjs_1.Story(storyJson);
|
|
}
|
|
continueStory() {
|
|
if (!this.story) {
|
|
throw new Error('No active Ink story');
|
|
}
|
|
const paragraphs = [];
|
|
const globalTags = [];
|
|
while (this.story.canContinue) {
|
|
const rawText = this.story.Continue();
|
|
const text = String(rawText || '').trim();
|
|
const tags = (0, tag_parser_1.parseTags)(this.story.currentTags || []);
|
|
tags
|
|
.filter((tag) => tag.key === 'title' || tag.key === 'author')
|
|
.forEach((tag) => globalTags.push(tag));
|
|
if (text) {
|
|
paragraphs.push({ text, tags });
|
|
}
|
|
else {
|
|
tags.forEach((tag) => globalTags.push(tag));
|
|
}
|
|
}
|
|
const choices = this.story.currentChoices.map((choice) => {
|
|
const tags = (0, tag_parser_1.parseTags)(choice.tags || []);
|
|
const category = (0, tag_parser_1.getTagValue)(tags, 'action');
|
|
const letter = (0, tag_parser_1.getTagValue)(tags, 'letter');
|
|
return {
|
|
index: choice.index,
|
|
text: String(choice.text || '').trim(),
|
|
tags,
|
|
category,
|
|
letter,
|
|
};
|
|
});
|
|
return {
|
|
turnId: this.nextTurnId++,
|
|
paragraphs,
|
|
choices,
|
|
inputMode: choices.length > 0 ? 'choice' : 'end',
|
|
globalTags: globalTags.length > 0 ? globalTags : undefined,
|
|
};
|
|
}
|
|
}
|
|
exports.InkEngine = InkEngine;
|
|
//# sourceMappingURL=ink-engine.js.map
|