Skip to content

Commit 517d260

Browse files
authored
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.
1 parent d4733c8 commit 517d260

File tree

9 files changed

+108
-17
lines changed

9 files changed

+108
-17
lines changed

core/src/main/java/org/geysermc/geyser/entity/GeyserEntityData.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.checkerframework.checker.nullness.qual.NonNull;
3030
import org.checkerframework.checker.nullness.qual.Nullable;
3131
import org.cloudburstmc.protocol.bedrock.packet.EmotePacket;
32+
import org.geysermc.geyser.input.InputLocksFlag;
3233
import org.geysermc.geyser.api.entity.EntityData;
3334
import org.geysermc.geyser.api.entity.type.GeyserEntity;
3435
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
@@ -87,7 +88,8 @@ public boolean lockMovement(boolean lock, @NonNull UUID owner) {
8788
movementLockOwners.remove(owner);
8889
}
8990

90-
session.lockInputs(session.camera().isCameraLocked(), isMovementLocked());
91+
session.setLockInput(InputLocksFlag.MOVEMENT, isMovementLocked());
92+
session.updateInputLocks();
9193
return isMovementLocked();
9294
}
9395

core/src/main/java/org/geysermc/geyser/entity/type/Entity.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,14 @@ public void setRiderSeatPosition(Vector3f position) {
614614
protected boolean isShaking() {
615615
return false;
616616
}
617+
/**
618+
* If true, the entity can be dismounted by pressing jump.
619+
*
620+
* @return whether the entity can be dismounted when pressing jump.
621+
*/
622+
public boolean doesJumpDismount() {
623+
return true;
624+
}
617625

618626
/**
619627
* x = Pitch, y = Yaw, z = HeadYaw

core/src/main/java/org/geysermc/geyser/entity/type/living/animal/horse/AbstractHorseEntity.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.geysermc.geyser.entity.EntityDefinition;
3838
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
3939
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
40+
import org.geysermc.geyser.input.InputLocksFlag;
4041
import org.geysermc.geyser.inventory.GeyserItemStack;
4142
import org.geysermc.geyser.item.Items;
4243
import org.geysermc.geyser.item.type.Item;
@@ -85,6 +86,15 @@ public void updateSaddled(boolean saddled) {
8586
// Shows the jump meter
8687
setFlag(EntityFlag.CAN_POWER_JUMP, saddled);
8788
super.updateSaddled(saddled);
89+
90+
// We want to allow player to press jump again if pressing jump doesn't dismount the entity.
91+
this.session.setLockInput(InputLocksFlag.JUMP, this.doesJumpDismount());
92+
this.session.updateInputLocks();
93+
}
94+
95+
@Override
96+
public boolean doesJumpDismount() {
97+
return !this.getFlag(EntityFlag.SADDLED);
8898
}
8999

90100
public void setHorseFlags(ByteEntityMetadata entityMetadata) {

core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.geysermc.geyser.entity.type.BoatEntity;
4444
import org.geysermc.geyser.entity.type.Entity;
4545
import org.geysermc.geyser.entity.type.LivingEntity;
46+
import org.geysermc.geyser.input.InputLocksFlag;
4647
import org.geysermc.geyser.inventory.GeyserItemStack;
4748
import org.geysermc.geyser.item.Items;
4849
import org.geysermc.geyser.level.block.Blocks;
@@ -476,6 +477,10 @@ public void setVehicle(Entity entity) {
476477
this.vehicle.updateBedrockMetadata();
477478
}
478479

480+
// Bedrock player can dismount by pressing jump while Java cannot, so we need to prevent player from jumping to match vanilla behaviour.
481+
this.session.setLockInput(InputLocksFlag.JUMP, entity != null && entity.doesJumpDismount());
482+
this.session.updateInputLocks();
483+
479484
super.setVehicle(entity);
480485
}
481486

core/src/main/java/org/geysermc/geyser/impl/camera/GeyserCameraData.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.geysermc.geyser.api.bedrock.camera.CameraPosition;
4949
import org.geysermc.geyser.api.bedrock.camera.CameraShake;
5050
import org.geysermc.geyser.api.bedrock.camera.GuiElement;
51+
import org.geysermc.geyser.input.InputLocksFlag;
5152
import org.geysermc.geyser.session.GeyserSession;
5253
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
5354

@@ -251,7 +252,8 @@ public boolean lockCamera(boolean lock, @NonNull UUID owner) {
251252
this.cameraLockOwners.remove(owner);
252253
}
253254

254-
session.lockInputs(isCameraLocked(), session.entities().isMovementLocked());
255+
session.setLockInput(InputLocksFlag.CAMERA, isCameraLocked());
256+
session.updateInputLocks();
255257
return isCameraLocked();
256258
}
257259

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) 2025 GeyserMC. http://geysermc.org
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
* THE SOFTWARE.
21+
*
22+
* @author GeyserMC
23+
* @link https://github.com/GeyserMC/Geyser
24+
*/
25+
26+
package org.geysermc.geyser.input;
27+
28+
import lombok.RequiredArgsConstructor;
29+
import lombok.Getter;
30+
31+
// 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)
32+
@RequiredArgsConstructor
33+
@Getter
34+
public enum InputLocksFlag {
35+
RESET(0),
36+
CAMERA(2),
37+
MOVEMENT(4),
38+
LATERAL_MOVEMENT(16),
39+
SNEAK(32),
40+
JUMP(64),
41+
MOUNT(128),
42+
DISMOUNT(256),
43+
MOVE_FORWARD(512),
44+
MOVE_BACKWARD(1024),
45+
MOVE_LEFT(2048),
46+
MOVE_RIGHT(4096);
47+
48+
private final int offset;
49+
}

core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.cloudburstmc.math.vector.Vector3f;
3535
import org.cloudburstmc.math.vector.Vector3i;
3636
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
37+
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
3738
import org.cloudburstmc.protocol.bedrock.packet.UpdateClientInputLocksPacket;
3839
import org.geysermc.erosion.util.BlockPositionIterator;
3940
import org.geysermc.geyser.entity.EntityDefinitions;
@@ -220,12 +221,12 @@ public BoundingBox getActiveBoundingBox() {
220221

221222
public void recalculatePosition() {
222223
PlayerEntity entity = session.getPlayerEntity();
223-
224-
// This does the job and won't interrupt velocity + rotation.
225-
UpdateClientInputLocksPacket inputLocksPacket = new UpdateClientInputLocksPacket();
226-
inputLocksPacket.setLockComponentData(0); // Don't actually lock anything.
227-
inputLocksPacket.setServerPosition(entity.getPosition());
228-
session.sendUpstreamPacket(inputLocksPacket);
224+
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
225+
movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
226+
movePlayerPacket.setPosition(entity.getPosition());
227+
movePlayerPacket.setRotation(entity.getBedrockRotation());
228+
movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL);
229+
session.sendUpstreamPacket(movePlayerPacket);
229230
}
230231

231232
public BlockPositionIterator collidableBlocksIterator(BoundingBox box) {

core/src/main/java/org/geysermc/geyser/session/GeyserSession.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
import org.geysermc.geyser.GeyserImpl;
114114
import org.geysermc.geyser.api.bedrock.camera.CameraData;
115115
import org.geysermc.geyser.api.bedrock.camera.CameraShake;
116+
import org.geysermc.geyser.input.InputLocksFlag;
116117
import org.geysermc.geyser.api.connection.GeyserConnection;
117118
import org.geysermc.geyser.api.entity.EntityData;
118119
import org.geysermc.geyser.api.entity.type.GeyserEntity;
@@ -234,6 +235,7 @@
234235
import java.util.Arrays;
235236
import java.util.BitSet;
236237
import java.util.Collections;
238+
import java.util.EnumSet;
237239
import java.util.HashSet;
238240
import java.util.List;
239241
import java.util.Locale;
@@ -748,6 +750,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
748750
@Accessors(fluent = true)
749751
private boolean hasAcceptedCodeOfConduct = false;
750752

753+
private final Set<InputLocksFlag> inputLocksSet = EnumSet.noneOf(InputLocksFlag.class);
754+
private boolean inputLockDirty;
755+
751756
public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop tickEventLoop) {
752757
this.geyser = geyser;
753758
this.upstream = new UpstreamSession(bedrockServerSession);
@@ -2377,17 +2382,21 @@ public void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteI
23772382
entities().showEmote(emoter, emoteId);
23782383
}
23792384

2380-
public void lockInputs(boolean camera, boolean movement) {
2385+
public void setLockInput(InputLocksFlag flag, boolean value) {
2386+
this.inputLockDirty |= value ? this.inputLocksSet.add(flag) : this.inputLocksSet.remove(flag);
2387+
}
2388+
2389+
public void updateInputLocks() {
2390+
if (!this.inputLockDirty) {
2391+
return;
2392+
}
2393+
this.inputLockDirty = false;
2394+
23812395
UpdateClientInputLocksPacket packet = new UpdateClientInputLocksPacket();
2382-
final int cameraOffset = 1 << 1;
2383-
final int movementOffset = 1 << 2;
23842396

23852397
int result = 0;
2386-
if (camera) {
2387-
result |= cameraOffset;
2388-
}
2389-
if (movement) {
2390-
result |= movementOffset;
2398+
for (InputLocksFlag other : this.inputLocksSet) {
2399+
result |= other.getOffset();
23912400
}
23922401

23932402
packet.setLockComponentData(result);
@@ -2396,6 +2405,10 @@ public void lockInputs(boolean camera, boolean movement) {
23962405
sendUpstreamPacket(packet);
23972406
}
23982407

2408+
public boolean getLockedInput(InputLocksFlag flag) {
2409+
return this.inputLocksSet.contains(flag);
2410+
}
2411+
23992412
@Override
24002413
public @NonNull CameraData camera() {
24012414
return this.cameraData;

core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ public void processInputs(SessionPlayerEntity entity, PlayerAuthInputPacket pack
127127
// https://mojang.github.io/bedrock-protocol-docs/html/enums.html
128128
// using the "raw" values allows us sending key presses even with locked input
129129
// There appear to be cases where the raw value is not sent - e.g. sneaking with a shield on mobile (1.21.80)
130-
.withJump(bedrockInput.contains(PlayerAuthInputData.JUMP_CURRENT_RAW) || bedrockInput.contains(PlayerAuthInputData.JUMP_DOWN))
130+
// We also need to check for water auto jumping, since bedrock don't send jumping value in those cases.
131+
.withJump(bedrockInput.contains(PlayerAuthInputData.JUMP_CURRENT_RAW) || bedrockInput.contains(PlayerAuthInputData.JUMP_DOWN) || bedrockInput.contains(PlayerAuthInputData.AUTO_JUMPING_IN_WATER))
131132
.withShift(session.isShouldSendSneak() || sneaking)
132133
.withSprint(bedrockInput.contains(PlayerAuthInputData.SPRINT_DOWN));
133134

0 commit comments

Comments
 (0)