Update script to write to vendor/hytale-server
This commit is contained in:
335
docs/03-event-system.md
Normal file
335
docs/03-event-system.md
Normal file
@@ -0,0 +1,335 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user