Class SwapManager

java.lang.Object
me.hackware.api.SwapManager

public class SwapManager extends Object
Centralized inventory-swap manager supporting instant (sub-tick) and multi-tick swaps with multiple swap strategies.

Modules call beginSwap(net.minecraft.world.item.Item, boolean) before using an item and endSwap(boolean) when done to restore inventory state.

  • Field Details

    • swapLogging

      public static boolean swapLogging
      Log every withSwap decision (caller, target, outcome) to the client log.
  • Method Details

    • get

      public static SwapManager get()
    • init

      public void init()
      Register tick handler. Call once during mod init.
    • getSwapMode

      public SwapManager.SwapMode getSwapMode()
    • setSwapMode

      public void setSwapMode(SwapManager.SwapMode mode)
    • beginSwap

      public boolean beginSwap(net.minecraft.world.item.Item item, boolean instant)
      Finds the given item and swaps it into the main hand.
      Parameters:
      item - the item to swap to
      instant - true for a single-tick swap (e.g. throw pearl), false for multi-tick (e.g. hold bow)
      Returns:
      true if the swap succeeded and the item is now in the main hand
    • beginSwap

      public boolean beginSwap(net.minecraft.world.item.Item item, boolean instant, boolean asymmetric)
      Finds the given item and swaps it into the main hand.
      Parameters:
      item - the item to swap to
      instant - true for a single-tick swap (e.g. throw pearl), false for multi-tick (e.g. hold bow)
      asymmetric - when true and the item has to be moved out of the deep inventory into a hotbar slot, it is left in the hotbar on endSwap(boolean) instead of being swapped back to its original slot. Only affects the container-swap (deep-inventory) path; hotbar items are unaffected.
      Returns:
      true if the swap succeeded and the item is now in the main hand
    • beginSwap

      public boolean beginSwap(InventoryUtil.FindItemResult result, boolean instant)
      Swaps the item at the given result slot into the main hand.
    • beginSwap

      public boolean beginSwap(InventoryUtil.FindItemResult result, boolean instant, boolean asymmetric)
      Swaps the item at the given result slot into the main hand.
      Parameters:
      asymmetric - see beginSwap(Item, boolean, boolean)
    • beginSilentHold

      public boolean beginSilentHold(InventoryUtil.FindItemResult result)
      Multi-tick swap that never moves the CLIENT's selection: only the server's held slot changes (a bare SetCarriedItem), so the local hand keeps rendering — and the local player keeps using — whatever they actually hold. Released with endSwap(false).

      This is the swap for server-side computations that span a whole server tick and so cannot be covered by an instant begin/end pair — the delayed-destroy completion, whose progress ServerPlayerGameMode.tick() recomputes as getDestroyProgress(held) × (elapsed + 1) from the item held at that moment. Packet-triggered computations (a STOP handled inline) stay on instant swaps, which are already invisible.

      Deep-inventory items fall back to the ordinary multi-tick swap: the item genuinely has to move into the hotbar, which no amount of slot spoofing hides.

      carriedIndex keeps tracking what we actually sent, so vanilla's ensureHasSentCarriedItem() pulls the server back to the real slot the moment the player attacks or uses an item — the hit lands with the real weapon and onTick(me.hackware.api.event.impl.TickStartEvent) re-asserts the tool next tick. The hold costs at most a delayed completion tick, never a wrong-item interaction. Vanilla's unconditional per-tick re-sync is routed through vanillaCarriedItemSync() so it doesn't undo the hold on every tick.

      Returns:
      true if the tool is now held server-side
    • isSilentHoldActive

      public boolean isSilentHoldActive()
      True while a beginSilentHold(me.hackware.api.util.InventoryUtil.FindItemResult) owns the server's slot without moving the client's.
    • vanillaCarriedItemSync

      public void vanillaCarriedItemSync()
      Stands in for vanilla's per-tick ensureHasSentCarriedItem() (redirected in MultiPlayerGameModeMixin). A silent hold owns the server's slot for its whole duration, so the unconditional re-sync is skipped while one is active — otherwise vanilla would restore the real slot every single tick and the hold could never span a server tick. With no hold active this is vanilla's behaviour exactly: send the client's selection, and only when the server isn't already on it.
    • endSwap

      public void endSwap(boolean instant)
      Restores inventory state from a previous beginSwap(net.minecraft.world.item.Item, boolean).
    • beginSwapSlot

      public boolean beginSwapSlot(int hotbarSlot, boolean instant)
      Swaps a specific hotbar slot into the main hand. Use when the caller already knows the target slot (e.g. AutoTool).
      Parameters:
      hotbarSlot - the hotbar slot (0-8)
      instant - true for sub-tick, false for multi-tick hold
      Returns:
      true if the swap succeeded
    • beginOffhandSwap

      public boolean beginOffhandSwap(int invSlot)
      Swaps an item from the given inventory slot into the offhand. Uses container SWAP with button 40 (offhand slot).
      Parameters:
      invSlot - the inventory slot index (0-35)
      Returns:
      true if the swap was performed
    • endOffhandSwap

      public void endOffhandSwap()
      Restores the offhand by swapping back.
    • isOffhandSwapped

      public boolean isOffhandSwapped()
      Returns true if an offhand swap is currently active.
    • canSwap

      public boolean canSwap(net.minecraft.world.item.Item item)
      Returns true if the given item can be swapped to under the current mode.
    • getSlot

      public InventoryUtil.FindItemResult getSlot(net.minecraft.world.item.Item item)
      Returns the best slot for the given item under the current swap mode.
    • wouldContainerSwap

      public boolean wouldContainerSwap(InventoryUtil.FindItemResult result, boolean instant)
      Predicts whether beginSwap(FindItemResult, boolean) would move the item with a container SWAP click (didSilentSwap) rather than a slot change / spoof — WITHOUT performing any swap.

      Lets callers in a hot path (e.g. crystal placement) bail BEFORE issuing a container click. A container click goes through handleInventoryMouseClick, which the click-slot limiter can cancel mid begin/end pair under burst, stranding the held item in the source slot (the ghost item). Pre-checking avoids even the transient begin→bail→end churn.

    • isSilentSwapActive

      public boolean isSilentSwapActive()
      Returns true if any silent swap (container or slot spoof) is currently active.
    • withSwap

      public boolean withSwap(InventoryUtil.FindItemResult target, Runnable action)
    • withSwap

      public boolean withSwap(InventoryUtil.FindItemResult target, int priority, Runnable action)
      Puts target in the active hand, runs action, and restores — a hotbar/offhand target through the budgeted carried-slot route, a main-inventory target through container clicks (which also work mid-use). Returns whether the action ran; an already-held target short-circuits with no swap spent.
    • silentBudgetLeft

      public int silentBudgetLeft(int priority)
      Carried-slot swaps still available this tick for a caller at priority.
    • isSwapInProgress

      public boolean isSwapInProgress()
      Whether any begin/end swap window (instant or multi-tick, silent or not) is currently open. Unlike isSilentSwapActive() this is also true for the plain selection-move path — the signal that an action happening right now runs with a weapon another module chose.
    • isContainerSwapActive

      public boolean isContainerSwapActive()
      Returns true only when a container swap is active (a slot-spoof swap sends a SetCarriedItem packet, not a container click, so it never collides with another container click stream / stateId). Safe to read off-thread.
    • isInstantSwapContainerBased

      public boolean isInstantSwapContainerBased()
      Returns true if the current instant swap used a container click (as opposed to hotbar slot spoofing). Callers that consume items (e.g. block placement, crystal placement) should bump the client stateId after endSwap(boolean) when this returns true; the server's broadcastChanges will send a count correction that increments the server stateId, and without a matching client bump the next container click would carry a stale id → full inventory resync → ghost items.
    • isSlotLocked

      public boolean isSlotLocked(int slotIndex)
      Returns true if the given inventory slot is currently locked by a silent swap.
    • reset

      public void reset()
      Clears all swap state. Called on disconnect so stale locks don't persist across server reconnects and block all future swaps.