Files
Documentation/content/core-concepts/events/event-system.en.md
2026-01-20 20:33:59 +01:00

6.1 KiB

title, type, weight
title type weight
Event System docs 1

Hytale uses an event-driven architecture where your plugin can subscribe to and react to game events.

The EventBus

The EventBus is the central hub for all events. It manages event registration and dispatching.

Registering Event Listeners

Use the EventRegistry from your plugin to register listeners:

@Override
public void start() {
    // Lambda syntax - PlayerReadyEvent fires when player is fully loaded
    // Note: PlayerReadyEvent has a String key, so use registerGlobal()
    getEventRegistry().registerGlobal(PlayerReadyEvent.class, event -> {
        Player player = event.getPlayer();
        getLogger().at(Level.INFO).log("Player ready: " + player.getDisplayName());
    });

    // Method reference
    getEventRegistry().register(PlayerDisconnectEvent.class, this::onPlayerDisconnect);
}

private void onPlayerDisconnect(PlayerDisconnectEvent event) {
    PlayerRef playerRef = event.getPlayerRef();
    getLogger().at(Level.INFO).log("Player left: " + playerRef.getUsername());
}

Registration Methods

Basic Registration

// Register with default priority (NORMAL)
getEventRegistry().register(PlayerConnectEvent.class, event -> {
    // Handle event
});

{{< callout type="warning" >}} Important: The simple register(Class, Consumer) method only works for events with a Void key type (like PlayerConnectEvent). For keyed events (like PlayerReadyEvent which has a String key), you must use registerGlobal() instead. Check the event's type parameter to determine which method to use. {{< /callout >}}

With Priority

// Register with specific priority
getEventRegistry().register(EventPriority.EARLY, PlayerConnectEvent.class, event -> {
    // This runs before NORMAL priority handlers
});

With Key

Some events support key-based registration for filtering:

// Only listen to events for a specific key
getEventRegistry().register(SomeKeyedEvent.class, myKey, event -> {
    // Only called when event key matches myKey
});

Global Registration

Listen to all events of a type, regardless of key:

getEventRegistry().registerGlobal(KeyedEvent.class, event -> {
    // Called for all instances of this event
});

Unhandled Registration

Handle events that weren't processed by any key-specific listener:

getEventRegistry().registerUnhandled(KeyedEvent.class, event -> {
    // Called when no specific handler matched
});

Async Global and Unhandled

For async events with global or unhandled registration:

// Async global - all keys
getEventRegistry().registerAsyncGlobal(AsyncKeyedEvent.class, future ->
    future.thenApply(event -> {
        // Process async
        return event;
    })
);

// Async unhandled - unmatched keys
getEventRegistry().registerAsyncUnhandled(AsyncKeyedEvent.class, future ->
    future.thenApply(event -> {
        // Handle unmatched
        return event;
    })
);

EventRegistration

Registration methods return an EventRegistration object:

EventRegistration<?, PlayerConnectEvent> registration =
    getEventRegistry().register(PlayerConnectEvent.class, this::onConnect);

// Later, you can unregister manually if needed
registration.unregister();

{{< callout type="info" >}} Event registrations are automatically cleaned up when your plugin shuts down. Manual unregistration is rarely needed. {{< /callout >}}

Event Interface Hierarchy

Events implement different interfaces based on their capabilities:

Interface Description
IBaseEvent<K> Base interface for all events
IEvent<K> Synchronous events
IAsyncEvent<K> Asynchronous events
ICancellable Events that can be cancelled
ICancellableEcsEvent ECS events that can be cancelled

Getting Registered Events

Query which events are registered:

// Get all registered event classes
Set<Class<? extends IBaseEvent<?>>> eventClasses =
    HytaleServer.get().getEventBus().getRegisteredEventClasses();

// Get registered event names
Set<String> eventNames =
    HytaleServer.get().getEventBus().getRegisteredEventClassNames();

// Get a specific registry by name
EventBusRegistry<?, ?, ?> registry =
    HytaleServer.get().getEventBus().getRegistry("PlayerConnectEvent");

Complete Example

public class WelcomePlugin extends JavaPlugin {

    public WelcomePlugin(JavaPluginInit init) {
        super(init);
    }

    @Override
    public void start() {
        // Handle player connection
        getEventRegistry().register(PlayerConnectEvent.class, event -> {
            PlayerRef playerRef = event.getPlayerRef();
            getLogger().at(Level.INFO).log("Player connecting: " + playerRef.getUsername());

            // You can set the world the player spawns in
            World lobbyWorld = Universe.get().getWorld("lobby");
            if (lobbyWorld != null) {
                event.setWorld(lobbyWorld);
            }
        });

        // Welcome message when fully ready
        // Note: PlayerReadyEvent has a String key, so use registerGlobal()
        getEventRegistry().registerGlobal(PlayerReadyEvent.class, event -> {
            Player player = event.getPlayer();
            player.sendMessage(Message.raw("Welcome to the server, " + player.getDisplayName() + "!"));
        });

        // Goodbye message on disconnect
        getEventRegistry().register(PlayerDisconnectEvent.class, event -> {
            PlayerRef playerRef = event.getPlayerRef();
            getLogger().at(Level.INFO).log(playerRef.getUsername() + " has left the server");
        });
    }
}

Player Event Lifecycle

Understanding when each event fires:

PlayerSetupConnectEvent  → Can cancel/redirect connection
        ↓
PlayerConnectEvent       → Player connecting, set spawn world
        ↓
AddPlayerToWorldEvent    → Player added to world
        ↓
PlayerReadyEvent         → Player fully loaded, safe to interact
        ↓
(gameplay events)
        ↓
DrainPlayerFromWorldEvent → Player leaving world
        ↓
PlayerDisconnectEvent    → Player disconnected