//-----------------------------------------------------------------------------
// Scene
//-----------------------------------------------------------------------------
/**
* @class RezScene
* @extends RezBasicObject
* @category Elements
* @description Represents a scene in the Rez game engine. Scenes are containers that manage
* the flow of cards and content, handling transitions between different parts of the game
* narrative.
*
* ## Scene Lifecycle
* Scenes follow a defined lifecycle:
* 1. **start** - Scene is initialized and begins playing its initial card
* 2. **ready** - Scene is fully rendered and ready for player interaction
* 3. **interrupt** - Scene is paused for an interlude (optional)
* 4. **resume** - Scene continues after an interlude (optional)
* 5. **finish** - Scene completes and is reset
*
* ## Layout Modes
* Scenes support two layout modes that control how cards are displayed:
* - **single** - Each new card replaces the previous one (default)
* - **stack** - Cards stack on top of each other; finished cards flip to show their back
*
* ## Card Management
* Scenes manage the current card and coordinate card transitions:
* - `initial_card` - The first card played when the scene starts
* - `current_card` - The currently active card
* - `last_card_id` - ID of the previously active card
* - Cards are wrapped in RezBlock instances and added to the scene's view layout
*
* ## Interludes
* Scenes can be interrupted for interludes - temporary scene switches that return
* to the original scene. The game maintains a scene stack for this purpose:
* - `interrupt()` - Called when an interlude begins
* - `resume(params)` - Called when returning from an interlude
*
* ## Event Handlers
* Scenes can define event handlers using the `on_<event>` attribute naming convention:
* - `on_start` - Called when the scene starts
* - `on_ready` - Called after the scene is rendered
* - `on_start_card` - Called when a new card begins
* - `on_finish_card` - Called when a card finishes
* - `on_interrupt` - Called when an interlude begins
* - `on_resume` - Called when returning from an interlude
* - `on_finish` - Called when the scene ends
*/
class RezScene extends RezBasicObject {
/**
* @function constructor
* @memberof RezScene#
* @param {string} id - unique identifier for this scene
* @param {object} attributes - scene attributes from Rez compilation
* @description Creates a new scene instance and initializes it to a reset state
*/
constructor(id, attributes) {
super("scene", id, attributes);
this.reset();
}
/**
* @function isStackLayout
* @memberof RezScene#
* @returns {boolean} true if this scene uses stack layout mode
* @description Determines if this scene stacks cards on top of each other (stack mode)
* or replaces the current card with each new one (single mode)
*/
get isStackLayout() {
return this.layout_mode === "stack";
}
/**
* @function current_block
* @memberof RezScene#
* @returns {RezLayout} the current view layout for this scene
* @description Returns the view layout that manages how content is displayed in this scene
*/
get current_block() {
return this.getViewLayout();
}
/**
* @function bindAs
* @memberof RezScene#
* @returns {string} "scene"
* @description Returns the binding identifier for template rendering
*/
bindAs() {
return "scene";
}
/**
* @function getViewTemplate
* @memberof RezScene#
* @param {boolean} flipped - ignored for scenes (only cards can be flipped)
* @returns {*} the template used to render this scene's layout
* @description Returns the layout template for rendering this scene. The flipped parameter is ignored since scenes cannot be flipped.
*/
getViewTemplate(_flipped) {
// Scenes can't be flipped, only cards
return this.$layout_template;
}
/**
* @function getViewLayout
* @memberof RezScene#
* @returns {RezLayout} the view layout instance for this scene
* @description Gets or creates the view layout for this scene. The layout is cached and reused.
*/
getViewLayout() {
this.$viewLayout = this.$viewLayout ?? this.createViewLayout();
return this.$viewLayout;
}
/**
* @function createViewLayout
* @memberof RezScene#
* @returns {RezStackLayout|RezSingleLayout} the appropriate layout instance
* @description Creates a new view layout based on the scene's layout mode.
* Returns RezStackLayout for stack mode or RezSingleLayout for single mode.
*/
createViewLayout() {
if(this.isStackLayout) {
return new RezStackLayout("scene", this);
} else {
return new RezSingleLayout("scene", this);
}
}
/**
* @function playCardWithId
* @memberof RezScene#
* @param {string} cardId - ID of the card to play
* @param {object} params - parameters to pass to the card
* @description Plays a card by looking it up by ID and calling playCard with the card instance
*/
playCardWithId(cardId, params = {}) {
this.playCard($t(cardId, "card", true), params);
}
/**
* @function playCard
* @memberof RezScene#
* @param {RezCard} newCard - the card instance to play
* @param {object} params - parameters to pass to the card
* @description Transitions to a new card, finishing the current card if any, starting the new one,
* updating the view, and triggering the card's ready event.
*/
playCard(newCard, params = {}) {
this.finishCurrentCard();
this.startNewCard(newCard, params);
}
/**
* @function finishCurrentCard
* @memberof RezScene#
* @description Finishes the currently active card by running its finish event,
* triggering the scene's finish_card event, and in stack layout mode, flipping the card.
*/
finishCurrentCard() {
if(this.current_card) {
this.current_card.runEvent("finish", {});
this.runEvent("card_did_finish", {card_id: this.current_card.id});
this.game.runEvent("card_did_finish", {card_id: this.current_card.id});
if(this.isStackLayout) {
this.current_card.current_block.flipped = true;
}
this.last_card_id = this.current_card_id;
this.current_card_id = "";
}
}
/**
* @function startNewCard
* @memberof RezScene#
* @param {RezCard} card - the card to start
* @param {object} params - parameters to pass to the card
* @description Sets up a new card as the current card, adds it to the view layout,
* and triggers the appropriate start events.
*/
startNewCard(card, params = {}) {
this.game.runEvent("card_will_start", {card_id: card.id, params: params});
this.runEvent("card_will_start", {card_id: card.id, params: params});
card.runEvent("will_start", params);
card.scene = this;
this.current_card = card;
this.addContentToViewLayout(params);
this.game.updateView();
this.current_card.runEvent("did_start", params);
this.runEvent("card_did_start", {card_id: card.id, params: params});
this.game.runEvent("card_did_start", {card_id: card.id, params: params});
}
/**
* @function resumeFromLoad
* @memberof RezScene#
* @description Resumes the scene after loading from a saved game state.
* Ensures the current card is properly restored to the view layout.
* @throws {Error} if no current card is available to resume
*/
resumeFromLoad() {
if(!(this.current_card instanceof RezCard)) {
throw new Error("Attempting to resume scene after reload but there is no current card!");
}
this.addContentToViewLayout({});
}
/**
* @function addContentToViewLayout
* @memberof RezScene#
* @param {object} params - parameters to pass to the content block
* @description Creates a new content block for the current card and adds it to the scene's view layout
*/
addContentToViewLayout(params = {}) {
const block = new RezBlock("card", this.current_card, params);
this.current_card.current_block = block;
this.getViewLayout().addContent(block);
}
/**
* @function reset
* @memberof RezScene#
* @description Resets the scene to its initial state, clearing the current card,
* view layout, and running status
*/
reset() {
this.current_card_id = "";
this.$viewLayout = null;
this.$running = false;
}
/**
* @function interrupt
* @memberof RezScene#
* @description Interrupts the current scene execution, typically when switching to an interlude scene.
* Triggers the scene's interrupt event.
*/
interrupt() {
console.log(`Interrupting scene |${this.id}|`);
this.runEvent("interrupt", {});
}
/**
* @function resume
* @memberof RezScene#
* @param {object} params - parameters passed from the interlude scene
* @description Resumes the scene after an interlude, triggering the scene's resume event.
* Note: Card resume/ready events are fired by RezGame.resumePrevScene after the view is updated.
*/
resume(params = {}) {
console.log(`Resuming scene |${this.id}|`);
this.runEvent("resume", params);
}
/**
* @function start
* @memberof RezScene#
* @param {object} params - parameters to pass to the scene and initial card
* @description Starts the scene by initializing it, triggering the start event,
* setting the running state, and playing the initial card
*/
start(params = {}) {
this.runEvent("start", params);
this.setAttribute("$running", true);
this.playCard(this.initial_card, params);
}
/**
* @function ready
* @memberof RezScene#
* @description Triggers the scene's ready event, indicating the scene is fully initialized and ready for interaction
*/
ready() {
this.runEvent("ready", {});
}
/**
* @function finish
* @memberof RezScene#
* @description Finishes the scene by completing the current card, triggering the finish event,
* setting running state to false, and resetting the scene
*/
finish() {
this.finishCurrentCard();
this.runEvent("finish", {});
this.setAttribute("$running", false);
this.reset();
}
}
window.Rez.RezScene = RezScene;
Source