1.16.5 How to tell if a player drops an item by pressing 'Q' with inventory closed

Discussion in 'Spigot Plugin Development' started by Sigong, Jun 30, 2021.

  1. I have some code that I only want to run when a player drops an item in their hotbar by pressing the 'Q' key while their inventory is closed (as opposed to, with an open inventory: hovering over an item and pressing q, or clicking on the item and clicking outside of the inventory.)
    I found this thread: https://www.spigotmc.org/threads/dr...as-dropped-by-q-key-or-from-inventory.174092/

    Is there another way to do it that can be fully contained in the PlayerDropItemEvent listener?
    Right now, I have a check to see if their main hand item's material is AIR, and it seems to be working, but if their main hand item is AIR and they manually drag the item out of the inventory and drop it, the code still runs.
     
  2. Unfortunately, that is NOT true... Even though the JavaDocs say that the result is nullable, Player#getOpenInventory() will always wield an InventoryView - if the player has inventory open then that inventory, otherwise it will return the player's own inventory.
    I know this sounds stupid, but it's true - I've tested this countless times. Even when a player just logged in and didn't open anything it returns their own inventory's InventoryView.
    It's the reason why listening to #InventoryOpenEvent NEVER works for a player inventory - it's one of Mojang's stupidest quirks... What they did is: "Hey! Let's leave the player's inventory always open, and when they press the open inventory button it just shows it or hides it! Why? Cuz why the F not?! F you all plugin developers!!! ........ But do send an InventoryCloseEvent when they close it. Just to F with them even more!..."
    So yeah... you can never know if a player's inventory is closed only via events.
    Sorry to disappoint...
    Good luck!

    Edit:
    I just thought of a way to do this!
    It requires you to listen to 2 events: InventoryClickEvent - to know if the player dropped something from their inventory: if yes then put the player in a list for 2 ticks (I think... test) and then when a PlayerDropItemEvent is called check if they're in that list - if yes then ignore, otherwise do your thing. Of course, don't forget to make sure that the InventoryClickEvent's action (InventoryClickEvent#getAction()) is actually a drop action.
     
    #2 DMan16, Jun 30, 2021
    Last edited: Jun 30, 2021
  3. I'm aware of that quirk. The final solution found in that thread was to make a list of players who are involved in InventoryClickEvents in InventoryType.CRAFTING, then figure out if they are dropping an item from the inventory by listening for InventoryCloseEvent.

    I guess a more specific version of my question would be (assuming what I am asking is possible): I've already figured out how to detect a drop from the main hand by testing for air in the main hand in the PlayerDropItemEvent handler. How do I prevent false positives when a player drops an item from an open inventory with air in the main hand?
     
  4. Yeah apologies for the fake news lmao. Did more digging around, apparently after 1.12 the survival inv is handled completely client side and the client is not even sending packets when a player is opening his/her inv, which means... even protocollib won't help.

    I think your edits will still be true even if the player drops from his/her inv. For example if you hover your mouse over an item in your inv and press Q, both the click and drop event are fired.
     
  5. Look at my edit, I added a pretty simple solution that requires fewer event listeners:
    Hope it pans out!
     
    • Optimistic Optimistic x 1
  6. That's a good idea, I'll let you know if it works.
     
  7. Well it would be kinda the same if they drop their held item with or without an inventory open right? Also what if the player has 2 items and drops 1, then the held item won't be air, what do you do in that case?
     
  8. That's fair, right now the code runs for the condition I want (drop item from mainhand without inventory open), but it also runs for some unwanted conditions (drop item from mainhand with inventory open, drop item from open inventory when mainhand has air). I'm trying to figure out how to prevent it from running for those two conditions, and Dman16's idea seems like a good one, so that's the next one I'll try.
    The item in question is a sword, so having multiple of them in a slot isn't an issue.
     
    • Like Like x 1
  9. Thanks for the suggestion, I got it working! Here's my code for anyone else needing this solution:
    (As a reminder, my goal was to run code when a specific sword ItemStack is dropped by pressing Q with a closed inventory (sort of like a sword special ability))

    Code (Java):
    @EventHandler
    public void onDrop(PlayerDropItemEvent event){
        if(event.getItemDrop().getItemStack().equals(sword) && !tempDropList.contains(event.getPlayer().getUniqueId())){
            event.setCancelled(true);
         
            //RUN THE DESIRED CODE HERE

        }
    }

    List<UUID> tempDropList = new ArrayList<UUID>(); //List of player uuids of players who have in the past 1/4 second done a click that will result in a sword drop

    @EventHandler
    public void openInventoryDropDetector(InventoryClickEvent event){
    if((event.getSlot() == -999 && event.getWhoClicked().getItemOnCursor().equals(sword)) || //If the sword is on the cursor and clicked outside of open inventory to drop it
            ((event.getClick() == ClickType.DROP || event.getClick() == ClickType.CONTROL_DROP) && event.getCurrentItem().equals(sword)) || //Q or control+Q in open inventory on slot with sword
            event.getClick() == ClickType.CREATIVE){ //A drop from inventory in the creative inventory is a ClickType.CREATIVE
            UUID uuid = event.getWhoClicked().getUniqueId();
            tempDropList.add(uuid);
            (new removeUUIDFromArrayListRunnable(uuid)).runTaskLater(instance, 5L);
        }
    }

    //Removes a uuid from the arraylist after a delay
    private class removeUUIDFromArrayListRunnable extends BukkitRunnable{

        public removeUUIDFromArrayListRunnable(UUID uuid){
            this.uuid = uuid;
        }

        private final UUID uuid;

        @Override
        public void run() {
            tempDropList.remove(uuid);
        }
    }
     
    #9 Sigong, Jul 1, 2021
    Last edited: Jul 1, 2021
    • Like Like x 1