An action describes the functionality for a Monogatari statement. When Monogatari reads a part of the script (a statement), it will look for an action that matches the statement and run it.
The life cycle of an action is divided in three parts: Mounting, Application, and Reverting.
Mounting Cycle
The mounting cycle runs once when the engine initializes. It has 3 steps:
1. Setup
Here the action sets up everything it needs for working. This includes:
Registering state variables
Registering history objects
Adding HTML content to the document
static async setup(selector) {// Register a history for tracking applied actionsthis.engine.history('myaction');// Register state variablesthis.engine.state({ myActionActive:false });}
2. Bind
Once setup is complete, bind event listeners or perform DOM operations:
3. Init
Final initialization after setup and binding are complete:
Statement Matching
Before executing an action, Monogatari checks if the current statement matches. Actions must implement matching functions:
matchString
For string statements (most common). Receives the statement split into an array by spaces:
matchObject
For object/JSON statements:
Application Cycle
The Application cycle runs when an action is executed from the script.
1. Will Apply
Called before the action is applied. Use for pre-application checks or setup:
If this method throws or returns a rejected promise, the action will not be applied.
2. Apply
The core logic of the action. This is where the actual work happens:
3. Did Apply
Called after the action is applied. Use for cleanup, history updates, and determining if the game should advance:
Revert Cycle
The Revert cycle runs when the player goes back in the game.
1. Will Revert
Called before reverting. Check if reversion is possible:
If this method throws or returns a rejected promise, the action will not be reverted.
2. Revert
Undo the action's effects:
3. Did Revert
Called after reverting. Cleanup and determine navigation behavior:
Flow Control Methods
These static methods are called by the engine to check if the game can proceed or rollback:
shouldProceed
Called when the user clicks to advance or auto-play triggers:
willProceed
Called after shouldProceed passes, before advancing:
shouldRollback
Called when the user tries to go back:
willRollback
Called after shouldRollback passes, before reverting:
Event Methods
These static methods respond to game events:
onStart
Called when the player starts a new game:
onLoad
Called when a saved game is loaded. Restore visual/audio state:
onSave
Called when the game is saved:
reset
Called when a game ends or before loading a new game:
Hook Methods
These static methods allow intercepting action execution:
beforeRun / afterRun
Called before/after an action's application cycle:
beforeRevert / afterRevert
Called before/after an action's revert cycle:
Instance Methods
interrupt
Called to interrupt an ongoing action (e.g., skipping typewriter animation):
async willApply() {
// Check preconditions
if (!this.isValid()) {
throw new Error('Invalid action');
}
}
async apply() {
// Perform the action
const element = document.createElement('div');
element.textContent = this.content;
document.body.appendChild(element);
}
async didApply(options = {}) {
const { updateHistory = true, updateState = true } = options;
if (updateHistory) {
this.engine.history('myaction').push(this._statement);
}
if (updateState) {
this.engine.state({ myActionActive: true });
}
// Return whether to advance automatically
return {
advance: true // true = advance to next statement
// false = wait for user interaction
};
}
async willRevert() {
if (this.engine.history('myaction').length === 0) {
return Promise.reject('No history to revert');
}
}
async revert() {
// Remove the element we added
const element = document.querySelector('.my-element');
if (element) {
element.remove();
}
// Restore previous state from history
this.engine.history('myaction').pop();
}
async didRevert() {
return {
advance: true, // true = continue reverting
// false = stop here
step: true // true = move to previous step
// false = stay on current step
};
}
static async shouldProceed(options) {
const { userInitiated, skip, autoPlay } = options;
// Return resolved promise to allow proceeding
// Throw or reject to prevent proceeding
if (this.isBlocking) {
throw new Error('Action is blocking');
}
}
static async willProceed() {
// Perform any cleanup before advancing
}
static async shouldRollback() {
// Return resolved promise to allow rollback
// Throw or reject to prevent rollback
}
static async willRollback() {
// Prepare for rollback
}
static async onStart() {
// Initialize for new game
this.engine.state({ myActionActive: false });
}
static async onLoad() {
// Restore state from saved game
const state = this.engine.state('myActionActive');
if (state) {
// Re-apply visual elements
}
}
static async onSave(slot) {
// slot.key - The storage key
// slot.value - The saved data
// Perform any save-related operations
}
static async reset() {
// Clean up all action-related elements
document.querySelectorAll('.my-elements').forEach(el => el.remove());
// Reset state
this.engine.state({ myActionActive: false });
}
static async beforeRun(context) {
// Called before willApply/apply/didApply
}
static async afterRun(context) {
// Called after the application cycle completes
}
static async beforeRevert(context) {
// Called before willRevert/revert/didRevert
}
static async afterRevert(context) {
// Called after the revert cycle completes
}
async interrupt() {
// Stop ongoing animation or process
this.stopAnimation();
}
// Get the original statement
const statement = this._statement;
// Get current cycle ('Application' or 'Revert')
const cycle = this._cycle;
// Access extra context
const extras = this._extras;
// Access the engine
const engine = this.engine;
┌─────────────────────────────────────────────────────────────┐
│ MOUNTING CYCLE │
│ (runs once when engine initializes) │
│ │
│ setup() → bind() → init() │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ APPLICATION CYCLE │
│ (runs when action is executed from script) │
│ │
│ matchString/matchObject → constructor → │
│ willApply() → apply() → didApply() │
│ │
│ Flow: shouldProceed() → willProceed() → [next action] │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ REVERT CYCLE │
│ (runs when player goes back) │
│ │
│ willRevert() → revert() → didRevert() │
│ │
│ Flow: shouldRollback() → willRollback() → [previous action]│
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ EVENT METHODS │
│ (called by engine at specific moments) │
│ │
│ onStart() - New game started │
│ onLoad() - Game loaded from save │
│ onSave() - Game saved │
│ reset() - Game ended or new game loading │
└─────────────────────────────────────────────────────────────┘