Inventory Fixes (#602)

* Fix edge case when shift clicking an output slot

* Don't send window close packet if window is already closed

* Limit amount of window close packets sent to the client

Fixes hidden inventory bar bug

* Restrict user from unusable chest inventory slots

* Fix crafting table slot mappings

* Always send cursor update
This commit is contained in:
AJ Ferguson 2020-06-02 08:48:26 -08:00 committed by GitHub
parent 18891a22f1
commit 3d357af739
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 149 additions and 36 deletions

View file

@ -166,6 +166,9 @@ public class GeyserSession implements CommandSender {
@Setter @Setter
private int craftSlot = 0; private int craftSlot = 0;
@Setter
private long lastWindowCloseTime = 0;
private MinecraftProtocol protocol; private MinecraftProtocol protocol;
public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) {

View file

@ -38,17 +38,23 @@ public class BedrockContainerCloseTranslator extends PacketTranslator<ContainerC
@Override @Override
public void translate(ContainerClosePacket packet, GeyserSession session) { public void translate(ContainerClosePacket packet, GeyserSession session) {
session.setLastWindowCloseTime(0);
byte windowId = packet.getWindowId(); byte windowId = packet.getWindowId();
Inventory openInventory = session.getInventoryCache().getOpenInventory();
if (windowId == -1) { //player inventory or crafting table if (windowId == -1) { //player inventory or crafting table
Inventory openInventory = session.getInventoryCache().getOpenInventory();
if (openInventory != null) { if (openInventory != null) {
windowId = (byte) openInventory.getId(); windowId = (byte) openInventory.getId();
} else { } else {
windowId = 0; windowId = 0;
} }
} }
ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(windowId);
session.sendDownstreamPacket(closeWindowPacket); if (windowId == 0 || (openInventory != null && openInventory.getId() == windowId)) {
InventoryUtils.closeInventory(session, windowId); ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(windowId);
session.getDownstream().getSession().send(closeWindowPacket);
InventoryUtils.closeInventory(session, windowId);
} else if (openInventory != null && openInventory.getId() != windowId) {
InventoryUtils.openInventory(session, openInventory);
}
} }
} }

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory;
import com.nukkitx.protocol.bedrock.data.InventoryActionData;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.updater.ChestInventoryUpdater;
import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
import org.geysermc.connector.utils.InventoryUtils;
import java.util.List;
public abstract class ChestInventoryTranslator extends BaseInventoryTranslator {
private final InventoryUpdater updater;
public ChestInventoryTranslator(int size, int paddedSize) {
super(size);
this.updater = new ChestInventoryUpdater(paddedSize);
}
@Override
public void updateInventory(GeyserSession session, Inventory inventory) {
updater.updateInventory(this, session, inventory);
}
@Override
public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
updater.updateSlot(this, session, inventory, slot);
}
@Override
public void translateActions(GeyserSession session, Inventory inventory, List<InventoryActionData> actions) {
for (InventoryActionData action : actions) {
if (action.getSource().getContainerId() == inventory.getId()) {
if (action.getSlot() >= size) {
updateInventory(session, inventory);
InventoryUtils.updateCursor(session);
return;
}
}
}
super.translateActions(session, inventory, actions);
}
}

View file

@ -92,7 +92,10 @@ public class CraftingInventoryTranslator extends BaseInventoryTranslator {
@Override @Override
public int javaSlotToBedrock(int slot) { public int javaSlotToBedrock(int slot) {
return slot == 0 ? 50 : slot + 31; if (slot < size) {
return slot == 0 ? 50 : slot + 31;
}
return super.javaSlotToBedrock(slot);
} }
@Override @Override

View file

@ -39,15 +39,13 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.network.translators.inventory.updater.ChestInventoryUpdater; import org.geysermc.connector.network.translators.inventory.updater.ChestInventoryUpdater;
import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
public class DoubleChestInventoryTranslator extends BaseInventoryTranslator { public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
private final int blockId; private final int blockId;
private final InventoryUpdater updater;
public DoubleChestInventoryTranslator(int size) { public DoubleChestInventoryTranslator(int size) {
super(size); super(size, 54);
BlockState javaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]"); BlockState javaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]");
this.blockId = BlockTranslator.getBedrockBlockId(javaBlockState); this.blockId = BlockTranslator.getBedrockBlockId(javaBlockState);
this.updater = new ChestInventoryUpdater(54);
} }
@Override @Override
@ -128,14 +126,4 @@ public class DoubleChestInventoryTranslator extends BaseInventoryTranslator {
blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock)); blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock));
session.sendUpstreamPacket(blockPacket); session.sendUpstreamPacket(blockPacket);
} }
@Override
public void updateInventory(GeyserSession session, Inventory inventory) {
updater.updateInventory(this, session, inventory);
}
@Override
public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
updater.updateSlot(this, session, inventory, slot);
}
} }

View file

@ -25,11 +25,35 @@
package org.geysermc.connector.network.translators.inventory; package org.geysermc.connector.network.translators.inventory;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockState;
import com.nukkitx.protocol.bedrock.data.ContainerType; import com.nukkitx.protocol.bedrock.data.ContainerType;
import org.geysermc.connector.network.translators.inventory.updater.ChestInventoryUpdater; import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder;
import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
public class SingleChestInventoryTranslator extends ChestInventoryTranslator {
private final InventoryHolder holder;
public class SingleChestInventoryTranslator extends BlockInventoryTranslator {
public SingleChestInventoryTranslator(int size) { public SingleChestInventoryTranslator(int size) {
super(size, "minecraft:chest[facing=north,type=single,waterlogged=false]", ContainerType.CONTAINER, new ChestInventoryUpdater(27)); super(size, 27);
BlockState javaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]");
this.holder = new BlockInventoryHolder(BlockTranslator.getBedrockBlockId(javaBlockState), ContainerType.CONTAINER);
}
@Override
public void prepareInventory(GeyserSession session, Inventory inventory) {
holder.prepareInventory(this, session, inventory);
}
@Override
public void openInventory(GeyserSession session, Inventory inventory) {
holder.openInventory(this, session, inventory);
}
@Override
public void closeInventory(GeyserSession session, Inventory inventory) {
holder.closeInventory(this, session, inventory);
} }
} }

View file

@ -187,11 +187,12 @@ public class InventoryActionDataTranslator {
} else if (translator.getSlotType(javaSlot) == SlotType.OUTPUT) { } else if (translator.getSlotType(javaSlot) == SlotType.OUTPUT) {
plan.add(Click.LEFT, javaSlot); plan.add(Click.LEFT, javaSlot);
} else { } else {
int cursorSlot = findTempSlot(inventory, session.getInventory().getCursor(), Collections.singletonList(javaSlot)); int cursorSlot = findTempSlot(inventory, session.getInventory().getCursor(), Collections.singletonList(javaSlot), false);
if (cursorSlot != -1) { if (cursorSlot != -1) {
plan.add(Click.LEFT, cursorSlot); plan.add(Click.LEFT, cursorSlot);
} else { } else {
translator.updateInventory(session, inventory); translator.updateInventory(session, inventory);
InventoryUtils.updateCursor(session);
return; return;
} }
plan.add(Click.LEFT, javaSlot); plan.add(Click.LEFT, javaSlot);
@ -245,11 +246,15 @@ public class InventoryActionDataTranslator {
int cursorSlot = -1; int cursorSlot = -1;
if (session.getInventory().getCursor() != null) { //move cursor contents to a temporary slot if (session.getInventory().getCursor() != null) { //move cursor contents to a temporary slot
cursorSlot = findTempSlot(inventory, session.getInventory().getCursor(), Arrays.asList(fromSlot, toSlot)); cursorSlot = findTempSlot(inventory,
session.getInventory().getCursor(),
Arrays.asList(fromSlot, toSlot),
translator.getSlotType(fromSlot) == SlotType.OUTPUT);
if (cursorSlot != -1) { if (cursorSlot != -1) {
plan.add(Click.LEFT, cursorSlot); plan.add(Click.LEFT, cursorSlot);
} else { } else {
translator.updateInventory(session, inventory); translator.updateInventory(session, inventory);
InventoryUtils.updateCursor(session);
return; return;
} }
} }
@ -298,7 +303,7 @@ public class InventoryActionDataTranslator {
InventoryUtils.updateCursor(session); InventoryUtils.updateCursor(session);
} }
private static int findTempSlot(Inventory inventory, ItemStack item, List<Integer> slotBlacklist) { private static int findTempSlot(Inventory inventory, ItemStack item, List<Integer> slotBlacklist, boolean emptyOnly) {
/*try and find a slot that can temporarily store the given item /*try and find a slot that can temporarily store the given item
only look in the main inventory and hotbar only look in the main inventory and hotbar
only slots that are empty or contain a different type of item are valid*/ only slots that are empty or contain a different type of item are valid*/
@ -314,6 +319,9 @@ public class InventoryActionDataTranslator {
ItemStack testItem = inventory.getItem(i); ItemStack testItem = inventory.getItem(i);
boolean acceptable = true; boolean acceptable = true;
if (testItem != null) { if (testItem != null) {
if (emptyOnly) {
continue;
}
for (ItemStack blacklistItem : itemBlacklist) { for (ItemStack blacklistItem : itemBlacklist) {
if (InventoryUtils.canStack(testItem, blacklistItem)) { if (InventoryUtils.canStack(testItem, blacklistItem)) {
acceptable = false; acceptable = false;

View file

@ -26,7 +26,6 @@
package org.geysermc.connector.network.translators.java.window; package org.geysermc.connector.network.translators.java.window;
import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerCloseWindowPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerCloseWindowPacket;
import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.Translator;
@ -37,9 +36,7 @@ public class JavaCloseWindowTranslator extends PacketTranslator<ServerCloseWindo
@Override @Override
public void translate(ServerCloseWindowPacket packet, GeyserSession session) { public void translate(ServerCloseWindowPacket packet, GeyserSession session) {
ContainerClosePacket closePacket = new ContainerClosePacket(); InventoryUtils.closeWindow(session, packet.getWindowId());
closePacket.setWindowId((byte)packet.getWindowId());
session.sendUpstreamPacket(closePacket);
InventoryUtils.closeInventory(session, packet.getWindowId()); InventoryUtils.closeInventory(session, packet.getWindowId());
} }
} }

View file

@ -38,8 +38,6 @@ import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.utils.InventoryUtils; import org.geysermc.connector.utils.InventoryUtils;
import java.util.concurrent.TimeUnit;
@Translator(packet = ServerOpenWindowPacket.class) @Translator(packet = ServerOpenWindowPacket.class)
public class JavaOpenWindowTranslator extends PacketTranslator<ServerOpenWindowPacket> { public class JavaOpenWindowTranslator extends PacketTranslator<ServerOpenWindowPacket> {
@ -80,8 +78,11 @@ public class JavaOpenWindowTranslator extends PacketTranslator<ServerOpenWindowP
if (openInventory != null) { if (openInventory != null) {
InventoryTranslator openTranslator = InventoryTranslator.INVENTORY_TRANSLATORS.get(openInventory.getWindowType()); InventoryTranslator openTranslator = InventoryTranslator.INVENTORY_TRANSLATORS.get(openInventory.getWindowType());
if (!openTranslator.getClass().equals(newTranslator.getClass())) { if (!openTranslator.getClass().equals(newTranslator.getClass())) {
InventoryUtils.closeWindow(session, openInventory.getId());
InventoryUtils.closeInventory(session, openInventory.getId()); InventoryUtils.closeInventory(session, openInventory.getId());
GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> InventoryUtils.openInventory(session, newInventory), 500, TimeUnit.MILLISECONDS); session.getInventoryCache().setOpenInventory(newInventory);
//The new window will be opened when the bedrock client sends the
//window close confirmation in BedrockContainerCloseTranslator
return; return;
} }
} }

View file

@ -41,8 +41,6 @@ public class JavaSetSlotTranslator extends PacketTranslator<ServerSetSlotPacket>
@Override @Override
public void translate(ServerSetSlotPacket packet, GeyserSession session) { public void translate(ServerSetSlotPacket packet, GeyserSession session) {
if (packet.getWindowId() == 255 && packet.getSlot() == -1) { //cursor if (packet.getWindowId() == 255 && packet.getSlot() == -1) { //cursor
if (Objects.equals(session.getInventory().getCursor(), packet.getItem()))
return;
if (session.getCraftSlot() != 0) if (session.getCraftSlot() != 0)
return; return;

View file

@ -31,6 +31,7 @@ import com.nukkitx.nbt.CompoundTagBuilder;
import com.nukkitx.nbt.tag.StringTag; import com.nukkitx.nbt.tag.StringTag;
import com.nukkitx.protocol.bedrock.data.ContainerId; import com.nukkitx.protocol.bedrock.data.ContainerId;
import com.nukkitx.protocol.bedrock.data.ItemData; import com.nukkitx.protocol.bedrock.data.ItemData;
import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket;
import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
import org.geysermc.common.ChatColor; import org.geysermc.common.ChatColor;
import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserConnector;
@ -69,19 +70,34 @@ public class InventoryUtils {
public static void closeInventory(GeyserSession session, int windowId) { public static void closeInventory(GeyserSession session, int windowId) {
if (windowId != 0) { if (windowId != 0) {
Inventory inventory = session.getInventoryCache().getInventories().get(windowId); Inventory inventory = session.getInventoryCache().getInventories().get(windowId);
if (inventory != null) { Inventory openInventory = session.getInventoryCache().getOpenInventory();
session.getInventoryCache().uncacheInventory(windowId);
if (inventory != null && openInventory != null && inventory.getId() == openInventory.getId()) {
InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType());
translator.closeInventory(session, inventory); translator.closeInventory(session, inventory);
session.getInventoryCache().uncacheInventory(windowId);
session.getInventoryCache().setOpenInventory(null); session.getInventoryCache().setOpenInventory(null);
} else {
return;
} }
} else { } else {
Inventory inventory = session.getInventory(); Inventory inventory = session.getInventory();
InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType());
translator.updateInventory(session, inventory); translator.updateInventory(session, inventory);
} }
session.setCraftSlot(0); session.setCraftSlot(0);
session.getInventory().setCursor(null); session.getInventory().setCursor(null);
updateCursor(session);
}
public static void closeWindow(GeyserSession session, int windowId) {
//Spamming close window packets can bug the client
if (System.currentTimeMillis() - session.getLastWindowCloseTime() > 500) {
ContainerClosePacket closePacket = new ContainerClosePacket();
closePacket.setWindowId((byte) windowId);
session.sendUpstreamPacket(closePacket);
session.setLastWindowCloseTime(System.currentTimeMillis());
}
} }
public static void updateCursor(GeyserSession session) { public static void updateCursor(GeyserSession session) {