2019-09-29 23:25:42 +00:00
|
|
|
/*
|
2022-01-01 19:03:05 +00:00
|
|
|
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
2019-09-29 23:25:42 +00:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2021-11-20 23:29:46 +00:00
|
|
|
package org.geysermc.geyser.translator.protocol.bedrock;
|
2019-09-29 23:25:42 +00:00
|
|
|
|
2022-01-30 16:15:07 +00:00
|
|
|
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
2019-09-29 23:25:42 +00:00
|
|
|
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
2021-11-18 03:02:38 +00:00
|
|
|
import com.github.steveice10.mc.protocol.data.game.entity.object.Direction;
|
2019-10-27 09:56:47 +00:00
|
|
|
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
2019-09-29 23:25:42 +00:00
|
|
|
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
2019-10-07 18:30:08 +00:00
|
|
|
import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction;
|
2019-09-29 23:25:42 +00:00
|
|
|
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
|
2022-01-20 23:09:35 +00:00
|
|
|
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClickPacket;
|
2022-02-25 03:49:10 +00:00
|
|
|
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.*;
|
2020-04-23 06:01:33 +00:00
|
|
|
import com.nukkitx.math.vector.Vector3f;
|
2020-07-21 17:17:55 +00:00
|
|
|
import com.nukkitx.math.vector.Vector3i;
|
2020-04-29 20:01:53 +00:00
|
|
|
import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
2021-05-11 01:14:30 +00:00
|
|
|
import com.nukkitx.protocol.bedrock.data.inventory.*;
|
2021-01-08 03:43:36 +00:00
|
|
|
import com.nukkitx.protocol.bedrock.packet.*;
|
2022-01-30 16:15:07 +00:00
|
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
2022-02-25 15:31:00 +00:00
|
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
|
2022-01-30 16:15:07 +00:00
|
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
2022-01-20 23:09:35 +00:00
|
|
|
import org.geysermc.geyser.entity.EntityDefinitions;
|
2021-11-20 23:29:46 +00:00
|
|
|
import org.geysermc.geyser.entity.type.Entity;
|
|
|
|
import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
2021-11-20 21:34:30 +00:00
|
|
|
import org.geysermc.geyser.inventory.GeyserItemStack;
|
2022-01-20 23:09:35 +00:00
|
|
|
import org.geysermc.geyser.inventory.Inventory;
|
2022-02-25 15:31:00 +00:00
|
|
|
import org.geysermc.geyser.inventory.PlayerInventory;
|
2022-01-20 23:09:35 +00:00
|
|
|
import org.geysermc.geyser.inventory.click.Click;
|
2021-11-20 23:29:46 +00:00
|
|
|
import org.geysermc.geyser.level.block.BlockStateValues;
|
2021-11-20 21:34:30 +00:00
|
|
|
import org.geysermc.geyser.registry.BlockRegistries;
|
|
|
|
import org.geysermc.geyser.registry.type.ItemMapping;
|
|
|
|
import org.geysermc.geyser.registry.type.ItemMappings;
|
2022-01-20 23:09:35 +00:00
|
|
|
import org.geysermc.geyser.session.GeyserSession;
|
|
|
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
|
|
|
import org.geysermc.geyser.translator.protocol.Translator;
|
2021-11-20 23:29:46 +00:00
|
|
|
import org.geysermc.geyser.util.BlockUtils;
|
2022-02-25 03:49:10 +00:00
|
|
|
import org.geysermc.geyser.util.EntityUtils;
|
|
|
|
import org.geysermc.geyser.util.InteractionResult;
|
2022-01-20 23:09:35 +00:00
|
|
|
import org.geysermc.geyser.util.InventoryUtils;
|
2020-04-23 06:01:33 +00:00
|
|
|
|
2022-01-20 23:09:35 +00:00
|
|
|
import java.util.List;
|
2020-10-13 00:02:41 +00:00
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
2021-01-08 03:43:36 +00:00
|
|
|
/**
|
|
|
|
* BedrockInventoryTransactionTranslator handles most interactions between the client and the world,
|
|
|
|
* or the client and their inventory.
|
|
|
|
*/
|
2020-03-24 04:24:17 +00:00
|
|
|
@Translator(packet = InventoryTransactionPacket.class)
|
2019-09-29 23:25:42 +00:00
|
|
|
public class BedrockInventoryTransactionTranslator extends PacketTranslator<InventoryTransactionPacket> {
|
|
|
|
|
2021-01-08 03:43:36 +00:00
|
|
|
private static final float MAXIMUM_BLOCK_PLACING_DISTANCE = 64f;
|
|
|
|
private static final int CREATIVE_EYE_HEIGHT_PLACE_DISTANCE = 49;
|
|
|
|
private static final int SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE = 36;
|
|
|
|
private static final float MAXIMUM_BLOCK_DESTROYING_DISTANCE = 36f;
|
|
|
|
|
2019-09-29 23:25:42 +00:00
|
|
|
@Override
|
2021-11-22 19:52:26 +00:00
|
|
|
public void translate(GeyserSession session, InventoryTransactionPacket packet) {
|
2021-01-08 00:40:34 +00:00
|
|
|
// Send book updates before opening inventories
|
|
|
|
session.getBookEditCache().checkForSend();
|
|
|
|
|
2021-07-13 01:19:40 +00:00
|
|
|
ItemMappings mappings = session.getItemMappings();
|
|
|
|
|
2019-09-29 23:25:42 +00:00
|
|
|
switch (packet.getTransactionType()) {
|
2019-10-20 21:25:41 +00:00
|
|
|
case NORMAL:
|
2020-10-16 23:25:05 +00:00
|
|
|
if (packet.getActions().size() == 2) {
|
|
|
|
InventoryActionData worldAction = packet.getActions().get(0);
|
|
|
|
InventoryActionData containerAction = packet.getActions().get(1);
|
|
|
|
if (worldAction.getSource().getType() == InventorySource.Type.WORLD_INTERACTION
|
|
|
|
&& worldAction.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) {
|
2022-02-25 15:31:00 +00:00
|
|
|
boolean dropAll = worldAction.getToItem().getCount() > 1;
|
|
|
|
|
|
|
|
if (session.getPlayerInventory().getHeldItemSlot() != containerAction.getSlot()) {
|
|
|
|
// Dropping an item that you don't have selected isn't supported in Java, but we can workaround it with an inventory hack
|
|
|
|
PlayerInventory inventory = session.getPlayerInventory();
|
|
|
|
int hotbarSlot = inventory.getOffsetForHotbar(containerAction.getSlot());
|
|
|
|
Click clickType = dropAll ? Click.DROP_ALL : Click.DROP_ONE;
|
|
|
|
Int2ObjectMap<ItemStack> changedItem;
|
|
|
|
if (dropAll) {
|
|
|
|
inventory.setItem(hotbarSlot, GeyserItemStack.EMPTY, session);
|
|
|
|
changedItem = Int2ObjectMaps.singleton(hotbarSlot, null);
|
|
|
|
} else {
|
|
|
|
GeyserItemStack itemStack = inventory.getItem(hotbarSlot);
|
|
|
|
if (itemStack.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
itemStack.sub(1);
|
|
|
|
changedItem = Int2ObjectMaps.singleton(hotbarSlot, itemStack.getItemStack());
|
|
|
|
}
|
|
|
|
ServerboundContainerClickPacket dropPacket = new ServerboundContainerClickPacket(
|
|
|
|
inventory.getId(), inventory.getStateId(), hotbarSlot, clickType.actionType, clickType.action,
|
|
|
|
inventory.getCursor().getItemStack(), changedItem);
|
|
|
|
session.sendDownstreamPacket(dropPacket);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (session.getPlayerInventory().getItemInHand().isEmpty()) {
|
2021-08-17 00:39:29 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-10-16 23:25:05 +00:00
|
|
|
|
2022-02-25 15:31:00 +00:00
|
|
|
ServerboundPlayerActionPacket dropPacket = new ServerboundPlayerActionPacket(
|
2021-08-17 00:39:29 +00:00
|
|
|
dropAll ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM,
|
|
|
|
BlockUtils.POSITION_ZERO,
|
2021-11-18 03:02:38 +00:00
|
|
|
Direction.DOWN
|
2021-08-17 00:39:29 +00:00
|
|
|
);
|
2022-02-25 15:31:00 +00:00
|
|
|
session.sendDownstreamPacket(dropPacket);
|
2021-08-17 00:39:29 +00:00
|
|
|
|
|
|
|
if (dropAll) {
|
|
|
|
session.getPlayerInventory().setItemInHand(GeyserItemStack.EMPTY);
|
|
|
|
} else {
|
|
|
|
session.getPlayerInventory().getItemInHand().sub(1);
|
|
|
|
}
|
2020-10-16 23:25:05 +00:00
|
|
|
}
|
|
|
|
}
|
2019-10-20 21:25:41 +00:00
|
|
|
break;
|
|
|
|
case INVENTORY_MISMATCH:
|
2019-11-28 03:55:58 +00:00
|
|
|
break;
|
2019-09-29 23:25:42 +00:00
|
|
|
case ITEM_USE:
|
2020-04-10 18:46:29 +00:00
|
|
|
switch (packet.getActionType()) {
|
2021-09-10 18:10:56 +00:00
|
|
|
case 0 -> {
|
2022-01-15 21:28:52 +00:00
|
|
|
Vector3i blockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getBlockFace());
|
|
|
|
|
|
|
|
if (session.getGeyser().getConfig().isDisableBedrockScaffolding()) {
|
|
|
|
float yaw = session.getPlayerEntity().getYaw();
|
|
|
|
boolean isGodBridging = switch (packet.getBlockFace()) {
|
|
|
|
case 2 -> yaw <= -135f || yaw > 135f;
|
|
|
|
case 3 -> yaw <= 45f && yaw > -45f;
|
|
|
|
case 4 -> yaw > 45f && yaw <= 135f;
|
|
|
|
case 5 -> yaw <= -45f && yaw > -135f;
|
|
|
|
default -> false;
|
|
|
|
};
|
|
|
|
if (isGodBridging) {
|
|
|
|
restoreCorrectBlock(session, blockPos, packet);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-24 16:54:18 +00:00
|
|
|
// Check to make sure the client isn't spamming interaction
|
|
|
|
// Based on Nukkit 1.0, with changes to ensure holding down still works
|
|
|
|
boolean hasAlreadyClicked = System.currentTimeMillis() - session.getLastInteractionTime() < 110.0 &&
|
2021-02-12 19:39:41 +00:00
|
|
|
packet.getBlockPosition().distanceSquared(session.getLastInteractionBlockPosition()) < 0.00001;
|
|
|
|
session.setLastInteractionBlockPosition(packet.getBlockPosition());
|
|
|
|
session.setLastInteractionPlayerPosition(session.getPlayerEntity().getPosition());
|
2020-09-24 16:54:18 +00:00
|
|
|
if (hasAlreadyClicked) {
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
// Only update the interaction time if it's valid - that way holding down still works.
|
|
|
|
session.setLastInteractionTime(System.currentTimeMillis());
|
|
|
|
}
|
2020-05-02 20:44:05 +00:00
|
|
|
|
|
|
|
// Bedrock sends block interact code for a Java entity so we send entity code back to Java
|
2021-07-13 01:19:40 +00:00
|
|
|
if (session.getBlockMappings().isItemFrame(packet.getBlockRuntimeId())) {
|
2021-05-09 19:44:41 +00:00
|
|
|
Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition());
|
|
|
|
if (itemFrameEntity != null) {
|
2022-02-25 03:49:10 +00:00
|
|
|
processEntityInteraction(session, packet, itemFrameEntity);
|
2021-05-09 19:44:41 +00:00
|
|
|
break;
|
|
|
|
}
|
2020-05-02 20:44:05 +00:00
|
|
|
}
|
|
|
|
|
2021-01-08 03:43:36 +00:00
|
|
|
/*
|
|
|
|
Checks to ensure that the range will be accepted by the server.
|
|
|
|
"Not in range" doesn't refer to how far a vanilla client goes (that's a whole other mess),
|
|
|
|
but how much a server will accept from the client maximum
|
|
|
|
*/
|
2021-09-10 01:27:38 +00:00
|
|
|
// Blocks cannot be placed or destroyed outside of the world border
|
|
|
|
if (!session.getWorldBorder().isInsideBorderBoundaries()) {
|
|
|
|
restoreCorrectBlock(session, blockPos, packet);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-01-08 03:43:36 +00:00
|
|
|
// CraftBukkit+ check - see https://github.com/PaperMC/Paper/blob/458db6206daae76327a64f4e2a17b67a7e38b426/Spigot-Server-Patches/0532-Move-range-check-for-block-placing-up.patch
|
|
|
|
Vector3f playerPosition = session.getPlayerEntity().getPosition();
|
|
|
|
|
|
|
|
// Adjust position for current eye height
|
2021-04-12 04:35:53 +00:00
|
|
|
switch (session.getPose()) {
|
2021-09-10 18:10:56 +00:00
|
|
|
case SNEAKING ->
|
2021-11-18 03:02:38 +00:00
|
|
|
playerPosition = playerPosition.sub(0, (EntityDefinitions.PLAYER.offset() - 1.27f), 0);
|
2021-09-10 18:10:56 +00:00
|
|
|
case SWIMMING,
|
|
|
|
FALL_FLYING, // Elytra
|
|
|
|
SPIN_ATTACK -> // Trident spin attack
|
2021-11-18 03:02:38 +00:00
|
|
|
playerPosition = playerPosition.sub(0, (EntityDefinitions.PLAYER.offset() - 0.4f), 0);
|
2021-09-10 18:10:56 +00:00
|
|
|
case SLEEPING ->
|
2021-11-18 03:02:38 +00:00
|
|
|
playerPosition = playerPosition.sub(0, (EntityDefinitions.PLAYER.offset() - 0.2f), 0);
|
2021-01-08 03:43:36 +00:00
|
|
|
} // else, we don't have to modify the position
|
|
|
|
|
|
|
|
float diffX = playerPosition.getX() - packet.getBlockPosition().getX();
|
|
|
|
float diffY = playerPosition.getY() - packet.getBlockPosition().getY();
|
|
|
|
float diffZ = playerPosition.getZ() - packet.getBlockPosition().getZ();
|
|
|
|
if (((diffX * diffX) + (diffY * diffY) + (diffZ * diffZ)) >
|
|
|
|
(session.getGameMode().equals(GameMode.CREATIVE) ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) {
|
|
|
|
restoreCorrectBlock(session, blockPos, packet);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Vanilla check
|
2021-11-18 03:02:38 +00:00
|
|
|
if (!(session.getPlayerEntity().getPosition().sub(0, EntityDefinitions.PLAYER.offset(), 0)
|
2021-01-08 03:43:36 +00:00
|
|
|
.distanceSquared(packet.getBlockPosition().toFloat().add(0.5f, 0.5f, 0.5f)) < MAXIMUM_BLOCK_PLACING_DISTANCE)) {
|
|
|
|
// The client thinks that its blocks have been successfully placed. Restore the server's blocks instead.
|
|
|
|
restoreCorrectBlock(session, blockPos, packet);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
Block place checks end - client is good to go
|
|
|
|
*/
|
|
|
|
|
2021-07-13 01:19:40 +00:00
|
|
|
if (packet.getItemInHand() != null && session.getItemMappings().getSpawnEggIds().contains(packet.getItemInHand().getId())) {
|
2021-11-20 21:34:30 +00:00
|
|
|
int blockState = session.getGeyser().getWorldManager().getBlockAt(session, packet.getBlockPosition());
|
2021-07-13 01:19:40 +00:00
|
|
|
if (blockState == BlockStateValues.JAVA_WATER_ID) {
|
2021-06-20 00:33:07 +00:00
|
|
|
// Otherwise causes multiple mobs to spawn - just send a use item packet
|
|
|
|
// TODO when we fix mobile bucket rotation, use it for this, too
|
2021-11-13 03:44:15 +00:00
|
|
|
ServerboundUseItemPacket itemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND);
|
2021-06-20 00:33:07 +00:00
|
|
|
session.sendDownstreamPacket(itemPacket);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-13 03:44:15 +00:00
|
|
|
ServerboundUseItemOnPacket blockPacket = new ServerboundUseItemOnPacket(
|
2020-04-10 18:46:29 +00:00
|
|
|
new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()),
|
2021-11-18 03:02:38 +00:00
|
|
|
Direction.VALUES[packet.getBlockFace()],
|
2020-04-10 18:46:29 +00:00
|
|
|
Hand.MAIN_HAND,
|
|
|
|
packet.getClickPosition().getX(), packet.getClickPosition().getY(), packet.getClickPosition().getZ(),
|
|
|
|
false);
|
2020-05-05 15:51:43 +00:00
|
|
|
session.sendDownstreamPacket(blockPacket);
|
2020-05-23 21:39:17 +00:00
|
|
|
|
2021-06-20 00:33:07 +00:00
|
|
|
if (packet.getItemInHand() != null) {
|
|
|
|
// Otherwise boats will not be able to be placed in survival and buckets won't work on mobile
|
2021-07-13 01:19:40 +00:00
|
|
|
if (session.getItemMappings().getBoatIds().contains(packet.getItemInHand().getId())) {
|
2021-11-13 03:44:15 +00:00
|
|
|
ServerboundUseItemPacket itemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND);
|
2020-10-13 00:02:41 +00:00
|
|
|
session.sendDownstreamPacket(itemPacket);
|
2021-08-02 02:20:15 +00:00
|
|
|
} else if (session.getItemMappings().getBucketIds().contains(packet.getItemInHand().getId())) {
|
2021-06-20 00:33:07 +00:00
|
|
|
// Let the server decide if the bucket item should change, not the client, and revert the changes the client made
|
|
|
|
InventorySlotPacket slotPacket = new InventorySlotPacket();
|
|
|
|
slotPacket.setContainerId(ContainerId.INVENTORY);
|
|
|
|
slotPacket.setSlot(packet.getHotbarSlot());
|
|
|
|
slotPacket.setItem(packet.getItemInHand());
|
|
|
|
session.sendUpstreamPacket(slotPacket);
|
2021-11-13 03:44:15 +00:00
|
|
|
// Don't send ServerboundUseItemPacket for powder snow buckets
|
2021-08-02 02:20:15 +00:00
|
|
|
if (packet.getItemInHand().getId() != session.getItemMappings().getStoredItems().powderSnowBucket().getBedrockId()) {
|
|
|
|
// Special check for crafting tables since clients don't send BLOCK_INTERACT when interacting
|
2021-11-20 21:34:30 +00:00
|
|
|
int blockState = session.getGeyser().getWorldManager().getBlockAt(session, packet.getBlockPosition());
|
2021-08-02 02:20:15 +00:00
|
|
|
if (session.isSneaking() || blockState != BlockRegistries.JAVA_IDENTIFIERS.get("minecraft:crafting_table")) {
|
|
|
|
// Delay the interaction in case the client doesn't intend to actually use the bucket
|
|
|
|
// See BedrockActionTranslator.java
|
2021-08-18 00:57:46 +00:00
|
|
|
session.setBucketScheduledFuture(session.scheduleInEventLoop(() -> {
|
2021-11-13 03:44:15 +00:00
|
|
|
ServerboundUseItemPacket itemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND);
|
2021-08-02 02:20:15 +00:00
|
|
|
session.sendDownstreamPacket(itemPacket);
|
|
|
|
}, 5, TimeUnit.MILLISECONDS));
|
|
|
|
}
|
|
|
|
}
|
2021-06-20 00:33:07 +00:00
|
|
|
}
|
2020-07-21 17:17:55 +00:00
|
|
|
}
|
2020-05-23 21:39:17 +00:00
|
|
|
|
2020-09-15 00:54:19 +00:00
|
|
|
if (packet.getActions().isEmpty()) {
|
|
|
|
if (session.getOpPermissionLevel() >= 2 && session.getGameMode() == GameMode.CREATIVE) {
|
|
|
|
// Otherwise insufficient permissions
|
2021-09-04 17:36:09 +00:00
|
|
|
if (session.getBlockMappings().getJigsawStateIds().contains(packet.getBlockRuntimeId())) {
|
2020-09-15 00:54:19 +00:00
|
|
|
ContainerOpenPacket openPacket = new ContainerOpenPacket();
|
|
|
|
openPacket.setBlockPosition(packet.getBlockPosition());
|
|
|
|
openPacket.setId((byte) 1);
|
|
|
|
openPacket.setType(ContainerType.JIGSAW_EDITOR);
|
|
|
|
openPacket.setUniqueEntityId(-1);
|
|
|
|
session.sendUpstreamPacket(openPacket);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-13 01:19:40 +00:00
|
|
|
ItemMapping handItem = mappings.getMapping(packet.getItemInHand());
|
2020-04-23 06:01:33 +00:00
|
|
|
if (handItem.isBlock()) {
|
2020-04-29 20:01:53 +00:00
|
|
|
session.setLastBlockPlacePosition(blockPos);
|
2020-04-23 06:01:33 +00:00
|
|
|
session.setLastBlockPlacedId(handItem.getJavaIdentifier());
|
|
|
|
}
|
2020-04-30 05:21:02 +00:00
|
|
|
session.setInteracting(true);
|
2021-09-10 18:10:56 +00:00
|
|
|
}
|
|
|
|
case 1 -> {
|
2021-05-09 05:25:57 +00:00
|
|
|
// Handled when sneaking
|
2021-07-13 01:19:40 +00:00
|
|
|
if (session.getPlayerInventory().getItemInHand().getJavaId() == mappings.getStoredItems().shield().getJavaId()) {
|
2020-04-25 03:11:28 +00:00
|
|
|
break;
|
2020-07-21 17:17:55 +00:00
|
|
|
}
|
|
|
|
|
2021-07-13 01:19:40 +00:00
|
|
|
// Handled in ITEM_USE if the item is not milk
|
2021-06-20 00:33:07 +00:00
|
|
|
if (packet.getItemInHand() != null) {
|
2021-07-13 01:19:40 +00:00
|
|
|
if (session.getItemMappings().getBucketIds().contains(packet.getItemInHand().getId()) &&
|
|
|
|
packet.getItemInHand().getId() != session.getItemMappings().getStoredItems().milkBucket().getBedrockId()) {
|
2021-06-20 00:33:07 +00:00
|
|
|
// Handled in case 0 if the item is not milk
|
|
|
|
break;
|
2021-07-13 01:19:40 +00:00
|
|
|
} else if (session.getItemMappings().getSpawnEggIds().contains(packet.getItemInHand().getId())) {
|
2021-06-20 00:33:07 +00:00
|
|
|
// Handled in case 0
|
|
|
|
break;
|
|
|
|
}
|
2020-07-21 17:17:55 +00:00
|
|
|
}
|
|
|
|
|
2021-11-13 03:44:15 +00:00
|
|
|
ServerboundUseItemPacket useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND);
|
2020-05-05 15:51:43 +00:00
|
|
|
session.sendDownstreamPacket(useItemPacket);
|
2022-01-20 23:09:35 +00:00
|
|
|
|
|
|
|
List<LegacySetItemSlotData> legacySlots = packet.getLegacySlots();
|
|
|
|
if (packet.getActions().size() == 1 && legacySlots.size() > 0) {
|
|
|
|
InventoryActionData actionData = packet.getActions().get(0);
|
|
|
|
LegacySetItemSlotData slotData = legacySlots.get(0);
|
|
|
|
if (slotData.getContainerId() == 6 && actionData.getToItem().getId() != 0) {
|
|
|
|
// The player is trying to swap out an armor piece that already has an item in it
|
|
|
|
if (session.getGeyser().getConfig().isAlwaysQuickChangeArmor()) {
|
|
|
|
// Java doesn't know when a player is in its own inventory and not, so we
|
|
|
|
// can abuse this feature to send a swap inventory packet
|
|
|
|
int bedrockHotbarSlot = packet.getHotbarSlot();
|
|
|
|
Click click = InventoryUtils.getClickForHotbarSwap(bedrockHotbarSlot);
|
|
|
|
if (click != null && slotData.getSlots().length != 0) {
|
|
|
|
Inventory playerInventory = session.getPlayerInventory();
|
|
|
|
// Bedrock sends us the index of the slot in the armor container; armor in Java
|
|
|
|
// Edition is offset by 5 in the player inventory
|
|
|
|
int armorSlot = slotData.getSlots()[0] + 5;
|
|
|
|
GeyserItemStack armorSlotItem = playerInventory.getItem(armorSlot);
|
|
|
|
GeyserItemStack hotbarItem = playerInventory.getItem(playerInventory.getOffsetForHotbar(bedrockHotbarSlot));
|
|
|
|
playerInventory.setItem(armorSlot, hotbarItem, session);
|
|
|
|
playerInventory.setItem(bedrockHotbarSlot, armorSlotItem, session);
|
|
|
|
|
2022-01-30 16:15:07 +00:00
|
|
|
Int2ObjectMap<ItemStack> changedSlots = new Int2ObjectOpenHashMap<>(2);
|
|
|
|
changedSlots.put(armorSlot, hotbarItem.getItemStack());
|
|
|
|
changedSlots.put(bedrockHotbarSlot, armorSlotItem.getItemStack());
|
|
|
|
|
2022-01-20 23:09:35 +00:00
|
|
|
ServerboundContainerClickPacket clickPacket = new ServerboundContainerClickPacket(
|
|
|
|
playerInventory.getId(), playerInventory.getStateId(), armorSlot,
|
2022-01-30 16:15:07 +00:00
|
|
|
click.actionType, click.action, null, changedSlots);
|
2022-01-20 23:09:35 +00:00
|
|
|
session.sendDownstreamPacket(clickPacket);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Disallowed; let's revert
|
|
|
|
session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-10 18:10:56 +00:00
|
|
|
}
|
|
|
|
case 2 -> {
|
2021-01-08 03:43:36 +00:00
|
|
|
int blockState = session.getGameMode() == GameMode.CREATIVE ?
|
2021-11-20 21:34:30 +00:00
|
|
|
session.getGeyser().getWorldManager().getBlockAt(session, packet.getBlockPosition()) : session.getBreakingBlock();
|
2020-04-29 20:01:53 +00:00
|
|
|
|
2021-01-08 03:43:36 +00:00
|
|
|
session.setLastBlockPlacedId(null);
|
|
|
|
session.setLastBlockPlacePosition(null);
|
|
|
|
|
|
|
|
// Same deal with vanilla block placing as above.
|
2021-09-10 01:27:38 +00:00
|
|
|
if (!session.getWorldBorder().isInsideBorderBoundaries()) {
|
|
|
|
restoreCorrectBlock(session, packet.getBlockPosition(), packet);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-01-08 03:43:36 +00:00
|
|
|
// This is working out the distance using 3d Pythagoras and the extra value added to the Y is the sneaking height of a java player.
|
2021-09-10 18:10:56 +00:00
|
|
|
Vector3f playerPosition = session.getPlayerEntity().getPosition();
|
2021-01-08 03:43:36 +00:00
|
|
|
Vector3f floatBlockPosition = packet.getBlockPosition().toFloat();
|
2021-09-10 18:10:56 +00:00
|
|
|
float diffX = playerPosition.getX() - (floatBlockPosition.getX() + 0.5f);
|
2021-11-18 03:02:38 +00:00
|
|
|
float diffY = (playerPosition.getY() - EntityDefinitions.PLAYER.offset()) - (floatBlockPosition.getY() + 0.5f) + 1.5f;
|
2021-09-10 18:10:56 +00:00
|
|
|
float diffZ = playerPosition.getZ() - (floatBlockPosition.getZ() + 0.5f);
|
2021-01-08 03:43:36 +00:00
|
|
|
float distanceSquared = diffX * diffX + diffY * diffY + diffZ * diffZ;
|
|
|
|
if (distanceSquared > MAXIMUM_BLOCK_DESTROYING_DISTANCE) {
|
|
|
|
restoreCorrectBlock(session, packet.getBlockPosition(), packet);
|
|
|
|
return;
|
2020-04-29 20:01:53 +00:00
|
|
|
}
|
2020-05-02 20:44:05 +00:00
|
|
|
|
2021-01-08 03:43:36 +00:00
|
|
|
LevelEventPacket blockBreakPacket = new LevelEventPacket();
|
|
|
|
blockBreakPacket.setType(LevelEventType.PARTICLE_DESTROY_BLOCK);
|
|
|
|
blockBreakPacket.setPosition(packet.getBlockPosition().toFloat());
|
2021-07-13 01:19:40 +00:00
|
|
|
blockBreakPacket.setData(session.getBlockMappings().getBedrockBlockId(blockState));
|
2021-01-08 03:43:36 +00:00
|
|
|
session.sendUpstreamPacket(blockBreakPacket);
|
2021-07-13 01:19:40 +00:00
|
|
|
session.setBreakingBlock(BlockStateValues.JAVA_AIR_ID);
|
2021-01-08 03:43:36 +00:00
|
|
|
|
2021-05-09 19:44:41 +00:00
|
|
|
Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition());
|
|
|
|
if (itemFrameEntity != null) {
|
2021-12-21 00:25:11 +00:00
|
|
|
ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(itemFrameEntity.getEntityId(),
|
2021-05-09 19:44:41 +00:00
|
|
|
InteractAction.ATTACK, session.isSneaking());
|
2020-05-05 15:51:43 +00:00
|
|
|
session.sendDownstreamPacket(attackPacket);
|
2020-05-02 20:44:05 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-04-10 18:46:29 +00:00
|
|
|
PlayerAction action = session.getGameMode() == GameMode.CREATIVE ? PlayerAction.START_DIGGING : PlayerAction.FINISH_DIGGING;
|
|
|
|
Position pos = new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ());
|
2021-11-18 03:02:38 +00:00
|
|
|
ServerboundPlayerActionPacket breakPacket = new ServerboundPlayerActionPacket(action, pos, Direction.VALUES[packet.getBlockFace()]);
|
2020-05-05 15:51:43 +00:00
|
|
|
session.sendDownstreamPacket(breakPacket);
|
2021-09-10 18:10:56 +00:00
|
|
|
}
|
2019-09-29 23:25:42 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ITEM_RELEASE:
|
2019-09-29 23:39:03 +00:00
|
|
|
if (packet.getActionType() == 0) {
|
2020-04-09 02:17:29 +00:00
|
|
|
// Followed to the Minecraft Protocol specification outlined at wiki.vg
|
2021-11-13 03:44:15 +00:00
|
|
|
ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, BlockUtils.POSITION_ZERO,
|
2021-11-18 03:02:38 +00:00
|
|
|
Direction.DOWN);
|
2020-05-05 15:51:43 +00:00
|
|
|
session.sendDownstreamPacket(releaseItemPacket);
|
2019-09-29 23:39:03 +00:00
|
|
|
}
|
2019-09-29 23:25:42 +00:00
|
|
|
break;
|
2019-10-07 18:30:08 +00:00
|
|
|
case ITEM_USE_ON_ENTITY:
|
2019-10-16 20:32:53 +00:00
|
|
|
Entity entity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId());
|
|
|
|
if (entity == null)
|
|
|
|
return;
|
2019-10-07 18:30:08 +00:00
|
|
|
|
2020-04-18 09:13:00 +00:00
|
|
|
//https://wiki.vg/Protocol#Interact_Entity
|
|
|
|
switch (packet.getActionType()) {
|
|
|
|
case 0: //Interact
|
2022-02-25 03:49:10 +00:00
|
|
|
processEntityInteraction(session, packet, entity);
|
2020-04-18 09:13:00 +00:00
|
|
|
break;
|
|
|
|
case 1: //Attack
|
2021-11-18 03:02:38 +00:00
|
|
|
if (entity.getDefinition() == EntityDefinitions.ENDER_DRAGON) {
|
2020-10-29 22:35:46 +00:00
|
|
|
// Redirects the attack to its body entity, this only happens when
|
|
|
|
// attacking the underbelly of the ender dragon
|
2021-12-21 00:25:11 +00:00
|
|
|
ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(entity.getEntityId() + 3,
|
2020-10-29 22:35:46 +00:00
|
|
|
InteractAction.ATTACK, session.isSneaking());
|
|
|
|
session.sendDownstreamPacket(attackPacket);
|
|
|
|
} else {
|
2021-12-21 00:25:11 +00:00
|
|
|
ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(entity.getEntityId(),
|
2020-10-29 22:35:46 +00:00
|
|
|
InteractAction.ATTACK, session.isSneaking());
|
|
|
|
session.sendDownstreamPacket(attackPacket);
|
|
|
|
}
|
2020-04-18 09:13:00 +00:00
|
|
|
break;
|
|
|
|
}
|
2019-10-16 20:32:53 +00:00
|
|
|
break;
|
2019-09-29 23:25:42 +00:00
|
|
|
}
|
|
|
|
}
|
2021-01-08 03:43:36 +00:00
|
|
|
|
2022-02-25 03:49:10 +00:00
|
|
|
private void processEntityInteraction(GeyserSession session, InventoryTransactionPacket packet, Entity entity) {
|
|
|
|
Vector3f entityPosition = entity.getPosition();
|
|
|
|
if (!session.getWorldBorder().isInsideBorderBoundaries(entityPosition)) {
|
|
|
|
// No transaction is able to go through (as of Java Edition 1.18.1)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Vector3f clickPosition = packet.getClickPosition().sub(entityPosition);
|
|
|
|
boolean isSpectator = session.getGameMode() == GameMode.SPECTATOR;
|
|
|
|
for (Hand hand : EntityUtils.HANDS) {
|
|
|
|
session.sendDownstreamPacket(new ServerboundInteractPacket(entity.getEntityId(),
|
|
|
|
InteractAction.INTERACT_AT, clickPosition.getX(), clickPosition.getY(), clickPosition.getZ(),
|
|
|
|
hand, session.isSneaking()));
|
|
|
|
|
|
|
|
InteractionResult result;
|
|
|
|
if (isSpectator) {
|
|
|
|
result = InteractionResult.PASS;
|
|
|
|
} else {
|
|
|
|
result = entity.interactAt(hand);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!result.consumesAction()) {
|
|
|
|
session.sendDownstreamPacket(new ServerboundInteractPacket(entity.getEntityId(),
|
|
|
|
InteractAction.INTERACT, hand, session.isSneaking()));
|
|
|
|
if (!isSpectator) {
|
|
|
|
result = entity.interact(hand);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result.consumesAction()) {
|
|
|
|
if (result.shouldSwing() && hand == Hand.OFF_HAND) {
|
|
|
|
// Currently, Bedrock will send us the arm swing packet in most cases. But it won't for offhand.
|
|
|
|
session.sendDownstreamPacket(new ServerboundSwingPacket(hand));
|
|
|
|
// Note here to look into sending the animation packet back to Bedrock
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-08 03:43:36 +00:00
|
|
|
/**
|
|
|
|
* Restore the correct block state from the server without updating the chunk cache.
|
|
|
|
*
|
|
|
|
* @param session the session of the Bedrock client
|
|
|
|
* @param blockPos the block position to restore
|
|
|
|
*/
|
2021-11-22 19:52:26 +00:00
|
|
|
private void restoreCorrectBlock(GeyserSession session, Vector3i blockPos, InventoryTransactionPacket packet) {
|
2021-11-20 21:34:30 +00:00
|
|
|
int javaBlockState = session.getGeyser().getWorldManager().getBlockAt(session, blockPos);
|
2021-01-08 03:43:36 +00:00
|
|
|
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
|
|
|
|
updateBlockPacket.setDataLayer(0);
|
|
|
|
updateBlockPacket.setBlockPosition(blockPos);
|
2021-07-13 01:19:40 +00:00
|
|
|
updateBlockPacket.setRuntimeId(session.getBlockMappings().getBedrockBlockId(javaBlockState));
|
2021-01-08 03:43:36 +00:00
|
|
|
updateBlockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
|
|
|
|
session.sendUpstreamPacket(updateBlockPacket);
|
|
|
|
|
|
|
|
UpdateBlockPacket updateWaterPacket = new UpdateBlockPacket();
|
|
|
|
updateWaterPacket.setDataLayer(1);
|
|
|
|
updateWaterPacket.setBlockPosition(blockPos);
|
2021-07-13 01:19:40 +00:00
|
|
|
updateWaterPacket.setRuntimeId(BlockRegistries.WATERLOGGED.get().contains(javaBlockState) ? session.getBlockMappings().getBedrockWaterId() : session.getBlockMappings().getBedrockAirId());
|
2021-01-08 03:43:36 +00:00
|
|
|
updateWaterPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
|
|
|
|
session.sendUpstreamPacket(updateWaterPacket);
|
|
|
|
|
|
|
|
// Reset the item in hand to prevent "missing" blocks
|
|
|
|
InventorySlotPacket slotPacket = new InventorySlotPacket();
|
|
|
|
slotPacket.setContainerId(ContainerId.INVENTORY);
|
|
|
|
slotPacket.setSlot(packet.getHotbarSlot());
|
|
|
|
slotPacket.setItem(packet.getItemInHand());
|
|
|
|
session.sendUpstreamPacket(slotPacket);
|
|
|
|
}
|
2019-09-29 23:25:42 +00:00
|
|
|
}
|