Effective Menu
Base class for a chest-style GUI menu laid out with a character pattern.
A subclass supplies a title, a row-based getPattern and a getSymbolsToItems mapping each pattern character to an item plus its click handlers. Optionally, getFreeSlotSymbol marks slots the player may place/take items in — changes are reported through onSlotChanged. Like the other bases, the menu registers itself on construction; open it with getMenu.
Per-viewer layout
getPattern, getSymbolsToItems and getFreeSlots all receive whoOpen — the player the menu is being built/handled for (nullable: null means the default, no-viewer layout) — so the layout can differ per viewer (e.g. slots unlocked by rank/purchase). A single menu object serves every player; there is no shared mutable "current player" field, so this is race-free even when several players open it at once. Ignore the parameter if your menu is static.
object ExampleMenu : EffectiveMenu() {
override fun getMenuTitle() = "Example"
override fun getPattern(whoOpen: Player?) = listOf(
" ",
" x ",
" ",
)
override fun getSymbolsToItems(whoOpen: Player?) = mapOf(
'x' to SlotData(ItemStack(Material.DIAMOND), listOf(
ClickData(ClickType.LEFT) { player -> player.sendMessage("hi") }
))
)
override fun getNamespacedData() = ExamplePlugin.instance to "example"
override fun getFreeSlotSymbol() = null
override fun getSlotsCount() = 27
override fun onSlotChanged(player: Player, slot: Int, item: ItemStack, wasPlaced: Boolean) = SlotChangeResult.ALLOW
}
// player.openInventory(ExampleMenu.getMenu(player))A built-in /emenu <menu> command opens any registered menu in-game.
Inheritors
Types
A click handler for a slot: which clicks (Bukkit ClickTypes, e.g. LEFT, RIGHT, MIDDLE, SHIFT_LEFT) trigger it — matched exactly, any one of the set — and the action to run for the clicking player. Register one type via ClickData(ClickType.LEFT) { … }, or several via ClickData(ClickType.LEFT, ClickType.SHIFT_LEFT) { … }.
What the framework does with the free-slot contents after onClose runs.
Whether a pending free-slot change is allowed. Returned from onSlotChanged before the change is applied: ALLOW lets it happen, CANCEL vetoes it (the underlying Bukkit event is cancelled, so the item stays where it was).
The item shown in a slot together with its click handlers.
Properties
The stable InventoryHolder shared by every inventory this menu opens. Read-only: it can't be replaced, but it is exposed so external code can identify "is this open/closed inventory mine?" via identity — inventory.holder === someMenu.inventoryHolder (every getMenu call builds a fresh Inventory but reuses this same holder).
Functions
Slot indices marked editable by getFreeSlotSymbol in whoOpen's layout, or null if none.
Pattern character marking player-editable slots, or null if the menu is read-only.
Inventory title shown at the top.
Owning plugin and a plugin-unique id; together they form the getNamespacedName.
Unique identity as "<plugin-name>:<id>", lowercased.
Row strings (9 chars each) mapping characters to items via getSymbolsToItems; null for empty.
Explicit inventory size, or null to size automatically from the pattern.
Maps each pattern character to its SlotData (item + click handlers).
Players who currently have this menu open.
Called when player closes this menu, before the framework decides what to do with the free-slot items. Read the final inventory state here if you need it (e.g. persist the contents), then return a CloseAction telling the framework how to dispose of the free slots.
Called before a free-slot change is applied. React to it and/or veto it: return SlotChangeResult.CANCEL to block the change (the Bukkit interaction is cancelled, so the item stays put), or SlotChangeResult.ALLOW to let it through. Use it as a slot filter (reject certain items), a read-only guard, or just to persist/update on change.
Opens the menu for whoOpen with contents pre-loaded into the free slots (in getFreeSlots order): the i-th non-null entry is placed into the i-th free slot; extras beyond the free-slot count are ignored.