2020import org .bukkit .event .EventHandler ;
2121import org .bukkit .event .EventPriority ;
2222import org .bukkit .event .Listener ;
23+ import org .bukkit .event .inventory .ClickType ;
2324import org .bukkit .event .inventory .InventoryClickEvent ;
2425import org .bukkit .event .inventory .InventoryCloseEvent ;
2526import org .bukkit .event .inventory .InventoryDragEvent ;
@@ -50,7 +51,9 @@ public class SpawnerStorageAction implements Listener {
5051
5152 private record TransferResult (boolean anyItemMoved , boolean inventoryFull , int totalMoved ) {}
5253 private final Map <UUID , Long > lastItemClickTime = new ConcurrentHashMap <>();
53- private static final long CLICK_DELAY_MS = 300 ;
54+ private final Map <UUID , Long > lastControlClickTime = new ConcurrentHashMap <>();
55+ private static final long ITEM_CLICK_DELAY_MS = 150 ;
56+ private static final long CONTROL_CLICK_DELAY_MS = 300 ;
5457 private final Random random = new Random ();
5558 private GuiLayout layout ;
5659
@@ -71,7 +74,7 @@ public void loadConfig() {
7174 }
7275
7376
74- @ EventHandler (priority = EventPriority .HIGH )
77+ @ EventHandler (priority = EventPriority .LOWEST )
7578 public void onInventoryClick (InventoryClickEvent event ) {
7679 if (!(event .getWhoClicked () instanceof Player player ) ||
7780 !(event .getInventory ().getHolder (false ) instanceof StoragePageHolder holder )) {
@@ -80,14 +83,20 @@ public void onInventoryClick(InventoryClickEvent event) {
8083
8184 SpawnerData spawner = holder .getSpawnerData ();
8285 int slot = event .getRawSlot ();
83-
84- // Cancel event immediately to prevent any vanilla behavior
8586 event .setCancelled (true );
8687
88+ // Handle clicks outside valid storage GUI area
8789 if (slot < 0 || slot >= INVENTORY_SIZE ) {
8890 return ;
8991 }
9092
93+ // Handle item slot clicks (taking items from storage)
94+ if (isItemSlot (slot )) {
95+ handleItemSlotClick (player , slot , holder , spawner , event );
96+ return ;
97+ }
98+
99+ // Handle control button clicks
91100 if (isControlSlot (slot )) {
92101 handleControlSlotClick (player , slot , holder , spawner , event .getInventory (), layout );
93102 }
@@ -131,7 +140,7 @@ private void handleControlSlotClick(Player player, int slot, StoragePageHolder h
131140 messageService .sendMessage (player , "no_permission" );
132141 return ;
133142 }
134- if (isClickTooFrequent (player )) {
143+ if (isControlClickTooFrequent (player )) {
135144 return ;
136145 }
137146 // Check if there are items to sell
@@ -154,8 +163,171 @@ private boolean isControlSlot(int slot) {
154163 return layout != null && layout .isSlotUsed (slot );
155164 }
156165
166+ private boolean isItemSlot (int slot ) {
167+ // First 45 slots (0-44) are for storage items
168+ return slot >= 0 && slot < STORAGE_SLOTS && !isControlSlot (slot );
169+ }
170+
171+ /**
172+ * Handles clicks on item slots in the storage GUI.
173+ * ALL clicks transfer items directly to player inventory (no cursor interaction).
174+ * - LEFT CLICK: Take 1 item from stack
175+ * - RIGHT CLICK: Take half of stack
176+ * - SHIFT CLICK: Take entire stack
177+ */
178+ private void handleItemSlotClick (Player player , int slot , StoragePageHolder holder ,
179+ SpawnerData spawner , InventoryClickEvent event ) {
180+ // Anti-spam check
181+ if (isItemClickTooFrequent (player )) {
182+ return ;
183+ }
184+
185+ Inventory inventory = event .getInventory ();
186+ ItemStack clickedItem = inventory .getItem (slot );
187+
188+ // Nothing to take from empty slot
189+ if (clickedItem == null || clickedItem .getType () == Material .AIR ) {
190+ return ;
191+ }
192+
193+ // Determine amount to take based on click type
194+ ClickType clickType = event .getClick ();
195+ int amountToTake ;
196+
197+ switch (clickType ) {
198+ case LEFT :
199+ // Left click = take only 1 item
200+ amountToTake = 1 ;
201+ break ;
202+ case RIGHT :
203+ // Right click = take half
204+ amountToTake = (int ) Math .ceil (clickedItem .getAmount () / 2.0 );
205+ break ;
206+ case SHIFT_LEFT :
207+ case SHIFT_RIGHT :
208+ // Shift click = take all
209+ amountToTake = clickedItem .getAmount ();
210+ break ;
211+ default :
212+ // Ignore other click types
213+ return ;
214+ }
215+
216+ // Transfer items to player inventory
217+ transferToPlayerInventory (player , clickedItem , amountToTake , inventory , spawner , holder );
218+ }
219+
220+ /**
221+ * Optimized method to transfer items from storage to player inventory.
222+ * Handles all click types with a single efficient path.
223+ */
224+ private void transferToPlayerInventory (Player player , ItemStack clickedItem , int amountToTake ,
225+ Inventory storageInv , SpawnerData spawner , StoragePageHolder holder ) {
226+ PlayerInventory playerInv = player .getInventory ();
227+ ItemStack toTransfer = clickedItem .clone ();
228+ toTransfer .setAmount (amountToTake );
229+
230+ int amountMoved = 0 ;
231+ int remaining = amountToTake ;
232+
233+ // Optimize: Try to stack with existing items first (more efficient)
234+ for (int i = 0 ; i < 36 && remaining > 0 ; i ++) {
235+ ItemStack slot = playerInv .getItem (i );
236+
237+ if (slot != null && slot .getType () != Material .AIR && slot .isSimilar (toTransfer )) {
238+ // Found similar item - try to stack
239+ int space = slot .getMaxStackSize () - slot .getAmount ();
240+ if (space > 0 ) {
241+ int add = Math .min (space , remaining );
242+ slot .setAmount (slot .getAmount () + add );
243+ amountMoved += add ;
244+ remaining -= add ;
245+ }
246+ }
247+ }
248+
249+ // Then fill empty slots
250+ for (int i = 0 ; i < 36 && remaining > 0 ; i ++) {
251+ ItemStack slot = playerInv .getItem (i );
252+
253+ if (slot == null || slot .getType () == Material .AIR ) {
254+ int stackSize = Math .min (remaining , toTransfer .getMaxStackSize ());
255+ ItemStack newStack = toTransfer .clone ();
256+ newStack .setAmount (stackSize );
257+ playerInv .setItem (i , newStack );
258+ amountMoved += stackSize ;
259+ remaining -= stackSize ;
260+ }
261+ }
262+
263+ // Update VirtualInventory if any items were moved
264+ if (amountMoved > 0 ) {
265+ ItemStack removed = toTransfer .clone ();
266+ removed .setAmount (amountMoved );
267+
268+ if (spawner .removeItemsAndUpdateSellValue (List .of (removed ))) {
269+ // Update display efficiently
270+ updatePageAfterRemoval (player , storageInv , spawner , holder );
271+
272+ // Single sound effect
273+ player .playSound (player .getLocation (), Sound .ENTITY_ITEM_PICKUP , 0.5f , 1.0f );
274+
275+ // Notify if inventory was full
276+ if (remaining > 0 ) {
277+ messageService .sendMessage (player , "inventory_full" );
278+ }
279+ }
280+ } else {
281+ // No items moved - inventory full
282+ messageService .sendMessage (player , "inventory_full" );
283+ }
284+ }
285+
286+
287+ /**
288+ * Updates the page display after items are removed from storage.
289+ */
290+ private void updatePageAfterRemoval (Player player , Inventory inventory ,
291+ SpawnerData spawner , StoragePageHolder holder ) {
292+ // Recalculate pages
293+ int newTotalPages = calculateTotalPages (spawner );
294+ int currentPage = holder .getCurrentPage ();
295+
296+ // Clamp to valid page range
297+ int adjustedPage = Math .max (1 , Math .min (currentPage , newTotalPages ));
298+
299+ holder .setTotalPages (newTotalPages );
300+ if (adjustedPage != currentPage ) {
301+ holder .setCurrentPage (adjustedPage );
302+ }
303+ holder .updateOldUsedSlots ();
304+
305+ // Update display
306+ SpawnerStorageUI spawnerStorageUI = plugin .getSpawnerStorageUI ();
307+ spawnerStorageUI .updateDisplay (inventory , spawner , adjustedPage , newTotalPages );
308+
309+ // Update title if pages changed
310+ if (newTotalPages != currentPage || adjustedPage != currentPage ) {
311+ updateInventoryTitle (player , spawner , adjustedPage , newTotalPages );
312+ }
313+
314+ // Update hologram and other viewers
315+ spawner .updateHologramData ();
316+ spawnerGuiViewManager .updateSpawnerMenuViewers (spawner );
317+
318+ // Check capacity
319+ if (spawner .getMaxSpawnerLootSlots () > holder .getOldUsedSlots () && spawner .getIsAtCapacity ()) {
320+ spawner .setIsAtCapacity (false );
321+ }
322+
323+ // Mark as modified
324+ if (!spawner .isInteracted ()) {
325+ spawner .markInteracted ();
326+ }
327+ }
328+
157329 private void handleDropPageItems (Player player , SpawnerData spawner , Inventory inventory ) {
158- if (isClickTooFrequent (player )) {
330+ if (isControlClickTooFrequent (player )) {
159331 return ;
160332 }
161333
@@ -259,7 +431,7 @@ private void dropItemsInDirection(Player player, List<ItemStack> items) {
259431
260432
261433 private void openFilterConfig (Player player , SpawnerData spawner ) {
262- if (isClickTooFrequent (player )) {
434+ if (isControlClickTooFrequent (player )) {
263435 return ;
264436 }
265437 filterConfigUI .openFilterConfigGUI (player , spawner );
@@ -304,17 +476,30 @@ private void updateInventoryTitle(Player player, SpawnerData spawner, int page,
304476 }
305477 }
306478
307- private boolean isClickTooFrequent (Player player ) {
479+ private boolean isItemClickTooFrequent (Player player ) {
308480 long now = System .currentTimeMillis ();
309481 long last = lastItemClickTime .getOrDefault (player .getUniqueId (), 0L );
310482 lastItemClickTime .put (player .getUniqueId (), now );
311- return (now - last ) < CLICK_DELAY_MS ;
483+
484+ if ((now - last ) < ITEM_CLICK_DELAY_MS ) {
485+ messageService .sendMessage (player , "click_too_fast" );
486+ return true ;
487+ }
488+ return false ;
489+ }
490+
491+ private boolean isControlClickTooFrequent (Player player ) {
492+ long now = System .currentTimeMillis ();
493+ long last = lastControlClickTime .getOrDefault (player .getUniqueId (), 0L );
494+ lastControlClickTime .put (player .getUniqueId (), now );
495+ return (now - last ) < CONTROL_CLICK_DELAY_MS ;
312496 }
313497
314498 @ EventHandler
315499 public void onPlayerQuit (PlayerQuitEvent event ) {
316500 UUID playerId = event .getPlayer ().getUniqueId ();
317501 lastItemClickTime .remove (playerId );
502+ lastControlClickTime .remove (playerId );
318503 }
319504
320505 private void openMainMenu (Player player , SpawnerData spawner ) {
@@ -352,7 +537,7 @@ private boolean isBedrockPlayer(Player player) {
352537 }
353538
354539 private void handleSortItemsClick (Player player , SpawnerData spawner , Inventory inventory ) {
355- if (isClickTooFrequent (player )) {
540+ if (isControlClickTooFrequent (player )) {
356541 return ;
357542 }
358543
@@ -455,7 +640,7 @@ private void openLootPage(Player player, SpawnerData spawner, int page) {
455640 }
456641
457642 public void handleTakeAllItems (Player player , Inventory sourceInventory ) {
458- if (isClickTooFrequent (player )) {
643+ if (isControlClickTooFrequent (player )) {
459644 return ;
460645 }
461646 StoragePageHolder holder = (StoragePageHolder ) sourceInventory .getHolder (false );
0 commit comments