Add combat tracker API (#11853)

This commit is contained in:
Illia Bondar
2025-04-30 20:24:33 +03:00
committed by GitHub
parent 646b80ca53
commit e663f99982
12 changed files with 546 additions and 0 deletions

View File

@ -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.
* <p>
* 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);
}

View File

@ -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.
* <p>
* 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);
}
}

View File

@ -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.
* <p>
* 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<CombatEntry> getCombatEntries();
/**
* Sets the entity's combat history.
* <p>
* 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<CombatEntry> 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();
}

View File

@ -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");
}

View File

@ -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;
}
}

View File

@ -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();
}