UNPKG

vn-engine

Version:

A powerful, flexible TypeScript library for creating visual novels and interactive narratives

1,517 lines â€ĸ 92.1 kB
import d from "lodash"; import * as $ from "js-yaml"; class k { constructor(e) { this.state = { currentScene: "", currentInstruction: 0, variables: /* @__PURE__ */ new Map(), storyFlags: /* @__PURE__ */ new Set(), choiceHistory: [], ...e }; } setCurrentScene(e) { this.state.currentScene = e; } getCurrentScene() { return this.state.currentScene; } setCurrentInstruction(e) { this.state.currentInstruction = e; } getCurrentInstruction() { return this.state.currentInstruction; } setStoryFlag(e) { this.state.storyFlags.add(e); } clearStoryFlag(e) { this.state.storyFlags.delete(e); } hasStoryFlag(e) { return this.state.storyFlags.has(e); } setVariable(e, t) { this.state.variables.set(e, t); } getVariable(e) { return this.state.variables.get(e); } addToVariable(e, t) { if (e.includes(".")) { const s = e.split("."), n = s[0], i = s.slice(1).join("."); let a = this.getVariable(n); (!a || typeof a != "object") && (a = {}); const o = this.getNestedProperty(a, i) || 0; this.setNestedProperty(a, i, o + t), this.setVariable(n, a); } else { const s = this.getVariable(e) || 0; this.setVariable(e, s + t); } } getNestedProperty(e, t) { if (!(!e || !t)) return t.split(".").reduce((s, n) => s && s[n] !== void 0 ? s[n] : void 0, e); } setNestedProperty(e, t, s) { const n = t.split("."); let i = e; for (let a = 0; a < n.length - 1; a++) (!i[n[a]] || typeof i[n[a]] != "object") && (i[n[a]] = {}), i = i[n[a]]; i[n[n.length - 1]] = s; } addChoice(e) { this.state.choiceHistory.push(e); } getChoiceHistory() { return [...this.state.choiceHistory]; } playerChose(e, t) { return this.state.choiceHistory.some( (s) => s.choiceText === e && (!t || s.scene === t) ); } getList(e) { const t = this.getVariable(e); return Array.isArray(t) ? t : []; } setList(e, t) { this.setVariable(e, t); } addToList(e, t) { const s = this.getList(e); s.push(t), this.setList(e, s); } addTime(e) { const t = this.getVariable("gameTime") || 0; this.setVariable("gameTime", t + e), this.setVariable("currentTime", t + e); } getCurrentTime() { return this.getVariable("gameTime") || this.getVariable("currentTime") || 0; } getState() { return { currentScene: this.state.currentScene, currentInstruction: this.state.currentInstruction, variables: new Map(this.state.variables), storyFlags: new Set(this.state.storyFlags), choiceHistory: [...this.state.choiceHistory] }; } serialize() { return { currentScene: this.state.currentScene, currentInstruction: this.state.currentInstruction, variables: Array.from(this.state.variables.entries()), storyFlags: Array.from(this.state.storyFlags), choiceHistory: [...this.state.choiceHistory], schemaVersion: "1.0.0", saveDate: (/* @__PURE__ */ new Date()).toISOString() }; } deserialize(e) { try { this.state = { currentScene: e.currentScene || "", currentInstruction: e.currentInstruction || 0, variables: new Map(e.variables || []), storyFlags: new Set(e.storyFlags || []), choiceHistory: e.choiceHistory || [] }; } catch (t) { console.error("Failed to deserialize game state:", t), this.state = { currentScene: "", currentInstruction: 0, variables: /* @__PURE__ */ new Map(), storyFlags: /* @__PURE__ */ new Set(), choiceHistory: [] }; } } setBulkVariables(e) { try { Object.entries(e).forEach(([t, s]) => { this.setVariable(t, s); }); } catch (t) { console.error("Error setting bulk variables:", t); } } setBulkFlags(e) { try { e.forEach((t) => { typeof t == "string" && t.length > 0 && this.setStoryFlag(t); }); } catch (t) { console.error("Error setting bulk flags:", t); } } reset() { this.state = { currentScene: "", currentInstruction: 0, variables: /* @__PURE__ */ new Map(), storyFlags: /* @__PURE__ */ new Set(), choiceHistory: [] }; } validateState() { const e = []; return typeof this.state.currentScene != "string" && e.push("Current scene must be a string"), (typeof this.state.currentInstruction != "number" || this.state.currentInstruction < 0) && e.push("Current instruction must be a non-negative number"), this.state.variables instanceof Map || e.push("Variables must be a Map"), this.state.storyFlags instanceof Set || e.push("Story flags must be a Set"), Array.isArray(this.state.choiceHistory) || e.push("Choice history must be an array"), { valid: e.length === 0, errors: e }; } } const H = class H { render(e, t) { try { let s = e; return s = this.processConditionals(s, t), s = this.processVariables(s, t), s; } catch (s) { return console.error("Simple template rendering error:", s), `[Template Error: ${s.message}]`; } } processConditionals(e, t) { return e = e.replace(H.CONDITION_ELSE_REGEX, (s, n, i, a) => this.evaluateCondition(n.trim(), t) ? i : a), e = e.replace(H.CONDITION_REGEX, (s, n, i) => this.evaluateCondition(n.trim(), t) ? i : ""), e; } processVariables(e, t) { return e.replace(H.VARIABLE_REGEX, (s, n) => { const i = this.evaluateExpression(n.trim(), t); return this.formatValue(i); }); } evaluateCondition(e, t) { try { const s = e.match(/hasFlag\s*\(\s*['"]([^'"]+)['"]\s*\)/); if (s) return t.computed.hasFlag(s[1]); if (/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(e)) { const o = this.evaluateExpression(e, t); return this.isTruthy(o); } const n = e.match(/^([a-zA-Z_][a-zA-Z0-9_.]*)\s+(eq|ne|gt|lt|gte|lte)\s+(.+)$/); if (n) { const [, o, g, c] = n, A = this.evaluateExpression(o, t), x = this.parseValue(c.trim()); return this.compareValues(A, g, x); } const i = e.match(/playerChose\s*\(\s*['"]([^'"]+)['"]\s*\)/); if (i) return t.computed.playerChose(i[1]); const a = this.evaluateExpression(e, t); return this.isTruthy(a); } catch (s) { return console.warn(`Failed to evaluate condition: ${e}`, s), !1; } } evaluateExpression(e, t) { const s = e.split("."); let n = { ...t.variables, ...t }; for (const i of s) if (n && typeof n == "object") if (i in n) n = n[i]; else { n = void 0; break; } else { n = void 0; break; } return n; } parseValue(e) { if (e.startsWith('"') && e.endsWith('"') || e.startsWith("'") && e.endsWith("'")) return e.slice(1, -1); if (/^-?\d+$/.test(e)) return parseInt(e, 10); if (/^-?\d*\.\d+$/.test(e)) return parseFloat(e); if (e === "true") return !0; if (e === "false") return !1; if (e === "null") return null; if (e !== "undefined") return e; } compareValues(e, t, s) { switch (t) { case "eq": return e == s; case "ne": return e != s; case "gt": return Number(e) > Number(s); case "gte": return Number(e) >= Number(s); case "lt": return Number(e) < Number(s); case "lte": return Number(e) <= Number(s); default: return !1; } } isTruthy(e) { return e == null ? !1 : typeof e == "boolean" ? e : typeof e == "number" ? e !== 0 && !isNaN(e) : typeof e == "string" || Array.isArray(e) ? e.length > 0 : typeof e == "object" ? Object.keys(e).length > 0 : !!e; } formatValue(e) { return e == null ? "" : typeof e == "string" ? e : typeof e == "number" || typeof e == "boolean" ? e.toString() : Array.isArray(e) ? e.join(", ") : typeof e == "object" ? JSON.stringify(e) : String(e); } getSupportedFeatures() { return { variables: !0, conditionals: !0, helpers: !1, loops: !1, partials: !1 }; } getEngineType() { return "simple"; } }; H.VARIABLE_REGEX = /\{\{([^}]+)\}\}/g, H.CONDITION_REGEX = /\{\{#if\s+([^}]+)\}\}(.*?)\{\{\/if\}\}/gs, H.CONDITION_ELSE_REGEX = /\{\{#if\s+([^}]+)\}\}(.*?)\{\{else\}\}(.*?)\{\{\/if\}\}/gs; let F = H; function N(r) { return d.isArrayLike(r); } const m = { first(r, e) { if (N(r)) return typeof e == "number" ? d.take(r, e) : d.head(r); }, last(r, e) { if (N(r)) return typeof e == "number" ? d.takeRight(r, e) : d.last(r); }, length(r) { return d.size(r); }, includes(r, e) { return d.includes(r, e); }, isEmpty(r) { return d.isEmpty(r); }, filter(r, e) { return d.filter(r, e); }, find(r, e) { return d.find(r, e); }, where(r, e) { return d.filter(r, e); }, map(r, e) { return d.map(r, e); }, pluck(r, e) { return d.map(r, e); }, join(r, e = ",") { return Array.isArray(r) ? r.join(e) : ""; }, groupBy(r, e) { return d.groupBy(r, e); }, chunk(r, e) { return d.chunk(r, Math.max(1, e)); }, unique(r) { return d.uniq(r); }, shuffle(r) { return d.shuffle([...r]); }, slice(r, e, t) { return d.slice(r, e, t); }, take(r, e) { return d.take(r, Math.max(0, e)); }, sample(r) { return d.sample(r); }, sampleSize(r, e) { return d.sampleSize(r, Math.max(0, e)); }, flatten(r) { return d.flatten(r); }, reverse(r) { return [...r].reverse(); }, concat(...r) { return d.concat([], ...r); }, compact(r) { return d.compact(r); }, without(r, ...e) { return d.without(r, ...e); }, randomChoice(r) { if (!(!Array.isArray(r) || r.length === 0)) return r[Math.floor(Math.random() * r.length)]; }, weightedChoice(r, e) { if (!Array.isArray(r) || !Array.isArray(e) || r.length !== e.length) return; const t = e.reduce((n, i) => n + Math.max(0, i), 0); if (t === 0) return; let s = Math.random() * t; for (let n = 0; n < r.length; n++) if (s -= Math.max(0, e[n]), s <= 0) return r[n]; return r[r.length - 1]; }, cycleNext(r, e) { if (!Array.isArray(r) || r.length === 0) return; const t = (e + 1) % r.length; return r[t]; }, findByProperty(r, e, t) { return d.find(r, { [e]: t }); } }; function z(r) { r.registerHelper("first", (e, t) => m.first(e, t)), r.registerHelper("last", (e, t) => m.last(e, t)), r.registerHelper("length", (e) => m.length(e)), r.registerHelper("size", (e) => m.length(e)), r.registerHelper("includes", (e, t) => m.includes(e, t)), r.registerHelper("isEmpty", (e) => m.isEmpty(e)), r.registerHelper("where", (e, t) => m.where(e, t)), r.registerHelper("pluck", (e, t) => m.pluck(e, t)), r.registerHelper("join", (e, t) => m.join(e, t)), r.registerHelper("groupBy", (e, t) => m.groupBy(e, t)), r.registerHelper("chunk", (e, t) => m.chunk(e, t)), r.registerHelper("unique", (e) => m.unique(e)), r.registerHelper("shuffle", (e) => m.shuffle(e)), r.registerHelper("slice", (e, t, s) => m.slice(e, t, s)), r.registerHelper("take", (e, t) => m.take(e, t)), r.registerHelper("sample", (e) => m.sample(e)), r.registerHelper("flatten", (e) => m.flatten(e)), r.registerHelper("reverse", (e) => m.reverse(e)), r.registerHelper("compact", (e) => m.compact(e)), r.registerHelper("without", (e, ...t) => { const s = t.slice(0, -1); return m.without(e, ...s); }), r.registerHelper("randomChoice", (e) => m.randomChoice(e)), r.registerHelper("weightedChoice", (e, t) => m.weightedChoice(e, t)), r.registerHelper("cycleNext", (e, t) => m.cycleNext(e, t)), r.registerHelper("findByProperty", (e, t, s) => m.findByProperty(e, t, s)), r.registerHelper("array", (...e) => (e.pop(), e)), r.registerHelper("range", (e, t, s) => d.range(e, t, s)), r.registerHelper("times", function(e, t) { let s = ""; for (let n = 0; n < e; n++) s += t.fn({ index: n, first: n === 0, last: n === e - 1 }); return s; }); } function I(r) { return r == null ? !1 : typeof r == "boolean" ? r : typeof r == "number" ? r !== 0 && !isNaN(r) : typeof r == "string" || Array.isArray(r) ? r.length > 0 : typeof r == "object" ? Object.keys(r).length > 0 : !!r; } function w(r, e = 0) { const t = Number(r); return isNaN(t) ? e : t; } const p = { eq(r, e) { return r === e; }, ne(r, e) { return r !== e; }, gt(r, e) { return w(r) > w(e); }, gte(r, e) { return w(r) >= w(e); }, lt(r, e) { return w(r) < w(e); }, lte(r, e) { return w(r) <= w(e); }, and(...r) { return r.every(I); }, or(...r) { return r.some(I); }, not(r) { return !I(r); }, contains(r, e) { return Array.isArray(r) || typeof r == "string" ? r.includes(e) : r && typeof r == "object" ? e in r : !1; }, isEmpty(r) { return r == null ? !0 : Array.isArray(r) || typeof r == "string" ? r.length === 0 : typeof r == "object" ? Object.keys(r).length === 0 : !1; }, isString(r) { return typeof r == "string"; }, isNumber(r) { return typeof r == "number" && !isNaN(r); }, isArray(r) { return Array.isArray(r); }, isObject(r) { return r !== null && typeof r == "object" && !Array.isArray(r); }, isBoolean(r) { return typeof r == "boolean"; }, compare(r, e, t) { switch (e) { case "==": case "eq": return r == t; case "===": case "eqStrict": return r === t; case "!=": case "ne": return r != t; case "!==": case "neStrict": return r !== t; case ">": case "gt": return p.gt(r, t); case ">=": case "gte": return p.gte(r, t); case "<": case "lt": return p.lt(r, t); case "<=": case "lte": return p.lte(r, t); case "contains": return p.contains(r, t); case "between": return p.between(r, w(t), w(arguments[3])); case "typeof": return typeof r === t; default: return !1; } }, between(r, e, t) { const s = w(r), n = w(e), i = w(t); return s >= n && s <= i; }, ifx(r, e, t) { return I(r) ? e : t; }, coalesce(...r) { for (const e of r) if (e != null) return e; }, defaultTo(r, e) { return r != null && r !== "" ? r : e; } }; function C(r) { return function(...e) { const t = e[e.length - 1], s = r(...e.slice(0, -1)); return t.fn ? s ? t.fn(this) : t.inverse(this) : s; }; } function O(r) { r.registerHelper("eq", C(p.eq)), r.registerHelper("ne", C(p.ne)), r.registerHelper("gt", C(p.gt)), r.registerHelper("gte", C(p.gte)), r.registerHelper("lt", C(p.lt)), r.registerHelper("lte", C(p.lte)), r.registerHelper("and", C(p.and)), r.registerHelper("or", C(p.or)), r.registerHelper("not", C(p.not)), r.registerHelper("contains", C(p.contains)), r.registerHelper("isEmpty", C(p.isEmpty)), r.registerHelper("isString", (e) => p.isString(e)), r.registerHelper("isNumber", (e) => p.isNumber(e)), r.registerHelper("isArray", (e) => p.isArray(e)), r.registerHelper("isObject", (e) => p.isObject(e)), r.registerHelper("isBoolean", (e) => p.isBoolean(e)), r.registerHelper("compare", function(e, t, s, n) { const i = p.compare(e, t, s); return n.fn ? i ? n.fn(this) : n.inverse(this) : i; }), r.registerHelper("between", function(e, t, s, n) { const i = p.between(e, t, s); return n.fn ? i ? n.fn(this) : n.inverse(this) : i; }), r.registerHelper("ifx", p.ifx), r.registerHelper("ternary", p.ifx), r.registerHelper("coalesce", function(...e) { return e.pop(), p.coalesce(...e); }), r.registerHelper("default", function(...e) { return e.pop(), p.defaultTo(e[0], e[1]); }), r.registerHelper("eqw", function(e, t, s) { const n = e == t; return s.fn ? n ? s.fn(this) : s.inverse(this) : n; }), r.registerHelper("neqw", function(e, t, s) { const n = e != t; return s.fn ? n ? s.fn(this) : s.inverse(this) : n; }); } function l(r, e = 0) { const t = Number(r); return isNaN(t) || !isFinite(t) ? e : t; } function V(r) { return Array.isArray(r) ? r.map((e) => l(e)).filter((e) => !isNaN(e)) : []; } const f = { add(r, e) { return l(r) + l(e); }, subtract(r, e) { return l(r) - l(e); }, multiply(r, e) { return l(r) * l(e); }, divide(r, e) { const t = l(e); return t === 0 ? 0 : l(r) / t; }, remainder(r, e) { const t = l(e); return t === 0 ? 0 : l(r) % t; }, abs(r) { return Math.abs(l(r)); }, min(...r) { const e = r.map(l).filter((t) => !isNaN(t)); return e.length > 0 ? Math.min(...e) : 0; }, max(...r) { const e = r.map(l).filter((t) => !isNaN(t)); return e.length > 0 ? Math.max(...e) : 0; }, round(r, e = 0) { const t = l(r), s = Math.pow(10, l(e)); return Math.round(t * s) / s; }, ceil(r) { return Math.ceil(l(r)); }, floor(r) { return Math.floor(l(r)); }, random(r, e) { const t = l(r), s = l(e); return Math.random() * (s - t) + t; }, randomInt(r, e) { const t = Math.ceil(l(r)), s = Math.floor(l(e)); return Math.floor(Math.random() * (s - t + 1)) + t; }, clamp(r, e, t) { const s = l(r), n = l(e), i = l(t); return Math.min(Math.max(s, n), i); }, sum(r) { return V(r).reduce((e, t) => e + t, 0); }, average(r) { const e = V(r); return e.length > 0 ? e.reduce((t, s) => t + s, 0) / e.length : 0; }, percentage(r, e) { const t = l(e); return t === 0 ? 0 : f.round(l(r) / t * 100, 2); }, statCheck(r, e) { const t = l(r), s = l(e), n = Math.random() * 100; return t + n >= s; }, rollDice(r, e = 1) { const t = Math.max(1, Math.floor(l(r))), s = Math.max(1, Math.floor(l(e))); let n = 0; for (let i = 0; i < s; i++) n += Math.floor(Math.random() * t) + 1; return n; }, lerp(r, e, t) { const s = l(r), n = l(e), i = f.clamp(l(t), 0, 1); return s + (n - s) * i; }, normalizeValue(r, e, t) { const s = l(r), n = l(e), i = l(t); return i === n ? 0 : f.clamp((s - n) / (i - n), 0, 1); }, formatNumber(r, e = 0) { const t = l(r), s = Math.max(0, Math.floor(l(e))); return t.toFixed(s); } }; function j(r) { r.registerHelper("add", (e, t) => f.add(e, t)), r.registerHelper("subtract", (e, t) => f.subtract(e, t)), r.registerHelper("multiply", (e, t) => f.multiply(e, t)), r.registerHelper("divide", (e, t) => f.divide(e, t)), r.registerHelper("remainder", (e, t) => f.remainder(e, t)), r.registerHelper("mod", (e, t) => f.remainder(e, t)), r.registerHelper("abs", (e) => f.abs(e)), r.registerHelper("min", (...e) => { const t = e.slice(0, -1); return f.min(...t); }), r.registerHelper("max", (...e) => { const t = e.slice(0, -1); return f.max(...t); }), r.registerHelper("round", (e, t) => f.round(e, t)), r.registerHelper("ceil", (e) => f.ceil(e)), r.registerHelper("floor", (e) => f.floor(e)), r.registerHelper("random", (e, t) => f.random(e, t)), r.registerHelper("randomInt", (e, t) => f.randomInt(e, t)), r.registerHelper("clamp", (e, t, s) => f.clamp(e, t, s)), r.registerHelper("sum", (e) => f.sum(e)), r.registerHelper("average", (e) => f.average(e)), r.registerHelper("percentage", (e, t) => f.percentage(e, t)), r.registerHelper("statCheck", function(e, t, s) { const n = f.statCheck(e, t); return s.fn ? n ? s.fn(this) : s.inverse(this) : n; }), r.registerHelper("rollDice", (e, t) => f.rollDice(e, t)), r.registerHelper("lerp", (e, t, s) => f.lerp(e, t, s)), r.registerHelper("normalize", (e, t, s) => f.normalizeValue(e, t, s)), r.registerHelper("formatNumber", (e, t) => f.formatNumber(e, t)), r.registerHelper("inRange", function(e, t, s, n) { const i = l(e), a = l(t), o = l(s), g = i >= a && i <= o; return n.fn ? g ? n.fn(this) : n.inverse(this) : g; }), r.registerHelper("isEven", function(e, t) { const s = l(e) % 2 === 0; return t.fn ? s ? t.fn(this) : t.inverse(this) : s; }), r.registerHelper("isOdd", function(e, t) { const s = l(e) % 2 !== 0; return t.fn ? s ? t.fn(this) : t.inverse(this) : s; }); } function h(r) { return typeof r == "string" ? r : r == null ? "" : String(r); } const u = { uppercase(r) { return h(r).toUpperCase(); }, lowercase(r) { return h(r).toLowerCase(); }, capitalize(r) { const e = h(r); return e.charAt(0).toUpperCase() + e.slice(1).toLowerCase(); }, capitalizeFirst(r) { const e = h(r); return e.charAt(0).toUpperCase() + e.slice(1); }, titleCase(r) { return h(r).replace( /\w\S*/g, (e) => e.charAt(0).toUpperCase() + e.slice(1).toLowerCase() ); }, trim(r) { return h(r).trim(); }, truncate(r, e, t = "...") { const s = h(r); return s.length <= e ? s : s.slice(0, e - t.length) + t; }, ellipsis(r, e) { return u.truncate(r, e, "â€Ļ"); }, replace(r, e, t) { return h(r).split(e).join(t); }, remove(r, e) { return u.replace(r, e, ""); }, reverse(r) { return h(r).split("").reverse().join(""); }, repeat(r, e) { const t = Math.max(0, Math.floor(e)); return h(r).repeat(t); }, padStart(r, e, t = " ") { return h(r).padStart(e, t); }, padEnd(r, e, t = " ") { return h(r).padEnd(e, t); }, center(r, e) { const t = h(r), s = Math.max(0, e - t.length), n = Math.floor(s / 2), i = s - n; return " ".repeat(n) + t + " ".repeat(i); }, startsWith(r, e) { return h(r).startsWith(e); }, endsWith(r, e) { return h(r).endsWith(e); }, includes(r, e) { return h(r).includes(e); }, substring(r, e, t) { return h(r).substring(e, t); }, words(r, e) { const t = h(r).trim().split(/\s+/).filter((s) => s.length > 0); return typeof e == "number" ? t.slice(0, e) : t; }, wordCount(r) { return u.words(r).length; }, slugify(r) { return h(r).toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, ""); }, stripTags(r) { return h(r).replace(/<[^>]*>/g, ""); }, typewriter(r, e = 50) { const t = h(r); return `<typewriter speed="${e}">${t}</typewriter>`; }, nameTag(r) { const e = h(r).trim(); return e ? `<name>${e}</name>` : ""; }, dialogueFormat(r, e) { const t = h(r).trim(), s = h(e).trim(); return t ? `${u.nameTag(t)} ${s}` : s; }, parseMarkdown(r) { return h(r).replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>").replace(/\*(.*?)\*/g, "<em>$1</em>").replace(/__(.*?)__/g, "<u>$1</u>").replace(/~~(.*?)~~/g, "<del>$1</del>"); }, sanitizeInput(r) { return h(r).replace(/[<>\"'&]/g, (e) => ({ "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;", "&": "&amp;" })[e] || e).trim(); }, colorText(r, e) { const t = h(r), s = h(e).trim(); return s ? `<color="${s}">${t}</color>` : t; } }; function L(r) { r.registerHelper("uppercase", (e) => u.uppercase(e)), r.registerHelper("lowercase", (e) => u.lowercase(e)), r.registerHelper("capitalize", (e) => u.capitalize(e)), r.registerHelper("capitalizeFirst", (e) => u.capitalizeFirst(e)), r.registerHelper("titleCase", (e) => u.titleCase(e)), r.registerHelper("trim", (e) => u.trim(e)), r.registerHelper("truncate", (e, t, s) => u.truncate(e, t, s)), r.registerHelper("ellipsis", (e, t) => u.ellipsis(e, t)), r.registerHelper("replace", (e, t, s) => u.replace(e, t, s)), r.registerHelper("remove", (e, t) => u.remove(e, t)), r.registerHelper("reverse", (e) => u.reverse(e)), r.registerHelper("repeat", (e, t) => u.repeat(e, t)), r.registerHelper("padStart", (e, t, s) => u.padStart(e, t, s)), r.registerHelper("padEnd", (e, t, s) => u.padEnd(e, t, s)), r.registerHelper("center", (e, t) => u.center(e, t)), r.registerHelper("startsWith", function(e, t, s) { const n = u.startsWith(e, t); return s.fn ? n ? s.fn(this) : s.inverse(this) : n; }), r.registerHelper("endsWith", function(e, t, s) { const n = u.endsWith(e, t); return s.fn ? n ? s.fn(this) : s.inverse(this) : n; }), r.registerHelper("includes", function(e, t, s) { const n = u.includes(e, t); return s.fn ? n ? s.fn(this) : s.inverse(this) : n; }), r.registerHelper("substring", (e, t, s) => u.substring(e, t, s)), r.registerHelper("words", (e, t) => u.words(e, t)), r.registerHelper("wordCount", (e) => u.wordCount(e)), r.registerHelper("slugify", (e) => u.slugify(e)), r.registerHelper("stripTags", (e) => u.stripTags(e)), r.registerHelper("typewriter", (e, t) => u.typewriter(e, t)), r.registerHelper("nameTag", (e) => u.nameTag(e)), r.registerHelper("dialogueFormat", (e, t) => u.dialogueFormat(e, t)), r.registerHelper("parseMarkdown", (e) => u.parseMarkdown(e)), r.registerHelper("sanitizeInput", (e) => u.sanitizeInput(e)), r.registerHelper("colorText", (e, t) => u.colorText(e, t)), r.registerHelper("charAt", (e, t) => h(e).charAt(Math.floor(t))); } let y = null; function U(r) { y = r; } function R(r, e) { if (!(!r || !e || typeof e != "string")) return e.split(".").reduce((t, s) => t != null && typeof t == "object" && s in t ? t[s] : void 0, r); } function _(r, e, t) { if (!r || !e || typeof e != "string") return; const s = e.split("."); let n = r; for (let i = 0; i < s.length - 1; i++) { const a = s[i]; (n[a] == null || typeof n[a] != "object" || Array.isArray(n[a])) && (n[a] = {}), n = n[a]; } n[s[s.length - 1]] = t; } function b(r, e = 0) { if (typeof r == "number" && !isNaN(r) && isFinite(r)) return r; const t = Number(r); return isNaN(t) || !isFinite(t) ? e : t; } function B(r) { if (r == null) return ""; if (typeof r == "string") return r; try { return String(r); } catch { return ""; } } const v = { hasFlag(r, e) { return !r || typeof r != "string" ? !1 : y ? y.hasStoryFlag(r) : Array.isArray(e.storyFlags) && e.storyFlags.includes(r); }, addFlag(r, e) { !r || typeof r != "string" || (y && y.setStoryFlag(r), Array.isArray(e.storyFlags) || (e.storyFlags = []), e.storyFlags.includes(r) || e.storyFlags.push(r)); }, removeFlag(r, e) { if (!r || typeof r != "string" || (y && y.clearStoryFlag(r), !Array.isArray(e.storyFlags))) return; const t = e.storyFlags.indexOf(r); t > -1 && e.storyFlags.splice(t, 1); }, toggleFlag(r, e) { return !r || typeof r != "string" ? !1 : v.hasFlag(r, e) ? (v.removeFlag(r, e), !1) : (v.addFlag(r, e), !0); }, getVar(r, e = void 0, t) { if (!r || typeof r != "string") return e; if (y) { const s = y.getVariable(r); if (s !== void 0) return s; } if (t != null && t.variables) { const s = R(t.variables, r); return s !== void 0 ? s : e; } return e; }, setVar(r, e, t) { !r || typeof r != "string" || !t || (y && y.setVariable(r, e), (!t.variables || typeof t.variables != "object") && (t.variables = {}), _(t.variables, r, e)); }, hasVar(r, e) { return !r || typeof r != "string" ? !1 : y ? y.getVariable(r) !== void 0 : e != null && e.variables ? R(e.variables, r) !== void 0 : !1; }, incrementVar(r, e, t) { if (!r || typeof r != "string" || !t) return; const s = b(v.getVar(r, 0, t)), n = b(e), i = s + n; v.setVar(r, i, t); }, playerChose(r, e, t) { return !r || typeof r != "string" ? !1 : y ? y.playerChose(r, e) : t != null && t.choiceHistory && Array.isArray(t.choiceHistory) ? t.choiceHistory.some((s) => { if (!s || typeof s != "object") return !1; const n = s.choiceText === r, i = !e || s.scene === e; return n && i; }) : !1; }, getLastChoice(r) { if (y) { const e = y.getChoiceHistory(); if (e.length > 0) return e[e.length - 1]; } if (r != null && r.choiceHistory && Array.isArray(r.choiceHistory) && r.choiceHistory.length > 0) return r.choiceHistory[r.choiceHistory.length - 1]; }, choiceCount(r) { return y ? y.getChoiceHistory().length : r != null && r.choiceHistory && Array.isArray(r.choiceHistory) ? r.choiceHistory.length : 0; }, formatTime(r) { const e = Math.floor(b(r)); if (e < 0) return "0m"; if (e < 60) return `${e}m`; const t = Math.floor(e / 60), s = e % 60; return s > 0 ? `${t}h ${s}m` : `${t}h`; }, randomBool(r = 0.5) { const e = b(r, 0.5), t = Math.max(0, Math.min(1, e)); return Math.random() < t; }, exportFlags(r) { if (y) { const e = y.getState(); return Array.from(e.storyFlags); } return r != null && r.storyFlags && Array.isArray(r.storyFlags) ? r.storyFlags.filter((e) => typeof e == "string" && e.length > 0) : []; }, exportVariables(r) { if (y) { const e = y.getState(); return Object.fromEntries(e.variables.entries()); } if (r != null && r.variables && typeof r.variables == "object") try { return JSON.parse(JSON.stringify(r.variables)); } catch { const e = {}; for (const [t, s] of Object.entries(r.variables)) try { JSON.stringify(s), e[t] = s; } catch { e[t] = "[Non-serializable value]"; } return e; } return {}; } }; function D(r) { if (!r || typeof r.registerHelper != "function") { console.warn("Invalid Handlebars instance provided to registerVNHelpers"); return; } try { r.registerHelper("setVar", function(e, t, s) { var n; try { const i = (n = s == null ? void 0 : s.data) == null ? void 0 : n.root; return i && v.setVar(e, t, i), ""; } catch (i) { return console.warn("Error in setVar helper:", i), ""; } }), r.registerHelper("getVar", function(e, t, s) { var n; try { typeof t == "object" && (t != null && t.fn) && (s = t, t = ""); const i = (n = s == null ? void 0 : s.data) == null ? void 0 : n.root, a = v.getVar(e, t, i); if (typeof a == "string" || typeof a == "number" || typeof a == "boolean") return a; if (Array.isArray(a)) return a.join(", "); if (typeof a == "object" && a !== null) try { return JSON.stringify(a); } catch { return String(a); } return String(a); } catch (i) { return console.warn("Error in getVar helper:", i), t || ""; } }), r.registerHelper("hasVar", function(e, t) { var s; try { const n = (s = t == null ? void 0 : t.data) == null ? void 0 : s.root, i = v.hasVar(e, n); return t.fn ? i ? t.fn(n) : t.inverse(n) : i; } catch (n) { return console.warn("Error in hasVar helper:", n), !1; } }), r.registerHelper("hasFlag", function(e, t) { var s; try { const n = (s = t == null ? void 0 : t.data) == null ? void 0 : s.root, i = v.hasFlag(e, n); return t.fn ? i ? t.fn(n) : t.inverse(n) : i; } catch (n) { return console.warn("Error in hasFlag helper:", n), !1; } }), r.registerHelper("addFlag", function(e, t) { var s; try { const n = (s = t == null ? void 0 : t.data) == null ? void 0 : s.root; return n && v.addFlag(e, n), ""; } catch (n) { return console.warn("Error in addFlag helper:", n), ""; } }), r.registerHelper("removeFlag", function(e, t) { var s; try { const n = (s = t == null ? void 0 : t.data) == null ? void 0 : s.root; return n && v.removeFlag(e, n), ""; } catch (n) { return console.warn("Error in removeFlag helper:", n), ""; } }), r.registerHelper("toggleFlag", function(e, t) { var s; try { const n = (s = t == null ? void 0 : t.data) == null ? void 0 : s.root; if (!n) return !1; const i = v.toggleFlag(e, n); return t.fn ? i ? t.fn(n) : t.inverse(n) : i; } catch (n) { return console.warn("Error in toggleFlag helper:", n), !1; } }), r.registerHelper("incrementVar", function(e, t, s) { var n; try { const i = (n = s == null ? void 0 : s.data) == null ? void 0 : n.root; return i ? v.incrementVar(e, t, i) : 0; } catch (i) { return console.warn("Error in incrementVar helper:", i), 0; } }), r.registerHelper("playerChose", function(...e) { var t; try { const s = e[e.length - 1], n = e[0], i = e.length > 2 ? e[1] : void 0, a = (t = s == null ? void 0 : s.data) == null ? void 0 : t.root; if (!a) return !1; const o = v.playerChose(n, i, a); return s.fn ? o ? s.fn(a) : s.inverse(a) : o; } catch (s) { return console.warn("Error in playerChose helper:", s), !1; } }), r.registerHelper("getLastChoice", function(e) { var t; try { const s = (t = e == null ? void 0 : e.data) == null ? void 0 : t.root; return s ? v.getLastChoice(s) : void 0; } catch (s) { console.warn("Error in getLastChoice helper:", s); return; } }), r.registerHelper("choiceCount", function(e) { var t; try { const s = (t = e == null ? void 0 : e.data) == null ? void 0 : t.root; return s ? v.choiceCount(s) : 0; } catch (s) { return console.warn("Error in choiceCount helper:", s), 0; } }), r.registerHelper("formatTime", (e) => { try { return v.formatTime(e); } catch (t) { return console.warn("Error in formatTime helper:", t), "0m"; } }), r.registerHelper("randomBool", function(e, t) { try { typeof e == "object" && (e != null && e.fn) && (t = e, e = 0.5); const s = v.randomBool(e); return t != null && t.fn ? s ? t.fn(this) : t.inverse(this) : s; } catch (s) { return console.warn("Error in randomBool helper:", s), !1; } }), r.registerHelper("debug", function(e, t, s) { try { t && typeof t == "object" && "fn" in t && (s = t, t = "DEBUG"); const n = B(t || "DEBUG"); if (console.log(`🐛 ${n}:`, e), e && typeof e == "object") if (console.log(" Type:", Array.isArray(e) ? "Array" : "Object"), Array.isArray(e)) console.log(" Length:", e.length); else try { console.log(" Keys:", Object.keys(e)); } catch { console.log(" Keys: [Unable to enumerate]"); } return ""; } catch (n) { return console.warn("Error in debug helper:", n), ""; } }), r.registerHelper("timestamp", () => { try { return Date.now(); } catch (e) { return console.warn("Error in timestamp helper:", e), 0; } }), r.registerHelper("currentDate", () => { try { return (/* @__PURE__ */ new Date()).toLocaleDateString(); } catch (e) { return console.warn("Error in currentDate helper:", e), ""; } }), r.registerHelper("currentTime", () => { try { return (/* @__PURE__ */ new Date()).toLocaleTimeString(); } catch (e) { return console.warn("Error in currentTime helper:", e), ""; } }); } catch (e) { console.error("Failed to register VN helpers:", e); } } const S = { hasAsset(r, e) { if (!Array.isArray(e)) return !1; const t = S.normalizeKey(r); return e.some((s) => s ? [ s.id === r, s.id === t, s.name === r, s.path === r, S.normalizeKey(s.name || "") === t, S.normalizeKey(s.path || "") === t ].some(Boolean) : !1); }, getAsset(r, e) { if (!Array.isArray(e)) return null; const t = S.normalizeKey(r); return e.find((s) => s ? s.id === r || s.id === t || s.name === r || s.path === r || S.normalizeKey(s.name || "") === t || S.normalizeKey(s.path || "") === t : !1) || null; }, resolveAsset(r, e) { const t = S.getAsset(r, e); return t && (t.data || t.url || t.path || t.src) || null; }, getMediaType(r) { var t; if (!r) return "unknown"; const e = ((t = r.split(".").pop()) == null ? void 0 : t.toLowerCase()) || ""; return ["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp"].includes(e) ? "image" : ["mp3", "wav", "ogg", "m4a", "aac", "flac"].includes(e) ? "audio" : ["mp4", "webm", "avi", "mov", "wmv", "flv"].includes(e) ? "video" : "unknown"; }, getAssetInfo(r, e) { const t = S.getAsset(r, e); return t ? { type: S.getMediaType(t.name || t.path || ""), size: t.size, name: t.name || t.path } : null; }, validateAsset(r, e) { return S.resolveAsset(r, e) !== null; }, assetCount(r) { return Array.isArray(r) ? r.length : 0; }, formatFileSize(r) { if (typeof r != "number" || r === 0) return "0 B"; const e = 1024, t = ["B", "KB", "MB", "GB"], s = Math.floor(Math.log(r) / Math.log(e)); return parseFloat((r / Math.pow(e, s)).toFixed(1)) + " " + t[s]; }, normalizeKey(r) { return r ? r.replace(/\.[^/.]+$/, "").toLowerCase().replace(/[^a-z0-9]/g, "_") : ""; } }; function P(r) { r.registerHelper("hasAsset", function(e, t, s) { const n = S.hasAsset(e, t); return s.fn ? n ? s.fn(this) : s.inverse(this) : n; }), r.registerHelper("getAsset", S.getAsset), r.registerHelper("resolveAsset", S.resolveAsset), r.registerHelper("getAssetInfo", S.getAssetInfo), r.registerHelper("getMediaType", S.getMediaType), r.registerHelper("normalizeKey", S.normalizeKey), r.registerHelper("assetCount", S.assetCount), r.registerHelper("formatFileSize", S.formatFileSize), r.registerHelper("validateAsset", function(e, t, s) { const n = S.validateAsset(e, t); return s.fn ? n ? s.fn(this) : s.inverse(this) : n; }), r.registerHelper("showImage", function(e, t, s, n) { const i = S.resolveAsset(e, t); if (!i) return ""; const a = typeof s == "string" ? s : e, o = typeof n == "string" ? ` class="${n}"` : ""; return new r.SafeString(`<img src="${i}" alt="${a}"${o}>`); }), r.registerHelper("playAudio", function(e, t, s = !1, n = !1) { const i = S.resolveAsset(e, t); if (!i) return ""; const a = s ? " autoplay" : "", o = n ? " loop" : ""; return new r.SafeString(`<audio src="${i}" controls${a}${o}></audio>`); }), r.registerHelper("playVideo", function(e, t, s = !1, n = !1, i) { const a = S.resolveAsset(e, t); if (!a) return ""; const o = s ? " autoplay" : "", g = n ? " loop" : "", c = typeof i == "string" ? ` class="${i}"` : ""; return new r.SafeString(`<video src="${a}" controls${o}${g}${c}></video>`); }); } const G = { array: m, comparison: p, math: f, string: u, vn: v, asset: S }; function M(r) { if (!r || typeof r.registerHelper != "function") { console.warn("âš ī¸ Invalid Handlebars instance provided to registerAllHelpers"); return; } try { z(r), O(r), j(r), L(r), D(r), P(r); } catch (e) { throw console.error("❌ Error registering VN Engine helpers:", e), e; } } function ae(r) { if (r && typeof r.registerHelper == "function") try { return M(r), { registered: !0, engine: "handlebars", message: "All helpers registered with Handlebars" }; } catch (e) { return { registered: !1, engine: "handlebars", message: `Failed to register helpers: ${e instanceof Error ? e.message : "Unknown error"}` }; } else return { registered: !1, engine: "simple", message: "Handlebars not available - using simple template engine without helpers" }; } function oe() { return { handlebarsRequired: [ "first", "last", "length", "includes", "isEmpty", "where", "pluck", "join", "groupBy", "chunk", "unique", "shuffle", "slice", "take", "sample", "flatten", "reverse", "compact", "without", "randomChoice", "weightedChoice", "array", "range", "times", "eq", "ne", "gt", "gte", "lt", "lte", "and", "or", "not", "contains", "isEmpty", "compare", "between", "ifx", "ternary", "coalesce", "default", "add", "subtract", "multiply", "divide", "mod", "abs", "min", "max", "round", "ceil", "floor", "random", "randomInt", "clamp", "sum", "average", "percentage", "statCheck", "rollDice", "lerp", "normalize", "formatNumber", "uppercase", "lowercase", "capitalize", "titleCase", "trim", "truncate", "replace", "repeat", "padStart", "padEnd", "center", "substring", "words", "wordCount", "slugify", "typewriter", "nameTag", "dialogueFormat", "parseMarkdown", "colorText", "hasFlag", "getVar", "setVar", "hasVar", "playerChose", "formatTime", "randomBool", "debug", "hasAsset", "getAsset", "resolveAsset", "getAssetInfo", "getMediaType", "normalizeKey", "assetCount", "formatFileSize", "validateAsset", "showImage", "playAudio", "playVideo" ], standalone: [ "All helper functions are available as JavaScript functions in the helpers object", "Use helpers.array.first(), helpers.math.add(), helpers.string.capitalize(), helpers.asset.hasAsset(), etc." ], description: "When Handlebars is not available, all helper functions can still be used directly from the helpers object in JavaScript code." }; } class W { constructor() { this.handlebars = null, this.isHandlebarsAvailable = !1, this.helpersRegistered = !1, this.isTestMode = !1, this.gameStateManager = null, this.simpleEngine = new F(), this.isTestMode = typeof process < "u" && process.argv.some((e) => e.includes("test")); } setGameStateManager(e) { this.gameStateManager = e, U(e); } async initialize() { await this.detectHandlebars(), this.isHandlebarsAvailable && await this.setupHelpers(); } async detectHandlebars() { try { if (typeof require < "u") try { const t = require("handlebars"); if (this.handlebars = t.default || t, this.handlebars && typeof this.handlebars.create == "function") { this.handlebars = this.handlebars.create(), this.isHandlebarsAvailable = !0, this.isTestMode || console.log("✅ Handlebars detected (sync) - Full template functionality available"); return; } } catch { } const e = await import("handlebars"); if (this.handlebars = e.default || e, this.handlebars && typeof this.handlebars.create == "function") this.handlebars = this.handlebars.create(), this.isHandlebarsAvailable = !0, this.isTestMode || console.log("✅ Handlebars detected (async) - Full template functionality available"); else throw new Error("Invalid Handlebars module"); } catch { this.isTestMode || console.log("â„šī¸ Handlebars not available - Using simple template engine"), this.isHandlebarsAvailable = !1; } } async setupHelpers() { if (!(!this.isHandlebarsAvailable || this.helpersRegistered)) try { if (M && this.handlebars) M(this.handlebars), this.helpersRegistered = !0, this.isTestMode || console.log("✅ VN Engine helpers registered with Handlebars"); else throw new Error("registerAllHelpers function not available"); } catch (e) { this.isTestMode || console.warn("Failed to register VN Engine helpers:", e); } } render(e, t) { return this.isHandlebarsAvailable && this.handlebars && this.helpersRegistered ? this.renderWithHandlebars(e, t) : this.simpleEngine.render(e, t); } renderStrict(e, t) { return this.isHandlebarsAvailable && this.handlebars && this.helpersRegistered ? this.handlebars.compile(e, { strict: !0, noEscape: !1, compat: !0 })(t) || "" : this.simpleEngine.render(e, t); } renderWithHandlebars(e, t) { try { return this.handlebars.compile(e, { strict: !1, noEscape: !1, compat: !0 })(t) || ""; } catch (s) { if (this.isTestMode || console.error("Handlebars template rendering error:", s), s.message.includes("Missing helper")) { const n = s.message.match(/Missing helper: "([^"]+)"/); if (n) return `[Missing Helper: ${n[1]}]`; } return `[Template Error: ${s.message}]`; } } getEngineInfo() { return this.isHandlebarsAvailable ? { type: "handlebars", isHandlebarsAvailable: !0, helpersRegistered: this.helpersRegistered, supportedFeatures: { variables: !0, conditionals: !0, helpers: this.helpersRegistered, loops: !0, partials: !0 } } : { type: "simple", isHandlebarsAvailable: !1, helpersRegistered: !1, supportedFeatures: this.simpleEngine.getSupportedFeatures() }; } registerHelper(e, t) { return this.isHandlebarsAvailable && this.handlebars ? (this.handlebars.registerHelper(e, t), !0) : (this.isTestMode || console.warn(`Cannot register helper "${e}" - Handlebars not available`), !1); } getHandlebarsInstance() { return this.isHandlebarsAvailable ? this.handlebars : null; } validateTemplate(e) { const t = this.getEngineInfo(); try { return this.isHandlebarsAvailable && this.handlebars ? (this.handlebars.compile(e), { valid: !0, engine: "handlebars", supportedFeatures: Object.keys(t.supportedFeatures).filter( (s) => t.supportedFeatures[s] ) }) : (this.simpleEngine.render(e, { storyFlags: [], variables: {}, choiceHistory: [], computed: { gameTime: "00:00", hasFlag: () => !1, getVar: () => "", playerChose: () => !1 } }), { valid: !0, engine: "simple", supportedFeatures: Object.keys(t.supportedFeatures).filter( (s) => t.supportedFeatures[s] ) }); } catch (s) { return { valid: !1, error: s.message, engine: this.isHandlebarsAvailable ? "handlebars" : "simple", supportedFeatures: [] }; } } supportsFeature(e) { return this.getEngineInfo().supportedFeatures[e]; } isReady() { return !this.isHandlebarsAvailable || this.helpersRegistered; } } class J { constructor(e, t) { this.gameState = e, this.templateManager = t, this.scenes = /* @__PURE__ */ new Map(), this.currentScene = "", this.currentInstructionIndex = 0, this.pendingChoices = null; } loadScenes(e) { this.scenes.clear(), e.forEach((t) => { this.scenes.set(t.name, t); }); } startScene(e, t = 0) { const s = this.scenes.get(e); return s ? (this.currentScene = e, this.currentInstructionIndex = t, this.pendingChoices = null, this.gameState.setCurrentScene(e), this.gameState.setCurrentInstruction(t), t >= s.instructions.length ? { type: "scene_complete" } : this.executeCurrentInstruction()) : { type: "error", error: `Scene "${e}" not found` }; } continue() { return this.pendingChoices ? { type: "error", error: "Cannot continue while choices are pending. Make a choice first." } : (this.currentInstructionIndex++, this.gameState.setCurrentInstruction(this.currentInstructionIndex), this.executeCurrentInstruction()); } makeChoice(e) { if (!this.pendingChoices) return { type: "error", error: "No choices are currently available" }; if (e == null || typeof e != "number" || isNaN(e)) return { type: "error", error: "Choice index must be a valid number" }; if (e < 0 || e >= this.pendingChoices.length) return { type: "error", error: `Invalid choice index: ${e}` }; const t = this.pendingChoices[e], s = { scene: this.currentScene, instruction: this.currentInstructionIndex, choiceIndex: e, choiceText: t.text, timestamp: Date.now() }; if (this.gameState.addChoice(s), t.actions && this.executeActions(t.actions), t.goto) { this.pendingChoices = null; const n = this.renderTemplate(t.goto); return this.startScene(n); } return this.pendingChoices = null, this.currentInstructionIndex++, this.gameState.setCurrentInstruction(this.currentInstructionIndex), this.executeCurrentInstruction(); } executeCurrentInstruction() { const e = this.scenes.get(this.currentScene); if (!e) return { type: "error", error: `Current scene "${this.currentScene}" not found` }; if (this.currentInstructionIndex >= e.instructions.length) return { type: "scene_complete" }; const t = e.instructions[this.currentInstructionIndex]; return this.executeInstruction(t); } executeInstruction(e) { try { switch (e.type) { case "dialogue": return this.executeDialogue(e); case "action": return this.executeActionInstruction( e ); case "conditional": return this.executeConditional(e); case "jump": return this.executeJump(e); default: return { type: "error", error: `Unknown instruction type: ${e.type}` }; } } catch (t) { return { type: "error", error: `Execution error: ${t.message}` }; } } executeDialogue(e) { if (e.actions && this.executeActions(e.actions), e.choices) { const t = this.filterAvailableChoices(e.choices); return t.length === 0 ? (this.currentInstruct