Custom GUIs (menus)
The model
Section titled “The model”A menu is a server-authoritative chest-style GUI owned by the plugin. Every plugin has Menus (plugin.Menus) to create one: Chest(rows, title) for one to six rows of nine slots, Hopper(title) for five slots and Dispenser(title) for a 3 x 3 grid. The menu is a display: a click never moves an item in or out unless your handler allows it.
| Type | What it is for |
|---|---|
Menus |
creates menus owned by your plugin: Chest, Hopper, Dispenser, Create(type, title, rows) |
Menu |
the GUI: Slot(slot, icon, onClick), FillEmpty, OnClick, OnClose, Open(player), Close(player), Viewers, Title, Refresh(), Dispose() |
MenuClick |
one click: Player, Slot, Type (a MenuClickType), Clicked, Cursor, Allow(), Cancel(), Close() |
MenuCloseReason |
why a menu closed: Player, Plugin, Unload, Quit, Replaced, Invalid |
ItemStack |
an immutable item: new ItemStack("minecraft:diamond_sword", 1), with WithName, WithLore, WithEnchantment, WithCount and more |
Rules:
- A click on one of the menu’s own slots is cancelled by default.
MenuClick.Allow()lets it through, so the player can take the stack out or put one in, just as in a chest.Cancel()denies it for good. - A click that only touches the player’s own inventory passes unless a handler cancels it. Set
Menu.LockPlayerInventoryto deny those too. - Handlers run on the tick thread, like every other callback.
- A menu is closed for all viewers when its plugin unloads or reloads, and its handlers are dropped.
ItemStackis a value:WithName(...)returns a new stack.
A warp selector
Section titled “A warp selector”A menu with one icon per warp. Each icon’s click handler teleports the player.
using Mc.Scripting.Inventories;using Mc.Scripting.Text;
namespace Warps;
public sealed class PluginMain : Plugin{ private Menu _menu = null!;
protected override void OnEnable() { _menu = Menus.Chest(3, Component.Parse("<dark_green>Warps")) .FillEmpty(new ItemStack("minecraft:gray_stained_glass_pane").WithName(Component.Text(" "))) .Slot(11, new ItemStack("minecraft:grass_block").WithName(Component.Legacy("&aSpawn")), click => Go(click.Player, "minecraft:overworld", 0, 80, 0)) .Slot(13, new ItemStack("minecraft:netherrack").WithName(Component.Legacy("&cNether hub")), click => Go(click.Player, "minecraft:the_nether", 0, 70, 0)) .Slot(15, new ItemStack("minecraft:end_stone").WithName(Component.Legacy("&5The End")), click => Go(click.Player, "minecraft:the_end", 100, 50, 0)) .OnClose(player => Log.Debug($"{player.Name} closed the warp menu"));
Commands.Register(Command.Literal("warps") .Executes(ctx => { _menu.Open(ctx.RequirePlayer()); return 1; }), description: "Open the warp menu"); }
private static void Go(Player player, string world, double x, double y, double z) { if (Server.GetWorld(world) is not { } target) return; // the dimension is not loaded player.Teleport(new Location(target, x, y, z)); }}The menu does not depend on the command: call Open from any event handler and it works the same way.
Open returns false when a handler cancelled the opening or the player is offline. Close(player) closes the menu for one viewer, and Close() closes it for everybody.
Handling clicks and closes
Section titled “Handling clicks and closes”OnClick(handler)runs for every click in the menu, before the slot’s own handler. Use it for a rule that covers the whole menu, such as logging clicks or cancelling every click when the player is not allowed to use it.- A slot’s handler runs only for a click the menu did not cancel. Its
MenuClick.ClickedandCursorare the stacks before the click. OnClosehas two forms:OnClose(player => ...)for the closing player, andOnClose((player, reason) => ...)to also get theMenuCloseReason. The cursor stack has already been given back when it runs.Menu.Titlecan be changed at any time. The viewers’ screens are reopened with the same contents.
The engine also raises MenuClickEvent and MenuCloseEvent for every menu, not only yours. They carry the menu’s native id instead of a Menu object, so use them to observe menus you do not own.
- Items you put in a menu are ordinary stacks:
Get,Set,AddItemandRemoveItemon the menu read and write them. - Shared state between plugins: inter-plugin services.
- Plain events and commands: first plugin.