From 0829b5cd4ebadd11ebef203c5c624eedad477bb3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 15 Mar 2022 13:34:56 -0400 Subject: [PATCH] Replicate Bedrock shield behavior more accurately If the player swings, then they cannot be holding their shield at the same time. Also fixes an animation edge case with other players. --- .../geyser/entity/type/LivingEntity.java | 22 +++- .../type/player/SessionPlayerEntity.java | 11 ++ .../geyser/session/GeyserSession.java | 112 +++++++++++++++++- .../bedrock/BedrockAnimateTranslator.java | 6 +- ...BedrockInventoryTransactionTranslator.java | 20 ++-- .../player/BedrockActionTranslator.java | 36 +----- .../java/entity/JavaAnimateTranslator.java | 3 + 7 files changed, 156 insertions(+), 54 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index a5214854e..0cce0f8df 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -99,13 +99,15 @@ public class LivingEntity extends Entity { public void setLivingEntityFlags(ByteEntityMetadata entityMetadata) { byte xd = entityMetadata.getPrimitiveValue(); - // Blocking gets triggered when using a bow, but if we set USING_ITEM for all items, it may look like - // you're "mining" with ex. a shield. + boolean isUsingItem = (xd & 0x01) == 0x01; + boolean isUsingOffhand = (xd & 0x02) == 0x02; + ItemMapping shield = session.getItemMappings().getStoredItems().shield(); - boolean isUsingShield = (getHand().getId() == shield.getBedrockId() || - getHand().equals(ItemData.AIR) && getOffHand().getId() == shield.getBedrockId()); - setFlag(EntityFlag.USING_ITEM, (xd & 0x01) == 0x01 && !isUsingShield); - setFlag(EntityFlag.BLOCKING, (xd & 0x01) == 0x01); + boolean isUsingShield = hasShield(isUsingOffhand, shield); + + setFlag(EntityFlag.USING_ITEM, isUsingItem && !isUsingShield); + // Override the blocking + setFlag(EntityFlag.BLOCKING, isUsingItem && isUsingShield); // Riptide spin attack setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, (xd & 0x04) == 0x04); @@ -142,6 +144,14 @@ public class LivingEntity extends Entity { } } + protected boolean hasShield(boolean offhand, ItemMapping shieldMapping) { + if (offhand) { + return offHand.getId() == shieldMapping.getBedrockId(); + } else { + return hand.getId() == shieldMapping.getBedrockId(); + } + } + @Override protected boolean isShaking() { return isMaxFrozenState; 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 8dd24bdb8..077f82171 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 @@ -38,6 +38,7 @@ import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.Getter; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.AttributeUtils; @@ -167,6 +168,16 @@ public class SessionPlayerEntity extends PlayerEntity { return super.createHealthAttribute(); } + @Override + protected boolean hasShield(boolean offhand, ItemMapping shieldMapping) { + // Must be overridden to point to the player's inventory cache + if (offhand) { + return session.getPlayerInventory().getOffhand().getJavaId() == shieldMapping.getJavaId(); + } else { + return session.getPlayerInventory().getItemInHand().getJavaId() == shieldMapping.getJavaId(); + } + } + @Override public void updateBedrockMetadata() { super.updateBedrockMetadata(); 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 fe63b0b0c..3cfe0e550 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -36,8 +36,11 @@ import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.ProtocolState; import com.github.steveice10.mc.protocol.data.UnexpectedEncryptionException; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; +import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.player.HandPreference; +import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; import com.github.steveice10.mc.protocol.data.game.setting.ChatVisibility; import com.github.steveice10.mc.protocol.data.game.setting.SkinPart; import com.github.steveice10.mc.protocol.data.game.statistic.CustomStatistic; @@ -46,6 +49,8 @@ import com.github.steveice10.mc.protocol.packet.handshake.serverbound.ClientInte import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientInformationPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemPacket; import com.github.steveice10.mc.protocol.packet.login.serverbound.ServerboundCustomQueryPacket; import com.github.steveice10.packetlib.BuiltinFlags; import com.github.steveice10.packetlib.Session; @@ -97,6 +102,7 @@ import org.geysermc.geyser.level.physics.CollisionManager; import org.geysermc.geyser.network.netty.LocalSession; import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.registry.type.BlockMappings; +import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.session.auth.AuthData; import org.geysermc.geyser.session.auth.AuthType; @@ -107,10 +113,7 @@ import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.text.MessageTranslator; -import org.geysermc.geyser.util.ChunkUtils; -import org.geysermc.geyser.util.DimensionUtils; -import org.geysermc.geyser.util.LoginEncryptionUtils; -import org.geysermc.geyser.util.MathUtils; +import org.geysermc.geyser.util.*; import javax.annotation.Nonnull; import java.net.ConnectException; @@ -422,6 +425,13 @@ public class GeyserSession implements GeyserConnection, CommandSender { @Setter private long lastVehicleMoveTimestamp = System.currentTimeMillis(); + /** + * Counts how many ticks have occurred since an arm animation started. + * -1 means there is no active arm swing. + */ + @Getter(AccessLevel.NONE) + private int armAnimationTicks = -1; + /** * Controls whether the daylight cycle gamerule has been sent to the client, so the sun/moon remain motionless. */ @@ -1107,6 +1117,34 @@ public class GeyserSession implements GeyserConnection, CommandSender { for (Tickable entity : entityCache.getTickableEntities()) { entity.tick(); } + + if (armAnimationTicks != -1) { + // As of 1.18.2 Java Edition, it appears that the swing time is dynamically updated depending on the + // player's effect status, but the animation can cut short if the duration suddenly decreases + // (from suddenly no longer having mining fatigue, for example) + // This math is referenced from Java Edition 1.18.2 + int swingTotalDuration; + int hasteLevel = Math.max(effectCache.getHaste(), effectCache.getConduitPower()); + if (hasteLevel > 0) { + swingTotalDuration = 6 - hasteLevel; + } else { + int miningFatigueLevel = effectCache.getMiningFatigue(); + if (miningFatigueLevel > 0) { + swingTotalDuration = 6 + miningFatigueLevel * 2; + } else { + swingTotalDuration = 6; + } + } + if (++armAnimationTicks >= swingTotalDuration) { + if (sneaking) { + // Attempt to re-activate blocking as our swing animation is up + if (attemptToBlock()) { + playerEntity.updateBedrockMetadata(); + } + } + armAnimationTicks = -1; + } + } } catch (Throwable throwable) { throwable.printStackTrace(); } @@ -1116,7 +1154,23 @@ public class GeyserSession implements GeyserConnection, CommandSender { this.authData = authData; } - public void setSneaking(boolean sneaking) { + public void startSneaking() { + // Toggle the shield, if there is no ongoing arm animation + // This matches Bedrock Edition behavior as of 1.18.12 + if (armAnimationTicks == -1) { + attemptToBlock(); + } + + setSneaking(true); + } + + public void stopSneaking() { + disableBlocking(); + + setSneaking(false); + } + + private void setSneaking(boolean sneaking) { this.sneaking = sneaking; // Update pose and bounding box on our end @@ -1201,6 +1255,54 @@ public class GeyserSession implements GeyserConnection, CommandSender { return null; } + /** + * Checks to see if a shield is in either hand to activate blocking. If so, it sets the Bedrock client to display + * blocking and sends a packet to the Java server. + */ + private boolean attemptToBlock() { + ItemMapping shield = itemMappings.getStoredItems().shield(); + + ServerboundUseItemPacket useItemPacket; + if (playerInventory.getItemInHand().getJavaId() == shield.getJavaId()) { + useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND); + } else if (playerInventory.getOffhand().getJavaId() == shield.getJavaId()) { + useItemPacket = new ServerboundUseItemPacket(Hand.OFF_HAND); + } else { + // No blocking + return false; + } + + sendDownstreamPacket(useItemPacket); + playerEntity.setFlag(EntityFlag.BLOCKING, true); + // Metadata should be updated later + return true; + } + + /** + * Starts ticking the amount of time that the Bedrock client has been swinging their arm, and disables blocking if + * blocking. + */ + public void activateArmAnimationTicking() { + armAnimationTicks = 0; + if (disableBlocking()) { + playerEntity.updateBedrockMetadata(); + } + } + + /** + * Indicates to the client to stop blocking and tells the Java server the same. + */ + private boolean disableBlocking() { + if (playerEntity.getFlag(EntityFlag.BLOCKING)) { + ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, + BlockUtils.POSITION_ZERO, Direction.DOWN); + sendDownstreamPacket(releaseItemPacket); + playerEntity.setFlag(EntityFlag.BLOCKING, false); + return true; + } + return false; + } + /** * Will be overwritten for GeyserConnect. */ diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java index ac4a4bb2e..670e55785 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockAnimateTranslator.java @@ -48,8 +48,10 @@ public class BedrockAnimateTranslator extends PacketTranslator { switch (packet.getAction()) { case SWING_ARM -> // Delay so entity damage can be processed first - session.scheduleInEventLoop(() -> - session.sendDownstreamPacket(new ServerboundSwingPacket(Hand.MAIN_HAND)), + session.scheduleInEventLoop(() -> { + session.sendDownstreamPacket(new ServerboundSwingPacket(Hand.MAIN_HAND)); + session.activateArmAnimationTicking(); + }, 25, TimeUnit.MILLISECONDS ); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 7129c1318..f120e4a19 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -411,22 +411,20 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator processEntityInteraction(session, packet, entity); // Interact + case 1 -> { // Attack + int entityId; if (entity.getDefinition() == EntityDefinitions.ENDER_DRAGON) { // Redirects the attack to its body entity, this only happens when // attacking the underbelly of the ender dragon - ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(entity.getEntityId() + 3, - InteractAction.ATTACK, session.isSneaking()); - session.sendDownstreamPacket(attackPacket); + entityId = entity.getEntityId() + 3; } else { - ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(entity.getEntityId(), - InteractAction.ATTACK, session.isSneaking()); - session.sendDownstreamPacket(attackPacket); + entityId = entity.getEntityId(); } - break; + ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(entityId, + InteractAction.ATTACK, session.isSneaking()); + session.sendDownstreamPacket(attackPacket); + } } break; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java index 52129797b..5429899fa 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java @@ -28,7 +28,10 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.object.Direction; import com.github.steveice10.mc.protocol.data.game.entity.player.*; -import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.*; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerCommandPacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; @@ -39,10 +42,8 @@ import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; -import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.registry.BlockRegistries; -import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; @@ -105,38 +106,13 @@ public class BedrockActionTranslator extends PacketTranslator