Events
Eighteen Bukkit events, all in zone.vao.voxen.event.
ChatMessageSendEvent
Section titled “ChatMessageSendEvent”Fires before delivery. Cancellable, and content is mutable, so this is where you rewrite
or drop a message.
| Field | Type | Notes |
|---|---|---|
player |
Player |
the sender |
channelId |
String |
channel the message is going to |
content |
String |
mutable, the message after the filter ran |
recipients |
MutableSet<Player> |
remove entries to hide the message from someone |
@EventHandlerpublic void onSend(ChatMessageSendEvent event) { if (event.getContent().contains("spoiler")) { event.setCancelled(true); return; } event.getRecipients().removeIf(p -> p.getWorld().equals(nether));}ChatMessageDeliveredEvent
Section titled “ChatMessageDeliveredEvent”Fires after delivery. Everything on it is read-only; use it for logging, Discord bridges and statistics.
| Field | Type | Notes |
|---|---|---|
player |
Player |
the sender |
channelId |
String |
channel it went to |
content |
String |
the plain text |
message |
Component |
the fully rendered message |
recipients |
Set<Player> |
who actually received it |
@EventHandlerpublic void onDelivered(ChatMessageDeliveredEvent event) { bridge.forward(PlainTextComponentSerializer.plainText().serialize(event.getMessage()));}ChannelSwitchEvent
Section titled “ChannelSwitchEvent”Fires before a player’s active channel changes, whether that came from the channel command, a quick chat prefix or the API. Cancelling leaves them where they were.
| Field | Type | Notes |
|---|---|---|
player |
Player |
who is switching |
from |
String |
the channel they were talking in, null when they had none |
to |
String |
where they are going |
@EventHandlerpublic void onSwitch(ChannelSwitchEvent event) { if (event.getTo().equals("staff") && !event.getPlayer().hasPermission("team.member")) { event.setCancelled(true); }}Switching also joins the channel, so cancelling leaves the membership alone as well.
PrivateMessageEvent
Section titled “PrivateMessageEvent”Fires before a private message leaves the sender, after the mute, ignore and toggle checks
have passed. Cancellable, and content is mutable.
| Field | Type | Notes |
|---|---|---|
sender |
Player |
who is writing |
target |
Player |
the recipient, or null when they are on another server |
targetName |
String |
always set, even for a remote recipient |
content |
String |
mutable, the raw text before the filter |
remote |
boolean |
whether the message is crossing the network |
@EventHandlerpublic void onPrivateMessage(PrivateMessageEvent event) { if (isGagged(event.getSender())) event.setCancelled(true);}Only the sending server fires it, so on a network the listener has to be installed everywhere to see every message.
ReportCreateEvent
Section titled “ReportCreateEvent”Fires before a report is written to the database, after the cooldown check. Cancelling drops it
silently, and reason may be rewritten before it is stored.
There is deliberately no report id here: the row does not exist yet, and the duplicate check still runs afterwards, so a report that reaches this event may never be stored. Anything that needs the id belongs on ReportOpenedEvent.
| Field | Type | Notes |
|---|---|---|
reporter |
Player |
who filed it |
target |
UUID |
who it is about |
targetName |
String |
their name at the time |
reason |
String |
mutable |
channelId |
String |
channel of the reported message, or null |
messageId |
UUID |
the reported message, or null |
messageContent |
String |
what it said, or null |
@EventHandlerpublic void onReport(ReportCreateEvent event) { if (event.getReason().isBlank()) event.setCancelled(true); else webhook.post(event.getTargetName() + ": " + event.getReason());}ReportOpenedEvent
Section titled “ReportOpenedEvent”Fires once the report is in the database and has an id. ReportCreateEvent runs earlier so it
can still be vetoed, which is exactly why it has no id yet — and why a report that the
duplicate check drops never reaches this event.
| Field | Type | Notes |
|---|---|---|
report |
ReportInfo |
the stored report, id and all |
@EventHandlerpublic void onReportOpened(ReportOpenedEvent event) { VoxenApi.reportCase(event.getReport().getId()).thenAccept(this::buildPanel);}Use this one to build anything that has to point back at a report, and pair it with
ReportUpdateEvent to follow it afterwards. It fires only on the server the report was filed
on, so one report means one event across the whole network.
ReportUpdateEvent
Section titled “ReportUpdateEvent”Fires before a report is claimed, resolved, dismissed or deleted, no matter where the change came from: a command, a dialog screen, the web panel or the API. Cancelling leaves the report untouched.
| Field | Type | Notes |
|---|---|---|
report |
ReportInfo |
the state before the change |
action |
ReportInfo.Action |
CLAIM, RESOLVE, DISMISS or DELETE |
moderator |
String |
a name, not a player: the console and the panel act under theirs |
@EventHandlerpublic void onReportUpdate(ReportUpdateEvent event) { if (event.getAction() == ReportInfo.Action.DELETE && !isLead(event.getModerator())) { event.setCancelled(true); }}ReportUpdatedEvent
Section titled “ReportUpdatedEvent”Fires once the change is written. ReportUpdateEvent runs before the write so it can still be
vetoed, which means the report it carries is the state as it was — redrawing anything from
it shows the old status.
| Field | Type | Notes |
|---|---|---|
report |
ReportInfo |
the state after the change; for DELETE, the last state it had |
action |
ReportInfo.Action |
what was done |
moderator |
String |
who did it |
@EventHandlerpublic void onReportUpdated(ReportUpdatedEvent event) { if (event.getModerator().startsWith("discord:")) return; redraw(event.getReport());}Mirror a report from this one, never from ReportUpdateEvent. Also on the storage worker.
ChatMessageDeleteEvent
Section titled “ChatMessageDeleteEvent”Fires for every chat message a moderator deletes, one event per message, whether it came from the delete command, the chat button, a report or the API.
| Field | Type | Notes |
|---|---|---|
target |
UUID |
whose message it was |
targetName |
String |
their name |
moderator |
String |
who deleted it |
content |
String |
the text that was removed |
messageId |
UUID |
set only when the deletion started from a report |
HelpRequestEvent
Section titled “HelpRequestEvent”Fires when a player asks the staff for help with /helpop, after the cooldown check and
before anything is sent or stored. Cancellable, and message is mutable.
| Field | Type | Notes |
|---|---|---|
player |
Player |
who is asking |
message |
String |
mutable |
tickets |
boolean |
false when the message is only broadcast, true when it opens a ticket |
TicketUpdateEvent
Section titled “TicketUpdateEvent”Fires after a ticket was opened, answered or closed, from any side: the player’s screen, the staff commands, the web panel or the API. Not cancellable — the change has already happened.
| Field | Type | Notes |
|---|---|---|
ticket |
TicketInfo |
the state after the change |
action |
TicketUpdateEvent.Action |
OPENED, REPLIED or CLOSED |
author |
String |
who wrote or closed it |
staff |
boolean |
which side of the conversation |
message |
String |
the text, null for CLOSED |
PlayerWarnEvent
Section titled “PlayerWarnEvent”Fires before a warning goes on a player’s record, whatever asked for it: a command, the
moderator screens, AI moderation or the API. Cancellable, and reason is mutable.
| Field | Type | Notes |
|---|---|---|
target |
UUID |
who is being warned |
targetName |
String |
their name at the time |
reason |
String |
mutable |
moderator |
String |
a name, not a player |
AiModerationEvent
Section titled “AiModerationEvent”Fires when AI moderation has scored a message and decided what to do, before any of it happens. Cancelling drops every action and the rule’s commands with them.
| Field | Type | Notes |
|---|---|---|
player |
Player |
who wrote the message |
content |
String |
what they wrote |
score |
double |
0 to 1 |
actions |
Set<AiModerationEvent.Action> |
REPORT, DELETE, WARN, KICK |
PlayerMuteEvent
Section titled “PlayerMuteEvent”Fires before a player is muted, from the command, the moderator screens or the API.
Cancelling calls the mute off; reason and expiresAt are both mutable, which is how you
cap a duration or force a reason.
| Field | Type | Notes |
|---|---|---|
target |
UUID |
who is being muted |
targetName |
String |
their name at the time |
channelId |
String |
the channel, or null for every channel |
moderator |
String |
who issued it |
reason |
String |
mutable, may be null |
expiresAt |
Long |
mutable, a timestamp in milliseconds, null for permanent |
@EventHandlerpublic void onMute(PlayerMuteEvent event) { long day = System.currentTimeMillis() + 86_400_000L; if (event.isPermanent() && !isLead(event.getModerator())) event.setExpiresAt(day);}PlayerUnmuteEvent
Section titled “PlayerUnmuteEvent”Fires before a mute is lifted, and only when there is one to lift, so a /voxen unmute on
someone who was never muted stays quiet. Cancelling leaves the mute in place.
| Field | Type | Notes |
|---|---|---|
target |
UUID |
who is being unmuted |
targetName |
String |
the name the mute was recorded under |
channelId |
String |
the channel, or null for the mute covering every channel |
allChannels |
boolean |
every mute the player has is going at once |
moderator |
String |
who lifted it, null when nobody was named |
NetworkJoinEvent, NetworkQuitEvent and NetworkSwitchEvent
Section titled “NetworkJoinEvent, NetworkQuitEvent and NetworkSwitchEvent”Three events covering where players are on the network, so nothing has to poll
networkPlayers() and compare.
| Event | Fires when | Carries |
|---|---|---|
NetworkJoinEvent |
a player arrives on the network | player (NetworkPlayer) |
NetworkQuitEvent |
a player leaves it for good | uuid, name, server |
NetworkSwitchEvent |
a player moves between two servers | player, from |
Each one fires on the server that saw it and nowhere else, so one movement means one event across the whole network — but it also means the addon has to be installed on every server to see everything.
A walk from survival to lobby is one NetworkSwitchEvent on the lobby, not a quit and a join.
That works because the quit is held back for quit.delay in modules/system-messages.yml; if
the player turns up elsewhere inside that window, the quit is dropped. The same delay is how
long NetworkQuitEvent waits, so a real leaver is reported a second or two after they
disconnect.
NicknameChangeEvent
Section titled “NicknameChangeEvent”Fires before a nickname changes, whatever asked for it: the nick command, a moderator
renaming somebody else or the API. Cancellable, and nickname is mutable, which is how you
force a format rather than refusing outright.
| Field | Type | Notes |
|---|---|---|
player |
Player |
whose nickname is changing |
from |
String? |
null when they had none |
nickname |
String? |
mutable, null means it is being cleared |
Length limits and the word filter have already passed by the time this fires, so anything you
write into nickname is stored as given. Voxen says nothing to the player when a handler
cancels, so tell them yourself.
PlayerIgnoreEvent
Section titled “PlayerIgnoreEvent”Fires before one player starts or stops ignoring another. Cancelling leaves the list alone.
| Field | Type | Notes |
|---|---|---|
source |
UUID |
whose list is changing |
target |
UUID |
who is going on or off it |
ignoring |
boolean |
true when adding, false when removing |
One event covers both directions, because a handler almost always wants to see both. Both sides are ids rather than players, since either can be offline. It does not fire when the list would not actually change.
PartyJoinEvent, PartyLeaveEvent and PartyDisbandEvent
Section titled “PartyJoinEvent, PartyLeaveEvent and PartyDisbandEvent”Three events covering party membership, so a plugin can follow parties without polling
VoxenApi.party.
| Event | Fires | Cancellable | Carries |
|---|---|---|---|
PartyJoinEvent |
before an invite is accepted | yes | player, party |
PartyLeaveEvent |
after somebody leaves or is kicked | no | member, party, reason |
PartyDisbandEvent |
while a party is being taken apart | no | party |
party is always a PartyInfo snapshot. On join it is the party before the new member, on
leave it is the party after they went, and on disband it still lists everybody who is about to
lose it.
reason is LEFT or KICKED. A disband fires one PartyDisbandEvent rather than a
PartyLeaveEvent per member, so handle both if you track membership. Creating a party fires
nothing, because the leader is a member from the moment it exists.
MailSendEvent
Section titled “MailSendEvent”Fires before a piece of mail is stored, from the mail command and from VoxenApi.sendMail
alike. Cancelling drops it silently and the recipient never learns it existed.
| Field | Type | Notes |
|---|---|---|
sender |
UUID |
who it is from |
senderName |
String |
the name shown in the mailbox |
recipient |
UUID |
who it is for |
content |
String |
mutable |
content has already been through the word filter on the command path. The mailbox limit is
checked after this fires, so a full mailbox still refuses a piece a handler let through.