Live Scripting - Pt 5 - Event Handlers#

Gameplay Using Event Handlers#

Welcome to the final installment in our five-part series on custom scripting with Xsolla Backend. So far, we’ve learned how to work with the Script Manager extension for Visual Studio Code, extend the REST API, implement custom data types, and write background jobs for processing data.

Today, we’ll cover how to implement asynchronous and semi-realtime gameplay using event handlers. In the last article, we created a background service to regenerate a player’s health over time. In this part, we will react to certain events, such as when a player completes a quest or logs in, to trigger a full replenishment of their health.

Scripts and Samples#

Telemetry and Event System Overview#

Before we dive into the code, let’s take a closer look at the telemetry and event system.

The telemetry services system in Xsolla Backend is responsible for managing all event data. Game clients, servers, and backend services send events to track information, which can then be processed by event handlers in real time. Event handlers are the foundation of important gameplay features like questing and progression systems.

Each telemetry event contains common data as well as data specific to the event. For this example, we will focus on replenishing a character’s health and mana each time their user logs in or completes a quest.

Step 1: Creating the Event Handler#

  1. Open your test workspace in Visual Studio Code and create a new file called CharacterEvents.ts in the events folder.

[IMAGE: New script: events/CharacterEvents.ts]

  1. Add the following code to define the class and the configuration for max health and mana:

export default class CharacterEvents {
    @MongoRepository(Character)
    private repo?: Repository<Character>;

    @Config("characterStats:health:max", STAT_DEFAULTS_HEALTH.max)
    private maxHealth: number;

    @Config("characterStats:mana:max", STAT_DEFAULTS_MANA.max)
    private maxMana: number;
}

Step 2: Writing the Event Handler#

In Xsolla Backend, an event handler is any function decorated with @OnEvent. The function must accept a single argument containing the event data. For this example, we will handle the UserLogin event to replenish the character’s health and mana upon login:

@OnEvent("UserLogin")
private async regenHealthAndMana(evt: Event): Promise<void> {
    const character: Document | undefined = await this.repo.updateOne(
        { userUid: evt.userId },
        {
            $set: {
                health: this.maxHealth,
                mana: this.maxMana,
                dateModified: new Date(),
            },
            $inc: {
                version: 1,
            },
        });
}

In this code, we use the userId from the event to find the corresponding character and reset their health and mana. The $set operator updates the fields, while $inc increments the version number.

Step 3: Supporting Multiple Events#

Next, let’s expand the example to handle both UserLogin and QuestComplete events. Each time a player completes a quest, the QuestComplete event is generated, and we can reset the character’s stats just like we do with the login event.

We can modify the @OnEvent decorator to handle both events by passing an array of event types:

@OnEvent(["UserLogin", "QuestComplete"])
private async regenHealthAndMana(evt: Event): Promise<void> {
    const character: Character | undefined = await this.repo.updateOne(
        { userUid: evt.userId },
        {
            $set: {
                health: this.maxHealth,
                mana: this.maxMana,
                dateModified: new Date(),
            },
            $inc: {
                version: 1,
            }
        });
}

This allows the function to respond to both UserLogin and QuestComplete events.

Step 4: Using Regular Expressions#

For even more flexibility, you can use regular expressions in the @OnEvent decorator to match multiple events. The same functionality can be written using a regex:

@OnEvent("UserLogin|QuestComplete")
private async regenHealthAndMana(evt: Event): Promise<void> {
    const character: Document | undefined = await this.repo.updateOne(
        { userUid: evt.userId },
        {
            $set: {
                health: this.maxHealth,
                mana: this.maxMana,
                dateModified: new Date(),
            },
            $inc: {
                version: 1,
            }
        });
}

You can even define a catch-all event handler using @OnEvent(".*") to respond to any event, although this is generally not recommended due to the risk of creating infinite loops.

Final Code#

Your final code should look like this:

import { MongoRepository as Repo, Document } from "typeorm";
import { Event } from "@composer-js/core";
import { ObjectDecorators, DatabaseDecorators } from "@composer-js/service-core";
import { AXRObjectDecorators } from "@acceleratxr/service-core";
import Character from "../models/Character";
import { STAT_DEFAULTS_HEALTH, STAT_DEFAULTS_MANA } from "../jobs/CharacterRegen";
const { Config } = ObjectDecorators;
const { EventListener, OnEvent } = AXRObjectDecorators;
const { MongoRepository } = DatabaseDecorators;

@EventListener()
export default class CharacterEvents {
    @MongoRepository(Character)
    private repo?: Repo<Character>;

    @Config("characterStats:health:max", STAT_DEFAULTS_HEALTH.max)
    private maxHealth: number;

    @Config("characterStats:mana:max", STAT_DEFAULTS_MANA.max)
    private maxMana: number;

    @OnEvent(["UserLogin", "QuestComplete"])
    private async regenHealthAndMana(evt: Event): Promise<void> {
        const character: Document | undefined = await this.repo.updateOne(
            { userUid: evt.userId },
            {
                $set: {
                    health: this.maxHealth,
                    mana: this.maxMana,
                    dateModified: new Date(),
                },
                $inc: {
                    version: 1,
                },
            });
    }
}

Conclusion#

In this final part of our series, we explored how to implement asynchronous gameplay using event handlers in the Xsolla Backend Live Scripting System. You now know how to:

  • Respond to real-time events using the @OnEvent decorator.

  • Replenish a character’s health and mana based on user actions such as login or quest completion.

  • Use multiple event handlers and even regular expressions to handle different types of events.

With this knowledge, you’re ready to create dynamic gameplay experiences in your own project.

Congratulations on completing the series, go forth and build something great!