Class CustomMenu
java.lang.Object
com.trevlar.slotkit.api.menu.CustomMenu
A complete custom menu from one
define(...).register() chain. It replaces
the four or so pieces a consumer used to write by hand for every custom
CustomContainerMenu: its own MenuType registration, its own
client-to-server open payload and receiver, its own MenuScreens.register,
and its own open call. Modeled on ParityGroup.register(...).
Recipe
// Consumer COMMON initializer (runs both sides):
public static final CustomMenu CUSTOM = CustomMenu
.define(Identifier.fromNamespaceAndPath(MOD_ID, "custom_menu"), MyMenu::buildHandler)
.title(Component.literal("My Custom Menu")) // optional; default = "menu.<ns>.<path>"
.validWhen(player -> player.isAlive()) // needed for a client to ask to open it
.arm(MyMenu::armBehaviors) // optional; arm slot behavior by Address, same chain
.register();
// Consumer CLIENT initializer: how each panel looks, and the screen class if the
// menu has its own (default CustomContainerScreen::new). Never in common code: naming a
// client class there crashes a dedicated server.
ClientMenu.of(MyMod.CUSTOM)
.screen(MyScreen::new)
.panel("mymod:menu:main", p -> p.in(ModeScope.BOTH, Placement.screen(PanelPosition.main())).add(label));
// The handler factory receives the menu's own MenuType (no reach-back to the
// CUSTOM handle being assigned above, see HandlerFactory):
public static CustomContainerMenu buildHandler(
MenuType<CustomContainerMenu> type, int syncId, Inventory inv) {
return CustomContainerMenu.builder(type) // type handed in, never CUSTOM.getType()
.panel("mymod:menu:main", p -> p.group("items", storage))
.build(syncId);
}
// Open it:
ClientMenu.of(CUSTOM).requestOpen(); // client: asks the server, which checks validWhen
CUSTOM.open(serverPlayer); // server-side direct (no networking)
Why the factory takes the MenuType
A handler must callCustomContainerMenu.builder(MenuType) with this menu's
MenuType, but that type is built inside register(). If the factory
had to reach back to the static handle the define(...).register() chain is
still assigning (CUSTOM.type(), say), it would only work because the
factory runs late. A consumer who reads the type early, or inlines the define
call, would hit a forward reference or a null. So the factory is a
CustomMenu.HandlerFactory that is handed the new type: both the MenuType's
client-side factory lambda and the server-side SimpleMenuProvider in
open(ServerPlayer) pass type in. The consumer never names the
handle.
The client/server split
register() runs in common init on either side. It builds and registers the
MenuType, and records the handle by id so the server can resolve an open
request. Everything the client adds (the screen class, how each panel looks, the
open request) belongs to ClientMenu, in SlotKit's client source set, so
this class names no client type.
Who may open it, and for how long
CustomMenu.Builder.validWhen is the menu's context predicate. A client's request to
open the menu is honored only when the menu declares one and it holds for that
player; the open menu closes when it stops holding (vanilla's stillValid,
checked on the server every tick). Without one, the menu opens only from the
server (open(ServerPlayer)) and stays open while the player is alive.
Namespace your panel ids
This menu'sid() is globally unique, but it does not scope the
Addresses of the slots its handler creates.
Those are keyed globally by (panelId, groupId, localIndex) (see
CreatedSlotAdapter.addressOf). So the panel ids the handler declares
(through CustomContainerMenu.builder(MenuType)) must be namespaced per mod, such as
"mymod:menu:main", just as container-parity panel ids are. Otherwise two
mods that both name a panel "main" collide and set behavior on each
other's slots. The same id is used for layout references and for addresses in
CustomMenu.Builder.arm.-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final classConfigures the menu.static interfaceBuilds the menu'sCustomContainerMenu. -
Method Summary
Modifier and TypeMethodDescriptionstatic List<CustomMenu> all()Every registered menu, in registration order.static @Nullable CustomMenubyId(net.minecraft.resources.Identifier id) Resolves a menu by the id carried in an open payload, ornullif no menu was registered under that id.static CustomMenu.Builderdefine(net.minecraft.resources.Identifier id, CustomMenu.HandlerFactory handlerFactory) Starts a custom-menu definition.static booleanhandleOpenRequest(net.minecraft.server.level.ServerPlayer player, net.minecraft.resources.Identifier id) A client's request to open menuid(the generic open payload), checked on the server: an unknown menu, a menu withoutCustomMenu.Builder.validWhen, or one whose predicate does not hold forplayeris refused and logged.net.minecraft.resources.Identifierid()This menu's registry id (also the open-payload key).voidopen(net.minecraft.server.level.ServerPlayer player) Opens this menu for the player, directly on the server with no extra networking.net.minecraft.world.inventory.MenuType<CustomContainerMenu> type()This menu's registeredMenuType.
-
Method Details
-
type
This menu's registeredMenuType. -
id
public net.minecraft.resources.Identifier id()This menu's registry id (also the open-payload key). -
all
Every registered menu, in registration order. The client registers their screens. -
open
public void open(net.minecraft.server.level.ServerPlayer player) Opens this menu for the player, directly on the server with no extra networking. Vanilla syncs the open to the client, which rebuilds the menu through this menu'sMenuTypeand shows the registered screen. -
byId
Resolves a menu by the id carried in an open payload, ornullif no menu was registered under that id. -
handleOpenRequest
@Internal public static boolean handleOpenRequest(net.minecraft.server.level.ServerPlayer player, net.minecraft.resources.Identifier id) A client's request to open menuid(the generic open payload), checked on the server: an unknown menu, a menu withoutCustomMenu.Builder.validWhen, or one whose predicate does not hold forplayeris refused and logged. Returns whether it opened. Main thread.