Skip to content

Event System

Hytale’s event system allows plugins to react to game events, from player connections to block breaks. The system supports both synchronous and asynchronous events, with features like priority ordering, keyed events, and cancellation.

  • Event interfaces: com.hypixel.hytale.event
  • Event registry: com.hypixel.hytale.event.EventRegistry
  • Built-in events: com.hypixel.hytale.server.core.event.events

The event system has two primary event interfaces:

InterfaceDescriptionHandler Type
IEvent<KeyType>Synchronous events processed immediatelyConsumer<EventType>
IAsyncEvent<KeyType>Asynchronous events processed with futuresFunction<CompletableFuture<EventType>, CompletableFuture<EventType>>

Both interfaces are generic over a KeyType:

  • Void for non-keyed events (most common)
  • A specific type for keyed events (e.g., String for chat channels)
// Synchronous event - most common
public interface IEvent<KeyType> extends IBaseEvent<KeyType> {}
// Asynchronous event - for operations that may need async processing
public interface IAsyncEvent<KeyType> extends IBaseEvent<KeyType> {}
// Cancellable mixin - allows handlers to prevent default behavior
public interface ICancellable {
boolean isCancelled();
void setCancelled(boolean cancelled);
}
@Override
protected void setup() {
// Simple event registration (default priority: NORMAL)
getEventRegistry().register(
PlayerConnectEvent.class,
this::onPlayerConnect
);
}
private void onPlayerConnect(PlayerConnectEvent event) {
// Handle the event
World world = event.getWorld();
PlayerRef player = event.getPlayerRef();
}
@Override
protected void setup() {
// Register with specific priority
getEventRegistry().register(
EventPriority.FIRST, // Process early
PlayerChatEvent.class,
this::onChatFirst
);
getEventRegistry().register(
EventPriority.LAST, // Process late (monitoring)
PlayerChatEvent.class,
this::onChatLast
);
}

Events are processed in priority order from lowest to highest:

PriorityValueTypical Use
FIRST-21844Modify event data before others see it
EARLY-10922Early processing, setup
NORMAL0Default, general handling
LATE10922React after modifications
LAST21844Final processing, logging, monitoring

You can also use raw short values for fine-grained control:

getEventRegistry().register(
(short) 5000, // Custom priority between NORMAL and LATE
SomeEvent.class,
this::onEvent
);

Some events are “keyed” - they have an associated key that handlers can filter on.

// Only receive events for a specific key
getEventRegistry().register(
SomeKeyedEvent.class,
"my-channel", // The key to filter on
this::onMyChannelEvent
);
// Receive all keyed events regardless of key
getEventRegistry().registerGlobal(
SomeKeyedEvent.class,
this::onAnyKeyedEvent
);

Register a handler that only fires if no keyed handler processed the event:

getEventRegistry().registerUnhandled(
SomeKeyedEvent.class,
this::onUnhandledEvent // Only called if no key-specific handler matched
);

Async events support asynchronous processing chains:

@Override
protected void setup() {
getEventRegistry().registerAsync(
PlayerChatEvent.class,
future -> future.thenApply(event -> {
// Process asynchronously
if (containsBannedWord(event.getContent())) {
event.setCancelled(true);
}
return event;
})
);
}
getEventRegistry().registerAsync(
EventPriority.EARLY,
PlayerChatEvent.class,
future -> future.thenApply(this::processChat)
);
// Async global listener
getEventRegistry().registerAsyncGlobal(
SomeAsyncKeyedEvent.class,
future -> future.thenApply(this::handleGlobally)
);
// Async unhandled listener
getEventRegistry().registerAsyncUnhandled(
SomeAsyncKeyedEvent.class,
future -> future.thenApply(this::handleUnhandled)
);

Events implementing ICancellable can be cancelled to prevent their default behavior:

private void onPlayerChat(PlayerChatEvent event) {
if (event.getContent().contains("badword")) {
event.setCancelled(true); // Message won't be sent
}
}
private void onPlayerChat(PlayerChatEvent event) {
if (event.isCancelled()) {
return; // Skip if already cancelled by another handler
}
// Process the event
}

Note: LAST priority handlers can still observe cancelled events for logging purposes.

Located in com.hypixel.hytale.server.core.event.events.player:

EventAsyncCancellableDescription
PlayerConnectEventNoNoPlayer connected to server
PlayerDisconnectEventNoNoPlayer disconnected
PlayerReadyEventNoNoPlayer fully loaded
PlayerChatEventYesYesPlayer sent chat message
PlayerInteractEventNoNoPlayer interaction
PlayerCraftEventNoNoPlayer crafted item
PlayerMouseMotionEventNoNoPlayer mouse movement
PlayerMouseButtonEventNoNoPlayer mouse click
PlayerSetupConnectEventNoNoPre-connection setup
PlayerSetupDisconnectEventNoNoPre-disconnect setup
DrainPlayerFromWorldEventNoNoPlayer leaving world
AddPlayerToWorldEventNoNoPlayer entering world

Located in com.hypixel.hytale.server.core.event.events.entity:

EventDescription
EntityEventBase entity event
EntityRemoveEventEntity removed from world
LivingEntityInventoryChangeEventEntity inventory changed
LivingEntityUseBlockEventEntity used a block

Located in com.hypixel.hytale.server.core.event.events.ecs:

EventDescription
BreakBlockEventBlock broken
PlaceBlockEventBlock placed
DamageBlockEventBlock damaged
UseBlockEventBlock used/interacted
DropItemEventItem dropped
InteractivelyPickupItemEventItem picked up
CraftRecipeEventRecipe crafted
ChangeGameModeEventGame mode changed
SwitchActiveSlotEventActive inventory slot changed
DiscoverZoneEventZone discovered

Located in com.hypixel.hytale.server.core.event.events.permissions:

EventDescription
PlayerPermissionChangeEventPlayer permissions changed
GroupPermissionChangeEventPermission group changed
PlayerGroupEventPlayer added/removed from group
EventDescription
ShutdownEventServer shutting down
PrepareUniverseEventUniverse being prepared
BootEventServer booting up

Many events allow modification:

private void onPlayerChat(PlayerChatEvent event) {
// Modify the message
String filtered = filterProfanity(event.getContent());
event.setContent(filtered);
// Change message targets
List<PlayerRef> newTargets = event.getTargets().stream()
.filter(this::canReceive)
.toList();
event.setTargets(newTargets);
// Custom formatting
event.setFormatter((player, msg) ->
Message.translation("custom.chat.format")
.param("name", player.getUsername())
.param("msg", msg)
);
}

Events provide context about what happened:

private void onPlayerConnect(PlayerConnectEvent event) {
// Get the player reference
PlayerRef playerRef = event.getPlayerRef();
// Get the ECS holder for components
Holder<EntityStore> holder = event.getHolder();
// Get/set the target world
World world = event.getWorld();
event.setWorld(differentWorld); // Change spawn world
// Access player component (deprecated, prefer holder)
Player player = event.getPlayer();
}
// Synchronous event
public class MyCustomEvent implements IEvent<Void> {
private final String data;
public MyCustomEvent(String data) {
this.data = data;
}
public String getData() {
return data;
}
}
// Cancellable async event
public class MyAsyncEvent implements IAsyncEvent<Void>, ICancellable {
private boolean cancelled = false;
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}
// Get the event bus from the server
IEventBus eventBus = HytaleServer.get().getEventBus();
// Dispatch a sync event
IEventDispatcher<MyEvent, MyEvent> dispatcher =
eventBus.dispatchFor(MyEvent.class, null);
MyEvent result = dispatcher.dispatch(new MyEvent("data"));
// Dispatch an async event
IEventDispatcher<MyAsyncEvent, CompletableFuture<MyAsyncEvent>> asyncDispatcher =
eventBus.dispatchForAsync(MyAsyncEvent.class, null);
CompletableFuture<MyAsyncEvent> future = asyncDispatcher.dispatch(new MyAsyncEvent());
  1. Choose the right priority: Use FIRST for modifications, LAST for monitoring
  2. Check cancellation: Early-exit if the event is already cancelled (unless monitoring)
  3. Don’t block: Keep handlers fast; use async events for slow operations
  4. Handle exceptions: Wrap risky code in try-catch to avoid breaking other handlers
  5. Use appropriate granularity: Register for specific keys when possible
private void onEvent(SomeEvent event) {
if (event instanceof ICancellable c && c.isCancelled()) {
return; // Skip cancelled events
}
try {
// Handler logic
} catch (Exception e) {
getLogger().at(Level.WARNING).withCause(e).log("Error handling event");
}
}

All event registrations made through getEventRegistry() are automatically unregistered when your plugin shuts down. You don’t need to manually unregister handlers.