Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for max stack size #5011

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ public int getAmount() {
return isEmpty() ? 0 : amount;
}

public int maxStackSize() {
return getComponent(DataComponentType.MAX_STACK_SIZE, asItem().maxStackSize());
}

public @Nullable DataComponents getComponents() {
return isEmpty() ? null : components;
}
Expand Down Expand Up @@ -133,12 +137,20 @@ public int getNetId() {
return isEmpty() ? 0 : netId;
}

public void add(int add) {
public int capacity() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add Javadoc here please - seems straightforward but took me a minute so let's be super clear.

Copy link
Member

@Konicai Konicai Sep 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or rename it to remainingCapacity/remainingSize?

i would expect capacity == maxStackSize because of typical capacity meaning (if thats what you meant too)

return Math.max(maxStackSize() - amount, 0);
}

public int add(int add) {
add = Math.min(add, capacity());
amount += add;
return add;
}

public void sub(int sub) {
public int sub(int sub) {
sub = Math.min(sub, amount);
amount -= sub;
return sub;
}

public ItemStack getItemStack() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
package org.geysermc.geyser.translator.inventory;

import it.unimi.dsi.fastutil.ints.*;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName;
Expand Down Expand Up @@ -63,7 +63,7 @@

import java.util.*;

@AllArgsConstructor
@RequiredArgsConstructor
public abstract class InventoryTranslator {

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

public final int size;
protected boolean refreshPending;

public abstract boolean prepareInventory(GeyserSession session, Inventory inventory);
public abstract void openInventory(GeyserSession session, Inventory inventory);
Expand Down Expand Up @@ -157,7 +158,7 @@ protected ItemStackResponse translateSpecialRequest(GeyserSession session, Inven
}

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

if (response.getResult() != ItemStackResponseStatus.OK) {
// Sync our copy of the inventory with Bedrock's to prevent desyncs
refresh = true;
this.refreshPending = true;
}

responsePacket.getEntries().add(response);
}
session.sendUpstreamPacket(responsePacket);

if (refresh) {
if (this.refreshPending) {
InventoryUtils.updateCursor(session);
updateInventory(session, inventory);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ public ItemStackResponse translateRequest(GeyserSession session, Inventory inven

PlayerInventory playerInv = session.getPlayerInventory();
IntSet affectedSlots = new IntOpenHashSet();

actionLoop:
for (ItemStackRequestAction action : request.getActions()) {
switch (action.getType()) {
case TAKE, PLACE -> {
Expand All @@ -260,15 +262,21 @@ public ItemStackResponse translateRequest(GeyserSession session, Inventory inven
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
GeyserItemStack sourceItem = inventory.getItem(sourceSlot);
if (playerInv.getCursor().isEmpty()) {
playerInv.setCursor(sourceItem.copy(0), session);
// Bypass stack limit for empty cursor
playerInv.setCursor(sourceItem.copy(transferAmount), session);
sourceItem.sub(transferAmount);
} else if (!InventoryUtils.canStack(sourceItem, playerInv.getCursor())) {
return rejectRequest(request);
} else {
transferAmount = playerInv.getCursor().add(transferAmount);
sourceItem.sub(transferAmount);
}

playerInv.getCursor().add(transferAmount);
sourceItem.sub(transferAmount);

affectedSlots.add(sourceSlot);
// Don't add to affectedSlots if nothing changed.
// Prevents sendCreativeAction from adjusting stack size.
if (transferAmount > 0) {
affectedSlots.add(sourceSlot);
}
} else if (isCursor(transferAction.getSource())) {
int destSlot = bedrockSlotToJava(transferAction.getDestination());
GeyserItemStack sourceItem = playerInv.getCursor();
Expand All @@ -278,10 +286,11 @@ public ItemStackResponse translateRequest(GeyserSession session, Inventory inven
return rejectRequest(request);
}

inventory.getItem(destSlot).add(transferAmount);
sourceItem.sub(transferAmount);

affectedSlots.add(destSlot);
transferAmount = inventory.getItem(destSlot).add(transferAmount);
if (transferAmount > 0) {
sourceItem.sub(transferAmount);
affectedSlots.add(destSlot);
}
} else {
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
int destSlot = bedrockSlotToJava(transferAction.getDestination());
Expand All @@ -292,11 +301,17 @@ public ItemStackResponse translateRequest(GeyserSession session, Inventory inven
return rejectRequest(request);
}

inventory.getItem(destSlot).add(transferAmount);
sourceItem.sub(transferAmount);
transferAmount = inventory.getItem(destSlot).add(transferAmount);
if (transferAmount > 0) {
sourceItem.sub(transferAmount);
affectedSlots.add(sourceSlot);
affectedSlots.add(destSlot);
}
}

affectedSlots.add(sourceSlot);
affectedSlots.add(destSlot);
if (transferAction.getCount() != transferAmount) {
this.refreshPending = true; // Fixes visual bug with cursor
break actionLoop; // Inventory is not what client expects right now
}
}
case SWAP -> {
Expand Down Expand Up @@ -361,10 +376,16 @@ public ItemStackResponse translateRequest(GeyserSession session, Inventory inven
return rejectRequest(request);
}

ServerboundSetCreativeModeSlotPacket creativeDropPacket = new ServerboundSetCreativeModeSlotPacket((short)-1, sourceItem.getItemStack(dropAction.getCount()));
int dropAmount = dropAction.getCount();
if (dropAmount > sourceItem.maxStackSize()) {
dropAmount = sourceItem.maxStackSize();
sourceItem.setAmount(dropAmount);
}

ServerboundSetCreativeModeSlotPacket creativeDropPacket = new ServerboundSetCreativeModeSlotPacket((short)-1, sourceItem.getItemStack(dropAmount));
session.sendDownstreamGamePacket(creativeDropPacket);

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

@Override
protected ItemStackResponse translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
ItemStack javaCreativeItem = null;
GeyserItemStack javaCreativeItem = null;
IntSet affectedSlots = new IntOpenHashSet();
CraftState craftState = CraftState.START;
boolean firstTransfer = true;

actionLoop:
for (ItemStackRequestAction action : request.getActions()) {
switch (action.getType()) {
case CRAFT_CREATIVE: {
Expand Down Expand Up @@ -456,26 +480,39 @@ protected ItemStackResponse translateCreativeRequest(GeyserSession session, Inve
return rejectRequest(request);
}

int transferAmount = Math.min(transferAction.getCount(), javaCreativeItem.maxStackSize());
if (isCursor(transferAction.getDestination())) {
if (session.getPlayerInventory().getCursor().isEmpty()) {
GeyserItemStack newItemStack = GeyserItemStack.from(javaCreativeItem);
newItemStack.setAmount(transferAction.getCount());
GeyserItemStack newItemStack = javaCreativeItem.copy(transferAmount);
session.getPlayerInventory().setCursor(newItemStack, session);
} else {
session.getPlayerInventory().getCursor().add(transferAction.getCount());
transferAmount = session.getPlayerInventory().getCursor().add(transferAmount);
}
//cursor is always included in response
} else {
int destSlot = bedrockSlotToJava(transferAction.getDestination());
if (inventory.getItem(destSlot).isEmpty()) {
GeyserItemStack newItemStack = GeyserItemStack.from(javaCreativeItem);
newItemStack.setAmount(transferAction.getCount());
GeyserItemStack newItemStack = javaCreativeItem.copy(transferAmount);
inventory.setItem(destSlot, newItemStack, session);
} else {
inventory.getItem(destSlot).add(transferAction.getCount());
// If the player is shift clicking an item with a stack size greater than java edition,
// emulate the action ourselves instead.
if (firstTransfer && inventory.getItem(destSlot).capacity() < transferAmount) {
GeyserItemStack newItemStack = javaCreativeItem.copy(javaCreativeItem.maxStackSize());
emulateCreativeQuickMove(session, inventory, affectedSlots, newItemStack);
this.refreshPending = true;
break actionLoop; // Ignore the rest of the client's actions
}

transferAmount = inventory.getItem(destSlot).add(transferAmount);
}
affectedSlots.add(destSlot);
}

firstTransfer = false;
if (transferAmount != transferAction.getCount()) {
this.refreshPending = true;
}
break;
}
case DROP: {
Expand All @@ -489,14 +526,8 @@ protected ItemStackResponse translateCreativeRequest(GeyserSession session, Inve
return rejectRequest(request);
}

ItemStack dropStack;
if (dropAction.getCount() == javaCreativeItem.getAmount()) {
dropStack = javaCreativeItem;
} else {
// Specify custom count
dropStack = new ItemStack(javaCreativeItem.getId(), dropAction.getCount(), javaCreativeItem.getDataComponents());
}
ServerboundSetCreativeModeSlotPacket creativeDropPacket = new ServerboundSetCreativeModeSlotPacket((short)-1, dropStack);
ItemStack dropItem = javaCreativeItem.getItemStack(Math.min(dropAction.getCount(), javaCreativeItem.maxStackSize()));
ServerboundSetCreativeModeSlotPacket creativeDropPacket = new ServerboundSetCreativeModeSlotPacket((short)-1, dropItem);
session.sendDownstreamGamePacket(creativeDropPacket);
break;
}
Expand All @@ -513,14 +544,47 @@ protected ItemStackResponse translateCreativeRequest(GeyserSession session, Inve
return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
}

private static void sendCreativeAction(GeyserSession session, Inventory inventory, int slot) {
private void sendCreativeAction(GeyserSession session, Inventory inventory, int slot) {
GeyserItemStack item = inventory.getItem(slot);

// This does not match java client behaviour, but the java server will ignore creative actions with illegal stack sizes
if (item.getAmount() > item.maxStackSize()) {
item.setAmount(item.maxStackSize());
this.refreshPending = true;
}

ItemStack itemStack = item.isEmpty() ? new ItemStack(-1, 0, null) : item.getItemStack();

ServerboundSetCreativeModeSlotPacket creativePacket = new ServerboundSetCreativeModeSlotPacket((short)slot, itemStack);
session.sendDownstreamGamePacket(creativePacket);
}

private static void emulateCreativeQuickMove(GeyserSession session, Inventory inventory, IntSet affectedSlots, GeyserItemStack creativeItem) {
int firstEmptySlot = -1; // Leftover stack is stored here

for (int i = 0; i < 36; i++) {
int slot = i < 9 ? i + 36 : i; // First iterate hotbar, then inventory
GeyserItemStack slotItem = inventory.getItem(slot);

if (firstEmptySlot == -1 && slotItem.isEmpty()) {
firstEmptySlot = slot;
}

if (InventoryUtils.canStack(slotItem, creativeItem) && slotItem.capacity() > 0) {
creativeItem.sub(slotItem.add(creativeItem.getAmount())); // Transfer as much as possible without passing stack capacity
affectedSlots.add(slot);
if (creativeItem.isEmpty()) {
return;
}
}
}

if (firstEmptySlot != -1) {
inventory.setItem(firstEmptySlot, creativeItem, session);
affectedSlots.add(firstEmptySlot);
}
}

private static boolean isCraftingGrid(ItemStackRequestSlotData slotInfoData) {
return slotInfoData.getContainer() == ContainerSlotType.CRAFTING_INPUT;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ public final class ItemTranslator {
private ItemTranslator() {
}

public static ItemStack translateToJava(GeyserSession session, ItemData data) {
public static GeyserItemStack translateToJava(GeyserSession session, ItemData data) {
if (data == null) {
return new ItemStack(Items.AIR_ID);
return GeyserItemStack.EMPTY;
}

ItemMapping bedrockItem = session.getItemMappings().getMapping(data);
Expand All @@ -119,7 +119,7 @@ public static ItemStack translateToJava(GeyserSession session, ItemData data) {
itemStack.setComponents(components);
}
}
return itemStack.getItemStack();
return itemStack;
}

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