frontispiece / packages / ink-processor / src / data / command.ts
command.ts
Raw
/*
 * 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 { CommandParams, InkEngine, VisualInkCommand, VisualInkState } from '../types'

/**
 * Commands are run when called in the story. Returns true if the command
 * should interrupt story flow, false otherwise.
 */
export type Command = (engine: InkEngine, params: CommandParams) => CommandReturn | undefined

export type CommandReturn = {
	interruptStoryFlow?: boolean
}

export type ExtendedCommandReturn = CommandReturn & {
	mutatedVisualState?: VisualInkState
}

export class CommandHandler {
	protected commandMap: Record<string, Command>

	constructor() {
		this.commandMap = {}
	}

	public addCommand(commandName: string, command: Command) {
		this.commandMap[commandName] = command
	}

	/**
	 * Accepts a command, and acts upon it for the current story.
	 * @param command The command to run
	 * @returns {boolean} Returns true if the command should interrupt story flow, false otherwise.
	 */
	public processCommand(
		command: VisualInkCommand,
		currentState?: VisualInkState,
		engine?: InkEngine,
	): ExtendedCommandReturn | undefined {
		const commandName = command.command.trim()
		if (!this.commandMap[commandName]) {
			return
		}

		// TODO: Ugh...this breaks so many conventions with immutable stuff, but it works
		// because we will be setting visualState to currentState regardless if we didn't mutate the visual state...
		// and this allows delays to still affect the engine.
		engine.visualState = currentState
		const returns: ExtendedCommandReturn =
			this.commandMap[commandName](engine, command.params) ?? {}

		if (engine.visualState !== currentState) {
			returns.mutatedVisualState = engine.visualState
		}

		return returns
	}
}