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:
Ursprung
63e60bc93c
Commit
0553905578
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren