Skip to content

Commit 4d6592b

Browse files
Various improvements to movement, teleport, collisions (#5703)
* Initial work. * Fixed keep velocity desync. * More work. * More work. * Fix this comment. * Little oopsie. * Save player motion when updating rotation. * Implement ROTATE_DELTA. * More work. * Fixed void floor properly. * Always set isOnGround to false if near the void floor. * Fixed collision correction. * Also use recalculate position method for this one. * Make no clip void conditional. * Update core/src/main/java/org/geysermc/geyser/session/cache/TeleportCache.java Co-authored-by: chris <[email protected]> * Some changes. * Fix: Collision check, there's more than one bamboo/dripstone block state, and minor touchups * Use Math.toRadians. * Oops, make this compile. * Specify the teleportation cause. * Only specify the teleport cause if mode is teleport. --------- Co-authored-by: chris <[email protected]>
1 parent dc5fc8f commit 4d6592b

File tree

11 files changed

+231
-184
lines changed

11 files changed

+231
-184
lines changed

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -204,16 +204,15 @@ public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYa
204204
movePlayerPacket.setPosition(this.position);
205205
movePlayerPacket.setRotation(getBedrockRotation());
206206
movePlayerPacket.setOnGround(isOnGround);
207-
movePlayerPacket.setMode(teleported ? MovePlayerPacket.Mode.TELEPORT : MovePlayerPacket.Mode.NORMAL);
208-
209-
if (teleported) {
210-
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN);
207+
movePlayerPacket.setMode(this instanceof SessionPlayerEntity || teleported ? MovePlayerPacket.Mode.TELEPORT : MovePlayerPacket.Mode.NORMAL);
208+
if (movePlayerPacket.getMode() == MovePlayerPacket.Mode.TELEPORT) {
209+
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
211210
}
212211

213212
session.sendUpstreamPacket(movePlayerPacket);
214213

215-
if (teleported) {
216-
// As of 1.19.0, head yaw seems to be ignored during teleports.
214+
if (teleported && !(this instanceof SessionPlayerEntity)) {
215+
// As of 1.19.0, head yaw seems to be ignored during teleports, also don't do this for session player.
217216
updateHeadLookRotation(headYaw);
218217
}
219218

@@ -239,17 +238,21 @@ public void moveRelative(double relX, double relY, double relZ, float yaw, float
239238
movePlayerPacket.setPosition(position);
240239
movePlayerPacket.setRotation(getBedrockRotation());
241240
movePlayerPacket.setOnGround(isOnGround);
242-
movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL);
241+
movePlayerPacket.setMode(this instanceof SessionPlayerEntity ? MovePlayerPacket.Mode.TELEPORT : MovePlayerPacket.Mode.NORMAL);
243242
// If the player is moved while sleeping, we have to adjust their y, so it appears
244243
// correctly on Bedrock. This fixes GSit's lay.
245244
if (getFlag(EntityFlag.SLEEPING)) {
246245
if (bedPosition != null && (bedPosition.getY() == 0 || bedPosition.distanceSquared(position.toInt()) > 4)) {
247246
// Force the player movement by using a teleport
248247
movePlayerPacket.setPosition(Vector3f.from(position.getX(), position.getY() - definition.offset() + 0.2f, position.getZ()));
249248
movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT);
250-
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN);
251249
}
252250
}
251+
252+
if (movePlayerPacket.getMode() == MovePlayerPacket.Mode.TELEPORT) {
253+
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
254+
}
255+
253256
session.sendUpstreamPacket(movePlayerPacket);
254257
if (leftParrot != null) {
255258
leftParrot.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, true);

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

Lines changed: 17 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@
3636
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
3737
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
3838
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
39+
import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket;
3940
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
4041
import org.geysermc.geyser.entity.EntityDefinitions;
4142
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
4243
import org.geysermc.geyser.entity.type.BoatEntity;
4344
import org.geysermc.geyser.entity.type.Entity;
4445
import org.geysermc.geyser.inventory.GeyserItemStack;
4546
import org.geysermc.geyser.item.Items;
46-
import org.geysermc.geyser.level.BedrockDimension;
4747
import org.geysermc.geyser.level.block.Blocks;
4848
import org.geysermc.geyser.level.block.property.Properties;
4949
import org.geysermc.geyser.level.block.type.BlockState;
@@ -117,15 +117,6 @@ public class SessionPlayerEntity extends PlayerEntity {
117117
@Getter @Setter
118118
private Vector2f bedrockInteractRotation = Vector2f.ZERO;
119119

120-
/**
121-
* Determines if our position is currently out-of-sync with the Java server
122-
* due to our workaround for the void floor
123-
* <p>
124-
* Must be reset when dying, switching worlds, or being teleported out of the void
125-
*/
126-
@Getter @Setter
127-
private boolean voidPositionDesynched;
128-
129120
public SessionPlayerEntity(GeyserSession session) {
130121
super(session, -1, 1, null, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, null, null);
131122

@@ -152,29 +143,18 @@ public void spawnEntity() {
152143

153144
@Override
154145
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) {
155-
if (voidPositionDesynched) {
156-
if (!isBelowVoidFloor()) {
157-
voidPositionDesynched = false; // No need to fix our offset; we've been moved
158-
}
159-
}
160146
super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround);
161147
session.getCollisionManager().updatePlayerBoundingBox(this.position.down(definition.offset()));
162148
}
163149

164-
@Override
165-
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
166-
if (voidPositionDesynched) {
167-
if (!isBelowVoidFloor()) {
168-
voidPositionDesynched = false; // No need to fix our offset; we've been moved
169-
}
170-
}
171-
super.moveAbsolute(position, yaw, pitch, headYaw, isOnGround, teleported);
172-
}
173-
174150
@Override
175151
public void setPosition(Vector3f position) {
176152
if (valid) { // Don't update during session init
177153
session.getCollisionManager().updatePlayerBoundingBox(position);
154+
155+
if (session.isNoClip() && position.getY() >= session.getBedrockDimension().minY() - 5) {
156+
session.setNoClip(false);
157+
}
178158
}
179159
this.position = position.add(0, definition.offset(), 0);
180160
}
@@ -200,6 +180,12 @@ public void updateOwnRotation(float yaw, float pitch, float headYaw) {
200180
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
201181

202182
session.sendUpstreamPacket(movePlayerPacket);
183+
184+
// We're just setting rotation, player shouldn't lose motion, send motion packet to account for that.
185+
SetEntityMotionPacket entityMotionPacket = new SetEntityMotionPacket();
186+
entityMotionPacket.setRuntimeEntityId(geyserId);
187+
entityMotionPacket.setMotion(motion);
188+
session.sendUpstreamPacket(entityMotionPacket);
203189
}
204190

205191
/**
@@ -211,6 +197,11 @@ public void updateOwnRotation(float yaw, float pitch, float headYaw) {
211197
*/
212198
public void setPositionManual(Vector3f position) {
213199
this.position = position;
200+
201+
// Player is "above" the void so they're not supposed to no clip.
202+
if (session.isNoClip() && position.getY() - EntityDefinitions.PLAYER.offset() >= session.getBedrockDimension().minY() - 5) {
203+
session.setNoClip(false);
204+
}
214205
}
215206

216207
/**
@@ -360,9 +351,6 @@ public void setLastDeathPosition(@Nullable GlobalPos pos) {
360351
} else {
361352
dirtyMetadata.put(EntityDataTypes.PLAYER_HAS_DIED, false);
362353
}
363-
364-
// We're either respawning or switching worlds, either way, we are no longer desynched
365-
this.setVoidPositionDesynched(false);
366354
}
367355

368356
@Override
@@ -448,51 +436,7 @@ public void setVehicle(Entity entity) {
448436

449437
super.setVehicle(entity);
450438
}
451-
452-
private boolean isBelowVoidFloor() {
453-
return position.getY() < voidFloorPosition();
454-
}
455-
456-
public int voidFloorPosition() {
457-
// The void floor is offset about 40 blocks below the bottom of the world
458-
BedrockDimension bedrockDimension = session.getBedrockDimension();
459-
return bedrockDimension.minY() - 40;
460-
}
461-
462-
/**
463-
* This method handles teleporting the player below or above the Bedrock void floor.
464-
* The Java server should never see this desync as we adjust the position that we send to it
465-
*
466-
* @param up in which direction to teleport - true to resync our position, or false to be
467-
* teleported below the void floor.
468-
*/
469-
public void teleportVoidFloorFix(boolean up) {
470-
// Safety to avoid double teleports
471-
if ((voidPositionDesynched && !up) || (!voidPositionDesynched && up)) {
472-
return;
473-
}
474-
475-
// Work around there being a floor at the bottom of the world and teleport the player below it
476-
// Moving from below to above the void floor works fine
477-
Vector3f newPosition = this.getPosition();
478-
if (up) {
479-
newPosition = newPosition.up(4f);
480-
voidPositionDesynched = false;
481-
} else {
482-
newPosition = newPosition.down(4f);
483-
voidPositionDesynched = true;
484-
}
485-
486-
this.setPositionManual(newPosition);
487-
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
488-
movePlayerPacket.setRuntimeEntityId(geyserId);
489-
movePlayerPacket.setPosition(newPosition);
490-
movePlayerPacket.setRotation(getBedrockRotation());
491-
movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT);
492-
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
493-
session.sendUpstreamPacketImmediately(movePlayerPacket);
494-
}
495-
439+
496440
/**
497441
* Used to calculate player jumping velocity for ground status calculation.
498442
*/

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

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +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;
37+
import org.cloudburstmc.protocol.bedrock.packet.UpdateClientInputLocksPacket;
3838
import org.geysermc.erosion.util.BlockPositionIterator;
3939
import org.geysermc.geyser.entity.EntityDefinitions;
4040
import org.geysermc.geyser.entity.type.player.PlayerEntity;
@@ -166,7 +166,7 @@ public BoundingBox getActiveBoundingBox() {
166166
}
167167
// We need to parse the float as a string since casting a float to a double causes us to
168168
// lose precision and thus, causes players to get stuck when walking near walls
169-
double javaY = bedrockPosition.getY() - EntityDefinitions.PLAYER.offset();
169+
double javaY = Double.parseDouble(Float.toString(bedrockPosition.getY() - EntityDefinitions.PLAYER.offset()));
170170

171171
Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY,
172172
Double.parseDouble(Float.toString(bedrockPosition.getZ())));
@@ -197,18 +197,19 @@ public BoundingBox getActiveBoundingBox() {
197197
return null;
198198
}
199199

200-
position = playerBoundingBox.getBottomCenter();
201-
202200
boolean newOnGround = adjustedMovement.getY() != movement.getY() && movement.getY() < 0 || onGround;
203201
// Send corrected position to Bedrock if they differ by too much to prevent de-syncs
204-
if (onGround != newOnGround || movement.distanceSquared(adjustedMovement) > INCORRECT_MOVEMENT_THRESHOLD) {
202+
if (onGround != newOnGround || position.distanceSquared(playerBoundingBox.getBottomCenter()) > INCORRECT_MOVEMENT_THRESHOLD) {
205203
PlayerEntity playerEntity = session.getPlayerEntity();
206204
// Client will dismount if on a vehicle
207205
if (playerEntity.getVehicle() == null && pistonCache.getPlayerMotion().equals(Vector3f.ZERO) && !pistonCache.isPlayerSlimeCollision()) {
208-
playerEntity.moveAbsolute(position.toFloat(), playerEntity.getYaw(), playerEntity.getPitch(), playerEntity.getHeadYaw(), onGround, true);
206+
recalculatePosition();
207+
return null;
209208
}
210209
}
211210

211+
position = playerBoundingBox.getBottomCenter();
212+
212213
if (!newOnGround) {
213214
// Trim the position to prevent rounding errors that make Java think we are clipping into a block
214215
position = Vector3d.from(position.getX(), Double.parseDouble(DECIMAL_FORMAT.format(position.getY())), position.getZ());
@@ -217,18 +218,14 @@ public BoundingBox getActiveBoundingBox() {
217218
return new CollisionResult(position, TriState.byBoolean(onGround));
218219
}
219220

220-
// TODO: This makes the player look upwards for some reason, rotation values must be wrong
221221
public void recalculatePosition() {
222222
PlayerEntity entity = session.getPlayerEntity();
223-
// Gravity might need to be reset...
224-
entity.updateBedrockMetadata(); // TODO may not be necessary
225-
226-
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
227-
movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
228-
movePlayerPacket.setPosition(entity.getPosition());
229-
movePlayerPacket.setRotation(entity.getBedrockRotation());
230-
movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL);
231-
session.sendUpstreamPacket(movePlayerPacket);
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);
232229
}
233230

234231
public BlockPositionIterator collidableBlocksIterator(BoundingBox box) {
@@ -280,7 +277,15 @@ public boolean correctPlayerPosition() {
280277

281278
// Main correction code
282279
for (iter.reset(); iter.hasNext(); iter.next()) {
283-
BlockCollision blockCollision = BlockUtils.getCollision(blocks[iter.getIteration()]);
280+
final int blockId = blocks[iter.getIteration()];
281+
282+
// These block have different offset between BE and JE so we ignore them because if we "correct" the position
283+
// it will lead to complication and more inaccurate movement.
284+
if (session.getBlockMappings().getCollisionIgnoredBlocks().contains(blockId)) {
285+
continue;
286+
}
287+
288+
BlockCollision blockCollision = BlockUtils.getCollision(blockId);
284289
if (blockCollision != null) {
285290
if (!blockCollision.correctPosition(session, iter.getX(), iter.getY(), iter.getZ(), playerBoundingBox)) {
286291
return false;

core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.google.common.collect.Interners;
3333
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
3434
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
35+
import it.unimi.dsi.fastutil.ints.IntArrayList;
3536
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
3637
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
3738
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
@@ -243,6 +244,7 @@ private static void registerBedrockBlocks() {
243244
.toList();
244245
Map<Block, NbtMap> flowerPotBlocks = new Object2ObjectOpenHashMap<>();
245246
Map<NbtMap, BlockDefinition> itemFrames = new Object2ObjectOpenHashMap<>();
247+
IntArrayList collisionIgnoredBlocks = new IntArrayList();
246248

247249
Set<BlockDefinition> jigsawDefinitions = new ObjectOpenHashSet<>();
248250
Map<String, BlockDefinition> structureBlockDefinitions = new Object2ObjectOpenHashMap<>();
@@ -308,6 +310,10 @@ private static void registerBedrockBlocks() {
308310
netherPortalBlockDefinition = bedrockDefinition;
309311
}
310312

313+
if (block == Blocks.BAMBOO || block == Blocks.POINTED_DRIPSTONE) {
314+
collisionIgnoredBlocks.add(javaRuntimeId);
315+
}
316+
311317
boolean waterlogged = blockState.getValue(Properties.WATERLOGGED, false)
312318
|| block == Blocks.BUBBLE_COLUMN || block == Blocks.KELP || block == Blocks.KELP_PLANT
313319
|| block == Blocks.SEAGRASS || block == Blocks.TALL_SEAGRASS;
@@ -326,6 +332,8 @@ private static void registerBedrockBlocks() {
326332
javaToBedrockBlocks[javaRuntimeId] = bedrockDefinition;
327333
}
328334

335+
builder.collisionIgnoredBlocks(collisionIgnoredBlocks);
336+
329337
if (commandBlockDefinition == null) {
330338
throw new AssertionError("Unable to find command block in palette");
331339
}

core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
package org.geysermc.geyser.registry.type;
2727

2828
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
29+
import it.unimi.dsi.fastutil.ints.IntArrayList;
2930
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
3031
import lombok.Builder;
3132
import lombok.Value;
@@ -66,6 +67,8 @@ public class BlockMappings implements DefinitionRegistry<BlockDefinition> {
6667
BlockDefinition mobSpawnerBlock;
6768
BlockDefinition netherPortalBlock;
6869

70+
IntArrayList collisionIgnoredBlocks;
71+
6972
Map<NbtMap, BlockDefinition> itemFrames;
7073
Map<Block, NbtMap> flowerPotBlocks;
7174

0 commit comments

Comments
 (0)