Class SlotOperations

java.lang.Object
com.trevlar.slotkit.api.window.SlotOperations

public final class SlotOperations extends Object
The slot operations vocabulary: the open set of things that can be done to a slot, what each slot lets through, and the one question every operation asks before it acts.

What an operation is

A slot has two independent facts about it, and keeping them apart is the whole design:
  • Category, what the slot is (SlotGroupCategory). Identity. A pocket declared PLAYER_INVENTORY is inventory storage, and every mod that searches the inventory should find it.
  • Operations, what may be done to it. Shift-click, double-click collect, drag-fill, a pickup landing in it, and whatever an inventory mod adds next.
They are separate because the useful cases cross: a slot that is inventory storage (so searches find it) may still want to sit out the bulk gestures, and a slot in a bespoke category may want full vanilla participation. Declaring the category does not decide the operations.

The vocabulary is open

An operation is named by a BehaviorKey, so any mod can add one: it declares its own key, defines it here so other mods can discover it, ships a name and a description for it (below), asks allows(SlotRef, BehaviorKey) in its own code before acting, and sends any clicks it performs under as(BehaviorKey, Runnable). SlotKit needs no change for a new operation to exist. Vanilla's own operations are BehaviorKeys.VANILLA_OPERATIONS, split one key per thing a player can do to a slot, and enforced at vanilla's seams by SlotKit itself.

Name and description

A settings screen that lists operations needs words for them. name(BehaviorKey) and description(BehaviorKey) are translatable components on keys derived from the id, the way keybinds work; a mod ships the lines in its own lang file:
"slot_operation.mymod.restock_take": "Restock takes from",
"slot_operation.mymod.restock_take.description": "Auto-restock may pull a refill out of this slot."
A missing line shows the key itself, Minecraft's own fallback.

Role

define(BehaviorKey, Role) records whether an operation takes items out of a slot, puts items in, or both (role(BehaviorKey)). A lock that protects an item already in a slot only cares about what takes it out, so a settings screen can hide the rest for any mod's operations, not just vanilla's. An operation that moves items between two slots is two keys, one TAKE and one PUT.

Where an operation applies

define(BehaviorKey, Role, AppliesTo) also records which slot groups the operation can act on at all, so a settings screen never offers a box the operation could never touch (appliesTo(BehaviorKey, SlotGroupId), groups(BehaviorKey)). An operation names the vanilla groups it applies to; every group a mod adds applies automatically, unless the operation opts out of it by id or by SlotGroupSet. An operation defined without saying applies to every group, so nothing defined before this existed changes. SlotKit declares vanilla's own operations from what vanilla does: nothing puts into an output slot, the crafter's result can't be touched at all, double-click collect skips the results vanilla's menus exclude, and a world pickup lands only in the hotbar, the main inventory and the offhand.

This is a declaration, not a rule SlotKit enforces: vanilla's seams already act only where vanilla does, and a mod's own operation acts where its code does. It is what a consumer reads to know where an operation can reach.

With no screen open

Q, Ctrl-Q and F while playing are the same DROP, DROP_STACK and OFFHAND_SWAP operations, on the selected hotbar slot; an offhand swap also needs the offhand slot to allow it. To the player Q is Q, screen or not.

Who says what a slot allows

Two mechanisms, one subtractive:
  1. The cascade is what the slot's author declared, most specific wins:
    per-slot declaration  >  the slot's group  >  the group's category  >  the key's default
    This is the window's ordinary cascade; the group and the category are two rungs inside its per-group level, ordered by GroupKey.precedence(). inherent(ModeScope, SlotGroupCategory, BehaviorKey, V) declares the category rung. A vanilla slot's group and category are one rung: vanilla contributes one group per category on a menu. Every built-in operation defaults to TRUE, so a slot nobody declared on is exactly vanilla.
  2. A veto is a rule some other mod lays on top, and it can only say no. A player's lock is the case: the player's policy over whatever the slot's author allowed. Written as a per-slot declaration it would overwrite the author's own settings and have nothing to restore on unlock; as a SlotOperations.Veto it sits beside the cascade and only subtracts.
allows(SlotRef, BehaviorKey) is the one question: the cascade says yes and no veto says no. A veto that decides by slot group registers as a SlotOperations.GroupVeto: it is handed the slot's group, resolved once per question (SlotGroups.of(SlotRef)), rather than working it out again on every slot of every click.

A veto that needs to see the item coming in registers as a SlotOperations.PutVeto. It is asked only by allowsPut(SlotRef, BehaviorKey, ItemStack), which every put seam uses with the stack it is about to place: "only into a container that already holds this item" is a put veto.

Simulated clicks

A mod that performs its operation by sending vanilla clicks wraps them:
SlotOperations.as(RESTOCK_TAKE, RESTOCK_PUT, () -> sendClicks());
Each click sent inside the block carries the operation it serves: the first for the slot it takes from, the second for the slot it puts into. The vetoes judge the carried operation, so a lock that refuses shift-click but allows restock lets the restock's shift-click through. The slot's author still judges the gesture: a group that sits out shift-click refuses a restock that arrives as a shift-click. A mod that wants to know first asks allows about both. The tag reaches the integrated server too. Clicks must be sent inside the block, on the calling thread; a click scheduled for later is not tagged. A simulated click that is not wrapped counts as the gesture it looks like, a manual click included.

Init-time reads are the one exception to "declare anywhere". Every built-in operation is SERVER tier, and SlotKit installs the server tier from its own initializer. Fabric does not order entrypoints between mods, so a consumer whose init runs first will see resolve answer the key's library default, and a declaration it makes there is buffered until the tier installs. Declare at init and read during play, as consumers normally do.

Thread-safe; every method is safe to call from mod init on any thread.

  • Method Details

    • define

      public static void define(BehaviorKey<?> operation, SlotOperations.Role role)
      Publishes operation so other mods can discover it through all(), with the SlotOperations.Role it plays on a slot. Idempotent per id; a second definition of the same id with a different key or a different role is refused rather than silently replacing the first, because mods resolve against whichever key they were compiled with.

      Defining is not required to use a key, the window resolves any BehaviorKey, it is how an operation becomes part of the shared vocabulary instead of a private one.

    • define

      public static void define(BehaviorKey<?> operation, SlotOperations.Role role, SlotOperations.AppliesTo appliesTo)
      define(BehaviorKey, Role) with the slot groups the operation can act on; see "Where an operation applies" in the class doc. A second definition with a different appliesTo is refused, as a different role is.
    • define

      public static void define(BehaviorKey<?> operation)
      define(BehaviorKey, Role) for an operation that does not say what it does to a slot, which is read as SlotOperations.Role.BOTH. A no-op for an operation already defined, whatever its role.
    • all

      public static Collection<BehaviorKey<?>> all()
      Every defined operation, in definition order.
    • byId

      public static @Nullable BehaviorKey<?> byId(net.minecraft.resources.Identifier id)
      The defined operation with this id, or null.
    • role

      public static @Nullable SlotOperations.Role role(BehaviorKey<?> operation)
      The role operation was defined with, or null if it was never defined.
    • appliesTo

      public static SlotOperations.AppliesTo appliesTo(BehaviorKey<?> operation)
      The slot groups operation was defined to apply to; SlotOperations.AppliesTo.EVERY_GROUP for one defined without saying, or never defined.
    • appliesTo

      public static boolean appliesTo(BehaviorKey<?> operation, SlotGroupId group)
      Whether operation can act on slots in group.
    • groups

      public static List<SlotGroupId> groups(BehaviorKey<?> operation)
      Every declared slot group (SlotGroups.all()) that operation applies to, in that order: the list a settings screen offers for it. To filter SlotGroups.listing() instead, keep an entry when any of its groups applies.
    • langKey

      public static String langKey(BehaviorKey<?> operation)
      The translation key an operation's name lives under: slot_operation.<namespace>.<path>.
    • name

      public static net.minecraft.network.chat.Component name(BehaviorKey<?> operation)
      The operation's display name, for a settings list.
    • description

      public static net.minecraft.network.chat.Component description(BehaviorKey<?> operation)
      One or two sentences on what the operation does to a slot, for a settings list.
    • inherent

      public static <V> void inherent(com.trevlar.menukit.api.panel.ModeScope scope, SlotGroupCategory category, BehaviorKey<V> operation, V value)
      Declares value as the default for operation on every slot in category that does not declare its own (the category rung), while the acting player's mode is in scope. A SURVIVAL or CREATIVE declaration wins over a BOTH one on the same category in its mode. Reaches created slots by address and vanilla slots through allows(SlotRef, BehaviorKey), which knows the menu.

      Declare it wherever you mint the category, typically your mod's init. Order does not matter: membership resolves per query, so groups registered before this call pick it up too.

    • inherent

      @Deprecated(since="2.1.0", forRemoval=true) public static <V> void inherent(SlotGroupCategory category, BehaviorKey<V> operation, V value)
      Deprecated, for removal: This API element is subject to removal in a future version.
      since 2.1.0, name the mode: inherent(ModeScope.BOTH, category, operation, value).
    • veto

      public static void veto(com.trevlar.menukit.api.panel.ModeScope scope, SlotOperations.Veto veto)
      Registers a veto that applies while the acting player's mode is in scope. The mode is read on every question, so /gamemode with a screen open flips it on the next click. With no acting player (automation) only a BOTH veto applies. Typically once, at your mod's init.
    • veto

      public static void veto(com.trevlar.menukit.api.panel.ModeScope scope, SlotOperations.GroupVeto veto)
      veto(ModeScope, Veto) for a veto that is handed the slot's group.
    • veto

      @Deprecated(since="2.1.0", forRemoval=true) public static void veto(SlotOperations.Veto veto)
      Deprecated, for removal: This API element is subject to removal in a future version.
      since 2.1.0, name the mode: veto(ModeScope.BOTH, veto).
      veto(ModeScope, Veto) in both modes.
    • veto

      @Deprecated(since="2.1.0", forRemoval=true) public static void veto(SlotOperations.GroupVeto veto)
      Deprecated, for removal: This API element is subject to removal in a future version.
      since 2.1.0, name the mode: veto(ModeScope.BOTH, veto).
    • vetoPut

      public static void vetoPut(com.trevlar.menukit.api.panel.ModeScope scope, SlotOperations.PutVeto veto)
      Registers a SlotOperations.PutVeto that applies while the acting player's mode is in scope, read per question as veto(ModeScope, Veto) is. Since 2.3.0.
    • vetoPut

      public static void vetoPut(SlotOperations.PutVeto veto)
      vetoPut(ModeScope, PutVeto) in both modes. Since 2.3.0.
    • as

      public static void as(BehaviorKey<TriBool> operation, Runnable clicks)
      Sends clicks as operation, for both the slots they take from and put into.
    • as

      public static void as(BehaviorKey<TriBool> take, BehaviorKey<TriBool> put, Runnable clicks)
      Sends clicks as the operation pair: take for the slots they take from, put for the slots they put into. See the class doc.
    • allows

      public static boolean allows(SlotRef ref, BehaviorKey<TriBool> operation)
      Whether operation may act on the slot ref describes: the cascade resolves TRUE and no SlotOperations.Veto denies it. Every operation, vanilla's and a mod's own, asks this before touching a slot.
    • allowsPut

      public static boolean allowsPut(SlotRef ref, BehaviorKey<TriBool> operation, net.minecraft.world.item.ItemStack incoming)
      allows(SlotRef, BehaviorKey), then every SlotOperations.PutVeto with the stack about to land. What a put asks when it knows the item; an empty incoming puts nothing, so no put veto is asked. Since 2.3.0.
    • allows

      public static boolean allows(net.minecraft.world.inventory.AbstractContainerMenu menu, net.minecraft.world.inventory.Slot slot, @Nullable net.minecraft.world.entity.player.Player player, BehaviorKey<TriBool> operation)
      allows(SlotRef, BehaviorKey) for a slot on an open menu.
    • allows

      public static boolean allows(net.minecraft.world.Container container, int containerSlot, @Nullable net.minecraft.world.entity.player.Player player, BehaviorKey<TriBool> operation)
      allows(SlotRef, BehaviorKey) for a slot reached with no menu open.
    • allowsGesture

      @Internal public static boolean allowsGesture(SlotRef ref, BehaviorKey<TriBool> gesture)
      What a vanilla seam asks for the gesture it is running: allows(SlotRef, BehaviorKey), or, when the click on this thread carries an operation (as(BehaviorKey, Runnable)), the slot's cascade on the gesture and allows on the carried operation for the gesture's role. Library seams only; a mod asks allows(SlotRef, BehaviorKey) about its own operation.
    • allowsPutGesture

      @Internal public static boolean allowsPutGesture(SlotRef ref, BehaviorKey<TriBool> gesture, net.minecraft.world.item.ItemStack incoming)
      allowsGesture(SlotRef, BehaviorKey) for a put seam that knows the stack about to land: the put vetoes judge it along with the operation the gesture serves. Library seams only; a mod asks allowsPut(SlotRef, BehaviorKey, ItemStack). Since 2.3.0.
    • allowsGesture

      @Internal public static boolean allowsGesture(net.minecraft.world.inventory.AbstractContainerMenu menu, net.minecraft.world.inventory.Slot slot, @Nullable net.minecraft.world.entity.player.Player player, BehaviorKey<TriBool> gesture)
      allowsGesture(SlotRef, BehaviorKey) for a slot on an open menu.