1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2025-12-19 14:59:27 +00:00

Prevent player from jumping out of vehicle by input locking (#5908)

* Initial changes to input locking.

* Working vehicle dismount locks.

* Locking jump dismount, more work on server-sided dismount.

* Just lock jump input, locking dismount cause unintended behaviour.

* Revert some old changes.

* Oops.

* Rename this to doesJumpDismount.

* Sort this.
This commit is contained in:
oryxel
2025-10-22 17:50:08 +07:00
committed by GitHub
parent d4733c8be2
commit 517d260c72
9 changed files with 108 additions and 17 deletions

View File

@@ -29,6 +29,7 @@ import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.packet.EmotePacket; import org.cloudburstmc.protocol.bedrock.packet.EmotePacket;
import org.geysermc.geyser.input.InputLocksFlag;
import org.geysermc.geyser.api.entity.EntityData; import org.geysermc.geyser.api.entity.EntityData;
import org.geysermc.geyser.api.entity.type.GeyserEntity; import org.geysermc.geyser.api.entity.type.GeyserEntity;
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity; import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
@@ -87,7 +88,8 @@ public class GeyserEntityData implements EntityData {
movementLockOwners.remove(owner); movementLockOwners.remove(owner);
} }
session.lockInputs(session.camera().isCameraLocked(), isMovementLocked()); session.setLockInput(InputLocksFlag.MOVEMENT, isMovementLocked());
session.updateInputLocks();
return isMovementLocked(); return isMovementLocked();
} }

View File

@@ -614,6 +614,14 @@ public class Entity implements GeyserEntity {
protected boolean isShaking() { protected boolean isShaking() {
return false; return false;
} }
/**
* If true, the entity can be dismounted by pressing jump.
*
* @return whether the entity can be dismounted when pressing jump.
*/
public boolean doesJumpDismount() {
return true;
}
/** /**
* x = Pitch, y = Yaw, z = HeadYaw * x = Pitch, y = Yaw, z = HeadYaw

View File

@@ -37,6 +37,7 @@ import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity; import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
import org.geysermc.geyser.input.InputLocksFlag;
import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.item.type.Item;
@@ -85,6 +86,15 @@ public class AbstractHorseEntity extends AnimalEntity {
// Shows the jump meter // Shows the jump meter
setFlag(EntityFlag.CAN_POWER_JUMP, saddled); setFlag(EntityFlag.CAN_POWER_JUMP, saddled);
super.updateSaddled(saddled); super.updateSaddled(saddled);
// We want to allow player to press jump again if pressing jump doesn't dismount the entity.
this.session.setLockInput(InputLocksFlag.JUMP, this.doesJumpDismount());
this.session.updateInputLocks();
}
@Override
public boolean doesJumpDismount() {
return !this.getFlag(EntityFlag.SADDLED);
} }
public void setHorseFlags(ByteEntityMetadata entityMetadata) { public void setHorseFlags(ByteEntityMetadata entityMetadata) {

View File

@@ -43,6 +43,7 @@ import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.entity.type.BoatEntity; import org.geysermc.geyser.entity.type.BoatEntity;
import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.LivingEntity; import org.geysermc.geyser.entity.type.LivingEntity;
import org.geysermc.geyser.input.InputLocksFlag;
import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.level.block.Blocks;
@@ -476,6 +477,10 @@ public class SessionPlayerEntity extends PlayerEntity {
this.vehicle.updateBedrockMetadata(); this.vehicle.updateBedrockMetadata();
} }
// Bedrock player can dismount by pressing jump while Java cannot, so we need to prevent player from jumping to match vanilla behaviour.
this.session.setLockInput(InputLocksFlag.JUMP, entity != null && entity.doesJumpDismount());
this.session.updateInputLocks();
super.setVehicle(entity); super.setVehicle(entity);
} }

View File

@@ -48,6 +48,7 @@ import org.geysermc.geyser.api.bedrock.camera.CameraPerspective;
import org.geysermc.geyser.api.bedrock.camera.CameraPosition; import org.geysermc.geyser.api.bedrock.camera.CameraPosition;
import org.geysermc.geyser.api.bedrock.camera.CameraShake; import org.geysermc.geyser.api.bedrock.camera.CameraShake;
import org.geysermc.geyser.api.bedrock.camera.GuiElement; import org.geysermc.geyser.api.bedrock.camera.GuiElement;
import org.geysermc.geyser.input.InputLocksFlag;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
@@ -251,7 +252,8 @@ public class GeyserCameraData implements CameraData {
this.cameraLockOwners.remove(owner); this.cameraLockOwners.remove(owner);
} }
session.lockInputs(isCameraLocked(), session.entities().isMovementLocked()); session.setLockInput(InputLocksFlag.CAMERA, isCameraLocked());
session.updateInputLocks();
return isCameraLocked(); return isCameraLocked();
} }

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2025 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.input;
import lombok.RequiredArgsConstructor;
import lombok.Getter;
// This is taken from (https://gist.github.com/wan-adrian/e919b46be3889d865801eb8883407587) or (https://github.com/PowerNukkitX/PowerNukkitX/blob/master/src/main/java/cn/nukkit/network/protocol/types/ClientInputLocksFlag.java)
@RequiredArgsConstructor
@Getter
public enum InputLocksFlag {
RESET(0),
CAMERA(2),
MOVEMENT(4),
LATERAL_MOVEMENT(16),
SNEAK(32),
JUMP(64),
MOUNT(128),
DISMOUNT(256),
MOVE_FORWARD(512),
MOVE_BACKWARD(1024),
MOVE_LEFT(2048),
MOVE_RIGHT(4096);
private final int offset;
}

View File

@@ -34,6 +34,7 @@ import org.cloudburstmc.math.vector.Vector3d;
import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateClientInputLocksPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateClientInputLocksPacket;
import org.geysermc.erosion.util.BlockPositionIterator; import org.geysermc.erosion.util.BlockPositionIterator;
import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.EntityDefinitions;
@@ -220,12 +221,12 @@ public class CollisionManager {
public void recalculatePosition() { public void recalculatePosition() {
PlayerEntity entity = session.getPlayerEntity(); PlayerEntity entity = session.getPlayerEntity();
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
// This does the job and won't interrupt velocity + rotation. movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
UpdateClientInputLocksPacket inputLocksPacket = new UpdateClientInputLocksPacket(); movePlayerPacket.setPosition(entity.getPosition());
inputLocksPacket.setLockComponentData(0); // Don't actually lock anything. movePlayerPacket.setRotation(entity.getBedrockRotation());
inputLocksPacket.setServerPosition(entity.getPosition()); movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL);
session.sendUpstreamPacket(inputLocksPacket); session.sendUpstreamPacket(movePlayerPacket);
} }
public BlockPositionIterator collidableBlocksIterator(BoundingBox box) { public BlockPositionIterator collidableBlocksIterator(BoundingBox box) {

View File

@@ -113,6 +113,7 @@ import org.geysermc.cumulus.form.util.FormBuilder;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.bedrock.camera.CameraData; import org.geysermc.geyser.api.bedrock.camera.CameraData;
import org.geysermc.geyser.api.bedrock.camera.CameraShake; import org.geysermc.geyser.api.bedrock.camera.CameraShake;
import org.geysermc.geyser.input.InputLocksFlag;
import org.geysermc.geyser.api.connection.GeyserConnection; import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.api.entity.EntityData; import org.geysermc.geyser.api.entity.EntityData;
import org.geysermc.geyser.api.entity.type.GeyserEntity; import org.geysermc.geyser.api.entity.type.GeyserEntity;
@@ -234,6 +235,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.BitSet; import java.util.BitSet;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -748,6 +750,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Accessors(fluent = true) @Accessors(fluent = true)
private boolean hasAcceptedCodeOfConduct = false; private boolean hasAcceptedCodeOfConduct = false;
private final Set<InputLocksFlag> inputLocksSet = EnumSet.noneOf(InputLocksFlag.class);
private boolean inputLockDirty;
public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop tickEventLoop) { public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop tickEventLoop) {
this.geyser = geyser; this.geyser = geyser;
this.upstream = new UpstreamSession(bedrockServerSession); this.upstream = new UpstreamSession(bedrockServerSession);
@@ -2377,17 +2382,21 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
entities().showEmote(emoter, emoteId); entities().showEmote(emoter, emoteId);
} }
public void lockInputs(boolean camera, boolean movement) { public void setLockInput(InputLocksFlag flag, boolean value) {
this.inputLockDirty |= value ? this.inputLocksSet.add(flag) : this.inputLocksSet.remove(flag);
}
public void updateInputLocks() {
if (!this.inputLockDirty) {
return;
}
this.inputLockDirty = false;
UpdateClientInputLocksPacket packet = new UpdateClientInputLocksPacket(); UpdateClientInputLocksPacket packet = new UpdateClientInputLocksPacket();
final int cameraOffset = 1 << 1;
final int movementOffset = 1 << 2;
int result = 0; int result = 0;
if (camera) { for (InputLocksFlag other : this.inputLocksSet) {
result |= cameraOffset; result |= other.getOffset();
}
if (movement) {
result |= movementOffset;
} }
packet.setLockComponentData(result); packet.setLockComponentData(result);
@@ -2396,6 +2405,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
sendUpstreamPacket(packet); sendUpstreamPacket(packet);
} }
public boolean getLockedInput(InputLocksFlag flag) {
return this.inputLocksSet.contains(flag);
}
@Override @Override
public @NonNull CameraData camera() { public @NonNull CameraData camera() {
return this.cameraData; return this.cameraData;

View File

@@ -127,7 +127,8 @@ public final class InputCache {
// https://mojang.github.io/bedrock-protocol-docs/html/enums.html // https://mojang.github.io/bedrock-protocol-docs/html/enums.html
// using the "raw" values allows us sending key presses even with locked input // using the "raw" values allows us sending key presses even with locked input
// There appear to be cases where the raw value is not sent - e.g. sneaking with a shield on mobile (1.21.80) // There appear to be cases where the raw value is not sent - e.g. sneaking with a shield on mobile (1.21.80)
.withJump(bedrockInput.contains(PlayerAuthInputData.JUMP_CURRENT_RAW) || bedrockInput.contains(PlayerAuthInputData.JUMP_DOWN)) // We also need to check for water auto jumping, since bedrock don't send jumping value in those cases.
.withJump(bedrockInput.contains(PlayerAuthInputData.JUMP_CURRENT_RAW) || bedrockInput.contains(PlayerAuthInputData.JUMP_DOWN) || bedrockInput.contains(PlayerAuthInputData.AUTO_JUMPING_IN_WATER))
.withShift(session.isShouldSendSneak() || sneaking) .withShift(session.isShouldSendSneak() || sneaking)
.withSprint(bedrockInput.contains(PlayerAuthInputData.SPRINT_DOWN)); .withSprint(bedrockInput.contains(PlayerAuthInputData.SPRINT_DOWN));