Files
hytale-server/docs/03-event-system.md

338 lines
9.3 KiB
Markdown

# 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<KeyType>
├── IEvent<KeyType> // Synchronous events
└── IAsyncEvent<KeyType> // Asynchronous events (CompletableFuture)
ICancellable // Mixin interface for cancellable events
```
### Key Classes
| Class | Description |
|------------------------|--------------------------------------------|
| `IEvent<K>` | Synchronous event interface |
| `IAsyncEvent<K>` | 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<String> {
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<String>, 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<String> {
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<String, MyCustomEvent> 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<Boolean> 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