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 33689dd54..581a6d319 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 @@ -401,12 +401,10 @@ public class Entity implements GeyserEntity { public void setFlags(ByteEntityMetadata entityMetadata) { byte xd = entityMetadata.getPrimitiveValue(); setFlag(EntityFlag.ON_FIRE, ((xd & 0x01) == 0x01) && !getFlag(EntityFlag.FIRE_IMMUNE)); // Otherwise immune entities sometimes flicker onfire - setFlag(EntityFlag.SNEAKING, (xd & 0x02) == 0x02); - setFlag(EntityFlag.SPRINTING, (xd & 0x08) == 0x08); - + setSneaking((xd & 0x02) == 0x02); + setSprinting((xd & 0x08) == 0x08); // Swimming is ignored here and instead we rely on the pose setGliding((xd & 0x80) == 0x80); - setInvisible((xd & 0x20) == 0x20); } @@ -426,6 +424,14 @@ public class Entity implements GeyserEntity { setFlag(EntityFlag.GLIDING, value); } + protected void setSprinting(boolean value) { + setFlag(EntityFlag.SPRINTING, value); + } + + protected void setSneaking(boolean value) { + setFlag(EntityFlag.SNEAKING, value); + } + /** * Set an int from 0 - this entity's maximum air - (air / maxAir) represents the percentage of bubbles left */ 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 cf973a07d..c2b45a536 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 @@ -100,6 +100,9 @@ public class SessionPlayerEntity extends PlayerEntity { private int lastAirSupply = getMaxAir(); + @Getter @Setter + private boolean insideScaffolding = false; + /** * The client last tick end velocity, used for calculating player onGround. */ @@ -206,11 +209,26 @@ public class SessionPlayerEntity extends PlayerEntity { } } + @Override + protected void setSprinting(boolean value) { + super.setSprinting(value); + session.setSprinting(value); + } + @Override protected void setGliding(boolean value) { session.setGliding(value); } + @Override + protected void setSneaking(boolean value) { + if (value) { + session.startSneaking(); + } else { + session.stopSneaking(); + } + } + @Override protected void setSpinAttack(boolean value) { session.setSpinAttack(value); 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 09e23e27a..006304f1a 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 @@ -37,8 +37,8 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket; import org.geysermc.erosion.util.BlockPositionIterator; import org.geysermc.geyser.entity.EntityDefinitions; -import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.player.PlayerEntity; +import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.entity.vehicle.ClientVehicle; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.level.block.Blocks; @@ -451,7 +451,8 @@ public class CollisionManager { * @param updateMetadata whether we should update metadata if something changed */ public void updateScaffoldingFlags(boolean updateMetadata) { - Entity entity = session.getPlayerEntity(); + SessionPlayerEntity entity = session.getPlayerEntity(); + entity.setInsideScaffolding(touchingScaffolding); boolean isSneakingWithScaffolding = (touchingScaffolding || onScaffolding) && session.isSneaking(); entity.setFlag(EntityFlag.OVER_DESCENDABLE_BLOCK, onScaffolding); 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 40aff17f9..b89fc4701 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1359,6 +1359,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { * blocking and sends a packet to the Java server. */ private boolean attemptToBlock() { + // Don't try to block while in scaffolding + if (playerEntity.isInsideScaffolding()) { + return false; + } + if (playerInventoryHolder.inventory().getItemInHand().asItem() == Items.SHIELD) { useItem(Hand.MAIN_HAND); } else if (playerInventoryHolder.inventory().getOffhand().asItem() == Items.SHIELD) { 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 83eacd2dc..5d6b60521 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 @@ -91,14 +91,9 @@ public final class InputCache { .withShift(bedrockInput.contains(PlayerAuthInputData.SNEAK_CURRENT_RAW) || bedrockInput.contains(PlayerAuthInputData.SNEAK_DOWN)) .withSprint(bedrockInput.contains(PlayerAuthInputData.SPRINT_DOWN)); - // Send sneaking state before inputs, matches Java client - 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) { + // Send sneaking before inputs; matches Java edition + boolean sneaking = isSneaking(bedrockInput); + if (session.isSneaking() != sneaking) { if (sneaking) { session.sendDownstreamGamePacket(new ServerboundPlayerCommandPacket(entity.javaId(), PlayerState.START_SNEAKING)); session.startSneaking(); @@ -129,4 +124,43 @@ public final class InputCache { public boolean lastHorizontalCollision() { return lastHorizontalCollision; } + + /* + As of 1.21.80: relying on the SNEAKING flag will also cause the player to be sneaking after opening chests while sneaking, + even after the client sent STOP_SNEAKING. See https://github.com/GeyserMC/Geyser/issues/5552 + Hence, we do not rely on the SNEAKING flag altogether :) + + This method is designed to detect changes in sneaking to return the new sneaking state. + */ + public boolean isSneaking(Set authInputData) { + // Flying doesn't send start / stop fly cases; might as well return early + if (session.isFlying()) { + // Of course e.g. mobile handles it differently with a descend case, while + // e.g. Win10 sends SNEAK_DOWN. Why? We'll never know. + return authInputData.contains(PlayerAuthInputData.DESCEND) || authInputData.contains(PlayerAuthInputData.SNEAK_DOWN); + } + + boolean sneaking = session.isSneaking(); + // Looping through input data as e.g. stop/start sneaking can be sent in the same packet + // and then, the last sent instruction matters + for (PlayerAuthInputData authInput : authInputData) { + switch (authInput) { + case STOP_SNEAKING -> sneaking = false; + case START_SNEAKING -> sneaking = true; + // DESCEND_BLOCK is ONLY sent while mobile clients are descending scaffolding. + // PERSIST_SNEAK is ALWAYS sent by mobile clients. + // fixes https://github.com/GeyserMC/Geyser/issues/5384 + case PERSIST_SNEAK -> { + // Ignoring start/stop sneaking while in scaffolding on purpose to ensure + // that we don't spam both cases for every block we went down + if (session.getPlayerEntity().isInsideScaffolding()) { + return authInputData.contains(PlayerAuthInputData.DESCEND_BLOCK) && + authInputData.contains(PlayerAuthInputData.SNEAK_CURRENT_RAW); + } + } + } + } + + return sneaking; + } }