/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { Story } from 'inkjs/engine/Story'; import { Container } from 'inkjs/engine/Container'; /** * Returns metadata for a section at the given path. * Metadata are listed as tags, which can be created by Recital's ink parser, and are attached to * knots and stitches. * @param story * @param path the path to the knot / stitch in the format `knot.stitch`. If you just want to go to a knot, path should be `knot`. * @param recursive set to true if you want the parents' meta to percolate down to this section. * @returns */ export const getMetaForInkStoryPath = ( story: Story, path: string, recursive: boolean = false ): Record<string, any> => { const tags: string[] = [] if (recursive) { const splits = path.split(".") let pointer = "" for (let i = 0; i < splits.length - 1; i++) { pointer += splits[i] tags.push(...(story.TagsForContentAtPath(pointer) || [])) pointer += "." } } tags.push(...(story.TagsForContentAtPath(path) || [])) const meta: Record<string, any> = {} for (let tag of tags) { if (!tag.includes(":")) { continue } const splits = tag.split(":") try { meta[splits[0]] = JSON.parse(splits[1]) } catch (e) { if (!meta._) { meta._ = [] } meta._.push(tag); } } return meta } export const getContainerAtPathString = function (story: Story, path: string) { let container = story.mainContentContainer for (var name of path.split(".")) { if (!container.namedContent.has(name)) return null container = container.namedContent.get(name) as Container } return container } export const getAllKnots = (story: Story): string[] => { const list: string[] = [] const container = story.mainContentContainer const knotKeys = container.namedContent.keys() for (let knot of knotKeys) { list.push(knot) } return list } /** * Given a top-level knot, returns all the stitches **as paths** in the form `knot.stitch` * @param story * @param knotName * @returns The list of stitches, as their paths. */ export const getAllStitchesForKnot = (story: Story, knotName: string): string[] => { let list: string[] = [] let container = story.KnotContainerWithName(knotName) if (!container) { return list; } let stitchKeys = container.namedContent.keys() for (let stitch of stitchKeys) { list.push(`${knotName}.${stitch}`) } return list } export const arePrereqsMetForStorylet = (story: Story, storyletPath: string) => { const meta = getMetaForInkStoryPath(story, storyletPath) let prereqs = meta.PREREQS ?? [] for (let prereq of prereqs) { // this will cover all of the comparison operators. // see https://www.programiz.com/javascript/comparison-logical const variableName = prereq.split(/(\!|\=|\<|\>)/g)[0]?.trim() // TODO: it's really easy to introduce bugs with string prereqs since you need to write them as // "var === 'value'", with the single quote inside double quotes. // Maybe detect variable value, and see if we should automatically fix quotes? // or use JSON parse and call it a day. let variableValue = story.variablesState.$(variableName) const evaluate = Function(`"use strict"; let ${variableName} = ${variableValue ? JSON.stringify(variableValue) : "undefined"} return ${prereq}`) let result = evaluate() if (!result) { return false } } return true } /** * Sets variables in an ink story using thet Javascript parser. * @param story * @param operations A list of operations of the form `variable = <xyz>` in string form. */ export const performVarOperationsOnStory = (story: Story, operations: string[]) => { for (let operation of operations) { const variableName = operation.split(/(\+|\-|\*|\/|%|)?=/g)[0]?.trim() let variableValue = story.variablesState.$(variableName) const evaluate = Function(`"use strict"; let ${variableName} = ${variableValue ? JSON.stringify(variableValue) : "undefined"} ${operation} return ${variableName}`) let result = evaluate() story.variablesState[variableName] = result } }