Causes des Events

Les événements sont utiles pour attacher une logique supplémentaire aux actions du jeu, mais ils ont l’inconvénient de ne fournir quasiment aucun contexte quant à ce qui a provoqué cet événement. L’objet Cause permet de fournir et de recevoir des informations contextuelles à propos de l’événement. Ces informations peuvent être utilisées pour modifier le comportement de votre event listener.

Par exemple, un plugin de protection de monde a besoin d’informations sur quel joueur a causé l’appel du ChangeBlockEvent avant de pouvoir décider si l’événement doit être annulé ou pas. Plutôt que de passer par la voie traditionnelle avec la création d’une multitude de sous-événements pour les différentes conditions de source, cette information est fournie dans la Cause de l’événement.

Every event provides a Cause object which can be interrogated for the information pertaining to why the event was fired. The Cause object can be retrieved from an event by simply calling Event#cause().

Cause et Contexte

L’objet Cause contient deux ensembles distincts d’informations, la pile de causes et l” EventContext.

  • La pile de causes de l’event est la cause directe capturée par importance. Il n’y a pas de noms attachés aux objets constituant cette pile.

  • Le contexte de l’event contient des informations supplémentaires sur cet event. Les contextes sont attachés à des clés, et n’ont pas d’ordre, ils ont tous la même importance.

As an example, if a sheep owned by a player eats some grass, the most direct cause of this is the sheep. The player would be in the EventContext as the EventContextKeys#PLAYER, giving event consumers that additional information about how the event has come about, but would not necessarily be within the direct cause itself.

Un autre exemple serait de vouloir savoir si vous simulez un joueur.Le joueur simulé ne sera peut être pas la cause directe puisqu’il ne sera pas impliqué dans l’action, mais il sera tout de même dans l” EventContext sous EventContextKeys#PLAYER_SIMULATED

Récupérer des objets de la cause directe

Structurellement, un objet Cause contient une liste séquentielle d’objets. Il y a de nombreuses méthodes de récupération des informations d’un objet Cause dont nous discuterons ici, pour obtenir une liste plus complète veuillez consulter les Javadocs.

Note

Les objets à l’intérieur d’une cause sont classés tels que le premier objet est la cause la plus immédiate de l’événement, et les objets suivants ont une importance décroissante et/ou peuvent seulement fournir des informations contextuelles.

Cause#root() retourne le premier objet à l’intérieur d’une cause. Cet objet est la cause la plus immédiate ou directe de l’événement. Puisqu’une Cause ne peut pas être vide, elle possède forcément une racine (root).

Cause#first(Class) retourne le premier objet dans la chaîne de la cause dont le type est soit identique, soit un sous-type de la classe donnée. Par exemple, pour une cause qui contient un joueur suivi par une entité : [Player, Entity, ...]

@Listener
public void onEvent(ExampleCauseEvent event) {
    Cause cause = event.cause(); // [Player, Entity]
    Optional<Player> firstPlayer = cause.first(Player.class); // 1
    Optional<Entity> firstEntity = cause.first(Entity.class); // 2
}

Les deux Optionals contiendraient l’objet joueur puisque son type correspond directement à la demande pour un objet de type Player, et il correspond aussi à la demande pour un objet de type Entity puisque Player est un sous-type d’Entity.

Cause#last(Class) est semblable à Cause#first(Class), sauf qu’il retourne la dernière valeur dans la chaîne de cause correspondant au type.

Pour continuer dans l’exemple ci-dessus, si au lieu de ça nous l’avions changé pour appeler Cause#last(Class), le premier Optional contiendrait encore l’objet Player, mais le second Optional contiendrait désormais l’entité que nous avons passé en seconde position de la cause.

Cause#containsType(Class) retourne un booléen et peut être utilisé pour vérifier si une chaîne de cause contient un objet correspondant au type fournit.

Cause#all() retourne simplement tous les objets à l’intérieur de la cause pour permettre une gestion plus avancée.

Contexte de l’event

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 EventContext comes in. The event context allows objects to be associated with unique names, in the form of EventContextKeys, allowing them to be easily identified and requested. Some examples of use cases for named causes is the Notifier of a ChangeBlockEvent.All or the Source of a DamageEntityEvent.

Contrairement à la pile de causes, qui ne garantit pas le type de l’objet contenu, la valeur d’un EventContextKey sera toujours du type spécifié par la clé.

Récupérer une entrée du contexte de la cause

@Listener
public void onGrow(ChangeBlockEvent.All event) {
    Optional<UUID> notifier = event.cause().context().get(EventContextKeys.NOTIFIER);
}

Cet exemple utilise EventContext#get(EventContextKey) qui peut être utilisé pour récupérer l’objet associé à un nom s’il est présent dans le contexte. De plus, EventContext#asMap() fournit une Map<EventContextKey<?>, Object> qui peut être utilisée pour obtenir tous les EventContextKeys et leurs objets.

Note

Les EventContextKey communs sont présents dans des fields statiques dans la classe EventContextKeys.

Créer une cause personnalisée

Créer une cause est simple, mais dépend de si sa création se fait sur le thread principal ou en asynchrone.

Note

Les objets Cause sont immuables donc il ne peuvent pas être modifiés une fois créés.

Utiliser le CauseStackManager

Avertissement

Le CauseStackManager ne fonctionne que sur le thread principal du serveur, si ce n’est pas le cas, un IllegalStateException sera lancé. Assurez-vous d’être sur le thread principal avant d’appeler les méthodes du CauseStackManager.

If you are creating your event on the main thread, then use the CauseStackManager from the appropriate Engine, which can be found at Engine#causeStackManager(). The CauseStackManager tracks the potential causes of events as the game runs, allowing for easy retrieval of the current Cause without effort. To see the current cause, call CauseStackManager#currentCause(). You may notice that your plugin’s PluginContainer is already in the returned Cause, as plugins are one of the objects tracked by the manager. Using the CauseStackManager for creating causes removes the need for boilerplate-like code where you supply objects like your plugin container, so that you can concentrate on adding your own causes.

Avant d’ajouter vos propres causes, vous devriez pousser une cause stack frame dans le manager. Ajouter une frame agot comme un état sauvegardé, quand vous en avez fini avec vos causes, la suppression de la frame rend au manager sont contenu original.

Astuce

Adding a frame to the CauseStackManager does not remove what is already in the manager, so anything that is in the cause stack and contexts before a stack frame is added will be there afterwards. You can verify this by calling Sponge.server().causeStackManager().currentCause() before and after the frame is pushed.

For example, if the cause stack contains a PluginContainer and a ServerPlayer when a frame is pushed, they will remain on the stack and will form part of the Cause if one is obtained from the frame.

Par exemple, si vous lancez un event qui simule un joueur dans une commande comme sudo, vous voudriez vouloir ajouter le joueur agissant comme cause et le GameProfile de ce joueur qua vous simulez dans le contexte (comme le joueur simulé n’est pas la cause responsable de l’event).

Créer une Cause personnalisée avec le CauseStackManager

Dans cet exemple, les variables seront remplies, la cause contiendrait le playerToSimulate comme racine de la cause, sourceRunningSudo sera le second objet dans la cause et le GameProfile dans EventContextKeys#PLAYER_SIMULATED à l’intérieur du contexte, en plus de tout ce que le CauseStackManager contient déjà. Votre code d’événement sera en bas de la méthode.

CommandSource sourceRunningSudo = ...;
Player playerToSimulate = ...;
try (CauseStackManager.StackFrame frame = Sponge.server().causeStackManager().pushCauseFrame()) {

  frame.pushCause(sourceRunningSudo);
  frame.pushCause(playerToSimulate);

  frame.addContext(EventContextKeys.PLAYER_SIMULATED, playerToSimulate.getProfile());

  Cause cause = frame.currentCause();
}

Notez que le dernier objet poussé dans la pile de causes sera la racine de la Cause comme les stacks suivent la structure « last in, first out » (LIFO).

Astuce

Pour plus d’informations à propos des types de données des piles et du pourquoi l’ordre est important, voir les javadocs de Stack ou cet article Wikipedia.

Utiliser le builder de Cause

Si vous créez un event qui n’est pas lancé depuis le thread principal, vous ne pouvez pas utiliser le CauseStackManager, à la place, vous devrez créer une Cause manuellement.

Créer une cause est simple en utilisant le Cause.Builder, vous pouvez obtenir un builder en appelant Cause.builder(). Pour ajouter une cause au builder, utilisez Cause.Builder#append(Object), mais notez que contrairement au CauseStackManager, le premier élément que vous ajoutez sera la racine, par le dernier.

Si vous voulez ajouter des éléments au contexte, il y a un builder séparé, le EventContext.Builder, disponible via EventContext#builder(). L” EventContext peut êyte ajouté en utilisant Cause.Builder#build(EventContext) qyand vous avez finit de configurer votre Cause.

Pour remprendre l’exemple précédent, voilà comment nous construirons la cause en utilisant son builder.

Créer une Cause personnalisé avec les builders de Cause et l’EventContext

Notez que dans cet exemple, les variables sont assignées, et que la première entrée mise dans la cause sera la racine.

CommandSource sourceRunningSudo = ...;
Player playerToSimulate = ...;
PluginContainer plugin = ...;

EventContext context = EventContext.builder()
  .add(EventContextKeys.PLAYER_SIMULATED, playerToSimulate.profile())
  .add(EventContextKeys.PLUGIN, plugin)
  .build();

Cause cause = Cause.builder()
  .append(playerToSimulate)
  .append(sourceRunningSudo)
  .append(plugin)
  .build(context);

Pensez bien à quelles informations inclure dans la cause, si vous lancez un event depuis votre plugin, qui est généralement lancé depuis d’autres moyens, c’est une bonne idée de mettre votre PluginContainer dans la cause, pour que les autres plugins sachent que l’event vient de votre plugin. Si vous lancez l’event après l’action d’un joueur, c’est généralement une bonne idée d’inclure ce joueur dans la cause.