Void uses two kinds of events:
world.trigger(), handled by Bevy observers.world.write_message(), consumed with MessageReader<T>.These are the primary extension points for plugins. They are triggered after the server has processed the corresponding packet and updated ECS state.
Triggered when a client finishes configuration and transitions to the Play state. At this point all player components are inserted but PlayerReady is not yet set (the client hasn't sent PlayerLoaded).
Triggered when the client sends PlayerLoaded, indicating they have received initial chunks and are ready to play. The PlayerReady marker component is inserted just before this event fires. Other players are notified of the new player via this event's observer.
Triggered when a ready player disconnects. Observers broadcast entity removal and tab-list updates to remaining players. The entity is despawned after the event is fully processed.
Triggered when a player's position changes (from SetPlayerPos or SetPlayerPosAndRot packets). The Position component is already updated when this event fires.
Triggered when a player's look direction changes.
Triggered after a chat command is dispatched (regardless of whether the command was found). Allows plugins to observe all command usage.
Triggered when a player sends a chat message (not a command). The message has already been broadcast to all ready players when this event fires.
Every incoming packet is also dispatched as a message event, giving plugins raw access to protocol data:
| Event | Packet Type |
|---|---|
HandshakePacketEvent |
serverbound::HandshakePacket |
StatusPacketEvent |
serverbound::StatusPacket |
LoginPacketEvent |
serverbound::LoginPacket |
ConfigurationPacketEvent |
serverbound::ConfigurationPacket |
PlayPacketEvent |
serverbound::PlayPacket |
Each contains client_id: u32, entity: Entity, and packet: <PacketType>.
These are dispatched via world.write_message() and can be consumed in systems using MessageReader<T>.
Register an observer function in your plugin:
Read raw packet messages in a system:
Semantic events use Bevy's trigger system:
The world.flush() call ensures all observer side-effects (entity spawns, component insertions, etc.) are applied immediately within the same tick, before subsequent systems run.