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

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 <github@onechris.mozmail.com>

* 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 <github@onechris.mozmail.com>
This commit is contained in:
oryxel
2025-08-20 16:03:50 +07:00
committed by GitHub
parent dc5fc8f54c
commit 4d6592b30c
11 changed files with 234 additions and 187 deletions

View File

@@ -204,16 +204,15 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
movePlayerPacket.setPosition(this.position); movePlayerPacket.setPosition(this.position);
movePlayerPacket.setRotation(getBedrockRotation()); movePlayerPacket.setRotation(getBedrockRotation());
movePlayerPacket.setOnGround(isOnGround); movePlayerPacket.setOnGround(isOnGround);
movePlayerPacket.setMode(teleported ? MovePlayerPacket.Mode.TELEPORT : MovePlayerPacket.Mode.NORMAL); movePlayerPacket.setMode(this instanceof SessionPlayerEntity || teleported ? MovePlayerPacket.Mode.TELEPORT : MovePlayerPacket.Mode.NORMAL);
if (movePlayerPacket.getMode() == MovePlayerPacket.Mode.TELEPORT) {
if (teleported) { movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN);
} }
session.sendUpstreamPacket(movePlayerPacket); session.sendUpstreamPacket(movePlayerPacket);
if (teleported) { if (teleported && !(this instanceof SessionPlayerEntity)) {
// As of 1.19.0, head yaw seems to be ignored during teleports. // As of 1.19.0, head yaw seems to be ignored during teleports, also don't do this for session player.
updateHeadLookRotation(headYaw); updateHeadLookRotation(headYaw);
} }
@@ -239,7 +238,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
movePlayerPacket.setPosition(position); movePlayerPacket.setPosition(position);
movePlayerPacket.setRotation(getBedrockRotation()); movePlayerPacket.setRotation(getBedrockRotation());
movePlayerPacket.setOnGround(isOnGround); movePlayerPacket.setOnGround(isOnGround);
movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL); movePlayerPacket.setMode(this instanceof SessionPlayerEntity ? MovePlayerPacket.Mode.TELEPORT : MovePlayerPacket.Mode.NORMAL);
// If the player is moved while sleeping, we have to adjust their y, so it appears // If the player is moved while sleeping, we have to adjust their y, so it appears
// correctly on Bedrock. This fixes GSit's lay. // correctly on Bedrock. This fixes GSit's lay.
if (getFlag(EntityFlag.SLEEPING)) { if (getFlag(EntityFlag.SLEEPING)) {
@@ -247,9 +246,13 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
// Force the player movement by using a teleport // Force the player movement by using a teleport
movePlayerPacket.setPosition(Vector3f.from(position.getX(), position.getY() - definition.offset() + 0.2f, position.getZ())); movePlayerPacket.setPosition(Vector3f.from(position.getX(), position.getY() - definition.offset() + 0.2f, position.getZ()));
movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT); movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT);
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN);
} }
} }
if (movePlayerPacket.getMode() == MovePlayerPacket.Mode.TELEPORT) {
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
}
session.sendUpstreamPacket(movePlayerPacket); session.sendUpstreamPacket(movePlayerPacket);
if (leftParrot != null) { if (leftParrot != null) {
leftParrot.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, true); leftParrot.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, true);

View File

@@ -36,6 +36,7 @@ import org.cloudburstmc.protocol.bedrock.data.AttributeData;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
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.MovePlayerPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
@@ -43,7 +44,6 @@ 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.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.BedrockDimension;
import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.property.Properties; import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.level.block.type.BlockState; import org.geysermc.geyser.level.block.type.BlockState;
@@ -117,15 +117,6 @@ public class SessionPlayerEntity extends PlayerEntity {
@Getter @Setter @Getter @Setter
private Vector2f bedrockInteractRotation = Vector2f.ZERO; private Vector2f bedrockInteractRotation = Vector2f.ZERO;
/**
* Determines if our position is currently out-of-sync with the Java server
* due to our workaround for the void floor
* <p>
* Must be reset when dying, switching worlds, or being teleported out of the void
*/
@Getter @Setter
private boolean voidPositionDesynched;
public SessionPlayerEntity(GeyserSession session) { public SessionPlayerEntity(GeyserSession session) {
super(session, -1, 1, null, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, null, null); super(session, -1, 1, null, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, null, null);
@@ -152,29 +143,18 @@ public class SessionPlayerEntity extends PlayerEntity {
@Override @Override
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) { public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) {
if (voidPositionDesynched) {
if (!isBelowVoidFloor()) {
voidPositionDesynched = false; // No need to fix our offset; we've been moved
}
}
super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround); super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround);
session.getCollisionManager().updatePlayerBoundingBox(this.position.down(definition.offset())); session.getCollisionManager().updatePlayerBoundingBox(this.position.down(definition.offset()));
} }
@Override
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
if (voidPositionDesynched) {
if (!isBelowVoidFloor()) {
voidPositionDesynched = false; // No need to fix our offset; we've been moved
}
}
super.moveAbsolute(position, yaw, pitch, headYaw, isOnGround, teleported);
}
@Override @Override
public void setPosition(Vector3f position) { public void setPosition(Vector3f position) {
if (valid) { // Don't update during session init if (valid) { // Don't update during session init
session.getCollisionManager().updatePlayerBoundingBox(position); session.getCollisionManager().updatePlayerBoundingBox(position);
if (session.isNoClip() && position.getY() >= session.getBedrockDimension().minY() - 5) {
session.setNoClip(false);
}
} }
this.position = position.add(0, definition.offset(), 0); this.position = position.add(0, definition.offset(), 0);
} }
@@ -200,6 +180,12 @@ public class SessionPlayerEntity extends PlayerEntity {
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR); movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
session.sendUpstreamPacket(movePlayerPacket); session.sendUpstreamPacket(movePlayerPacket);
// We're just setting rotation, player shouldn't lose motion, send motion packet to account for that.
SetEntityMotionPacket entityMotionPacket = new SetEntityMotionPacket();
entityMotionPacket.setRuntimeEntityId(geyserId);
entityMotionPacket.setMotion(motion);
session.sendUpstreamPacket(entityMotionPacket);
} }
/** /**
@@ -211,6 +197,11 @@ public class SessionPlayerEntity extends PlayerEntity {
*/ */
public void setPositionManual(Vector3f position) { public void setPositionManual(Vector3f position) {
this.position = position; this.position = position;
// Player is "above" the void so they're not supposed to no clip.
if (session.isNoClip() && position.getY() - EntityDefinitions.PLAYER.offset() >= session.getBedrockDimension().minY() - 5) {
session.setNoClip(false);
}
} }
/** /**
@@ -360,9 +351,6 @@ public class SessionPlayerEntity extends PlayerEntity {
} else { } else {
dirtyMetadata.put(EntityDataTypes.PLAYER_HAS_DIED, false); dirtyMetadata.put(EntityDataTypes.PLAYER_HAS_DIED, false);
} }
// We're either respawning or switching worlds, either way, we are no longer desynched
this.setVoidPositionDesynched(false);
} }
@Override @Override
@@ -448,51 +436,7 @@ public class SessionPlayerEntity extends PlayerEntity {
super.setVehicle(entity); super.setVehicle(entity);
} }
private boolean isBelowVoidFloor() {
return position.getY() < voidFloorPosition();
}
public int voidFloorPosition() {
// The void floor is offset about 40 blocks below the bottom of the world
BedrockDimension bedrockDimension = session.getBedrockDimension();
return bedrockDimension.minY() - 40;
}
/**
* This method handles teleporting the player below or above the Bedrock void floor.
* The Java server should never see this desync as we adjust the position that we send to it
*
* @param up in which direction to teleport - true to resync our position, or false to be
* teleported below the void floor.
*/
public void teleportVoidFloorFix(boolean up) {
// Safety to avoid double teleports
if ((voidPositionDesynched && !up) || (!voidPositionDesynched && up)) {
return;
}
// Work around there being a floor at the bottom of the world and teleport the player below it
// Moving from below to above the void floor works fine
Vector3f newPosition = this.getPosition();
if (up) {
newPosition = newPosition.up(4f);
voidPositionDesynched = false;
} else {
newPosition = newPosition.down(4f);
voidPositionDesynched = true;
}
this.setPositionManual(newPosition);
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
movePlayerPacket.setRuntimeEntityId(geyserId);
movePlayerPacket.setPosition(newPosition);
movePlayerPacket.setRotation(getBedrockRotation());
movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT);
movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR);
session.sendUpstreamPacketImmediately(movePlayerPacket);
}
/** /**
* Used to calculate player jumping velocity for ground status calculation. * Used to calculate player jumping velocity for ground status calculation.
*/ */

View File

@@ -34,7 +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.geysermc.erosion.util.BlockPositionIterator; import org.geysermc.erosion.util.BlockPositionIterator;
import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.entity.type.player.PlayerEntity;
@@ -166,7 +166,7 @@ public class CollisionManager {
} }
// We need to parse the float as a string since casting a float to a double causes us to // We need to parse the float as a string since casting a float to a double causes us to
// lose precision and thus, causes players to get stuck when walking near walls // lose precision and thus, causes players to get stuck when walking near walls
double javaY = bedrockPosition.getY() - EntityDefinitions.PLAYER.offset(); double javaY = Double.parseDouble(Float.toString(bedrockPosition.getY() - EntityDefinitions.PLAYER.offset()));
Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY, Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY,
Double.parseDouble(Float.toString(bedrockPosition.getZ()))); Double.parseDouble(Float.toString(bedrockPosition.getZ())));
@@ -197,18 +197,19 @@ public class CollisionManager {
return null; return null;
} }
position = playerBoundingBox.getBottomCenter();
boolean newOnGround = adjustedMovement.getY() != movement.getY() && movement.getY() < 0 || onGround; boolean newOnGround = adjustedMovement.getY() != movement.getY() && movement.getY() < 0 || onGround;
// Send corrected position to Bedrock if they differ by too much to prevent de-syncs // Send corrected position to Bedrock if they differ by too much to prevent de-syncs
if (onGround != newOnGround || movement.distanceSquared(adjustedMovement) > INCORRECT_MOVEMENT_THRESHOLD) { if (onGround != newOnGround || position.distanceSquared(playerBoundingBox.getBottomCenter()) > INCORRECT_MOVEMENT_THRESHOLD) {
PlayerEntity playerEntity = session.getPlayerEntity(); PlayerEntity playerEntity = session.getPlayerEntity();
// Client will dismount if on a vehicle // Client will dismount if on a vehicle
if (playerEntity.getVehicle() == null && pistonCache.getPlayerMotion().equals(Vector3f.ZERO) && !pistonCache.isPlayerSlimeCollision()) { if (playerEntity.getVehicle() == null && pistonCache.getPlayerMotion().equals(Vector3f.ZERO) && !pistonCache.isPlayerSlimeCollision()) {
playerEntity.moveAbsolute(position.toFloat(), playerEntity.getYaw(), playerEntity.getPitch(), playerEntity.getHeadYaw(), onGround, true); recalculatePosition();
return null;
} }
} }
position = playerBoundingBox.getBottomCenter();
if (!newOnGround) { if (!newOnGround) {
// Trim the position to prevent rounding errors that make Java think we are clipping into a block // Trim the position to prevent rounding errors that make Java think we are clipping into a block
position = Vector3d.from(position.getX(), Double.parseDouble(DECIMAL_FORMAT.format(position.getY())), position.getZ()); position = Vector3d.from(position.getX(), Double.parseDouble(DECIMAL_FORMAT.format(position.getY())), position.getZ());
@@ -217,18 +218,14 @@ public class CollisionManager {
return new CollisionResult(position, TriState.byBoolean(onGround)); return new CollisionResult(position, TriState.byBoolean(onGround));
} }
// TODO: This makes the player look upwards for some reason, rotation values must be wrong
public void recalculatePosition() { public void recalculatePosition() {
PlayerEntity entity = session.getPlayerEntity(); PlayerEntity entity = session.getPlayerEntity();
// Gravity might need to be reset...
entity.updateBedrockMetadata(); // TODO may not be necessary
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) {
@@ -280,7 +277,15 @@ public class CollisionManager {
// Main correction code // Main correction code
for (iter.reset(); iter.hasNext(); iter.next()) { for (iter.reset(); iter.hasNext(); iter.next()) {
BlockCollision blockCollision = BlockUtils.getCollision(blocks[iter.getIteration()]); final int blockId = blocks[iter.getIteration()];
// These block have different offset between BE and JE so we ignore them because if we "correct" the position
// it will lead to complication and more inaccurate movement.
if (session.getBlockMappings().getCollisionIgnoredBlocks().contains(blockId)) {
continue;
}
BlockCollision blockCollision = BlockUtils.getCollision(blockId);
if (blockCollision != null) { if (blockCollision != null) {
if (!blockCollision.correctPosition(session, iter.getX(), iter.getY(), iter.getZ(), playerBoundingBox)) { if (!blockCollision.correctPosition(session, iter.getX(), iter.getY(), iter.getZ(), playerBoundingBox)) {
return false; return false;

View File

@@ -32,6 +32,7 @@ import com.google.common.collect.Interner;
import com.google.common.collect.Interners; import com.google.common.collect.Interners;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
@@ -243,6 +244,7 @@ public final class BlockRegistryPopulator {
.toList(); .toList();
Map<Block, NbtMap> flowerPotBlocks = new Object2ObjectOpenHashMap<>(); Map<Block, NbtMap> flowerPotBlocks = new Object2ObjectOpenHashMap<>();
Map<NbtMap, BlockDefinition> itemFrames = new Object2ObjectOpenHashMap<>(); Map<NbtMap, BlockDefinition> itemFrames = new Object2ObjectOpenHashMap<>();
IntArrayList collisionIgnoredBlocks = new IntArrayList();
Set<BlockDefinition> jigsawDefinitions = new ObjectOpenHashSet<>(); Set<BlockDefinition> jigsawDefinitions = new ObjectOpenHashSet<>();
Map<String, BlockDefinition> structureBlockDefinitions = new Object2ObjectOpenHashMap<>(); Map<String, BlockDefinition> structureBlockDefinitions = new Object2ObjectOpenHashMap<>();
@@ -308,6 +310,10 @@ public final class BlockRegistryPopulator {
netherPortalBlockDefinition = bedrockDefinition; netherPortalBlockDefinition = bedrockDefinition;
} }
if (block == Blocks.BAMBOO || block == Blocks.POINTED_DRIPSTONE) {
collisionIgnoredBlocks.add(javaRuntimeId);
}
boolean waterlogged = blockState.getValue(Properties.WATERLOGGED, false) boolean waterlogged = blockState.getValue(Properties.WATERLOGGED, false)
|| block == Blocks.BUBBLE_COLUMN || block == Blocks.KELP || block == Blocks.KELP_PLANT || block == Blocks.BUBBLE_COLUMN || block == Blocks.KELP || block == Blocks.KELP_PLANT
|| block == Blocks.SEAGRASS || block == Blocks.TALL_SEAGRASS; || block == Blocks.SEAGRASS || block == Blocks.TALL_SEAGRASS;
@@ -326,6 +332,8 @@ public final class BlockRegistryPopulator {
javaToBedrockBlocks[javaRuntimeId] = bedrockDefinition; javaToBedrockBlocks[javaRuntimeId] = bedrockDefinition;
} }
builder.collisionIgnoredBlocks(collisionIgnoredBlocks);
if (commandBlockDefinition == null) { if (commandBlockDefinition == null) {
throw new AssertionError("Unable to find command block in palette"); throw new AssertionError("Unable to find command block in palette");
} }

View File

@@ -26,6 +26,7 @@
package org.geysermc.geyser.registry.type; package org.geysermc.geyser.registry.type;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import lombok.Builder; import lombok.Builder;
import lombok.Value; import lombok.Value;
@@ -66,6 +67,8 @@ public class BlockMappings implements DefinitionRegistry<BlockDefinition> {
BlockDefinition mobSpawnerBlock; BlockDefinition mobSpawnerBlock;
BlockDefinition netherPortalBlock; BlockDefinition netherPortalBlock;
IntArrayList collisionIgnoredBlocks;
Map<NbtMap, BlockDefinition> itemFrames; Map<NbtMap, BlockDefinition> itemFrames;
Map<Block, NbtMap> flowerPotBlocks; Map<Block, NbtMap> flowerPotBlocks;

View File

@@ -95,6 +95,7 @@ import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket; import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket; import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetCommandsEnabledPacket; import org.cloudburstmc.protocol.bedrock.packet.SetCommandsEnabledPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetTimePacket; import org.cloudburstmc.protocol.bedrock.packet.SetTimePacket;
import org.cloudburstmc.protocol.bedrock.packet.StartGamePacket; import org.cloudburstmc.protocol.bedrock.packet.StartGamePacket;
import org.cloudburstmc.protocol.bedrock.packet.SyncEntityPropertyPacket; import org.cloudburstmc.protocol.bedrock.packet.SyncEntityPropertyPacket;
@@ -632,6 +633,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
*/ */
private boolean flying = false; private boolean flying = false;
/**
* If the current player should be able to noclip through blocks, this is used for void floor workaround and not spectator.
*/
private boolean noClip = false;
@Setter @Setter
private boolean instabuild = false; private boolean instabuild = false;
@@ -1380,6 +1386,15 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
playerEntity.updateBedrockMetadata(); playerEntity.updateBedrockMetadata();
} }
public void setNoClip(boolean noClip) {
if (this.noClip == noClip) {
return;
}
this.noClip = noClip;
this.sendAdventureSettings();
}
public void setFlying(boolean flying) { public void setFlying(boolean flying) {
this.flying = flying; this.flying = flying;
@@ -1835,7 +1850,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
return itemNetId.getAndIncrement(); return itemNetId.getAndIncrement();
} }
public void confirmTeleport(Vector3d position) { public void confirmTeleport(Vector3f position) {
if (unconfirmedTeleport == null) { if (unconfirmedTeleport == null) {
return; return;
} }
@@ -1850,8 +1865,15 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
if (unconfirmedTeleport.shouldResend()) { if (unconfirmedTeleport.shouldResend()) {
unconfirmedTeleport.resetUnconfirmedFor(); unconfirmedTeleport.resetUnconfirmedFor();
geyser.getLogger().debug("Resending teleport " + unconfirmedTeleport.getTeleportConfirmId()); geyser.getLogger().debug("Resending teleport " + unconfirmedTeleport.getTeleportConfirmId());
getPlayerEntity().moveAbsolute(Vector3f.from(unconfirmedTeleport.getX(), unconfirmedTeleport.getY(), unconfirmedTeleport.getZ()), getPlayerEntity().moveAbsolute(unconfirmedTeleport.getPosition(),
unconfirmedTeleport.getYaw(), unconfirmedTeleport.getPitch(), playerEntity.isOnGround(), true); unconfirmedTeleport.getYaw(), unconfirmedTeleport.getPitch(), playerEntity.isOnGround(), true);
if (unconfirmedTeleport.getTeleportType() == TeleportCache.TeleportType.KEEP_VELOCITY) {
SetEntityMotionPacket entityMotionPacket = new SetEntityMotionPacket();
entityMotionPacket.setRuntimeEntityId(playerEntity.getGeyserId());
entityMotionPacket.setMotion(unconfirmedTeleport.getVelocity());
this.sendUpstreamPacket(entityMotionPacket);
}
} }
} }
@@ -2035,6 +2057,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
abilities.add(Ability.INSTABUILD); abilities.add(Ability.INSTABUILD);
} }
if (noClip && !spectator) {
abilities.add(Ability.NO_CLIP);
}
if (commandPermission == CommandPermission.GAME_DIRECTORS) { if (commandPermission == CommandPermission.GAME_DIRECTORS) {
// Fixes a bug? since 1.19.11 where the player can change their gamemode in Bedrock settings and // Fixes a bug? since 1.19.11 where the player can change their gamemode in Bedrock settings and
// a packet is not sent to the server. // a packet is not sent to the server.

View File

@@ -25,9 +25,9 @@
package org.geysermc.geyser.session.cache; package org.geysermc.geyser.session.cache;
import org.cloudburstmc.math.vector.Vector3d;
import lombok.Data; import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.cloudburstmc.math.vector.Vector3f;
/** /**
* Represents a teleport ID and corresponding coordinates that need to be confirmed. <br> * Represents a teleport ID and corresponding coordinates that need to be confirmed. <br>
@@ -42,24 +42,37 @@ import lombok.RequiredArgsConstructor;
@Data @Data
public class TeleportCache { public class TeleportCache {
private static final double ERROR_X_AND_Z = 0.1; private static final float ERROR_X_AND_Z = 0.1f;
private static final double ERROR_Y = 0.1; private static final float ERROR_Y = 0.1f;
/** /**
* How many move packets the teleport can be unconfirmed for before it gets resent to the client * How many move packets the teleport can be unconfirmed for before it gets resent to the client
*/ */
private static final int RESEND_THRESHOLD = 20; // Make it one full second with auth input private static final int RESEND_THRESHOLD = 20; // Make it one full second with auth input
private final double x, y, z; public TeleportCache(Vector3f position, float pitch, float yaw, int teleportConfirmId) {
this.position = position;
this.velocity = Vector3f.ZERO;
this.pitch = pitch;
this.yaw = yaw;
this.teleportConfirmId = teleportConfirmId;
this.teleportType = TeleportType.NORMAL;
}
private final Vector3f position;
private final Vector3f velocity;
private final float pitch, yaw; private final float pitch, yaw;
private final int teleportConfirmId; private final int teleportConfirmId;
private final TeleportType teleportType;
private int unconfirmedFor = 0; private int unconfirmedFor = 0;
public boolean canConfirm(Vector3d position) { public boolean canConfirm(Vector3f position) {
return (Math.abs(this.x - position.getX()) < ERROR_X_AND_Z && final float distanceX = Math.abs(this.position.getX() - position.getX());
Math.abs(this.y - position.getY()) < ERROR_Y && final float distanceY = Math.abs(this.position.getY() - position.getY());
Math.abs(this.z - position.getZ()) < ERROR_X_AND_Z); final float distanceZ = Math.abs(this.position.getZ() - position.getZ());
return distanceX < ERROR_X_AND_Z && distanceY < ERROR_Y && distanceZ < ERROR_X_AND_Z;
} }
public void incrementUnconfirmedFor() { public void incrementUnconfirmedFor() {
@@ -73,4 +86,9 @@ public class TeleportCache {
public boolean shouldResend() { public boolean shouldResend() {
return unconfirmedFor >= RESEND_THRESHOLD; return unconfirmedFor >= RESEND_THRESHOLD;
} }
public enum TeleportType {
NORMAL,
KEEP_VELOCITY;
}
} }

View File

@@ -28,9 +28,13 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player.input;
import org.cloudburstmc.math.vector.Vector3d; import org.cloudburstmc.math.vector.Vector3d;
import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData; import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket; import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket;
import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.BoatEntity;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
import org.geysermc.geyser.level.physics.BoundingBox;
import org.geysermc.geyser.level.physics.CollisionResult; import org.geysermc.geyser.level.physics.CollisionResult;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.text.ChatColor;
@@ -55,11 +59,12 @@ final class BedrockMovePlayer {
// Ignore movement packets until Bedrock's position matches the teleported position // Ignore movement packets until Bedrock's position matches the teleported position
if (session.getUnconfirmedTeleport() != null) { if (session.getUnconfirmedTeleport() != null) {
session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityDefinitions.PLAYER.offset(), 0)); session.confirmTeleport(packet.getPosition().sub(0, EntityDefinitions.PLAYER.offset(), 0));
return; return;
} }
boolean actualPositionChanged = !entity.getPosition().equals(packet.getPosition()); // This is vanilla behaviour, LocalPlayer#sendPosition 1.21.8.
boolean actualPositionChanged = entity.getPosition().distanceSquared(packet.getPosition()) > 4e-8;
if (actualPositionChanged) { if (actualPositionChanged) {
// Send book update before the player moves // Send book update before the player moves
@@ -96,14 +101,47 @@ final class BedrockMovePlayer {
// Client is telling us it wants to move down, but something is blocking it from doing so. // Client is telling us it wants to move down, but something is blocking it from doing so.
boolean isOnGround; boolean isOnGround;
if (hasVehicle) { if (hasVehicle || session.isNoClip()) {
// VERTICAL_COLLISION is not accurate while in a vehicle (as of 1.21.62) // VERTICAL_COLLISION is not accurate while in a vehicle (as of 1.21.62)
// If the player is riding a vehicle or is in spectator mode, onGround is always set to false for the player // If the player is riding a vehicle or is in spectator mode, onGround is always set to false for the player
// Also do this if player have no clip ability since they shouldn't be able to collide with anything.
isOnGround = false; isOnGround = false;
} else { } else {
isOnGround = packet.getInputData().contains(PlayerAuthInputData.VERTICAL_COLLISION) && entity.getLastTickEndVelocity().getY() < 0; isOnGround = packet.getInputData().contains(PlayerAuthInputData.VERTICAL_COLLISION) && entity.getLastTickEndVelocity().getY() < 0;
} }
// Resolve https://github.com/GeyserMC/Geyser/issues/3521, no void floor on java so player not supposed to collide with anything.
// Therefore, we're fixing this by allowing player to no clip to clip through the floor, not only this fixed the issue but
// player y velocity should match java perfectly, much better than teleport player right down :)
// Shouldn't mess with anything because beyond this point there is nothing to collide and not even entities since they're prob dead.
if (packet.getPosition().getY() - EntityDefinitions.PLAYER.offset() < session.getBedrockDimension().minY() - 5) {
// Ensuring that we still can collide with collidable entity that are also in the void (eg: boat, shulker)
boolean possibleOnGround = false;
BoundingBox boundingBox = session.getCollisionManager().getPlayerBoundingBox().clone();
// Extend down by y velocity subtract by 2 so that we are a "little" ahead and can send no clip in time before player hit the entity.
boundingBox.extend(0, packet.getDelta().getY() - 2, 0);
for (Entity other : session.getEntityCache().getEntities().values()) {
if (!other.getFlag(EntityFlag.COLLIDABLE)) {
continue;
}
final BoundingBox entityBoundingBox = new BoundingBox(0, 0, 0, other.getBoundingBoxWidth(), other.getBoundingBoxHeight(), other.getBoundingBoxWidth());
// Also offset the position down for boat as their position is offset.
entityBoundingBox.translate(other.getPosition().down(other instanceof BoatEntity ? entity.getDefinition().offset() : 0).toDouble());
if (entityBoundingBox.checkIntersection(boundingBox)) {
possibleOnGround = true;
break;
}
}
session.setNoClip(!possibleOnGround);
}
entity.setLastTickEndVelocity(packet.getDelta()); entity.setLastTickEndVelocity(packet.getDelta());
entity.setMotion(packet.getDelta()); entity.setMotion(packet.getDelta());
@@ -135,35 +173,6 @@ final class BedrockMovePlayer {
CollisionResult result = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), isOnGround, packet.getInputData().contains(PlayerAuthInputData.HANDLE_TELEPORT)); CollisionResult result = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), isOnGround, packet.getInputData().contains(PlayerAuthInputData.HANDLE_TELEPORT));
if (result != null) { // A null return value cancels the packet if (result != null) { // A null return value cancels the packet
Vector3d position = result.correctedMovement(); Vector3d position = result.correctedMovement();
boolean isBelowVoid = entity.isVoidPositionDesynched();
boolean teleportThroughVoidFloor, mustResyncPosition;
// Compare positions here for void floor fix below before the player's position variable is set to the packet position
if (entity.getPosition().getY() >= packet.getPosition().getY() && !isBelowVoid) {
int floorY = position.getFloorY();
int voidFloorLocation = entity.voidFloorPosition();
teleportThroughVoidFloor = floorY <= (voidFloorLocation + 1) && floorY >= voidFloorLocation;
} else {
teleportThroughVoidFloor = false;
}
if (teleportThroughVoidFloor || isBelowVoid) {
// https://github.com/GeyserMC/Geyser/issues/3521 - no void floor in Java so we cannot be on the ground.
isOnGround = false;
}
if (isBelowVoid) {
int floorY = position.getFloorY();
int voidFloorLocation = entity.voidFloorPosition();
mustResyncPosition = floorY < voidFloorLocation && floorY >= voidFloorLocation - 1;
} else {
mustResyncPosition = false;
}
double yPosition = position.getY();
if (entity.isVoidPositionDesynched()) { // not using the cached variable on purpose
yPosition += 4; // We are de-synched since we had to teleport below the void floor.
}
Packet movePacket; Packet movePacket;
if (rotationChanged) { if (rotationChanged) {
@@ -171,7 +180,7 @@ final class BedrockMovePlayer {
movePacket = new ServerboundMovePlayerPosRotPacket( movePacket = new ServerboundMovePlayerPosRotPacket(
isOnGround, isOnGround,
horizontalCollision, horizontalCollision,
position.getX(), yPosition, position.getZ(), position.getX(), position.getY(), position.getZ(),
yaw, pitch yaw, pitch
); );
entity.setYaw(yaw); entity.setYaw(yaw);
@@ -179,7 +188,7 @@ final class BedrockMovePlayer {
entity.setHeadYaw(headYaw); entity.setHeadYaw(headYaw);
} else { } else {
// Rotation did not change; don't send an update with rotation // Rotation did not change; don't send an update with rotation
movePacket = new ServerboundMovePlayerPosPacket(isOnGround, horizontalCollision, position.getX(), yPosition, position.getZ()); movePacket = new ServerboundMovePlayerPosPacket(isOnGround, horizontalCollision, position.getX(), position.getY(), position.getZ());
} }
entity.setPositionManual(packet.getPosition()); entity.setPositionManual(packet.getPosition());
@@ -187,12 +196,6 @@ final class BedrockMovePlayer {
// Send final movement changes // Send final movement changes
session.sendDownstreamGamePacket(movePacket); session.sendDownstreamGamePacket(movePacket);
if (teleportThroughVoidFloor) {
entity.teleportVoidFloorFix(false);
} else if (mustResyncPosition) {
entity.teleportVoidFloorFix(true);
}
session.getInputCache().markPositionPacketSent(); session.getInputCache().markPositionPacketSent();
session.getSkullCache().updateVisibleSkulls(); session.getSkullCache().updateVisibleSkulls();
} }

View File

@@ -26,6 +26,7 @@
package org.geysermc.geyser.translator.protocol.java.entity; package org.geysermc.geyser.translator.protocol.java.entity;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData; import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket; import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket;
@@ -58,7 +59,7 @@ public class JavaSetPassengersTranslator extends PacketTranslator<ClientboundSet
if (passenger == session.getPlayerEntity()) { if (passenger == session.getPlayerEntity()) {
session.getPlayerEntity().setVehicle(entity); session.getPlayerEntity().setVehicle(entity);
// We need to confirm teleports before entering a vehicle, or else we will likely exit right out // We need to confirm teleports before entering a vehicle, or else we will likely exit right out
session.confirmTeleport(passenger.getPosition().down(EntityDefinitions.PLAYER.offset()).toDouble()); session.confirmTeleport(passenger.getPosition().down(EntityDefinitions.PLAYER.offset()));
if (entity instanceof ClientVehicle clientVehicle) { if (entity instanceof ClientVehicle clientVehicle) {
clientVehicle.getVehicleComponent().onMount(); clientVehicle.getVehicleComponent().onMount();

View File

@@ -30,14 +30,15 @@ import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.packet.ChunkRadiusUpdatedPacket; import org.cloudburstmc.protocol.bedrock.packet.ChunkRadiusUpdatedPacket;
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket; import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
import org.cloudburstmc.protocol.bedrock.packet.RespawnPacket; import org.cloudburstmc.protocol.bedrock.packet.RespawnPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket;
import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
import org.geysermc.geyser.item.hashing.DataComponentHashers;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.TeleportCache; import org.geysermc.geyser.session.cache.TeleportCache;
import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.ChunkUtils; import org.geysermc.geyser.util.ChunkUtils;
import org.geysermc.geyser.util.MathUtils;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PositionElement; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PositionElement;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerPositionPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerPositionPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.level.ServerboundAcceptTeleportationPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.level.ServerboundAcceptTeleportationPacket;
@@ -48,16 +49,27 @@ public class JavaPlayerPositionTranslator extends PacketTranslator<ClientboundPl
@Override @Override
public void translate(GeyserSession session, ClientboundPlayerPositionPacket packet) { public void translate(GeyserSession session, ClientboundPlayerPositionPacket packet) {
if (!session.isLoggedIn()) if (!session.isLoggedIn()) {
return; return;
}
SessionPlayerEntity entity = session.getPlayerEntity(); final SessionPlayerEntity entity = session.getPlayerEntity();
Vector3d pos = packet.getPosition(); Vector3d position = packet.getPosition();
position = position.add(
packet.getRelatives().contains(PositionElement.X) ? entity.getPosition().getX() : 0,
packet.getRelatives().contains(PositionElement.Y) ? entity.getPosition().getY() - EntityDefinitions.PLAYER.offset() : 0,
packet.getRelatives().contains(PositionElement.Z) ? entity.getPosition().getZ() : 0);
float newPitch = MathUtils.clamp(packet.getXRot() + (packet.getRelatives().contains(PositionElement.X_ROT) ? entity.getPitch() : 0), -90, 90);
float newYaw = packet.getYRot() + (packet.getRelatives().contains(PositionElement.Y_ROT) ? entity.getYaw() : 0);
final int teleportId = packet.getId();
acceptTeleport(session, position, newYaw, newPitch, teleportId);
if (!session.isSpawned()) { if (!session.isSpawned()) {
// TODO this behavior seems outdated (1.21.2). entity.setPosition(position.toFloat());
// The server sends an absolute teleport everytime the player is respawned
entity.setPosition(pos.toFloat());
entity.setYaw(packet.getYRot()); entity.setYaw(packet.getYRot());
entity.setPitch(packet.getXRot()); entity.setPitch(packet.getXRot());
entity.setHeadYaw(packet.getYRot()); entity.setHeadYaw(packet.getYRot());
@@ -75,21 +87,17 @@ public class JavaPlayerPositionTranslator extends PacketTranslator<ClientboundPl
movePlayerPacket.setPosition(entity.getPosition()); movePlayerPacket.setPosition(entity.getPosition());
movePlayerPacket.setRotation(entity.getBedrockRotation()); movePlayerPacket.setRotation(entity.getBedrockRotation());
movePlayerPacket.setMode(MovePlayerPacket.Mode.RESPAWN); movePlayerPacket.setMode(MovePlayerPacket.Mode.RESPAWN);
session.sendUpstreamPacket(movePlayerPacket); session.sendUpstreamPacket(movePlayerPacket);
// Fixes incorrect rotation upon login // Fixes incorrect rotation upon login
// Yes, even that's not respected by Bedrock. Try it out in singleplayer! // Yes, even that's not respected by Bedrock. Try it out in singleplayer!
// Log out and back in - and you're looking elsewhere :) // Log out and back in - and you're looking elsewhere :)
entity.updateOwnRotation(entity.getYaw(), entity.getPitch(), entity.getHeadYaw()); entity.updateOwnRotation(entity.getYaw(), entity.getPitch(), entity.getHeadYaw());
session.setSpawned(true); session.setSpawned(true);
// DataComponentHashers.testHashing(session); // TODO remove me // DataComponentHashers.testHashing(session); // TODO remove me
// Make sure the player moves away from (0, 32767, 0) before accepting movement packets // Make sure the player moves away from (0, 32767, 0) before accepting movement packets
session.setUnconfirmedTeleport(new TeleportCache(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ(), packet.getXRot(), packet.getYRot(), packet.getId())); // TODO session.setUnconfirmedTeleport(new TeleportCache(entity.position(), packet.getXRot(), packet.getYRot(), packet.getId()));
acceptTeleport(session, packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ(), packet.getYRot(), packet.getXRot(), packet.getId());
if (session.getServerRenderDistance() > 32 && !session.isEmulatePost1_13Logic()) { if (session.getServerRenderDistance() > 32 && !session.isEmulatePost1_13Logic()) {
// See DimensionUtils for an explanation // See DimensionUtils for an explanation
@@ -100,7 +108,7 @@ public class JavaPlayerPositionTranslator extends PacketTranslator<ClientboundPl
session.setLastChunkPosition(null); session.setLastChunkPosition(null);
} }
ChunkUtils.updateChunkPosition(session, pos.toInt()); ChunkUtils.updateChunkPosition(session, position.toInt());
if (session.getGeyser().getConfig().isDebugMode()) { if (session.getGeyser().getConfig().isDebugMode()) {
session.getGeyser().getLogger().debug("Spawned player at " + packet.getPosition()); session.getGeyser().getLogger().debug("Spawned player at " + packet.getPosition());
@@ -108,45 +116,54 @@ public class JavaPlayerPositionTranslator extends PacketTranslator<ClientboundPl
return; return;
} }
// If coordinates are relative, then add to the existing coordinate session.getGeyser().getLogger().debug("Teleport (" + teleportId + ") from " + entity.getPosition().getX() + " " + (entity.getPosition().getY() - EntityDefinitions.PLAYER.offset()) + " " + entity.getPosition().getZ());
double newX = pos.getX() +
(packet.getRelatives().contains(PositionElement.X) ? entity.getPosition().getX() : 0);
double newY = pos.getY() +
(packet.getRelatives().contains(PositionElement.Y) ? entity.getPosition().getY() - EntityDefinitions.PLAYER.offset() : 0);
double newZ = pos.getZ() +
(packet.getRelatives().contains(PositionElement.Z) ? entity.getPosition().getZ() : 0);
float newPitch = packet.getXRot() + (packet.getRelatives().contains(PositionElement.X_ROT) ? entity.getPitch() : 0);
float newYaw = packet.getYRot() + (packet.getRelatives().contains(PositionElement.Y_ROT) ? entity.getYaw() : 0);
int id = packet.getId();
session.getGeyser().getLogger().debug("Teleport (" + id + ") from " + entity.getPosition().getX() + " " + (entity.getPosition().getY() - EntityDefinitions.PLAYER.offset()) + " " + entity.getPosition().getZ());
Vector3f lastPlayerPosition = entity.getPosition().down(EntityDefinitions.PLAYER.offset()); Vector3f lastPlayerPosition = entity.getPosition().down(EntityDefinitions.PLAYER.offset());
float lastPlayerPitch = entity.getPitch(); float lastPlayerPitch = entity.getPitch();
Vector3f teleportDestination = Vector3f.from(newX, newY, newZ); float lastPlayerYaw = entity.getYaw();
entity.moveAbsolute(teleportDestination, newYaw, newPitch, false, true); Vector3f teleportDestination = position.toFloat();
session.getGeyser().getLogger().debug("to " + entity.getPosition().getX() + " " + (entity.getPosition().getY() - EntityDefinitions.PLAYER.offset()) + " " + entity.getPosition().getZ()); Vector3f deltaMovement = packet.getDeltaMovement().toFloat().add(
packet.getRelatives().contains(PositionElement.DELTA_X) ? entity.getMotion().getX() : 0,
packet.getRelatives().contains(PositionElement.DELTA_Y) ? entity.getMotion().getY() : 0,
packet.getRelatives().contains(PositionElement.DELTA_Z) ? entity.getMotion().getZ() : 0
);
// Bedrock ignores teleports that are extremely close to the player's original position and orientation, if (packet.getRelatives().contains(PositionElement.ROTATE_DELTA)) {
// so check if we need to cache the teleport deltaMovement = MathUtils.xYRot(deltaMovement, (float) Math.toRadians(lastPlayerPitch - newPitch), (float) Math.toRadians(lastPlayerYaw - newYaw));
if (lastPlayerPosition.distanceSquared(teleportDestination) < 0.001 && Math.abs(newPitch - lastPlayerPitch) < 5) {
session.setUnconfirmedTeleport(null);
} else {
session.setUnconfirmedTeleport(new TeleportCache(newX, newY, newZ, newPitch, newYaw, id));
} }
acceptTeleport(session, newX, newY, newZ, newYaw, newPitch, id); entity.moveAbsolute(teleportDestination, newYaw, newPitch, false, true);
TeleportCache.TeleportType type = TeleportCache.TeleportType.NORMAL;
if (deltaMovement.distanceSquared(Vector3f.ZERO) > 1.0E-8F) {
entity.setMotion(deltaMovement);
// Our motion got reset by the teleport but the deltaMovement is not 0 so send a motion packet to fix that.
SetEntityMotionPacket entityMotionPacket = new SetEntityMotionPacket();
entityMotionPacket.setRuntimeEntityId(entity.getGeyserId());
entityMotionPacket.setMotion(entity.getMotion());
session.sendUpstreamPacket(entityMotionPacket);
type = TeleportCache.TeleportType.KEEP_VELOCITY;
}
// Bedrock ignores teleports that are extremely close to the player's original position and orientation, so check if we need to cache the teleport
if (lastPlayerPosition.distanceSquared(teleportDestination) < 0.001 && Math.abs(newPitch - lastPlayerPitch) < 5 && Math.abs(newYaw - lastPlayerYaw) < 5) {
session.setUnconfirmedTeleport(null);
} else {
session.setUnconfirmedTeleport(new TeleportCache(teleportDestination, deltaMovement, newPitch, newYaw, teleportId, type));
}
session.getGeyser().getLogger().debug("to " + entity.getPosition().getX() + " " + (entity.getPosition().getY() - EntityDefinitions.PLAYER.offset()) + " " + entity.getPosition().getZ());
} }
private void acceptTeleport(GeyserSession session, double x, double y, double z, float yaw, float pitch, int id) { private void acceptTeleport(GeyserSession session, Vector3d position, float yaw, float pitch, int id) {
// Confirm the teleport when we receive it to match Java edition // Confirm the teleport when we receive it to match Java edition
ServerboundAcceptTeleportationPacket teleportConfirmPacket = new ServerboundAcceptTeleportationPacket(id); ServerboundAcceptTeleportationPacket teleportConfirmPacket = new ServerboundAcceptTeleportationPacket(id);
session.sendDownstreamGamePacket(teleportConfirmPacket); session.sendDownstreamGamePacket(teleportConfirmPacket);
// Servers (especially ones like Hypixel) expect exact coordinates given back to them. // Servers (especially ones like Hypixel) expect exact coordinates given back to them.
ServerboundMovePlayerPosRotPacket positionPacket = new ServerboundMovePlayerPosRotPacket(false, false, x, y, z, yaw, pitch); ServerboundMovePlayerPosRotPacket positionPacket = new ServerboundMovePlayerPosRotPacket(false, false, position.getX(), position.getY(), position.getZ(), yaw, pitch);
session.sendDownstreamGamePacket(positionPacket); session.sendDownstreamGamePacket(positionPacket);
} }
} }

View File

@@ -25,9 +25,28 @@
package org.geysermc.geyser.util; package org.geysermc.geyser.util;
import org.cloudburstmc.math.TrigMath;
import org.cloudburstmc.math.vector.Vector3f;
public class MathUtils { public class MathUtils {
public static final double SQRT_OF_TWO = Math.sqrt(2); public static final double SQRT_OF_TWO = Math.sqrt(2);
public static Vector3f xYRot(Vector3f velocity, float pitch, float yaw) {
float pitchCos = TrigMath.cos(pitch);
float pitchSin = TrigMath.sin(pitch);
float yawCos = TrigMath.cos(yaw);
float yawSin = TrigMath.sin(yaw);
float e = velocity.getY() * pitchCos + velocity.getZ() * pitchSin;
float i = velocity.getZ() * pitchCos - velocity.getY() * pitchSin;
velocity = Vector3f.from(velocity.getX(), e, i);
float d1 = velocity.getX() * yawCos + velocity.getZ() * yawSin;
float i1 = velocity.getZ() * yawCos - velocity.getX() * yawSin;
return Vector3f.from(d1, e, i1);
}
/** /**
* Wrap the given float degrees to be between -180.0 and 180.0. * Wrap the given float degrees to be between -180.0 and 180.0.
* *