事件原因
事件系统适合为游戏中的每个动作插入附加的逻辑,但它们天生缺少提供”触发这个事件的起因“的能力。通过使用 Cause 对象,我们可以获取到一些上下文信息,这些上下文信息有助于决定你的事件监听器改变的行为。
例如,一个世界保护插件需要了解是什么玩家导致了 ChangeBlockEvent 从而可以决定这一事件到底是否需要取消,相应的信息将直接由 Cause
提供,而不是像传统的事件处理系统一样,通过创建大量的不同子事件以提供信息。
每个事件都提供一个 Cause
对象用于描述触发该事件的相关信息。你可以直接通过调用 Event#getCause() 方法获取到一个 Cause
对象。
从事件原因中检索对象
从它结构上来说,一个 Cause
对象包含了一有序列表,其内有若干对象。这里将介绍几种从这个对象中获取具体信息的方法。参阅 Javadocs 以获得完整列表。
注解
在事件原因中的对象会按照这样的顺序排序:第一个对象是最直接的原因,第二个对象是次直接的原因,依此类推。靠后的对象可能仅仅用于提供上下文信息。
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.Grow 的 Notifier 和 DamageEntityEvent 的 Source 的。
从事件原因中检索被指名的对象
@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
用于添加进事件原因中。