Creating custom events
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(...)
andstate.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.