Skip to content

Commit 1104707

Browse files
Fix: Bedrock players dying of fall damage, instead of falling in the void (#4704)
* fix #4649 * add javadoc
1 parent c00a02e commit 1104707

File tree

2 files changed

+106
-27
lines changed

2 files changed

+106
-27
lines changed

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

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,18 @@
2727

2828
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
2929
import lombok.Getter;
30+
import lombok.Setter;
3031
import org.checkerframework.checker.nullness.qual.Nullable;
3132
import org.cloudburstmc.math.vector.Vector3f;
3233
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
3334
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
3435
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
36+
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
3537
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
3638
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
3739
import org.geysermc.geyser.item.Items;
3840
import org.geysermc.geyser.network.GameProtocol;
41+
import org.geysermc.geyser.level.BedrockDimension;
3942
import org.geysermc.geyser.session.GeyserSession;
4043
import org.geysermc.geyser.util.AttributeUtils;
4144
import org.geysermc.geyser.util.DimensionUtils;
@@ -69,6 +72,15 @@ public class SessionPlayerEntity extends PlayerEntity {
6972

7073
private int lastAirSupply = getMaxAir();
7174

75+
/**
76+
* Determines if our position is currently out-of-sync with the Java server
77+
* due to our workaround for the void floor
78+
* <p>
79+
* Must be reset when dying, switching worlds, or being teleported out of the void
80+
*/
81+
@Getter @Setter
82+
private boolean voidPositionDesynched;
83+
7284
public SessionPlayerEntity(GeyserSession session) {
7385
super(session, -1, 1, null, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, null, null);
7486

@@ -87,10 +99,25 @@ public void spawnEntity() {
8799

88100
@Override
89101
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) {
102+
if (voidPositionDesynched) {
103+
if (!isBelowVoidFloor()) {
104+
voidPositionDesynched = false; // No need to fix our offset; we've been moved
105+
}
106+
}
90107
super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround);
91108
session.getCollisionManager().updatePlayerBoundingBox(this.position.down(definition.offset()));
92109
}
93110

111+
@Override
112+
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
113+
if (voidPositionDesynched) {
114+
if (!isBelowVoidFloor()) {
115+
voidPositionDesynched = false; // No need to fix our offset; we've been moved
116+
}
117+
}
118+
super.moveAbsolute(position, yaw, pitch, headYaw, isOnGround, teleported);
119+
}
120+
94121
@Override
95122
public void setPosition(Vector3f position) {
96123
if (valid) { // Don't update during session init
@@ -225,6 +252,9 @@ public void setLastDeathPosition(@Nullable GlobalPos pos) {
225252
} else {
226253
dirtyMetadata.put(EntityDataTypes.PLAYER_HAS_DIED, false);
227254
}
255+
256+
// We're either respawning or switching worlds, either way, we are no longer desynched
257+
this.setVoidPositionDesynched(false);
228258
}
229259

230260
@Override
@@ -276,4 +306,48 @@ public void resetAttributes() {
276306
public void resetAir() {
277307
this.setAirSupply(getMaxAir());
278308
}
309+
310+
private boolean isBelowVoidFloor() {
311+
return position.getY() < voidFloorPosition();
312+
}
313+
314+
public int voidFloorPosition() {
315+
// The void floor is offset about 40 blocks below the bottom of the world
316+
BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension();
317+
return bedrockDimension.minY() - 40;
318+
}
319+
320+
/**
321+
* This method handles teleporting the player below or above the Bedrock void floor.
322+
* The Java server should never see this desync as we adjust the position that we send to it
323+
*
324+
* @param up in which direction to teleport - true to resync our position, or false to be
325+
* teleported below the void floor.
326+
*/
327+
public void teleportVoidFloorFix(boolean up) {
328+
// Safety to avoid double teleports
329+
if ((voidPositionDesynched && !up) || (!voidPositionDesynched && up)) {
330+
return;
331+
}
332+
333+
// Work around there being a floor at the bottom of the world and teleport the player below it
334+
// Moving from below to above the void floor works fine
335+
Vector3f newPosition = this.getPosition();
336+
if (up) {
337+
newPosition = newPosition.up(4f);
338+
voidPositionDesynched = false;
339+
} else {
340+
newPosition = newPosition.down(4f);
341+
voidPositionDesynched = true;
342+
}
343+
344+
this.setPositionManual(newPosition);
345+
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
346+
movePlayerPacket.setRuntimeEntityId(geyserId);
347+
movePlayerPacket.setPosition(newPosition);
348+
movePlayerPacket.setRotation(getBedrockRotation());
349+
movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT);
350+
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
351+
session.sendUpstreamPacketImmediately(movePlayerPacket);
352+
}
279353
}

core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockMovePlayerTranslator.java

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,19 @@
2525

2626
package org.geysermc.geyser.translator.protocol.bedrock.entity.player;
2727

28-
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket;
29-
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosRotPacket;
30-
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerRotPacket;
31-
import org.geysermc.mcprotocollib.network.packet.Packet;
3228
import org.cloudburstmc.math.vector.Vector3d;
3329
import org.cloudburstmc.math.vector.Vector3f;
3430
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
3531
import org.geysermc.geyser.entity.EntityDefinitions;
3632
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
37-
import org.geysermc.geyser.level.BedrockDimension;
3833
import org.geysermc.geyser.session.GeyserSession;
3934
import org.geysermc.geyser.text.ChatColor;
4035
import org.geysermc.geyser.translator.protocol.PacketTranslator;
4136
import org.geysermc.geyser.translator.protocol.Translator;
37+
import org.geysermc.mcprotocollib.network.packet.Packet;
38+
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket;
39+
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosRotPacket;
40+
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerRotPacket;
4241

4342
@Translator(packet = MovePlayerPacket.class)
4443
public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPacket> {
@@ -93,37 +92,50 @@ public void translate(GeyserSession session, MovePlayerPacket packet) {
9392
Vector3d position = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.isOnGround(), packet.getMode() == MovePlayerPacket.Mode.TELEPORT);
9493
if (position != null) { // A null return value cancels the packet
9594
boolean onGround = packet.isOnGround();
95+
boolean isBelowVoid = entity.isVoidPositionDesynched();
9696

97-
boolean teleportThroughVoidFloor;
97+
boolean teleportThroughVoidFloor, mustResyncPosition;
9898
// Compare positions here for void floor fix below before the player's position variable is set to the packet position
99-
if (entity.getPosition().getY() >= packet.getPosition().getY()) {
99+
if (entity.getPosition().getY() >= packet.getPosition().getY() && !isBelowVoid) {
100100
int floorY = position.getFloorY();
101-
// The void floor is offset about 40 blocks below the bottom of the world
102-
BedrockDimension bedrockDimension = session.getChunkCache().getBedrockDimension();
103-
int voidFloorLocation = bedrockDimension.minY() - 40;
104-
teleportThroughVoidFloor = floorY <= (voidFloorLocation + 2) && floorY >= voidFloorLocation;
105-
if (teleportThroughVoidFloor) {
106-
// https://github.com/GeyserMC/Geyser/issues/3521 - no void floor in Java so we cannot be on the ground.
107-
onGround = false;
108-
}
101+
int voidFloorLocation = entity.voidFloorPosition();
102+
teleportThroughVoidFloor = floorY <= (voidFloorLocation + 1) && floorY >= voidFloorLocation;
109103
} else {
110104
teleportThroughVoidFloor = false;
111105
}
112106

107+
if (teleportThroughVoidFloor || isBelowVoid) {
108+
// https://github.com/GeyserMC/Geyser/issues/3521 - no void floor in Java so we cannot be on the ground.
109+
onGround = false;
110+
}
111+
112+
if (isBelowVoid) {
113+
int floorY = position.getFloorY();
114+
int voidFloorLocation = entity.voidFloorPosition();
115+
mustResyncPosition = floorY < voidFloorLocation && floorY >= voidFloorLocation - 1;
116+
} else {
117+
mustResyncPosition = false;
118+
}
119+
120+
double yPosition = position.getY();
121+
if (entity.isVoidPositionDesynched()) { // not using the cached variable on purpose
122+
yPosition += 4; // We are de-synched since we had to teleport below the void floor.
123+
}
124+
113125
Packet movePacket;
114126
if (rotationChanged) {
115127
// Send rotation updates as well
116128
movePacket = new ServerboundMovePlayerPosRotPacket(
117129
onGround,
118-
position.getX(), position.getY(), position.getZ(),
130+
position.getX(), yPosition, position.getZ(),
119131
yaw, pitch
120132
);
121133
entity.setYaw(yaw);
122134
entity.setPitch(pitch);
123135
entity.setHeadYaw(headYaw);
124136
} else {
125137
// Rotation did not change; don't send an update with rotation
126-
movePacket = new ServerboundMovePlayerPosPacket(onGround, position.getX(), position.getY(), position.getZ());
138+
movePacket = new ServerboundMovePlayerPosPacket(onGround, position.getX(), yPosition, position.getZ());
127139
}
128140

129141
entity.setPositionManual(packet.getPosition());
@@ -133,16 +145,9 @@ public void translate(GeyserSession session, MovePlayerPacket packet) {
133145
session.sendDownstreamGamePacket(movePacket);
134146

135147
if (teleportThroughVoidFloor) {
136-
// Work around there being a floor at the bottom of the world and teleport the player below it
137-
// Moving from below to above the void floor works fine
138-
entity.setPosition(entity.getPosition().sub(0, 4f, 0));
139-
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
140-
movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
141-
movePlayerPacket.setPosition(entity.getPosition());
142-
movePlayerPacket.setRotation(entity.getBedrockRotation());
143-
movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT);
144-
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
145-
session.sendUpstreamPacket(movePlayerPacket);
148+
entity.teleportVoidFloorFix(false);
149+
} else if (mustResyncPosition) {
150+
entity.teleportVoidFloorFix(true);
146151
}
147152

148153
session.getSkullCache().updateVisibleSkulls();

0 commit comments

Comments
 (0)