mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
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.
This commit is contained in:
parent
7f5d81772b
commit
0829b5cd4e
7 changed files with 156 additions and 54 deletions
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -48,8 +48,10 @@ public class BedrockAnimateTranslator extends PacketTranslator<AnimatePacket> {
|
|||
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
|
||||
);
|
||||
|
|
|
@ -411,22 +411,20 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
|
||||
//https://wiki.vg/Protocol#Interact_Entity
|
||||
switch (packet.getActionType()) {
|
||||
case 0: //Interact
|
||||
processEntityInteraction(session, packet, entity);
|
||||
break;
|
||||
case 1: //Attack
|
||||
case 0 -> 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;
|
||||
}
|
||||
|
|
|
@ -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<PlayerActionPacket
|
|||
ServerboundPlayerCommandPacket startSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SNEAKING);
|
||||
session.sendDownstreamPacket(startSneakPacket);
|
||||
|
||||
// Toggle the shield, if relevant
|
||||
PlayerInventory playerInv = session.getPlayerInventory();
|
||||
ItemMapping shield = session.getItemMappings().getMapping("minecraft:shield");
|
||||
if ((playerInv.getItemInHand().getJavaId() == shield.getJavaId()) ||
|
||||
(playerInv.getOffhand().getJavaId() == shield.getJavaId())) {
|
||||
ServerboundUseItemPacket useItemPacket;
|
||||
if (playerInv.getItemInHand().getJavaId() == shield.getJavaId()) {
|
||||
useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND);
|
||||
} else {
|
||||
// Else we just assume it's the offhand, to simplify logic and to assure the packet gets sent
|
||||
useItemPacket = new ServerboundUseItemPacket(Hand.OFF_HAND);
|
||||
}
|
||||
session.sendDownstreamPacket(useItemPacket);
|
||||
session.getPlayerEntity().setFlag(EntityFlag.BLOCKING, true);
|
||||
// metadata will be updated when sneaking
|
||||
}
|
||||
|
||||
session.setSneaking(true);
|
||||
session.startSneaking();
|
||||
break;
|
||||
case STOP_SNEAK:
|
||||
ServerboundPlayerCommandPacket stopSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SNEAKING);
|
||||
session.sendDownstreamPacket(stopSneakPacket);
|
||||
|
||||
// Stop shield, if necessary
|
||||
if (session.getPlayerEntity().getFlag(EntityFlag.BLOCKING)) {
|
||||
ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, BlockUtils.POSITION_ZERO, Direction.DOWN);
|
||||
session.sendDownstreamPacket(releaseItemPacket);
|
||||
session.getPlayerEntity().setFlag(EntityFlag.BLOCKING, false);
|
||||
// metadata will be updated when sneaking
|
||||
}
|
||||
|
||||
session.setSneaking(false);
|
||||
session.stopSneaking();
|
||||
break;
|
||||
case START_SPRINT:
|
||||
if (!entity.getFlag(EntityFlag.SWIMMING)) {
|
||||
|
|
|
@ -50,6 +50,9 @@ public class JavaAnimateTranslator extends PacketTranslator<ClientboundAnimatePa
|
|||
switch (packet.getAnimation()) {
|
||||
case SWING_ARM:
|
||||
animatePacket.setAction(AnimatePacket.Action.SWING_ARM);
|
||||
if (entity.getEntityId() == session.getPlayerEntity().getEntityId()) {
|
||||
session.activateArmAnimationTicking();
|
||||
}
|
||||
break;
|
||||
case SWING_OFFHAND:
|
||||
// Use the OptionalPack to trigger the animation
|
||||
|
|
Loading…
Reference in a new issue