diff --git a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_17_1to1_17/Protocol1_17_1To1_17.java b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_17_1to1_17/Protocol1_17_1To1_17.java index 92040ff2..b7a1dfbc 100644 --- a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_17_1to1_17/Protocol1_17_1To1_17.java +++ b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_17_1to1_17/Protocol1_17_1To1_17.java @@ -71,7 +71,7 @@ protected void registerPackets() { // Length is encoded as a var int in 1.17.1 wrapper.write(Types.ITEM1_13_2_SHORT_ARRAY, wrapper.read(Types.ITEM1_13_2_ARRAY)); - // Carried item - should work without adding it to the array above + // Carried item - forward as CONTAINER_SET_SLOT for <1.17 clients Item carried = wrapper.read(Types.ITEM1_13_2); PlayerLastCursorItem lastCursorItem = wrapper.user().get(PlayerLastCursorItem.class); @@ -82,6 +82,16 @@ protected void registerPackets() { // for a subsequent drag lastCursorItem.setLastCursorItem(carried); + + // In 1.17.1+, the carried/cursor item is part of CONTAINER_SET_CONTENT, + // but <1.17 clients don't have this field. Send it as a separate + // CONTAINER_SET_SLOT packet so the client's cursor state is properly synced. + // This fixes inventory desyncs when the server cancels InventoryClickEvents. + PacketWrapper cursorPacket = wrapper.create(ClientboundPackets1_17.CONTAINER_SET_SLOT); + cursorPacket.write(Types.BYTE, (byte) -1); // Window ID: -1 for cursor + cursorPacket.write(Types.SHORT, (short) -1); // Slot: -1 for cursor + cursorPacket.write(Types.ITEM1_13_2, carried); + cursorPacket.send(Protocol1_17_1To1_17.class); } }); diff --git a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_17to1_16_4/rewriter/BlockItemPacketRewriter1_17.java b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_17to1_16_4/rewriter/BlockItemPacketRewriter1_17.java index 266defe7..5b2c7215 100644 --- a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_17to1_16_4/rewriter/BlockItemPacketRewriter1_17.java +++ b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_17to1_16_4/rewriter/BlockItemPacketRewriter1_17.java @@ -26,6 +26,7 @@ import com.viaversion.viabackwards.api.rewriters.MapColorRewriter; import com.viaversion.viabackwards.protocol.v1_17to1_16_4.Protocol1_17To1_16_4; import com.viaversion.viabackwards.protocol.v1_17to1_16_4.data.MapColorMappings1_16_4; +import com.viaversion.viabackwards.protocol.v1_17_1to1_17.storage.InventoryStateIds; import com.viaversion.viabackwards.protocol.v1_17to1_16_4.storage.PlayerLastCursorItem; import com.viaversion.viaversion.api.data.entity.EntityTracker; import com.viaversion.viaversion.api.minecraft.BlockChangeRecord; @@ -83,8 +84,26 @@ public void register() { int mode = wrapper.passthrough(Types.VAR_INT); // Mode Item clicked = handleItemToServer(wrapper.user(), wrapper.read(Types.ITEM1_13_2)); // Clicked item - // The 1.17 client would check the entire inventory for changes before -> after a click and send the changed slots here - wrapper.write(Types.VAR_INT, 0); // Empty array of slot+item + // The 1.17 client would check the entire inventory for changes before -> after a click + // and send the changed slots here. We include the clicked slot so the server + // detects any desync (e.g. cancelled InventoryClickEvent) and sends corrections. + if (slot >= 0 && clicked != null) { + wrapper.write(Types.VAR_INT, 1); // One modified slot + wrapper.write(Types.SHORT, slot); // The clicked slot + wrapper.write(Types.ITEM1_13_2, (Item) null); // Predicted: empty (item picked up) + } else { + wrapper.write(Types.VAR_INT, 0); // Empty array of slot+item + } + + // Non-PICKUP modes (shift-click, swap, throw, drag, etc.) affect multiple slots + // that we can't easily predict. Force a state ID mismatch so the server + // sends a full inventory resync instead of individual slot corrections. + if (mode != 0) { + InventoryStateIds stateIds = wrapper.user().get(InventoryStateIds.class); + if (stateIds != null) { + stateIds.setStateId(wrapper.get(Types.BYTE, 0), -1); + } + } PlayerLastCursorItem state = wrapper.user().get(PlayerLastCursorItem.class); if (mode == 0 && button == 0 && clicked != null) {