Solved How to detect player not placing a block

Discussion in 'Spigot Plugin Development' started by Nuubles, Nov 14, 2018.

  1. Yes, you read the title correctly. I need to know when a player does NOT place a block but instead clicks a block (run the code only when the block is not placed). One example of this is when a player clicks a block with a flower in hand, sometimes it places it and sometimes it won't eg. on oak_planks vs on grass_block.

    The problem I'm having is that in PlayerInteractEvent you can only see when a player clicks a block, along with the information that which hand was used for this action, but I don't have a way of checking whether the block(flower) was actually placed on ground (special effects when clicking just with flower, but don't want to prevent player from placing it to the ground or pots).
    You could say that "just check that if the block player clicked is dirt" or something along the lines, but what if player clicked the side of, let's say a house, in order to place a flower to the ground, or the side of a dirt block, without actually placing the flower.

    I found two ways to handle this:
    Check if there is a "dirt/pot" type of block below the block at the clicked side of the clicked block
    and
    Somehow run BlockPlacedEvent before PlayerInteractEvent to block the following PlayerInteractEvent from happening, and causing the problems.

    Yes, the two methods above I suppose would work (haven't tested the latter yet though), but are there any other more elegant ways of doing this so that the code wouldn't look so messy?
     
    #1 Nuubles, Nov 14, 2018
    Last edited: Nov 14, 2018
  2. As per Javadocs of the event can be fired and already being marked as cancelled if the vanilla behaviour is to do nothing.
     
    • Informative Informative x 1
  3. Tried the mechanic, almost worked. Using your way of doing it would work when clicking air, but it does not resolve the problem which occurs when placing flowers facing the side of another block, as it says the event is not canceled.
    I mean clicking the side of a block with flower does say the event is not canceled, even though nothing actually happens, and clicking the side of another block on ground results in the flower being placed.

    Here's a video illustrating the problem


    Here's the stripped version of the code which I tested
    Code (Java):
    @EventHandler (priority = EventPriority.HIGHEST, ignoreCancelled = false)
    public void onPlayerInteract(PlayerInteractEvent event)
    {
        Bukkit.broadcastMessage("Cancelled: " + event.isCancelled());
    }
     
    #3 Nuubles, Nov 14, 2018
    Last edited: Nov 14, 2018
  4. If you are using spigot 1.12.2 or 1.13.2, report it as a bug on the issue tracker.
    Also ignoreCancelled is false by default.
     
    • Agree Agree x 1
  5. I raised a new ticket from this
     
  6. As to quote md_5:
    This thread is unresolved, will close by sunday/monday if no solution is found.
     
  7. Thank you! This event didn't have the player property so I had to get it the dirty way which may not always get the correct player but it gets the player. I created a new event called "NoActionEvent" which I'll be using in the future. I'll paste the code and a demonstration video + code of the usage. May need to modify this a bit to also create this event as uncancelled.

    Video of usage with the code below


    This is the modified version of the code which is needed to enable this event

    Code (Java):
    @Override
    public void onEnable()
    {
        NoActionEvent.register(this);
    }
    Here's the code which I used to blow the dandelion (not gonna paste the blowDandelion code here as it relies on my core plugin with too much files/data to paste here).

    Code (Java):
    @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
    private void noActionEvent(NoActionEvent event)
    {
        if(!pl.getConfig().getBoolean(configKey))
            return;
     
        if(event.getHand() == null || event.getPlayer().hasMetadata("NPC"))
            return;
     
        if(event.getItem() != null && event.getItem().getType().equals(Material.DANDELION))
            blowDandelion(event.getPlayer(), event.getHand());
    }

    The event class code
    Code (Java):

    import org.bukkit.Bukkit;
    import org.bukkit.block.Block;
    import org.bukkit.entity.Player;
    import org.bukkit.event.Cancellable;
    import org.bukkit.event.Event;
    import org.bukkit.event.EventHandler;
    import org.bukkit.event.EventPriority;
    import org.bukkit.event.HandlerList;
    import org.bukkit.event.Listener;
    import org.bukkit.event.block.Action;
    import org.bukkit.event.block.BlockBreakEvent;
    import org.bukkit.event.block.BlockCanBuildEvent;
    import org.bukkit.event.player.PlayerInteractEvent;
    import org.bukkit.inventory.EquipmentSlot;
    import org.bukkit.inventory.ItemStack;
    import org.bukkit.plugin.java.JavaPlugin;

    /**
     * This event is fired when no action is done when clicking a block or
     * clicking air. May fire multiple times, refer to PlayerInteractEvent.
     *
     * You MUST register this in the main class with
     * <i>NoActionEvent.register(Plugin);</i>
     *
     * @author Nuubles
     *
     */

    public class NoActionEvent extends Event implements Cancellable {

        private static final HandlerList handlers = new HandlerList();
        private static final NoActionEventListener listener = new NoActionEventListener();
        private boolean cancelled = false;
       
        /* Custom class variables */
        private Player player;
        private EquipmentSlot hand;
        private Block block;
        private Action action;
        private ItemStack item;

        /**
        * Initializes this class with all variables, true only when clicking air
        * @param player
        * @param block
        * @param hand
        * @param action
        * @param blockFace
        * @param item
        */

        public NoActionEvent(Player player, EquipmentSlot hand, Action action, ItemStack item, Block block)
        {
            this.player = player;
            this.hand = hand;
            this.action = action;
            this.item = item;
            this.block = block;
        }
       
        /**
        * Gets the player who did this action.
        * @return
        */

        public Player getPlayer()
        {
            return player;
        }
       
        /**
        * Gets the clicked block in this event
        * @return
        */

        public Block getBlock()
        {
            return block;
        }
       
        /**
        * Gets the hand used in this event
        * @return
        */

        public EquipmentSlot getHand()
        {
            return hand;
        }
       
        /**
        * Gets the action done
        * @return
        */

        public Action getAction()
        {
            return action;
        }

        /**
        * Gets the itemstack used by player
        * @return
        */

        public ItemStack getItem()
        {
            return item;
        }
       
        /**
        * Registers this event to be used
        * @param plugin plugin to which this will be registered
        */

        public static void register(JavaPlugin plugin) {
            Bukkit.getPluginManager().registerEvents(listener, plugin);
        }
       
        /**
        * {@inheritDoc}
        */

        public boolean isCancelled() {
            return cancelled;
        }

        /**
        * {@inheritDoc}
        */

        public void setCancelled(boolean cancelled) {
            this.cancelled = cancelled;
        }

        /**
        * {@inheritDoc}
        */
     
        public HandlerList getHandlers() {
            return handlers;
        }
       
        /**
        * {@inheritDoc}
        */

        public static HandlerList getHandlerList()
        {
            return handlers;
        }
       
        /**
        * Listenes to the necessary events to make this event possible
        * @author Nuubles
        *
        */

        private static class NoActionEventListener implements Listener
        {
            private boolean blockBroken = false;
            private Player player;
           
            @EventHandler (priority = EventPriority.MONITOR)
            private void blockBreak(BlockBreakEvent event)
            {
                blockBroken = true;
            }
           
            @EventHandler (priority = EventPriority.MONITOR)
            private void playerInteractEvent(PlayerInteractEvent event)
            {
                if(event.getAction().equals(Action.PHYSICAL) || blockBroken)
                {
                    blockBroken = false;
                    return;
                }
               
                if(event.isCancelled())
                    Bukkit.getPluginManager().callEvent(
                            new NoActionEvent(
                                    event.getPlayer(),
                                    event.getHand(),
                                    event.getAction(),
                                    event.getItem(),
                                    event.getClickedBlock())
                            );
               
                this.player = event.getPlayer();
            }
           
            @EventHandler (priority = EventPriority.MONITOR)
            private void blockCanBuildEvent(BlockCanBuildEvent event)
            {
                //Gets the players in a radius of 100
                /*Collection<Entity> entities =
                        event.getBlock().getWorld().getNearbyEntities(
                                event.getBlock().getLocation(), 20, 20, 20, (e -> (e instanceof Player)));
               
                Player player = null;
                Double minDist = null;
               
                //Get the closest players
                if(!entities.isEmpty()) {
                    for(Entity p : entities) {
                        double dist = p.getLocation().distanceSquared(event.getBlock().getLocation());
                        if(minDist == null) {
                            player = (Player)p;
                            minDist = dist;
                        }
                        else if(minDist > dist) {
                            player = (Player)p;
                            minDist = dist;
                        }
                    }
                }*/

                       
                if(!event.isBuildable() && player != null)
                {
                    Bukkit.getPluginManager().callEvent(
                            new NoActionEvent(
                                    player,
                                    EquipmentSlot.HAND,
                                    Action.RIGHT_CLICK_BLOCK,
                                    player.getInventory().getItemInMainHand(),
                                    event.getBlock()) );
                    player = null;
                }
            }
        }
    }
     
     
    #8 Nuubles, Nov 17, 2018
    Last edited: Nov 17, 2018
  8. Try to save last player of playerinteractevent and use it in BlockCanBuildEvent.

    Maybe here is room for some improvement @md_5, because there is definitely something missing here.
     
  9. Updated the code, it now gets the correct player. Seems to work pretty smoothly except when clicking inside some other block i.e trapdoor without actually opening it. I'll take a closer look at it later on and update the code.
     
  10. md_5

    Administrator Developer

    I've added a player to the event, it wasn't easily possible prior to 1.13 which is why it was missing
     
    • Like Like x 1
    • Friendly Friendly x 1