使用事件
使用 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>)。 基于延续的任务是使用 BungeeCordAsyncEvent意图的监听器的最接近等价物,但有一个稍微不同的编程模型, 即每个监听器仍然按顺序运行,只是单个监听器可以推迟将控制权传递给下一个监听器,直到它完成。
为了保持与旧版本 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() 检查结果。