From 2a4a115432f5a1d78399a1784ae0c2e9531281e3 Mon Sep 17 00:00:00 2001 From: TonytheMacaroni Date: Sun, 16 Feb 2025 14:46:59 -0500 Subject: [PATCH] Add EntityEquipmentChangedEvent (#12011) --- .../event/player/PlayerArmorChangeEvent.java | 23 +++++ .../entity/EntityEquipmentChangedEvent.java | 89 +++++++++++++++++++ .../world/entity/LivingEntity.java.patch | 47 ++++++++-- 3 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 paper-api/src/main/java/io/papermc/paper/event/entity/EntityEquipmentChangedEvent.java diff --git a/paper-api/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java b/paper-api/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java index c7cc612ec..f146a8cc9 100644 --- a/paper-api/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java +++ b/paper-api/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java @@ -5,6 +5,7 @@ import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; @@ -16,8 +17,10 @@ import static org.bukkit.Material.*; * Called when the player themselves change their armor items *

* Not currently called for environmental factors though it MAY BE IN THE FUTURE + * @apiNote Use {@link io.papermc.paper.event.entity.EntityEquipmentChangedEvent} for all entity equipment changes */ @NullMarked +@ApiStatus.Obsolete(since = "1.21.4") public class PlayerArmorChangeEvent extends PlayerEvent { private static final HandlerList HANDLER_LIST = new HandlerList(); @@ -38,11 +41,27 @@ public class PlayerArmorChangeEvent extends PlayerEvent { * Gets the type of slot being altered. * * @return type of slot being altered + * @deprecated {@link SlotType} does not accurately represent what item types are valid in each slot. Use {@link #getSlot()} instead. */ + @Deprecated(since = "1.21.4") public SlotType getSlotType() { return this.slotType; } + /** + * Gets the slot being altered. + * + * @return slot being altered + */ + public EquipmentSlot getSlot() { + return switch (this.slotType) { + case HEAD -> EquipmentSlot.HEAD; + case CHEST -> EquipmentSlot.CHEST; + case LEGS -> EquipmentSlot.LEGS; + case FEET -> EquipmentSlot.FEET; + }; + } + /** * Gets the existing item that's being replaced * @@ -70,6 +89,10 @@ public class PlayerArmorChangeEvent extends PlayerEvent { return HANDLER_LIST; } + /** + * @deprecated {@link SlotType} does not accurately represent what item types are valid in each slot. + */ + @Deprecated(since = "1.21.4") public enum SlotType { HEAD(NETHERITE_HELMET, DIAMOND_HELMET, GOLDEN_HELMET, IRON_HELMET, CHAINMAIL_HELMET, LEATHER_HELMET, CARVED_PUMPKIN, PLAYER_HEAD, SKELETON_SKULL, ZOMBIE_HEAD, CREEPER_HEAD, WITHER_SKELETON_SKULL, TURTLE_HELMET, DRAGON_HEAD, PIGLIN_HEAD), CHEST(NETHERITE_CHESTPLATE, DIAMOND_CHESTPLATE, GOLDEN_CHESTPLATE, IRON_CHESTPLATE, CHAINMAIL_CHESTPLATE, LEATHER_CHESTPLATE, ELYTRA), diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/EntityEquipmentChangedEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/EntityEquipmentChangedEvent.java new file mode 100644 index 000000000..ebb31c12d --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/EntityEquipmentChangedEvent.java @@ -0,0 +1,89 @@ +package io.papermc.paper.event.entity; + +import java.util.Collections; +import java.util.Map; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Unmodifiable; +import org.jspecify.annotations.NullMarked; + +/** + * Called whenever a change to an entity's equipment has been detected. This event is called after effects from + * attribute modifiers and enchantments have been updated. + *

+ * Examples of actions that can trigger this event: + *

+ */ +@NullMarked +public class EntityEquipmentChangedEvent extends EntityEvent { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final Map equipmentChanges; + + @ApiStatus.Internal + public EntityEquipmentChangedEvent(final LivingEntity entity, final Map equipmentChanges) { + super(entity); + + this.equipmentChanges = equipmentChanges; + } + + @Override + public LivingEntity getEntity() { + return (LivingEntity) this.entity; + } + + /** + * Gets a map of changed slots to their respective equipment changes. + * + * @return the equipment changes map + */ + public @Unmodifiable Map getEquipmentChanges() { + return Collections.unmodifiableMap(this.equipmentChanges); + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } + + /** + * Represents a change in equipment for a single equipment slot. + */ + @ApiStatus.NonExtendable + public interface EquipmentChange { + + /** + * Gets the existing item that is being replaced. + * + * @return the existing item + */ + @Contract(pure = true, value = "-> new") + ItemStack oldItem(); + + /** + * Gets the new item that is replacing the existing item. + * + * @return the new item + */ + @Contract(pure = true, value = "-> new") + ItemStack newItem(); + } +} diff --git a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch index 8015fbab2..862afaa4e 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/LivingEntity.java.patch @@ -1336,20 +1336,57 @@ Map map = this.collectEquipmentChanges(); if (map != null) { this.handleHandSwap(map); -@@ -2595,6 +_,13 @@ +@@ -2586,6 +_,20 @@ + @Nullable + private Map collectEquipmentChanges() { + Map map = null; ++ // Paper start - EntityEquipmentChangedEvent ++ record EquipmentChangeImpl(org.bukkit.inventory.ItemStack oldItem, org.bukkit.inventory.ItemStack newItem) implements io.papermc.paper.event.entity.EntityEquipmentChangedEvent.EquipmentChange { ++ @Override ++ public org.bukkit.inventory.ItemStack oldItem() { ++ return this.oldItem.clone(); ++ } ++ ++ @Override ++ public org.bukkit.inventory.ItemStack newItem() { ++ return this.newItem.clone(); ++ } ++ } ++ Map equipmentChanges = null; ++ // Paper end - EntityEquipmentChangedEvent + + for (EquipmentSlot equipmentSlot : EquipmentSlot.VALUES) { + ItemStack itemStack = switch (equipmentSlot.getType()) { +@@ -2595,11 +_,20 @@ }; ItemStack itemBySlot = this.getItemBySlot(equipmentSlot); if (this.equipmentHasChanged(itemStack, itemBySlot)) { -+ // Paper start - PlayerArmorChangeEvent ++ // Paper start - EntityEquipmentChangedEvent, PlayerArmorChangeEvent ++ final org.bukkit.inventory.ItemStack oldItem = CraftItemStack.asBukkitCopy(itemStack); ++ final org.bukkit.inventory.ItemStack newItem = CraftItemStack.asBukkitCopy(itemBySlot); + if (this instanceof ServerPlayer && equipmentSlot.getType() == EquipmentSlot.Type.HUMANOID_ARMOR) { -+ final org.bukkit.inventory.ItemStack oldItem = CraftItemStack.asBukkitCopy(itemStack); -+ final org.bukkit.inventory.ItemStack newItem = CraftItemStack.asBukkitCopy(itemBySlot); + new com.destroystokyo.paper.event.player.PlayerArmorChangeEvent((org.bukkit.entity.Player) this.getBukkitEntity(), com.destroystokyo.paper.event.player.PlayerArmorChangeEvent.SlotType.valueOf(equipmentSlot.name()), oldItem, newItem).callEvent(); + } -+ // Paper end - PlayerArmorChangeEvent ++ // Paper end - EntityEquipmentChangedEvent, PlayerArmorChangeEvent if (map == null) { map = Maps.newEnumMap(EquipmentSlot.class); ++ equipmentChanges = Maps.newEnumMap(org.bukkit.inventory.EquipmentSlot.class); // Paper - EntityEquipmentChangedEvent } + + map.put(equipmentSlot, itemBySlot); ++ equipmentChanges.put(org.bukkit.craftbukkit.CraftEquipmentSlot.getSlot(equipmentSlot), new EquipmentChangeImpl(oldItem, newItem)); // Paper - EntityEquipmentChangedEvent + AttributeMap attributes = this.getAttributes(); + if (!itemStack.isEmpty()) { + this.stopLocationBasedEffects(itemStack, equipmentSlot, attributes); +@@ -2624,6 +_,8 @@ + } + } + } ++ ++ new io.papermc.paper.event.entity.EntityEquipmentChangedEvent(this.getBukkitLivingEntity(), equipmentChanges).callEvent(); // Paper - EntityEquipmentChangedEvent + } + + return map; @@ -2664,7 +_,7 @@ this.lastBodyItemStack = itemStack; }