diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/EntityEffectTickEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/EntityEffectTickEvent.java new file mode 100644 index 000000000000..fef69314f616 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/EntityEffectTickEvent.java @@ -0,0 +1,75 @@ +package io.papermc.paper.event.entity; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +/** + * An event that is triggered when an entity receives a potion effect instantly + * or when the potion effect is applied on each tick (e.g. every 25 ticks for Poison level 1). + *
+ * For example, this event may be called when an entity regenerates health + * or takes poison damage as a result of a potion effect. + */ +@NullMarked +public class EntityEffectTickEvent extends EntityEvent implements Cancellable { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final PotionEffectType type; + private final int amplifier; + private boolean cancelled; + + @ApiStatus.Internal + public EntityEffectTickEvent(final LivingEntity entity, final PotionEffectType type, final int amplifier) { + super(entity); + this.type = type; + this.amplifier = amplifier; + } + + @Override + public LivingEntity getEntity() { + return (LivingEntity) super.getEntity(); + } + + /** + * Gets the type of the potion effect associated with this event. + * + * @return the {@link PotionEffectType} of the effect + */ + public PotionEffectType getType() { + return type; + } + + /** + * Gets the amplifier level of the potion effect associated with this event. + * + * @return the amplifier level of the potion effect + */ + public int getAmplifier() { + return amplifier; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(final boolean cancel) { + this.cancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } +} diff --git a/paper-server/patches/sources/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch index 040430b4431d..274a719765de 100644 --- a/paper-server/patches/sources/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch @@ -9,8 +9,11 @@ } else { entity.hurtServer(level, entity.damageSources().magic(), 6 << amplifier); } -@@ -30,7 +_,7 @@ +@@ -28,9 +_,10 @@ + public void applyInstantenousEffect( + ServerLevel level, @Nullable Entity source, @Nullable Entity indirectSource, LivingEntity entity, int amplifier, double health ) { ++ if (!new io.papermc.paper.event.entity.EntityEffectTickEvent(entity.getBukkitLivingEntity(), org.bukkit.craftbukkit.potion.CraftPotionEffectType.minecraftToBukkit(this), amplifier).callEvent()) { return; } // Paper - Add EntityEffectTickEvent if (this.isHarm == entity.isInvertedHealAndHarm()) { int i = (int)(health * (4 << amplifier) + 0.5); - entity.heal(i); diff --git a/paper-server/patches/sources/net/minecraft/world/effect/MobEffect.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/MobEffect.java.patch new file mode 100644 index 000000000000..20dfc7fa71ab --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/effect/MobEffect.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/effect/MobEffect.java ++++ b/net/minecraft/world/effect/MobEffect.java +@@ -76,6 +_,7 @@ + public void applyInstantenousEffect( + ServerLevel level, @Nullable Entity source, @Nullable Entity indirectSource, LivingEntity entity, int amplifier, double health + ) { ++ if (!new io.papermc.paper.event.entity.EntityEffectTickEvent(entity.getBukkitLivingEntity(), org.bukkit.craftbukkit.potion.CraftPotionEffectType.minecraftToBukkit(this), amplifier).callEvent()) { return; } // Paper - Add EntityEffectTickEvent + this.applyEffectTick(level, entity, amplifier); + } + diff --git a/paper-server/patches/sources/net/minecraft/world/effect/MobEffectInstance.java.patch b/paper-server/patches/sources/net/minecraft/world/effect/MobEffectInstance.java.patch new file mode 100644 index 000000000000..7cdda27b07a0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/effect/MobEffectInstance.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/effect/MobEffectInstance.java ++++ b/net/minecraft/world/effect/MobEffectInstance.java +@@ -216,6 +_,7 @@ + int i = this.isInfiniteDuration() ? entity.tickCount : this.duration; + if (entity.level() instanceof ServerLevel serverLevel + && this.effect.value().shouldApplyEffectTickThisTick(i, this.amplifier) ++ && new io.papermc.paper.event.entity.EntityEffectTickEvent(entity.getBukkitLivingEntity(), org.bukkit.craftbukkit.potion.CraftPotionEffectType.minecraftHolderToBukkit(this.effect), this.amplifier).callEvent() // Paper - Add EntityEffectTickEvent + && !this.effect.value().applyEffectTick(serverLevel, entity, this.amplifier)) { + entity.removeEffect(this.effect); + }