SPIGOT-7300, #829: Add new DamageSource API providing enhanced information about entity damage

By: Doc <nachito94@msn.com>
Also-by: 2008Choco <hawkeboyz2@hotmail.com>
This commit is contained in:
Bukkit/Spigot
2024-02-11 09:54:21 +11:00
parent e46e33f5e2
commit f9381f1dc4
13 changed files with 467 additions and 27 deletions

View File

@@ -0,0 +1,53 @@
package org.bukkit.damage;
import com.google.common.base.Preconditions;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* Represents a type of effect that occurs when damage is inflicted. Currently,
* effects only determine the sound that plays.
*/
@ApiStatus.Experimental
public interface DamageEffect {
/**
* The default damage effect.
*/
public static final DamageEffect HURT = getDamageEffect("hurt");
/**
* Thorns.
*/
public static final DamageEffect THORNS = getDamageEffect("thorns");
/**
* Drowning.
*/
public static final DamageEffect DROWNING = getDamageEffect("drowning");
/**
* A single burn tick (fire, lava, etc.).
*/
public static final DamageEffect BURNING = getDamageEffect("burning");
/**
* Poked by a berry bush.
*/
public static final DamageEffect POKING = getDamageEffect("poking");
/**
* Freeze tick (powder snow).
*/
public static final DamageEffect FREEZING = getDamageEffect("freezing");
@NotNull
private static DamageEffect getDamageEffect(@NotNull String key) {
return Preconditions.checkNotNull(Bukkit.getUnsafe().getDamageEffect(key), "No DamageEffect found for %s. This is a bug.", key);
}
/**
* Get the {@link Sound} played for this {@link DamageEffect}.
*
* @return the sound
*/
@NotNull
public Sound getSound();
}

View File

@@ -0,0 +1,26 @@
package org.bukkit.damage;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.ApiStatus;
/**
* A means of damage scaling with respect to the server's difficulty.
*/
@ApiStatus.Experimental
public enum DamageScaling {
/**
* Damage is not scaled.
*/
NEVER,
/**
* Damage is scaled only when the
* {@link DamageSource#getCausingEntity() causing entity} is not a
* {@link Player}.
*/
WHEN_CAUSED_BY_LIVING_NON_PLAYER,
/**
* Damage is always scaled.
*/
ALWAYS;
}

View File

@@ -0,0 +1,155 @@
package org.bukkit.damage;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents a source of damage.
*/
@ApiStatus.Experimental
public interface DamageSource {
/**
* Get the {@link DamageType}.
*
* @return the damage type
*/
@NotNull
public DamageType getDamageType();
/**
* Get the {@link Entity} that caused the damage to occur.
* <p>
* Not to be confused with {@link #getDirectEntity()}, the causing entity is
* the entity to which the damage is ultimately attributed if the receiver
* is killed. If, for example, the receiver was damaged by a projectile, the
* shooter/thrower would be returned.
*
* @return an Entity or null
*/
@Nullable
public Entity getCausingEntity();
/**
* Get the {@link Entity} that directly caused the damage.
* <p>
* Not to be confused with {@link #getCausingEntity()}, the direct entity is
* the entity that actually inflicted the damage. If, for example, the
* receiver was damaged by a projectile, the projectile would be returned.
*
* @return an Entity or null
*/
@Nullable
public Entity getDirectEntity();
/**
* Get the {@link Location} from where the damage originated. This will only
* be present if an entity did not cause the damage.
*
* @return the location, or null if none
*/
@Nullable
public Location getDamageLocation();
/**
* Get the {@link Location} from where the damage originated.
* <p>
* This is a convenience method to get the final location of the damage.
* This method will attempt to return
* {@link #getDamageLocation() the damage location}. If this is null, the
* {@link #getCausingEntity() causing entity location} will be returned.
* Finally if there is no damage location nor a causing entity, null will be
* returned.
*
* @return the source of the location or null.
*/
@Nullable
public Location getSourceLocation();
/**
* Get if this damage is indirect.
* <p>
* Damage is considered indirect if {@link #getCausingEntity()} is not equal
* to {@link #getDirectEntity()}. This will be the case, for example, if a
* skeleton shot an arrow or a player threw a potion.
*
* @return {@code true} if is indirect, {@code false} otherwise.
*/
public boolean isIndirect();
/**
* Get the amount of hunger exhaustion caused by this damage.
*
* @return the amount of hunger exhaustion caused.
*/
public float getFoodExhaustion();
/**
* Gets if this source of damage scales with difficulty.
*
* @return {@code True} if scales.
*/
public boolean scalesWithDifficulty();
/**
* Create a new {@link DamageSource.Builder}.
*
* @param damageType the {@link DamageType} to use
* @return a {@link DamageSource.Builder}
*/
@NotNull
@SuppressWarnings("deprecation")
public static Builder builder(@NotNull DamageType damageType) {
return Bukkit.getUnsafe().createDamageSourceBuilder(damageType);
}
/**
* Utility class to make building a {@link DamageSource} easier. Only a
* {@link DamageType} is required.
*/
public static interface Builder {
/**
* Set the {@link Entity} that caused the damage.
*
* @param entity the entity
* @return this instance. Allows for chained method calls
* @see DamageSource#getCausingEntity()
*/
@NotNull
public Builder withCausingEntity(@NotNull Entity entity);
/**
* Set the {@link Entity} that directly inflicted the damage.
*
* @param entity the entity
* @return this instance. Allows for chained method calls
* @see DamageSource#getDirectEntity()
*/
@NotNull
public Builder withDirectEntity(@NotNull Entity entity);
/**
* Set the {@link Location} of the source of damage.
*
* @param location the location where the damage occurred
* @return this instance. Allows for chained method calls
* @see DamageSource#getSourceLocation()
*/
@NotNull
public Builder withDamageLocation(@NotNull Location location);
/**
* Create a new {@link DamageSource} instance using the supplied
* parameters.
*
* @return the damage source instance
*/
@NotNull
public DamageSource build();
}
}

View File

@@ -0,0 +1,117 @@
package org.bukkit.damage;
import com.google.common.base.Preconditions;
import org.bukkit.Keyed;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.Translatable;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* Represent a type of damage that an entity can receive.
* <p>
* Constants in this class include the base types provided by the vanilla
* server. Data packs are capable of registering more types of damage which may
* be obtained through the {@link Registry#DAMAGE_TYPE}.
*
* @see <a href="https://minecraft.wiki/w/Damage_type">Minecraft Wiki</a>
*/
@ApiStatus.Experimental
public interface DamageType extends Keyed, Translatable {
public static final DamageType IN_FIRE = getDamageType("in_fire");
public static final DamageType LIGHTNING_BOLT = getDamageType("lightning_bolt");
public static final DamageType ON_FIRE = getDamageType("on_fire");
public static final DamageType LAVA = getDamageType("lava");
public static final DamageType HOT_FLOOR = getDamageType("hot_floor");
public static final DamageType IN_WALL = getDamageType("in_wall");
public static final DamageType CRAMMING = getDamageType("cramming");
public static final DamageType DROWN = getDamageType("drown");
public static final DamageType STARVE = getDamageType("starve");
public static final DamageType CACTUS = getDamageType("cactus");
public static final DamageType FALL = getDamageType("fall");
public static final DamageType FLY_INTO_WALL = getDamageType("fly_into_wall");
public static final DamageType OUT_OF_WORLD = getDamageType("out_of_world");
public static final DamageType GENERIC = getDamageType("generic");
public static final DamageType MAGIC = getDamageType("magic");
public static final DamageType WITHER = getDamageType("wither");
public static final DamageType DRAGON_BREATH = getDamageType("dragon_breath");
public static final DamageType DRY_OUT = getDamageType("dry_out");
public static final DamageType SWEET_BERRY_BUSH = getDamageType("sweet_berry_bush");
public static final DamageType FREEZE = getDamageType("freeze");
public static final DamageType STALAGMITE = getDamageType("stalagmite");
public static final DamageType FALLING_BLOCK = getDamageType("falling_block");
public static final DamageType FALLING_ANVIL = getDamageType("falling_anvil");
public static final DamageType FALLING_STALACTITE = getDamageType("falling_stalactite");
public static final DamageType STING = getDamageType("sting");
public static final DamageType MOB_ATTACK = getDamageType("mob_attack");
public static final DamageType MOB_ATTACK_NO_AGGRO = getDamageType("mob_attack_no_aggro");
public static final DamageType PLAYER_ATTACK = getDamageType("player_attack");
public static final DamageType ARROW = getDamageType("arrow");
public static final DamageType TRIDENT = getDamageType("trident");
public static final DamageType MOB_PROJECTILE = getDamageType("mob_projectile");
public static final DamageType FIREWORKS = getDamageType("fireworks");
public static final DamageType FIREBALL = getDamageType("fireball");
public static final DamageType UNATTRIBUTED_FIREBALL = getDamageType("unattributed_fireball");
public static final DamageType WITHER_SKULL = getDamageType("wither_skull");
public static final DamageType THROWN = getDamageType("thrown");
public static final DamageType INDIRECT_MAGIC = getDamageType("indirect_magic");
public static final DamageType THORNS = getDamageType("thorns");
public static final DamageType EXPLOSION = getDamageType("explosion");
public static final DamageType PLAYER_EXPLOSION = getDamageType("player_explosion");
public static final DamageType SONIC_BOOM = getDamageType("sonic_boom");
public static final DamageType BAD_RESPAWN_POINT = getDamageType("bad_respawn_point");
public static final DamageType OUTSIDE_BORDER = getDamageType("outside_border");
public static final DamageType GENERIC_KILL = getDamageType("generic_kill");
@NotNull
private static DamageType getDamageType(@NotNull String key) {
NamespacedKey namespacedKey = NamespacedKey.minecraft(key);
return Preconditions.checkNotNull(Registry.DAMAGE_TYPE.get(namespacedKey), "No DamageType found for %s. This is a bug.", namespacedKey);
}
/**
* {@inheritDoc}
* <p>
* The returned key is that of the death message sent when this damage type
* is responsible for the death of an entity.
* <p>
* <strong>Note</strong> This translation key is only used if
* {@link #getDeathMessageType()} is {@link DeathMessageType#DEFAULT}
*/
@NotNull
@Override
public String getTranslationKey();
/**
* Get the {@link DamageScaling} for this damage type.
*
* @return the damage scaling
*/
@NotNull
public DamageScaling getDamageScaling();
/**
* Get the {@link DamageEffect} for this damage type.
*
* @return the damage effect
*/
@NotNull
public DamageEffect getDamageEffect();
/**
* Get the {@link DeathMessageType} for this damage type.
*
* @return the death message type
*/
@NotNull
public DeathMessageType getDeathMessageType();
/**
* Get the amount of hunger exhaustion caused by this damage type.
*
* @return the exhaustion
*/
public float getExhaustion();
}

View File

@@ -0,0 +1,26 @@
package org.bukkit.damage;
import org.jetbrains.annotations.ApiStatus;
/**
* Represents a type of death message used by a {@link DamageSource}.
*/
@ApiStatus.Experimental
public enum DeathMessageType {
/**
* No special death message logic is applied.
*/
DEFAULT,
/**
* Shows a variant of fall damage death instead of a regular death message.
* <br>
* <b>Example:</b> death.fell.assist.item
*/
FALL_VARIANTS,
/**
* Shows the intentional game design death message instead of a regular
* death message.
*/
INTENTIONAL_GAME_DESIGN;
}

View File

@@ -0,0 +1,5 @@
/**
* Classes concerning damage types and sources applicable to living entities.
*/
@org.jetbrains.annotations.ApiStatus.Experimental
package org.bukkit.damage;