diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 7dbbf0b52..a8ca770aa 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -204,16 +204,15 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { movePlayerPacket.setPosition(this.position); movePlayerPacket.setRotation(getBedrockRotation()); movePlayerPacket.setOnGround(isOnGround); - movePlayerPacket.setMode(teleported ? MovePlayerPacket.Mode.TELEPORT : MovePlayerPacket.Mode.NORMAL); - - if (teleported) { - movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN); + movePlayerPacket.setMode(this instanceof SessionPlayerEntity || teleported ? MovePlayerPacket.Mode.TELEPORT : MovePlayerPacket.Mode.NORMAL); + if (movePlayerPacket.getMode() == MovePlayerPacket.Mode.TELEPORT) { + movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR); } session.sendUpstreamPacket(movePlayerPacket); - if (teleported) { - // As of 1.19.0, head yaw seems to be ignored during teleports. + if (teleported && !(this instanceof SessionPlayerEntity)) { + // As of 1.19.0, head yaw seems to be ignored during teleports, also don't do this for session player. updateHeadLookRotation(headYaw); } @@ -239,7 +238,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { movePlayerPacket.setPosition(position); movePlayerPacket.setRotation(getBedrockRotation()); 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 // correctly on Bedrock. This fixes GSit's lay. if (getFlag(EntityFlag.SLEEPING)) { @@ -247,9 +246,13 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { // Force the player movement by using a teleport movePlayerPacket.setPosition(Vector3f.from(position.getX(), position.getY() - definition.offset() + 0.2f, position.getZ())); movePlayerPacket.setMode(MovePlayerPacket.Mode.TELEPORT); - movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.UNKNOWN); } } + + if (movePlayerPacket.getMode() == MovePlayerPacket.Mode.TELEPORT) { + movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR); + } + session.sendUpstreamPacket(movePlayerPacket); if (leftParrot != null) { leftParrot.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, true); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index b6835151e..deae0fd7e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -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.EntityFlag; import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket; +import org.cloudburstmc.protocol.bedrock.packet.SetEntityMotionPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.entity.EntityDefinitions; 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.inventory.GeyserItemStack; 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.property.Properties; import org.geysermc.geyser.level.block.type.BlockState; @@ -117,15 +117,6 @@ public class SessionPlayerEntity extends PlayerEntity { @Getter @Setter 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 - *

- * Must be reset when dying, switching worlds, or being teleported out of the void - */ - @Getter @Setter - private boolean voidPositionDesynched; - public SessionPlayerEntity(GeyserSession session) { super(session, -1, 1, null, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, null, null); @@ -152,29 +143,18 @@ public class SessionPlayerEntity extends PlayerEntity { @Override 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); 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 public void setPosition(Vector3f position) { if (valid) { // Don't update during session init session.getCollisionManager().updatePlayerBoundingBox(position); + + if (session.isNoClip() && position.getY() >= session.getBedrockDimension().minY() - 5) { + session.setNoClip(false); + } } this.position = position.add(0, definition.offset(), 0); } @@ -200,6 +180,12 @@ public class SessionPlayerEntity extends PlayerEntity { movePlayerPacket.setTeleportationCause(MovePlayerPacket.TeleportationCause.BEHAVIOR); 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) { 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 { 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 @@ -448,51 +436,7 @@ public class SessionPlayerEntity extends PlayerEntity { 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. */ diff --git a/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java b/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java index 006304f1a..cae5287be 100644 --- a/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/physics/CollisionManager.java @@ -34,7 +34,7 @@ import org.cloudburstmc.math.vector.Vector3d; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.math.vector.Vector3i; 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.geyser.entity.EntityDefinitions; 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 // 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, Double.parseDouble(Float.toString(bedrockPosition.getZ()))); @@ -197,18 +197,19 @@ public class CollisionManager { return null; } - position = playerBoundingBox.getBottomCenter(); - 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 - if (onGround != newOnGround || movement.distanceSquared(adjustedMovement) > INCORRECT_MOVEMENT_THRESHOLD) { + if (onGround != newOnGround || position.distanceSquared(playerBoundingBox.getBottomCenter()) > INCORRECT_MOVEMENT_THRESHOLD) { PlayerEntity playerEntity = session.getPlayerEntity(); // Client will dismount if on a vehicle 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) { // 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()); @@ -217,18 +218,14 @@ public class CollisionManager { 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() { PlayerEntity entity = session.getPlayerEntity(); - // Gravity might need to be reset... - entity.updateBedrockMetadata(); // TODO may not be necessary - MovePlayerPacket movePlayerPacket = new MovePlayerPacket(); - movePlayerPacket.setRuntimeEntityId(entity.getGeyserId()); - movePlayerPacket.setPosition(entity.getPosition()); - movePlayerPacket.setRotation(entity.getBedrockRotation()); - movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL); - session.sendUpstreamPacket(movePlayerPacket); + // This does the job and won't interrupt velocity + rotation. + UpdateClientInputLocksPacket inputLocksPacket = new UpdateClientInputLocksPacket(); + inputLocksPacket.setLockComponentData(0); // Don't actually lock anything. + inputLocksPacket.setServerPosition(entity.getPosition()); + session.sendUpstreamPacket(inputLocksPacket); } public BlockPositionIterator collidableBlocksIterator(BoundingBox box) { @@ -280,7 +277,15 @@ public class CollisionManager { // Main correction code 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.correctPosition(session, iter.getX(), iter.getY(), iter.getZ(), playerBoundingBox)) { return false; diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java index 738658a8d..007bc889c 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java @@ -32,6 +32,7 @@ import com.google.common.collect.Interner; import com.google.common.collect.Interners; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; 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.Object2ObjectMaps; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; @@ -243,6 +244,7 @@ public final class BlockRegistryPopulator { .toList(); Map flowerPotBlocks = new Object2ObjectOpenHashMap<>(); Map itemFrames = new Object2ObjectOpenHashMap<>(); + IntArrayList collisionIgnoredBlocks = new IntArrayList(); Set jigsawDefinitions = new ObjectOpenHashSet<>(); Map structureBlockDefinitions = new Object2ObjectOpenHashMap<>(); @@ -308,6 +310,10 @@ public final class BlockRegistryPopulator { netherPortalBlockDefinition = bedrockDefinition; } + if (block == Blocks.BAMBOO || block == Blocks.POINTED_DRIPSTONE) { + collisionIgnoredBlocks.add(javaRuntimeId); + } + boolean waterlogged = blockState.getValue(Properties.WATERLOGGED, false) || block == Blocks.BUBBLE_COLUMN || block == Blocks.KELP || block == Blocks.KELP_PLANT || block == Blocks.SEAGRASS || block == Blocks.TALL_SEAGRASS; @@ -326,6 +332,8 @@ public final class BlockRegistryPopulator { javaToBedrockBlocks[javaRuntimeId] = bedrockDefinition; } + builder.collisionIgnoredBlocks(collisionIgnoredBlocks); + if (commandBlockDefinition == null) { throw new AssertionError("Unable to find command block in palette"); } diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java index c783aee56..22679ecfd 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.registry.type; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import lombok.Builder; import lombok.Value; @@ -66,6 +67,8 @@ public class BlockMappings implements DefinitionRegistry { BlockDefinition mobSpawnerBlock; BlockDefinition netherPortalBlock; + IntArrayList collisionIgnoredBlocks; + Map itemFrames; Map flowerPotBlocks; diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index cab6964b8..f02a03b51 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -95,6 +95,7 @@ import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket; import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket; 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.StartGamePacket; import org.cloudburstmc.protocol.bedrock.packet.SyncEntityPropertyPacket; @@ -632,6 +633,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { */ 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 private boolean instabuild = false; @@ -1380,6 +1386,15 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { playerEntity.updateBedrockMetadata(); } + public void setNoClip(boolean noClip) { + if (this.noClip == noClip) { + return; + } + + this.noClip = noClip; + this.sendAdventureSettings(); + } + public void setFlying(boolean flying) { this.flying = flying; @@ -1835,7 +1850,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { return itemNetId.getAndIncrement(); } - public void confirmTeleport(Vector3d position) { + public void confirmTeleport(Vector3f position) { if (unconfirmedTeleport == null) { return; } @@ -1850,8 +1865,15 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { if (unconfirmedTeleport.shouldResend()) { unconfirmedTeleport.resetUnconfirmedFor(); 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); + + 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); } + if (noClip && !spectator) { + abilities.add(Ability.NO_CLIP); + } + if (commandPermission == CommandPermission.GAME_DIRECTORS) { // 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. diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/TeleportCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/TeleportCache.java index 7a7a5f1e2..d672338f7 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/TeleportCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/TeleportCache.java @@ -25,9 +25,9 @@ package org.geysermc.geyser.session.cache; -import org.cloudburstmc.math.vector.Vector3d; import lombok.Data; import lombok.RequiredArgsConstructor; +import org.cloudburstmc.math.vector.Vector3f; /** * Represents a teleport ID and corresponding coordinates that need to be confirmed.
@@ -42,24 +42,37 @@ import lombok.RequiredArgsConstructor; @Data public class TeleportCache { - private static final double ERROR_X_AND_Z = 0.1; - private static final double ERROR_Y = 0.1; + private static final float ERROR_X_AND_Z = 0.1f; + 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 */ 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 int teleportConfirmId; + private final TeleportType teleportType; private int unconfirmedFor = 0; - public boolean canConfirm(Vector3d position) { - return (Math.abs(this.x - position.getX()) < ERROR_X_AND_Z && - Math.abs(this.y - position.getY()) < ERROR_Y && - Math.abs(this.z - position.getZ()) < ERROR_X_AND_Z); + public boolean canConfirm(Vector3f position) { + final float distanceX = Math.abs(this.position.getX() - position.getX()); + final float distanceY = Math.abs(this.position.getY() - position.getY()); + 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() { @@ -73,4 +86,9 @@ public class TeleportCache { public boolean shouldResend() { return unconfirmedFor >= RESEND_THRESHOLD; } + + public enum TeleportType { + NORMAL, + KEEP_VELOCITY; + } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java index 9bad0b830..ca844a388 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/input/BedrockMovePlayer.java @@ -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.Vector3f; import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData; +import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket; 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.level.physics.BoundingBox; import org.geysermc.geyser.level.physics.CollisionResult; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.ChatColor; @@ -55,11 +59,12 @@ final class BedrockMovePlayer { // Ignore movement packets until Bedrock's position matches the teleported position 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; } - 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) { // 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. boolean isOnGround; - if (hasVehicle) { + if (hasVehicle || session.isNoClip()) { // 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 + // Also do this if player have no clip ability since they shouldn't be able to collide with anything. isOnGround = false; } else { 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.setMotion(packet.getDelta()); @@ -135,35 +173,6 @@ final class BedrockMovePlayer { CollisionResult result = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), isOnGround, packet.getInputData().contains(PlayerAuthInputData.HANDLE_TELEPORT)); if (result != null) { // A null return value cancels the packet 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; if (rotationChanged) { @@ -171,7 +180,7 @@ final class BedrockMovePlayer { movePacket = new ServerboundMovePlayerPosRotPacket( isOnGround, horizontalCollision, - position.getX(), yPosition, position.getZ(), + position.getX(), position.getY(), position.getZ(), yaw, pitch ); entity.setYaw(yaw); @@ -179,7 +188,7 @@ final class BedrockMovePlayer { entity.setHeadYaw(headYaw); } else { // 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()); @@ -187,12 +196,6 @@ final class BedrockMovePlayer { // Send final movement changes session.sendDownstreamGamePacket(movePacket); - if (teleportThroughVoidFloor) { - entity.teleportVoidFloorFix(false); - } else if (mustResyncPosition) { - entity.teleportVoidFloorFix(true); - } - session.getInputCache().markPositionPacketSent(); session.getSkullCache().updateVisibleSkulls(); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetPassengersTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetPassengersTranslator.java index 1c3ac4688..e8f99fbea 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetPassengersTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSetPassengersTranslator.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.translator.protocol.java.entity; 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.EntityLinkData; import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket; @@ -58,7 +59,7 @@ public class JavaSetPassengersTranslator extends PacketTranslator 32 && !session.isEmulatePost1_13Logic()) { // See DimensionUtils for an explanation @@ -100,7 +108,7 @@ public class JavaPlayerPositionTranslator extends PacketTranslator 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 ServerboundAcceptTeleportationPacket teleportConfirmPacket = new ServerboundAcceptTeleportationPacket(id); session.sendDownstreamGamePacket(teleportConfirmPacket); // 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); } } diff --git a/core/src/main/java/org/geysermc/geyser/util/MathUtils.java b/core/src/main/java/org/geysermc/geyser/util/MathUtils.java index 08bed56f4..01e03d4c1 100644 --- a/core/src/main/java/org/geysermc/geyser/util/MathUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/MathUtils.java @@ -25,9 +25,28 @@ package org.geysermc.geyser.util; +import org.cloudburstmc.math.TrigMath; +import org.cloudburstmc.math.vector.Vector3f; + public class MathUtils { 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. *