Skip to content

Events

Eighteen Bukkit events, all in zone.vao.voxen.event.

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
@EventHandler
public void onSend(ChatMessageSendEvent event) {
if (event.getContent().contains("spoiler")) {
event.setCancelled(true);
return;
}
event.getRecipients().removeIf(p -> p.getWorld().equals(nether));
}

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
@EventHandler
public void onDelivered(ChatMessageDeliveredEvent event) {
bridge.forward(PlainTextComponentSerializer.plainText().serialize(event.getMessage()));
}

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
@EventHandler
public 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.

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
@EventHandler
public 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.

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
@EventHandler
public void onReport(ReportCreateEvent event) {
if (event.getReason().isBlank()) event.setCancelled(true);
else webhook.post(event.getTargetName() + ": " + event.getReason());
}

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
@EventHandler
public 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.

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
@EventHandler
public void onReportUpdate(ReportUpdateEvent event) {
if (event.getAction() == ReportInfo.Action.DELETE && !isLead(event.getModerator())) {
event.setCancelled(true);
}
}

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
@EventHandler
public 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.

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

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

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

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

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

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
@EventHandler
public void onMute(PlayerMuteEvent event) {
long day = System.currentTimeMillis() + 86_400_000L;
if (event.isPermanent() && !isLead(event.getModerator())) event.setExpiresAt(day);
}

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.

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.

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.

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.