From f22a4959f2d1acc89d0934204e5df7b2f39e3e57 Mon Sep 17 00:00:00 2001
From: mrtz6 <78804817+mrtz6@users.noreply.github.com>
Date: Wed, 20 May 2026 21:40:46 +0200
Subject: [PATCH 1/8] feat: add PlayerBundleItemSelectEvent
---
build-data/paper.at | 1 +
.../player/PlayerBundleItemSelectEvent.java | 65 +++++++++++++++++++
...on-checking-in-player-move-packet-ha.patch | 12 ++--
...0026-DataConverter-Moonrise-co-fixes.patch | 6 +-
.../ServerGamePacketListenerImpl.java.patch | 36 +++++++++-
5 files changed, 110 insertions(+), 10 deletions(-)
create mode 100644 paper-api/src/main/java/io/papermc/paper/event/player/PlayerBundleItemSelectEvent.java
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/player/PlayerBundleItemSelectEvent.java b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBundleItemSelectEvent.java
new file mode 100644
index 000000000000..b5d3ced4945c
--- /dev/null
+++ b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBundleItemSelectEvent.java
@@ -0,0 +1,65 @@
+package io.papermc.paper.event.player;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.player.PlayerEvent;
+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 PlayerEvent {
+
+ private static final HandlerList HANDLER_LIST = new HandlerList();
+
+ private final ItemStack bundle;
+ private final ItemStack selectedItem;
+ private final int selectedIndex;
+
+ @ApiStatus.Internal
+ public PlayerBundleItemSelectEvent(final Player player, final ItemStack bundle, final ItemStack selectedItem, final int selectedIndex) {
+ super(player);
+
+ this.bundle = bundle;
+ this.selectedItem = selectedItem;
+ this.selectedIndex = selectedIndex;
+ }
+
+ /**
+ * Gets the bundle item.
+ * @return the bundle item
+ */
+ public ItemStack getBundle() {
+ return this.bundle;
+ }
+
+ /**
+ * Gets the selected item inside the bundle.
+ * @return the selected item
+ */
+ public ItemStack getSelectedItem() {
+ return this.selectedItem;
+ }
+
+ /**
+ * 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..00ccf8091df6 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 d24c6e5369a25e847a342c0a97735c6270ae58a2..b62928b2203434fbc77b374428a69d19d89d99e9 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
+@@ -1570,7 +1604,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
+@@ -1609,6 +1643,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
+@@ -1642,7 +1677,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
+@@ -1780,7 +1825,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
+@@ -1799,6 +1844,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..11d69922f815 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 b62928b2203434fbc77b374428a69d19d89d99e9..46d6c6e345c3eb9652c9ef9737d8446165ebe466 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -1819,9 +1819,14 @@ public class ServerGamePacketListenerImpl
+@@ -1848,9 +1848,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..a303f4278490 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,41 @@
}
@Override
-@@ -572,6 +_,7 @@
+@@ -567,11 +_,41 @@
+ public void handleBundleItemSelectedPacket(final ServerboundSelectBundleItemPacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());
+ this.player.containerMenu.setSelectedBundleItemIndex(packet.slotId(), packet.selectedItemIndex());
++
++ // Paper start - PlayerBundleItemSelectEvent
++ if (this.player.isCreative() && this.player.containerMenu instanceof net.minecraft.world.inventory.InventoryMenu) {
++ return;
++ }
++
++ final int slotId = packet.slotId();
++ final int selectedIndex = packet.selectedItemIndex();
++ if (slotId < 0 || slotId >= this.player.containerMenu.slots.size() || selectedIndex == net.minecraft.world.item.component.BundleContents.NO_SELECTED_ITEM_INDEX) {
++ return;
++ }
++
++ final ItemStack item = this.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) {
++ if (!new net.minecraft.world.item.component.BundleContents.Mutable(contents).indexIsOutsideAllowedBounds(selectedIndex)) {
++ new io.papermc.paper.event.player.PlayerBundleItemSelectEvent(
++ this.getCraftPlayer(),
++ item.asBukkitMirror(),
++ java.util.Objects.requireNonNull(contents.getSelectedItem()).create().asBukkitMirror(),
++ selectedIndex
++ ).callEvent();
++ }
++ }
++ // Paper end - PlayerBundleItemSelectEvent
+ }
+
@Override
public void handleRecipeBookChangeSettingsPacket(final ServerboundRecipeBookChangeSettingsPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());
From 69f438169761d575d40c866a7d7ed13581162360 Mon Sep 17 00:00:00 2001
From: mrtz6 <78804817+mrtz6@users.noreply.github.com>
Date: Wed, 20 May 2026 22:00:09 +0200
Subject: [PATCH 2/8] style: adjust javadoc formatting
---
.../paper/event/player/PlayerBundleItemSelectEvent.java | 3 +++
1 file changed, 3 insertions(+)
diff --git a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBundleItemSelectEvent.java b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBundleItemSelectEvent.java
index b5d3ced4945c..1e0f385219b0 100644
--- a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBundleItemSelectEvent.java
+++ b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBundleItemSelectEvent.java
@@ -32,6 +32,7 @@ public PlayerBundleItemSelectEvent(final Player player, final ItemStack bundle,
/**
* Gets the bundle item.
+ *
* @return the bundle item
*/
public ItemStack getBundle() {
@@ -40,6 +41,7 @@ public ItemStack getBundle() {
/**
* Gets the selected item inside the bundle.
+ *
* @return the selected item
*/
public ItemStack getSelectedItem() {
@@ -48,6 +50,7 @@ public ItemStack getSelectedItem() {
/**
* Gets the selected index.
+ *
* @return the selected index
*/
public int getSelectedIndex() {
From 1e189bda49955db1c5c785e7f6b18214da2fa544 Mon Sep 17 00:00:00 2001
From: mrtz6 <78804817+mrtz6@users.noreply.github.com>
Date: Wed, 20 May 2026 22:21:00 +0200
Subject: [PATCH 3/8] format: add clarification parenthesis
---
.../server/network/ServerGamePacketListenerImpl.java.patch | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 a303f4278490..6bce759532d3 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
@@ -405,7 +405,7 @@
+
+ final net.minecraft.world.item.component.BundleContents contents = item.get(net.minecraft.core.component.DataComponents.BUNDLE_CONTENTS);
+ if (contents != null) {
-+ if (!new net.minecraft.world.item.component.BundleContents.Mutable(contents).indexIsOutsideAllowedBounds(selectedIndex)) {
++ if (!(new net.minecraft.world.item.component.BundleContents.Mutable(contents)).indexIsOutsideAllowedBounds(selectedIndex)) {
+ new io.papermc.paper.event.player.PlayerBundleItemSelectEvent(
+ this.getCraftPlayer(),
+ item.asBukkitMirror(),
From 0115042404b3d8af7aef509d02b588b138f61c0a Mon Sep 17 00:00:00 2001
From: mrtz6 <78804817+mrtz6@users.noreply.github.com>
Date: Wed, 20 May 2026 22:49:25 +0200
Subject: [PATCH 4/8] docs: clarify return on creative mode
---
...collision-checking-in-player-move-packet-ha.patch | 12 ++++++------
.../0026-DataConverter-Moonrise-co-fixes.patch | 4 ++--
.../network/ServerGamePacketListenerImpl.java.patch | 4 +++-
3 files changed, 11 insertions(+), 9 deletions(-)
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 00ccf8091df6..b19632d06779 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 d24c6e5369a25e847a342c0a97735c6270ae58a2..b62928b2203434fbc77b374428a69d19d89d99e9 100644
+index ec9ad6ae3166771ccc43f10b9b7f58831376b924..718cfc527f93c1cb2e7808a1d8d511790efcff6c 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 d24c6e5369a25e847a342c0a97735c6270ae58a2..b62928b2203434fbc77b374428a69d19
}
@Override
-@@ -1570,7 +1604,7 @@ public class ServerGamePacketListenerImpl
+@@ -1572,7 +1606,7 @@ public class ServerGamePacketListenerImpl
}
}
@@ -81,7 +81,7 @@ index d24c6e5369a25e847a342c0a97735c6270ae58a2..b62928b2203434fbc77b374428a69d19
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
-@@ -1609,6 +1643,7 @@ public class ServerGamePacketListenerImpl
+@@ -1611,6 +1645,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 d24c6e5369a25e847a342c0a97735c6270ae58a2..b62928b2203434fbc77b374428a69d19
// Paper start - prevent position desync
if (this.awaitingPositionFromClient != null) {
return; // ... thanks Mojang for letting move calls teleport across dimensions.
-@@ -1642,7 +1677,17 @@ public class ServerGamePacketListenerImpl
+@@ -1644,7 +1679,17 @@ public class ServerGamePacketListenerImpl
}
// Paper start - Add fail move event
@@ -108,7 +108,7 @@ index d24c6e5369a25e847a342c0a97735c6270ae58a2..b62928b2203434fbc77b374428a69d19
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);
-@@ -1780,7 +1825,7 @@ public class ServerGamePacketListenerImpl
+@@ -1782,7 +1827,7 @@ public class ServerGamePacketListenerImpl
private boolean updateAwaitingTeleport() {
if (this.awaitingPositionFromClient != null) {
@@ -117,7 +117,7 @@ index d24c6e5369a25e847a342c0a97735c6270ae58a2..b62928b2203434fbc77b374428a69d19
this.awaitingTeleportTime = this.tickCount;
this.teleport(
this.awaitingPositionFromClient.x,
-@@ -1799,6 +1844,34 @@ public class ServerGamePacketListenerImpl
+@@ -1801,6 +1846,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 11d69922f815..c79988809121 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 b62928b2203434fbc77b374428a69d19d89d99e9..46d6c6e345c3eb9652c9ef9737d8446165ebe466 100644
+index 718cfc527f93c1cb2e7808a1d8d511790efcff6c..80233a6a16197f26b4c2d59189a69429d7819d11 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -1848,9 +1848,14 @@ public class ServerGamePacketListenerImpl
+@@ -1850,9 +1850,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<>();
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 6bce759532d3..e6917337ea2b 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,13 +382,15 @@
}
@Override
-@@ -567,11 +_,41 @@
+@@ -567,11 +_,43 @@
public void handleBundleItemSelectedPacket(final ServerboundSelectBundleItemPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());
this.player.containerMenu.setSelectedBundleItemIndex(packet.slotId(), packet.selectedItemIndex());
+
+ // Paper start - PlayerBundleItemSelectEvent
+ if (this.player.isCreative() && this.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;
+ }
+
From 3bb7d561abd79cf2312a7c45b2549075cb34bec7 Mon Sep 17 00:00:00 2001
From: mrtz6 <78804817+mrtz6@users.noreply.github.com>
Date: Thu, 21 May 2026 13:44:45 +0200
Subject: [PATCH 5/8] feat: expose previous selection and change supertype to
InventoryEvent
---
.../PlayerBundleItemSelectEvent.java | 130 ++++++++++++++++++
.../player/PlayerBundleItemSelectEvent.java | 68 ---------
...on-checking-in-player-move-packet-ha.patch | 12 +-
...0026-DataConverter-Moonrise-co-fixes.patch | 4 +-
.../ServerGamePacketListenerImpl.java.patch | 55 ++++----
5 files changed, 166 insertions(+), 103 deletions(-)
create mode 100644 paper-api/src/main/java/io/papermc/paper/event/inventory/PlayerBundleItemSelectEvent.java
delete mode 100644 paper-api/src/main/java/io/papermc/paper/event/player/PlayerBundleItemSelectEvent.java
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-api/src/main/java/io/papermc/paper/event/player/PlayerBundleItemSelectEvent.java b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBundleItemSelectEvent.java
deleted file mode 100644
index 1e0f385219b0..000000000000
--- a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerBundleItemSelectEvent.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package io.papermc.paper.event.player;
-
-import org.bukkit.entity.Player;
-import org.bukkit.event.HandlerList;
-import org.bukkit.event.player.PlayerEvent;
-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 PlayerEvent {
-
- private static final HandlerList HANDLER_LIST = new HandlerList();
-
- private final ItemStack bundle;
- private final ItemStack selectedItem;
- private final int selectedIndex;
-
- @ApiStatus.Internal
- public PlayerBundleItemSelectEvent(final Player player, final ItemStack bundle, final ItemStack selectedItem, final int selectedIndex) {
- super(player);
-
- this.bundle = bundle;
- this.selectedItem = selectedItem;
- this.selectedIndex = selectedIndex;
- }
-
- /**
- * Gets the bundle item.
- *
- * @return the bundle item
- */
- public ItemStack getBundle() {
- return this.bundle;
- }
-
- /**
- * Gets the selected item inside the bundle.
- *
- * @return the selected item
- */
- public ItemStack getSelectedItem() {
- return this.selectedItem;
- }
-
- /**
- * 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 b19632d06779..2c5f7d617533 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 ec9ad6ae3166771ccc43f10b9b7f58831376b924..718cfc527f93c1cb2e7808a1d8d511790efcff6c 100644
+index 679e32dcc04e4affca327e1f77920630ec3a1842..e96efe1c8192e9fb5930adc46195ce753856263d 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 ec9ad6ae3166771ccc43f10b9b7f58831376b924..718cfc527f93c1cb2e7808a1d8d51179
}
@Override
-@@ -1572,7 +1606,7 @@ public class ServerGamePacketListenerImpl
+@@ -1570,7 +1604,7 @@ public class ServerGamePacketListenerImpl
}
}
@@ -81,7 +81,7 @@ index ec9ad6ae3166771ccc43f10b9b7f58831376b924..718cfc527f93c1cb2e7808a1d8d51179
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
-@@ -1611,6 +1645,7 @@ public class ServerGamePacketListenerImpl
+@@ -1609,6 +1643,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 ec9ad6ae3166771ccc43f10b9b7f58831376b924..718cfc527f93c1cb2e7808a1d8d51179
// Paper start - prevent position desync
if (this.awaitingPositionFromClient != null) {
return; // ... thanks Mojang for letting move calls teleport across dimensions.
-@@ -1644,7 +1679,17 @@ public class ServerGamePacketListenerImpl
+@@ -1642,7 +1677,17 @@ public class ServerGamePacketListenerImpl
}
// Paper start - Add fail move event
@@ -108,7 +108,7 @@ index ec9ad6ae3166771ccc43f10b9b7f58831376b924..718cfc527f93c1cb2e7808a1d8d51179
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);
-@@ -1782,7 +1827,7 @@ public class ServerGamePacketListenerImpl
+@@ -1780,7 +1825,7 @@ public class ServerGamePacketListenerImpl
private boolean updateAwaitingTeleport() {
if (this.awaitingPositionFromClient != null) {
@@ -117,7 +117,7 @@ index ec9ad6ae3166771ccc43f10b9b7f58831376b924..718cfc527f93c1cb2e7808a1d8d51179
this.awaitingTeleportTime = this.tickCount;
this.teleport(
this.awaitingPositionFromClient.x,
-@@ -1801,6 +1846,34 @@ public class ServerGamePacketListenerImpl
+@@ -1799,6 +1844,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 c79988809121..bc3d24ba71ab 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 718cfc527f93c1cb2e7808a1d8d511790efcff6c..80233a6a16197f26b4c2d59189a69429d7819d11 100644
+index e96efe1c8192e9fb5930adc46195ce753856263d..f04d07d82f3d9c693fa7765570162677e09dd625 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -1850,9 +1850,14 @@ public class ServerGamePacketListenerImpl
+@@ -1848,9 +1848,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<>();
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 e6917337ea2b..c5b9a51aef9e 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,41 +382,42 @@
}
@Override
-@@ -567,11 +_,43 @@
+@@ -565,13 +_,43 @@
+
+ @Override
public void handleBundleItemSelectedPacket(final ServerboundSelectBundleItemPacket packet) {
- PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());
- this.player.containerMenu.setSelectedBundleItemIndex(packet.slotId(), packet.selectedItemIndex());
-+
+- PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());
++ net.minecraft.network.protocol.PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());
++ // this.player.containerMenu.setSelectedBundleItemIndex(packet.slotId(), packet.selectedItemIndex()); // Paper - PlayerBundleItemSelectEvent; moved to expose previous index
+ // Paper start - PlayerBundleItemSelectEvent
-+ if (this.player.isCreative() && this.player.containerMenu instanceof net.minecraft.world.inventory.InventoryMenu) {
++ if (!this.player.isCreative() || !(this.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;
-+ }
-+
-+ final int slotId = packet.slotId();
-+ final int selectedIndex = packet.selectedItemIndex();
-+ if (slotId < 0 || slotId >= this.player.containerMenu.slots.size() || selectedIndex == net.minecraft.world.item.component.BundleContents.NO_SELECTED_ITEM_INDEX) {
-+ return;
-+ }
-+
-+ final ItemStack item = this.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) {
-+ if (!(new net.minecraft.world.item.component.BundleContents.Mutable(contents)).indexIsOutsideAllowedBounds(selectedIndex)) {
-+ new io.papermc.paper.event.player.PlayerBundleItemSelectEvent(
-+ this.getCraftPlayer(),
-+ item.asBukkitMirror(),
-+ java.util.Objects.requireNonNull(contents.getSelectedItem()).create().asBukkitMirror(),
-+ selectedIndex
-+ ).callEvent();
++ final int slotId = packet.slotId();
++ final int selectedIndex = packet.selectedItemIndex();
++ if (slotId >= 0 && slotId < this.player.containerMenu.slots.size() && selectedIndex != net.minecraft.world.item.component.BundleContents.NO_SELECTED_ITEM_INDEX) {
++ final net.minecraft.world.item.ItemStack item = this.player.containerMenu.slots.get(slotId).getItem();
++ if (item.is(net.minecraft.tags.ItemTags.BUNDLES)) {
++ final net.minecraft.world.item.component.BundleContents contents = item.get(net.minecraft.core.component.DataComponents.BUNDLE_CONTENTS);
++ if (contents != null) {
++ if (!(new net.minecraft.world.item.component.BundleContents.Mutable(contents)).indexIsOutsideAllowedBounds(selectedIndex)) {
++ new io.papermc.paper.event.inventory.PlayerBundleItemSelectEvent(
++ this.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();
++ }
++ }
++ }
+ }
+ }
+ // Paper end - PlayerBundleItemSelectEvent
+ this.player.containerMenu.setSelectedBundleItemIndex(packet.slotId(), packet.selectedItemIndex());
}
@Override
From 7804af60f10a450e4e2d73a0c163748abef57393 Mon Sep 17 00:00:00 2001
From: mrtz6 <78804817+mrtz6@users.noreply.github.com>
Date: Thu, 21 May 2026 15:10:24 +0200
Subject: [PATCH 6/8] fix: revert FQDN addition
---
.../server/network/ServerGamePacketListenerImpl.java.patch | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
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 c5b9a51aef9e..7cc8a823e8d3 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,12 +382,10 @@
}
@Override
-@@ -565,13 +_,43 @@
-
+@@ -566,12 +_,42 @@
@Override
public void handleBundleItemSelectedPacket(final ServerboundSelectBundleItemPacket packet) {
-- PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());
-+ net.minecraft.network.protocol.PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());
+ // this.player.containerMenu.setSelectedBundleItemIndex(packet.slotId(), packet.selectedItemIndex()); // Paper - PlayerBundleItemSelectEvent; moved to expose previous index
+ // Paper start - PlayerBundleItemSelectEvent
+ if (!this.player.isCreative() || !(this.player.containerMenu instanceof net.minecraft.world.inventory.InventoryMenu)) {
From a0ded529266c6fccdbd8fe96e9b8169042432fac Mon Sep 17 00:00:00 2001
From: mrtz6 <78804817+mrtz6@users.noreply.github.com>
Date: Thu, 21 May 2026 16:12:13 +0200
Subject: [PATCH 7/8] docs: remove unnecessary comment
---
...collision-checking-in-player-move-packet-ha.patch | 12 ++++++------
.../0026-DataConverter-Moonrise-co-fixes.patch | 4 ++--
.../network/ServerGamePacketListenerImpl.java.patch | 3 +--
3 files changed, 9 insertions(+), 10 deletions(-)
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 2c5f7d617533..11f19a0cc658 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 679e32dcc04e4affca327e1f77920630ec3a1842..e96efe1c8192e9fb5930adc46195ce753856263d 100644
+index 897d08ff6ee4a590c345d35b8b5c0a3195f20184..81e8d5b62481229f1af04f376ed8bfbb876e8158 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 679e32dcc04e4affca327e1f77920630ec3a1842..e96efe1c8192e9fb5930adc46195ce75
}
@Override
-@@ -1570,7 +1604,7 @@ public class ServerGamePacketListenerImpl
+@@ -1569,7 +1603,7 @@ public class ServerGamePacketListenerImpl
}
}
@@ -81,7 +81,7 @@ index 679e32dcc04e4affca327e1f77920630ec3a1842..e96efe1c8192e9fb5930adc46195ce75
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
-@@ -1609,6 +1643,7 @@ public class ServerGamePacketListenerImpl
+@@ -1608,6 +1642,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 679e32dcc04e4affca327e1f77920630ec3a1842..e96efe1c8192e9fb5930adc46195ce75
// Paper start - prevent position desync
if (this.awaitingPositionFromClient != null) {
return; // ... thanks Mojang for letting move calls teleport across dimensions.
-@@ -1642,7 +1677,17 @@ public class ServerGamePacketListenerImpl
+@@ -1641,7 +1676,17 @@ public class ServerGamePacketListenerImpl
}
// Paper start - Add fail move event
@@ -108,7 +108,7 @@ index 679e32dcc04e4affca327e1f77920630ec3a1842..e96efe1c8192e9fb5930adc46195ce75
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);
-@@ -1780,7 +1825,7 @@ public class ServerGamePacketListenerImpl
+@@ -1779,7 +1824,7 @@ public class ServerGamePacketListenerImpl
private boolean updateAwaitingTeleport() {
if (this.awaitingPositionFromClient != null) {
@@ -117,7 +117,7 @@ index 679e32dcc04e4affca327e1f77920630ec3a1842..e96efe1c8192e9fb5930adc46195ce75
this.awaitingTeleportTime = this.tickCount;
this.teleport(
this.awaitingPositionFromClient.x,
-@@ -1799,6 +1844,34 @@ public class ServerGamePacketListenerImpl
+@@ -1798,6 +1843,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 bc3d24ba71ab..0a9f0aa1022d 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 e96efe1c8192e9fb5930adc46195ce753856263d..f04d07d82f3d9c693fa7765570162677e09dd625 100644
+index 81e8d5b62481229f1af04f376ed8bfbb876e8158..128d50a97d04aa658e31caf21eb6a9c88c46efbe 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -1848,9 +1848,14 @@ public class ServerGamePacketListenerImpl
+@@ -1847,9 +1847,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<>();
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 7cc8a823e8d3..32833c0cc7df 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,11 +382,10 @@
}
@Override
-@@ -566,12 +_,42 @@
+@@ -566,12 +_,41 @@
@Override
public void handleBundleItemSelectedPacket(final ServerboundSelectBundleItemPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());
-+ // this.player.containerMenu.setSelectedBundleItemIndex(packet.slotId(), packet.selectedItemIndex()); // Paper - PlayerBundleItemSelectEvent; moved to expose previous index
+ // Paper start - PlayerBundleItemSelectEvent
+ if (!this.player.isCreative() || !(this.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,
From e5e563f67f3f2c236b3f41134c43773029248db7 Mon Sep 17 00:00:00 2001
From: mrtz6 <78804817+mrtz6@users.noreply.github.com>
Date: Thu, 21 May 2026 16:44:54 +0200
Subject: [PATCH 8/8] refactor: extract event firing to CraftEventFactory
---
...on-checking-in-player-move-packet-ha.patch | 12 +++----
...0026-DataConverter-Moonrise-co-fixes.patch | 4 +--
.../ServerGamePacketListenerImpl.java.patch | 31 ++----------------
.../craftbukkit/event/CraftEventFactory.java | 32 +++++++++++++++++++
4 files changed, 42 insertions(+), 37 deletions(-)
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 11f19a0cc658..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 897d08ff6ee4a590c345d35b8b5c0a3195f20184..81e8d5b62481229f1af04f376ed8bfbb876e8158 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 897d08ff6ee4a590c345d35b8b5c0a3195f20184..81e8d5b62481229f1af04f376ed8bfbb
}
@Override
-@@ -1569,7 +1603,7 @@ public class ServerGamePacketListenerImpl
+@@ -1542,7 +1576,7 @@ public class ServerGamePacketListenerImpl
}
}
@@ -81,7 +81,7 @@ index 897d08ff6ee4a590c345d35b8b5c0a3195f20184..81e8d5b62481229f1af04f376ed8bfbb
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
-@@ -1608,6 +1642,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 897d08ff6ee4a590c345d35b8b5c0a3195f20184..81e8d5b62481229f1af04f376ed8bfbb
// Paper start - prevent position desync
if (this.awaitingPositionFromClient != null) {
return; // ... thanks Mojang for letting move calls teleport across dimensions.
-@@ -1641,7 +1676,17 @@ public class ServerGamePacketListenerImpl
+@@ -1614,7 +1649,17 @@ public class ServerGamePacketListenerImpl
}
// Paper start - Add fail move event
@@ -108,7 +108,7 @@ index 897d08ff6ee4a590c345d35b8b5c0a3195f20184..81e8d5b62481229f1af04f376ed8bfbb
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);
-@@ -1779,7 +1824,7 @@ public class ServerGamePacketListenerImpl
+@@ -1752,7 +1797,7 @@ public class ServerGamePacketListenerImpl
private boolean updateAwaitingTeleport() {
if (this.awaitingPositionFromClient != null) {
@@ -117,7 +117,7 @@ index 897d08ff6ee4a590c345d35b8b5c0a3195f20184..81e8d5b62481229f1af04f376ed8bfbb
this.awaitingTeleportTime = this.tickCount;
this.teleport(
this.awaitingPositionFromClient.x,
-@@ -1798,6 +1843,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 0a9f0aa1022d..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 81e8d5b62481229f1af04f376ed8bfbb876e8158..128d50a97d04aa658e31caf21eb6a9c88c46efbe 100644
+index 5933a3a25954c0c7a2eac2361a805c751f286684..be4c9d9d70f0bac382f008ab9decc9023dbc5e57 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -1847,9 +1847,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<>();
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 32833c0cc7df..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,38 +382,11 @@
}
@Override
-@@ -566,12 +_,41 @@
+@@ -566,12 +_,14 @@
@Override
public void handleBundleItemSelectedPacket(final ServerboundSelectBundleItemPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());
-+ // Paper start - PlayerBundleItemSelectEvent
-+ if (!this.player.isCreative() || !(this.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.
-+
-+ final int slotId = packet.slotId();
-+ final int selectedIndex = packet.selectedItemIndex();
-+ if (slotId >= 0 && slotId < this.player.containerMenu.slots.size() && selectedIndex != net.minecraft.world.item.component.BundleContents.NO_SELECTED_ITEM_INDEX) {
-+ final net.minecraft.world.item.ItemStack item = this.player.containerMenu.slots.get(slotId).getItem();
-+ if (item.is(net.minecraft.tags.ItemTags.BUNDLES)) {
-+ final net.minecraft.world.item.component.BundleContents contents = item.get(net.minecraft.core.component.DataComponents.BUNDLE_CONTENTS);
-+ if (contents != null) {
-+ if (!(new net.minecraft.world.item.component.BundleContents.Mutable(contents)).indexIsOutsideAllowedBounds(selectedIndex)) {
-+ new io.papermc.paper.event.inventory.PlayerBundleItemSelectEvent(
-+ this.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();
-+ }
-+ }
-+ }
-+ }
-+ }
-+ // Paper end - PlayerBundleItemSelectEvent
++ CraftEventFactory.callPlayerBundleItemSelectEvent(this.player, packet.slotId(), packet.selectedItemIndex()); // Paper - PlayerBundleItemSelectEvent
this.player.containerMenu.setSelectedBundleItemIndex(packet.slotId(), packet.selectedItemIndex());
}
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);