# Events
Bookshelf provides custom Bukkit events that fire during key plugin interactions. All events extend `BaseEvent`, which provides a convenience `call()` method and shared `HandlerList`.
---
## BaseEvent
All Bookshelf events extend `BaseEvent`, which is an abstract class extending Bukkit's `Event`.
```java
public abstract class BaseEvent extends Event {
public boolean call() {
Bukkit.getPluginManager().callEvent(this);
return this instanceof Cancellable && ((Cancellable) this).isCancelled();
}
}
```
The `call()` method fires the event through Bukkit's event system and returns `true` if the event was cancelled. This allows compact event dispatching:
```java
if (new PlayerChatEvent(player, prefix, message).call()) {
return; // Event was cancelled by a listener
}
```
---
## Available Events
### PlayerChatEvent
Fires when a player sends a chat message through Bookshelf's chat system. Implements `Cancellable`.
| Field | Type | Description |
|---|---|---|
| `player` | `Player` | The player who sent the message. |
| `prefix` | `Component` | The chat prefix component. Settable. |
| `suffix` | `Component` | The chat suffix component. Settable. |
| `message` | `Component` | The chat message content. Settable. |
| `permission` | `String` | Permission required to see this message. Settable. |
| `playerColor` | `String` | Hex color for the player name. Settable. |
| `messageColor` | `String` | Hex color for the message text. Settable. |
| `viewers` | `List<UUID>` | Specific recipients. Null means all players. Settable. |
| `isModified` | `boolean` | Whether another plugin has modified this event. |
```java
@EventHandler
public void onChat(PlayerChatEvent event) {
// Change message color for VIP players
if (event.getPlayer().hasPermission("vip.chat")) {
event.messageColor(NamedTextColor.GOLD.asHexString());
}
// Restrict viewers to nearby players
List<UUID> nearby = event.getPlayer().getLocation()
.getNearbyPlayers(50).stream()
.map(Player::getUniqueId)
.toList();
event.setViewers(nearby);
}
```
---
### PlayerChatModerationEvent
Fires when a player's chat message is blocked by a moderation check. Implements `Cancellable`. Cancelling this event **overrides the moderation** and allows the message through.
| Field | Type | Description |
|---|---|---|
| `player` | `Player` | The player whose message was moderated. |
| `message` | `Component` | The original chat message. |
| `type` | `ModerationType` | The type of moderation that triggered. |
| `detail` | `String` (nullable) | Type-specific context (see table below). |
#### ModerationType Enum
| Value | Triggered By | `detail` Contains |
|---|---|---|
| `WARNED` | Player has active warn infraction | Warn reason |
| `MUTED` | Player has active mute infraction | Mute reason |
| `GLOBAL_MUTE` | Server chat is muted | `null` |
| `BANNED_WORD` | Message contains blacklisted word | The matched word |
| `CHARACTER_SPAM` | Repeated character detection | The raw message |
| `COOLDOWN` | Sending messages too fast | Cooldown duration string |
| `DUPLICATE_MESSAGE` | Same message sent twice in a row | The duplicate message text |
```java
@EventHandler
public void onModeration(PlayerChatModerationEvent event) {
// Allow VIP players to bypass cooldowns
if (event.getType() == PlayerChatModerationEvent.ModerationType.COOLDOWN
&& event.getPlayer().hasPermission("vip.chat.bypass")) {
event.setCancelled(true); // Override moderation, let message through
}
// Log all moderation actions
getLogger().info(event.getPlayer().getName() + " blocked by " + event.getType()
+ (event.getDetail() != null ? ": " + event.getDetail() : ""));
}
```
---
### PlayerMessageEvent
Fires when a player sends a private message (whisper). Supports both local and cross-network messages. Implements `Cancellable`.
| Field | Type | Description |
|---|---|---|
| `player` | `Player` | The player who sent the message. |
| `target` | `Player` (nullable) | The target player, if on the same server. |
| `targetUniqueId` | `UUID` (nullable) | The target UUID, for cross-network messages. |
| `targetName` | `String` (nullable) | The target name, for cross-network messages. |
```java
@EventHandler
public void onMessage(PlayerMessageEvent event) {
if (event.isLocal()) {
// Message sent on same server
Player target = event.getTarget();
} else if (event.isCrossNetwork()) {
// Message sent to another server
UUID targetId = event.getTargetUniqueId();
String targetName = event.getTargetName();
}
}
```
---
### PlayerCraftCustomItemEvent
Fires when a player crafts a `CustomItem` registered through the item system.
| Field | Type | Description |
|---|---|---|
| `player` | `Player` | The player crafting the item. |
| `customItem` | `CustomItem` | The custom item being crafted. |
| `result` | `ItemStack` | The resulting ItemStack. |
| `cancelled` | `boolean` | Whether crafting is cancelled. |
```java
@EventHandler
public void onCraftCustom(PlayerCraftCustomItemEvent event) {
if (event.getCustomItem().id().equals("legendary_sword")) {
event.getPlayer().sendMessage("You forged a legendary weapon!");
}
}
```
---
### PlayerEquipArmorEvent
Fires when a player equips armor. Implements `Cancellable`.
| Field | Type | Description |
|---|---|---|
| `player` | `Player` | The player equipping armor. |
| `itemStack` | `ItemStack` | The armor piece being equipped. |
| `slot` | `EquipmentSlot` | The equipment slot (HEAD, CHEST, LEGS, FEET). |
```java
@EventHandler
public void onEquipArmor(PlayerEquipArmorEvent event) {
if (event.getSlot() == EquipmentSlot.HEAD) {
event.getPlayer().sendMessage("New helmet equipped!");
}
}
```
---
### PlayerRespawnEvent
Fires when a player respawns. Allows overriding the respawn location.
| Field | Type | Description |
|---|---|---|
| `player` | `Player` | The player respawning. |
| `respawnLocation` | `Location` (nullable) | Override respawn location. Null uses default. |
```java
@EventHandler
public void onRespawn(PlayerRespawnEvent event) {
// Redirect respawn to a custom location
Location lobby = new Location(Bukkit.getWorld("world"), 0, 100, 0);
event.setRespawnLocation(lobby);
}
```
---
### BookshelfReloadEvent
Fires when the Bookshelf plugin reloads its configuration. Contains no fields. Use this to refresh any cached data that depends on Bookshelf's config.
```java
@EventHandler
public void onReload(BookshelfReloadEvent event) {
// Refresh cached configuration values
reloadMyConfig();
}
```
---
## Infraction Events
All infraction events extend `PlayerInfractionEvent`, an abstract cancellable event with an `issuer` and `reason`.
### PlayerInfractionEvent (Base)
| Field | Type | Description |
|---|---|---|
| `issuer` | `CommandSender` | The staff member who issued the infraction. |
| `reason` | `String` | The infraction reason. Settable. |
### PlayerBanEvent
Fires when a player is banned. Extends `PlayerInfractionEvent`.
| Field | Type | Description |
|---|---|---|
| `targetUUID` | `UUID` | The banned player's UUID. |
| `targetName` | `String` | The banned player's name. |
| `duration` | `long` | Ban duration in milliseconds. Settable. |
### PlayerBanIPEvent
Fires when an IP address is banned. Extends `PlayerInfractionEvent`.
| Field | Type | Description |
|---|---|---|
| `ip` | `String` | The banned IP address. |
| `duration` | `long` | Ban duration in milliseconds. Settable. |
### PlayerKickEvent
Fires when a player is kicked. Extends `PlayerInfractionEvent`.
| Field | Type | Description |
|---|---|---|
| `targetUUID` | `UUID` | The kicked player's UUID. |
| `targetName` | `String` | The kicked player's name. |
### PlayerMuteEvent
Fires when a player is muted. Extends `PlayerInfractionEvent`.
| Field | Type | Description |
|---|---|---|
| `targetUUID` | `UUID` | The muted player's UUID. |
| `targetName` | `String` | The muted player's name. |
| `duration` | `long` | Mute duration in milliseconds. Settable. |
### PlayerWarnEvent
Fires when a player is warned. Extends `PlayerInfractionEvent`.
| Field | Type | Description |
|---|---|---|
| `targetUUID` | `UUID` | The warned player's UUID. |
| `targetName` | `String` | The warned player's name. |
### PlayerUnbanEvent
Fires when a player is unbanned. Extends `PlayerInfractionEvent`.
| Field | Type | Description |
|---|---|---|
| `targetUUID` | `UUID` | The unbanned player's UUID. |
| `targetName` | `String` | The unbanned player's name. |
### PlayerUnbanIPEvent
Fires when an IP ban is removed. Extends `PlayerInfractionEvent`.
| Field | Type | Description |
|---|---|---|
| `ip` | `String` | The unbanned IP address. |
### PlayerUnmuteEvent
Fires when a player is unmuted. Extends `PlayerInfractionEvent`.
| Field | Type | Description |
|---|---|---|
| `targetUUID` | `UUID` | The unmuted player's UUID. |
| `targetName` | `String` | The unmuted player's name. |
```java
@EventHandler
public void onBan(PlayerBanEvent event) {
// Log bans to an external service
logToDiscord(event.getIssuer().getName() + " banned " + event.getTargetName());
}
@EventHandler
public void onMute(PlayerMuteEvent event) {
// Extend mute duration for repeat offenders
if (isRepeatOffender(event.getTargetUUID())) {
event.setDuration(event.getDuration() * 2);
}
}
```
---
## Privacy Events
### PlayerBlockEvent
Fires when a player blocks another player. Implements `Cancellable`.
| Field | Type | Description |
|---|---|---|
| `player` | `Player` | The player performing the block. |
| `target` | `Player` | The player being blocked. |
### PlayerUnblockEvent
Fires when a player unblocks another player. Implements `Cancellable`.
| Field | Type | Description |
|---|---|---|
| `player` | `Player` | The player performing the unblock. |
| `target` | `Player` | The player being unblocked. |
```java
@EventHandler
public void onBlock(PlayerBlockEvent event) {
// Prevent blocking staff members
if (event.getTarget().hasPermission("staff.unblockable")) {
event.setCancelled(true);
event.getPlayer().sendMessage("You cannot block staff members.");
}
}
```
---
## Listening for Events
Register listeners using standard Bukkit event handling. All Bookshelf events use the same `HandlerList` from `BaseEvent`.
```java
public class MyListener implements Listener {
@EventHandler
public void onChat(PlayerChatEvent event) {
// Handle chat
}
@EventHandler(priority = EventPriority.HIGH)
public void onMessage(PlayerMessageEvent event) {
// Handle with high priority
}
}
// Register in onEnable()
getServer().getPluginManager().registerEvents(new MyListener(), this);
```
---
## Related Pages
- [[Bookshelf/Developers/API Reference]] - Core API classes
- [[Bookshelf/Developers/Overview]] - Module architecture
- [[Command System]] - Command creation