Add an event listener
Wire up a listener that reacts to a Bukkit/Paper event, matching the project's stack and
conventions. Follow the collaborative rule: propose the design, show the code, then write
after the user approves.
Phase 1: Load context
- Read
.mcplugin/config.yml for platform, mc_version, api_version, package,
main_class, plugin_name. If it's missing, tell the user to run /setup-platform (and
/scaffold) first.
- Read the event reference and pitfalls (relative to this skill;
Glob as fallback):
../../references/api/events.md — event catalogue, handler shape, priority, Paper events
../../references/pitfalls.md — the "Events" and "Threading" sections
- Look at existing source to match style:
Glob src/main/java/**/*.java, read the main class
and any existing Listener classes (reuse one if it's the natural home for the new handler).
Phase 2: Clarify what to listen for
Ask (AskUserQuestion or plain text) only what you can't infer from the argument/description:
- Which event(s) — confirm the exact class name (e.g.
PlayerJoinEvent,
BlockBreakEvent, EntityDamageByEntityEvent). If unsure the event exists in the target
version, say so and verify against events.md rather than guessing. Common families to map
the user's intent onto:
- Player lifecycle:
PlayerJoinEvent, PlayerQuitEvent, PlayerRespawnEvent,
PlayerDeathEvent.
- World interaction:
BlockBreakEvent, BlockPlaceEvent, PlayerInteractEvent.
- Combat/damage:
EntityDamageEvent, EntityDamageByEntityEvent, EntityDeathEvent.
- Inventory/chat:
InventoryClickEvent, AsyncPlayerChatEvent (chat fires async —
don't touch the world from it without hopping to the main thread; Paper has its own
Component-based chat event, prefer it there).
- What should happen when it fires, and under what condition (filter by world, permission,
block type, item, etc.).
- Priority —
LOWEST→HIGHEST for ordering, or MONITOR to observe the final outcome
(read-only; never mutate in a MONITOR handler).
- Cancelled events — should the handler skip events another plugin already cancelled?
(
ignoreCancelled = true), or should it do the cancelling (event.setCancelled(true))?
Phase 3: Choose the handling strategy
Decide from the stack and explain the pick briefly:
- One handler vs. several — group related handlers in a single
Listener class; split only
when concerns differ. Prefer adding to an existing listener over creating a new one.
- Blocking work (DB/HTTP/file I/O) inside the handler → don't do it inline. Run it async,
then hop back to the main thread to touch the world/player. On Folia there is no main thread —
use the region/entity schedulers (
../../references/api/scheduler.md).
- Text output — on Paper prefer Adventure
Component (e.g. player.sendMessage(Component…))
over legacy String messages; on Spigot fall back to String.
- Attaching data to the involved item/entity → use
PersistentDataContainer + a reused
NamespacedKey, not the deprecated metadata API.
Phase 4: Implement
Generate, in the project's package:
- A class implementing
org.bukkit.event.Listener with one @EventHandler method per event:
- Exactly one event parameter; annotation present (a missing
@EventHandler silently no-ops).
priority / ignoreCancelled set as clarified.
- Null-checks where the API can return null (
event.getClickedBlock(), ItemMeta, etc.).
- No heavy work in hot events —
PlayerMoveEvent, BlockPhysicsEvent, entity ticking
fire constantly; early-return when nothing relevant changed (e.g. same block position).
- Cancel by calling
event.setCancelled(true) when the intent is to stop the action —
returning early from the handler does nothing on its own.
- Register it: in the main class
onEnable, call
getServer().getPluginManager().registerEvents(new YourListener(this), this); (a listener
does nothing until registered). Pass any dependencies the handler needs via the constructor.
If the plugin holds per-player state built from these events, clear it in onDisable and on
PlayerQuitEvent — key it by UUID, never by a retained Player object (memory leak).
Show the new/edited files (the listener class, the onEnable diff) and get approval before
writing.
Phase 5: Verify + hand off
- Re-check the pitfalls: listener registered in
onEnable, every handler has @EventHandler
with a single correct event parameter, cancelled-event handling matches intent
(isCancelled / ignoreCancelled vs. setCancelled(true)), MONITOR handlers don't mutate,
no blocking or heavy work in hot/high-frequency events (async + main-thread hop where needed).
- Suggest next steps: "
/build to compile, then /run-server to trigger the event in game."
Do not fabricate APIs for a Minecraft version newer than ../../references/api/VERSION.md
documents — if unsure an event or method exists in the target version, say so and verify.
1---2name: add-listener3description: Add an event listener to a Minecraft plugin — a Listener class with @EventHandler methods, wired into onEnable with registerEvents. Use this whenever the user wants to react to something happening in-game: on join/quit, block break/place, player interact, entity damage/death, inventory click, chat, respawn, or any Bukkit/Paper event. Handles priority, cancelled-event handling, and hot-event performance. Reads the target stack from .mcplugin/config.yml.4---56# Add an event listener78Wire up a listener that reacts to a Bukkit/Paper event, matching the project's stack and9conventions. Follow the collaborative rule: propose the design, show the code, then write10after the user approves.1112## Phase 1: Load context1314- Read `.mcplugin/config.yml` for `platform`, `mc_version`, `api_version`, `package`,15 `main_class`, `plugin_name`. If it's missing, tell the user to run `/setup-platform` (and16 `/scaffold`) first.17- Read the event reference and pitfalls (relative to this skill; `Glob` as fallback):18 - `../../references/api/events.md` — event catalogue, handler shape, priority, Paper events19 - `../../references/pitfalls.md` — the "Events" and "Threading" sections20- Look at existing source to match style: `Glob` `src/main/java/**/*.java`, read the main class21 and any existing `Listener` classes (reuse one if it's the natural home for the new handler).2223## Phase 2: Clarify what to listen for2425Ask (AskUserQuestion or plain text) only what you can't infer from the argument/description:26- **Which event(s)** — confirm the exact class name (e.g. `PlayerJoinEvent`,27 `BlockBreakEvent`, `EntityDamageByEntityEvent`). If unsure the event exists in the target28 version, say so and verify against `events.md` rather than guessing. Common families to map29 the user's intent onto:30 - *Player lifecycle:* `PlayerJoinEvent`, `PlayerQuitEvent`, `PlayerRespawnEvent`,31 `PlayerDeathEvent`.32 - *World interaction:* `BlockBreakEvent`, `BlockPlaceEvent`, `PlayerInteractEvent`.33 - *Combat/damage:* `EntityDamageEvent`, `EntityDamageByEntityEvent`, `EntityDeathEvent`.34 - *Inventory/chat:* `InventoryClickEvent`, `AsyncPlayerChatEvent` (chat fires **async** —35 don't touch the world from it without hopping to the main thread; Paper has its own36 Component-based chat event, prefer it there).37- **What should happen** when it fires, and under what condition (filter by world, permission,38 block type, item, etc.).39- **Priority** — `LOWEST`→`HIGHEST` for ordering, or `MONITOR` to observe the final outcome40 (read-only; never mutate in a `MONITOR` handler).41- **Cancelled events** — should the handler skip events another plugin already cancelled?42 (`ignoreCancelled = true`), or should it *do* the cancelling (`event.setCancelled(true)`)?4344## Phase 3: Choose the handling strategy4546Decide from the stack and explain the pick briefly:47- **One handler vs. several** — group related handlers in a single `Listener` class; split only48 when concerns differ. Prefer adding to an existing listener over creating a new one.49- **Blocking work** (DB/HTTP/file I/O) inside the handler → don't do it inline. Run it async,50 then hop back to the main thread to touch the world/player. On Folia there is no main thread —51 use the region/entity schedulers (`../../references/api/scheduler.md`).52- **Text output** — on Paper prefer Adventure `Component` (e.g. `player.sendMessage(Component…)`)53 over legacy `String` messages; on Spigot fall back to `String`.54- **Attaching data** to the involved item/entity → use `PersistentDataContainer` + a reused55 `NamespacedKey`, not the deprecated metadata API.5657## Phase 4: Implement5859Generate, in the project's package:601. A class implementing `org.bukkit.event.Listener` with one `@EventHandler` method per event:61 - Exactly one event parameter; annotation present (a missing `@EventHandler` silently no-ops).62 - `priority` / `ignoreCancelled` set as clarified.63 - Null-checks where the API can return null (`event.getClickedBlock()`, `ItemMeta`, etc.).64 - **No heavy work in hot events** — `PlayerMoveEvent`, `BlockPhysicsEvent`, entity ticking65 fire constantly; early-return when nothing relevant changed (e.g. same block position).66 - Cancel by calling `event.setCancelled(true)` when the intent is to *stop* the action —67 returning early from the handler does nothing on its own.682. **Register it**: in the main class `onEnable`, call69 `getServer().getPluginManager().registerEvents(new YourListener(this), this);` (a listener70 does nothing until registered). Pass any dependencies the handler needs via the constructor.71 If the plugin holds per-player state built from these events, clear it in `onDisable` and on72 `PlayerQuitEvent` — key it by `UUID`, never by a retained `Player` object (memory leak).7374Show the new/edited files (the listener class, the `onEnable` diff) and get approval before75writing.7677## Phase 5: Verify + hand off7879- Re-check the pitfalls: listener **registered** in `onEnable`, every handler has `@EventHandler`80 with a single correct event parameter, cancelled-event handling matches intent81 (`isCancelled` / `ignoreCancelled` vs. `setCancelled(true)`), `MONITOR` handlers don't mutate,82 no blocking or heavy work in hot/high-frequency events (async + main-thread hop where needed).83- Suggest next steps: "`/build` to compile, then `/run-server` to trigger the event in game."8485Do not fabricate APIs for a Minecraft version newer than `../../references/api/VERSION.md`86documents — if unsure an event or method exists in the target version, say so and verify.