Events
Minecraft Java Plugin Claude Skills
npx -y skills add MrPippi/MJP-Claude-Skills --skill eventsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.9 KB, as published. Nobody here has run it
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.*orio.papermc.paper.event.*event - Deciding event priority for overlapping listeners
- Handling cancellable events correctly
- Working with async events (especially chat)
API Quick Reference
| 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 |
Code Pattern
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);
}
Common Pitfalls
-
Calling Bukkit methods from async event handlers:
AsyncChatEventfires on an async thread. Callingplayer.teleport(),world.dropItem(), or any world-mutating method will throwIllegalStateExceptionor cause data corruption. Schedule back to main thread withBukkit.getScheduler().runTask(plugin, runnable). -
Using
MONITORpriority to cancel events:MONITORis for read-only observation after all other plugins have processed the event. Cancelling atMONITORis 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. AddignoreCancelled = trueunless 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 inonEnable. -
Still using
AsyncPlayerChatEvent: This event is deprecated in Paper 1.19+ and removed in some builds. UseAsyncChatEventfromio.papermc.paper.event.playerinstead.
Version Notes
- 1.21:
AsyncChatEventis the standard chat event.AsyncPlayerChatEventis deprecated. - 1.21.1: No breaking changes to event API vs 1.21.
- Both:
PlayerMoveEventfires very frequently (every tick a player is loaded). Useevent.hasExplicitlyMoved()orevent.hasChangedPosition()to filter out head rotations.
Related Skills
- player-events.md — Detailed PlayerJoinEvent, PlayerMoveEvent, PlayerInteractEvent reference
- world-events.md — Block and entity events
- custom-events.md — Creating and firing your own events
- async-events.md — Threading rules for async events
- ../commands/SKILL.md — Command registration