3
0
Mirror von https://github.com/GeyserMC/Geyser.git synchronisiert 2024-11-20 06:50:09 +01:00

Handle illegal stack sizes in creative mode

Dieser Commit ist enthalten in:
AJ Ferguson 2024-08-31 20:24:18 -04:00
Ursprung 63e60bc93c
Commit 0553905578
4 geänderte Dateien mit 119 neuen und 42 gelöschten Zeilen

Datei anzeigen

@ -85,6 +85,10 @@ public class GeyserItemStack {
return isEmpty() ? 0 : amount;
}
public int maxStackSize() {
return getComponent(DataComponentType.MAX_STACK_SIZE, asItem().maxStackSize());
}
public @Nullable DataComponents getComponents() {
return isEmpty() ? null : components;
}
@ -133,12 +137,20 @@ public class GeyserItemStack {
return isEmpty() ? 0 : netId;
}
public void add(int add) {
amount += add;
public int capacity() {
return Math.max(maxStackSize() - amount, 0);
}
public void sub(int sub) {
public int add(int add) {
add = Math.min(add, capacity());
amount += add;
return add;
}
public int sub(int sub) {
sub = Math.min(sub, amount);
amount -= sub;
return sub;
}
public ItemStack getItemStack() {

Datei anzeigen

@ -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;
@ -63,7 +63,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.recipe.Ingredient;
import java.util.*;
@AllArgsConstructor
@RequiredArgsConstructor
public abstract class InventoryTranslator {
public static final InventoryTranslator PLAYER_INVENTORY_TRANSLATOR = new PlayerInventoryTranslator();
@ -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);
@ -157,7 +158,7 @@ public abstract class InventoryTranslator {
}
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;
@ -182,14 +183,14 @@ public abstract class InventoryTranslator {
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);
}

Datei anzeigen

@ -244,6 +244,8 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
PlayerInventory playerInv = session.getPlayerInventory();
IntSet affectedSlots = new IntOpenHashSet();
actionLoop:
for (ItemStackRequestAction action : request.getActions()) {
switch (action.getType()) {
case TAKE, PLACE -> {
@ -260,15 +262,21 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
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);
// 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();
@ -278,10 +286,11 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
return rejectRequest(request);
}
inventory.getItem(destSlot).add(transferAmount);
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());
@ -292,13 +301,19 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
return rejectRequest(request);
}
inventory.getItem(destSlot).add(transferAmount);
transferAmount = inventory.getItem(destSlot).add(transferAmount);
if (transferAmount > 0) {
sourceItem.sub(transferAmount);
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 -> {
SwapAction swapAction = (SwapAction) action;
if (!(checkNetId(session, inventory, swapAction.getSource()) && checkNetId(session, inventory, swapAction.getDestination()))) {
@ -361,10 +376,16 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
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
@ -404,9 +425,12 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
@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: {
@ -456,26 +480,39 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
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: {
@ -489,14 +526,8 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
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;
}
@ -513,14 +544,47 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
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;
}

Datei anzeigen

@ -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);
@ -119,7 +119,7 @@ public final class ItemTranslator {
itemStack.setComponents(components);
}
}
return itemStack.getItemStack();
return itemStack;
}
public static ItemData.@NonNull Builder translateToBedrock(GeyserSession session, int javaId, int count, DataComponents components) {