diff --git a/paper-api/src/main/java/io/papermc/paper/event/entity/ItemTransportingEntitySortEvent.java b/paper-api/src/main/java/io/papermc/paper/event/entity/ItemTransportingEntitySortEvent.java new file mode 100644 index 000000000000..e86999dfe69c --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/entity/ItemTransportingEntitySortEvent.java @@ -0,0 +1,90 @@ +package io.papermc.paper.event.entity; + +import org.bukkit.block.Container; +import org.bukkit.entity.CopperGolem; +import org.bukkit.entity.Entity; +import org.bukkit.event.HandlerList; +import org.bukkit.event.entity.EntityEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +/** + * Called when an item-transporting entity (typically a {@link CopperGolem}, + * although other entities may be possible through non-API means) + * is inspecting a destination {@link Container} to decide if the item it is holding + * belongs in said container. This gives plugin developers an opportunity to + * override the {@link CopperGolem}'s sorting behavior. + */ +@NullMarked +public class ItemTransportingEntitySortEvent extends EntityEvent { + protected static final HandlerList HANDLER_LIST = new HandlerList(); + + public enum ItemTransportingEntityDecision { + ALLOW_DESTINATION, + REJECT_DESTINATION, + DEFAULT + } + + private final ItemStack itemStack; + private final Inventory containerInventory; + private ItemTransportingEntityDecision decision; + + @ApiStatus.Internal + public ItemTransportingEntitySortEvent( + final Entity entity, + final ItemStack itemStack, + final Inventory containerInventory + ) { + super(entity); + this.containerInventory = containerInventory; + this.itemStack = itemStack; + this.decision = ItemTransportingEntityDecision.DEFAULT; + } + + /** + * Sets if the item belongs in the container. + * + * @param d Whether the item belongs in the chest. + */ + public void setDecision(boolean d) { + this.decision = d ? ItemTransportingEntityDecision.ALLOW_DESTINATION : ItemTransportingEntityDecision.REJECT_DESTINATION; + } + + /** + * Gets if the held item stack belongs in the container. + * + * @return Whether the item stack belongs in the chest. + */ + public ItemTransportingEntityDecision getDecision() { + return this.decision; + } + + /** + * Gets the container the entity is comparing to the held item. + * + * @return The potential destination container + */ + public Inventory getContainerInventory() { + return this.containerInventory; + } + + /** + * Gets the held item stack being compared against the container. + * + * @return The held item stack + */ + public ItemStack getHeldItemStack() { + return this.itemStack; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } +} diff --git a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/TransportItemsBetweenContainers.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/TransportItemsBetweenContainers.java.patch index d5c3556c7c9a..e41d9dedb298 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/TransportItemsBetweenContainers.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/ai/behavior/TransportItemsBetweenContainers.java.patch @@ -12,3 +12,22 @@ return isValidTarget ? transportItemTarget : null; } } +@@ -505,7 +_,17 @@ + } + + private static boolean matchesLeavingItemsRequirement(final PathfinderMob body, final Container container) { +- return container.isEmpty() || hasItemMatchingHandItem(body, container); ++ // Paper start - Add event for container assessment ++ io.papermc.paper.event.entity.ItemTransportingEntitySortEvent sortEvent = org.bukkit.craftbukkit.event.CraftEventFactory.createItemTransportingEntitySortEvent(body, container); ++ sortEvent.callEvent(); ++ if (sortEvent.getDecision() != ++ io.papermc.paper.event.entity.ItemTransportingEntitySortEvent.ItemTransportingEntityDecision.DEFAULT) { ++ return sortEvent.getDecision() ++ == io.papermc.paper.event.entity.ItemTransportingEntitySortEvent.ItemTransportingEntityDecision.ALLOW_DESTINATION; ++ } else { ++ return container.isEmpty() || hasItemMatchingHandItem(body, container); ++ } ++ // Paper end - Add event for container assessment + } + + private static boolean hasItemMatchingHandItem(final PathfinderMob body, final Container container) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 7a42d1810f74..6cfaf48221c5 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -7,11 +7,13 @@ import com.mojang.authlib.GameProfile; import com.mojang.datafixers.util.Either; import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.block.TileStateInventoryHolder; import io.papermc.paper.block.bed.BedEnterProblem; import io.papermc.paper.connection.HorriblePlayerLoginEventHack; import io.papermc.paper.connection.PlayerConnection; import io.papermc.paper.event.block.BlockLockCheckEvent; import io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent; +import io.papermc.paper.event.entity.ItemTransportingEntitySortEvent; import io.papermc.paper.event.entity.ItemTransportingEntityValidateTargetEvent; import io.papermc.paper.event.player.PlayerBedFailEnterEvent; import io.papermc.paper.event.player.PlayerToggleEntityAgeLockEvent; @@ -105,6 +107,8 @@ import org.bukkit.craftbukkit.entity.CraftLivingEntity; import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.craftbukkit.entity.CraftSpellcaster; +import org.bukkit.craftbukkit.inventory.CraftContainer; +import org.bukkit.craftbukkit.inventory.CraftInventory; import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.craftbukkit.inventory.CraftItemType; @@ -2421,10 +2425,15 @@ public static boolean callPlayerToggleEntityAgeLockEvent(net.minecraft.world.ent return true; } + public static ItemTransportingEntitySortEvent createItemTransportingEntitySortEvent(PathfinderMob mob, Container container) { + return new ItemTransportingEntitySortEvent(mob.getBukkitEntity(), mob.getMainHandItem().asBukkitCopy(), new CraftInventory(container)); + } + public static ClockTimeSkipEvent createTimeSkipEvent(final CommandSourceStack source, final long skipAmount) { if (io.papermc.paper.configuration.GlobalConfiguration.get().time.affectsAllWorlds) { return new ClockTimeSkipEvent(ClockTimeSkipEvent.SkipReason.COMMAND, skipAmount); } return new TimeSkipEvent(source.getLevel().getWorld(), ClockTimeSkipEvent.SkipReason.COMMAND, skipAmount); } + }