diff --git a/build-data/paper.at b/build-data/paper.at index fc06d593c438..0ef9b3431958 100644 --- a/build-data/paper.at +++ b/build-data/paper.at @@ -560,6 +560,7 @@ public net.minecraft.world.item.ItemStackLinkedSet TYPE_AND_TAG public net.minecraft.world.item.JukeboxSongPlayer song public net.minecraft.world.item.MapItem createNewSavedData(Lnet/minecraft/server/level/ServerLevel;IIIZZLnet/minecraft/resources/ResourceKey;)Lnet/minecraft/world/level/saveddata/maps/MapId; public net.minecraft.world.item.StandingAndWallBlockItem wallBlock +public net.minecraft.world.item.component.BundleContents$Mutable indexIsOutsideAllowedBounds(I)Z public net.minecraft.world.item.component.ItemContainerContents MAX_SIZE public net.minecraft.world.item.component.ItemContainerContents items public net.minecraft.world.item.component.ResolvableProfile unpack()Lcom/mojang/datafixers/util/Either; diff --git a/paper-api/src/main/java/io/papermc/paper/event/inventory/PlayerBundleItemSelectEvent.java b/paper-api/src/main/java/io/papermc/paper/event/inventory/PlayerBundleItemSelectEvent.java new file mode 100644 index 000000000000..f27f7cde6259 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/inventory/PlayerBundleItemSelectEvent.java @@ -0,0 +1,130 @@ +package io.papermc.paper.event.inventory; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +/** + * Called when a {@link Player} selects an item inside a bundle. + *

+ * NOTE: This event does not fire for bundle item selections in creative mode player inventories. + */ +@NullMarked +public final class PlayerBundleItemSelectEvent extends InventoryEvent { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final ItemStack bundle; + private final int rawSlot; + private final int slot; + + private final ItemStack previousItem; + private final ItemStack selectedItem; + + private final int previousIndex; + private final int selectedIndex; + + @ApiStatus.Internal + public PlayerBundleItemSelectEvent(final InventoryView view, final ItemStack bundle, final int rawSlot, final ItemStack previousItem, final ItemStack selectedItem, final int previousIndex, final int selectedIndex) { + super(view); + + this.bundle = bundle; + this.rawSlot = rawSlot; + this.slot = view.convertSlot(rawSlot); + + this.previousItem = previousItem; + this.selectedItem = selectedItem; + + this.previousIndex = previousIndex; + this.selectedIndex = selectedIndex; + } + + /** + * Gets the player who triggered the event. + * + * @return the player + */ + public Player getPlayer() { + return (Player) this.getView().getPlayer(); + } + + /** + * Gets the bundle item. + * + * @return the bundle item + */ + public ItemStack getBundle() { + return this.bundle; + } + + /** + * Gets the slot number of the bundle, depending on which {@link Inventory} it is located in. + * + * @return the slot number + * @see InventoryClickEvent#getSlot() + */ + public int getSlot() { + return this.slot; + } + + /** + * Gets the raw slot number of the bundle inside the {@link InventoryView}. + * + * @return the raw slot number + * @see InventoryClickEvent#getRawSlot() + */ + public int getRawSlot() { + return this.rawSlot; + } + + /** + * Gets the previously selected item inside the bundle. If no item was previously selected, this will return an empty item. + + * @return the previously selected item + */ + public ItemStack getPreviousItem() { + return this.previousItem; + } + + /** + * Gets the selected item inside the bundle. + * + * @return the selected item + */ + public ItemStack getSelectedItem() { + return this.selectedItem; + } + + /** + * Gets the previously selected index. If no item was previously selected, this will be -1. + * + * @return the previously selected index + */ + public int getPreviousIndex() { + return this.previousIndex; + } + + /** + * Gets the selected index. + * + * @return the selected index + */ + public int getSelectedIndex() { + return this.selectedIndex; + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } +} diff --git a/paper-server/patches/features/0022-Optimise-collision-checking-in-player-move-packet-ha.patch b/paper-server/patches/features/0022-Optimise-collision-checking-in-player-move-packet-ha.patch index 068477b5ba1f..abeae4a7c8de 100644 --- a/paper-server/patches/features/0022-Optimise-collision-checking-in-player-move-packet-ha.patch +++ b/paper-server/patches/features/0022-Optimise-collision-checking-in-player-move-packet-ha.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Optimise collision checking in player move packet handling Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index fad017de5d9fc31988aa4edf97fb3f9bcbee56c8..06132bde0aed9dd9088c884ee31056b9176b6f90 100644 +index e8ec4da3241c2cb212efdb75974e942b5d434695..5933a3a25954c0c7a2eac2361a805c751f286684 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -633,6 +633,7 @@ public class ServerGamePacketListenerImpl @@ -72,7 +72,7 @@ index fad017de5d9fc31988aa4edf97fb3f9bcbee56c8..06132bde0aed9dd9088c884ee31056b9 } @Override -@@ -1541,7 +1575,7 @@ public class ServerGamePacketListenerImpl +@@ -1542,7 +1576,7 @@ public class ServerGamePacketListenerImpl } } @@ -81,7 +81,7 @@ index fad017de5d9fc31988aa4edf97fb3f9bcbee56c8..06132bde0aed9dd9088c884ee31056b9 xDist = targetX - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above yDist = targetY - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above zDist = targetZ - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above -@@ -1580,6 +1614,7 @@ public class ServerGamePacketListenerImpl +@@ -1581,6 +1615,7 @@ public class ServerGamePacketListenerImpl boolean playerStandsOnSomething = this.player.verticalCollisionBelow; this.player.move(MoverType.PLAYER, new Vec3(xDist, yDist, zDist)); this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move @@ -89,7 +89,7 @@ index fad017de5d9fc31988aa4edf97fb3f9bcbee56c8..06132bde0aed9dd9088c884ee31056b9 // Paper start - prevent position desync if (this.awaitingPositionFromClient != null) { return; // ... thanks Mojang for letting move calls teleport across dimensions. -@@ -1613,7 +1648,17 @@ public class ServerGamePacketListenerImpl +@@ -1614,7 +1649,17 @@ public class ServerGamePacketListenerImpl } // Paper start - Add fail move event @@ -108,7 +108,7 @@ index fad017de5d9fc31988aa4edf97fb3f9bcbee56c8..06132bde0aed9dd9088c884ee31056b9 if (!allowMovement) { io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK, targetX, targetY, targetZ, targetYRot, targetXRot, false); -@@ -1751,7 +1796,7 @@ public class ServerGamePacketListenerImpl +@@ -1752,7 +1797,7 @@ public class ServerGamePacketListenerImpl private boolean updateAwaitingTeleport() { if (this.awaitingPositionFromClient != null) { @@ -117,7 +117,7 @@ index fad017de5d9fc31988aa4edf97fb3f9bcbee56c8..06132bde0aed9dd9088c884ee31056b9 this.awaitingTeleportTime = this.tickCount; this.teleport( this.awaitingPositionFromClient.x, -@@ -1770,6 +1815,34 @@ public class ServerGamePacketListenerImpl +@@ -1771,6 +1816,34 @@ public class ServerGamePacketListenerImpl } } diff --git a/paper-server/patches/features/0026-DataConverter-Moonrise-co-fixes.patch b/paper-server/patches/features/0026-DataConverter-Moonrise-co-fixes.patch index 900a854dba73..e10ebdbe87f6 100644 --- a/paper-server/patches/features/0026-DataConverter-Moonrise-co-fixes.patch +++ b/paper-server/patches/features/0026-DataConverter-Moonrise-co-fixes.patch @@ -42,10 +42,10 @@ index d2877c20f389d0131e1dd208b464f590671e5d82..27bdc70d861ca39487ad16cb3afb89d6 updateComponent(root, "minecraft:trim", hiddenComponents); updateComponent(root, "minecraft:unbreakable", hiddenComponents); diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 06132bde0aed9dd9088c884ee31056b9176b6f90..08374da2d9b84e8ff13c670d7f45867fb0bdd897 100644 +index 5933a3a25954c0c7a2eac2361a805c751f286684..be4c9d9d70f0bac382f008ab9decc9023dbc5e57 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1819,9 +1819,14 @@ public class ServerGamePacketListenerImpl +@@ -1820,9 +1820,14 @@ public class ServerGamePacketListenerImpl private boolean hasNewCollision(final ServerLevel level, final Entity entity, final AABB oldBox, final AABB newBox) { final List collisionsBB = new java.util.ArrayList<>(); final List collisionsVoxel = new java.util.ArrayList<>(); @@ -62,7 +62,7 @@ index 06132bde0aed9dd9088c884ee31056b9176b6f90..08374da2d9b84e8ff13c670d7f45867f ); diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index fa3809be5c0488c5a1d2639579d68619b0a55de9..c332d48f5cac2f3be93201c66f4f7cdba916cbac 100644 +index 7e4cf8a13e0ff2198cb7b6d80fb3fa343339943b..4522fde3a08637a01130ad39dc8a11a2c4f7f1cb 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -1529,7 +1529,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch index 535ad83014bf..76249fa5cc28 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch @@ -382,7 +382,14 @@ } @Override -@@ -572,6 +_,7 @@ +@@ -566,12 +_,14 @@ + @Override + public void handleBundleItemSelectedPacket(final ServerboundSelectBundleItemPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level()); ++ CraftEventFactory.callPlayerBundleItemSelectEvent(this.player, packet.slotId(), packet.selectedItemIndex()); // Paper - PlayerBundleItemSelectEvent + this.player.containerMenu.setSelectedBundleItemIndex(packet.slotId(), packet.selectedItemIndex()); + } + @Override public void handleRecipeBookChangeSettingsPacket(final ServerboundRecipeBookChangeSettingsPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level()); 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..fb9a1a98dddc 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 @@ -1606,6 +1606,38 @@ public static BlockIgniteEvent callBlockIgniteEvent(Level level, BlockPos pos, I return event; } + public static void callPlayerBundleItemSelectEvent(final net.minecraft.world.entity.player.Player player, final int slotId, final int selectedIndex) { + if (player.isCreative() && player.containerMenu instanceof net.minecraft.world.inventory.InventoryMenu) { + // The packet's slotId will always be 0 if this packet is sent for a player selecting an item in a bundle in their creative inventory, + // making it unfit for firing an event. + return; + } + + if (slotId < 0 || slotId >= player.containerMenu.slots.size() || selectedIndex == net.minecraft.world.item.component.BundleContents.NO_SELECTED_ITEM_INDEX) { + return; + } + + final net.minecraft.world.item.ItemStack item = player.containerMenu.slots.get(slotId).getItem(); + if (!item.is(net.minecraft.tags.ItemTags.BUNDLES)) { + return; + } + + final net.minecraft.world.item.component.BundleContents contents = item.get(net.minecraft.core.component.DataComponents.BUNDLE_CONTENTS); + if (contents == null || (new net.minecraft.world.item.component.BundleContents.Mutable(contents)).indexIsOutsideAllowedBounds(selectedIndex)) { + return; + } + + new io.papermc.paper.event.inventory.PlayerBundleItemSelectEvent( + player.containerMenu.getBukkitView(), + item.asBukkitMirror(), + slotId, + contents.getSelectedItem() != null ? contents.getSelectedItem().create().asBukkitMirror() : org.bukkit.inventory.ItemStack.empty(), + contents.items().get(selectedIndex).create().asBukkitMirror(), + contents.getSelectedItemIndex(), + selectedIndex + ).callEvent(); + } + public static void handleInventoryCloseEvent(net.minecraft.world.entity.player.Player human, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { InventoryCloseEvent event = new InventoryCloseEvent(human.containerMenu.getBukkitView(), reason); // Paper human.level().getCraftServer().getPluginManager().callEvent(event);