跳到主要内容

使用事件

使用 Velocity 的 @Subscribe 注解监听事件很简单。你已经在主类中看到了一个这样的监听器,使用了 ProxyInitializeEvent。 更多事件可以在 Javadoc 中找到。

创建监听器方法

要监听事件,如下所示用 @Subscribe 标记方法。 这与你可能熟悉的其他 API 中的注解驱动事件监听类似;它相当于 Bukkit/Bungee 的 @EventHandler 和 Sponge 的 @Listener

@Subscribe
public void onPlayerChat(PlayerChatEvent event) {
// 在这里处理
}
提示

注意导入的是 com.velocitypowered.api.event.Subscribe 而_不是_ com.google.common.eventbus 中的。

顺序

每个监听器都有一个 priority。 当事件被触发时,监听器的调用顺序由它们的 priority 定义。 优先级越高,事件处理器被调用的时间越早。

@Subscribe 注解中声明所需的顺序:

@Subscribe(priority = 0, order = PostOrder.CUSTOM)
public void onPlayerChat(PlayerChatEvent event) {
// 在这里处理
}

如果你不指定顺序,-32768 是默认值。

备注

由于兼容性限制,你必须指定 PostOrder.CUSTOM 才能使用这个字段。

注册监听器

Velocity 自动将你的主插件类注册为事件监听器。这对于初始化和简单的插件来说很方便,但对于更复杂的插件, 你可能想要将事件处理器与主插件类分开。要这样做,你需要使用 EventManager 注册你的其他监听器:

事件系统支持将对象注册为监听器(允许你使用 @Subscribe 标记事件处理器)或注册函数式监听器。

将对象注册为监听器

server.getEventManager().register(plugin, listener);

两个参数都是 Object。 第一个参数是你的插件对象,第二个参数应该是要注册的监听器。例如:

@Plugin(id = "myfirstplugin", name = "My Plugin", version = "0.1.0", dependencies = {@Dependency(id = "wonderplugin")})
public class VelocityTest {

private final ProxyServer server;
private final Logger logger;

@Inject
public VelocityTest(ProxyServer server, Logger logger) {
this.server = server;
this.logger = logger;
}

@Subscribe
public void onInitialize(ProxyInitializeEvent event) {
server.getEventManager().register(this, new MyListener());
}
}

public class MyListener {

@Subscribe(order = PostOrder.EARLY)
public void onPlayerChat(PlayerChatEvent event) {
// 在这里处理
}

}

注册函数式监听器

作为 @Subscribe 的替代方案,你也可以使用函数式 EventHandler 接口,并通过 register(Object plugin, Class<E> eventClass, EventHandler<E> handler) 注册:

  server.getEventManager().register(this, PlayerChatEvent.class, event -> {
// 在这里处理
});

异步处理事件

在 Velocity 3.0.0 中,事件现在可以异步处理。事件系统允许插件暂停向每个监听器发送事件, 异步执行一些计算或 I/O,然后恢复处理事件。所有 Velocity 事件都有异步处理的能力, 但只有一些会在继续之前明确等待事件完成触发。

对于基于注解的监听器,要异步处理事件,只需要返回一个 EventTask 或添加一个第二个返回 Continuation 参数:

  @Subscribe(priority = 100, order = PostOrder.CUSTOM)
public void onLogin(LoginEvent event, Continuation continuation) {
doSomeAsyncProcessing().addListener(continuation::resume, continuation::resumeWithException);
}

@Subscribe(priority = 100, order = PostOrder.CUSTOM)
public EventTask onPlayerChat(PlayerChatEvent event) {
if (mustFurtherProcess(event)) {
return EventTask.async(() => ...);
}
return null;
}

函数式监听器只需要实现 AwaitingEventExecutor 并返回一个 EventTask

  server.getEventManager().register(this, PlayerChatEvent.class, (AwaitingEventExecutor) event -> {
if (mustFurtherProcess(event)) {
return EventTask.async(() => ...);
}
return null;
});

有两种类型的事件任务:

  • 异步任务只是异步运行一个执行单元。要获取基本事件任务,使用 EventTask.async(Runnable)。 基本事件任务是 Velocity 1.x.x 事件监听器和 Bukkit API 中异步事件的最接近等价物。
  • 延续任务为监听器提供一个回调(称为 Continuation),以在(可能异步的)工作完成时恢复事件处理。要获取基于延续的 事件任务,使用 EventTask.withContinuation(Consumer<Continuation>)。 基于延续的任务是使用 BungeeCord AsyncEvent 意图的监听器的最接近等价物,但有一个稍微不同的编程模型, 即每个监听器仍然按顺序运行,只是单个监听器可以推迟将控制权传递给下一个监听器,直到它完成。
警告

为了保持与旧版本 Velocity 的兼容性,Velocity 3.0.0 异步运行所有事件监听器。这种行为将在 Polymer 中改变, 并且如果你需要异步执行某些工作,将需要你明确提供一个事件任务(或使用延续)。敦促所有开发者现在就进行过渡。

创建事件

在 Velocity 上创建事件与其他平台有些不同。但是,大部分内容都非常相似。

创建事件类

首先我们需要为我们的事件创建一个类。在本教程中,我们假设你正在制作一个私聊插件, 因此使用 PrivateMessageEvent。这部分大多是样板代码。

public class PrivateMessageEvent {

private final Player sender;
private final Player recipient;
private final String message;

public PrivateMessageEvent(Player sender, Player recipient, String message) {
this.sender = sender;
this.recipient = recipient;
this.message = message;
}

public Player sender() {
return sender;
}

public Player recipient() {
return recipient;
}

public String message() {
return message;
}

// toString, equals, 和 hashCode 可以根据需要添加

}

你会注意到你的事件不需要扩展或实现任何东西。它们就是能工作。

触发事件

要触发事件,你需要获取服务器的事件管理器并使用 fire 方法。注意这返回一个 CompletableFuture, 所以如果你想在所有监听器处理完事件后继续逻辑,使用回调:

server.getEventManager().fire(new PrivateMessageEvent(sender, recipient, message)).thenAccept((event) -> {
// 事件已完成触发
// 执行一些依赖于结果的逻辑
});

使用 ResultedEvent

Velocity 使用泛型化的 ResultedEvent 用于有某种"结果"的事件。事件的结果类型由其泛型类型定义;例如 PrivateMessageEvent implements ResultedEvent<ResultType>

一些常见的结果类型是 GenericResult, 用于简单的允许/拒绝结果,以及组件结果,用于结果可能被拒绝并附带原因的事件(如登录事件)。

使用通用结果比你在其他平台上可能习惯的 isCancelled/setCancelled 方法更全面, 后者的含义模糊且仅限于简单的布尔值。在这个例子中,我们将使用 GenericResult, 这样监听器就可以将我们的 PrivateMessageEvent 标记为允许或拒绝。

public class PrivateMessageEvent implements ResultedEvent<GenericResult> {

private final Player sender;
private final Player recipient;
private final String message;

private GenericResult result = GenericResult.allowed(); // 默认允许

public PrivateMessageEvent(Player sender, Player recipient, String message) {
this.sender = sender;
this.recipient = recipient;
this.message = message;
}

public Player sender() {
return sender;
}

public Player recipient() {
return recipient;
}

public String message() {
return message;
}

@Override
public GenericResult result() {
return result;
}

@Override
public void setResult(GenericResult result) {
this.result = Objects.requireNonNull(result);
}

}

按照惯例,ResultedEvent 的结果永远不应该为 null。在这里,我们使用 Objects#requireNonNull(Object) 来确保这一点。

监听器可以通过使用 event.setResult(GenericResult.denied()) 来"拒绝"事件,你可以通过 event.getResult() 检查结果。