Add a clickable inventory GUI
Wire up a chest-style menu with clickable buttons, 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, package, main_class,
plugin_name. If it's missing, tell the user to run /setup-platform (and /scaffold) first.
- Read the reference and pitfalls (relative to this skill;
Glob as fallback):
../../references/api/inventory-gui.md — Inventory, InventoryHolder, click handling
../../references/pitfalls.md — the "Events", "Threading", and "Memory leaks" sections
- Look at existing source to match style:
Glob src/main/java/**/*.java, read the main class
and any existing menu/listener classes.
Phase 2: Clarify the menu
Ask (AskUserQuestion or plain text) only what you can't infer:
- Size and layout — rows (a chest is 9 per row, max 6 rows / 54 slots) and the menu title.
- Items — which slots hold which
Material, their display names/lore, and whether any are
filler/decoration vs. real buttons.
- What each click does — per button: run a command, give an item, toggle a setting, open a
sub-menu, close. Note whether left/right/shift-click should differ.
- Who opens it and how — a command, an event, an NPC interaction.
- Dynamic content — is the menu static, or does it change per player / need pagination
(next/prev buttons over a list too big for one screen)? Pagination state lives on the holder.
Phase 3: Choose the identification strategy
Decide from the stack and explain the pick briefly — how the click handler knows a click is in
this menu matters for safety:
- Custom
InventoryHolder (recommended) — create the inventory with
Bukkit.createInventory(new YourMenuHolder(...), size, title) and identify clicks by
event.getInventory().getHolder() instanceof YourMenuHolder. Robust and lets the holder carry
per-menu state (e.g. page number, target player).
- Title-string match — fragile: titles can collide, and on Paper titles are
Components
(compare via the Adventure API, not equals on a legacy string). Only for the simplest cases;
never rely on it alone to gate the anti-theft cancel.
Phase 4: Implement
Generate, in the project's package:
- A
YourMenuHolder implements InventoryHolder carrying any per-open state and building/
returning the Inventory.
- A GUI/menu class that builds items via
ItemStack + ItemMeta — set display name and
lore (on Paper use Adventure Component via displayName(...); on Spigot
setDisplayName(String)), place them in slots, and expose an open(Player) method.
- An
InventoryClickEvent handler (a registered Listener) that:
- Confirms the inventory is this menu (holder check), then always
event.setCancelled(true) for menu-slot clicks so players can't take the items
(anti-theft) — do this before running the button action.
- Dispatches on the clicked slot (and click type if relevant) to the button behaviour.
- Guards against clicks in the player's own inventory / outside slots as intended, and blocks
shift-click and number-key hotbar swaps that could pull items out of the menu.
- Reads
event.getRawSlot() to tell top-inventory (menu) slots from the player's inventory,
and event.getWhoClicked() (cast to Player) for the actor — do any world side effects on
the main thread (the click event already runs there; only added async work needs a hop).
- Register the listener in
onEnable via getServer().getPluginManager().registerEvents.
Optionally handle InventoryCloseEvent to clean up per-menu state (avoid leaking Player
references — key state by UUID).
Show the new/edited files (holder, menu class, click listener, onEnable diff) and get approval
before writing.
Phase 5: Verify + hand off
- Re-check the pitfalls: menu-slot clicks are cancelled (items can't be stolen), the menu is
identified by
InventoryHolder (not by title string alone), the click listener is
registered, no Player objects retained long-term (use UUID), and any async work in a
button hops back to the main thread before touching the world/inventory.
- Suggest next steps: "
/build to compile, then /run-server; open the menu and click each
button to confirm actions fire and items stay put."
Do not fabricate APIs for a Minecraft version newer than ../../references/api/VERSION.md
documents — if unsure a method exists in the target version, say so and verify.
1---2name: add-gui3description: Add a clickable inventory GUI to a Minecraft plugin — a menu built from an Inventory of ItemStacks with an InventoryClickEvent handler that cancels menu clicks. Use this whenever the user wants a chest menu, GUI, clickable interface, item-based menu, shop screen, settings panel, paginated selector, or 'open an inventory the player can click buttons in' for their Bukkit/Paper plugin. Reads the target stack from .mcplugin/config.yml.4---56# Add a clickable inventory GUI78Wire up a chest-style menu with clickable buttons, matching the project's stack and conventions.9Follow the collaborative rule: propose the design, show the code, then write after the user10approves.1112## Phase 1: Load context1314- Read `.mcplugin/config.yml` for `platform`, `mc_version`, `package`, `main_class`,15 `plugin_name`. If it's missing, tell the user to run `/setup-platform` (and `/scaffold`) first.16- Read the reference and pitfalls (relative to this skill; `Glob` as fallback):17 - `../../references/api/inventory-gui.md` — `Inventory`, `InventoryHolder`, click handling18 - `../../references/pitfalls.md` — the "Events", "Threading", and "Memory leaks" sections19- Look at existing source to match style: `Glob` `src/main/java/**/*.java`, read the main class20 and any existing menu/listener classes.2122## Phase 2: Clarify the menu2324Ask (AskUserQuestion or plain text) only what you can't infer:25- **Size and layout** — rows (a chest is 9 per row, max 6 rows / 54 slots) and the menu title.26- **Items** — which slots hold which `Material`, their display names/lore, and whether any are27 filler/decoration vs. real buttons.28- **What each click does** — per button: run a command, give an item, toggle a setting, open a29 sub-menu, close. Note whether left/right/shift-click should differ.30- **Who opens it and how** — a command, an event, an NPC interaction.31- **Dynamic content** — is the menu static, or does it change per player / need pagination32 (next/prev buttons over a list too big for one screen)? Pagination state lives on the holder.3334## Phase 3: Choose the identification strategy3536Decide from the stack and explain the pick briefly — **how the click handler knows a click is in37*this* menu** matters for safety:38- **Custom `InventoryHolder`** (recommended) — create the inventory with39 `Bukkit.createInventory(new YourMenuHolder(...), size, title)` and identify clicks by40 `event.getInventory().getHolder() instanceof YourMenuHolder`. Robust and lets the holder carry41 per-menu state (e.g. page number, target player).42- **Title-string match** — fragile: titles can collide, and on Paper titles are `Component`s43 (compare via the Adventure API, not `equals` on a legacy string). Only for the simplest cases;44 never rely on it alone to gate the anti-theft cancel.4546## Phase 4: Implement4748Generate, in the project's package:491. **A `YourMenuHolder implements InventoryHolder`** carrying any per-open state and building/50 returning the `Inventory`.512. **A GUI/menu class** that builds items via `ItemStack` + `ItemMeta` — set display name and52 lore (on Paper use Adventure `Component` via `displayName(...)`; on Spigot53 `setDisplayName(String)`), place them in slots, and expose an `open(Player)` method.543. **An `InventoryClickEvent` handler** (a registered `Listener`) that:55 - Confirms the inventory is this menu (holder check), then **always56 `event.setCancelled(true)` for menu-slot clicks** so players can't take the items57 (anti-theft) — do this *before* running the button action.58 - Dispatches on the clicked slot (and click type if relevant) to the button behaviour.59 - Guards against clicks in the player's own inventory / outside slots as intended, and blocks60 shift-click and number-key hotbar swaps that could pull items out of the menu.61 - Reads `event.getRawSlot()` to tell top-inventory (menu) slots from the player's inventory,62 and `event.getWhoClicked()` (cast to `Player`) for the actor — do any world side effects on63 the main thread (the click event already runs there; only *added* async work needs a hop).644. **Register the listener** in `onEnable` via `getServer().getPluginManager().registerEvents`.65 Optionally handle `InventoryCloseEvent` to clean up per-menu state (avoid leaking `Player`66 references — key state by `UUID`).6768Show the new/edited files (holder, menu class, click listener, `onEnable` diff) and get approval69before writing.7071## Phase 5: Verify + hand off7273- Re-check the pitfalls: menu-slot clicks are **cancelled** (items can't be stolen), the menu is74 **identified by `InventoryHolder`** (not by title string alone), the click listener is75 **registered**, no `Player` objects retained long-term (use `UUID`), and any async work in a76 button hops back to the main thread before touching the world/inventory.77- Suggest next steps: "`/build` to compile, then `/run-server`; open the menu and click each78 button to confirm actions fire and items stay put."7980Do not fabricate APIs for a Minecraft version newer than `../../references/api/VERSION.md`81documents — if unsure a method exists in the target version, say so and verify.