//  [java]
    public void translate(GeyserSession session, PlayerActionPacket packet) {
        SessionPlayerEntity entity = session.getPlayerEntity();

        // Send book update before any player action
        if (packet.getAction() != PlayerActionType.RESPAWN) {
            session.getBookEditCache().checkForSend();
        }

        Vector3i vector = packet.getBlockPosition();
        Position position = new Position(vector.getX(), vector.getY(), vector.getZ());

        switch (packet.getAction()) {
            case RESPAWN:
                // Respawn process is finished and the server and client are both OK with respawning.
                EntityEventPacket eventPacket = new EntityEventPacket();
                eventPacket.setRuntimeEntityId(entity.getGeyserId());
                eventPacket.setType(EntityEventType.RESPAWN);
                eventPacket.setData(0);
                session.sendUpstreamPacket(eventPacket);
                // Resend attributes or else in rare cases the user can think they're not dead when they are, upon joining the server
                UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
                attributesPacket.setRuntimeEntityId(entity.getGeyserId());
                attributesPacket.setAttributes(new ArrayList<>(entity.getAttributes().values()));
                session.sendUpstreamPacket(attributesPacket);
                break;
            case START_SWIMMING:
                ClientPlayerStatePacket startSwimPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_SPRINTING);
                session.sendDownstreamPacket(startSwimPacket);

                session.setSwimming(true);
                break;
            case STOP_SWIMMING:
                ClientPlayerStatePacket stopSwimPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.STOP_SPRINTING);
                session.sendDownstreamPacket(stopSwimPacket);

                session.setSwimming(false);
                break;
            case START_GLIDE:
                // Otherwise gliding will not work in creative
                ClientPlayerAbilitiesPacket playerAbilitiesPacket = new ClientPlayerAbilitiesPacket(false);
                session.sendDownstreamPacket(playerAbilitiesPacket);
            case STOP_GLIDE:
                ClientPlayerStatePacket glidePacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_ELYTRA_FLYING);
                session.sendDownstreamPacket(glidePacket);
                break;
            case START_SNEAK:
                ClientPlayerStatePacket startSneakPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_SNEAKING);
                session.sendDownstreamPacket(startSneakPacket);

                // Toggle the shield, if relevant
                PlayerInventory playerInv = session.getPlayerInventory();
                ItemMapping shield = session.getItemMappings().getMapping("minecraft:shield");
                if ((playerInv.getItemInHand().getJavaId() == shield.getJavaId()) ||
                        (playerInv.getOffhand().getJavaId() == shield.getJavaId())) {
                    ClientPlayerUseItemPacket useItemPacket;
                    if (playerInv.getItemInHand().getJavaId() == shield.getJavaId()) {
                        useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
                    } else {
                        // Else we just assume it's the offhand, to simplify logic and to assure the packet gets sent
                        useItemPacket = new ClientPlayerUseItemPacket(Hand.OFF_HAND);
                    }
                    session.sendDownstreamPacket(useItemPacket);
                    session.getPlayerEntity().getMetadata().getFlags().setFlag(EntityFlag.BLOCKING, true);
                    // metadata will be updated when sneaking
                }

                session.setSneaking(true);
                break;
            case STOP_SNEAK:
                ClientPlayerStatePacket stopSneakPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.STOP_SNEAKING);
                session.sendDownstreamPacket(stopSneakPacket);

                // Stop shield, if necessary
                if (session.getPlayerEntity().getMetadata().getFlags().getFlag(EntityFlag.BLOCKING)) {
                    ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, BlockUtils.POSITION_ZERO, BlockFace.DOWN);
                    session.sendDownstreamPacket(releaseItemPacket);
                    session.getPlayerEntity().getMetadata().getFlags().setFlag(EntityFlag.BLOCKING, false);
                    // metadata will be updated when sneaking
                }

                session.setSneaking(false);
                break;
            case START_SPRINT:
                ClientPlayerStatePacket startSprintPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_SPRINTING);
                session.sendDownstreamPacket(startSprintPacket);
                session.setSprinting(true);
                break;
            case STOP_SPRINT:
                ClientPlayerStatePacket stopSprintPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.STOP_SPRINTING);
                session.sendDownstreamPacket(stopSprintPacket);
                session.setSprinting(false);
                break;
            case DROP_ITEM:
                ClientPlayerActionPacket dropItemPacket = new ClientPlayerActionPacket(PlayerAction.DROP_ITEM, position, BlockFace.values()[packet.getFace()]);
                session.sendDownstreamPacket(dropItemPacket);
                break;
            case STOP_SLEEP:
                ClientPlayerStatePacket stopSleepingPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.LEAVE_BED);
                session.sendDownstreamPacket(stopSleepingPacket);
                break;
            case BLOCK_INTERACT:
                // Client means to interact with a block; cancel bucket interaction, if any
                if (session.getBucketScheduledFuture() != null) {
                    session.getBucketScheduledFuture().cancel(true);
                    session.setBucketScheduledFuture(null);
                }
                // Otherwise handled in BedrockInventoryTransactionTranslator
                break;
            case START_BREAK:
                // Start the block breaking animation
                if (session.getGameMode() != GameMode.CREATIVE) {
                    int blockState = session.getConnector().getWorldManager().getBlockAt(session, vector);
                    LevelEventPacket startBreak = new LevelEventPacket();
                    startBreak.setType(LevelEventType.BLOCK_START_BREAK);
                    startBreak.setPosition(vector.toFloat());
                    double breakTime = BlockUtils.getSessionBreakTime(session, BlockRegistries.JAVA_BLOCKS.get(blockState)) * 20;
                    startBreak.setData((int) (65535 / breakTime));
                    session.setBreakingBlock(blockState);
                    session.sendUpstreamPacket(startBreak);
                }

                // Account for fire - the client likes to hit the block behind.
                Vector3i fireBlockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getFace());
                int blockUp = session.getConnector().getWorldManager().getBlockAt(session, fireBlockPos);
                String identifier = BlockRegistries.JAVA_IDENTIFIERS.get().get(blockUp);
                if (identifier.startsWith("minecraft:fire") || identifier.startsWith("minecraft:soul_fire")) {
                    ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(fireBlockPos.getX(),
                            fireBlockPos.getY(), fireBlockPos.getZ()), BlockFace.values()[packet.getFace()]);
                    session.sendDownstreamPacket(startBreakingPacket);
                    if (session.getGameMode() == GameMode.CREATIVE) {
                        break;
                    }
                }

                ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, position, BlockFace.values()[packet.getFace()]);
                session.sendDownstreamPacket(startBreakingPacket);
                break;
            case CONTINUE_BREAK:
                if (session.getGameMode() == GameMode.CREATIVE) {
                    break;
                }
                Vector3f vectorFloat = vector.toFloat();
                LevelEventPacket continueBreakPacket = new LevelEventPacket();
                continueBreakPacket.setType(LevelEventType.PARTICLE_CRACK_BLOCK);
                continueBreakPacket.setData((session.getBlockMappings().getBedrockBlockId(session.getBreakingBlock())) | (packet.getFace() << 24));
                continueBreakPacket.setPosition(vectorFloat);
                session.sendUpstreamPacket(continueBreakPacket);

                // Update the break time in the event that player conditions changed (jumping, effects applied)
                LevelEventPacket updateBreak = new LevelEventPacket();
                updateBreak.setType(LevelEventType.BLOCK_UPDATE_BREAK);
                updateBreak.setPosition(vectorFloat);
                double breakTime = BlockUtils.getSessionBreakTime(session, BlockRegistries.JAVA_BLOCKS.get(session.getBreakingBlock())) * 20;
                updateBreak.setData((int) (65535 / breakTime));
                session.sendUpstreamPacket(updateBreak);
                break;
            case ABORT_BREAK:
                if (session.getGameMode() != GameMode.CREATIVE) {
                    // As of 1.16.210: item frame items are taken out here.
                    // Survival also sends START_BREAK, but by attaching our process here adventure mode also works
                    Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition());
                    if (itemFrameEntity != null) {
                        ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) itemFrameEntity.getEntityId(),
                                InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking());
                        session.sendDownstreamPacket(interactPacket);
                        break;
                    }
                }

                ClientPlayerActionPacket abortBreakingPacket = new ClientPlayerActionPacket(PlayerAction.CANCEL_DIGGING, position, BlockFace.DOWN);
                session.sendDownstreamPacket(abortBreakingPacket);
                LevelEventPacket stopBreak = new LevelEventPacket();
                stopBreak.setType(LevelEventType.BLOCK_STOP_BREAK);
                stopBreak.setPosition(vector.toFloat());
                stopBreak.setData(0);
                session.setBreakingBlock(BlockStateValues.JAVA_AIR_ID);
                session.sendUpstreamPacket(stopBreak);
                break;
            case STOP_BREAK:
                // Handled in BedrockInventoryTransactionTranslator
                break;
            case DIMENSION_CHANGE_SUCCESS:
                //sometimes the client doesn't feel like loading
                PlayStatusPacket spawnPacket = new PlayStatusPacket();
                spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
                session.sendUpstreamPacket(spawnPacket);

                attributesPacket = new UpdateAttributesPacket();
                attributesPacket.setRuntimeEntityId(entity.getGeyserId());
                attributesPacket.setAttributes(new ArrayList<>(entity.getAttributes().values()));
                session.sendUpstreamPacket(attributesPacket);

                session.getEntityCache().updateBossBars();
                break;
            case JUMP:
                entity.setOnGround(false); // Increase block break time while jumping
                break;
        }
    }