Skip to content

Commit ff2dbff

Browse files
committed
feat: add PlayerBundleItemSelectEvent
1 parent 10a73fe commit ff2dbff

4 files changed

Lines changed: 171 additions & 1 deletion

File tree

build-data/paper.at

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@ public net.minecraft.world.item.ItemStackLinkedSet TYPE_AND_TAG
560560
public net.minecraft.world.item.JukeboxSongPlayer song
561561
public net.minecraft.world.item.MapItem createNewSavedData(Lnet/minecraft/server/level/ServerLevel;IIIZZLnet/minecraft/resources/ResourceKey;)Lnet/minecraft/world/level/saveddata/maps/MapId;
562562
public net.minecraft.world.item.StandingAndWallBlockItem wallBlock
563+
public net.minecraft.world.item.component.BundleContents$Mutable indexIsOutsideAllowedBounds(I)Z
563564
public net.minecraft.world.item.component.ItemContainerContents MAX_SIZE
564565
public net.minecraft.world.item.component.ItemContainerContents items
565566
public net.minecraft.world.item.component.ResolvableProfile unpack()Lcom/mojang/datafixers/util/Either;
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package io.papermc.paper.event.inventory;
2+
3+
import org.bukkit.entity.Player;
4+
import org.bukkit.event.HandlerList;
5+
import org.bukkit.event.inventory.InventoryClickEvent;
6+
import org.bukkit.event.inventory.InventoryEvent;
7+
import org.bukkit.inventory.Inventory;
8+
import org.bukkit.inventory.InventoryView;
9+
import org.bukkit.inventory.ItemStack;
10+
import org.jetbrains.annotations.ApiStatus;
11+
import org.jspecify.annotations.NullMarked;
12+
13+
/**
14+
* Called when a {@link Player} selects an item inside a bundle.
15+
* <p>
16+
* NOTE: This event does not fire for bundle item selections in creative mode player inventories.
17+
*/
18+
@NullMarked
19+
public final class PlayerBundleItemSelectEvent extends InventoryEvent {
20+
21+
private static final HandlerList HANDLER_LIST = new HandlerList();
22+
23+
private final ItemStack bundle;
24+
private final int rawSlot;
25+
private final int slot;
26+
27+
private final ItemStack previousItem;
28+
private final ItemStack selectedItem;
29+
30+
private final int previousIndex;
31+
private final int selectedIndex;
32+
33+
@ApiStatus.Internal
34+
public PlayerBundleItemSelectEvent(final InventoryView view, final ItemStack bundle, final int rawSlot, final ItemStack previousItem, final ItemStack selectedItem, final int previousIndex, final int selectedIndex) {
35+
super(view);
36+
37+
this.bundle = bundle;
38+
this.rawSlot = rawSlot;
39+
this.slot = view.convertSlot(rawSlot);
40+
41+
this.previousItem = previousItem;
42+
this.selectedItem = selectedItem;
43+
44+
this.previousIndex = previousIndex;
45+
this.selectedIndex = selectedIndex;
46+
}
47+
48+
/**
49+
* Gets the player who triggered the event.
50+
*
51+
* @return the player
52+
*/
53+
public Player getPlayer() {
54+
return (Player) this.getView().getPlayer();
55+
}
56+
57+
/**
58+
* Gets the bundle item.
59+
*
60+
* @return the bundle item
61+
*/
62+
public ItemStack getBundle() {
63+
return this.bundle;
64+
}
65+
66+
/**
67+
* Gets the slot number of the bundle, depending on which {@link Inventory} it is located in.
68+
*
69+
* @return the slot number
70+
* @see InventoryClickEvent#getSlot()
71+
*/
72+
public int getSlot() {
73+
return this.slot;
74+
}
75+
76+
/**
77+
* Gets the raw slot number of the bundle inside the {@link InventoryView}.
78+
*
79+
* @return the raw slot number
80+
* @see InventoryClickEvent#getRawSlot()
81+
*/
82+
public int getRawSlot() {
83+
return this.rawSlot;
84+
}
85+
86+
/**
87+
* Gets the previously selected item inside the bundle. If no item was previously selected, this will return an empty item.
88+
89+
* @return the previously selected item
90+
*/
91+
public ItemStack getPreviousItem() {
92+
return this.previousItem;
93+
}
94+
95+
/**
96+
* Gets the selected item inside the bundle.
97+
*
98+
* @return the selected item
99+
*/
100+
public ItemStack getSelectedItem() {
101+
return this.selectedItem;
102+
}
103+
104+
/**
105+
* Gets the previously selected index. If no item was previously selected, this will be -1.
106+
*
107+
* @return the previously selected index
108+
*/
109+
public int getPreviousIndex() {
110+
return this.previousIndex;
111+
}
112+
113+
/**
114+
* Gets the selected index.
115+
*
116+
* @return the selected index
117+
*/
118+
public int getSelectedIndex() {
119+
return this.selectedIndex;
120+
}
121+
122+
@Override
123+
public HandlerList getHandlers() {
124+
return HANDLER_LIST;
125+
}
126+
127+
public static HandlerList getHandlerList() {
128+
return HANDLER_LIST;
129+
}
130+
}

paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,14 @@
382382
}
383383

384384
@Override
385-
@@ -572,6 +_,7 @@
385+
@@ -566,12 +_,14 @@
386+
@Override
387+
public void handleBundleItemSelectedPacket(final ServerboundSelectBundleItemPacket packet) {
388+
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());
389+
+ CraftEventFactory.callPlayerBundleItemSelectEvent(this.player, packet.slotId(), packet.selectedItemIndex()); // Paper - PlayerBundleItemSelectEvent
390+
this.player.containerMenu.setSelectedBundleItemIndex(packet.slotId(), packet.selectedItemIndex());
391+
}
392+
386393
@Override
387394
public void handleRecipeBookChangeSettingsPacket(final ServerboundRecipeBookChangeSettingsPacket packet) {
388395
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());

paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,6 +1606,38 @@ public static BlockIgniteEvent callBlockIgniteEvent(Level level, BlockPos pos, I
16061606
return event;
16071607
}
16081608

1609+
public static void callPlayerBundleItemSelectEvent(final net.minecraft.world.entity.player.Player player, final int slotId, final int selectedIndex) {
1610+
if (player.isCreative() && player.containerMenu instanceof net.minecraft.world.inventory.InventoryMenu) {
1611+
// 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,
1612+
// making it unfit for firing an event.
1613+
return;
1614+
}
1615+
1616+
if (slotId < 0 || slotId >= player.containerMenu.slots.size() || selectedIndex == net.minecraft.world.item.component.BundleContents.NO_SELECTED_ITEM_INDEX) {
1617+
return;
1618+
}
1619+
1620+
final net.minecraft.world.item.ItemStack item = player.containerMenu.slots.get(slotId).getItem();
1621+
if (!item.is(net.minecraft.tags.ItemTags.BUNDLES)) {
1622+
return;
1623+
}
1624+
1625+
final net.minecraft.world.item.component.BundleContents contents = item.get(net.minecraft.core.component.DataComponents.BUNDLE_CONTENTS);
1626+
if (contents == null || (new net.minecraft.world.item.component.BundleContents.Mutable(contents)).indexIsOutsideAllowedBounds(selectedIndex)) {
1627+
return;
1628+
}
1629+
1630+
new io.papermc.paper.event.inventory.PlayerBundleItemSelectEvent(
1631+
player.containerMenu.getBukkitView(),
1632+
item.asBukkitMirror(),
1633+
slotId,
1634+
contents.getSelectedItem() != null ? contents.getSelectedItem().create().asBukkitMirror() : org.bukkit.inventory.ItemStack.empty(),
1635+
contents.items().get(selectedIndex).create().asBukkitMirror(),
1636+
contents.getSelectedItemIndex(),
1637+
selectedIndex
1638+
).callEvent();
1639+
}
1640+
16091641
public static void handleInventoryCloseEvent(net.minecraft.world.entity.player.Player human, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
16101642
InventoryCloseEvent event = new InventoryCloseEvent(human.containerMenu.getBukkitView(), reason); // Paper
16111643
human.level().getCraftServer().getPluginManager().callEvent(event);

0 commit comments

Comments
 (0)