From e663f9998200bada01bea7b82316f120b8cc5e62 Mon Sep 17 00:00:00 2001 From: Illia Bondar Date: Wed, 30 Apr 2025 20:24:33 +0300 Subject: [PATCH] Add combat tracker API (#11853) --- build-data/paper.at | 5 + .../io/papermc/paper/InternalAPIBridge.java | 28 +++++ .../paper/world/damagesource/CombatEntry.java | 82 +++++++++++++ .../world/damagesource/CombatTracker.java | 110 ++++++++++++++++++ .../world/damagesource/FallLocationType.java | 63 ++++++++++ .../damagesource/FallLocationTypeImpl.java | 14 +++ .../java/org/bukkit/entity/LivingEntity.java | 11 ++ .../damagesource/CombatTracker.java.patch | 30 +++++ .../paper/PaperServerInternalAPIBridge.java | 49 ++++++++ .../damagesource/PaperCombatEntryWrapper.java | 32 +++++ .../PaperCombatTrackerWrapper.java | 110 ++++++++++++++++++ .../craftbukkit/entity/CraftLivingEntity.java | 12 ++ 12 files changed, 546 insertions(+) create mode 100644 paper-api/src/main/java/io/papermc/paper/world/damagesource/CombatEntry.java create mode 100644 paper-api/src/main/java/io/papermc/paper/world/damagesource/CombatTracker.java create mode 100644 paper-api/src/main/java/io/papermc/paper/world/damagesource/FallLocationType.java create mode 100644 paper-api/src/main/java/io/papermc/paper/world/damagesource/FallLocationTypeImpl.java create mode 100644 paper-server/patches/sources/net/minecraft/world/damagesource/CombatTracker.java.patch create mode 100644 paper-server/src/main/java/io/papermc/paper/world/damagesource/PaperCombatEntryWrapper.java create mode 100644 paper-server/src/main/java/io/papermc/paper/world/damagesource/PaperCombatTrackerWrapper.java diff --git a/build-data/paper.at b/build-data/paper.at index 494df2d8b3..7eec65d5c7 100644 --- a/build-data/paper.at +++ b/build-data/paper.at @@ -138,6 +138,11 @@ public net.minecraft.world.BossEvent overlay public net.minecraft.world.CompoundContainer container1 public net.minecraft.world.CompoundContainer container2 public net.minecraft.world.SimpleContainer items +public net.minecraft.world.damagesource.CombatTracker entries +public net.minecraft.world.damagesource.CombatTracker getMostSignificantFall()Lnet/minecraft/world/damagesource/CombatEntry; +public net.minecraft.world.damagesource.CombatTracker inCombat +public net.minecraft.world.damagesource.CombatTracker mob +public net.minecraft.world.damagesource.CombatTracker takingDamage public net.minecraft.world.damagesource.DamageSource (Lnet/minecraft/core/Holder;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/Vec3;)V public net.minecraft.world.effect.MobEffect attributeModifiers public net.minecraft.world.effect.MobEffect$AttributeTemplate diff --git a/paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java b/paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java index 861c42cbfa..422fdd93d1 100644 --- a/paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java +++ b/paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java @@ -1,10 +1,15 @@ package io.papermc.paper; +import io.papermc.paper.world.damagesource.CombatEntry; +import io.papermc.paper.world.damagesource.FallLocationType; import net.kyori.adventure.util.Services; import org.bukkit.block.Biome; import org.bukkit.damage.DamageEffect; +import org.bukkit.damage.DamageSource; +import org.bukkit.entity.LivingEntity; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Static bridge to the server internals. @@ -45,5 +50,28 @@ public interface InternalAPIBridge { @Deprecated(forRemoval = true, since = "1.21.5") @ApiStatus.ScheduledForRemoval(inVersion = "1.22") Biome constructLegacyCustomBiome(); + + /** + * Creates a new combat entry. + *

+ * The fall location and fall distance will be calculated from the entity's current state. + * + * @param entity entity + * @param damageSource damage source + * @param damage damage amount + * @return new combat entry + */ + CombatEntry createCombatEntry(LivingEntity entity, DamageSource damageSource, float damage); + + /** + * Creates a new combat entry + * + * @param damageSource damage source + * @param damage damage amount + * @param fallLocationType fall location type + * @param fallDistance fall distance + * @return combat entry + */ + CombatEntry createCombatEntry(DamageSource damageSource, float damage, @Nullable FallLocationType fallLocationType, float fallDistance); } diff --git a/paper-api/src/main/java/io/papermc/paper/world/damagesource/CombatEntry.java b/paper-api/src/main/java/io/papermc/paper/world/damagesource/CombatEntry.java new file mode 100644 index 0000000000..7b5256cffe --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/world/damagesource/CombatEntry.java @@ -0,0 +1,82 @@ +package io.papermc.paper.world.damagesource; + +import io.papermc.paper.InternalAPIBridge; +import org.bukkit.damage.DamageSource; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** + * Represents a combat entry + */ +@NullMarked +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public interface CombatEntry { + + /** + * Gets the damage source. + * + * @return the damage source + */ + DamageSource getDamageSource(); + + /** + * Gets the amount of damage caused. + * + * @return the amount of damage caused + */ + float getDamage(); + + /** + * Gets the fall location type at the time of this damage. + * + * @return the fall location type + */ + @Nullable FallLocationType getFallLocationType(); + + /** + * Gets the fall distance at the time of this damage. + * + * @return the fall distance + */ + float getFallDistance(); + + /** + * Creates a new combat entry. + *

+ * The fall location and fall distance will be calculated from the entity's current state. + * + * @param entity entity + * @param damageSource damage source + * @param damage damage amount + * @return combat entry + * @see #combatEntry(DamageSource, float, FallLocationType, float) + */ + static CombatEntry combatEntry(final LivingEntity entity, final DamageSource damageSource, final float damage) { + return InternalAPIBridge.get().createCombatEntry(entity, damageSource, damage); + } + + /** + * Creates a new combat entry + * + * @param damageSource damage source + * @param damage damage amount + * @param fallLocationType fall location type + * @param fallDistance fall distance + * @return a new combat entry + * @see CombatTracker#calculateFallLocationType() + * @see Entity#getFallDistance() + */ + static CombatEntry combatEntry( + final DamageSource damageSource, + final float damage, + @Nullable final FallLocationType fallLocationType, + final float fallDistance + ) { + return InternalAPIBridge.get().createCombatEntry(damageSource, damage, fallLocationType, fallDistance); + } + +} diff --git a/paper-api/src/main/java/io/papermc/paper/world/damagesource/CombatTracker.java b/paper-api/src/main/java/io/papermc/paper/world/damagesource/CombatTracker.java new file mode 100644 index 0000000000..22c6d97320 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/world/damagesource/CombatTracker.java @@ -0,0 +1,110 @@ +package io.papermc.paper.world.damagesource; + +import net.kyori.adventure.text.Component; +import org.bukkit.entity.LivingEntity; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +/** + * Represents entity's combat tracker + */ +@NullMarked +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public interface CombatTracker { + + /** + * Gets the entity behind this combat tracker. + * + * @return the entity behind this combat tracker + */ + LivingEntity getEntity(); + + /** + * Gets the list of recorded combat entries. + *

+ * The returned list is a copy, so any modifications + * to its contents won't affect this entity's + * combat history. + * + * @return the list of combat entries + * @see #setCombatEntries(List) + */ + List getCombatEntries(); + + /** + * Sets the entity's combat history. + *

+ * Note that overriding the entity's combat history won't + * affect the entity's current or new combat state. + * Reset the current combat state and register new combat entries instead + * if you want the new history to affect the combat state. + * + * @param combatEntries combat entries + * @see #resetCombatState() + * @see #addCombatEntry(CombatEntry) + */ + void setCombatEntries(List combatEntries); + + /** + * Calculates the most significant fall damage entry. + * + * @return the most significant fall damage entry + */ + @Nullable CombatEntry computeMostSignificantFall(); + + /** + * Checks whether the entity is in combat, + * i.e. has taken damage from an entity + * since the combat tracking has begun. + * + * @return whether the entity is in combat + */ + boolean isInCombat(); + + /** + * Checks whether the entity has started recording damage, + * i.e. its combat tracking is active. + * + * @return whether the entity has started recording damage + */ + boolean isTakingDamage(); + + /** + * Gets the last or current combat duration. + * + * @return the combat duration + * @see #isInCombat() + */ + int getCombatDuration(); + + /** + * Adds a new entry the pool of combat entries, + * updating the entity's combat state. + * + * @param combatEntry combat entry + */ + void addCombatEntry(CombatEntry combatEntry); + + /** + * Constructs a death message based on the current combat history. + * + * @return a death message + */ + Component getDeathMessage(); + + /** + * Resets entity's combat state, clearing combat history. + */ + void resetCombatState(); + + /** + * Calculates the fall location type from the current entity's location. + * + * @return the fall location type + */ + @Nullable FallLocationType calculateFallLocationType(); +} diff --git a/paper-api/src/main/java/io/papermc/paper/world/damagesource/FallLocationType.java b/paper-api/src/main/java/io/papermc/paper/world/damagesource/FallLocationType.java new file mode 100644 index 0000000000..1dcec3aef2 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/world/damagesource/FallLocationType.java @@ -0,0 +1,63 @@ +package io.papermc.paper.world.damagesource; + +import net.kyori.adventure.translation.Translatable; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +/** + * Represents a type of location from which the entity fell. + */ +@NullMarked +@ApiStatus.Experimental +public sealed interface FallLocationType extends Translatable permits FallLocationTypeImpl { + + /** + * Gets the fall location id. + * + * @return the fall location id + */ + String id(); + + /** + * Gets the translation key used for a fall death message + * caused by falling from this location + * + * @return the translation key + */ + @Override + String translationKey(); + + /** + * The entity was not within a special fall location. + */ + FallLocationType GENERIC = new FallLocationTypeImpl("generic"); + /** + * The entity was within the ladder. + */ + FallLocationType LADDER = new FallLocationTypeImpl("ladder"); + /** + * The entity was in vines. + */ + FallLocationType VINES = new FallLocationTypeImpl("vines"); + /** + * The entity was in weeping wines. + */ + FallLocationType WEEPING_VINES = new FallLocationTypeImpl("weeping_vines"); + /** + * The entity was in twisting vines. + */ + FallLocationType TWISTING_VINES = new FallLocationTypeImpl("twisting_vines"); + /** + * The entity was in scaffolding. + */ + FallLocationType SCAFFOLDING = new FallLocationTypeImpl("scaffolding"); + /** + * The entity was within some other climbable block. + */ + FallLocationType OTHER_CLIMBABLE = new FallLocationTypeImpl("other_climbable"); + /** + * The entity was in water. + */ + FallLocationType WATER = new FallLocationTypeImpl("water"); + +} diff --git a/paper-api/src/main/java/io/papermc/paper/world/damagesource/FallLocationTypeImpl.java b/paper-api/src/main/java/io/papermc/paper/world/damagesource/FallLocationTypeImpl.java new file mode 100644 index 0000000000..c5b97cea39 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/world/damagesource/FallLocationTypeImpl.java @@ -0,0 +1,14 @@ +package io.papermc.paper.world.damagesource; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +record FallLocationTypeImpl(String id) implements FallLocationType { + + @Override + public String translationKey() { + // Same as net.minecraft.world.damagesource.FallLocation#languageKey + return "death.fell.accident." + this.id; + } + +} diff --git a/paper-api/src/main/java/org/bukkit/entity/LivingEntity.java b/paper-api/src/main/java/org/bukkit/entity/LivingEntity.java index 61794d69b7..7f2892dd93 100644 --- a/paper-api/src/main/java/org/bukkit/entity/LivingEntity.java +++ b/paper-api/src/main/java/org/bukkit/entity/LivingEntity.java @@ -4,6 +4,8 @@ import java.util.Collection; import java.util.List; import java.util.Set; import java.util.UUID; +import io.papermc.paper.world.damagesource.CombatTracker; +import io.papermc.paper.world.damagesource.FallLocationType; import org.bukkit.FluidCollisionMode; import org.bukkit.Location; import org.bukkit.Material; @@ -21,6 +23,7 @@ import org.bukkit.scoreboard.Scoreboard; import org.bukkit.scoreboard.Team; import org.bukkit.util.RayTraceResult; import org.bukkit.util.Vector; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -1452,4 +1455,12 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource */ boolean canUseEquipmentSlot(org.bukkit.inventory.@NotNull EquipmentSlot slot); // Paper end - Expose canUseSlot + + /** + * Gets the entity's combat tracker + * + * @return the entity's combat tracker + */ + @ApiStatus.Experimental + @NotNull CombatTracker getCombatTracker(); } diff --git a/paper-server/patches/sources/net/minecraft/world/damagesource/CombatTracker.java.patch b/paper-server/patches/sources/net/minecraft/world/damagesource/CombatTracker.java.patch new file mode 100644 index 0000000000..49f1423f59 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/damagesource/CombatTracker.java.patch @@ -0,0 +1,30 @@ +--- a/net/minecraft/world/damagesource/CombatTracker.java ++++ b/net/minecraft/world/damagesource/CombatTracker.java +@@ -38,6 +_,13 @@ + this.recheckStatus(); + FallLocation currentFallLocation = FallLocation.getCurrentFallLocation(this.mob); + CombatEntry combatEntry = new CombatEntry(source, damage, currentFallLocation, (float)this.mob.fallDistance); ++ // Paper start - Combat tracker API ++ recordDamageAndCheckCombatState(combatEntry); ++ } ++ ++ public void recordDamageAndCheckCombatState(final CombatEntry combatEntry) { ++ final DamageSource source = combatEntry.source(); ++ // Paper end - Combat tracker API + this.entries.add(combatEntry); + this.lastDamageTime = this.mob.tickCount; + this.takingDamage = true; +@@ -147,6 +_,13 @@ + public void recheckStatus() { + int i = this.inCombat ? 300 : 100; + if (this.takingDamage && (!this.mob.isAlive() || this.mob.tickCount - this.lastDamageTime > i)) { ++ // Paper start - Combat tracker API ++ resetCombatState(); ++ } ++ } ++ ++ public void resetCombatState() {{ ++ // Paper end - Combat tracker API + boolean flag = this.inCombat; + this.takingDamage = false; + this.inCombat = false; diff --git a/paper-server/src/main/java/io/papermc/paper/PaperServerInternalAPIBridge.java b/paper-server/src/main/java/io/papermc/paper/PaperServerInternalAPIBridge.java index 43e88cd778..d693356269 100644 --- a/paper-server/src/main/java/io/papermc/paper/PaperServerInternalAPIBridge.java +++ b/paper-server/src/main/java/io/papermc/paper/PaperServerInternalAPIBridge.java @@ -1,10 +1,21 @@ package io.papermc.paper; +import io.papermc.paper.world.damagesource.CombatEntry; +import io.papermc.paper.world.damagesource.FallLocationType; +import io.papermc.paper.world.damagesource.PaperCombatEntryWrapper; +import io.papermc.paper.world.damagesource.PaperCombatTrackerWrapper; +import net.minecraft.Optionull; +import net.minecraft.world.damagesource.FallLocation; import org.bukkit.block.Biome; import org.bukkit.craftbukkit.block.CraftBiome; import org.bukkit.craftbukkit.damage.CraftDamageEffect; +import org.bukkit.craftbukkit.damage.CraftDamageSource; +import org.bukkit.craftbukkit.entity.CraftLivingEntity; import org.bukkit.damage.DamageEffect; +import org.bukkit.damage.DamageSource; +import org.bukkit.entity.LivingEntity; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; @NullMarked public class PaperServerInternalAPIBridge implements InternalAPIBridge { @@ -22,4 +33,42 @@ public class PaperServerInternalAPIBridge implements InternalAPIBridge { } return Holder.LEGACY_CUSTOM; } + + @Override + public CombatEntry createCombatEntry(final LivingEntity entity, final DamageSource damageSource, final float damage) { + final net.minecraft.world.entity.LivingEntity mob = ((CraftLivingEntity) entity).getHandle(); + final FallLocation fallLocation = FallLocation.getCurrentFallLocation(mob); + return createCombatEntry( + ((CraftDamageSource) damageSource).getHandle(), + damage, + fallLocation, + (float) mob.fallDistance + ); + } + + @Override + public CombatEntry createCombatEntry( + final DamageSource damageSource, + final float damage, + @Nullable final FallLocationType fallLocationType, + final float fallDistance + ) { + return createCombatEntry( + ((CraftDamageSource) damageSource).getHandle(), + damage, + Optionull.map(fallLocationType, PaperCombatTrackerWrapper::paperToMinecraft), + fallDistance + ); + } + + private CombatEntry createCombatEntry( + final net.minecraft.world.damagesource.DamageSource damageSource, + final float damage, + final net.minecraft.world.damagesource.@Nullable FallLocation fallLocation, + final float fallDistance + ) { + return new PaperCombatEntryWrapper(new net.minecraft.world.damagesource.CombatEntry( + damageSource, damage, fallLocation, fallDistance + )); + } } diff --git a/paper-server/src/main/java/io/papermc/paper/world/damagesource/PaperCombatEntryWrapper.java b/paper-server/src/main/java/io/papermc/paper/world/damagesource/PaperCombatEntryWrapper.java new file mode 100644 index 0000000000..354c57fde1 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/world/damagesource/PaperCombatEntryWrapper.java @@ -0,0 +1,32 @@ +package io.papermc.paper.world.damagesource; + +import net.minecraft.Optionull; +import org.bukkit.craftbukkit.damage.CraftDamageSource; +import org.bukkit.damage.DamageSource; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public record PaperCombatEntryWrapper(net.minecraft.world.damagesource.CombatEntry handle) implements CombatEntry { + + @Override + public DamageSource getDamageSource() { + return new CraftDamageSource(this.handle.source()); + } + + @Override + public float getDamage() { + return this.handle.damage(); + } + + @Override + public @Nullable FallLocationType getFallLocationType() { + return Optionull.map(this.handle.fallLocation(), PaperCombatTrackerWrapper::minecraftToPaper); + } + + @Override + public float getFallDistance() { + return this.handle.fallDistance(); + } + +} diff --git a/paper-server/src/main/java/io/papermc/paper/world/damagesource/PaperCombatTrackerWrapper.java b/paper-server/src/main/java/io/papermc/paper/world/damagesource/PaperCombatTrackerWrapper.java new file mode 100644 index 0000000000..323f97ea48 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/world/damagesource/PaperCombatTrackerWrapper.java @@ -0,0 +1,110 @@ +package io.papermc.paper.world.damagesource; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import io.papermc.paper.adventure.PaperAdventure; +import java.util.ArrayList; +import java.util.List; +import net.kyori.adventure.text.Component; +import net.minecraft.Util; +import net.minecraft.world.damagesource.FallLocation; +import org.bukkit.entity.LivingEntity; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public record PaperCombatTrackerWrapper( + net.minecraft.world.damagesource.CombatTracker handle +) implements CombatTracker { + + @Override + public LivingEntity getEntity() { + return this.handle.mob.getBukkitLivingEntity(); + } + + @Override + public List getCombatEntries() { + final List combatEntries = new ArrayList<>(this.handle.entries.size()); + this.handle.entries.forEach(combatEntry -> combatEntries.add(new PaperCombatEntryWrapper(combatEntry))); + return combatEntries; + } + + @Override + public void setCombatEntries(final List combatEntries) { + this.handle.entries.clear(); + combatEntries.forEach(combatEntry -> this.handle.entries.add(((PaperCombatEntryWrapper) combatEntry).handle())); + } + + @Override + public @Nullable CombatEntry computeMostSignificantFall() { + final net.minecraft.world.damagesource.CombatEntry combatEntry = this.handle.getMostSignificantFall(); + return combatEntry == null ? null : new PaperCombatEntryWrapper(combatEntry); + } + + @Override + public boolean isInCombat() { + return this.handle.inCombat; + } + + @Override + public boolean isTakingDamage() { + return this.handle.takingDamage; + } + + @Override + public int getCombatDuration() { + return this.handle.getCombatDuration(); + } + + @Override + public void addCombatEntry(final CombatEntry combatEntry) { + final net.minecraft.world.damagesource.CombatEntry entry = ((PaperCombatEntryWrapper) combatEntry).handle(); + this.handle.recordDamageAndCheckCombatState(entry); + } + + @Override + public Component getDeathMessage() { + return PaperAdventure.asAdventure(this.handle.getDeathMessage()); + } + + @Override + public void resetCombatState() { + this.handle.resetCombatState(); + } + + @Override + public FallLocationType calculateFallLocationType() { + final FallLocation fallLocation = FallLocation.getCurrentFallLocation(this.handle().mob); + return fallLocation == null ? FallLocationType.GENERIC : PaperCombatTrackerWrapper.minecraftToPaper(fallLocation); + } + + private static final BiMap FALL_LOCATION_MAPPING = Util.make(() -> { + final BiMap map = HashBiMap.create(8); + map.put(FallLocation.GENERIC, FallLocationType.GENERIC); + map.put(FallLocation.LADDER, FallLocationType.LADDER); + map.put(FallLocation.VINES, FallLocationType.VINES); + map.put(FallLocation.WEEPING_VINES, FallLocationType.WEEPING_VINES); + map.put(FallLocation.TWISTING_VINES, FallLocationType.TWISTING_VINES); + map.put(FallLocation.SCAFFOLDING, FallLocationType.SCAFFOLDING); + map.put(FallLocation.OTHER_CLIMBABLE, FallLocationType.OTHER_CLIMBABLE); + map.put(FallLocation.WATER, FallLocationType.WATER); + return map; + }); + + public static FallLocation paperToMinecraft(final FallLocationType fallLocationType) { + final FallLocation fallLocation = FALL_LOCATION_MAPPING.inverse().get(fallLocationType); + if (fallLocation == null) { + throw new IllegalArgumentException("Unknown fall location type: " + fallLocationType.id()); + } + return fallLocation; + } + + public static FallLocationType minecraftToPaper(final FallLocation fallLocation) { + final FallLocationType fallLocationType = FALL_LOCATION_MAPPING.get(fallLocation); + if (fallLocationType == null) { + throw new IllegalArgumentException("Unknown fall location: " + fallLocation.id()); + } + return fallLocationType; + } + +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java index bd39ecac80..478bb11e1a 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java @@ -9,6 +9,9 @@ import java.util.List; import java.util.Set; import java.util.UUID; import net.minecraft.Optionull; +import io.papermc.paper.world.damagesource.CombatTracker; +import io.papermc.paper.world.damagesource.PaperCombatTrackerWrapper; +import io.papermc.paper.world.damagesource.FallLocationType; import net.minecraft.core.component.DataComponents; import net.minecraft.network.protocol.game.ClientboundHurtAnimationPacket; import net.minecraft.server.level.ServerLevel; @@ -90,11 +93,15 @@ import org.bukkit.util.RayTraceResult; import org.bukkit.util.Vector; public class CraftLivingEntity extends CraftEntity implements LivingEntity { + + private final PaperCombatTrackerWrapper combatTracker; private CraftEntityEquipment equipment; public CraftLivingEntity(final CraftServer server, final net.minecraft.world.entity.LivingEntity entity) { super(server, entity); + this.combatTracker = new PaperCombatTrackerWrapper(entity.getCombatTracker()); + if (entity instanceof Mob || entity instanceof ArmorStand) { this.equipment = new CraftEntityEquipment(this); } @@ -1167,4 +1174,9 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { public boolean canUseEquipmentSlot(org.bukkit.inventory.EquipmentSlot slot) { return this.getHandle().canUseSlot(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); } + + @Override + public CombatTracker getCombatTracker() { + return this.combatTracker; + } }