Skip to content

Commit 0553905

Browse files
committed
Handle illegal stack sizes in creative mode
1 parent 63e60bc commit 0553905

File tree

4 files changed

+118
-41
lines changed

4 files changed

+118
-41
lines changed

core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java

+14-2
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ public int getAmount() {
8585
return isEmpty() ? 0 : amount;
8686
}
8787

88+
public int maxStackSize() {
89+
return getComponent(DataComponentType.MAX_STACK_SIZE, asItem().maxStackSize());
90+
}
91+
8892
public @Nullable DataComponents getComponents() {
8993
return isEmpty() ? null : components;
9094
}
@@ -133,12 +137,20 @@ public int getNetId() {
133137
return isEmpty() ? 0 : netId;
134138
}
135139

136-
public void add(int add) {
140+
public int capacity() {
141+
return Math.max(maxStackSize() - amount, 0);
142+
}
143+
144+
public int add(int add) {
145+
add = Math.min(add, capacity());
137146
amount += add;
147+
return add;
138148
}
139149

140-
public void sub(int sub) {
150+
public int sub(int sub) {
151+
sub = Math.min(sub, amount);
141152
amount -= sub;
153+
return sub;
142154
}
143155

144156
public ItemStack getItemStack() {

core/src/main/java/org/geysermc/geyser/translator/inventory/InventoryTranslator.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
package org.geysermc.geyser.translator.inventory;
2727

2828
import it.unimi.dsi.fastutil.ints.*;
29-
import lombok.AllArgsConstructor;
29+
import lombok.RequiredArgsConstructor;
3030
import org.checkerframework.checker.nullness.qual.Nullable;
3131
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
3232
import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName;
@@ -63,7 +63,7 @@
6363

6464
import java.util.*;
6565

66-
@AllArgsConstructor
66+
@RequiredArgsConstructor
6767
public abstract class InventoryTranslator {
6868

6969
public static final InventoryTranslator PLAYER_INVENTORY_TRANSLATOR = new PlayerInventoryTranslator();
@@ -108,6 +108,7 @@ public abstract class InventoryTranslator {
108108
public static final int PLAYER_INVENTORY_OFFSET = 9;
109109

110110
public final int size;
111+
protected boolean refreshPending;
111112

112113
public abstract boolean prepareInventory(GeyserSession session, Inventory inventory);
113114
public abstract void openInventory(GeyserSession session, Inventory inventory);
@@ -157,7 +158,7 @@ protected ItemStackResponse translateSpecialRequest(GeyserSession session, Inven
157158
}
158159

159160
public final void translateRequests(GeyserSession session, Inventory inventory, List<ItemStackRequest> requests) {
160-
boolean refresh = false;
161+
this.refreshPending = false;
161162
ItemStackResponsePacket responsePacket = new ItemStackResponsePacket();
162163
for (ItemStackRequest request : requests) {
163164
ItemStackResponse response;
@@ -182,14 +183,14 @@ public final void translateRequests(GeyserSession session, Inventory inventory,
182183

183184
if (response.getResult() != ItemStackResponseStatus.OK) {
184185
// Sync our copy of the inventory with Bedrock's to prevent desyncs
185-
refresh = true;
186+
this.refreshPending = true;
186187
}
187188

188189
responsePacket.getEntries().add(response);
189190
}
190191
session.sendUpstreamPacket(responsePacket);
191192

192-
if (refresh) {
193+
if (this.refreshPending) {
193194
InventoryUtils.updateCursor(session);
194195
updateInventory(session, inventory);
195196
}

core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java

+95-31
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,8 @@ public ItemStackResponse translateRequest(GeyserSession session, Inventory inven
244244

245245
PlayerInventory playerInv = session.getPlayerInventory();
246246
IntSet affectedSlots = new IntOpenHashSet();
247+
248+
actionLoop:
247249
for (ItemStackRequestAction action : request.getActions()) {
248250
switch (action.getType()) {
249251
case TAKE, PLACE -> {
@@ -260,15 +262,21 @@ public ItemStackResponse translateRequest(GeyserSession session, Inventory inven
260262
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
261263
GeyserItemStack sourceItem = inventory.getItem(sourceSlot);
262264
if (playerInv.getCursor().isEmpty()) {
263-
playerInv.setCursor(sourceItem.copy(0), session);
265+
// Bypass stack limit for empty cursor
266+
playerInv.setCursor(sourceItem.copy(transferAmount), session);
267+
sourceItem.sub(transferAmount);
264268
} else if (!InventoryUtils.canStack(sourceItem, playerInv.getCursor())) {
265269
return rejectRequest(request);
270+
} else {
271+
transferAmount = playerInv.getCursor().add(transferAmount);
272+
sourceItem.sub(transferAmount);
266273
}
267274

268-
playerInv.getCursor().add(transferAmount);
269-
sourceItem.sub(transferAmount);
270-
271-
affectedSlots.add(sourceSlot);
275+
// Don't add to affectedSlots if nothing changed.
276+
// Prevents sendCreativeAction from adjusting stack size.
277+
if (transferAmount > 0) {
278+
affectedSlots.add(sourceSlot);
279+
}
272280
} else if (isCursor(transferAction.getSource())) {
273281
int destSlot = bedrockSlotToJava(transferAction.getDestination());
274282
GeyserItemStack sourceItem = playerInv.getCursor();
@@ -278,10 +286,11 @@ public ItemStackResponse translateRequest(GeyserSession session, Inventory inven
278286
return rejectRequest(request);
279287
}
280288

281-
inventory.getItem(destSlot).add(transferAmount);
282-
sourceItem.sub(transferAmount);
283-
284-
affectedSlots.add(destSlot);
289+
transferAmount = inventory.getItem(destSlot).add(transferAmount);
290+
if (transferAmount > 0) {
291+
sourceItem.sub(transferAmount);
292+
affectedSlots.add(destSlot);
293+
}
285294
} else {
286295
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
287296
int destSlot = bedrockSlotToJava(transferAction.getDestination());
@@ -292,11 +301,17 @@ public ItemStackResponse translateRequest(GeyserSession session, Inventory inven
292301
return rejectRequest(request);
293302
}
294303

295-
inventory.getItem(destSlot).add(transferAmount);
296-
sourceItem.sub(transferAmount);
304+
transferAmount = inventory.getItem(destSlot).add(transferAmount);
305+
if (transferAmount > 0) {
306+
sourceItem.sub(transferAmount);
307+
affectedSlots.add(sourceSlot);
308+
affectedSlots.add(destSlot);
309+
}
310+
}
297311

298-
affectedSlots.add(sourceSlot);
299-
affectedSlots.add(destSlot);
312+
if (transferAction.getCount() != transferAmount) {
313+
this.refreshPending = true; // Fixes visual bug with cursor
314+
break actionLoop; // Inventory is not what client expects right now
300315
}
301316
}
302317
case SWAP -> {
@@ -361,10 +376,16 @@ public ItemStackResponse translateRequest(GeyserSession session, Inventory inven
361376
return rejectRequest(request);
362377
}
363378

364-
ServerboundSetCreativeModeSlotPacket creativeDropPacket = new ServerboundSetCreativeModeSlotPacket((short)-1, sourceItem.getItemStack(dropAction.getCount()));
379+
int dropAmount = dropAction.getCount();
380+
if (dropAmount > sourceItem.maxStackSize()) {
381+
dropAmount = sourceItem.maxStackSize();
382+
sourceItem.setAmount(dropAmount);
383+
}
384+
385+
ServerboundSetCreativeModeSlotPacket creativeDropPacket = new ServerboundSetCreativeModeSlotPacket((short)-1, sourceItem.getItemStack(dropAmount));
365386
session.sendDownstreamGamePacket(creativeDropPacket);
366387

367-
sourceItem.sub(dropAction.getCount());
388+
sourceItem.sub(dropAmount);
368389
}
369390
case DESTROY -> {
370391
// Only called when a creative client wants to destroy an item... I think - Camotoy
@@ -404,9 +425,12 @@ public ItemStackResponse translateRequest(GeyserSession session, Inventory inven
404425

405426
@Override
406427
protected ItemStackResponse translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
407-
ItemStack javaCreativeItem = null;
428+
GeyserItemStack javaCreativeItem = null;
408429
IntSet affectedSlots = new IntOpenHashSet();
409430
CraftState craftState = CraftState.START;
431+
boolean firstTransfer = true;
432+
433+
actionLoop:
410434
for (ItemStackRequestAction action : request.getActions()) {
411435
switch (action.getType()) {
412436
case CRAFT_CREATIVE: {
@@ -456,26 +480,39 @@ protected ItemStackResponse translateCreativeRequest(GeyserSession session, Inve
456480
return rejectRequest(request);
457481
}
458482

483+
int transferAmount = Math.min(transferAction.getCount(), javaCreativeItem.maxStackSize());
459484
if (isCursor(transferAction.getDestination())) {
460485
if (session.getPlayerInventory().getCursor().isEmpty()) {
461-
GeyserItemStack newItemStack = GeyserItemStack.from(javaCreativeItem);
462-
newItemStack.setAmount(transferAction.getCount());
486+
GeyserItemStack newItemStack = javaCreativeItem.copy(transferAmount);
463487
session.getPlayerInventory().setCursor(newItemStack, session);
464488
} else {
465-
session.getPlayerInventory().getCursor().add(transferAction.getCount());
489+
transferAmount = session.getPlayerInventory().getCursor().add(transferAmount);
466490
}
467491
//cursor is always included in response
468492
} else {
469493
int destSlot = bedrockSlotToJava(transferAction.getDestination());
470494
if (inventory.getItem(destSlot).isEmpty()) {
471-
GeyserItemStack newItemStack = GeyserItemStack.from(javaCreativeItem);
472-
newItemStack.setAmount(transferAction.getCount());
495+
GeyserItemStack newItemStack = javaCreativeItem.copy(transferAmount);
473496
inventory.setItem(destSlot, newItemStack, session);
474497
} else {
475-
inventory.getItem(destSlot).add(transferAction.getCount());
498+
// If the player is shift clicking an item with a stack size greater than java edition,
499+
// emulate the action ourselves instead.
500+
if (firstTransfer && inventory.getItem(destSlot).capacity() < transferAmount) {
501+
GeyserItemStack newItemStack = javaCreativeItem.copy(javaCreativeItem.maxStackSize());
502+
emulateCreativeQuickMove(session, inventory, affectedSlots, newItemStack);
503+
this.refreshPending = true;
504+
break actionLoop; // Ignore the rest of the client's actions
505+
}
506+
507+
transferAmount = inventory.getItem(destSlot).add(transferAmount);
476508
}
477509
affectedSlots.add(destSlot);
478510
}
511+
512+
firstTransfer = false;
513+
if (transferAmount != transferAction.getCount()) {
514+
this.refreshPending = true;
515+
}
479516
break;
480517
}
481518
case DROP: {
@@ -489,14 +526,8 @@ protected ItemStackResponse translateCreativeRequest(GeyserSession session, Inve
489526
return rejectRequest(request);
490527
}
491528

492-
ItemStack dropStack;
493-
if (dropAction.getCount() == javaCreativeItem.getAmount()) {
494-
dropStack = javaCreativeItem;
495-
} else {
496-
// Specify custom count
497-
dropStack = new ItemStack(javaCreativeItem.getId(), dropAction.getCount(), javaCreativeItem.getDataComponents());
498-
}
499-
ServerboundSetCreativeModeSlotPacket creativeDropPacket = new ServerboundSetCreativeModeSlotPacket((short)-1, dropStack);
529+
ItemStack dropItem = javaCreativeItem.getItemStack(Math.min(dropAction.getCount(), javaCreativeItem.maxStackSize()));
530+
ServerboundSetCreativeModeSlotPacket creativeDropPacket = new ServerboundSetCreativeModeSlotPacket((short)-1, dropItem);
500531
session.sendDownstreamGamePacket(creativeDropPacket);
501532
break;
502533
}
@@ -513,14 +544,47 @@ protected ItemStackResponse translateCreativeRequest(GeyserSession session, Inve
513544
return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
514545
}
515546

516-
private static void sendCreativeAction(GeyserSession session, Inventory inventory, int slot) {
547+
private void sendCreativeAction(GeyserSession session, Inventory inventory, int slot) {
517548
GeyserItemStack item = inventory.getItem(slot);
549+
550+
// This does not match java client behaviour, but the java server will ignore creative actions with illegal stack sizes
551+
if (item.getAmount() > item.maxStackSize()) {
552+
item.setAmount(item.maxStackSize());
553+
this.refreshPending = true;
554+
}
555+
518556
ItemStack itemStack = item.isEmpty() ? new ItemStack(-1, 0, null) : item.getItemStack();
519557

520558
ServerboundSetCreativeModeSlotPacket creativePacket = new ServerboundSetCreativeModeSlotPacket((short)slot, itemStack);
521559
session.sendDownstreamGamePacket(creativePacket);
522560
}
523561

562+
private static void emulateCreativeQuickMove(GeyserSession session, Inventory inventory, IntSet affectedSlots, GeyserItemStack creativeItem) {
563+
int firstEmptySlot = -1; // Leftover stack is stored here
564+
565+
for (int i = 0; i < 36; i++) {
566+
int slot = i < 9 ? i + 36 : i; // First iterate hotbar, then inventory
567+
GeyserItemStack slotItem = inventory.getItem(slot);
568+
569+
if (firstEmptySlot == -1 && slotItem.isEmpty()) {
570+
firstEmptySlot = slot;
571+
}
572+
573+
if (InventoryUtils.canStack(slotItem, creativeItem) && slotItem.capacity() > 0) {
574+
creativeItem.sub(slotItem.add(creativeItem.getAmount())); // Transfer as much as possible without passing stack capacity
575+
affectedSlots.add(slot);
576+
if (creativeItem.isEmpty()) {
577+
return;
578+
}
579+
}
580+
}
581+
582+
if (firstEmptySlot != -1) {
583+
inventory.setItem(firstEmptySlot, creativeItem, session);
584+
affectedSlots.add(firstEmptySlot);
585+
}
586+
}
587+
524588
private static boolean isCraftingGrid(ItemStackRequestSlotData slotInfoData) {
525589
return slotInfoData.getContainer() == ContainerSlotType.CRAFTING_INPUT;
526590
}

core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ public final class ItemTranslator {
100100
private ItemTranslator() {
101101
}
102102

103-
public static ItemStack translateToJava(GeyserSession session, ItemData data) {
103+
public static GeyserItemStack translateToJava(GeyserSession session, ItemData data) {
104104
if (data == null) {
105-
return new ItemStack(Items.AIR_ID);
105+
return GeyserItemStack.EMPTY;
106106
}
107107

108108
ItemMapping bedrockItem = session.getItemMappings().getMapping(data);
@@ -119,7 +119,7 @@ public static ItemStack translateToJava(GeyserSession session, ItemData data) {
119119
itemStack.setComponents(components);
120120
}
121121
}
122-
return itemStack.getItemStack();
122+
return itemStack;
123123
}
124124

125125
public static ItemData.@NonNull Builder translateToBedrock(GeyserSession session, int javaId, int count, DataComponents components) {

0 commit comments

Comments
 (0)