---
title: Event System
type: docs
weight: 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:

```java
@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

```java
// 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

```java
// 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:

```java
// 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:

```java
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:

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

### Async Global and Unhandled

For async events with global or unhandled registration:

```java
// 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:

```java
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:

```java
// 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

```java
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
```
