一、前言
前段时间遇到这样一个问题:我希望监听玩家使用特殊盾牌格挡伤害的事件,然后给这个这个“特殊的盾牌”增添一个计数。
比如原先在Lore中显示“成功格挡数:0”,在格挡伤害后,将其修改为“成功格挡数:1”。
“我怎么知道玩家什么时候成功格挡了伤害?”,为了解决这个问题,我进行了以下探究。
二、探究
2.1 HumenEntity#isBlocking
首先我们要知道,HumenEntity 接口下存在一个 isBlocking() 方法。
它的描述是"Check if the player is currently blocking (ie with a shield)."。
即:检测玩家当前是否正使用盾牌格挡。
即:玩家主手或副手放置一个盾牌,然后按住右键,此时 player.isBlocking() 将返回 true;
       松开右键,或移除盾牌,此时 player.isBlocking() 将返回 false。
由此,一位群友提出:这个我会,之前我写过,只要你监听 EntityDamageByEntityEvent,然后进行如下操作:// 获取受到伤害的实体
Entity entity = event.getEntity();
// 收到伤害的实体不是玩家则停止操作
if (!(entity instanceof Player)) return;
// 将 entity 强制转换为 Player 类型
Player player = (Player) entity;
// 判断玩家是否处于格挡状态
if (player.isBlocking()) {
    // 进行添加计数操作
}复制代码这样就能正确判断“玩家使用盾牌成功格挡伤害”了吗?我认为不能。
假设玩家A举盾时玩家B从他背后向他膝盖射了一箭,成功格挡了吗?没有。但这种方式却认为成功格挡了。
因为这种判断方式只是简单地认为“玩家举盾时受到伤害,代表成功格挡”,而这很明显是不严谨的。
因此,此时应该进行寻根溯源,反编译服务端后针对 isBlocking 方法进行一揽子搜索引用,找到格挡减伤的判断根源
2.2 寻根溯源
Lorg/bukkit/craftbukkit/v1_20_R1/entity/CraftHumanEntity;isBlocking()Z    public boolean isBlocking() {
        return this.getHandle().isBlocking();
    }复制代码Lorg/bukkit/craftbukkit/v1_20_R1/entity/CraftHumanEntity;getHandle()Lnet/minecraft/world/entity/player/Player;    public Player getHandle() {
        return (Player)this.entity;
    }复制代码public abstract class Player extends LivingEntity
Lnet/minecraft/world/entity/LivingEntity;isBlocking()Z    public boolean isBlocking() {
        if (this.isUsingItem() && !this.useItem.isEmpty()) {
            net.minecraft.world.item.Item item = this.useItem.getItem();
            return item.getUseAnimation(this.useItem) != UseAnim.BLOCK ? false : item.getUseDuration(this.useItem) - this.useItemRemaining >= this.getShieldBlockingDelay();
        } else {
            return false;
        }
    }复制代码Lnet/minecraft/world/entity/LivingEntity;damageEntity0(Lnet/minecraft/world/damagesource/DamageSource;F)Z    protected boolean damageEntity0(final DamageSource damagesource, float f) {
        if (!this.isInvulnerableTo(damagesource)) {
            boolean human = this instanceof Player;
            float originalDamage = f;
            // 省略
            Function blocking = new Function() {
                public Double apply(Double f) {
                    return -(LivingEntity.this.isDamageSourceBlocked(damagesource) ? f : 0.0D);
                }
            };
            float blockingModifier = ((Double)blocking.apply((double)f)).floatValue();
            f += blockingModifier;
            // 省略
            EntityDamageEvent event = CraftEventFactory.handleLivingEntityDamageEvent(this, damagesource, (double)originalDamage, (double)hardHatModifier, (double)blockingModifier, (double)armorModifier, (double)resistanceModifier, (double)magicModifier, (double)absorptionModifier, hardHat, blocking, armor, resistance, magic, absorption);
            // 省略
            }
        } else {
            return false;
        }
    }复制代码Lnet/minecraft/world/entity/LivingEntity;isDamageSourceBlocked(Lnet/minecraft/world/damagesource/DamageSource;)Z    public boolean isDamageSourceBlocked(DamageSource source) {
        Entity entity = source.getDirectEntity();
        boolean flag = false;
        if (entity instanceof AbstractArrow) {
            AbstractArrow entityarrow = (AbstractArrow)entity;
            if (entityarrow.getPierceLevel() > 0) {
                flag = true;
            }
        }
        if (!source.is(DamageTypeTags.BYPASSES_SHIELD) && this.isBlocking() && !flag) {
            Vec3 vec3d = source.getSourcePosition();
            if (vec3d != null) {
                Vec3 vec3d1 = this.getViewVector(1.0F);
                Vec3 vec3d2 = vec3d.vectorTo(this.position()).normalize();
                vec3d2 = new Vec3(vec3d2.x, 0.0D, vec3d2.z);
                if (vec3d2.dot(vec3d1)
                    return true;
                }
            }
        }
        return false;
    }复制代码Lorg/bukkit/craftbukkit/v1_20_R1/event/CraftEventFactory;handleLivingEntityDamageEvent(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;DDDDDDDLcom/google/common/base/Function;Lcom/google/common/base/Function;Lcom/google/common/base/Function;Lcom/google/common/base/Function;Lcom/google/common/base/Function;Lcom/google/common/base/Function;)Lorg/bukkit/event/entity/EntityDamageEvent;    public static EntityDamageEvent handleLivingEntityDamageEvent(Entity damagee, DamageSource source, double rawDamage, double hardHatModifier, double blockingModifier, double armorModifier, double resistanceModifier, double magicModifier, double absorptionModifier, Function hardHat, Function blocking, Function armor, Function resistance, Function magic, Function absorption) {
        Map modifiers = new EnumMap(DamageModifier.class);
        Map> modifierFunctions = new EnumMap(DamageModifier.class);
        modifiers.put(DamageModifier.BASE, rawDamage);
        modifierFunctions.put(DamageModifier.BASE, ZERO);
        if (source.is(DamageTypes.FALLING_BLOCK) || source.is(DamageTypes.FALLING_ANVIL)) {
            modifiers.put(DamageModifier.HARD_HAT, hardHatModifier);
            modifierFunctions.put(DamageModifier.HARD_HAT, hardHat);
        }
        if (damagee instanceof net.minecraft.world.entity.player.Player) {
            modifiers.put(DamageModifier.BLOCKING, blockingModifier);
            modifierFunctions.put(DamageModifier.BLOCKING, blocking);
        }
        modifiers.put(DamageModifier.ARMOR, armorModifier);
        modifierFunctions.put(DamageModifier.ARMOR, armor);
        modifiers.put(DamageModifier.RESISTANCE, resistanceModifier);
        modifierFunctions.put(DamageModifier.RESISTANCE, resistance);
        modifiers.put(DamageModifier.MAGIC, magicModifier);
        modifierFunctions.put(DamageModifier.MAGIC, magic);
        modifiers.put(DamageModifier.ABSORPTION, absorptionModifier);
        modifierFunctions.put(DamageModifier.ABSORPTION, absorption);
        return handleEntityDamageEvent(damagee, source, modifiers, modifierFunctions);
    }复制代码Lorg/bukkit/craftbukkit/v1_20_R1/event/CraftEventFactory;handleEntityDamageEvent(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;Ljava/util/Map;Ljava/util/Map;)Lorg/bukkit/event/entity/EntityDamageEvent;    private static EntityDamageEvent handleEntityDamageEvent(Entity entity, DamageSource source, Map modifiers, Map> modifierFunctions) {
        return handleEntityDamageEvent(entity, source, modifiers, modifierFunctions, false);
    }复制代码Lorg/bukkit/craftbukkit/v1_20_R1/event/CraftEventFactory;handleEntityDamageEvent(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/damagesource/DamageSource;Ljava/util/Map;Ljava/util/Map;Z)Lorg/bukkit/event/entity/EntityDamageEvent;// 过长,省略,关键点为 new EntityDamageByEntityEvent
new EntityDamageByEntityEvent(damager, entity.getBukkitEntity(), cause, modifiers, modifierFunctions, source.isCritical())复制代码根据这一揽子追溯我们可以得知伤害事件的触发要经历以下步骤:
触发伤害事件前,NMS 中对护甲、格挡、伤害吸收等内容分别进行一套计算,创建对应的 DamageModifier将所有 DamageModifier 传入 handleLivingEntityDamageEvent 方法,用于创建 EntityDamageEvent所有 DamageModifier 被汇总至 Map modifiersmodifiers 作为一个参数,被用于构建 EntityDamageByEntityEvent
而创建 blocking 相关 DamageModifier 前,需要通过通过 isDamageSourceBlocked 方法进行以下判断:
判断格挡箭矢的穿透等判断伤害源是否可以无视格挡通过 isBlocking 方法判断玩家举起的是不是盾牌通过 isBlocking 方法判断盾牌是否处于冷却判断伤害源与玩家所成的攻击角度,计算这个角度的攻击是否可以被盾牌格挡
通过以上判断后,blocking 相关的 DamageModifier 将吸收所有剩余伤害。
如果未通过判断,对应 DamageModifier 吸收的伤害数值将为 0。
由此我们可以得知:通过 DamageModifier 才能正确判断伤害事件中是否存在被格挡吸收的伤害。
2.3 EntityDamageEvent#getDamage
Lorg/bukkit/event/entity/EntityDamageEvent;getDamage(Lorg/bukkit/event/entity/EntityDamageEvent$DamageModifier;)D    public double getDamage(@NotNull DamageModifier type) throws IllegalArgumentException {
        Validate.notNull(type, "Cannot have null DamageModifier");
        final Double damage = modifiers.get(type);
        return damage == null ? 0 : damage;
    }复制代码EntityDamageByEntityEvent 是 EntityDamageEvent 的子类,可以使用其中的 getDamage 方法。
通过上面的一系列探究我们可以得知,只需要获取 blocking 相关 DamageModifier 吸收的伤害数值,就能判断是否发生格挡吸收伤害事件。
三、结论
通过以上探究,得出正确的判断代码如下:// 获取受到伤害的实体
Entity entity = event.getEntity();
// 收到伤害的实体不是玩家则停止操作
if (!(entity instanceof Player)) return;
// 将 entity 强制转换为 Player 类型
Player player = (Player) entity;
// 判断玩家是否通过格挡吸收了伤害
if (event.getDamage(EntityDamageEvent.DamageModifier.BLOCKING) > 0.0) {
    // 进行添加计数操作
}复制代码