docs/paper/events/SKILL.md
# Events Skill — Paper ## Purpose Reference this skill when implementing event listeners on a Paper server. Covers event registration, priority ordering, cancellation, and thread-safety rules for all Bukkit/Paper events. ## When to Use This Skill - Adding a listener for any `org.bukkit.event.*` or `io.papermc.paper.event.*` event - Deciding event priority for overlapping listeners - Handling cancellable events correctly - Working with async events (especially chat) ## API Quick Reference | C
npx skillsauth add MrPippi/MPS docs/paper/eventsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reference this skill when implementing event listeners on a Paper server. Covers event registration, priority ordering, cancellation, and thread-safety rules for all Bukkit/Paper events.
org.bukkit.event.* or io.papermc.paper.event.* event| Class / Method | Purpose | Notes |
|---------------|---------|-------|
| Listener (interface) | Marker interface for event listener classes | Required on listener class |
| @EventHandler | Marks a method as an event handler | Method must have exactly one event parameter |
| EventPriority | Controls listener order: LOWEST → LOW → NORMAL → HIGH → HIGHEST → MONITOR | MONITOR = read-only, never cancel here |
| ignoreCancelled = true | Skip handler if event already cancelled | Recommended for most handlers |
| PluginManager#registerEvents(Listener, Plugin) | Register all @EventHandler methods in a listener | Call once in onEnable |
| HandlerList#unregisterAll(Plugin) | Unregister all listeners for a plugin | Called automatically on onDisable |
| Event#isCancelled() | Check if event is already cancelled | |
| Cancellable#setCancelled(boolean) | Cancel the event | Prevents default action |
| AsyncChatEvent | Paper's modern async chat event | Replaces deprecated AsyncPlayerChatEvent |
package com.yourorg.myplugin.events;
import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.java.JavaPlugin;
public class PlayerJoinListener implements Listener {
private final JavaPlugin plugin;
public PlayerJoinListener(JavaPlugin plugin) {
this.plugin = plugin;
}
// Standard listener: NORMAL priority, skip if already cancelled
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
// Set custom join message using Adventure components
event.joinMessage(Component.text("+ ")
.color(NamedTextColor.GREEN)
.append(Component.text(player.getName())
.color(NamedTextColor.WHITE)));
}
@EventHandler(priority = EventPriority.NORMAL)
public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
event.quitMessage(Component.text("- ")
.color(NamedTextColor.RED)
.append(Component.text(player.getName())
.color(NamedTextColor.WHITE)));
}
// Async chat event — DO NOT call Bukkit object mutations here
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onChat(AsyncChatEvent event) {
Player player = event.getPlayer();
Component originalMessage = event.message();
// Reading player data: safe (immutable snapshot)
String name = player.getName();
// Modifying the message: safe (event object, not Bukkit world state)
event.message(Component.text("[Chat] ")
.color(NamedTextColor.GRAY)
.append(originalMessage));
// NOT safe here: player.teleport(), world.dropItem(), etc.
// If you must do Bukkit ops, schedule back to main thread:
// Bukkit.getScheduler().runTask(plugin, () -> { ... });
}
}
Registering the listener in your main class:
@Override
public void onEnable() {
getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this);
}
Calling Bukkit methods from async event handlers: AsyncChatEvent fires on an async thread. Calling player.teleport(), world.dropItem(), or any world-mutating method will throw IllegalStateException or cause data corruption. Schedule back to main thread with Bukkit.getScheduler().runTask(plugin, runnable).
Using MONITOR priority to cancel events: MONITOR is for read-only observation after all other plugins have processed the event. Cancelling at MONITOR is undefined behavior and may be ignored.
Not using ignoreCancelled = true: Without it, your handler fires even when another plugin has already cancelled the event, potentially overriding that decision. Add ignoreCancelled = true unless you specifically need to handle already-cancelled events.
Registering listeners multiple times: Calling registerEvents() twice on the same listener instance doubles all event firings. Register each listener exactly once in onEnable.
Still using AsyncPlayerChatEvent: This event is deprecated in Paper 1.19+ and removed in some builds. Use AsyncChatEvent from io.papermc.paper.event.player instead.
AsyncChatEvent is the standard chat event. AsyncPlayerChatEvent is deprecated.PlayerMoveEvent fires very frequently (every tick a player is loaded). Use event.hasExplicitlyMoved() or event.hasChangedPosition() to filter out head rotations.development
透過 NMS Scoreboard/Objective/Team API 操作 sidebar、tablist 顯示名稱與計分板(Paper NMS + Mojang-mapped)/ Operate sidebar, tablist, and scoreboard via NMS Scoreboard/Objective/Team API
research
操作 GameProfile 進行 skin 注入,用於 NPC 外觀設定與假玩家實體(Paper NMS + Mojang-mapped)/ Manipulate GameProfile for skin injection used in NPC appearance and fake player entities
tools
透過 ClientboundLevelParticlesPacket 實現進階 NMS 粒子效果:客戶端專屬、大量粒子、自定義參數(Paper NMS + Mojang-mapped)/ Advanced NMS particle effects via ClientboundLevelParticlesPacket with per-client and bulk support
documentation
直接操作 CompoundTag 讀寫物品、實體、方塊實體的 NBT 資料(Paper NMS + Mojang-mapped)/ Read and write NBT data on items, entities, and block entities via CompoundTag