Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-11-20 15:00:11 +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;
|
return isEmpty() ? 0 : amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int maxStackSize() {
|
||||||
|
return getComponent(DataComponentType.MAX_STACK_SIZE, asItem().maxStackSize());
|
||||||
|
}
|
||||||
|
|
||||||
public @Nullable DataComponents getComponents() {
|
public @Nullable DataComponents getComponents() {
|
||||||
return isEmpty() ? null : components;
|
return isEmpty() ? null : components;
|
||||||
}
|
}
|
||||||
@ -133,12 +137,20 @@ public class GeyserItemStack {
|
|||||||
return isEmpty() ? 0 : netId;
|
return isEmpty() ? 0 : netId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(int add) {
|
public int capacity() {
|
||||||
amount += add;
|
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;
|
amount -= sub;
|
||||||
|
return sub;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemStack getItemStack() {
|
public ItemStack getItemStack() {
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
package org.geysermc.geyser.translator.inventory;
|
package org.geysermc.geyser.translator.inventory;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.ints.*;
|
import it.unimi.dsi.fastutil.ints.*;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
|
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName;
|
import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName;
|
||||||
@ -63,7 +63,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.recipe.Ingredient;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@AllArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public abstract class InventoryTranslator {
|
public abstract class InventoryTranslator {
|
||||||
|
|
||||||
public static final InventoryTranslator PLAYER_INVENTORY_TRANSLATOR = new PlayerInventoryTranslator();
|
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 static final int PLAYER_INVENTORY_OFFSET = 9;
|
||||||
|
|
||||||
public final int size;
|
public final int size;
|
||||||
|
protected boolean refreshPending;
|
||||||
|
|
||||||
public abstract boolean prepareInventory(GeyserSession session, Inventory inventory);
|
public abstract boolean prepareInventory(GeyserSession session, Inventory inventory);
|
||||||
public abstract void openInventory(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) {
|
public final void translateRequests(GeyserSession session, Inventory inventory, List<ItemStackRequest> requests) {
|
||||||
boolean refresh = false;
|
this.refreshPending = false;
|
||||||
ItemStackResponsePacket responsePacket = new ItemStackResponsePacket();
|
ItemStackResponsePacket responsePacket = new ItemStackResponsePacket();
|
||||||
for (ItemStackRequest request : requests) {
|
for (ItemStackRequest request : requests) {
|
||||||
ItemStackResponse response;
|
ItemStackResponse response;
|
||||||
@ -182,14 +183,14 @@ public abstract class InventoryTranslator {
|
|||||||
|
|
||||||
if (response.getResult() != ItemStackResponseStatus.OK) {
|
if (response.getResult() != ItemStackResponseStatus.OK) {
|
||||||
// Sync our copy of the inventory with Bedrock's to prevent desyncs
|
// Sync our copy of the inventory with Bedrock's to prevent desyncs
|
||||||
refresh = true;
|
this.refreshPending = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
responsePacket.getEntries().add(response);
|
responsePacket.getEntries().add(response);
|
||||||
}
|
}
|
||||||
session.sendUpstreamPacket(responsePacket);
|
session.sendUpstreamPacket(responsePacket);
|
||||||
|
|
||||||
if (refresh) {
|
if (this.refreshPending) {
|
||||||
InventoryUtils.updateCursor(session);
|
InventoryUtils.updateCursor(session);
|
||||||
updateInventory(session, inventory);
|
updateInventory(session, inventory);
|
||||||
}
|
}
|
||||||
|
@ -244,6 +244,8 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
|||||||
|
|
||||||
PlayerInventory playerInv = session.getPlayerInventory();
|
PlayerInventory playerInv = session.getPlayerInventory();
|
||||||
IntSet affectedSlots = new IntOpenHashSet();
|
IntSet affectedSlots = new IntOpenHashSet();
|
||||||
|
|
||||||
|
actionLoop:
|
||||||
for (ItemStackRequestAction action : request.getActions()) {
|
for (ItemStackRequestAction action : request.getActions()) {
|
||||||
switch (action.getType()) {
|
switch (action.getType()) {
|
||||||
case TAKE, PLACE -> {
|
case TAKE, PLACE -> {
|
||||||
@ -260,15 +262,21 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
|||||||
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
|
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
|
||||||
GeyserItemStack sourceItem = inventory.getItem(sourceSlot);
|
GeyserItemStack sourceItem = inventory.getItem(sourceSlot);
|
||||||
if (playerInv.getCursor().isEmpty()) {
|
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())) {
|
} else if (!InventoryUtils.canStack(sourceItem, playerInv.getCursor())) {
|
||||||
return rejectRequest(request);
|
return rejectRequest(request);
|
||||||
|
} else {
|
||||||
|
transferAmount = playerInv.getCursor().add(transferAmount);
|
||||||
|
sourceItem.sub(transferAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
playerInv.getCursor().add(transferAmount);
|
// Don't add to affectedSlots if nothing changed.
|
||||||
sourceItem.sub(transferAmount);
|
// Prevents sendCreativeAction from adjusting stack size.
|
||||||
|
if (transferAmount > 0) {
|
||||||
affectedSlots.add(sourceSlot);
|
affectedSlots.add(sourceSlot);
|
||||||
|
}
|
||||||
} else if (isCursor(transferAction.getSource())) {
|
} else if (isCursor(transferAction.getSource())) {
|
||||||
int destSlot = bedrockSlotToJava(transferAction.getDestination());
|
int destSlot = bedrockSlotToJava(transferAction.getDestination());
|
||||||
GeyserItemStack sourceItem = playerInv.getCursor();
|
GeyserItemStack sourceItem = playerInv.getCursor();
|
||||||
@ -278,10 +286,11 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
|||||||
return rejectRequest(request);
|
return rejectRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
inventory.getItem(destSlot).add(transferAmount);
|
transferAmount = inventory.getItem(destSlot).add(transferAmount);
|
||||||
sourceItem.sub(transferAmount);
|
if (transferAmount > 0) {
|
||||||
|
sourceItem.sub(transferAmount);
|
||||||
affectedSlots.add(destSlot);
|
affectedSlots.add(destSlot);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
|
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
|
||||||
int destSlot = bedrockSlotToJava(transferAction.getDestination());
|
int destSlot = bedrockSlotToJava(transferAction.getDestination());
|
||||||
@ -292,11 +301,17 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
|||||||
return rejectRequest(request);
|
return rejectRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
inventory.getItem(destSlot).add(transferAmount);
|
transferAmount = inventory.getItem(destSlot).add(transferAmount);
|
||||||
sourceItem.sub(transferAmount);
|
if (transferAmount > 0) {
|
||||||
|
sourceItem.sub(transferAmount);
|
||||||
|
affectedSlots.add(sourceSlot);
|
||||||
|
affectedSlots.add(destSlot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
affectedSlots.add(sourceSlot);
|
if (transferAction.getCount() != transferAmount) {
|
||||||
affectedSlots.add(destSlot);
|
this.refreshPending = true; // Fixes visual bug with cursor
|
||||||
|
break actionLoop; // Inventory is not what client expects right now
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case SWAP -> {
|
case SWAP -> {
|
||||||
@ -361,10 +376,16 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
|||||||
return rejectRequest(request);
|
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);
|
session.sendDownstreamGamePacket(creativeDropPacket);
|
||||||
|
|
||||||
sourceItem.sub(dropAction.getCount());
|
sourceItem.sub(dropAmount);
|
||||||
}
|
}
|
||||||
case DESTROY -> {
|
case DESTROY -> {
|
||||||
// Only called when a creative client wants to destroy an item... I think - Camotoy
|
// Only called when a creative client wants to destroy an item... I think - Camotoy
|
||||||
@ -404,9 +425,12 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ItemStackResponse translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
|
protected ItemStackResponse translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
|
||||||
ItemStack javaCreativeItem = null;
|
GeyserItemStack javaCreativeItem = null;
|
||||||
IntSet affectedSlots = new IntOpenHashSet();
|
IntSet affectedSlots = new IntOpenHashSet();
|
||||||
CraftState craftState = CraftState.START;
|
CraftState craftState = CraftState.START;
|
||||||
|
boolean firstTransfer = true;
|
||||||
|
|
||||||
|
actionLoop:
|
||||||
for (ItemStackRequestAction action : request.getActions()) {
|
for (ItemStackRequestAction action : request.getActions()) {
|
||||||
switch (action.getType()) {
|
switch (action.getType()) {
|
||||||
case CRAFT_CREATIVE: {
|
case CRAFT_CREATIVE: {
|
||||||
@ -456,26 +480,39 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
|||||||
return rejectRequest(request);
|
return rejectRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int transferAmount = Math.min(transferAction.getCount(), javaCreativeItem.maxStackSize());
|
||||||
if (isCursor(transferAction.getDestination())) {
|
if (isCursor(transferAction.getDestination())) {
|
||||||
if (session.getPlayerInventory().getCursor().isEmpty()) {
|
if (session.getPlayerInventory().getCursor().isEmpty()) {
|
||||||
GeyserItemStack newItemStack = GeyserItemStack.from(javaCreativeItem);
|
GeyserItemStack newItemStack = javaCreativeItem.copy(transferAmount);
|
||||||
newItemStack.setAmount(transferAction.getCount());
|
|
||||||
session.getPlayerInventory().setCursor(newItemStack, session);
|
session.getPlayerInventory().setCursor(newItemStack, session);
|
||||||
} else {
|
} else {
|
||||||
session.getPlayerInventory().getCursor().add(transferAction.getCount());
|
transferAmount = session.getPlayerInventory().getCursor().add(transferAmount);
|
||||||
}
|
}
|
||||||
//cursor is always included in response
|
//cursor is always included in response
|
||||||
} else {
|
} else {
|
||||||
int destSlot = bedrockSlotToJava(transferAction.getDestination());
|
int destSlot = bedrockSlotToJava(transferAction.getDestination());
|
||||||
if (inventory.getItem(destSlot).isEmpty()) {
|
if (inventory.getItem(destSlot).isEmpty()) {
|
||||||
GeyserItemStack newItemStack = GeyserItemStack.from(javaCreativeItem);
|
GeyserItemStack newItemStack = javaCreativeItem.copy(transferAmount);
|
||||||
newItemStack.setAmount(transferAction.getCount());
|
|
||||||
inventory.setItem(destSlot, newItemStack, session);
|
inventory.setItem(destSlot, newItemStack, session);
|
||||||
} else {
|
} 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);
|
affectedSlots.add(destSlot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
firstTransfer = false;
|
||||||
|
if (transferAmount != transferAction.getCount()) {
|
||||||
|
this.refreshPending = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DROP: {
|
case DROP: {
|
||||||
@ -489,14 +526,8 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
|||||||
return rejectRequest(request);
|
return rejectRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemStack dropStack;
|
ItemStack dropItem = javaCreativeItem.getItemStack(Math.min(dropAction.getCount(), javaCreativeItem.maxStackSize()));
|
||||||
if (dropAction.getCount() == javaCreativeItem.getAmount()) {
|
ServerboundSetCreativeModeSlotPacket creativeDropPacket = new ServerboundSetCreativeModeSlotPacket((short)-1, dropItem);
|
||||||
dropStack = javaCreativeItem;
|
|
||||||
} else {
|
|
||||||
// Specify custom count
|
|
||||||
dropStack = new ItemStack(javaCreativeItem.getId(), dropAction.getCount(), javaCreativeItem.getDataComponents());
|
|
||||||
}
|
|
||||||
ServerboundSetCreativeModeSlotPacket creativeDropPacket = new ServerboundSetCreativeModeSlotPacket((short)-1, dropStack);
|
|
||||||
session.sendDownstreamGamePacket(creativeDropPacket);
|
session.sendDownstreamGamePacket(creativeDropPacket);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -513,14 +544,47 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
|||||||
return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
|
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);
|
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();
|
ItemStack itemStack = item.isEmpty() ? new ItemStack(-1, 0, null) : item.getItemStack();
|
||||||
|
|
||||||
ServerboundSetCreativeModeSlotPacket creativePacket = new ServerboundSetCreativeModeSlotPacket((short)slot, itemStack);
|
ServerboundSetCreativeModeSlotPacket creativePacket = new ServerboundSetCreativeModeSlotPacket((short)slot, itemStack);
|
||||||
session.sendDownstreamGamePacket(creativePacket);
|
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) {
|
private static boolean isCraftingGrid(ItemStackRequestSlotData slotInfoData) {
|
||||||
return slotInfoData.getContainer() == ContainerSlotType.CRAFTING_INPUT;
|
return slotInfoData.getContainer() == ContainerSlotType.CRAFTING_INPUT;
|
||||||
}
|
}
|
||||||
|
@ -100,9 +100,9 @@ public final class ItemTranslator {
|
|||||||
private ItemTranslator() {
|
private ItemTranslator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ItemStack translateToJava(GeyserSession session, ItemData data) {
|
public static GeyserItemStack translateToJava(GeyserSession session, ItemData data) {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return new ItemStack(Items.AIR_ID);
|
return GeyserItemStack.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemMapping bedrockItem = session.getItemMappings().getMapping(data);
|
ItemMapping bedrockItem = session.getItemMappings().getMapping(data);
|
||||||
@ -119,7 +119,7 @@ public final class ItemTranslator {
|
|||||||
itemStack.setComponents(components);
|
itemStack.setComponents(components);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return itemStack.getItemStack();
|
return itemStack;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ItemData.@NonNull Builder translateToBedrock(GeyserSession session, int javaId, int count, DataComponents components) {
|
public static ItemData.@NonNull Builder translateToBedrock(GeyserSession session, int javaId, int count, DataComponents components) {
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren