2727
2828import it .unimi .dsi .fastutil .objects .Object2ObjectOpenHashMap ;
2929import lombok .Getter ;
30+ import lombok .Setter ;
3031import org .checkerframework .checker .nullness .qual .Nullable ;
3132import org .cloudburstmc .math .vector .Vector3f ;
3233import org .cloudburstmc .protocol .bedrock .data .AttributeData ;
3334import org .cloudburstmc .protocol .bedrock .data .entity .EntityDataTypes ;
3435import org .cloudburstmc .protocol .bedrock .data .entity .EntityFlag ;
36+ import org .cloudburstmc .protocol .bedrock .packet .MovePlayerPacket ;
3537import org .cloudburstmc .protocol .bedrock .packet .UpdateAttributesPacket ;
3638import org .geysermc .geyser .entity .attribute .GeyserAttributeType ;
3739import org .geysermc .geyser .item .Items ;
3840import org .geysermc .geyser .network .GameProtocol ;
41+ import org .geysermc .geyser .level .BedrockDimension ;
3942import org .geysermc .geyser .session .GeyserSession ;
4043import org .geysermc .geyser .util .AttributeUtils ;
4144import 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}
0 commit comments