事件过滤

现在,你已经花了一点时间来处理你的事件。同时你也已经注意到这些事件有着若干个先决条件,你需要在编写事件监听器的代码时去检查这些先决条件。一些注解——我们称其为事件过滤器(Event Filter)——可以帮助你自动验证事件的若干方面,然后在其失败时不去调用你对应的事件监听器。这允许你的事件监听器更加专注于处理逻辑,而不是在先决条件上分心,以保证更整洁的代码。

事件过滤器有两种类型:事件类型过滤器(Event Type Filter)和参数过滤器(Parameter Filter)。

事件类型过滤器是若干个用于方法(也就是你的事件监听器)的注解,这些注解和 Listener 处于同样的位置,并且提供若干个基于事件的类型的筛选操作。

参数过滤器首先检查事件包含的对象,诸如事件原因(Cause)。有两种类型的参数过滤器:参数来源过滤器(Parameter Sources)和参数筛选过滤器(Parameter Filters)。每一个多出来的参数都需要拥有至少一个参数来源过滤器,同时拥有零个、一个、或多个参数筛选过滤器。

事件类型过滤器

@Include/@Exclude 是两个用于监听父事件类型的注解,比如 AffectEntityEvent 。结合使用这两个注解,在监听事件的时候,你将只会收到这两个注解决定的事件类型。

舉例來說:

@Listener
@Exclude(InteractBlockEvent.Primary.class)
public void onInteract(InteractBlockEvent event) {
    // do something
}

这一监听器将正常地在所有 InteractBlockEvent 的子类被触发时调用。然而, Exclude 这一注解的存在会排除掉 InteractBlockEvent.Primary 这一事件,即阻止该监听器在其被触发时调用(使其仅剩下 InteractBlockEvent.Secondary 可以被监听)。

下面是关于 Include 注解的示例:

@Listener
@Include({DamageEntityEvent.class, DestructEntityEvent.class})
public void onEvent(EntityEvent event) {
    // do something
}

这一监听器将正常地在所有 EntityEvent 的子类被触发时调用。然而, Include 这一注解的存在将只会允许监听器监听 DamageEntityEventDestructEntityEvent

@IsCancelled 允许设置被取消的事件是否会触发当前对应的监听器。默认情况下是不会触发被取消的事件的,不过这一注解的意义就是自定义这件事,有下面三种 Tristate 的可选项可以被传入 IsCancelled 注解。

  • Tristate.FALSE 是默认的处理行为,亦即没有 IsCancelled 时的行为,即事件被取消时不会触发该监听器。

  • Tristate.UNDEFINED 会使得你的事件监听器无视事件是否被取消,照监听不误。

  • Tristate.TRUE 将只会在前一个事件监听器取消了该事件时触发。

参数过滤器

参数过滤器允许你检查事件中的对象,分为参数来源过滤器和参数筛选过滤器两种。所有的参数过滤器都作用在你的事件监听器的非事件本身的方法参数声明上,并要求一个参数来源过滤器和可有可无的参数筛选过滤器。

参数来源过滤器

参数来源过滤器告诉事件系统需要的参数是什么样子的。这一参数对象可能在事件原因中,也可能只是事件对象本身的一个参数。

@First 这一注解告诉事件系统提供事件原因中第一个满足指定类型的参数(和 Cause#first(Class) 的返回值相同)。如果这样的对象并不存在,那么事件系统将不会触发该事件监听器。

在这一示例中你的事件监听器将只在一个玩家包含在事件原因中时触发,然后对应的 player 参数将被设置为包含的第一个玩家对象。

@Listener
public void onInteract(InteractBlockEvent.Secondary event, @First Player player) {
    // do something
}

@Last 这一注解和 @First 类似,只不过和 Cause#last(Class) 的返回值相同。

@Listener
public void onInteract(InteractBlockEvent.Secondary event, @Last Player player) {
    // do something
}

@First 这一注解告诉事件系统提供事件原因中第一个在满足指定类型的对象之前的对象作为参数(和 Cause#before(Class) 的返回值相同)。两个对象都需要满足对应的类型。如果不满足这一条件,那么事件系统将同样不会触发该事件监听器。

在这一示例中你的事件监听器将只在一个玩家包含在事件原因,并在同样包含在事件原因的插件主类之前时触发,然后对应的 player 参数将被设置为该玩家对象。

@Listener
public void onInteract(InteractBlockEvent.Secondary event, @Before(PluginContainer.class) Player player) {
    // do something
}

@After 这一注解和 @Before 类似,只不过对应的方法为 Cause#after(Class)

@All 指定的参数类型需要为数组。返回的数组将和 Cause#allOf(Class) 的返回值一致。默认情况下在没有找到满足条件的数组时将返回一个空数组,不过可以通过设置 ignoreEmpty=false 来阻止这一行为。

在这一示例中你的事件监听器将总是会被触发,尽管在找不到事件原因中包含的玩家对象时会返回一个空数组。

@Listener
public void onInteract(InteractBlockEvent.Secondary event, @All(ignoreEmpty=false) Player[] players) {
    // do something
}

@Root 注解会获取事件原因中的根对象,也就是和 Cause#root() 的返回值相同。这一注解同样会对根对象的类型进行检查,以匹配声明的参数类型。

@Named 这一注解告诉事件系统提供事件原因中满足指定名称的对象作为参数(和 Cause#get(String, Class) 的返回值相同)。返回的对象同样需要满足声明的参数类型。如果没有找到满足条件的对象,那么事件系统仍将不会触发该事件监听器。

在这一示例中你的事件监听器将只在一个玩家包含在事件原因,并被指定名为 NamedCause.OWNER 时触发,然后对应的 player 参数将被设置为该玩家对象。

@Listener
public void onInteract(InteractBlockEvent.Secondary event, @Named(NamedCause.OWNER) Player player) {
    // do something
}

@Getter 这一注解首先获取事件的一个对应指定名称的 Getter 并设置其返回值为参数值。如果该 Getter 返回一个 Optional 对象,那么这一注解将自动打开这一层 Optional

在此示例中,我们试图通过 @Getter 注解去获取 getUseItemResult 方法的返回值。

@Listener
public void onInteract(InteractBlockEvent.Secondary event, @Getter("getUseItemResult") Tristate result) {
    // do something
}

参数筛选过滤器

参数筛选过滤器用于为作为方法参数的对象添加额外的限制。如果这些对象不满足任何一个限制,那么将不会触发相应的事件监听器。

@Supports 注解用于检查一个 DataHolder 是否支持特定的 DataManipulator 数据类型, DataManipulator 对应的 Class 会被传入注解的参数用于检查。这一操作等价于对 CompositeValueStore#supports(Class<? extends H>) 的返回值的检查。

在这一示例中你的事件监听器将只在一个实体包含在事件原因,并支持 FlyingData 这一 DataManipulator 时触发。

@Listener
public void onInteract(InteractBlockEvent.Secondary event, @First @Supports(FlyingData.class) Entity entity) {
    // do something
}

@Has 这一注解和 @Supports 类似,只不过满足条件是给定的 DataHolder 必须包含有给定类型的 DataManipulator

在这一示例中你的事件监听器将只在一个实体包含在事件原因,并包含有 FlyingData 的一个实例时触发。

@Listener
public void onInteract(InteractBlockEvent.Secondary event, @First @Has(FlyingData.class) Entity entity) {
    // do something
}

備註

@Has@Supports 两个注解都有一个可选的 inverse 参数,默认被设置为假,如果被设置为真,那么会在其 存在、 拥有指定类型的 DataManipulator 时触发事件监听器,反之不触发。