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 ;
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 ;
2023-04-07 01:47:37 +00:00
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.* ;
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-10-30 00:23:21 +00:00
import org.cloudburstmc.math.vector.Vector3d ;
import org.cloudburstmc.math.vector.Vector3f ;
import org.cloudburstmc.math.vector.Vector3i ;
import org.cloudburstmc.protocol.bedrock.data.LevelEvent ;
2023-08-21 23:04:08 +00:00
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition ;
2023-05-23 20:34:50 +00:00
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition ;
2022-10-30 00:23:21 +00:00
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType ;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData ;
2022-12-21 00:47:45 +00:00
import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.InventoryActionData ;
import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.InventorySource ;
2023-06-19 00:02:27 +00:00
import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.InventoryTransactionType ;
2023-05-16 23:38:49 +00:00
import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.LegacySetItemSlotData ;
2022-10-30 00:23:21 +00:00
import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket ;
import org.cloudburstmc.protocol.bedrock.packet.InventoryTransactionPacket ;
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket ;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket ;
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 ;
2022-06-24 20:48:28 +00:00
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity ;
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 ;
2022-12-29 20:10:40 +00:00
import org.geysermc.geyser.item.Items ;
2023-04-08 16:45:13 +00:00
import org.geysermc.geyser.item.type.BlockItem ;
import org.geysermc.geyser.item.type.BoatItem ;
import org.geysermc.geyser.item.type.Item ;
2022-12-29 20:10:40 +00:00
import org.geysermc.geyser.item.type.SpawnEggItem ;
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 ;
2022-01-20 23:09:35 +00:00
import org.geysermc.geyser.session.GeyserSession ;
2023-08-21 23:04:08 +00:00
import org.geysermc.geyser.session.cache.SkullCache ;
2023-05-16 23:38:49 +00:00
import org.geysermc.geyser.skin.FakeHeadProvider ;
2022-06-24 20:48:28 +00:00
import org.geysermc.geyser.translator.inventory.InventoryTranslator ;
import org.geysermc.geyser.translator.inventory.item.ItemTranslator ;
2022-01-20 23:09:35 +00:00
import org.geysermc.geyser.translator.protocol.PacketTranslator ;
import org.geysermc.geyser.translator.protocol.Translator ;
2022-11-29 02:53:17 +00:00
import org.geysermc.geyser.util.BlockUtils ;
import org.geysermc.geyser.util.CooldownUtils ;
import org.geysermc.geyser.util.EntityUtils ;
import org.geysermc.geyser.util.InteractionResult ;
2023-05-16 23:38:49 +00:00
import org.geysermc.geyser.util.InventoryUtils ;
2020-04-23 06:01:33 +00:00
2023-05-16 23:38:49 +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 ) {
2023-06-19 00:02:27 +00:00
if ( packet . getTransactionType ( ) = = InventoryTransactionType . NORMAL & & packet . getActions ( ) . size ( ) = = 3 ) {
InventoryActionData containerAction = packet . getActions ( ) . get ( 0 ) ;
if ( containerAction . getSource ( ) . getType ( ) = = InventorySource . Type . CONTAINER & &
session . getPlayerInventory ( ) . getHeldItemSlot ( ) = = containerAction . getSlot ( ) & &
containerAction . getFromItem ( ) . getDefinition ( ) = = session . getItemMappings ( ) . getStoredItems ( ) . writableBook ( ) . getBedrockDefinition ( ) ) {
// Ignore InventoryTransactions related to editing books as that is handled in BedrockBookEditTranslator
return ;
}
}
2021-01-08 00:40:34 +00:00
// Send book updates before opening inventories
session . getBookEditCache ( ) . checkForSend ( ) ;
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 (
2022-08-29 16:26:30 +00:00
inventory . getJavaId ( ) , inventory . getStateId ( ) , hotbarSlot , clickType . actionType , clickType . action ,
2022-02-25 15:31:00 +00:00
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 ,
2022-05-25 23:17:49 +00:00
Vector3i . ZERO ,
2022-05-25 19:55:15 +00:00
Direction . DOWN ,
2022-09-27 23:24:50 +00:00
0
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-04-05 01:03:43 +00:00
final Vector3i packetBlockPosition = packet . getBlockPosition ( ) ;
Vector3i blockPos = BlockUtils . getBlockPosition ( packetBlockPosition , packet . getBlockFace ( ) ) ;
2022-01-15 21:28:52 +00:00
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 ;
}
}
2023-08-21 23:04:08 +00:00
// Check if this is a double placement due to an extended collision block
if ( ! session . getBlockMappings ( ) . getExtendedCollisionBoxes ( ) . isEmpty ( ) ) {
Vector3i belowBlockPos = null ;
switch ( packet . getBlockFace ( ) ) {
case 1 - > belowBlockPos = blockPos . add ( 0 , - 2 , 0 ) ;
case 2 - > belowBlockPos = blockPos . add ( 0 , - 1 , 1 ) ;
case 3 - > belowBlockPos = blockPos . add ( 0 , - 1 , - 1 ) ;
case 4 - > belowBlockPos = blockPos . add ( 1 , - 1 , 0 ) ;
case 5 - > belowBlockPos = blockPos . add ( - 1 , - 1 , 0 ) ;
}
if ( belowBlockPos ! = null ) {
int belowBlock = session . getGeyser ( ) . getWorldManager ( ) . getBlockAt ( session , belowBlockPos ) ;
BlockDefinition extendedCollisionDefinition = session . getBlockMappings ( ) . getExtendedCollisionBoxes ( ) . get ( belowBlock ) ;
if ( extendedCollisionDefinition ! = null & & ( System . currentTimeMillis ( ) - session . getLastInteractionTime ( ) ) < 200 ) {
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 & &
2022-04-05 01:03:43 +00:00
packetBlockPosition . distanceSquared ( session . getLastInteractionBlockPosition ( ) ) < 0 . 00001 ;
session . setLastInteractionBlockPosition ( packetBlockPosition ) ;
2021-02-12 19:39:41 +00:00
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
2022-06-24 20:48:28 +00:00
if ( isIncorrectHeldItem ( session , packet ) ) {
restoreCorrectBlock ( session , blockPos , packet ) ;
return ;
}
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
2022-10-30 16:34:08 +00:00
if ( session . getBlockMappings ( ) . isItemFrame ( packet . getBlockDefinition ( ) ) ) {
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 ( ) ;
2022-07-03 01:17:14 +00:00
playerPosition = playerPosition . down ( EntityDefinitions . PLAYER . offset ( ) - session . getEyeHeight ( ) ) ;
2021-01-08 03:43:36 +00:00
2022-04-05 01:03:43 +00:00
boolean creative = session . getGameMode ( ) = = GameMode . CREATIVE ;
float diffX = playerPosition . getX ( ) - packetBlockPosition . getX ( ) ;
float diffY = playerPosition . getY ( ) - packetBlockPosition . getY ( ) ;
float diffZ = playerPosition . getZ ( ) - packetBlockPosition . getZ ( ) ;
2021-01-08 03:43:36 +00:00
if ( ( ( diffX * diffX ) + ( diffY * diffY ) + ( diffZ * diffZ ) ) >
2022-04-05 01:03:43 +00:00
( creative ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE ) ) {
restoreCorrectBlock ( session , blockPos , packet ) ;
return ;
}
double clickPositionFullX = ( double ) packetBlockPosition . getX ( ) + ( double ) packet . getClickPosition ( ) . getX ( ) ;
double clickPositionFullY = ( double ) packetBlockPosition . getY ( ) + ( double ) packet . getClickPosition ( ) . getY ( ) ;
double clickPositionFullZ = ( double ) packetBlockPosition . getZ ( ) + ( double ) packet . getClickPosition ( ) . getZ ( ) ;
// More recent Paper check - https://github.com/PaperMC/Paper/blob/87e11bf7fdf48ecdf3e1cae383c368b9b61d7df9/patches/server/0470-Move-range-check-for-block-placing-up.patch
double clickDiffX = playerPosition . getX ( ) - clickPositionFullX ;
double clickDiffY = playerPosition . getY ( ) - clickPositionFullY ;
double clickDiffZ = playerPosition . getZ ( ) - clickPositionFullZ ;
if ( ( ( clickDiffX * clickDiffX ) + ( clickDiffY * clickDiffY ) + ( clickDiffZ * clickDiffZ ) ) >
( creative ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE ) ) {
2021-01-08 03:43:36 +00:00
restoreCorrectBlock ( session , blockPos , packet ) ;
return ;
}
2022-04-05 01:03:43 +00:00
Vector3f blockCenter = Vector3f . from ( packetBlockPosition . getX ( ) + 0 . 5f , packetBlockPosition . getY ( ) + 0 . 5f , packetBlockPosition . getZ ( ) + 0 . 5f ) ;
2021-01-08 03:43:36 +00:00
// Vanilla check
2021-11-18 03:02:38 +00:00
if ( ! ( session . getPlayerEntity ( ) . getPosition ( ) . sub ( 0 , EntityDefinitions . PLAYER . offset ( ) , 0 )
2022-04-05 01:03:43 +00:00
. distanceSquared ( blockCenter ) < MAXIMUM_BLOCK_PLACING_DISTANCE ) ) {
2021-01-08 03:43:36 +00:00
// The client thinks that its blocks have been successfully placed. Restore the server's blocks instead.
restoreCorrectBlock ( session , blockPos , packet ) ;
return ;
}
2022-04-05 01:03:43 +00:00
// More recent vanilla check (as of 1.18.2)
double clickDistanceX = clickPositionFullX - blockCenter . getX ( ) ;
double clickDistanceY = clickPositionFullY - blockCenter . getY ( ) ;
double clickDistanceZ = clickPositionFullZ - blockCenter . getZ ( ) ;
if ( ! ( Math . abs ( clickDistanceX ) < 1 . 0000001D & & Math . abs ( clickDistanceY ) < 1 . 0000001D & & Math . abs ( clickDistanceZ ) < 1 . 0000001D ) ) {
restoreCorrectBlock ( session , blockPos , packet ) ;
return ;
}
2023-08-21 23:04:08 +00:00
2021-01-08 03:43:36 +00:00
/ *
Block place checks end - client is good to go
* /
2022-12-29 20:10:40 +00:00
if ( packet . getItemInHand ( ) ! = null & & session . getItemMappings ( ) . getMapping ( packet . getItemInHand ( ) ) . getJavaItem ( ) instanceof SpawnEggItem ) {
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
2022-06-24 20:48:28 +00:00
useItem ( session , packet , blockState ) ;
2021-06-20 00:33:07 +00:00
break ;
}
}
2021-11-13 03:44:15 +00:00
ServerboundUseItemOnPacket blockPacket = new ServerboundUseItemOnPacket (
2022-05-25 23:17:49 +00:00
packet . getBlockPosition ( ) ,
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 ( ) ,
2022-05-25 19:55:15 +00:00
false ,
2022-07-10 03:02:19 +00:00
session . getWorldCache ( ) . nextPredictionSequence ( ) ) ;
2020-05-05 15:51:43 +00:00
session . sendDownstreamPacket ( blockPacket ) ;
2020-05-23 21:39:17 +00:00
2023-04-08 16:45:13 +00:00
Item item = session . getPlayerInventory ( ) . getItemInHand ( ) . asItem ( ) ;
2021-06-20 00:33:07 +00:00
if ( packet . getItemInHand ( ) ! = null ) {
2022-10-30 00:23:21 +00:00
ItemDefinition definition = packet . getItemInHand ( ) . getDefinition ( ) ;
2022-06-24 20:48:28 +00:00
int blockState = session . getGeyser ( ) . getWorldManager ( ) . getBlockAt ( session , packet . getBlockPosition ( ) ) ;
// Otherwise boats will not be able to be placed in survival and buckets, lily pads, frogspawn, and glass bottles won't work on mobile
2023-04-08 16:45:13 +00:00
if ( item instanceof BoatItem | | item = = Items . LILY_PAD | | item = = Items . FROGSPAWN ) {
2022-06-24 20:48:28 +00:00
useItem ( session , packet , blockState ) ;
2023-04-08 16:45:13 +00:00
} else if ( item = = Items . GLASS_BOTTLE ) {
2022-06-24 20:48:28 +00:00
if ( ! session . isSneaking ( ) & & BlockStateValues . isCauldron ( blockState ) & & ! BlockStateValues . isNonWaterCauldron ( blockState ) ) {
// ServerboundUseItemPacket is not sent for water cauldrons and glass bottles
return ;
}
useItem ( session , packet , blockState ) ;
2022-10-30 00:23:21 +00:00
} else if ( session . getItemMappings ( ) . getBuckets ( ) . contains ( definition ) ) {
2021-11-13 03:44:15 +00:00
// Don't send ServerboundUseItemPacket for powder snow buckets
2022-10-30 00:23:21 +00:00
if ( definition ! = session . getItemMappings ( ) . getStoredItems ( ) . powderSnowBucket ( ) . getBedrockDefinition ( ) ) {
2022-06-24 20:48:28 +00:00
if ( ! session . isSneaking ( ) & & BlockStateValues . isCauldron ( blockState ) ) {
// ServerboundUseItemPacket is not sent for cauldrons and buckets
return ;
2021-08-02 02:20:15 +00:00
}
2022-06-24 20:48:28 +00:00
session . setPlacedBucket ( useItem ( session , packet , blockState ) ) ;
} else {
session . setPlacedBucket ( true ) ;
2021-08-02 02:20:15 +00:00
}
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
2022-10-30 16:34:08 +00:00
if ( session . getBlockMappings ( ) . getJigsawStates ( ) . contains ( packet . getBlockDefinition ( ) ) ) {
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 ) ;
}
}
}
2023-04-08 16:45:13 +00:00
if ( item instanceof BlockItem ) {
2020-04-29 20:01:53 +00:00
session . setLastBlockPlacePosition ( blockPos ) ;
2023-04-08 16:45:13 +00:00
session . setLastBlockPlacedId ( item . javaIdentifier ( ) ) ;
2020-04-23 06:01:33 +00:00
}
2020-04-30 05:21:02 +00:00
session . setInteracting ( true ) ;
2021-09-10 18:10:56 +00:00
}
case 1 - > {
2022-06-24 20:48:28 +00:00
if ( isIncorrectHeldItem ( session , packet ) ) {
InventoryTranslator . PLAYER_INVENTORY_TRANSLATOR . updateSlot ( session , session . getPlayerInventory ( ) , session . getPlayerInventory ( ) . getOffsetForHotbar ( packet . getHotbarSlot ( ) ) ) ;
break ;
}
2021-05-09 05:25:57 +00:00
// Handled when sneaking
2022-12-29 20:10:40 +00:00
if ( session . getPlayerInventory ( ) . getItemInHand ( ) . asItem ( ) = = Items . SHIELD ) {
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 ) {
2022-10-30 00:23:21 +00:00
if ( session . getItemMappings ( ) . getBuckets ( ) . contains ( packet . getItemInHand ( ) . getDefinition ( ) ) & &
packet . getItemInHand ( ) . getDefinition ( ) ! = session . getItemMappings ( ) . getStoredItems ( ) . milkBucket ( ) . getBedrockDefinition ( ) ) {
2021-06-20 00:33:07 +00:00
// Handled in case 0 if the item is not milk
break ;
2022-12-29 20:10:40 +00:00
} else if ( session . getItemMappings ( ) . getMapping ( packet . getItemInHand ( ) ) . getJavaItem ( ) instanceof SpawnEggItem ) {
2021-06-20 00:33:07 +00:00
// Handled in case 0
break ;
2022-10-30 03:02:11 +00:00
} else if ( packet . getItemInHand ( ) . getDefinition ( ) = = session . getItemMappings ( ) . getStoredItems ( ) . glassBottle ( ) . getBedrockDefinition ( ) ) {
2022-06-24 20:48:28 +00:00
// Handled in case 0
break ;
2021-06-20 00:33:07 +00:00
}
2020-07-21 17:17:55 +00:00
}
2022-07-10 03:02:19 +00:00
ServerboundUseItemPacket useItemPacket = new ServerboundUseItemPacket ( Hand . MAIN_HAND , session . getWorldCache ( ) . nextPredictionSequence ( ) ) ;
2020-05-05 15:51:43 +00:00
session . sendDownstreamPacket ( useItemPacket ) ;
2023-05-16 23:38:49 +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 . getFromItem ( ) . isNull ( ) ) {
// The player is trying to swap out an armor piece that already has an item in it
// 1.19.4 brings this natively, but we need this specific case for custom head rendering to work
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 ;
if ( armorSlot = = 5 ) {
GeyserItemStack armorSlotItem = playerInventory . getItem ( armorSlot ) ;
if ( armorSlotItem . asItem ( ) = = Items . PLAYER_HEAD ) {
FakeHeadProvider . restoreOriginalSkin ( session , session . getPlayerEntity ( ) ) ;
}
}
}
}
}
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
2022-07-10 03:02:19 +00:00
int sequence = session . getWorldCache ( ) . nextPredictionSequence ( ) ;
2022-09-27 23:24:50 +00:00
session . getWorldCache ( ) . markPositionInSequence ( packet . getBlockPosition ( ) ) ;
// -1 means we don't know what block they're breaking
if ( blockState = = - 1 ) {
2022-07-10 03:02:19 +00:00
blockState = BlockStateValues . JAVA_AIR_ID ;
}
2021-01-08 03:43:36 +00:00
LevelEventPacket blockBreakPacket = new LevelEventPacket ( ) ;
2022-10-30 00:23:21 +00:00
blockBreakPacket . setType ( LevelEvent . PARTICLE_DESTROY_BLOCK ) ;
2021-01-08 03:43:36 +00:00
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 ) ;
2022-07-10 03:02:19 +00:00
session . setBreakingBlock ( - 1 ) ;
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 ;
2022-07-10 03:02:19 +00:00
ServerboundPlayerActionPacket breakPacket = new ServerboundPlayerActionPacket ( action , packet . getBlockPosition ( ) , Direction . VALUES [ packet . getBlockFace ( ) ] , sequence ) ;
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
2022-05-25 23:17:49 +00:00
ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket ( PlayerAction . RELEASE_USE_ITEM , Vector3i . ZERO ,
2022-09-27 23:24:50 +00:00
Direction . DOWN , 0 ) ;
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 ( ) ) {
2022-03-15 17:34:56 +00:00
case 0 - > processEntityInteraction ( session , packet , entity ) ; // Interact
case 1 - > { // Attack
int entityId ;
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
2022-03-15 17:34:56 +00:00
entityId = entity . getEntityId ( ) + 3 ;
2020-10-29 22:35:46 +00:00
} else {
2022-03-15 17:34:56 +00:00
entityId = entity . getEntityId ( ) ;
2020-10-29 22:35:46 +00:00
}
2022-03-15 17:34:56 +00:00
ServerboundInteractPacket attackPacket = new ServerboundInteractPacket ( entityId ,
InteractAction . ATTACK , session . isSneaking ( ) ) ;
session . sendDownstreamPacket ( attackPacket ) ;
2022-08-13 02:25:07 +00:00
2022-11-29 02:53:17 +00:00
// Since 1.19.10, LevelSoundEventPackets are no longer sent by the client when attacking entities
CooldownUtils . sendCooldown ( session ) ;
2022-03-15 17:34:56 +00:00
}
2020-04-18 09:13:00 +00:00
}
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 ) ;
2023-08-21 23:04:08 +00:00
BlockDefinition bedrockBlock = session . getBlockMappings ( ) . getBedrockBlock ( javaBlockState ) ;
if ( BlockStateValues . getSkullVariant ( javaBlockState ) = = 3 ) {
// The changed block was a player skull so check if a custom block was defined for this skull
SkullCache . Skull skull = session . getSkullCache ( ) . getSkulls ( ) . get ( blockPos ) ;
if ( skull ! = null & & skull . getBlockDefinition ( ) ! = null ) {
bedrockBlock = skull . getBlockDefinition ( ) ;
}
}
2021-01-08 03:43:36 +00:00
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket ( ) ;
updateBlockPacket . setDataLayer ( 0 ) ;
updateBlockPacket . setBlockPosition ( blockPos ) ;
2023-08-21 23:04:08 +00:00
updateBlockPacket . setDefinition ( bedrockBlock ) ;
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 ) ;
2023-04-07 01:47:37 +00:00
updateWaterPacket . setDefinition ( BlockRegistries . WATERLOGGED . get ( ) . get ( javaBlockState ) ? session . getBlockMappings ( ) . getBedrockWater ( ) : session . getBlockMappings ( ) . getBedrockAir ( ) ) ;
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
2022-06-24 20:48:28 +00:00
InventoryTranslator . PLAYER_INVENTORY_TRANSLATOR . updateSlot ( session , session . getPlayerInventory ( ) , session . getPlayerInventory ( ) . getOffsetForHotbar ( packet . getHotbarSlot ( ) ) ) ;
}
private boolean isIncorrectHeldItem ( GeyserSession session , InventoryTransactionPacket packet ) {
int javaSlot = session . getPlayerInventory ( ) . getOffsetForHotbar ( packet . getHotbarSlot ( ) ) ;
2022-10-30 00:23:21 +00:00
ItemDefinition expectedItem = ItemTranslator . getBedrockItemDefinition ( session , session . getPlayerInventory ( ) . getItem ( javaSlot ) ) ;
ItemDefinition heldItemId = packet . getItemInHand ( ) = = null ? ItemData . AIR . getDefinition ( ) : packet . getItemInHand ( ) . getDefinition ( ) ;
2022-06-24 20:48:28 +00:00
2022-10-30 00:23:21 +00:00
if ( ! expectedItem . equals ( heldItemId ) ) {
session . getGeyser ( ) . getLogger ( ) . debug ( session . bedrockUsername ( ) + " 's held item has desynced! Expected: " + expectedItem + " Received: " + heldItemId ) ;
2022-06-24 20:48:28 +00:00
session . getGeyser ( ) . getLogger ( ) . debug ( " Packet: " + packet ) ;
return true ;
}
2022-10-30 00:23:21 +00:00
2022-06-24 20:48:28 +00:00
return false ;
}
private boolean useItem ( GeyserSession session , InventoryTransactionPacket packet , int blockState ) {
// Update the player's inventory to remove any items added by the client itself
Inventory playerInventory = session . getPlayerInventory ( ) ;
int heldItemSlot = playerInventory . getOffsetForHotbar ( packet . getHotbarSlot ( ) ) ;
InventoryTranslator . PLAYER_INVENTORY_TRANSLATOR . updateSlot ( session , playerInventory , heldItemSlot ) ;
2023-04-08 16:45:13 +00:00
GeyserItemStack itemStack = playerInventory . getItem ( heldItemSlot ) ;
if ( itemStack . getAmount ( ) > 1 ) {
if ( itemStack . asItem ( ) = = Items . BUCKET | | itemStack . asItem ( ) = = Items . GLASS_BOTTLE ) {
2022-06-24 20:48:28 +00:00
// Using a stack of buckets or glass bottles will result in an item being added to the first empty slot.
// We need to revert the item in case the interaction fails. The order goes from left to right in the
// hotbar. Then left to right and top to bottom in the inventory.
for ( int i = 0 ; i < 36 ; i + + ) {
int slot = i ;
if ( i < 9 ) {
slot = playerInventory . getOffsetForHotbar ( slot ) ;
}
if ( playerInventory . getItem ( slot ) . isEmpty ( ) ) {
InventoryTranslator . PLAYER_INVENTORY_TRANSLATOR . updateSlot ( session , playerInventory , slot ) ;
break ;
}
}
}
}
// Check if the player is interacting with a block
if ( ! session . isSneaking ( ) ) {
2023-04-07 01:47:37 +00:00
if ( BlockRegistries . INTERACTIVE . get ( ) . get ( blockState ) ) {
2022-06-24 20:48:28 +00:00
return false ;
}
boolean mayBuild = session . getGameMode ( ) = = GameMode . SURVIVAL | | session . getGameMode ( ) = = GameMode . CREATIVE ;
2023-04-07 01:47:37 +00:00
if ( mayBuild & & BlockRegistries . INTERACTIVE_MAY_BUILD . get ( ) . get ( blockState ) ) {
2022-06-24 20:48:28 +00:00
return false ;
}
}
Vector3f target = packet . getBlockPosition ( ) . toFloat ( ) . add ( packet . getClickPosition ( ) ) ;
lookAt ( session , target ) ;
2022-07-10 03:02:19 +00:00
ServerboundUseItemPacket itemPacket = new ServerboundUseItemPacket ( Hand . MAIN_HAND , session . getWorldCache ( ) . nextPredictionSequence ( ) ) ;
2022-06-24 20:48:28 +00:00
session . sendDownstreamPacket ( itemPacket ) ;
return true ;
}
/ * *
* Determine the rotation necessary to activate this transaction .
*
* The position between the intended click position and the player can be determined with two triangles .
* First , we compute the difference of the X and Z coordinates :
*
* Player position ( 0 , 0 )
* |
* |
* |
* | _____________ Intended target ( - 3 , 2 )
*
* We then use the Pythagorean Theorem to find the direct line ( hypotenuse ) on the XZ plane . Finding the angle of the
* triangle from there , closest to the player , gives us our yaw rotation value
* Then doing the same using the new XZ distance and Y difference , we can find the direct line of sight from the
* player to the intended target , and the pitch rotation value . We can then send the necessary packets to update
* the player ' s rotation .
*
* @param session the Geyser Session
* @param target the position to look at
* /
private void lookAt ( GeyserSession session , Vector3f target ) {
// Use the bounding box's position since we need the player's position seen by the Java server
Vector3d playerPosition = session . getCollisionManager ( ) . getPlayerBoundingBox ( ) . getBottomCenter ( ) ;
float xDiff = ( float ) ( target . getX ( ) - playerPosition . getX ( ) ) ;
2022-07-03 01:17:14 +00:00
float yDiff = ( float ) ( target . getY ( ) - ( playerPosition . getY ( ) + session . getEyeHeight ( ) ) ) ;
2022-06-24 20:48:28 +00:00
float zDiff = ( float ) ( target . getZ ( ) - playerPosition . getZ ( ) ) ;
// First triangle on the XZ plane
float yaw = ( float ) - Math . toDegrees ( Math . atan2 ( xDiff , zDiff ) ) ;
// Second triangle on the Y axis using the hypotenuse of the first triangle as a side
double xzHypot = Math . sqrt ( xDiff * xDiff + zDiff * zDiff ) ;
float pitch = ( float ) - Math . toDegrees ( Math . atan2 ( yDiff , xzHypot ) ) ;
SessionPlayerEntity entity = session . getPlayerEntity ( ) ;
ServerboundMovePlayerPosRotPacket returnPacket = new ServerboundMovePlayerPosRotPacket ( entity . isOnGround ( ) , playerPosition . getX ( ) , playerPosition . getY ( ) , playerPosition . getZ ( ) , entity . getYaw ( ) , entity . getPitch ( ) ) ;
// This matches Java edition behavior
ServerboundMovePlayerPosRotPacket movementPacket = new ServerboundMovePlayerPosRotPacket ( entity . isOnGround ( ) , playerPosition . getX ( ) , playerPosition . getY ( ) , playerPosition . getZ ( ) , yaw , pitch ) ;
session . sendDownstreamPacket ( movementPacket ) ;
if ( session . getLookBackScheduledFuture ( ) ! = null ) {
session . getLookBackScheduledFuture ( ) . cancel ( false ) ;
}
if ( Math . abs ( entity . getYaw ( ) - yaw ) > 1f | | Math . abs ( entity . getPitch ( ) - pitch ) > 1f ) {
session . setLookBackScheduledFuture ( session . scheduleInEventLoop ( ( ) - > {
Vector3d newPlayerPosition = session . getCollisionManager ( ) . getPlayerBoundingBox ( ) . getBottomCenter ( ) ;
if ( ! newPlayerPosition . equals ( playerPosition ) | | entity . getYaw ( ) ! = returnPacket . getYaw ( ) | | entity . getPitch ( ) ! = returnPacket . getPitch ( ) ) {
// The player moved/rotated so there is no need to change their rotation back
return ;
}
session . sendDownstreamPacket ( returnPacket ) ;
} , 150 , TimeUnit . MILLISECONDS ) ) ;
}
}
2023-05-16 23:38:49 +00:00
}