Working with Events

Sponge provides a system to (1) fire events and then (2) listen for events.

Overview

Events are used to inform plugins of certain happenings. Many events can also be cancelled – that is, the action that the event refers to can be prevented from occurring. Cancellable events implement the Cancellable interface.

Sponge itself contains many events; however, plugins can create their own events which other plugins can subscribe to.

Subscribing is the act of listening to an event. An event handler is what subscribes to an event. Event handlers are assigned a priority that determines the order in which the event handler is run in context of other event handlers (such as those from other plugins). For example, an event handler with EARLY priority will return before most other event handlers.

Events cannot be sent to a specific set of plugins. All plugins that subscribe to an event will be notified of the event.

The event bus or event manager is the class that keeps track of which plugins have subscribed to which event, and is also responsible for distributing events to event handlers. Sponge comes with an event bus.

An important note about events in Sponge is that the event bus supports supertypes. For example, there’s a BlockBreakEvent that extends a BlockChangeEvent. A plugin could subscribe to the BlockChangeEvent and still receive BlockBreakEvent events. However, a plugin subscribed to just BlockBreakEvent would not receive other types of BlockChangeEvent.

The event bus in Sponge is a high-performance event bus.

Event Handlers

In order to listen for event, an event handler must be registered. This is done by making a method with any name, having the first (and only) parameter be of the desired event type that you want to catch, and then affixing @Subscribe to the method. This is illustrated below.

import org.spongepowered.api.util.event.Subscribe;

@Subscribe
public void someEventHandler(SomeEvent event) {
    // Do something with the event
}

In addition, the object that has these methods must be registered with the event bus.

eventBus.register(theObject);

The event bus is the org.spongepowered.api.service.event.EventManager class. See Accessing Services to learn how to get access to this instance.

Tip

For event handlers on your main plugin class, you do not need to register the object for events because Sponge will do it automatically.

Note that, by default, @Subscribe is configured so that your event handler will not be called if the event in question is cancellable and has been cancelled (such as by another plugin).

Note

It is currently not possible to register a dynamic event handler by providing an event type and a handler.

About @Subscribe

The @Subscribe annotation has a few configurable fields:

  • order is the order in which the event handler is to be run. See the Order enum in Sponge to see the available options.
  • ignoreCancelled, if true (which is default true), causes the event handler to be skipped if the event in question is cancellable and has been cancelled (by a previously-executed plugin, for example).

Firing Events

To dispatch an event, you need an object that implements the org.spongepowered.api.util.event.Event interface.

You can fire events using the event bus (org.spongepowered.api.service.event.EventManager):

boolean cancelled = eventBus.post(theEventObject);

The method returns true if the event was cancelled, false if not.

Firing Sponge Events

It is possible to generate instances of built-in events with the static SpongeEventFactory.

Creating Custom Events

You can write your own event classes and dispatch those events using the method described above.

An event class must implement the Event interface. Alternatively you can extend the AbstractEvent class.

If you want your event to be cancellable, the class must also implement Cancellable.

Example: Custom Event Class

package example.event;

import org.spongepowered.api.entity.player.Player;
import org.spongepowered.api.event.AbstractEvent;
import org.spongepowered.api.util.event.Cancellable;

public class PrivateMessageEvent extends AbstractEvent implements Cancellable {

   private boolean cancelled = false;

   private Player sender;
   private Player recipient;

   private String message;

   public Player getSender() {
      return sender;
   }

   public Player getRecipient() {
      return recipient;
   }

   public String getMessage() {
      return message;
   }

   @Override
   public boolean isCancelled() {
      return cancelled;
   }

   @Override
   public void setCancelled(boolean cancel) {
      cancelled = cancel;
   }

   public PrivateMessageEvent(Player sender, Player recipient, String message) {
      this.sender = sender;
      this.recipient = recipient;
      this.message = message;
   }
}

Example: Fire Custom Event

eventBus.post(new PrivateMessageEvent(playerA, playerB, "Hello World!");

Example: Listen for Custom Event

@Subscribe
public void pmEventHandler(PrivateMessageEvent event) {
    if(event.getMessage().equals("hi i am from planetminecraft")) {
       event.setCancelled();
       return;
    }

    String senderName = event.getSender().getName();
    event.getReceipient().sendMessage(ChatTypes.CHAT, "PM from " + senderName + ": " + event.getMessage());
}

Callbacks

Callbacks are a more advanced feature of Sponge’s event system.

Callbacks allow plugins to cooperate better when they override vanilla behavior. When an event is invoked, Sponge runs through the event handlers in order from first to last. Then Sponge runs through the callback list in order from last to first. Vanilla is always the first callback added, meaning that vanilla’s handler will be executed last.

Plugins that don’t use callbacks can also use the simpler setCancelled(boolean) method, which will disable all callbacks. However, some plugins may just need to disable vanilla behavior, modify another plugin’s behavior, or disable that behavior completely. These are cases where the flexibility offered through callbacks is required.

A plugin can add as many callbacks as it needs during an event, and plugins can cancel specific callbacks. However, a plugin cannot reorder or remove callbacks, as some behaviors (especially vanilla) cannot be reordered. Additionally, all modifications to the callback list, should be done in the event handler itself. Attempting to change the list during callback execution will cause a ConcurrentModificationException. Callbacks should only be added or cancelled in event handlers who’s Order property allows event cancellation.

Note

ExplosionEvent doesn’t exist in the API currently, it is just used for example purposes.

Example: Adding a Callback to Disable Explosions and Spawn an Arrow

Note

This is a bad example, but use-cases for callbacks are going to be very specific so this just demonstrates the code needed to add one.

@Subscribe
// final not required unless using an inner class that needs access to it.
public void onExplosion(final ExplosionEvent event) {
    for (EventCallback callback : event.getCallbacks()) {
        // Disable vanilla behavior
        if (callback.isBaseGame()) {
            if (callback instanceof Cancellable) {
                ((Cancellable) callback).setCancelled(true);
            }
        }
    }

    event.getCallbacks().add(new EventCallback() {
        public boolean isBaseGame() {
            // Not a base game (i.e. Vanilla) behavior
            return false;
        }

        public void run() {
            Extent extent = event.getEntity().getLocation().getExtent();

            // Create an arrow
            extent.createEntity(EntityTypes.ARROW, event.getEntity().getLocation().getPosition());
        }
    });
}

Example: Disable Chair Sitting Added by CraftBook

Note

This example will break if other plugins enable or disable callbacks.

@Subscribe
public void onPlayerInteractBlock(PlayerInteractBlockEvent event) {
    boolean foundChair = false;

    for (Callback callback : event.getCallbacks())
        if (callback instanceof com.sk89q.craftbook.mechanic.Chair) {
            if (callback instanceof Cancellable) {
                ((Cancellable) callback).setCancelled(true);
            }
            foundChair = true;
            break;
        }
    }

    if (foundChair) {
        for (Callback callback : event.getCallbacks()) {
            if (!(callback instanceof com.sk89q.craftbook.mechanic.Chair)) {
              if (callback instanceof Cancellable) {
                  ((Cancellable) callback).setCancelled(false);
              }
            }
        }
    }
}

Example: Modifying Behaviors

@Subscribe
public void onExplosion(ExplosionEvent event) {
    for (Callback callback : event.getCallbacks()) {
        if (callback instanceof example.FireworksExplosion) {
            ((example.FireworksExplosion) callback).setYield(200);
        }
    }
}

Thanks to @sk89q for the callback examples. They were copied from his original PR #232.