From 120769c7f68c90c7717236ae84de47bfbe039965 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 12 Apr 2021 00:35:53 -0400 Subject: [PATCH] Allow for crawling and moving in one-block spaces where possible (#1814) This commit brings full support for crawling, sneaking under 1.5-block-tall spaces, and swimming in one-block areas. There is a check in place that decreases the player's speed to something comparable to Java if they are in a situation where they would otherwise go at normal walking speed (for example: without the check, a Bedrock player would go at full walking speed while crawling). --- .../org/geysermc/connector/entity/Entity.java | 41 +++++++--- .../entity/player/SessionPlayerEntity.java | 31 +++++++- .../network/session/GeyserSession.java | 77 ++++++++++++++++++- .../BedrockAdventureSettingsTranslator.java | 10 ++- ...BedrockInventoryTransactionTranslator.java | 20 +++-- .../player/BedrockActionTranslator.java | 10 ++- .../collision/CollisionManager.java | 61 ++++++++++++--- .../entity/JavaEntityPositionTranslator.java | 3 +- .../JavaEntityPropertiesTranslator.java | 7 +- .../world/block/BlockTranslator.java | 10 +++ .../geysermc/connector/utils/BlockUtils.java | 3 +- 11 files changed, 229 insertions(+), 44 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 2dcd49fb0..bc371690b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -263,7 +263,7 @@ public class Entity { metadata.getFlags().setFlag(EntityFlag.ON_FIRE, ((xd & 0x01) == 0x01) && !metadata.getFlags().getFlag(EntityFlag.FIRE_IMMUNE)); // Otherwise immune entities sometimes flicker onfire metadata.getFlags().setFlag(EntityFlag.SNEAKING, (xd & 0x02) == 0x02); metadata.getFlags().setFlag(EntityFlag.SPRINTING, (xd & 0x08) == 0x08); - metadata.getFlags().setFlag(EntityFlag.SWIMMING, ((xd & 0x10) == 0x10) && metadata.getFlags().getFlag(EntityFlag.SPRINTING)); // Otherwise swimming is enabled on older servers + // Swimming is ignored here and instead we rely on the pose metadata.getFlags().setFlag(EntityFlag.GLIDING, (xd & 0x80) == 0x80); // Armour stands are handled in their own class @@ -297,16 +297,37 @@ public class Entity { case 5: // no gravity metadata.getFlags().setFlag(EntityFlag.HAS_GRAVITY, !(boolean) entityMetadata.getValue()); break; - case 6: // Pose change - if (entityMetadata.getValue().equals(Pose.SLEEPING)) { - metadata.getFlags().setFlag(EntityFlag.SLEEPING, true); - metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.2f); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.2f); - } else if (metadata.getFlags().getFlag(EntityFlag.SLEEPING)) { - metadata.getFlags().setFlag(EntityFlag.SLEEPING, false); - metadata.put(EntityData.BOUNDING_BOX_WIDTH, getEntityType().getWidth()); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, getEntityType().getHeight()); + case 6: // Pose change - typically used for bounding box and not animation + Pose pose = (Pose) entityMetadata.getValue(); + + metadata.getFlags().setFlag(EntityFlag.SLEEPING, pose.equals(Pose.SLEEPING)); + // Triggered when crawling + metadata.getFlags().setFlag(EntityFlag.SWIMMING, pose.equals(Pose.SWIMMING)); + float width = entityType.getWidth(); + float height = entityType.getHeight(); + switch (pose) { + case SLEEPING: + if (this instanceof LivingEntity) { + width = 0.2f; + height = 0.2f; + } + break; + case SNEAKING: + if (entityType == EntityType.PLAYER) { + height = 1.5f; + } + break; + case FALL_FLYING: + case SPIN_ATTACK: + case SWIMMING: + if (entityType == EntityType.PLAYER) { + // Seems like this is only cared about for players; nothing else + height = 0.6f; + } + break; } + metadata.put(EntityData.BOUNDING_BOX_WIDTH, width); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height); break; } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/player/SessionPlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/player/SessionPlayerEntity.java index 24cc8c0e0..611d0d1e4 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/player/SessionPlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/player/SessionPlayerEntity.java @@ -26,7 +26,10 @@ package org.geysermc.connector.entity.player; import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.network.session.GeyserSession; import java.util.UUID; @@ -35,6 +38,10 @@ import java.util.UUID; * The entity class specifically for a {@link GeyserSession}'s player. */ public class SessionPlayerEntity extends PlayerEntity { + /** + * Whether to check for updated speed after all entity metadata has been processed + */ + private boolean refreshSpeed = false; private final GeyserSession session; @@ -43,7 +50,6 @@ public class SessionPlayerEntity extends PlayerEntity { valid = true; this.session = session; - this.session.getCollisionManager().updatePlayerBoundingBox(position); } @Override @@ -64,4 +70,27 @@ public class SessionPlayerEntity extends PlayerEntity { } super.setPosition(position); } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + super.updateBedrockMetadata(entityMetadata, session); + if (entityMetadata.getId() == 0) { + session.setSwimmingInWater((((byte) entityMetadata.getValue()) & 0x10) == 0x10 && metadata.getFlags().getFlag(EntityFlag.SPRINTING)); + refreshSpeed = true; + } else if (entityMetadata.getId() == 6) { + session.setPose((Pose) entityMetadata.getValue()); + refreshSpeed = true; + } + } + + @Override + public void updateBedrockMetadata(GeyserSession session) { + super.updateBedrockMetadata(session); + if (refreshSpeed) { + if (session.adjustSpeed()) { + updateBedrockAttributes(session); + } + refreshSpeed = false; + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 417fcb145..624a9eb30 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -35,6 +35,7 @@ import com.github.steveice10.mc.auth.service.MsaAuthenticationService; import com.github.steveice10.mc.protocol.MinecraftConstants; import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.SubProtocol; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; @@ -55,6 +56,8 @@ import com.nukkitx.protocol.bedrock.BedrockPacket; import com.nukkitx.protocol.bedrock.BedrockServerSession; import com.nukkitx.protocol.bedrock.data.*; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.*; import com.nukkitx.protocol.bedrock.v431.Bedrock_v431; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -78,6 +81,8 @@ import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.Tickable; +import org.geysermc.connector.entity.attribute.Attribute; +import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.entity.player.SessionPlayerEntity; import org.geysermc.connector.entity.player.SkullPlayerEntity; import org.geysermc.connector.inventory.Inventory; @@ -219,6 +224,12 @@ public class GeyserSession implements CommandSender { private boolean sneaking; + /** + * Stores the Java pose that the server and/or Geyser believes the player currently has. + */ + @Setter + private Pose pose = Pose.STANDING; + @Setter private boolean sprinting; @@ -228,6 +239,22 @@ public class GeyserSession implements CommandSender { @Setter private boolean jumping; + /** + * Whether the player is swimming in water. + * Used to update speed when crawling. + */ + @Setter + private boolean swimmingInWater; + + /** + * Tracks the original speed attribute. + * + * We need to do this in order to emulate speeds when sneaking under 1.5-blocks-tall areas if the player isn't sneaking, + * and when crawling. + */ + @Setter + private float originalSpeedAttribute; + /** * The dimension of the player. * As all entities are in the same world, this can be safely applied to all other entities. @@ -427,8 +454,7 @@ public class GeyserSession implements CommandSender { this.collisionManager = new CollisionManager(this); this.playerEntity = new SessionPlayerEntity(this); - this.worldCache = new WorldCache(this); - this.windowCache = new WindowCache(this); + collisionManager.updatePlayerBoundingBox(this.playerEntity.getPosition()); this.playerInventory = new PlayerInventory(); this.openInventory = null; @@ -829,8 +855,22 @@ public class GeyserSession implements CommandSender { public void setSneaking(boolean sneaking) { this.sneaking = sneaking; - collisionManager.updatePlayerBoundingBox(); - collisionManager.updateScaffoldingFlags(); + + // Update pose and bounding box on our end + if (!sneaking && adjustSpeed()) { + // Update attributes since we're still "sneaking" under a 1.5-block-tall area + playerEntity.updateBedrockAttributes(this); + // the server *should* update our pose once it has returned to normal + } else { + this.pose = sneaking ? Pose.SNEAKING : Pose.STANDING; + playerEntity.getMetadata().put(EntityData.BOUNDING_BOX_HEIGHT, sneaking ? 1.5f : playerEntity.getEntityType().getHeight()); + playerEntity.getMetadata().getFlags().setFlag(EntityFlag.SNEAKING, sneaking); + + collisionManager.updatePlayerBoundingBox(); + collisionManager.updateScaffoldingFlags(false); + } + + playerEntity.updateBedrockMetadata(this); if (mouseoverEntity != null) { // Horses, etc can change their property depending on if you're sneaking @@ -838,6 +878,35 @@ public class GeyserSession implements CommandSender { } } + public void setSwimming(boolean swimming) { + this.pose = swimming ? Pose.SWIMMING : Pose.STANDING; + playerEntity.getMetadata().put(EntityData.BOUNDING_BOX_HEIGHT, swimming ? 0.6f : playerEntity.getEntityType().getHeight()); + playerEntity.getMetadata().getFlags().setFlag(EntityFlag.SWIMMING, swimming); + playerEntity.updateBedrockMetadata(this); + } + + /** + * Adjusts speed if the player is crawling. + * + * @return true if attributes should be updated. + */ + public boolean adjustSpeed() { + Attribute currentPlayerSpeed = playerEntity.getAttributes().get(AttributeType.MOVEMENT_SPEED); + if (currentPlayerSpeed != null) { + if ((pose.equals(Pose.SNEAKING) && !sneaking && collisionManager.isUnderSlab()) || + (!swimmingInWater && playerEntity.getMetadata().getFlags().getFlag(EntityFlag.SWIMMING) && !collisionManager.isPlayerInWater())) { + // Either of those conditions means that Bedrock goes zoom when they shouldn't be + currentPlayerSpeed.setValue(originalSpeedAttribute / 3.32f); + return true; + } else if (originalSpeedAttribute != currentPlayerSpeed.getValue()) { + // Speed has reset to normal + currentPlayerSpeed.setValue(originalSpeedAttribute); + return true; + } + } + return false; + } + /** * Will be overwritten for GeyserConnect. */ diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockAdventureSettingsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockAdventureSettingsTranslator.java index 5274cef8e..bae205fd7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockAdventureSettingsTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockAdventureSettingsTranslator.java @@ -27,6 +27,7 @@ package org.geysermc.connector.network.translators.bedrock; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerAbilitiesPacket; import com.nukkitx.protocol.bedrock.data.AdventureSetting; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -37,8 +38,13 @@ public class BedrockAdventureSettingsTranslator extends PacketTranslator { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java index 2bee1c215..7c4a95cbb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityPropertiesTranslator.java @@ -67,7 +67,12 @@ public class JavaEntityPropertiesTranslator extends PacketTranslator> blocksIterator = BLOCKS_JSON.fields(); while (blocksIterator.hasNext()) { javaRuntimeId++; @@ -199,6 +201,9 @@ public abstract class BlockTranslator { } else if (javaId.startsWith("minecraft:spawner")) { spawnerRuntimeId = javaRuntimeId; + + } else if ("minecraft:water[level=0]".equals(javaId)) { + waterRuntimeId = javaRuntimeId; } } @@ -222,6 +227,11 @@ public abstract class BlockTranslator { } JAVA_RUNTIME_SPAWNER_ID = spawnerRuntimeId; + if (waterRuntimeId == -1) { + throw new AssertionError("Unable to find Java water in palette"); + } + JAVA_WATER_ID = waterRuntimeId; + BlockTranslator1_16_100.init(); BlockTranslator1_16_210.init(); BLOCKS_JSON = null; // We no longer require this so let it garbage collect away diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java index 36d1f5826..997e4aee4 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java @@ -133,8 +133,7 @@ public class BlockUtils { hasteLevel = session.getEffectCache().getEffectLevel(Effect.FASTER_DIG); miningFatigueLevel = session.getEffectCache().getEffectLevel(Effect.SLOWER_DIG); - boolean isInWater = session.getConnector().getConfig().isCacheChunks() - && session.getBlockTranslator().getBedrockBlockId(session.getConnector().getWorldManager().getBlockAt(session, session.getPlayerEntity().getPosition().toInt())) == session.getBlockTranslator().getBedrockWaterId(); + boolean isInWater = session.getCollisionManager().isPlayerInWater(); boolean insideOfWaterWithoutAquaAffinity = isInWater && ItemUtils.getEnchantmentLevel(session.getPlayerInventory().getItem(5).getNbt(), "minecraft:aqua_affinity") < 1;