# Event System The Hytale event system provides a powerful mechanism for plugins to react to game events. It supports both synchronous and asynchronous events with priority-based dispatching. ## Event Architecture ### Event Interface Hierarchy ``` IBaseEvent ├── IEvent // Synchronous events └── IAsyncEvent // Asynchronous events (CompletableFuture) ICancellable // Mixin interface for cancellable events ``` ### Key Classes | Class | Description | |------------------------|--------------------------------------------| | `IEvent` | Synchronous event interface | | `IAsyncEvent` | Asynchronous event interface | | `ICancellable` | Interface for events that can be cancelled | | `EventRegistry` | Plugin-specific event registration | | `SyncEventBusRegistry` | Global synchronous event bus | | `EventPriority` | Event listener priority levels | ## Event Priorities Events are dispatched to listeners in priority order: | Priority | Value | Description | |----------|--------|-------------------------------| | `FIRST` | -21844 | Runs first, before all others | | `EARLY` | -10922 | Runs early, after FIRST | | `NORMAL` | 0 | Default priority | | `LATE` | 10922 | Runs late, after NORMAL | | `LAST` | 21844 | Runs last, after all others | Lower values run first. Use `FIRST` sparingly - typically for monitoring/logging. Use `LAST` for final processing after other plugins have had a chance to modify the event. ## Registering Event Listeners ### Basic Registration ```java @Override protected void setup() { // Register with default NORMAL priority getEventRegistry().register(PlayerConnectEvent.class, this::onPlayerConnect); } private void onPlayerConnect(PlayerConnectEvent event) { getLogger().info("Player connected: " + event.getPlayer().getName()); } ``` ### With Priority ```java // Run before other listeners getEventRegistry().register(EventPriority.FIRST, PlayerConnectEvent.class, event -> { getLogger().debug("Logging player connect (first)"); }); // Run after other listeners getEventRegistry().register(EventPriority.LAST, PlayerConnectEvent.class, event -> { getLogger().debug("Final processing (last)"); }); ``` ### Global Listeners Global listeners receive all events of a type, regardless of the event's key: ```java // Receive all entity events getEventRegistry().registerGlobal(EntityEvent.class, event -> { // Handle any entity event }); ``` ### Keyed Events Some events are keyed (e.g., by world name). You can listen for specific keys: ```java // Listen for events in a specific world getEventRegistry().register(AddPlayerToWorldEvent.class, "worldName", event -> { // Only triggered for "worldName" }); ``` ### Unhandled Events Register for events that no other listener handled: ```java getEventRegistry().registerUnhandled(CustomEvent.class, event -> { // Only called if no other listener handled this event }); ``` ## Asynchronous Events Async events return `CompletableFuture` and are handled on separate threads: ```java // Register async event listener getEventRegistry().registerAsync(AsyncDataLoadEvent.class, event -> { return CompletableFuture.supplyAsync(() -> { // Perform async operation return loadData(event.getDataId()); }); }); // Global async listener getEventRegistry().registerAsyncGlobal(AsyncEvent.class, this::handleAsync); // Unhandled async listener getEventRegistry().registerAsyncUnhandled(AsyncEvent.class, this::handleUnhandledAsync); ``` ## Cancellable Events Events implementing `ICancellable` can be cancelled by listeners: ```java getEventRegistry().register(PlayerInteractEvent.class, event -> { if (shouldBlock(event)) { event.setCancelled(true); } }); ``` When checking if an event was cancelled: ```java getEventRegistry().register(EventPriority.LAST, PlayerInteractEvent.class, event -> { if (!event.isCancelled()) { // Event was not cancelled by earlier listeners processInteraction(event); } }); ``` ## Common Event Types ### Server Lifecycle Events | Event | Description | |------------------------|-------------------------| | `BootEvent` | Server boot complete | | `ShutdownEvent` | Server shutting down | | `PrepareUniverseEvent` | Universe initialization | ### Player Events | Event | Description | |-----------------------------|----------------------------------| | `PlayerConnectEvent` | Player connected to server | | `PlayerDisconnectEvent` | Player disconnected | | `AddPlayerToWorldEvent` | Player added to a world | | `DrainPlayerFromWorldEvent` | Player removed from a world | | `PlayerInteractEvent` | Player interaction (cancellable) | | `PlayerMouseButtonEvent` | Mouse button input (cancellable) | ### Entity Events | Event | Description | |-----------------------------|-----------------------------| | `EntityRemoveEvent` | Entity removed from world | | `LivingEntityUseBlockEvent` | Living entity using a block | ### World Events | Event | Description | |------------------------|-----------------------------| | `AddWorldEvent` | World added to universe | | `RemoveWorldEvent` | World removed | | `StartWorldEvent` | World started | | `AllWorldsLoadedEvent` | All worlds finished loading | ### ECS Events | Event | Description | |---------------------|-------------------| | `UseBlockEvent` | Block usage event | | `DiscoverZoneEvent` | Zone discovery | ## Creating Custom Events ### Synchronous Event ```java public class MyCustomEvent implements IEvent { private final String key; private final Player player; private final String data; public MyCustomEvent(String key, Player player, String data) { this.key = key; this.player = player; this.data = data; } @Override public String getKey() { return key; } public Player getPlayer() { return player; } public String getData() { return data; } } ``` ### Cancellable Event ```java public class MyCancellableEvent implements IEvent, ICancellable { private final String key; private boolean cancelled = false; public MyCancellableEvent(String key) { this.key = key; } @Override public String getKey() { return key; } @Override public boolean isCancelled() { return cancelled; } @Override public void setCancelled(boolean cancelled) { this.cancelled = cancelled; } } ``` ### Async Event ```java public class MyAsyncEvent implements IAsyncEvent { private final String key; public MyAsyncEvent(String key) { this.key = key; } @Override public String getKey() { return key; } } ``` ## Dispatching Events To dispatch custom events: ```java // Get the event bus SyncEventBusRegistry eventBus = getEventBus(MyCustomEvent.class); // Dispatch event MyCustomEvent event = new MyCustomEvent("key", player, "data"); eventBus.dispatch(event); // For cancellable events, check result MyCancellableEvent cancellable = new MyCancellableEvent("key"); eventBus.dispatch(cancellable); if (!cancellable.isCancelled()) { // Proceed with action } ``` ## Event Registration Management ### Unregistering Listeners Event registration returns an `EventRegistration` that can be used to unregister: ```java EventRegistration registration = getEventRegistry().register( PlayerConnectEvent.class, this::onConnect ); // Later, to unregister: registration.unregister(); ``` ### Combining Registrations Multiple registrations can be combined for bulk management: ```java EventRegistration reg1 = getEventRegistry().register(Event1.class, this::handle1); EventRegistration reg2 = getEventRegistry().register(Event2.class, this::handle2); EventRegistration combined = reg1.combine(reg2); // Unregister both at once combined.unregister(); ``` ### Conditional Listeners Registrations can be conditionally enabled: ```java // Listener only active while condition is true Supplier isEnabled = () -> config.get().featureEnabled; // The registration checks isEnabled before calling the listener ``` ## Best Practices 1. **Use appropriate priorities** - Don't use `FIRST` unless you need to monitor/log before modifications 2. **Check cancellation state** - If using `LATE` or `LAST`, check if event was cancelled 3. **Don't block in sync handlers** - Use async events for long-running operations 4. **Clean up registrations** - Unregister listeners in `shutdown()` if needed 5. **Use keyed events** - When possible, listen for specific keys to reduce unnecessary handler calls 6. **Handle exceptions** - Wrap handler code in try-catch to prevent crashing other listeners 7. **Document custom events** - When creating custom events, document their key type and when they're dispatched