Event Priority Manipulation

Discussion in 'Resources' started by teej107, Sep 9, 2015.

Thread Status:
Not open for further replies.
  1. Offline

    teej107

    Now before I start to show you what I have to show you, I personally don't have/can't find a purpose to use this and I must warn you that you can seriously mess with plugins unless you are 100% sure of what you know you want.

    The question that I've seen occasionally on Bukkit:
    Pretty much everybody's answer is "you can't". That got me thinking though because I honestly never tried to see if the usual answer is right and the truth is that you actually can cancel events with certain priorities. Now there is usually a better alternative that the OP of the question can take, but as I was playing around with this, I also realized that you can have event priorities below LOWEST and higher than MONITOR.

    Some of you may have noticed that Bukkit has a RegisteredListener class. You might have guessed it by now, but this event priority manipulation can be accomplished with that class. The class provides these methods right here:

    Here is a basic example showing the event priorities in action.

    Code:
    public class EventPriorityTest extends JavaPlugin implements Listener
    {
        @Override
        public void onEnable()
        {
            Bukkit.getPluginManager().registerEvents(this, this);
        }
    
        @EventHandler(priority = EventPriority.LOWEST)
        public void lowestEvent(PlayerMoveEvent event)
        {
            eventTest(event, ChatColor.RED + "Lowest");
        }
    
        @EventHandler(priority = EventPriority.LOW)
        public void lowEvent(PlayerMoveEvent event)
        {
            eventTest(event, ChatColor.GOLD + "Low");
        }
    
        //Default
        @EventHandler(priority = EventPriority.NORMAL)
        public void normalEvent(PlayerMoveEvent event)
        {
            eventTest(event, ChatColor.YELLOW + "Normal");
        }
    
        @EventHandler(priority = EventPriority.HIGH)
        public void highEvent(PlayerMoveEvent event)
        {
            eventTest(event, ChatColor.GREEN + "High");
        }
    
        @EventHandler(priority = EventPriority.HIGHEST)
        public void highestEvent(PlayerMoveEvent event)
        {
            eventTest(event, ChatColor.AQUA + "Highest");
        }
    
        @EventHandler(priority = EventPriority.MONITOR)
        public void monitorEvent(PlayerMoveEvent event)
        {
            eventTest(event, ChatColor.LIGHT_PURPLE + "Monitor");
        }
    
        private void eventTest(PlayerMoveEvent event, String s)
        {
            if (!event.getTo().toVector().equals(event.getFrom().toVector()))
            {
                Bukkit.broadcastMessage(s);
            }
        }
    }
    Now when the event gets called, the messages get sent in this order (like it should):
    1. Lowest
    2. Low
    3. Normal
    4. High
    5. Highest
    6. Monitor
    If we want to start to mix things up a bit, it's time to make our own RegisteredListener wrapper class. Here is one.

    Code:
    public class RegisteredListenerWrapper extends RegisteredListener
    {
        private RegisteredListener registeredListener;
    
        public RegisteredListenerWrapper(RegisteredListener registeredListener)
        {
            //Filling in bogus info because it's required
            super(null, null, null, null, false);
    
            this.registeredListener = registeredListener;
        }
    
        @Override
        public Listener getListener()
        {
            return registeredListener.getListener();
        }
    
        @Override
        public Plugin getPlugin()
        {
            return registeredListener.getPlugin();
        }
    
        @Override
        public EventPriority getPriority()
        {
            return registeredListener.getPriority();
        }
    
        @Override
        public void callEvent(Event event) throws EventException
        {
            registeredListener.callEvent(event);
        }
    
        @Override
        public boolean isIgnoringCancelled()
        {
            return registeredListener.isIgnoringCancelled();
        }
    }
    You may replace Bukkit's RegisteredListeners with the wrapper by doing something like this:
    Code:
    //If you want to replace all listeners with the wrapper
            for(RegisteredListener rl : PlayerMoveEvent.getHandlerList().getRegisteredListeners())
            {
                //Unregister the current registered one so we don't have duplicates
                PlayerMoveEvent.getHandlerList().unregister(rl);
    
                //Stuffing in your wrapper RegisteredListener
                PlayerMoveEvent.getHandlerList().register(new RegisteredListenerWrapper(rl));
            }
    Now that we have full control in what goes on in the RegisteredListener, we could do something like this in the class:

    Code:
    @Override
        public EventPriority getPriority()
        {
            if(registeredListener.getPriority() == EventPriority.MONITOR)
                return EventPriority.HIGH;
    
            return registeredListener.getPriority();
        }
    Now if we call the events from the basic example plugin, the messages are now sent in this order:

    1. Lowest
    2. Low
    3. Normal
    4. High
    5. Monitor
    6. Highest
    Notice anything weird?

    If you want to have an above MONITOR or below LOWEST event priority, you could do something like this:

    Code:
        @Override
        public void callEvent(Event event) throws EventException
        {
            //Below LOWEST event priority code here
            registeredListener.callEvent(event);
            //Above MONITOR event priority code here
        }

    If you want to completely remove a plugin's event or a certain priority, you might do something like this:
    Code:
            for (RegisteredListener rl : <Event of choice>.getHandlerList().getRegisteredListeners())
            {
                if (rl.getPlugin().getName().equals("plugin name here") && rl.getPriority() == EventPriority.MONITOR)
                {
                    <Event of choice>.getHandlerList().unregister(rl);
                }
    
            }
    There is a lot you can do with this and I have just shown a few examples of what you can do. To tell you the truth, as I was writing this, I realized that it is entirely possible to create a plugin that can make events on MONITOR truly unmodifiable.
     
    Last edited: Sep 9, 2015
    meguy26 and Totom3 like this.
  2. Offline

    meguy26

    @teej107
    This is certainly an interesting concept. I could see it being useful for logging plugin (post monitor), and perhaps for creating a framework with additional priorities.
     
  3. Offline

    Totom3

    @teej107 Very interesting ideas in here! It opened my eyes to the fact that we can extend the Events API.

    What you can do with this: a better Events API? I could:
    • Use numeric event priorities (instead of enum).
    • Add a better way to unregister certain handlers (make sure users don't need to deal at all with the HandlersList mess).
    • Add a non-reflective way of handling events (for example, have a EventHandler2<E extends Event> interface).
    • As you pointed out, prevent monitor handlers from modifying an event (e.g. it throws an exception if you try to).
    I have seen many custom commands APIs, but can't say the same for events. I'd definitely switch to any API that offers what I listed above.
     
Thread Status:
Not open for further replies.

Share This Page