Creating custom events

From JFTSE Wiki
Jump to navigation Jump to search

Scripted Event System

This page documents how to implement custom in-game event logic using the internal JavaScript scripting system.

All scripts are located in:

game-server/src/main/resources/scripts/event

Files are named as:

<ID>_<name>.js

Example:

  • 1_exampleEvent.js
  • 2_exampleEvent2.js
  • 3_weeklyLogin.js

Registering to an Event

Scripts are registered to server events via:

geb.on("ON_LOGIN", function(client) {
    // Runs on player login
});

Another example for match end:

geb.on("MP_MATCH_END", function(game, room, clients) {
    // Runs when match ends
});

The string refers to a constant from the GameEventType enum:

com.jftse.emulator.server.core.life.event.GameEventType

Script Environment

The following bindings are available in every script:

gameManager
serviceManager
threadManager
eventHandler
state
geb

These expose access to game services, event queues, persistent state, and event registration.

Using Persistent State

To save player-specific or global data across restarts:

let accountId = client.getAccountId();
let loginData = JSON.parse(state.get("loginTracker", accountId) || "{}");

loginData.lastLogin = new Date().toISOString();

state.set("loginTracker", accountId, JSON.stringify(loginData));

This allows tracking login streaks, cooldowns, event progress, and more.

Java Class Access

You can use Java.type(...) to access and work with server-side Java classes directly from your script. This allows you to integrate deeply with server logic, access player data, send packets, schedule tasks, and more.

Example: Give gold and send a chat message

let PlayerScriptableImpl = Java.type("com.jftse.emulator.server.core.interaction.PlayerScriptableImpl");

let helper = new PlayerScriptableImpl(client);
helper.giveGold(500);
helper.sendChat("System", "You received 500 gold!");

Example: Send a room chat packet manually

let S2CChatRoomAnswerPacket = Java.type("com.jftse.emulator.server.core.packets.chat.S2CChatRoomAnswerPacket");

let packet = new S2CChatRoomAnswerPacket(2, "Server", "Welcome!");
client.getConnection().sendTCP(packet);

Example: Access services via ServiceManager

You can access any backend service via the pre-bound serviceManager instance:

let playerService = serviceManager.getPlayerService();
let player = playerService.findById(playerId);

Example: Schedule delayed task with EventHandler

Use eventHandler.createRunnableEvent to run code after a delay (milliseconds):

let PlayerScriptableImpl = Java.type("com.jftse.emulator.server.core.interaction.PlayerScriptableImpl");
let helper = new PlayerScriptableImpl(client);

let event = eventHandler.createRunnableEvent(function () {
    helper.sendChat("System", "This message appears after 3 seconds!");
}, 3000);

eventHandler.offer(event);

Tips

  • Only use classes that exist in the current server (game-server or chat-server).
  • Prefer using available bindings like client, state, gameManager, serviceManager when possible.
  • Combine everything — services, packets, player APIs — to build your own gameplay features.

Example Scripts

These examples demonstrate how to respond to server events using the `geb.on(...)` method in JavaScript. Each file is named using the format `id_name.js` (e.g. `1_exampleEvent.js`) and is auto-loaded from the event script folder if placed there.

1_exampleEvent.js

Send a login greeting to a player using a helper class:

geb.on("ON_LOGIN", function(client) {
    let PlayerScriptableImpl = Java.type("com.jftse.emulator.server.core.interaction.PlayerScriptableImpl");
    let helper = new PlayerScriptableImpl(client);
    helper.sendChat("System", "Welcome back, player!");
});

2_exampleEvent2.js

Rewards gold and a message to every player in a finished multiplayer match:

geb.on("MP_MATCH_END", function(game, room, clients) {
    clients.forEach(function(client) {
        let PlayerScriptableImpl = Java.type("com.jftse.emulator.server.core.interaction.PlayerScriptableImpl");
        let helper = new PlayerScriptableImpl(client);
        helper.giveGold(200);
        helper.sendChat("Match", "You earned 200 gold for finishing!");
    });
});

3_weeklyLogin.js

Tracks login streaks using `state` storage (per account):

geb.on("ON_LOGIN", function (client) {
    const accountId = client.getAccountId();
    const playerId = client.getActivePlayerId();
    const today = new Date().toISOString().split('T')[0];

    if (!accountId || !playerId) return;

    let loginState;
    try {
        loginState = JSON.parse(state.get("weeklyLogin", accountId) || "{}");
    } catch (e) {
        loginState = {};
    }

    loginState.playerId = loginState.playerId || playerId;
    loginState.lastLoginDate = loginState.lastLoginDate || null;
    loginState.loginCount = loginState.loginCount || 0;

    if (loginState.lastLoginDate !== today) {
        loginState.lastLoginDate = today;
        loginState.loginCount += 1;

        state.set("weeklyLogin", accountId, JSON.stringify(loginState));

        let PlayerScriptableImpl = Java.type("com.jftse.emulator.server.core.interaction.PlayerScriptableImpl");
        let helper = new PlayerScriptableImpl(client);
        helper.sendChat("System", `You've logged in ${loginState.loginCount} time(s) this week!`);
    }
});

4_exampleDelayedEvent.js

Sends a delayed chat message 3 seconds after login:

geb.on("ON_LOGIN", function(client) {
    let PlayerScriptableImpl = Java.type("com.jftse.emulator.server.core.interaction.PlayerScriptableImpl");

    let event = eventHandler.createRunnableEvent(function () {
        let helper = new PlayerScriptableImpl(client);
        helper.sendChat("System", "This message was delayed by 3 seconds.");
    }, 3000);

    eventHandler.offer(event);
});

Event Types and Registration

To register for events, you call:

geb.on("EVENT_NAME", function(...) { ... });

Valid event names come from the enum:

com.jftse.emulator.server.core.life.event.GameEventType

Examples include:

  • ON_LOGIN
  • LOBBY_JOINED
  • MP_MATCH_END

Not all enum values are triggered yet. If an event you need doesn’t fire or seems unsupported:

  • Ask in Discord (`#event-creators-club`)
  • Provide the name you need (e.g. MP_PLAYER_HITS_TARGET)
  • Describe what data you'd want passed (e.g. client, score, game, etc.)

The dev team can wire up new events if they make sense.

Manually Triggering Events from Script

You can also call any other registered event manually inside your script using:

geb.call("EVENT_NAME", ...args);

This is useful for composing logic across multiple scripts, chaining behaviors, or invoking shared logic. The called event must have been previously registered using geb.on(...).

Example:

geb.on("ON_LOGIN", function(client) {
    geb.call("GREET_PLAYER", client);
});

geb.on("GREET_PLAYER", function(client) {
    let helper = new (Java.type("com.jftse.emulator.server.core.interaction.PlayerScriptableImpl"))(client);
    helper.sendChat("System", "Welcome!");
});

Tips

  • Use state.get(...) and state.set(...) to persist data per player, account, or globally
  • Validate input: check client, playerId, room, etc. before acting
  • Use eventHandler.createRunnableEvent(...) to delay code execution
let event = eventHandler.createRunnableEvent(function () {
    // delayed logic
}, 3000);
eventHandler.offer(event);
  • You can access Java classes via Java.type("...")
  • Stick to one purpose per script — modularity helps with debugging
  • Explore existing scripts and backend source to learn event arguments

Need Help?

Join us in #event-creators-club on Discord. You can share your scripts, get help, request missing events, or propose new scripting features.