From a98bd6e05ad26910e3ebdb41d6b41bb0bfadd2fb Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sat, 3 May 2025 19:00:13 +0200 Subject: [PATCH 01/14] Prevent elytra gliding to match Java client behavior To-Do's: - Firework boosting is still very broken - see how BDS handles this mess Other changes: Don't send player's onGround when riding vehicles Support null values for data components (removal of default components) Fix possible NPE in JavaSetEquipmentTranslator with debug mode enabled --- .../type/player/SessionPlayerEntity.java | 69 +++++++++++++++++++ .../geyser/inventory/GeyserItemStack.java | 35 +++++++--- .../geyser/inventory/PlayerInventory.java | 15 ++++ .../inventory/item/StoredItemMappings.java | 2 + .../level/physics/CollisionManager.java | 5 ++ .../geyser/session/GeyserSession.java | 2 +- ...BedrockInventoryTransactionTranslator.java | 2 + .../player/input/BedrockMovePlayer.java | 6 +- .../BedrockPlayerAuthInputTranslator.java | 34 ++++++--- .../entity/JavaSetEquipmentTranslator.java | 4 +- gradle/libs.versions.toml | 2 +- 11 files changed, 149 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index e0422036f16..c2695b40ef8 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -31,6 +31,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.math.vector.Vector2f; import org.cloudburstmc.math.vector.Vector3f; +import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.protocol.bedrock.data.AttributeData; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; @@ -38,13 +39,20 @@ import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.level.BedrockDimension; import org.geysermc.geyser.level.block.Blocks; +import org.geysermc.geyser.level.block.property.Properties; +import org.geysermc.geyser.level.block.type.BlockState; +import org.geysermc.geyser.level.block.type.TrapDoorBlock; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.tags.BlockTag; import org.geysermc.geyser.util.AttributeUtils; import org.geysermc.geyser.util.DimensionUtils; import org.geysermc.geyser.util.MathUtils; +import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect; +import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot; import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.Attribute; import org.geysermc.mcprotocollib.protocol.data.game.entity.attribute.AttributeType; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.GlobalPos; @@ -52,6 +60,8 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.ByteEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.FloatEntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.Equippable; import java.util.Collections; import java.util.List; @@ -428,4 +438,63 @@ public float getJumpVelocity() { return velocity + 0.1F * session.getEffectCache().getJumpPower(); } + + public boolean isOnClimbableBlock() { + if (session.getGameMode() == GameMode.SPECTATOR) { + return false; + } + Vector3i pos = getPosition().down(EntityDefinitions.PLAYER.offset()).toInt(); + BlockState state = session.getGeyser().getWorldManager().blockAt(session, pos); + if (session.getTagCache().is(BlockTag.CLIMBABLE, state.block())) { + return true; + } + + if (state.block() instanceof TrapDoorBlock) { + if (!state.getValue(Properties.OPEN)) { + return false; + } else { + BlockState belowState = session.getGeyser().getWorldManager().blockAt(session, pos.down()); + return belowState.is(Blocks.LADDER) && belowState.getValue(Properties.HORIZONTAL_FACING) == state.getValue(Properties.HORIZONTAL_FACING); + } + } + return false; + } + + public boolean canStartGliding() { + // You can't start gliding when levitation is applied + if (session.getEffectCache().getEntityEffects().contains(Effect.LEVITATION)) { + return false; + } + + if (this.isOnClimbableBlock()) { + return false; + } + + if (session.getCollisionManager().isPlayerTouchingWater()) { + return false; + } + + // Unfortunately gliding is still client-side, so we cannot force the client to glide even + // if we wanted to. However, we still need to check that gliding is possible even with, say, + // an elytra that does not have the glider component. + for (Map.Entry entry : session.getPlayerInventory().getEquipment().entrySet()) { + if (entry.getValue().getComponent(DataComponentTypes.GLIDER) != null) { + Equippable equippable = entry.getValue().getComponent(DataComponentTypes.EQUIPPABLE); + if (equippable != null && equippable.slot() == entry.getKey() && !entry.getValue().nextDamageWillBreak()) { + return true; + } + } + } + + return false; + } + + public boolean isGliding() { + return getFlag(EntityFlag.GLIDING); + } + + public void stopGliding() { + setFlag(EntityFlag.GLIDING, false); + updateBedrockMetadata(); + } } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java b/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java index a66b07598e6..b55c063ee74 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java @@ -126,14 +126,14 @@ public int getAmount() { } /** - * @return the {@link DataComponents} that aren't the base/default components. + * @return the {@link DataComponents} patch that's sent over the network. */ public @Nullable DataComponents getComponents() { return isEmpty() ? null : components; } /** - * @return whether this GeyserItemStack has any additional components on top of + * @return whether this GeyserItemStack has any component modifications additional to * the base item components. */ public boolean hasNonBaseComponents() { @@ -159,16 +159,13 @@ public DataComponents getOrCreateComponents() { */ @Nullable public T getComponent(@NonNull DataComponentType type) { - if (components == null) { - return asItem().getComponent(type); - } - - T value = components.get(type); - if (value == null) { - return asItem().getComponent(type); + // A data component patch may contain null values to remove base components + // e.g. an elytra without the glider component + if (components != null && components.getDataComponents().containsKey(type)) { + return components.get(type); } - return value; + return asItem().getComponent(type); } public T getComponentElseGet(@NonNull DataComponentType type, Supplier supplier) { @@ -251,6 +248,24 @@ public SlotDisplay asSlotDisplay() { return new ItemStackSlotDisplay(this.getItemStack()); } + public int getMaxDamage() { + return getComponentElseGet(DataComponentTypes.MAX_DAMAGE, () -> 0); + } + + public int getDamage() { + // Damage can't be negative + int damage = Math.max(this.getComponentElseGet(DataComponentTypes.DAMAGE, () -> 0), 0); + return Math.min(damage, this.getMaxDamage()); + } + + public boolean nextDamageWillBreak() { + return this.isDamageable() && this.getDamage() >= this.getMaxDamage() - 1; + } + + public boolean isDamageable() { + return getComponent(DataComponentTypes.MAX_DAMAGE) != null && getComponent(DataComponentTypes.UNBREAKABLE) == null && getComponent(DataComponentTypes.DAMAGE) != null; + } + public Item asItem() { if (isEmpty()) { return Items.AIR; diff --git a/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java b/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java index a3af293d937..825dc27c6b3 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java @@ -31,9 +31,12 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; import org.jetbrains.annotations.Range; +import java.util.Map; + @Getter public class PlayerInventory extends Inventory { /** @@ -83,6 +86,18 @@ public GeyserItemStack getItemInHand() { return items[36 + heldItemSlot]; } + // TODO other equipment slots??? + public Map getEquipment() { + return Map.of( + EquipmentSlot.MAIN_HAND, getItemInHand(), + EquipmentSlot.OFF_HAND, items[45], + EquipmentSlot.BOOTS, items[8], + EquipmentSlot.LEGGINGS, items[7], + EquipmentSlot.CHESTPLATE, items[6], + EquipmentSlot.HELMET, items[5] + ); + } + public boolean eitherHandMatchesItem(@NonNull Item item) { return getItemInHand().asItem() == item || getItemInHand(Hand.OFF_HAND).asItem() == item; } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java b/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java index a8a711cc234..bf2908a4c25 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java @@ -47,6 +47,7 @@ public class StoredItemMappings { private final ItemMapping compass; private final ItemMapping crossbow; private final ItemMapping egg; + private final ItemMapping fireworkRocket; private final ItemMapping glassBottle; private final ItemMapping milkBucket; private final ItemMapping powderSnowBucket; @@ -66,6 +67,7 @@ public StoredItemMappings(Map itemMappings) { this.compass = load(itemMappings, Items.COMPASS); this.crossbow = load(itemMappings, Items.CROSSBOW); this.egg = load(itemMappings, Items.EGG); + this.fireworkRocket = load(itemMappings, Items.FIREWORK_ROCKET); this.glassBottle = load(itemMappings, Items.GLASS_BOTTLE); this.milkBucket = load(itemMappings, Items.MILK_BUCKET); this.powderSnowBucket = load(itemMappings, Items.POWDER_SNOW_BUCKET); diff --git a/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java b/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java index ff65579352f..6665b6eb843 100644 --- a/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java @@ -424,6 +424,11 @@ public boolean isPlayerInWater() { return state.is(Blocks.WATER) && state.getValue(Properties.LEVEL) == 0; } + public boolean isPlayerTouchingWater() { + BlockState state = session.getGeyser().getWorldManager().blockAt(session, session.getPlayerEntity().position().toInt()); + return state.is(Blocks.WATER); + } + public boolean isWaterInEyes() { double eyeX = playerBoundingBox.getMiddleX(); double eyeY = playerBoundingBox.getMiddleY() - playerBoundingBox.getSizeY() / 2d + session.getEyeHeight(); diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 354e640b26c..f469c35a9b0 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1967,7 +1967,7 @@ public float getEyeHeight() { FALL_FLYING, // Elytra SPIN_ATTACK -> 0.4f; // Trident spin attack case SLEEPING -> 0.2f; - default -> EntityDefinitions.PLAYER.offset(); + default -> EntityDefinitions.PLAYER.offset(); // 1.62F }; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 56202bc9490..822a0049f28 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -411,6 +411,8 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet) } } } + } else if (packet.getItemInHand().getDefinition() == session.getItemMappings().getStoredItems().fireworkRocket().getBedrockDefinition()) { + // TODO prevent elytra boosting when not gliding } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java index 0d8386bc143..3a2f36d5f43 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java @@ -33,7 +33,6 @@ import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.level.physics.CollisionResult; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.session.cache.tags.BlockTag; import org.geysermc.geyser.text.ChatColor; import org.geysermc.mcprotocollib.network.packet.Packet; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket; @@ -93,7 +92,7 @@ static void translate(GeyserSession session, PlayerAuthInputPacket packet) { } // Due to how ladder works on Bedrock, we won't get climbing velocity from tick end unless if you're colliding horizontally. So we account for it ourselves. - boolean onClimbableBlock = session.getTagCache().is(BlockTag.CLIMBABLE, session.getGeyser().getWorldManager().blockAt(session, entity.getPosition().sub(0, EntityDefinitions.PLAYER.offset(), 0).toInt()).block()); + boolean onClimbableBlock = entity.isOnClimbableBlock(); if (onClimbableBlock && packet.getInputData().contains(PlayerAuthInputData.JUMPING)) { entity.setLastTickEndVelocity(Vector3f.from(entity.getLastTickEndVelocity().getX(), 0.2F, entity.getLastTickEndVelocity().getZ())); } @@ -102,7 +101,8 @@ static void translate(GeyserSession session, PlayerAuthInputPacket packet) { boolean isOnGround; if (hasVehicle) { // VERTICAL_COLLISION is not accurate while in a vehicle (as of 1.21.62) - isOnGround = Math.abs(entity.getLastTickEndVelocity().getY()) < 0.1; + // If the player is riding a vehicle or is in spectator mode, onGround is always set to false for the player + isOnGround = false; } else { isOnGround = packet.getInputData().contains(PlayerAuthInputData.VERTICAL_COLLISION) && entity.getLastTickEndVelocity().getY() < 0; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java index 8e09c6c98b3..c85c648100b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java @@ -140,12 +140,31 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(false)); } case START_GLIDING -> { - // Otherwise gliding will not work in creative - ServerboundPlayerAbilitiesPacket playerAbilitiesPacket = new ServerboundPlayerAbilitiesPacket(false); - session.sendDownstreamGamePacket(playerAbilitiesPacket); - sendPlayerGlideToggle(session, entity); + // Bedrock can send both start_glide and stop_glide in the same packet. + // last replicated on 1.21.70 by "walking" and jumping while in water + if (!entity.isGliding() && !packet.getInputData().contains(PlayerAuthInputData.STOP_GLIDING)) { + entity.setFlag(EntityFlag.GLIDING, true); + + if (entity.canStartGliding()) { + // On Java you can't start gliding while flying + if (session.isFlying()) { + session.setFlying(false); + session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(false)); + } + entity.updateBedrockMetadata(); + session.sendDownstreamGamePacket(new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_ELYTRA_FLYING)); + } else { + entity.stopGliding(); + // return to flying if we can't start gliding + if (session.isFlying()) { + session.sendAdventureSettings(); + } + } + } + } + case STOP_GLIDING -> { + entity.setFlag(EntityFlag.GLIDING, false); } - case STOP_GLIDING -> sendPlayerGlideToggle(session, entity); case MISSED_SWING -> { session.setLastAirHitTick(session.getTicks()); @@ -178,11 +197,6 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { } } - private static void sendPlayerGlideToggle(GeyserSession session, Entity entity) { - ServerboundPlayerCommandPacket glidePacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_ELYTRA_FLYING); - session.sendDownstreamGamePacket(glidePacket); - } - private static void processItemUseTransaction(GeyserSession session, ItemUseTransaction transaction) { if (transaction.getActionType() == 2) { int blockState = session.getGameMode() == GameMode.CREATIVE ? diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEquipmentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEquipmentTranslator.java index 7e43ef6e199..b9d65200e41 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEquipmentTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetEquipmentTranslator.java @@ -49,8 +49,8 @@ public void translate(GeyserSession session, ClientboundSetEquipmentPacket packe return; if (!(entity instanceof LivingEntity livingEntity)) { - session.getGeyser().getLogger().debug("Attempted to add armor to a non-living entity type (" + - entity.getDefinition().entityType().name() + ")."); + session.getGeyser().getLogger().debug("Attempted to add armor to a non-living entity (" + + entity.getDefinition().identifier() + ")."); return; } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 28759540ac8..0359ad59594 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ protocol-common = "3.0.0.Beta6-20250415.154650-7" protocol-codec = "3.0.0.Beta6-20250415.154650-7" raknet = "1.0.0.CR3-20250218.160705-18" minecraftauth = "4.1.1" -mcprotocollib = "1.21.5-20250410.194555-25" +mcprotocollib = "1.21.5-20250428.154438-26" adventure = "4.14.0" adventure-platform = "4.3.0" junit = "5.9.2" From 2484ec940e5ce10361826c8b533816f196c6a07f Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sun, 4 May 2025 17:41:35 +0200 Subject: [PATCH 02/14] Add bounding box height update when a player starts gliding, send sprinting state at correct time --- .../geysermc/geyser/entity/type/Entity.java | 7 +++ .../geyser/session/GeyserSession.java | 16 ++++++ .../geyser/session/cache/InputCache.java | 14 +++++- .../BedrockPlayerAuthInputTranslator.java | 49 +++++++------------ 4 files changed, 54 insertions(+), 32 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index f8eafb5d751..c8dee0f26a7 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -41,10 +41,12 @@ import org.cloudburstmc.protocol.bedrock.packet.MoveEntityDeltaPacket; import org.cloudburstmc.protocol.bedrock.packet.RemoveEntityPacket; import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.entity.type.GeyserEntity; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.GeyserDirtyMetadata; import org.geysermc.geyser.entity.properties.GeyserEntityPropertyManager; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.scoreboard.Team; import org.geysermc.geyser.session.GeyserSession; @@ -377,6 +379,9 @@ public void updateBedrockMetadata() { propertyManager.applyIntProperties(entityDataPacket.getProperties().getIntProperties()); propertyManager.applyFloatProperties(entityDataPacket.getProperties().getFloatProperties()); } + if (this instanceof SessionPlayerEntity) { + GeyserImpl.getInstance().getLogger().severe(entityDataPacket.toString()); + } session.sendUpstreamPacket(entityDataPacket); } } @@ -528,7 +533,9 @@ public void setGravity(BooleanEntityMetadata entityMetadata) { * Usually used for bounding box and not animation. */ public void setPose(Pose pose) { + GeyserImpl.getInstance().getLogger().info("setting pose: " + pose); setFlag(EntityFlag.SLEEPING, pose.equals(Pose.SLEEPING)); + setFlag(EntityFlag.GLIDING, pose.equals(Pose.FALL_FLYING)); // Triggered when crawling setFlag(EntityFlag.SWIMMING, pose.equals(Pose.SWIMMING)); setDimensions(pose); diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index f469c35a9b0..8803e1109af 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1254,6 +1254,18 @@ public void stopSneaking() { setSneaking(false); } + public void setGliding(boolean gliding) { + if (gliding) { + this.pose = Pose.FALL_FLYING; + playerEntity.setBoundingBoxHeight(0.6f); + } else { + this.pose = Pose.STANDING; + playerEntity.setBoundingBoxHeight(playerEntity.getDefinition().height()); + } + playerEntity.setFlag(EntityFlag.GLIDING, gliding); + playerEntity.updateBedrockMetadata(); + } + private void setSneaking(boolean sneaking) { this.sneaking = sneaking; @@ -1790,6 +1802,10 @@ public void sendGameRule(String gameRule, Object value) { private static final Ability[] USED_ABILITIES = Ability.values(); + public void sendAbilities() { + + } + /** * Send an AdventureSettingsPacket to the client with the latest flags */ diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java index b6b0703d15d..0b1d576bf73 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java @@ -78,6 +78,7 @@ public void processInputs(PlayerEntity entity, PlayerAuthInputPacket packet) { } boolean sneaking = bedrockInput.contains(PlayerAuthInputData.SNEAKING); + boolean sprint = bedrockInput.contains(PlayerAuthInputData.SPRINTING); // TODO when is UP_LEFT, etc. used? this.inputPacket = this.inputPacket @@ -87,7 +88,7 @@ public void processInputs(PlayerEntity entity, PlayerAuthInputPacket packet) { .withRight(right) .withJump(bedrockInput.contains(PlayerAuthInputData.JUMPING)) // Looks like this only triggers when the JUMP key input is being pressed. There's also JUMP_DOWN? .withShift(sneaking) - .withSprint(bedrockInput.contains(PlayerAuthInputData.SPRINTING)); // SPRINTING will trigger even if the player isn't moving + .withSprint(sprint); // Send sneaking state before inputs, matches Java client if (oldInputPacket.isShift() != sneaking) { @@ -100,6 +101,17 @@ public void processInputs(PlayerEntity entity, PlayerAuthInputPacket packet) { } } + // TODO test whether accounting for swimming is needed + if (oldInputPacket.isSprint() != sprint) { + if (sprint) { + session.sendDownstreamGamePacket(new ServerboundPlayerCommandPacket(entity.javaId(), PlayerState.START_SPRINTING)); + session.setSprinting(true); + } else { + session.sendDownstreamGamePacket(new ServerboundPlayerCommandPacket(entity.javaId(), PlayerState.STOP_SPRINTING)); + session.setSprinting(false); + } + } + if (oldInputPacket != this.inputPacket) { // Simple equality check is fine since we're checking for an instance change. session.sendDownstreamGamePacket(this.inputPacket); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java index c85c648100b..23b3ffdbfe6 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java @@ -28,17 +28,15 @@ import org.cloudburstmc.math.GenericMath; import org.cloudburstmc.math.vector.Vector2f; import org.cloudburstmc.math.vector.Vector3f; -import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.protocol.bedrock.data.InputMode; import org.cloudburstmc.protocol.bedrock.data.LevelEvent; -import org.cloudburstmc.protocol.bedrock.data.PlayerActionType; import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.ItemUseTransaction; import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket; import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; -import org.cloudburstmc.protocol.bedrock.packet.PlayerActionPacket; import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.BoatEntity; import org.geysermc.geyser.entity.type.Entity; @@ -66,6 +64,7 @@ import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerCommandPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket; +import java.util.HashSet; import java.util.Set; @Translator(packet = PlayerAuthInputPacket.class) @@ -83,24 +82,19 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { processVehicleInput(session, packet, wasJumping); Set inputData = packet.getInputData(); + // These inputs are sent in order, so if e.g. START_GLIDING and STOP_GLIDING are both present, + // it's important to make sure we send the last known status instead of both to the Java server. + Set leftOverInputData = new HashSet<>(packet.getInputData()); for (PlayerAuthInputData input : inputData) { + leftOverInputData.remove(input); switch (input) { + case HANDLE_TELEPORT, ASCEND_BLOCK, DESCEND_BLOCK -> { + // TODO handle_teleport case + // TODO scaffolding fixes for mobile + GeyserImpl.getInstance().getLogger().error("handle tp / scaffolding!"); + } case PERFORM_ITEM_INTERACTION -> processItemUseTransaction(session, packet.getItemUseTransaction()); case PERFORM_BLOCK_ACTIONS -> BedrockBlockActions.translate(session, packet.getPlayerActions()); - case START_SPRINTING -> { - if (!entity.getFlag(EntityFlag.SWIMMING)) { - ServerboundPlayerCommandPacket startSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING); - session.sendDownstreamGamePacket(startSprintPacket); - session.setSprinting(true); - } - } - case STOP_SPRINTING -> { - if (!entity.getFlag(EntityFlag.SWIMMING)) { - ServerboundPlayerCommandPacket stopSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING); - session.sendDownstreamGamePacket(stopSprintPacket); - } - session.setSprinting(false); - } case START_SWIMMING -> session.setSwimming(true); case STOP_SWIMMING -> session.setSwimming(false); case START_CRAWLING -> session.setCrawling(true); @@ -123,16 +117,9 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { session.setFlying(true); session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(true)); } else { - // update whether we can fly + // Stop flying & remind the client about not trying to fly :) + session.setFlying(false); session.sendAdventureSettings(); - // stop flying - PlayerActionPacket stopFlyingPacket = new PlayerActionPacket(); - stopFlyingPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId()); - stopFlyingPacket.setAction(PlayerActionType.STOP_FLYING); - stopFlyingPacket.setBlockPosition(Vector3i.ZERO); - stopFlyingPacket.setResultPosition(Vector3i.ZERO); - stopFlyingPacket.setFace(0); - session.sendUpstreamPacket(stopFlyingPacket); } } case STOP_FLYING -> { @@ -141,17 +128,16 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { } case START_GLIDING -> { // Bedrock can send both start_glide and stop_glide in the same packet. + // We only want to start gliding if the client hasn't stopped gliding in the same tick. // last replicated on 1.21.70 by "walking" and jumping while in water - if (!entity.isGliding() && !packet.getInputData().contains(PlayerAuthInputData.STOP_GLIDING)) { - entity.setFlag(EntityFlag.GLIDING, true); - + if (!entity.isGliding() && !leftOverInputData.contains(PlayerAuthInputData.STOP_GLIDING)) { if (entity.canStartGliding()) { // On Java you can't start gliding while flying if (session.isFlying()) { session.setFlying(false); session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(false)); } - entity.updateBedrockMetadata(); + session.setGliding(true); session.sendDownstreamGamePacket(new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_ELYTRA_FLYING)); } else { entity.stopGliding(); @@ -163,7 +149,8 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { } } case STOP_GLIDING -> { - entity.setFlag(EntityFlag.GLIDING, false); + GeyserImpl.getInstance().getLogger().info("Stopping gliding"); + session.setGliding(false); } case MISSED_SWING -> { session.setLastAirHitTick(session.getTicks()); From dd1426ed61e2f8a49ad9b2f8be16ed1ad9545efc Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Mon, 5 May 2025 17:44:28 +0200 Subject: [PATCH 03/14] Properly track spin attack - should fix - and don't allow the Bedrock client to stop gliding mid-air --- .../geysermc/geyser/entity/type/Entity.java | 19 +++++----- .../geyser/entity/type/LivingEntity.java | 2 +- .../entity/type/living/animal/GoatEntity.java | 2 +- .../type/living/animal/SnifferEntity.java | 2 +- .../type/living/animal/horse/CamelEntity.java | 2 +- .../entity/type/player/PlayerEntity.java | 2 +- .../type/player/SessionPlayerEntity.java | 23 ++++++++---- .../geyser/inventory/PlayerInventory.java | 2 +- .../geyser/session/GeyserSession.java | 36 +++++++------------ .../BedrockPlayerAuthInputTranslator.java | 16 ++++----- 10 files changed, 52 insertions(+), 54 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index c8dee0f26a7..5d4bccbefa7 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -41,12 +41,10 @@ import org.cloudburstmc.protocol.bedrock.packet.MoveEntityDeltaPacket; import org.cloudburstmc.protocol.bedrock.packet.RemoveEntityPacket; import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket; -import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.entity.type.GeyserEntity; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.GeyserDirtyMetadata; import org.geysermc.geyser.entity.properties.GeyserEntityPropertyManager; -import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.scoreboard.Team; import org.geysermc.geyser.session.GeyserSession; @@ -379,9 +377,6 @@ public void updateBedrockMetadata() { propertyManager.applyIntProperties(entityDataPacket.getProperties().getIntProperties()); propertyManager.applyFloatProperties(entityDataPacket.getProperties().getFloatProperties()); } - if (this instanceof SessionPlayerEntity) { - GeyserImpl.getInstance().getLogger().severe(entityDataPacket.toString()); - } session.sendUpstreamPacket(entityDataPacket); } } @@ -410,7 +405,7 @@ public void setFlags(ByteEntityMetadata entityMetadata) { setFlag(EntityFlag.SPRINTING, (xd & 0x08) == 0x08); // Swimming is ignored here and instead we rely on the pose - setFlag(EntityFlag.GLIDING, (xd & 0x80) == 0x80); + setGliding((xd & 0x80) == 0x80); setInvisible((xd & 0x20) == 0x20); } @@ -424,6 +419,13 @@ protected void setInvisible(boolean value) { setFlag(EntityFlag.INVISIBLE, value); } + /** + * Set a boolean - whether the entity is gliding + */ + protected void setGliding(boolean value) { + setFlag(EntityFlag.GLIDING, value); + } + /** * Set an int from 0 - this entity's maximum air - (air / maxAir) represents the percentage of bubbles left */ @@ -533,9 +535,8 @@ public void setGravity(BooleanEntityMetadata entityMetadata) { * Usually used for bounding box and not animation. */ public void setPose(Pose pose) { - GeyserImpl.getInstance().getLogger().info("setting pose: " + pose); setFlag(EntityFlag.SLEEPING, pose.equals(Pose.SLEEPING)); - setFlag(EntityFlag.GLIDING, pose.equals(Pose.FALL_FLYING)); + // FALL_FLYING is instead set via setFlags // Triggered when crawling setFlag(EntityFlag.SWIMMING, pose.equals(Pose.SWIMMING)); setDimensions(pose); @@ -544,7 +545,7 @@ public void setPose(Pose pose) { /** * Set the height and width of the entity's bounding box */ - protected void setDimensions(Pose pose) { + public void setDimensions(Pose pose) { // No flexibility options for basic entities setBoundingBoxHeight(definition.height()); setBoundingBoxWidth(definition.width()); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index 928e9b764e1..d7a786dadad 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -279,7 +279,7 @@ protected boolean isShaking() { } @Override - protected void setDimensions(Pose pose) { + public void setDimensions(Pose pose) { if (pose == Pose.SLEEPING) { setBoundingBoxWidth(0.2f); setBoundingBoxHeight(0.2f); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java index b5e4ad11708..38c2822e447 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java @@ -63,7 +63,7 @@ public void setScreamer(BooleanEntityMetadata entityMetadata) { } @Override - protected void setDimensions(Pose pose) { + public void setDimensions(Pose pose) { if (pose == Pose.LONG_JUMPING) { setBoundingBoxWidth(LONG_JUMPING_WIDTH); setBoundingBoxHeight(LONG_JUMPING_HEIGHT); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java index 203a48f1975..02270ef8bc9 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java @@ -64,7 +64,7 @@ public void setPose(Pose pose) { } @Override - protected void setDimensions(Pose pose) { + public void setDimensions(Pose pose) { if (getFlag(EntityFlag.DIGGING)) { setBoundingBoxHeight(DIGGING_HEIGHT); setBoundingBoxWidth(definition.width()); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java index ca39bd1e636..b4facaf0e7b 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java @@ -113,7 +113,7 @@ public void setPose(Pose pose) { } @Override - protected void setDimensions(Pose pose) { + public void setDimensions(Pose pose) { if (pose == Pose.SITTING) { setBoundingBoxHeight(definition.height() - SITTING_HEIGHT_DIFFERENCE); setBoundingBoxWidth(definition.width()); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 2bdbb56df3f..8d8790edb33 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -416,7 +416,7 @@ protected void scoreVisibility(boolean show) { } @Override - protected void setDimensions(Pose pose) { + public void setDimensions(Pose pose) { float height; float width; switch (pose) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index c2695b40ef8..1736189c535 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -206,6 +206,11 @@ public void setFlags(ByteEntityMetadata entityMetadata) { } } + @Override + protected void setGliding(boolean value) { + session.setGliding(value); + } + /** * Since 1.19.40, the client must be re-informed of its bounding box on respawn * See issue 3370 @@ -466,7 +471,7 @@ public boolean canStartGliding() { return false; } - if (this.isOnClimbableBlock()) { + if (this.isOnClimbableBlock() || session.getPlayerEntity().isOnGround()) { return false; } @@ -484,17 +489,23 @@ public boolean canStartGliding() { return true; } } + + // Bedrock will NOT allow flight when not wearing an elytra; even if it doesn't have a glider component + if (entry.getKey() == EquipmentSlot.CHESTPLATE && !entry.getValue().asItem().equals(Items.ELYTRA)) { + return false; + } } return false; } - public boolean isGliding() { - return getFlag(EntityFlag.GLIDING); + public void setGlidingFlag(boolean gliding) { + setFlag(EntityFlag.GLIDING, gliding); + // ALWAYS send the gliding flag - otherwise the Bedrock client can misbehave + setFlagsDirty(true); } - public void stopGliding() { - setFlag(EntityFlag.GLIDING, false); - updateBedrockMetadata(); + public boolean isGliding() { + return getFlag(EntityFlag.GLIDING); } } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java b/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java index 825dc27c6b3..afc9a57b24d 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java @@ -86,7 +86,7 @@ public GeyserItemStack getItemInHand() { return items[36 + heldItemSlot]; } - // TODO other equipment slots??? + // TODO other equipment slots public Map getEquipment() { return Map.of( EquipmentSlot.MAIN_HAND, getItemInHand(), diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 8803e1109af..c318ddf5e31 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1254,15 +1254,14 @@ public void stopSneaking() { setSneaking(false); } + public void setSpinAttack(boolean spinAttack) { + switchPose(spinAttack, EntityFlag.DAMAGE_NEARBY_MOBS, Pose.SPIN_ATTACK); + } + public void setGliding(boolean gliding) { - if (gliding) { - this.pose = Pose.FALL_FLYING; - playerEntity.setBoundingBoxHeight(0.6f); - } else { - this.pose = Pose.STANDING; - playerEntity.setBoundingBoxHeight(playerEntity.getDefinition().height()); - } - playerEntity.setFlag(EntityFlag.GLIDING, gliding); + this.pose = gliding ? Pose.FALL_FLYING : Pose.STANDING; + playerEntity.setDimensions(this.pose); + playerEntity.setGlidingFlag(gliding); playerEntity.updateBedrockMetadata(); } @@ -1302,22 +1301,17 @@ public void setSwimming(boolean swimming) { playerEntity.updateBedrockMetadata(); return; } - toggleSwimmingPose(swimming, EntityFlag.SWIMMING); + switchPose(swimming, EntityFlag.SWIMMING, Pose.SWIMMING); } public void setCrawling(boolean crawling) { - toggleSwimmingPose(crawling, EntityFlag.CRAWLING); + switchPose(crawling, EntityFlag.CRAWLING, Pose.SWIMMING); } - private void toggleSwimmingPose(boolean crawling, EntityFlag flag) { - if (crawling) { - this.pose = Pose.SWIMMING; - playerEntity.setBoundingBoxHeight(0.6f); - } else { - this.pose = Pose.STANDING; - playerEntity.setBoundingBoxHeight(playerEntity.getDefinition().height()); - } - playerEntity.setFlag(flag, crawling); + private void switchPose(boolean value, EntityFlag flag, Pose pose) { + this.pose = value ? pose : Pose.STANDING; + playerEntity.setDimensions(this.pose); + playerEntity.setFlag(flag, value); playerEntity.updateBedrockMetadata(); } @@ -1802,10 +1796,6 @@ public void sendGameRule(String gameRule, Object value) { private static final Ability[] USED_ABILITIES = Ability.values(); - public void sendAbilities() { - - } - /** * Send an AdventureSettingsPacket to the client with the latest flags */ diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java index 23b3ffdbfe6..0da0c1787f4 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java @@ -36,7 +36,6 @@ import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket; import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket; -import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.BoatEntity; import org.geysermc.geyser.entity.type.Entity; @@ -88,11 +87,6 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { for (PlayerAuthInputData input : inputData) { leftOverInputData.remove(input); switch (input) { - case HANDLE_TELEPORT, ASCEND_BLOCK, DESCEND_BLOCK -> { - // TODO handle_teleport case - // TODO scaffolding fixes for mobile - GeyserImpl.getInstance().getLogger().error("handle tp / scaffolding!"); - } case PERFORM_ITEM_INTERACTION -> processItemUseTransaction(session, packet.getItemUseTransaction()); case PERFORM_BLOCK_ACTIONS -> BedrockBlockActions.translate(session, packet.getPlayerActions()); case START_SWIMMING -> session.setSwimming(true); @@ -128,7 +122,7 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { } case START_GLIDING -> { // Bedrock can send both start_glide and stop_glide in the same packet. - // We only want to start gliding if the client hasn't stopped gliding in the same tick. + // We only want to start gliding if the client has not stopped gliding in the same tick. // last replicated on 1.21.70 by "walking" and jumping while in water if (!entity.isGliding() && !leftOverInputData.contains(PlayerAuthInputData.STOP_GLIDING)) { if (entity.canStartGliding()) { @@ -140,7 +134,7 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { session.setGliding(true); session.sendDownstreamGamePacket(new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_ELYTRA_FLYING)); } else { - entity.stopGliding(); + session.setGliding(false); // return to flying if we can't start gliding if (session.isFlying()) { session.sendAdventureSettings(); @@ -148,9 +142,11 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { } } } + case START_SPIN_ATTACK -> session.setSpinAttack(true); + case STOP_SPIN_ATTACK -> session.setSpinAttack(false); case STOP_GLIDING -> { - GeyserImpl.getInstance().getLogger().info("Stopping gliding"); - session.setGliding(false); + // Java doesn't allow elytra gliding to stop mid-air. + session.setGliding(entity.isGliding() && entity.canStartGliding()); } case MISSED_SWING -> { session.setLastAirHitTick(session.getTicks()); From 7b654e5fb4ee5dfb7d54b1945412f5fb2d5328d6 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Mon, 5 May 2025 18:09:38 +0200 Subject: [PATCH 04/14] also correct pose --- .../java/org/geysermc/geyser/entity/type/LivingEntity.java | 6 +++++- .../geyser/entity/type/player/SessionPlayerEntity.java | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index d7a786dadad..c96f5a2f7cf 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -206,12 +206,16 @@ public void setLivingEntityFlags(ByteEntityMetadata entityMetadata) { setFlag(EntityFlag.BLOCKING, isUsingItem && isUsingShield); // Riptide spin attack - setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, (xd & 0x04) == 0x04); + setSpinAttack((xd & 0x04) == 0x04); // OptionalPack usage setFlag(EntityFlag.EMERGING, isUsingItem && isUsingOffhand); } + public void setSpinAttack(boolean value) { + setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, value); + } + public void setHealth(FloatEntityMetadata entityMetadata) { this.health = entityMetadata.getPrimitiveValue(); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index 1736189c535..074b47f7d78 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -211,6 +211,11 @@ protected void setGliding(boolean value) { session.setGliding(value); } + @Override + public void setSpinAttack(boolean value) { + session.setSpinAttack(value); + } + /** * Since 1.19.40, the client must be re-informed of its bounding box on respawn * See issue 3370 From e6267b2259f1eedbfdfe728a69d24aa8d6250965 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Tue, 6 May 2025 09:14:18 +0200 Subject: [PATCH 05/14] temp --- .../geysermc/geyser/entity/type/Entity.java | 5 +++ .../geyser/entity/type/LivingEntity.java | 6 ++- .../type/player/SessionPlayerEntity.java | 15 +++++-- .../geyser/session/GeyserSession.java | 6 ++- ...BedrockInventoryTransactionTranslator.java | 27 +++++++++++++ .../BedrockRequestAbilityTranslator.java | 40 +++++++++---------- .../player/BedrockPlayerActionTranslator.java | 4 ++ .../BedrockPlayerAuthInputTranslator.java | 11 ++++- 8 files changed, 86 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index 5d4bccbefa7..f0ae85d4304 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -41,10 +41,12 @@ import org.cloudburstmc.protocol.bedrock.packet.MoveEntityDeltaPacket; import org.cloudburstmc.protocol.bedrock.packet.RemoveEntityPacket; import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.entity.type.GeyserEntity; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.GeyserDirtyMetadata; import org.geysermc.geyser.entity.properties.GeyserEntityPropertyManager; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.scoreboard.Team; import org.geysermc.geyser.session.GeyserSession; @@ -377,6 +379,9 @@ public void updateBedrockMetadata() { propertyManager.applyIntProperties(entityDataPacket.getProperties().getIntProperties()); propertyManager.applyFloatProperties(entityDataPacket.getProperties().getFloatProperties()); } + if (this instanceof SessionPlayerEntity) { + GeyserImpl.getInstance().getLogger().info("updating flags! " + entityDataPacket); + } session.sendUpstreamPacket(entityDataPacket); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index c96f5a2f7cf..7fb99651661 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -201,7 +201,7 @@ public void setLivingEntityFlags(ByteEntityMetadata entityMetadata) { boolean isUsingShield = hasShield(isUsingOffhand); - setFlag(EntityFlag.USING_ITEM, isUsingItem && !isUsingShield); + setUsingItem(isUsingItem && !isUsingShield); // Override the blocking setFlag(EntityFlag.BLOCKING, isUsingItem && isUsingShield); @@ -216,6 +216,10 @@ public void setSpinAttack(boolean value) { setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, value); } + public void setUsingItem(boolean value) { + setFlag(EntityFlag.USING_ITEM, value); + } + public void setHealth(FloatEntityMetadata entityMetadata) { this.health = entityMetadata.getPrimitiveValue(); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index 074b47f7d78..2be9326d015 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -37,6 +37,7 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.inventory.GeyserItemStack; @@ -216,6 +217,12 @@ public void setSpinAttack(boolean value) { session.setSpinAttack(value); } + @Override + public void setUsingItem(boolean value) { + GeyserImpl.getInstance().getLogger().error("Server is stopping item use! " + value); + setFlagForce(EntityFlag.USING_ITEM, value); + } + /** * Since 1.19.40, the client must be re-informed of its bounding box on respawn * See issue 3370 @@ -504,10 +511,12 @@ public boolean canStartGliding() { return false; } - public void setGlidingFlag(boolean gliding) { - setFlag(EntityFlag.GLIDING, gliding); - // ALWAYS send the gliding flag - otherwise the Bedrock client can misbehave + // A method that is guaranteed to send updated flags, even if "nothing changed". + // This is useful to prevent e.g. Elytra gliding stopping + public void setFlagForce(EntityFlag flag, boolean value) { + setFlag(flag, value); setFlagsDirty(true); + updateBedrockMetadata(); } public boolean isGliding() { diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index c318ddf5e31..0eb79063d1e 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -667,6 +667,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Setter private @Nullable ItemData currentBook = null; + @Setter + private int fireworkBoostTick; + /** * Stores cookies sent by the Java server. */ @@ -1261,8 +1264,7 @@ public void setSpinAttack(boolean spinAttack) { public void setGliding(boolean gliding) { this.pose = gliding ? Pose.FALL_FLYING : Pose.STANDING; playerEntity.setDimensions(this.pose); - playerEntity.setGlidingFlag(gliding); - playerEntity.updateBedrockMetadata(); + playerEntity.setFlagForce(EntityFlag.GLIDING, gliding); } private void setSneaking(boolean sneaking) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 822a0049f28..5bc2ca407b7 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -30,9 +30,11 @@ import org.cloudburstmc.math.vector.Vector3d; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3i; +import org.cloudburstmc.protocol.bedrock.data.MovementEffectType; import org.cloudburstmc.protocol.bedrock.data.SoundEvent; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; +import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.InventoryActionData; @@ -42,8 +44,11 @@ import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket; import org.cloudburstmc.protocol.bedrock.packet.InventoryTransactionPacket; import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket; +import org.cloudburstmc.protocol.bedrock.packet.MovementEffectPacket; import org.cloudburstmc.protocol.bedrock.packet.PlaySoundPacket; +import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; @@ -173,6 +178,7 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet) case INVENTORY_MISMATCH: break; case ITEM_USE: + GeyserImpl.getInstance().getLogger().info("start item use! " + packet); switch (packet.getActionType()) { case 0 -> { final Vector3i packetBlockPosition = packet.getBlockPosition(); @@ -362,6 +368,7 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet) } case 1 -> { if (isIncorrectHeldItem(session, packet)) { + session.getPlayerEntity().setFlag(EntityFlag.USING_ITEM, false); session.getPlayerInventoryHolder().updateSlot(session.getPlayerInventory().getOffsetForHotbar(packet.getHotbarSlot())); break; } @@ -371,6 +378,9 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet) break; } + // NOT updating here on purpose; will do that in auth input OR when the java server replies + //session.getPlayerEntity().setFlag(EntityFlag.USING_ITEM, true); + // Handled in ITEM_USE if the item is not milk if (packet.getItemInHand() != null) { if (session.getItemMappings().getBuckets().contains(packet.getItemInHand().getDefinition()) && @@ -412,7 +422,24 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet) } } } else if (packet.getItemInHand().getDefinition() == session.getItemMappings().getStoredItems().fireworkRocket().getBedrockDefinition()) { + GeyserImpl.getInstance().getLogger().error("firework! " + session.getPlayerEntity().isGliding() + " " + session.getPlayerEntity().getMotion() + " " + packet); // TODO prevent elytra boosting when not gliding + if (!session.getPlayerEntity().isGliding()) { + // Need to wait until we can "confirm" the session wants to use this rocket to fly + session.setFireworkBoostTick(session.getTicks()); + } else { + // Prevent boost from applying, we're gonna do that instead of the server + MovementEffectPacket effectPacket = new MovementEffectPacket(); + effectPacket.setEffectType(MovementEffectType.GLIDE_BOOST); + effectPacket.setDuration(0); + effectPacket.setEntityRuntimeId(session.getPlayerEntity().getEntityId()); + session.sendUpstreamPacket(effectPacket); + + SetEntityMotionPacket motionPacket = new SetEntityMotionPacket(); + motionPacket.setMotion(session.getPlayerEntity().getMotion()); + motionPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId()); + session.sendUpstreamPacket(motionPacket); + } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRequestAbilityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRequestAbilityTranslator.java index d0c29c6a926..f85de7e03ba 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRequestAbilityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRequestAbilityTranslator.java @@ -25,11 +25,8 @@ package org.geysermc.geyser.translator.protocol.bedrock; -import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode; -import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket; -import org.cloudburstmc.protocol.bedrock.data.Ability; -import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.packet.RequestAbilityPacket; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -42,23 +39,24 @@ public class BedrockRequestAbilityTranslator extends PacketTranslator { + GeyserImpl.getInstance().getLogger().error("unhandled: " + packet); + } } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java index 0da0c1787f4..410d6f994c1 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java @@ -36,6 +36,7 @@ import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket; import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket; +import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.BoatEntity; import org.geysermc.geyser.entity.type.Entity; @@ -87,7 +88,10 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { for (PlayerAuthInputData input : inputData) { leftOverInputData.remove(input); switch (input) { - case PERFORM_ITEM_INTERACTION -> processItemUseTransaction(session, packet.getItemUseTransaction()); + case PERFORM_ITEM_INTERACTION -> { + GeyserImpl.getInstance().getLogger().info(packet.toString()); + processItemUseTransaction(session, packet.getItemUseTransaction()); + } case PERFORM_BLOCK_ACTIONS -> BedrockBlockActions.translate(session, packet.getPlayerActions()); case START_SWIMMING -> session.setSwimming(true); case STOP_SWIMMING -> session.setSwimming(false); @@ -167,6 +171,10 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { // Java edition sends a cooldown when hitting air. CooldownUtils.sendCooldown(session); } + case START_USING_ITEM -> { + GeyserImpl.getInstance().getLogger().info("Using item! " + packet); + //entity.setFlag(EntityFlag.USING_ITEM, true); + } } } @@ -181,6 +189,7 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { } private static void processItemUseTransaction(GeyserSession session, ItemUseTransaction transaction) { + GeyserImpl.getInstance().getLogger().info("item transaction: " + transaction); if (transaction.getActionType() == 2) { int blockState = session.getGameMode() == GameMode.CREATIVE ? session.getGeyser().getWorldManager().getBlockAt(session, transaction.getBlockPosition()) : session.getBreakingBlock(); From f8fa4a3ed5f3ee5df66efb952a045b5a96067ffb Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Fri, 9 May 2025 22:12:56 +0200 Subject: [PATCH 06/14] Cleanup --- .../geysermc/geyser/entity/type/Entity.java | 5 -- .../geyser/entity/type/FireworkEntity.java | 32 +++++----- .../geyser/entity/type/LivingEntity.java | 6 +- .../type/player/SessionPlayerEntity.java | 13 +--- .../geyser/inventory/GeyserItemStack.java | 2 +- .../inventory/item/StoredItemMappings.java | 2 - .../geyser/session/GeyserSession.java | 7 +-- ...BedrockInventoryTransactionTranslator.java | 29 --------- .../BedrockRequestAbilityTranslator.java | 62 ------------------- .../player/BedrockPlayerActionTranslator.java | 4 -- .../BedrockPlayerAuthInputTranslator.java | 15 ++--- gradle/libs.versions.toml | 2 +- 12 files changed, 27 insertions(+), 152 deletions(-) delete mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRequestAbilityTranslator.java diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index f0ae85d4304..5d4bccbefa7 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -41,12 +41,10 @@ import org.cloudburstmc.protocol.bedrock.packet.MoveEntityDeltaPacket; import org.cloudburstmc.protocol.bedrock.packet.RemoveEntityPacket; import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket; -import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.entity.type.GeyserEntity; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.GeyserDirtyMetadata; import org.geysermc.geyser.entity.properties.GeyserEntityPropertyManager; -import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.scoreboard.Team; import org.geysermc.geyser.session.GeyserSession; @@ -379,9 +377,6 @@ public void updateBedrockMetadata() { propertyManager.applyIntProperties(entityDataPacket.getProperties().getIntProperties()); propertyManager.applyFloatProperties(entityDataPacket.getProperties().getFloatProperties()); } - if (this instanceof SessionPlayerEntity) { - GeyserImpl.getInstance().getLogger().info("updating flags! " + entityDataPacket); - } session.sendUpstreamPacket(entityDataPacket); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java index d7a9990fea1..c938cfa8e6c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java @@ -27,9 +27,7 @@ import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; -import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket; import org.geysermc.geyser.entity.EntityDefinition; -import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.TooltipOptions; import org.geysermc.geyser.session.GeyserSession; @@ -72,20 +70,22 @@ public void setPlayerGliding(EntityMetadata entityMetadata) { // and checks to make sure the player that is gliding is the one getting sent the packet // or else every player near the gliding player will boost too. if (optional.isPresent() && optional.getAsInt() == session.getPlayerEntity().getEntityId()) { - PlayerEntity entity = session.getPlayerEntity(); - float yaw = entity.getYaw(); - float pitch = entity.getPitch(); - // Uses math from NukkitX - entity.setMotion(Vector3f.from( - -Math.sin(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)) * 2, - -Math.sin(Math.toRadians(pitch)) * 2, - Math.cos(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)) * 2)); - // Need to update the EntityMotionPacket or else the player won't boost - SetEntityMotionPacket entityMotionPacket = new SetEntityMotionPacket(); - entityMotionPacket.setRuntimeEntityId(entity.getGeyserId()); - entityMotionPacket.setMotion(entity.getMotion()); - - session.sendUpstreamPacket(entityMotionPacket); + // TODO Firework rocket boosting is client side. Sending this boost is no longer needed + // Good luck to whoever is going to try implementing cancelling firework rocket boosting :) +// PlayerEntity entity = session.getPlayerEntity(); +// float yaw = entity.getYaw(); +// float pitch = entity.getPitch(); +// // Uses math from NukkitX +// entity.setMotion(Vector3f.from( +// -Math.sin(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)) * 2, +// -Math.sin(Math.toRadians(pitch)) * 2, +// Math.cos(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)) * 2)); +// // Need to update the EntityMotionPacket or else the player won't boost +// SetEntityMotionPacket entityMotionPacket = new SetEntityMotionPacket(); +// entityMotionPacket.setRuntimeEntityId(entity.getGeyserId()); +// entityMotionPacket.setMotion(entity.getMotion()); +// +// session.sendUpstreamPacket(entityMotionPacket); } } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index 7fb99651661..c96f5a2f7cf 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -201,7 +201,7 @@ public void setLivingEntityFlags(ByteEntityMetadata entityMetadata) { boolean isUsingShield = hasShield(isUsingOffhand); - setUsingItem(isUsingItem && !isUsingShield); + setFlag(EntityFlag.USING_ITEM, isUsingItem && !isUsingShield); // Override the blocking setFlag(EntityFlag.BLOCKING, isUsingItem && isUsingShield); @@ -216,10 +216,6 @@ public void setSpinAttack(boolean value) { setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, value); } - public void setUsingItem(boolean value) { - setFlag(EntityFlag.USING_ITEM, value); - } - public void setHealth(FloatEntityMetadata entityMetadata) { this.health = entityMetadata.getPrimitiveValue(); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index 2be9326d015..081746c0408 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -37,7 +37,6 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; -import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.inventory.GeyserItemStack; @@ -217,12 +216,6 @@ public void setSpinAttack(boolean value) { session.setSpinAttack(value); } - @Override - public void setUsingItem(boolean value) { - GeyserImpl.getInstance().getLogger().error("Server is stopping item use! " + value); - setFlagForce(EntityFlag.USING_ITEM, value); - } - /** * Since 1.19.40, the client must be re-informed of its bounding box on respawn * See issue 3370 @@ -511,12 +504,8 @@ public boolean canStartGliding() { return false; } - // A method that is guaranteed to send updated flags, even if "nothing changed". - // This is useful to prevent e.g. Elytra gliding stopping - public void setFlagForce(EntityFlag flag, boolean value) { - setFlag(flag, value); + public void forceFlagUpdate() { setFlagsDirty(true); - updateBedrockMetadata(); } public boolean isGliding() { diff --git a/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java b/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java index b55c063ee74..5f4ce6b452d 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/GeyserItemStack.java @@ -161,7 +161,7 @@ public DataComponents getOrCreateComponents() { public T getComponent(@NonNull DataComponentType type) { // A data component patch may contain null values to remove base components // e.g. an elytra without the glider component - if (components != null && components.getDataComponents().containsKey(type)) { + if (components != null && components.contains(type)) { return components.get(type); } diff --git a/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java b/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java index bf2908a4c25..a8a711cc234 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/StoredItemMappings.java @@ -47,7 +47,6 @@ public class StoredItemMappings { private final ItemMapping compass; private final ItemMapping crossbow; private final ItemMapping egg; - private final ItemMapping fireworkRocket; private final ItemMapping glassBottle; private final ItemMapping milkBucket; private final ItemMapping powderSnowBucket; @@ -67,7 +66,6 @@ public StoredItemMappings(Map itemMappings) { this.compass = load(itemMappings, Items.COMPASS); this.crossbow = load(itemMappings, Items.CROSSBOW); this.egg = load(itemMappings, Items.EGG); - this.fireworkRocket = load(itemMappings, Items.FIREWORK_ROCKET); this.glassBottle = load(itemMappings, Items.GLASS_BOTTLE); this.milkBucket = load(itemMappings, Items.MILK_BUCKET); this.powderSnowBucket = load(itemMappings, Items.POWDER_SNOW_BUCKET); diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 5ffe2287afc..e808a1754ea 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -667,9 +667,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Setter private @Nullable ItemData currentBook = null; - @Setter - private int fireworkBoostTick; - /** * Stores cookies sent by the Java server. */ @@ -1268,9 +1265,7 @@ public void setSpinAttack(boolean spinAttack) { } public void setGliding(boolean gliding) { - this.pose = gliding ? Pose.FALL_FLYING : Pose.STANDING; - playerEntity.setDimensions(this.pose); - playerEntity.setFlagForce(EntityFlag.GLIDING, gliding); + switchPose(gliding, EntityFlag.GLIDING, Pose.FALL_FLYING); } private void setSneaking(boolean sneaking) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 5bc2ca407b7..56202bc9490 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -30,11 +30,9 @@ import org.cloudburstmc.math.vector.Vector3d; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3i; -import org.cloudburstmc.protocol.bedrock.data.MovementEffectType; import org.cloudburstmc.protocol.bedrock.data.SoundEvent; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; -import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.InventoryActionData; @@ -44,11 +42,8 @@ import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket; import org.cloudburstmc.protocol.bedrock.packet.InventoryTransactionPacket; import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket; -import org.cloudburstmc.protocol.bedrock.packet.MovementEffectPacket; import org.cloudburstmc.protocol.bedrock.packet.PlaySoundPacket; -import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket; -import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; @@ -178,7 +173,6 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet) case INVENTORY_MISMATCH: break; case ITEM_USE: - GeyserImpl.getInstance().getLogger().info("start item use! " + packet); switch (packet.getActionType()) { case 0 -> { final Vector3i packetBlockPosition = packet.getBlockPosition(); @@ -368,7 +362,6 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet) } case 1 -> { if (isIncorrectHeldItem(session, packet)) { - session.getPlayerEntity().setFlag(EntityFlag.USING_ITEM, false); session.getPlayerInventoryHolder().updateSlot(session.getPlayerInventory().getOffsetForHotbar(packet.getHotbarSlot())); break; } @@ -378,9 +371,6 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet) break; } - // NOT updating here on purpose; will do that in auth input OR when the java server replies - //session.getPlayerEntity().setFlag(EntityFlag.USING_ITEM, true); - // Handled in ITEM_USE if the item is not milk if (packet.getItemInHand() != null) { if (session.getItemMappings().getBuckets().contains(packet.getItemInHand().getDefinition()) && @@ -421,25 +411,6 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet) } } } - } else if (packet.getItemInHand().getDefinition() == session.getItemMappings().getStoredItems().fireworkRocket().getBedrockDefinition()) { - GeyserImpl.getInstance().getLogger().error("firework! " + session.getPlayerEntity().isGliding() + " " + session.getPlayerEntity().getMotion() + " " + packet); - // TODO prevent elytra boosting when not gliding - if (!session.getPlayerEntity().isGliding()) { - // Need to wait until we can "confirm" the session wants to use this rocket to fly - session.setFireworkBoostTick(session.getTicks()); - } else { - // Prevent boost from applying, we're gonna do that instead of the server - MovementEffectPacket effectPacket = new MovementEffectPacket(); - effectPacket.setEffectType(MovementEffectType.GLIDE_BOOST); - effectPacket.setDuration(0); - effectPacket.setEntityRuntimeId(session.getPlayerEntity().getEntityId()); - session.sendUpstreamPacket(effectPacket); - - SetEntityMotionPacket motionPacket = new SetEntityMotionPacket(); - motionPacket.setMotion(session.getPlayerEntity().getMotion()); - motionPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId()); - session.sendUpstreamPacket(motionPacket); - } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRequestAbilityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRequestAbilityTranslator.java deleted file mode 100644 index f85de7e03ba..00000000000 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockRequestAbilityTranslator.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2019-2022 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.geyser.translator.protocol.bedrock; - -import org.cloudburstmc.protocol.bedrock.packet.RequestAbilityPacket; -import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; - -/** - * Replaces the AdventureSettingsPacket completely in 1.19.30. - */ -@Translator(packet = RequestAbilityPacket.class) -public class BedrockRequestAbilityTranslator extends PacketTranslator { - - @Override - public void translate(GeyserSession session, RequestAbilityPacket packet) { - GeyserImpl.getInstance().getLogger().info(packet.toString()); - // TODO: Since 1.20.30, this was replaced by a START_FLYING and STOP_FLYING case in BedrockActionTranslator -// if (packet.getAbility() == Ability.FLYING) { -// boolean isFlying = packet.isBoolValue(); -// if (!isFlying && session.getGameMode() == GameMode.SPECTATOR) { -// // We should always be flying in spectator mode -// session.sendAdventureSettings(); -// return; -// } else if (isFlying && session.getPlayerEntity().getFlag(EntityFlag.SWIMMING) && session.getCollisionManager().isPlayerInWater()) { -// // As of 1.18.1, Java Edition cannot fly while in water, but it can fly while crawling -// // If this isn't present, swimming on a 1.13.2 server and then attempting to fly will put you into a flying/swimming state that is invalid on JE -// session.sendAdventureSettings(); -// return; -// } -// -// session.setFlying(isFlying); -// ServerboundPlayerAbilitiesPacket abilitiesPacket = new ServerboundPlayerAbilitiesPacket(isFlying); -// session.sendDownstreamGamePacket(abilitiesPacket); -// } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockPlayerActionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockPlayerActionTranslator.java index f7e996e5731..797505e9914 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockPlayerActionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockPlayerActionTranslator.java @@ -31,7 +31,6 @@ import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket; import org.cloudburstmc.protocol.bedrock.packet.PlayerActionPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; -import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; @@ -117,9 +116,6 @@ public void translate(GeyserSession session, PlayerActionPacket packet) { attributesPacket.getAttributes().addAll(entity.getAttributes().values()); session.sendUpstreamPacket(attributesPacket); } - default -> { - GeyserImpl.getInstance().getLogger().error("unhandled: " + packet); - } } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java index ff09b1cbcd8..b20b3c3d565 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java @@ -36,7 +36,6 @@ import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket; import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket; -import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.BoatEntity; import org.geysermc.geyser.entity.type.Entity; @@ -89,7 +88,6 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { leftOverInputData.remove(input); switch (input) { case PERFORM_ITEM_INTERACTION -> { - GeyserImpl.getInstance().getLogger().info(packet.toString()); processItemUseTransaction(session, packet.getItemUseTransaction()); } case PERFORM_BLOCK_ACTIONS -> BedrockBlockActions.translate(session, packet.getPlayerActions()); @@ -128,7 +126,7 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { // Bedrock can send both start_glide and stop_glide in the same packet. // We only want to start gliding if the client has not stopped gliding in the same tick. // last replicated on 1.21.70 by "walking" and jumping while in water - if (!entity.isGliding() && !leftOverInputData.contains(PlayerAuthInputData.STOP_GLIDING)) { + if (!leftOverInputData.contains(PlayerAuthInputData.STOP_GLIDING)) { if (entity.canStartGliding()) { // On Java you can't start gliding while flying if (session.isFlying()) { @@ -138,6 +136,7 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { session.setGliding(true); session.sendDownstreamGamePacket(new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_ELYTRA_FLYING)); } else { + entity.forceFlagUpdate(); session.setGliding(false); // return to flying if we can't start gliding if (session.isFlying()) { @@ -150,7 +149,10 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { case STOP_SPIN_ATTACK -> session.setSpinAttack(false); case STOP_GLIDING -> { // Java doesn't allow elytra gliding to stop mid-air. - session.setGliding(entity.isGliding() && entity.canStartGliding()); + boolean shouldBeGliding = entity.isGliding() && entity.canStartGliding(); + // Always update; Bedrock can get real weird if the gliding state is mismatching + entity.forceFlagUpdate(); + session.setGliding(shouldBeGliding); } case MISSED_SWING -> { session.setLastAirHitTick(session.getTicks()); @@ -171,10 +173,6 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { // Java edition sends a cooldown when hitting air. CooldownUtils.sendCooldown(session); } - case START_USING_ITEM -> { - GeyserImpl.getInstance().getLogger().info("Using item! " + packet); - //entity.setFlag(EntityFlag.USING_ITEM, true); - } } } @@ -189,7 +187,6 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { } private static void processItemUseTransaction(GeyserSession session, ItemUseTransaction transaction) { - GeyserImpl.getInstance().getLogger().info("item transaction: " + transaction); if (transaction.getActionType() == 2) { int blockState = session.getGameMode() == GameMode.CREATIVE ? session.getGeyser().getWorldManager().getBlockAt(session, transaction.getBlockPosition()) : session.getBreakingBlock(); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6bff9f5f759..da00044c08a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,7 @@ protocol-common = "3.0.0.Beta6-20250506.012145-17" protocol-codec = "3.0.0.Beta6-20250506.012145-17" raknet = "1.0.0.CR3-20250218.160705-18" minecraftauth = "4.1.1" -mcprotocollib = "1.21.5-20250428.154438-26" +mcprotocollib = "1.21.5-20250509.144049-29" adventure = "4.14.0" adventure-platform = "4.3.0" junit = "5.9.2" From 7f28d92dea988f323c1fafc5f149c2a9cbcc42fa Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sat, 10 May 2025 15:51:12 +0200 Subject: [PATCH 07/14] Send sprinting state before inputs, resolve issue mentioned in https://github.com/GeyserMC/Geyser/issues/1705 --- .../level/physics/CollisionManager.java | 3 + .../geyser/session/cache/InputCache.java | 58 +++++++++++++------ 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java b/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java index 6665b6eb843..09e23e27acc 100644 --- a/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java @@ -424,6 +424,9 @@ public boolean isPlayerInWater() { return state.is(Blocks.WATER) && state.getValue(Properties.LEVEL) == 0; } + /** + * @return if the player is currently touching water + */ public boolean isPlayerTouchingWater() { BlockState state = session.getGeyser().getWorldManager().blockAt(session, session.getPlayerEntity().position().toInt()); return state.is(Blocks.WATER); diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java index 0b1d576bf73..50731d39a56 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java @@ -32,7 +32,9 @@ import org.cloudburstmc.protocol.bedrock.data.InputMode; import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData; import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket; -import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerState; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.level.ServerboundPlayerInputPacket; @@ -43,6 +45,7 @@ public final class InputCache { private final GeyserSession session; private ServerboundPlayerInputPacket inputPacket = new ServerboundPlayerInputPacket(false, false, false, false, false, false, false); + @Setter private boolean lastHorizontalCollision; private int ticksSinceLastMovePacket; @Getter @Setter @@ -56,7 +59,7 @@ public InputCache(GeyserSession session) { this.session = session; } - public void processInputs(PlayerEntity entity, PlayerAuthInputPacket packet) { + public void processInputs(SessionPlayerEntity entity, PlayerAuthInputPacket packet) { // Input is sent to the server before packet positions, as of 1.21.2 Set bedrockInput = packet.getInputData(); var oldInputPacket = this.inputPacket; @@ -78,17 +81,7 @@ public void processInputs(PlayerEntity entity, PlayerAuthInputPacket packet) { } boolean sneaking = bedrockInput.contains(PlayerAuthInputData.SNEAKING); - boolean sprint = bedrockInput.contains(PlayerAuthInputData.SPRINTING); - - // TODO when is UP_LEFT, etc. used? - this.inputPacket = this.inputPacket - .withForward(up) - .withBackward(down) - .withLeft(left) - .withRight(right) - .withJump(bedrockInput.contains(PlayerAuthInputData.JUMPING)) // Looks like this only triggers when the JUMP key input is being pressed. There's also JUMP_DOWN? - .withShift(sneaking) - .withSprint(sprint); + boolean sprint = isSprinting(bedrockInput, session.isSprinting()); // Send sneaking state before inputs, matches Java client if (oldInputPacket.isShift() != sneaking) { @@ -101,17 +94,37 @@ public void processInputs(PlayerEntity entity, PlayerAuthInputPacket packet) { } } - // TODO test whether accounting for swimming is needed - if (oldInputPacket.isSprint() != sprint) { + // We're checking the session here as we need to check the current sprint state, not the keypress + if (session.isSprinting() != sprint) { if (sprint) { - session.sendDownstreamGamePacket(new ServerboundPlayerCommandPacket(entity.javaId(), PlayerState.START_SPRINTING)); - session.setSprinting(true); + // Check if the player is standing on but not surrounded by water; don't allow sprinting in that case + // resolves + if (!GameProtocol.is1_21_80orHigher(session) && session.getCollisionManager().isPlayerTouchingWater() && !session.getCollisionManager().isPlayerInWater()) { + // Update movement speed attribute to prevent sprinting on water. This is fixed in 1.21.80+ natively. + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(entity.getGeyserId()); + attributesPacket.getAttributes().addAll(entity.getAttributes().values()); + session.sendUpstreamPacket(attributesPacket); + } else { + session.sendDownstreamGamePacket(new ServerboundPlayerCommandPacket(entity.javaId(), PlayerState.START_SPRINTING)); + session.setSprinting(true); + } } else { session.sendDownstreamGamePacket(new ServerboundPlayerCommandPacket(entity.javaId(), PlayerState.STOP_SPRINTING)); session.setSprinting(false); } } + // TODO when is UP_LEFT, etc. used? + this.inputPacket = this.inputPacket + .withForward(up) + .withBackward(down) + .withLeft(left) + .withRight(right) + // https://mojang.github.io/bedrock-protocol-docs/html/enums.html + .withJump(bedrockInput.contains(PlayerAuthInputData.JUMP_DOWN)) + .withShift(bedrockInput.contains(PlayerAuthInputData.SNEAK_DOWN) || bedrockInput.contains(PlayerAuthInputData.SNEAK_TOGGLE_DOWN)) + .withSprint(bedrockInput.contains(PlayerAuthInputData.SPRINT_DOWN)); if (oldInputPacket != this.inputPacket) { // Simple equality check is fine since we're checking for an instance change. session.sendDownstreamGamePacket(this.inputPacket); } @@ -134,7 +147,14 @@ public boolean lastHorizontalCollision() { return lastHorizontalCollision; } - public void setLastHorizontalCollision(boolean lastHorizontalCollision) { - this.lastHorizontalCollision = lastHorizontalCollision; + // Determines whether the client is currently sprinting. + public boolean isSprinting(Set authInputData, boolean sprinting) { + for (PlayerAuthInputData authInput : authInputData) { + switch (authInput) { + case START_SPRINTING -> sprinting = true; + case STOP_SPRINTING -> sprinting = false; + } + } + return sprinting; } } From 53e87d1c5854b14f8c7007550b5ae14f85390e1c Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sun, 11 May 2025 17:35:03 +0200 Subject: [PATCH 08/14] send sprinting after inputs --- .../geyser/session/cache/InputCache.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java index 50731d39a56..b0d3d4e8ab5 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java @@ -94,6 +94,20 @@ public void processInputs(SessionPlayerEntity entity, PlayerAuthInputPacket pack } } + // TODO when is UP_LEFT, etc. used? + this.inputPacket = this.inputPacket + .withForward(up) + .withBackward(down) + .withLeft(left) + .withRight(right) + // https://mojang.github.io/bedrock-protocol-docs/html/enums.html + .withJump(bedrockInput.contains(PlayerAuthInputData.JUMP_DOWN)) + .withShift(bedrockInput.contains(PlayerAuthInputData.SNEAK_DOWN) || bedrockInput.contains(PlayerAuthInputData.SNEAK_TOGGLE_DOWN)) + .withSprint(bedrockInput.contains(PlayerAuthInputData.SPRINT_DOWN)); + if (oldInputPacket != this.inputPacket) { // Simple equality check is fine since we're checking for an instance change. + session.sendDownstreamGamePacket(this.inputPacket); + } + // We're checking the session here as we need to check the current sprint state, not the keypress if (session.isSprinting() != sprint) { if (sprint) { @@ -115,19 +129,6 @@ public void processInputs(SessionPlayerEntity entity, PlayerAuthInputPacket pack } } - // TODO when is UP_LEFT, etc. used? - this.inputPacket = this.inputPacket - .withForward(up) - .withBackward(down) - .withLeft(left) - .withRight(right) - // https://mojang.github.io/bedrock-protocol-docs/html/enums.html - .withJump(bedrockInput.contains(PlayerAuthInputData.JUMP_DOWN)) - .withShift(bedrockInput.contains(PlayerAuthInputData.SNEAK_DOWN) || bedrockInput.contains(PlayerAuthInputData.SNEAK_TOGGLE_DOWN)) - .withSprint(bedrockInput.contains(PlayerAuthInputData.SPRINT_DOWN)); - if (oldInputPacket != this.inputPacket) { // Simple equality check is fine since we're checking for an instance change. - session.sendDownstreamGamePacket(this.inputPacket); - } } public boolean wasJumping() { From 39b24adf5a889701f65612343fb498429f7244e4 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sun, 11 May 2025 17:55:06 +0200 Subject: [PATCH 09/14] Send vehicle input before sprinting which is sent before player movement --- .../geyser/session/cache/InputCache.java | 36 ----------------- .../BedrockPlayerAuthInputTranslator.java | 39 +++++++++++++++++-- 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java index b0d3d4e8ab5..834e75a6976 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java @@ -32,9 +32,7 @@ import org.cloudburstmc.protocol.bedrock.data.InputMode; import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData; import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket; -import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; -import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerState; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.level.ServerboundPlayerInputPacket; @@ -81,7 +79,6 @@ public void processInputs(SessionPlayerEntity entity, PlayerAuthInputPacket pack } boolean sneaking = bedrockInput.contains(PlayerAuthInputData.SNEAKING); - boolean sprint = isSprinting(bedrockInput, session.isSprinting()); // Send sneaking state before inputs, matches Java client if (oldInputPacket.isShift() != sneaking) { @@ -107,28 +104,6 @@ public void processInputs(SessionPlayerEntity entity, PlayerAuthInputPacket pack if (oldInputPacket != this.inputPacket) { // Simple equality check is fine since we're checking for an instance change. session.sendDownstreamGamePacket(this.inputPacket); } - - // We're checking the session here as we need to check the current sprint state, not the keypress - if (session.isSprinting() != sprint) { - if (sprint) { - // Check if the player is standing on but not surrounded by water; don't allow sprinting in that case - // resolves - if (!GameProtocol.is1_21_80orHigher(session) && session.getCollisionManager().isPlayerTouchingWater() && !session.getCollisionManager().isPlayerInWater()) { - // Update movement speed attribute to prevent sprinting on water. This is fixed in 1.21.80+ natively. - UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); - attributesPacket.setRuntimeEntityId(entity.getGeyserId()); - attributesPacket.getAttributes().addAll(entity.getAttributes().values()); - session.sendUpstreamPacket(attributesPacket); - } else { - session.sendDownstreamGamePacket(new ServerboundPlayerCommandPacket(entity.javaId(), PlayerState.START_SPRINTING)); - session.setSprinting(true); - } - } else { - session.sendDownstreamGamePacket(new ServerboundPlayerCommandPacket(entity.javaId(), PlayerState.STOP_SPRINTING)); - session.setSprinting(false); - } - } - } public boolean wasJumping() { @@ -147,15 +122,4 @@ public boolean shouldSendPositionReminder() { public boolean lastHorizontalCollision() { return lastHorizontalCollision; } - - // Determines whether the client is currently sprinting. - public boolean isSprinting(Set authInputData, boolean sprinting) { - for (PlayerAuthInputData authInput : authInputData) { - switch (authInput) { - case START_SPRINTING -> sprinting = true; - case STOP_SPRINTING -> sprinting = false; - } - } - return sprinting; - } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java index b20b3c3d565..16bf3721a74 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java @@ -36,6 +36,7 @@ import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket; import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket; +import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.BoatEntity; import org.geysermc.geyser.entity.type.Entity; @@ -45,6 +46,7 @@ import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.entity.vehicle.ClientVehicle; import org.geysermc.geyser.level.block.type.Block; +import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -76,9 +78,7 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { boolean wasJumping = session.getInputCache().wasJumping(); session.getInputCache().processInputs(entity, packet); - BedrockMovePlayer.translate(session, packet); - - processVehicleInput(session, packet, wasJumping); + ServerboundPlayerCommandPacket sprintPacket = null; Set inputData = packet.getInputData(); // These inputs are sent in order, so if e.g. START_GLIDING and STOP_GLIDING are both present, @@ -95,6 +95,29 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { case STOP_SWIMMING -> session.setSwimming(false); case START_CRAWLING -> session.setCrawling(true); case STOP_CRAWLING -> session.setCrawling(false); + case START_SPRINTING -> { + if (!leftOverInputData.contains(PlayerAuthInputData.STOP_SPRINTING)) { + // Check if the player is standing on but not surrounded by water; don't allow sprinting in that case + // resolves + if (!GameProtocol.is1_21_80orHigher(session) && session.getCollisionManager().isPlayerTouchingWater() && !session.getCollisionManager().isPlayerInWater()) { + // Update movement speed attribute to prevent sprinting on water. This is fixed in 1.21.80+ natively. + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(entity.getGeyserId()); + attributesPacket.getAttributes().addAll(entity.getAttributes().values()); + session.sendUpstreamPacket(attributesPacket); + } else { + sprintPacket = new ServerboundPlayerCommandPacket(entity.javaId(), PlayerState.START_SPRINTING); + session.setSprinting(true); + } + } + } + case STOP_SPRINTING -> { + if (!leftOverInputData.contains(PlayerAuthInputData.START_SPRINTING)) { + sprintPacket = new ServerboundPlayerCommandPacket(entity.javaId(), PlayerState.STOP_SPRINTING); + session.setSprinting(false); + } + + } case START_FLYING -> { // Since 1.20.30 if (session.isCanFly()) { if (session.getGameMode() == GameMode.SPECTATOR) { @@ -176,6 +199,16 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { } } + // Vehicle input is send before player movement + processVehicleInput(session, packet, wasJumping); + + // Java edition sends sprinting after vehicle input, but before player movement + if (sprintPacket != null) { + session.sendDownstreamGamePacket(sprintPacket); + } + + BedrockMovePlayer.translate(session, packet); + // Only set steering values when the vehicle is a boat and when the client is actually in it if (entity.getVehicle() instanceof BoatEntity && inputData.contains(PlayerAuthInputData.IN_CLIENT_PREDICTED_IN_VEHICLE)) { boolean up = inputData.contains(PlayerAuthInputData.UP); From ecd9d835cf6cbe5faa269f9b34aa9ef2f2d1941a Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sun, 11 May 2025 19:33:37 +0200 Subject: [PATCH 10/14] Slight cleanup --- .../geyser/session/cache/InputCache.java | 22 +++++++++---------- .../BedrockPlayerAuthInputTranslator.java | 15 +++---------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java index 834e75a6976..9aada7086a4 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java @@ -78,9 +78,19 @@ public void processInputs(SessionPlayerEntity entity, PlayerAuthInputPacket pack right = analogMovement.getX() < 0; } - boolean sneaking = bedrockInput.contains(PlayerAuthInputData.SNEAKING); + // TODO when is UP_LEFT, etc. used? + this.inputPacket = this.inputPacket + .withForward(up) + .withBackward(down) + .withLeft(left) + .withRight(right) + // https://mojang.github.io/bedrock-protocol-docs/html/enums.html + .withJump(bedrockInput.contains(PlayerAuthInputData.JUMP_DOWN)) + .withShift(bedrockInput.contains(PlayerAuthInputData.SNEAK_DOWN) || bedrockInput.contains(PlayerAuthInputData.SNEAK_TOGGLE_DOWN)) + .withSprint(bedrockInput.contains(PlayerAuthInputData.SPRINT_DOWN)); // Send sneaking state before inputs, matches Java client + boolean sneaking = bedrockInput.contains(PlayerAuthInputData.SNEAKING); if (oldInputPacket.isShift() != sneaking) { if (sneaking) { session.sendDownstreamGamePacket(new ServerboundPlayerCommandPacket(entity.javaId(), PlayerState.START_SNEAKING)); @@ -91,16 +101,6 @@ public void processInputs(SessionPlayerEntity entity, PlayerAuthInputPacket pack } } - // TODO when is UP_LEFT, etc. used? - this.inputPacket = this.inputPacket - .withForward(up) - .withBackward(down) - .withLeft(left) - .withRight(right) - // https://mojang.github.io/bedrock-protocol-docs/html/enums.html - .withJump(bedrockInput.contains(PlayerAuthInputData.JUMP_DOWN)) - .withShift(bedrockInput.contains(PlayerAuthInputData.SNEAK_DOWN) || bedrockInput.contains(PlayerAuthInputData.SNEAK_TOGGLE_DOWN)) - .withSprint(bedrockInput.contains(PlayerAuthInputData.SPRINT_DOWN)); if (oldInputPacket != this.inputPacket) { // Simple equality check is fine since we're checking for an instance change. session.sendDownstreamGamePacket(this.inputPacket); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java index 16bf3721a74..ca3b31a70e6 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java @@ -87,9 +87,7 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { for (PlayerAuthInputData input : inputData) { leftOverInputData.remove(input); switch (input) { - case PERFORM_ITEM_INTERACTION -> { - processItemUseTransaction(session, packet.getItemUseTransaction()); - } + case PERFORM_ITEM_INTERACTION -> processItemUseTransaction(session, packet.getItemUseTransaction()); case PERFORM_BLOCK_ACTIONS -> BedrockBlockActions.translate(session, packet.getPlayerActions()); case START_SWIMMING -> session.setSwimming(true); case STOP_SWIMMING -> session.setSwimming(false); @@ -287,15 +285,8 @@ private static void processVehicleInput(GeyserSession session, PlayerAuthInputPa if (vehicle instanceof AbstractHorseEntity && !(vehicle instanceof LlamaEntity)) { sendMovement = !(vehicle instanceof ClientVehicle); } else if (vehicle instanceof BoatEntity) { - if (vehicle.getPassengers().size() == 1) { - // The player is the only rider - sendMovement = true; - } else { - // Check if the player is the front rider - if (session.getPlayerEntity().isRidingInFront()) { - sendMovement = true; - } - } + // The player is either the only or the front rider. + sendMovement = vehicle.getPassengers().size() == 1 || session.getPlayerEntity().isRidingInFront(); } if (vehicle instanceof AbstractHorseEntity && !vehicle.getFlag(EntityFlag.HAS_DASH_COOLDOWN)) { From 14549c241e893d021a77ddb3c4acf3aaa97a2832 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sun, 11 May 2025 19:37:59 +0200 Subject: [PATCH 11/14] Rename setDimensions -> setDimensionsFromPose --- .../main/java/org/geysermc/geyser/entity/type/Entity.java | 6 +++--- .../java/org/geysermc/geyser/entity/type/LivingEntity.java | 4 ++-- .../geyser/entity/type/living/animal/GoatEntity.java | 4 ++-- .../geyser/entity/type/living/animal/SnifferEntity.java | 6 +++--- .../geyser/entity/type/living/animal/horse/CamelEntity.java | 4 ++-- .../geysermc/geyser/entity/type/player/PlayerEntity.java | 4 ++-- .../java/org/geysermc/geyser/session/GeyserSession.java | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index 5d4bccbefa7..33689dd546c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -171,7 +171,7 @@ protected void initializeMetadata() { dirtyMetadata.put(EntityDataTypes.SCALE, 1f); dirtyMetadata.put(EntityDataTypes.COLOR, (byte) 0); dirtyMetadata.put(EntityDataTypes.AIR_SUPPLY_MAX, getMaxAir()); - setDimensions(Pose.STANDING); + setDimensionsFromPose(Pose.STANDING); setFlag(EntityFlag.HAS_GRAVITY, true); setFlag(EntityFlag.HAS_COLLISION, true); setFlag(EntityFlag.CAN_SHOW_NAME, true); @@ -539,13 +539,13 @@ public void setPose(Pose pose) { // FALL_FLYING is instead set via setFlags // Triggered when crawling setFlag(EntityFlag.SWIMMING, pose.equals(Pose.SWIMMING)); - setDimensions(pose); + setDimensionsFromPose(pose); } /** * Set the height and width of the entity's bounding box */ - public void setDimensions(Pose pose) { + protected void setDimensionsFromPose(Pose pose) { // No flexibility options for basic entities setBoundingBoxHeight(definition.height()); setBoundingBoxWidth(definition.width()); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index c96f5a2f7cf..fb04691a50a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -283,12 +283,12 @@ protected boolean isShaking() { } @Override - public void setDimensions(Pose pose) { + protected void setDimensionsFromPose(Pose pose) { if (pose == Pose.SLEEPING) { setBoundingBoxWidth(0.2f); setBoundingBoxHeight(0.2f); } else { - super.setDimensions(pose); + super.setDimensionsFromPose(pose); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java index 38c2822e447..b954bb7a557 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/GoatEntity.java @@ -63,12 +63,12 @@ public void setScreamer(BooleanEntityMetadata entityMetadata) { } @Override - public void setDimensions(Pose pose) { + protected void setDimensionsFromPose(Pose pose) { if (pose == Pose.LONG_JUMPING) { setBoundingBoxWidth(LONG_JUMPING_WIDTH); setBoundingBoxHeight(LONG_JUMPING_HEIGHT); } else { - super.setDimensions(pose); + super.setDimensionsFromPose(pose); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java index 02270ef8bc9..2def028698c 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/SnifferEntity.java @@ -64,12 +64,12 @@ public void setPose(Pose pose) { } @Override - public void setDimensions(Pose pose) { + protected void setDimensionsFromPose(Pose pose) { if (getFlag(EntityFlag.DIGGING)) { setBoundingBoxHeight(DIGGING_HEIGHT); setBoundingBoxWidth(definition.width()); } else { - super.setDimensions(pose); + super.setDimensionsFromPose(pose); } } @@ -90,7 +90,7 @@ public void setSnifferState(ObjectEntityMetadata entityMetadata) { setFlag(EntityFlag.DIGGING, snifferState == SnifferState.DIGGING); setFlag(EntityFlag.RISING, snifferState == SnifferState.RISING); - setDimensions(pose); + setDimensionsFromPose(pose); if (getFlag(EntityFlag.DIGGING)) { digTicks = DIG_END; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java index b4facaf0e7b..6239122f447 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/CamelEntity.java @@ -113,12 +113,12 @@ public void setPose(Pose pose) { } @Override - public void setDimensions(Pose pose) { + protected void setDimensionsFromPose(Pose pose) { if (pose == Pose.SITTING) { setBoundingBoxHeight(definition.height() - SITTING_HEIGHT_DIFFERENCE); setBoundingBoxWidth(definition.width()); } else { - super.setDimensions(pose); + super.setDimensionsFromPose(pose); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 8d8790edb33..dd48ca73974 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -416,7 +416,7 @@ protected void scoreVisibility(boolean show) { } @Override - public void setDimensions(Pose pose) { + public void setDimensionsFromPose(Pose pose) { float height; float width; switch (pose) { @@ -433,7 +433,7 @@ public void setDimensions(Pose pose) { width = 0.2f; } default -> { - super.setDimensions(pose); + super.setDimensionsFromPose(pose); return; } } diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index e808a1754ea..40aff17f971 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1313,7 +1313,7 @@ public void setCrawling(boolean crawling) { private void switchPose(boolean value, EntityFlag flag, Pose pose) { this.pose = value ? pose : Pose.STANDING; - playerEntity.setDimensions(this.pose); + playerEntity.setDimensionsFromPose(this.pose); playerEntity.setFlag(flag, value); playerEntity.updateBedrockMetadata(); } From 9d07c94129bd301dc639a4b4af74637c3ae57c27 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sun, 11 May 2025 19:41:01 +0200 Subject: [PATCH 12/14] setSpinAttack should be protected --- .../main/java/org/geysermc/geyser/entity/type/LivingEntity.java | 2 +- .../geysermc/geyser/entity/type/player/SessionPlayerEntity.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index fb04691a50a..f4a7885181f 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -212,7 +212,7 @@ public void setLivingEntityFlags(ByteEntityMetadata entityMetadata) { setFlag(EntityFlag.EMERGING, isUsingItem && isUsingOffhand); } - public void setSpinAttack(boolean value) { + protected void setSpinAttack(boolean value) { setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, value); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index 081746c0408..cf973a07db0 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -212,7 +212,7 @@ protected void setGliding(boolean value) { } @Override - public void setSpinAttack(boolean value) { + protected void setSpinAttack(boolean value) { session.setSpinAttack(value); } From 4cc655a1bff4af07086beabf8cd5234816ddfe2e Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sun, 11 May 2025 20:41:26 +0200 Subject: [PATCH 13/14] Resolve mobile players being unable to climb down scaffolding --- .../geysermc/geyser/session/cache/InputCache.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java index 9aada7086a4..8ab120ba577 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java @@ -85,12 +85,18 @@ public void processInputs(SessionPlayerEntity entity, PlayerAuthInputPacket pack .withLeft(left) .withRight(right) // https://mojang.github.io/bedrock-protocol-docs/html/enums.html - .withJump(bedrockInput.contains(PlayerAuthInputData.JUMP_DOWN)) - .withShift(bedrockInput.contains(PlayerAuthInputData.SNEAK_DOWN) || bedrockInput.contains(PlayerAuthInputData.SNEAK_TOGGLE_DOWN)) + // using the "raw" values allows us sending key presses even with locked input + .withJump(bedrockInput.contains(PlayerAuthInputData.JUMP_CURRENT_RAW)) + .withShift(bedrockInput.contains(PlayerAuthInputData.SNEAK_CURRENT_RAW)) .withSprint(bedrockInput.contains(PlayerAuthInputData.SPRINT_DOWN)); // Send sneaking state before inputs, matches Java client - boolean sneaking = bedrockInput.contains(PlayerAuthInputData.SNEAKING); + boolean sneaking = bedrockInput.contains(PlayerAuthInputData.SNEAKING) || + // DESCEND_BLOCK is ONLY sent while mobile clients are descending scaffolding. + // PERSIST_SNEAK is ALWAYS sent by mobile clients. + // While we could use SNEAK_CURRENT_RAW, that would also be sent with locked inputs. + // fixes https://github.com/GeyserMC/Geyser/issues/5384 + (bedrockInput.contains(PlayerAuthInputData.DESCEND_BLOCK) && bedrockInput.contains(PlayerAuthInputData.PERSIST_SNEAK)); if (oldInputPacket.isShift() != sneaking) { if (sneaking) { session.sendDownstreamGamePacket(new ServerboundPlayerCommandPacket(entity.javaId(), PlayerState.START_SNEAKING)); From 0992bd3eac914426f4f223e24204e53972109827 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Sun, 11 May 2025 20:51:36 +0200 Subject: [PATCH 14/14] Don't send STOP_SPRINTING packet if we weren't sprinting --- .../entity/player/input/BedrockPlayerAuthInputTranslator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java index ca3b31a70e6..94aadad953a 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockPlayerAuthInputTranslator.java @@ -110,11 +110,11 @@ public void translate(GeyserSession session, PlayerAuthInputPacket packet) { } } case STOP_SPRINTING -> { - if (!leftOverInputData.contains(PlayerAuthInputData.START_SPRINTING)) { + // Don't send sprinting update when we weren't sprinting + if (!leftOverInputData.contains(PlayerAuthInputData.START_SPRINTING) && session.isSprinting()) { sprintPacket = new ServerboundPlayerCommandPacket(entity.javaId(), PlayerState.STOP_SPRINTING); session.setSprinting(false); } - } case START_FLYING -> { // Since 1.20.30 if (session.isCanFly()) {