事件原因

用于为游戏添加额外的行为,事件是很不错的选择,不过一大缺点似乎就是事件本身还不能够回答事件的 原因 到底是什么。事件原因( Cause )允许提供和接收有关事件的其他上下文信息,然后用于你的事件监听器的行为。

例如,一个世界保护插件需要了解是什么玩家导致了 ChangeBlockEvent 从而可以决定这一事件到底是否需要取消,相应的信息将直接由 Cause 提供,而不是像传统的事件处理系统一样,通过创建大量的不同子事件以提供信息。

每个事件都提供一个 Cause 对象用于描述触发该事件的相关信息。你可以直接通过调用 Event#getCause() 方法获取到一个 Cause 对象。

从事件原因中检索对象

一个 Cause 对象在结构上包含一串对象的序列。有若干种可以用于获取 Cause 的信息的方法,也就是接下来我们要讨论的内容。关于更多的信息可以查看对应的 JavaDoc 链接

注解

在事件原因中的对象会按照这样的顺序排序:第一个对象是最直接的原因,第二个对象是次直接的原因,依此类推。靠后的对象可能仅仅用于提供上下文信息。

Cause#root() 返回事件的直接起因,即距离事件发生最近的或者最直接的原因。由于 Cause 不能为空,所以 Sponge 保证 root 是存在的。

Cause#first(Class) 返回第一个满足给定类型或者其子类的事件起因。比如我们假设有一个 Cause 包含了一个玩家,随后包含了一个实体,即 [Player, Entity, ...]

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

这两个 Optional 都将返回玩家对象,因为不管是玩家还是实体都满足玩家对象作为其子类。

Cause#last(Class) 方法和 Cause#first(Class) 类似,只不过它返回最后一个满足对应类型的对象。

我们继续上面的例子。如果我们转而去使用 Cause#last(Class) ,那么第一个 Optional 将仍然返回玩家对象,不过第二个返回的对象就不同了:它将返回上面我们提到的,处于第二个位置的那个实体。

Cause#containsType(Class) 返回一个布尔值表示其是否包含这样一个满足给定类型的对象。

Cause#all() 直接返回所有的对象以支持更高级的操作。

被指名的原因

有的时候对象的顺序并不足以让我们获取正确的和事件有关的我们想要的对象。这便是 NamedCause 大显身手的时机。被指名的事件原因( NamedCause )提供了一个方法用于把一些名称和 唯一 的对象联系起来以方便我们识别和获取。下面的示例是关于 ChangeBlockEvent.GrowNotifierDamageEntityEventSource 的。

从事件原因中检索被指名的对象

@Listener
public void onGrow(ChangeBlockEvent.Grow event) {
    Optional<Player> notifier = event.getCause().get(NamedCause.NOTIFIER, Player.class);
}

这一示例中使用了 Cause#get(String, Class<T>) 方法用于检索和名称相关联的对象。此外, Cause#getNamedCauses() 提供了一个 Map<String, Object> 用于找到所有的存在对应对象的名称及其对应的对象。

注解

一些常见的用于 NamedCause 的名称,都作为静态字段出现在 NamedCause 类中。有部分名称适用于特殊的事件,其对应的静态字段位于事件对应的类中,如 DamageEntityEvent#SOURCE

自定义事件原因

实际上,在触发事件时创建一个事件原因是十分简单的,困难的是决定到底把什么信息放进去。如果你的插件想要触发一个经常被以其他方式触发的事件的话,那么你可能需要在事件原因中囊括一下你的插件主类,以保证其他的插件可以得知事件的来源是你的插件。此外,如果你想要代表一个玩家触发一个事件,那么往往你还需要把玩家置入插件原因中。

注解

因为 Cause 对象是不可变的,所以一旦创建则不可修改。

通过使用 Cause#of(NamedCause) ,你可以生成一个包含一系列对象的事件原因。传入参数的对象顺序决定了它们在事件原因中的顺序因此第一个传入的参数代表的是根对象。切记 Cause 对象不能为空,所以请至少传入一个参数。

如果你已经有了一个 Cause 对象,并想要把一些更多的对象加入这个事件原因,那么你可以使用 Cause#with(NamedCause, NamedCause…) 来帮助你在原有的 Cause 对象的基础上,追加额外的对象,以生成一个新的事件原因。

最后如果你想要添加一个对象和一个名称至一个 Cause 对象(即 NamedCause ),那么你可以首先调用 NamedCause#of(String, Object) 方法,然后把返回的 NamedCause 用于添加进事件原因中。