Issue: When using an Arrow of Harming, a fully drawn bow may deal less damage than a barely drawn bow.
Mechanism: In 26.3, Post Hit Damage Immunity (hereinafter referred to as PHDI) is calculated as follows: if this.damageCooldownTime > 10 and the damage does not have the BYPASSES_COOLDOWN tag, then the incoming damage is compared with this.lastHurt, and the actual damage is obtained as damage - this.lastHurt; finally, this.lastHurt is set to damage, thereby implementing “within one PHDI, the highest damage value takes effect.” However, PHDI adjusts the damage value first, before all other damage reduction effects. This means that regardless of what damage reduction methods the mob has, this.lastHurt will only record the damage before damage reduction, i.e., only the raw damage. For a bow enchanted with Power III, using a normal arrow and fully drawing the bow can deal 13 to 20 arrow damage; if it is an Arrow of Harming II, an additional 12 magic damage is applied. When the player wears a full Netherite armor set, the 13 to 20 arrow damage is reduced to 3.95 to 7.2, but this.lastHurt still remains 13 to 20. Then the 12 magic damage is nullified because PHDI has recorded 13 to 20, so the final damage is 3.95 to 7.2. However, if a normal arrow is used with a barely drawn bow, it deals only about 2 arrow damage, while an Arrow of Harming II still applies 12 magic damage. This 2 arrow damage is reduced to about 0.4, and this.lastHurt is 2. Then the 12 magic damage, because PHDI has recorded 2, leaves 10 magic damage remaining, so the final total damage is 10.4.
Steps to Reproduce:
Two players:
one wears a full Netherite armor set;
the other shoots using a bow enchanted with Power III and an Arrow of Harming
Fully draw the bow, observe the damage value;
After the target's health is fully restored, barely draw the bow, observe the damage value.
public boolean hurtServer(final ServerLevel level, final DamageSource source, float damage) {
if (this.isInvulnerableTo(level, source)) {
return false;
} else if (this.isDeadOrDying()) {
return false;
} else if (source.is(DamageTypeTags.IS_FIRE) && this.hasEffect(MobEffects.FIRE_RESISTANCE)) {
return false;
} else {
if (this.isSleeping()) {
this.stopSleeping();
}
this.noActionTime = 0;
if (damage < 0.0F) {
damage = 0.0F;
}
float originalDamage = damage;
ItemStack itemInUse = this.getUseItem();
float damageBlocked = this.applyItemBlocking(level, source, damage);
damage -= damageBlocked;
boolean blocked = damageBlocked > 0.0F;
if (source.is(DamageTypeTags.IS_FREEZING) && this.is(EntityTypeTags.FREEZE_HURTS_EXTRA_TYPES)) {
damage *= 5.0F;
}
if (source.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
this.hurtHelmet(source, damage);
damage *= 0.75F;
}
if (Float.isNaN(damage) || Float.isInfinite(damage)) {
damage = Float.MAX_VALUE;
}
boolean tookFullDamage = true;
if ((float)this.damageCooldownTime > 10.0F && !source.is(DamageTypeTags.BYPASSES_COOLDOWN)) {
if (damage <= this.lastHurt) {
return false;
}
this.actuallyHurt(level, source, damage - this.lastHurt);
this.lastHurt = damage;
tookFullDamage = false;
} else {
this.lastHurt = damage;
this.damageCooldownTime = 20;
this.actuallyHurt(level, source, damage);
this.hurtDuration = 10;
this.hurtTime = this.hurtDuration;
}
………………
}
}
………………
protected void actuallyHurt(final ServerLevel level, final DamageSource source, float dmg) {
if (!this.isInvulnerableTo(level, source)) {
dmg = this.getDamageAfterArmorAbsorb(source, dmg);
dmg = this.getDamageAfterMagicAbsorb(source, dmg);
float originalDamage = dmg;
dmg = Math.max(dmg - this.getAbsorptionAmount(), 0.0F);
this.setAbsorptionAmount(this.getAbsorptionAmount() - (originalDamage - dmg));
float absorbedDamage = originalDamage - dmg;
if (absorbedDamage > 0.0F && absorbedDamage < 3.4028235E37F) {
Entity var7 = source.getEntity();
if (var7 instanceof ServerPlayer) {
ServerPlayer serverPlayer = (ServerPlayer)var7;
serverPlayer.awardStat(Stats.DAMAGE_DEALT_ABSORBED, Math.round(absorbedDamage * 10.0F));
}
}
if (dmg != 0.0F) {
this.getCombatTracker().recordDamage(source, dmg);
this.setHealth(this.getHealth() - dmg);
this.setAbsorptionAmount(this.getAbsorptionAmount() - dmg);
this.gameEvent(GameEvent.ENTITY_DAMAGE);
}
}
}Expected: Regardless of what arrow is used, the damage from a fully drawn bow should always be higher than the damage from a barely drawn bow.
Additional Notes: The underlying mechanism of this bug is quite profound: it stems from the ordering of PHDI and damage reduction calculations, which can actually lead to many abnormal outcomes, and “When using an Arrow of Harming, a fully drawn bow may deal less damage than a barely drawn bow.“ is only one of them. Since I cannot include multiple issues in a single report, I have split them up and reported them separately.
Linked issues
Attachments
Comments 2
Can confirm in 26.4 snapshot 5. Although this behavior may fall under MC-107856, I have opted instead to leave this report open and link it as caused by that issue. If Mojang leaves this issue open, MC-251707 should be forward-resolved into here, and MC-193178 should be marked as related. However, it would also be reasonable to resolve this as another duplicate of MC-107856.
In the video below, the arrow is Harming II, the potion is Healing II, and the mannequing was summoned with /summon minecraft:mannequin ~ ~ ~ {equipment:{head:{id:netherite_helmet},chest:{id:netherite_chestplate},legs:{id:netherite_leggings},feet:{id:netherite_boots}}}.
Thank you for helping us improve Minecraft! We saved your files: