diff --git a/source/plugin/event/causes.rst b/source/plugin/event/causes.rst new file mode 100644 index 000000000000..23d6b72fc741 --- /dev/null +++ b/source/plugin/event/causes.rst @@ -0,0 +1,122 @@ +============ +Event Causes +============ + +Events are great for attaching additional logic to game actions, but +they have the drawback of providing next to no context as to what has **caused** that event to +occur. The ``Cause`` object allows providing and receiving additional contextual information about +the event. This contextual information can then used to modify the behavior of your event listener. + +For example, a world protection plugin needs information on what player has caused a +ChangeBlockEvent to occur before they can decide if the event should be cancelled or not. +Rather than go with the traditional route of creating a multitude of subevents for the different source +conditions this information is instead provided in the ``Cause`` of the event. + +Any event which extends ``CauseTracked`` provides a ``Cause`` object which can be interogated +for the information pertaining to why the event was fired. The Cause object can be retrived from +an event by simply calling ``event.getCause()``. + +Retrieving objects from a Cause +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Structurally, a ``Cause`` object contains a sequential list of objects. There are several methods of +retrieving information from a Cause object which we will discuss here, for a more complete +listing please see the javadocs **link**. + +.. note:: + + The objects within a cause are ordered such that the first object is the most immediate + cause of the event, and subsequent objects are of decreasing importance and/or may only + provide contextual information. + +``Cause#root()`` returns the first object within the cause, if it exists. This object is the most +immediate or direct cause of the event. + +``Cause#first(Class)`` returns the first object in the cause chain whose type is either the +same as or is a subtype of the given class. For example given a cause which contained a player +followed by an entity ``[Player, Entity, ...]`` + +.. code-block:: java + + @Listener + public void onEvent(ExampleCauseEvent event) { + Cause cause = event.getCause(); // [Player, Entity] + Optional firstPlayer = cause.first(Player.class); // 1 + Optionml firstEntity = cause.first(Entity.class); // 2 + } + +Both optionals would contain the player object as it's type directly matched request for a +Player type and it matched the request for an Entity type as Player is a subtype of Entity. + +``Cause#last(Class)`` is similar to *Cause#first(Class)* except it returns the last value in +the cause chain matching the type. + +Continuing from the example above, if we instead changed it to call ``Cause.last(Class)`` the first +optional would contain the player object still, but the second optional would now contain +the entity that we passed in the second position of the cause. + +``Cause.has(Class)`` returns a boolean value and can be used to check if a cause chain +contains any object matching the provided type. + +``Cause.all()`` simply returns all objects within the cause allowing more advanced handling. + +Named Causes +~~~~~~~~~~~~ + +Sometimes the ordering of objects within the cause isn't enough to get the proper idea of what +an object represents in relation to the event. This is where ``NamedCause`` comes in. Named +causes provide a method for tagging objects within a cause with a **unique** name allowing them +to be easily identified and requested. Some examples of use cases for named causes is the +`Notifier` of a GrowBlockEvent or the `Source` of a DamageEntityEvent. + +**Retrieving a named entry from a cause** + +.. code-block:: java + + @Listener + public void onGrow(GrowBlockEvent event) { + Optional notifier = event.getCause().get(NamedCause.NOTIFIER); + } + +This example makes use of ``Cause.get(String name)`` which can be used to retrive the object +associated with a name if it is present within the cause chain. Additionally +``Cause.getNamedCauses()`` provides a ``Map`` which can be used to find all +present names and their associated objects. + +.. note:: + + Some common identifying names for ``NamedCause``\ s are present as static fields in the + ``NamedCause`` class. Identifiers which are specific to certain events can often be found + as static fields on the event class, for example ``DamageEntityEvent.SOURCE``. + +Creating custom Causes +~~~~~~~~~~~~~~~~~~~~~~ + +Creating a cause to use when firing an event is extremely easy. The hardest part is deciding +what information to include in the cause. If you're firing an event from your plugin which is +usually triggered through other means perhaps you want to include your plugin container so +other plugins know that the event comes from your plugin. Or if you are firing the event on +behalf of a player due to some action it's usually a good idea to include that player in +the cause. + +.. note:: + + Cause objects are immutable therefore cannot be modified once created. + +In the simplest case where you don't want to provide any information you can simply use +``Cause.of()`` to get a cause which contains no objects. + +Using ``Cause.of(Object...)`` or ``Cause.ofNullable(Object...)`` you can construct a cause +from a series of objects. The objects will be added to the cause chain in the order that they +are passed to the method, so the first object parameter will become the root cause. + +If you already have a cause object and would like to append some more objects to the +chain you can use ``Cause.with(Object...)``. This constructs a new Cause object containing +first the objects already present in the original cause, then followed by the additional +objects that you provided. + +Finally if you wish to add an object to a cause with a defined named first call +``NamedCause.of(String name, Object object)`` and then pass the returned ``NamedCause`` instance +to the cause chain as you would a normal object. Simply calling ``Cause.of(name, object)`` +will result in a cause chain containing two objects (the String name, and then the object), +rather than one object with an attached name. diff --git a/source/plugin/event/custom.rst b/source/plugin/event/custom.rst new file mode 100644 index 000000000000..f47f24b4fa01 --- /dev/null +++ b/source/plugin/event/custom.rst @@ -0,0 +1,84 @@ +====================== +Creating Custom Events +====================== + +You can write your own event classes and dispatch those events using the method described above. + +An event class must either implement the ``Event`` interface or extend the ``AbstractEvent`` class. + +If you want your event to be cancellable, the class must also implement ``Cancellable``. + +Example: Custom Event Class +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: java + + import org.spongepowered.api.entity.player.Player; + import org.spongepowered.api.event.impl.AbstractEvent; + import org.spongepowered.api.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 +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: java + + game.getEventManager().post(new PrivateMessageEvent(playerA, playerB, "Hello World!")); + + +Example: Listen for Custom Event +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: java + + import org.spongepowered.api.text.Texts; + import org.spongepowered.api.text.chat.ChatTypes; + + @Listener + public void onPrivateMessage(PrivateMessageEvent event) { + if(event.getMessage().equals("hi i am from planetminecraft")) { + event.setCancelled(true); + return; + } + + String senderName = event.getSender().getName(); + event.getRecipient().sendMessage(ChatTypes.CHAT, Texts.of("PM from " + senderName + ": " + event.getMessage())); + } diff --git a/source/plugin/event/filters.rst b/source/plugin/event/filters.rst new file mode 100644 index 000000000000..402bcd03a56d --- /dev/null +++ b/source/plugin/event/filters.rst @@ -0,0 +1,186 @@ +============= +Event Filters +============= + +Now that you've spent a bit of time working with events you've probably noticed that there are several preconditions that you +very commonly check while writing an event listener. Event filters are a group of annotations that assist you by allowing you +to automatically validate aspects of the event, and if the validation fails then your listener will not be called. This allows +your listener to be dedicated to the logic of your handler, rather than the preconditions, resulting in cleaner code. + +Event filters come in two varieties, event type filters and parameter filters. + +Event type filters are method annotations that are applied to your listener method along with the ``@Listener`` annotation and +provide several filters based on the type or state of the event. + +Parameter filters validate objects contained within the event such as the cause. They come in a further two varieties +parameter sources and parameter filters. Each additional parameter must have one source annotation, and optionally may include +any number of filter annotations. + +Event Type Filters +================== + +**@Include/@Exclude** +These two parameters are used in conjunction with listening for supertype events such as ``EntityEvent`` while narrowing the +events that you receive to just a subset of the events extending the event you're listening for. + +For example: + +.. code-block:: java + + @Listener + @Exclude(InteractBlockEvent.Primary.class) + public void onInteract(InteractBlockEvent event) { + // do something + } + +This listener would normally be called for all events extending InteractBlockEvent. However, the ``Exclude`` annotation +will prevent your listener from being called for the ``InteractBlockEvent.Primary`` event (leaving just the +``InteractBlockEvent.Secondary`` event). + +An example with ``@Include`` could be: + +.. code-block:: java + + @Listener + @Include({DamageEntityEvent.class, DestructEntityEvent.class}) + public void onEvent(EntityEvent event) { + // do something + } + +This listener would normally be called for all EntityEvents, however the ``Include`` annotation narrows it to +only recieve ``DamageEntityEvent`` and ``DestructEntityEvent``\ s. + +**@IsCancelled** +This annotation allows filtering events by their cancellation state at the time that your event listener would normally be +called. By default your event listener will not be called if the event has been cancelled by a previous event listener. +However you can change this behavior to one of three states depending on the ``Tristate`` value in the ``@IsCancelled`` +annotation. + + - ``Tristate.FALSE`` is the default behavior if the ``IsCancelled`` annotation is not present, and will not call your + listener if the event has been cancelled. + - ``Tristate.UNDEFINED`` will cause your listener to be called regardless of the cancellation state of the event. + - ``Tristate.TRUE`` will cause your listener to be called only if the event has been cancelled by a previous event listener. + +Parameter Filters +================= + +Parameter filters allow you to filter based on objects within the event. These annotations come in two types, sources and +filters. Each additional parameter for your listener method, beyond the normal event parameter, requires exactly one source +annotation and may optionally have any number of filter annotations. + +Parameter Source Annotations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Parameter source annotations tell the event system where it should look for this parameter's value. This may be in the events +cause or in a member of the event object itself. + +**@First** This parameter source annotation tells the event system to find the first object in the event's cause which matches +the type of your parameter (This is equivalent to ``Cause#first(Class)``). If no object is found matching this parameter +then your listener is not called. + +**In this example your listener will only be called if there is a player in the event's cause, and the ``player`` parameter +will be set to the first player present the cause.** + +.. code-block:: java + + @Listener + public void onInteract(InteractBlockEvent.Secondary event, @First Player player) { + // do something + } + +**@Last** This is similar to ``@First`` however it instead makes a call to ``Cause#last(Class)``. + +.. code-block:: java + + @Listener + public void onInteract(InteractBlockEvent.Secondary event, @Last Player player) { + // do something + } + +**@Before** This parameter source annotation tells the event system to find the object before +the one of the type specified by the annotation parameter (This is equivalent to ``Cause#before(Class)``). +Additionally, the found object must match the type of the parameter. If no object is found meeting these criteria, +then your listener is not called. + +**In this example your listener will only be called if there is a player located before a plugin container in the event's cause. +The ``player`` parameter will be set to that player.** + +.. code-block:: java + + @Listener + public void onInteract(InteractBlockEvent.Secondary event, @Before(PluginContainer.class) Player player) { + // do something + } + +**@After** This is similar to ``@Before``, but it instead uses ``Cause#after(Class)``. + +**@All** This parameter source annotation requires that the annotated parameter be an array +type. The returned array will be equivalent to the contents of calling ``Cause#all(Class)``. By default if the +returned array would be empty then the validation fails however this can be disabled by setting ``ignoreEmpty=false``. + +**In this example your listener will always be called, although the players array may be empty if the event's cause contained +no players.** + +.. code-block:: java + + @Listener + public void onInteract(InteractBlockEvent.Secondary event, @All(ignoreEmpty=false) Player[] players) { + // do something + } + +**@Root** This parameter source annotation will fetch the root object of the cause, equivalent to ``Cause#root()``. +It also performs an additional check that the type of the root object matches the type of your parameter. + +**@Named** This parameter source annotation tells the event system to find the object with the name specified by the annotation +parameter (This is equivalent to ``Cause#get(String)``). Additionally, the found object must match the type of the parameter. If +no object is found meeting these criteria, then your listener is not called. + +**In this example your listener will only be called if there is a player associated with the name ``NamedCause.OWNER``. +The ``player`` parameter will be set to that player.** + +.. code-block:: java + + @Listener + public void onInteract(InteractBlockEvent.Secondary event, @Named(NamedCause.OWNER) Player player) { + // do something + } + +Parameter Filter Annotations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Parameter filter annotations add additional validation to objects returned from parameter source annotations. As with all +event filters if any of these validations fail then your listener will not be called. + +**@Supports** +This parameter filter may be applied to any parameter type which is a ``DataHolder``. It takes a class extending +``DataManipulator`` as its parameter and validates that the annotated DataHolder supports the given DataManipulator +ype. This validation is equivalent to ``DataHolder#supports(Class)``. + +**In this example the listener will be called only if there is an entity in the event's cause, and if that entity supports +the data manipulator ``FlyingData``.** + +.. code-block:: java + + @Listener + public void onInteract(InteractBlockEvent.Secondary event, @First @Supports(FlyingData.class) Entity entity) { + // do something + } + +**@Has** +This parameter filter is similar to the ``@Supports`` parameter filter except that it additionally validates that the +``DataHolder`` contains an instance of the given ``DataManipulator``. This validation is equivalent to +``DataHolder#hasData(Class)``. + +**In this example the listener will be called only if there is an entity in the event's cause, and if that entity has an +instance of ``FlyingData`` available.** + +.. code-block:: java + + @Listener + public void onInteract(InteractBlockEvent.Secondary event, @First @Has(FlyingData.class) Entity entity) { + // do something + } + +.. note:: + Both ``@Has`` and ``@Supports`` have an optional parameter ``inverse()`` which can be set to cause validation + to fail if the does have, or does support, the target DataManipulator. diff --git a/source/plugin/event/index.rst b/source/plugin/event/index.rst new file mode 100644 index 000000000000..894fcd70d32d --- /dev/null +++ b/source/plugin/event/index.rst @@ -0,0 +1,39 @@ +=================== +Working with Events +=================== + +Sponge provides a system to: + +- Listen for events +- Fire 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 listen to. + +Event listeners are assigned a priority that determines the order in which the event listener is run in context of other +event listeners (such as those from other plugins). For example, an event listener with ``EARLY`` priority will return +before most other event listeners. + +Events cannot be sent to a specific set of plugins. All plugins that listen to an event will be notified of the event. + +The event bus or event manager is the class that keeps track of which plugins are listening to which event, +and is also responsible for distributing events to event listeners. + + + +Contents +======== + +.. toctree:: + :maxdepth: 2 + :titlesonly: + + listeners + causes + filters + custom diff --git a/source/plugin/events.rst b/source/plugin/event/listeners.rst similarity index 57% rename from source/plugin/events.rst rename to source/plugin/event/listeners.rst index bc839f45aceb..ff922ff86ef1 100644 --- a/source/plugin/events.rst +++ b/source/plugin/event/listeners.rst @@ -1,39 +1,9 @@ -=================== -Working with Events -=================== - -Sponge provides a system to: - -- Listen for events -- Fire 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 listen to. - -Event listeners are assigned a priority that determines the order in which the event listener is run in context of other -event listeners (such as those from other plugins). For example, an event listener with ``EARLY`` priority will return -before most other event listeners. - -Events cannot be sent to a specific set of plugins. All plugins that listen to an event will be notified of the event. - -The event bus or event manager is the class that keeps track of which plugins are listening to which event, -and is also responsible for distributing events to event listeners. - -.. note:: - The event bus **supports supertypes**. For example, ``ChangeBlockEvent.Break`` extends ``ChangeBlockEvent``. - Therefore, a plugin could listen to ``ChangeBlockEvent`` and still receive ``ChangeBlockEvent.Break``\ s. However, - a plugin listening to just ``ChangeBlockEvent.Break`` would not be notified of other types of ``ChangeBlockEvent``. - +=============== Event Listeners =============== In order to listen for an event, an event listener must be registered. This is done by making a method with any name, -defining the first (and only) parameter to be the desired event type, and then affixing ``@Listener`` to the method, +defining the first parameter to be the desired event type, and then affixing ``@Listener`` to the method, as illustrated below. .. code-block:: java @@ -53,6 +23,11 @@ In addition, the class containing these methods must be registered with the even events because Sponge will do it automatically. +.. note:: + The event bus **supports supertypes**. For example, ``ChangeBlockEvent.Break`` extends ``ChangeBlockEvent``. + Therefore, a plugin could listen to ``ChangeBlockEvent`` and still receive ``ChangeBlockEvent.Break``\ s. However, + a plugin listening to just ``ChangeBlockEvent.Break`` would not be notified of other types of ``ChangeBlockEvent``. + Registering and Unregistering Event Listeners ============================================= @@ -144,8 +119,6 @@ The ``@Listener`` annotation has a few configurable fields: * ``order`` is the order in which the event listener is to be run. See the ``org.spongepowered.api.event.Order`` enum in Sponge to see the available options. -* ``ignoreCancelled``, if true (which is default true), causes the event listener to be skipped if the event in question - is cancellable and has been cancelled (by a previously-executed plugin, for example). By default, ``@Listener`` is configured so that your event listener will *not* be called if the event in question is cancellable and has been cancelled (such as by another plugin). @@ -180,88 +153,3 @@ Example: Firing LightningEvent LightningEvent lightningEvent = SpongeEventFactory.createLightningEvent(game, Cause.empty()); game.getEventManager().post(lightningEvent); - - -Creating Custom Events -====================== - -You can write your own event classes and dispatch those events using the method described above. - -An event class must either implement the ``Event`` interface or extend the ``AbstractEvent`` class. - -If you want your event to be cancellable, the class must also implement ``Cancellable``. - -Example: Custom Event Class -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: java - - import org.spongepowered.api.entity.player.Player; - import org.spongepowered.api.event.impl.AbstractEvent; - import org.spongepowered.api.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 -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: java - - game.getEventManager().post(new PrivateMessageEvent(playerA, playerB, "Hello World!")); - - -Example: Listen for Custom Event -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: java - - import org.spongepowered.api.text.Texts; - import org.spongepowered.api.text.chat.ChatTypes; - - @Listener - public void onPrivateMessage(PrivateMessageEvent event) { - if(event.getMessage().equals("hi i am from planetminecraft")) { - event.setCancelled(true); - return; - } - - String senderName = event.getSender().getName(); - event.getRecipient().sendMessage(ChatTypes.CHAT, Texts.of("PM from " + senderName + ": " + event.getMessage())); - } diff --git a/source/plugin/index.rst b/source/plugin/index.rst index 1f07a950eddf..88ed33cd6935 100644 --- a/source/plugin/index.rst +++ b/source/plugin/index.rst @@ -44,7 +44,7 @@ Contents logging text/index commands/index - events + event/index configuration/index data/index blocks/index diff --git a/source/plugin/main-class.rst b/source/plugin/main-class.rst index 021c24afe6eb..c6b9e06d7b3c 100644 --- a/source/plugin/main-class.rst +++ b/source/plugin/main-class.rst @@ -70,7 +70,7 @@ game. In the example below, ``onServerStart()`` is called when the ``GameStarted .. tip:: - The Sponge documentation provides a guide with more information on events (see :doc:`events`). Normally, in addition + The Sponge documentation provides a guide with more information on events (see :doc:`event/index`). Normally, in addition to prefixing event-handler methods with ``@Listener``, you must also register your object with Sponge's event bus. However, your main plugin class is registered automatically.