Class CustomMenu

java.lang.Object
com.trevlar.slotkit.api.menu.CustomMenu

public final class CustomMenu extends Object
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 call CustomContainerMenu.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's id() 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 Classes
    Modifier and Type
    Class
    Description
    static final class 
    Configures the menu.
    static interface 
    Builds the menu's CustomContainerMenu.
  • Method Summary

    Modifier and Type
    Method
    Description
    all()
    Every registered menu, in registration order.
    static @Nullable CustomMenu
    byId(net.minecraft.resources.Identifier id)
    Resolves a menu by the id carried in an open payload, or null if no menu was registered under that id.
    define(net.minecraft.resources.Identifier id, CustomMenu.HandlerFactory handlerFactory)
    Starts a custom-menu definition.
    static boolean
    handleOpenRequest(net.minecraft.server.level.ServerPlayer player, net.minecraft.resources.Identifier id)
    A client's request to open menu id (the generic open payload), checked on the server: an unknown menu, a menu without CustomMenu.Builder.validWhen, or one whose predicate does not hold for player is refused and logged.
    net.minecraft.resources.Identifier
    id()
    This menu's registry id (also the open-payload key).
    void
    open(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>
    This menu's registered MenuType.

    Methods inherited from class Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Method Details

    • define

      public static CustomMenu.Builder define(net.minecraft.resources.Identifier id, CustomMenu.HandlerFactory handlerFactory)
      Starts a custom-menu definition.
      Parameters:
      id - the registry id for the menu's MenuType, also the key the generic open payload carries
      handlerFactory - builds the CustomContainerMenu from (type, syncId, playerInventory). It is handed the menu's own MenuType, so it never reaches back to the handle being assigned. Runs the same way on both sides (server through the menu provider, client through the MenuType factory), so storages must be the same size on both for sync
    • type

      public net.minecraft.world.inventory.MenuType<CustomContainerMenu> type()
      This menu's registered MenuType.
    • id

      public net.minecraft.resources.Identifier id()
      This menu's registry id (also the open-payload key).
    • all

      @Internal public static List<CustomMenu> 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's MenuType and shows the registered screen.
    • byId

      @Internal public static @Nullable CustomMenu byId(net.minecraft.resources.Identifier id)
      Resolves a menu by the id carried in an open payload, or null if 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 menu id (the generic open payload), checked on the server: an unknown menu, a menu without CustomMenu.Builder.validWhen, or one whose predicate does not hold for player is refused and logged. Returns whether it opened. Main thread.