Class SwapManager
Modules call beginSwap(net.minecraft.world.item.Item, boolean) before using an item and endSwap(boolean)
when done to restore inventory state.
-
Nested Class Summary
Nested Classes -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic booleanLog every withSwap decision (caller, target, outcome) to the client log. -
Method Summary
Modifier and TypeMethodDescriptionbooleanbeginOffhandSwap(int invSlot) Swaps an item from the given inventory slot into the offhand.booleanMulti-tick swap that never moves the CLIENT's selection: only the server's held slot changes (a bareSetCarriedItem), so the local hand keeps rendering — and the local player keeps using — whatever they actually hold.booleanbeginSwap(InventoryUtil.FindItemResult result, boolean instant) Swaps the item at the given result slot into the main hand.booleanbeginSwap(InventoryUtil.FindItemResult result, boolean instant, boolean asymmetric) Swaps the item at the given result slot into the main hand.booleanbeginSwap(InventoryUtil.FindItemResult result, boolean instant, boolean asymmetric, boolean keep) AsbeginSwap(Item, boolean, boolean, boolean), on the stack at the given result slot.booleanbeginSwap(net.minecraft.world.item.Item item, boolean instant) Finds the given item and swaps it into the main hand.booleanbeginSwap(net.minecraft.world.item.Item item, boolean instant, boolean asymmetric) booleanbeginSwap(net.minecraft.world.item.Item item, boolean instant, boolean asymmetric, boolean keep) AsbeginSwap(Item, boolean, boolean); withkeep, the selection is left on the item atendSwap(boolean)— a real hotbar select (a deep item promoted into the hotbar first, asymmetric), kept for the next placement of it instead of a silent swap there and back.booleanbeginSwapSlot(int hotbarSlot, boolean instant) Swaps a specific hotbar slot into the main hand.booleanWhetherserveSelectionForAttack()would succeed, sending nothing.booleancanSwap(net.minecraft.world.item.Item item) Returns true if the given item can be swapped to under the current mode.One-line dump of both swap windows and the slot bookkeeping, for "why was that swap refused" logs.intdisplaceableHotbarSlot(IntPredicate spared) The hotbar slot an item moved up from the main inventory displaces most cheaply, inchooseAsymmetricHotbarSlot(net.minecraft.world.item.Item)'s order, for callers that move one up themselves.voidRestores the offhand by swapping back.voidendSwap(boolean instant) Restores inventory state from a previousbeginSwap(net.minecraft.world.item.Item, boolean).static SwapManagerget()getSlot(net.minecraft.world.item.Item item) Returns the best slot for the given item under the current swap mode.voidinit()Register tick handler.booleanReturns 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).booleanReturns true if the current instant swap used a container click (as opposed to hotbar slot spoofing).booleanReturns true if an offhand swap is currently active.booleanTrue while abeginSilentHold(me.hackware.api.util.InventoryUtil.FindItemResult)owns the server's slot without moving the client's.booleanTrue while a silent hold actually has the server on its tool: active, and not standing aside for a main-hand use or a reserved main hand (both stoponTick(me.hackware.api.event.impl.TickStartEvent)re-asserting it).booleanReturns true if any silent swap (container or slot spoof) is currently active.booleanisSlotLocked(int slotIndex) Returns true if the given inventory slot is currently locked by a silent swap.booleanWhether any begin/end swap window (instant or multi-tick, silent or not) is currently open.booleanTrue while a bareSetCarriedItemwould cost a main-hand use: the use is live on the client, or ended so recently that the server may still be inside it.voidreserveMainHand(int ticks) Puts the player's real selection on the wire now and keeps the server on it forticksticks.voidreset()Clears all swap state.booleanForces a real held-slot round trip: a neighbouring slot, then the slot the server should rest on.booleanPuts the player's real selection on the server for a raw-packet hit sent this tick, which is what vanilla'sattack()does throughensureHasSentCarriedItem().voidintsilentBudgetLeft(int priority) Carried-slot swaps still available this tick for a caller atpriority.voidStands in for vanilla's per-tickensureHasSentCarriedItem()(redirected inMultiPlayerGameModeMixin).booleanwithSwap(InventoryUtil.FindItemResult target, int priority, Runnable action) Putstargetin the active hand, runsaction, and restores — a hotbar target by its slot and any other target by its item (so an offhand target under a spoofed server slot swaps in a spare copy).booleanwithSwap(InventoryUtil.FindItemResult target, Runnable action) booleanwouldContainerSwap(InventoryUtil.FindItemResult result, boolean instant) Predicts whetherbeginSwap(FindItemResult, boolean)would move the item with a container SWAP click (didSilentSwap) rather than a slot change / spoof — WITHOUT performing any swap.
-
Field Details
-
swapLogging
public static boolean swapLoggingLog every withSwap decision (caller, target, outcome) to the client log.
-
-
Method Details
-
get
-
init
public void init()Register tick handler. Call once during mod init. -
getSwapMode
-
setSwapMode
-
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 toinstant- 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, boolean keep) AsbeginSwap(Item, boolean, boolean); withkeep, the selection is left on the item atendSwap(boolean)— a real hotbar select (a deep item promoted into the hotbar first, asymmetric), kept for the next placement of it instead of a silent swap there and back. Only where the swap was such a select: one that had to be silent (a spoof under another swap, a container swap while eating in the main hand) still swaps back. -
beginSwap
public boolean beginSwap(InventoryUtil.FindItemResult result, boolean instant, boolean asymmetric, boolean keep) AsbeginSwap(Item, boolean, boolean, boolean), on the stack at the given result slot. -
beginSwap
public boolean beginSwap(net.minecraft.world.item.Item item, boolean instant, boolean asymmetric) -
beginSwap
Swaps the item at the given result slot into the main hand. -
beginSwap
Swaps the item at the given result slot into the main hand.- Parameters:
asymmetric- seebeginSwap(Item, boolean, boolean)
-
beginSilentHold
Multi-tick swap that never moves the CLIENT's selection: only the server's held slot changes (a bareSetCarriedItem), so the local hand keeps rendering — and the local player keeps using — whatever they actually hold. Released withendSwap(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 asgetDestroyProgress(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.
carriedIndexkeeps tracking what we actually sent, so vanilla'sensureHasSentCarriedItem()pulls the server back to the real slot the moment the player attacks or uses an item — the hit lands with the real weapon andonTick(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 throughvanillaCarriedItemSync()so it doesn't undo the hold on every tick.- Returns:
- true if the tool is now held server-side
-
mainhandUseProtected
public boolean mainhandUseProtected()True while a bareSetCarriedItemwould cost a main-hand use: the use is live on the client, or ended so recently that the server may still be inside it. The server starts a chew a ping late and finishes it a ping late (later still under 20 TPS), so a slot packet sent the tick the client finishes voids the food that was just eaten. -
isSilentHoldActive
public boolean isSilentHoldActive()True while abeginSilentHold(me.hackware.api.util.InventoryUtil.FindItemResult)owns the server's slot without moving the client's. -
isSilentHoldEffective
public boolean isSilentHoldEffective()True while a silent hold actually has the server on its tool: active, and not standing aside for a main-hand use or a reserved main hand (both stoponTick(me.hackware.api.event.impl.TickStartEvent)re-asserting it). -
vanillaCarriedItemSync
public void vanillaCarriedItemSync()Stands in for vanilla's per-tickensureHasSentCarriedItem()(redirected inMultiPlayerGameModeMixin). 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. AreserveMainHand(int)outranks the hold, so the sync runs. -
reserveMainHand
public void reserveMainHand(int ticks) Puts the player's real selection on the wire now and keeps the server on it forticksticks. A silent hold stands down for that long, the same way it yields to a main-hand eat, and resumes by itself afterwards. For an item that only works while the server genuinely holds it: a totem ahead of a hit that could pop.- Parameters:
ticks- how long the selection stays reserved; callers renew it each tick they still need it
-
serveSelectionForAttack
public boolean serveSelectionForAttack()Puts the player's real selection on the server for a raw-packet hit sent this tick, which is what vanilla'sattack()does throughensureHasSentCarriedItem(). A silent hold (SilentMine's tool on a parked block) stands aside for the rest of this tick and re-asserts its tool at the nextonTick(me.hackware.api.event.impl.TickStartEvent), so no slot change follows the hit in its own tick (Grim's PacketOrderE) and the parked block only loses one completion tick.- Returns:
- true when the server holds the selection now; false when putting it there would take the slot from a main-hand use (a chew or its server-side tail), so the hit has to wait
-
canServeSelectionForAttack
public boolean canServeSelectionForAttack()WhetherserveSelectionForAttack()would succeed, sending nothing. -
endSwap
public void endSwap(boolean instant) Restores inventory state from a previousbeginSwap(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
Returns the best slot for the given item under the current swap mode. -
wouldContainerSwap
Predicts whetherbeginSwap(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
-
withSwap
Putstargetin the active hand, runsaction, and restores — a hotbar target by its slot and any other target by its item (so an offhand target under a spoofed server slot swaps in a spare copy). Hotbar and offhand targets spend the carried-slot budget and are refused mid-use; main-inventory targets go 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 atpriority. -
isSwapInProgress
public boolean isSwapInProgress()Whether any begin/end swap window (instant or multi-tick, silent or not) is currently open. UnlikeisSilentSwapActive()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 afterendSwap(boolean)when this returns true; the server'sbroadcastChangeswill 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. -
describeState
One-line dump of both swap windows and the slot bookkeeping, for "why was that swap refused" logs. -
resyncServerSlot
public boolean resyncServerSlot()Forces a real held-slot round trip: a neighbouring slot, then the slot the server should rest on.sendSlotPacket(int)is skipped whenever our bookkeeping says the server is already there, so if that bookkeeping and the server ever disagree, nothing short of an actual selection change repairs it. This is that selection change, without moving the hand on screen. Two different slot ids, so not the duplicate BadPacketsA looks for. Refused while an instant swap window is open. -
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. -
displaceableHotbarSlot
The hotbar slot an item moved up from the main inventory displaces most cheaply, inchooseAsymmetricHotbarSlot(net.minecraft.world.item.Item)'s order, for callers that move one up themselves.- Parameters:
spared- slots never picked- Returns:
- the slot, or -1 when every slot is spared
-