SoulFire LogoSoulFire

Event System

Understand how SoulFire events are registered, what the current event hierarchy looks like, and which mutable hooks exist before you reach for Mixins.

The event system is the first extension seam you should use. It is much cheaper to maintain than Mixins, and SoulFire already exposes a meaningful amount of lifecycle, session, bot, chat, packet, and settings behavior through events.

How listener registration works

Every Plugin registers itself in two ways when it is constructed:

  • it registers static listeners declared on the class
  • it registers instance listeners declared on the plugin object

That is why both of these patterns work:

@EventHandler
public static void onBotInit(BotConnectionInitEvent event) {
  // stateless handler
}
@EventHandler
public void onSettingsRegistryInit(InstanceSettingsRegistryInitEvent event) {
  // instance handler
}

The annotation comes from LambdaEvents:

import net.lenni0451.lambdaevents.EventHandler;

Event hierarchy

The public event model is split into three scopes:

  • SoulFireGlobalEvent: events that belong to the whole SoulFire server
  • SoulFireInstanceEvent: events that belong to one instance
  • SoulFireBotEvent: events that belong to one bot and also imply instance access

That hierarchy means a bot event gives you a BotConnection, and from that you can also reach the InstanceManager and SoulFireServer.

Lifecycle events

These are the main lifecycle hooks currently exposed:

EventScopeWhen it fires
CommandManagerInitEventglobalafter built-in commands are registered and before you add your own
ServerSettingsRegistryInitEventglobalwhile the server settings registry is being assembled
InstanceSettingsRegistryInitEventinstancewhile an instance settings registry is being assembled
InstanceInitEventinstanceimmediately after an InstanceManager is created
SessionStartEventinstancewhen an instance session starts
SessionTickEventinstanceon each instance tick while the session is ticking
SessionBotRemoveEventinstancewhen a disconnected bot is evicted from the active bot map
SessionEndedEventinstancewhen the session ends

Bot events

These are the current bot-facing events exposed in com.soulfiremc.server.api.event.bot:

EventWhat it is for
PreBotConnectEventasync pre-connect hook before the bot joins
BotConnectionInitEventright after the BotConnection object exists
BotPreTickEventbot tick hook before the tick body finishes
BotPostTickEventbot tick hook after the tick body finishes
BotPreEntityTickEventbefore entity tracking logic ticks
BotPostEntityTickEventafter entity tracking logic ticks
ChatMessageReceiveEventincoming chat messages
BotPacketPreReceiveEventinspect or replace inbound packets before handling
BotPacketPreSendEventinspect or replace outbound packets before send
BotDamageEventhealth went down
BotOpenContainerEventa container or screen opened
BotClientBrandEventoutgoing client brand can be changed
BotClientSettingsEventoutgoing client settings can be changed
BotShouldRespawnEventcontrol automatic respawn behavior
BotDisconnectedEventthe bot disconnected

Mutable hooks

Some events are plain immutable records. Some are deliberately mutable and are the lowest-friction way to change behavior without a Mixin.

Use these when they match your need:

  • BotPacketPreReceiveEvent: replace the packet, or set it to null to suppress handling
  • BotPacketPreSendEvent: replace the packet, or set it to null to suppress sending
  • BotClientBrandEvent: change the brand string
  • BotClientSettingsEvent: change the ClientInformation
  • BotShouldRespawnEvent: set shouldRespawn

SoulFire currently exposes AbstractCancellable, but the public event surface mostly relies on mutable event objects rather than broad cancellation. For many low-level hooks, replacing a packet or mutating the outgoing value is the intended extension model.

Timing and thread context

The event name alone is not enough. You also need to care about timing:

  • PreBotConnectEvent runs before the player is ready and can do blocking work
  • BotConnectionInitEvent has a fully constructed BotConnection, but the bot is not connected yet
  • BotPreTickEvent and BotPostTickEvent run in the live bot tick loop
  • BotPreEntityTickEvent and BotPostEntityTickEvent are even closer to the entity update path

If you need minecraft().player, always assume it may still be null in early lifecycle hooks.

Custom commands

If your plugin needs terminal commands, listen for CommandManagerInitEvent and add Brigadier commands there. SoulFire posts this event after its built-in commands are already registered.

When to stop and use a Mixin instead

Use a Mixin only when:

  • there is no event at the right timing point
  • you need access to data that the event surface does not expose
  • you need to alter control flow inside Minecraft or SoulFire internals

Before writing the Mixin, check whether one of the packet or client-setting events already covers the use case.

Reference code to study

The built-in plugins and SoulFire's own event-emitting mixins are the best reference:

Next steps

How is this page?

Last updated on

On this page