使用配置任务(Configuration Tasks)
客户端与服务端的网络协议中有一个专门的阶段,允许服务端在玩家真正进入游戏前对客户端进行配置。这个阶段被称为配置阶段(configuration phase),例如原版服务端会在此阶段向客户端发送资源包信息。
这个阶段同样可以被模组(mod)用于在玩家进入游戏前对客户端进行自定义配置。
注册配置任务(Registering a configuration task)
使用配置阶段的第一步是注册一个配置任务(configuration task)。你可以在 RegisterConfigurationTasksEvent 事件中注册新的配置任务。
@SubscribeEvent
public static void register(final RegisterConfigurationTasksEvent event) {
event.register(new MyConfigurationTask());
}
RegisterConfigurationTasksEvent 事件会在 mod bus 上被触发,并且会暴露当前服务端用于配置相关客户端的监听器(listener)。模组开发者可以利用这个暴露的监听器来判断客户端是否运行了该模组,如果是,则注册一个配置任务。
实现配置任务(Implementing a configuration task)
配置任务(configuration task)是一个简单的接口:ICustomConfigurationTask。该接口包含两个方法:void run(Consumer<CustomPacketPayload> sender);,以及 ConfigurationTask.Type type();,后者用于返回该配置任务的类型。类型用于标识不同的配置任务。下面是一个配置任务的示例:
public record MyConfigurationTask implements ICustomConfigurationTask {
public static final ConfigurationTask.Type TYPE = new ConfigurationTask.Type(ResourceLocation.fromNamespaceAndPath("mymod", "my_task"));
@Override
public void run(final Consumer<CustomPacketPayload> sender) {
final MyData payload = new MyData();
sender.accept(payload);
}
@Override
public ConfigurationTask.Type type() {
return TYPE;
}
}
确认配置任务(Acknowledging a configuration task)
你的配置任务会在服务端执行,并且服务端需要知道何时可以执行下一个配置任务。为此,需要对该配置任务的执行进行确认(acknowledgement)。
主要有两种实现方式:
捕获监听器(Capturing the listener)
当客户端不需 要对配置任务进行确认时,可以直接捕获监听器(listener),并在服务端直接确认该配置任务。
public record MyConfigurationTask(ServerConfigurationPacketListener listener) implements ICustomConfigurationTask {
public static final ConfigurationTask.Type TYPE = new ConfigurationTask.Type(ResourceLocation.fromNamespaceAndPath("mymod", "my_task"));
@Override
public void run(final Consumer<CustomPacketPayload> sender) {
final MyData payload = new MyData();
sender.accept(payload);
this.listener().finishCurrentTask(this.type());
}
@Override
public ConfigurationTask.Type type() {
return TYPE;
}
}
要使用这样的配置任务(configuration task),需要在 RegisterConfigurationTasksEvent 事件中获取并传递监听器(listener)。
@SubscribeEvent
public static void register(final RegisterConfigurationTasksEvent event) {
event.register(new MyConfigurationTask(event.getListener()));
}
这样,在当前配置任务完成后,下一个配置任务会立即执行,客户端无需对该配置任务进行确认(acknowledge)。此外,服务端不会等待客户端正确处理已发送的 payload。
确认配置任务(Acknowledging the configuration task)
当客户端需要对配置任务进行确认时,你需要自行向客户端发送 payload:
public record AckPayload() implements CustomPacketPayload {
public static final CustomPacketPayload.Type<AckPayload> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath("mymod", "ack"));
// 无需写入数据的单元编解码器
public static final StreamCodec<ByteBuf, AckPayload> STREAM_CODEC = StreamCodec.unit(new AckPayload());
@Override
public CustomPacketPayload.Type<? extends CustomPacketPayload> type() {
return TYPE;
}
}
当服 务端的配置任务(configuration task)发送的 payload 被客户端正确处理后,你可以将此 payload 发送回服务端,以确认配置任务。
public void onMyData(MyData data, IPayloadContext context) {
context.enqueueWork(() -> {
blah(data.name());
})
.exceptionally(e -> {
// 处理异常
context.disconnect(Component.translatable("my_mod.configuration.failed", e.getMessage()));
return null;
})
.thenAccept(v -> {
context.reply(new AckPayload());
});
}
这里 onMyData 是处理服务端配置任务发送过来的 payload 的 handler。
当服务端收到此 payload 时,会确认该配置任务,并立即执行下一个配置任务:
public void onAck(AckPayload payload, IPayloadContext context) {
context.finishCurrentTask(MyConfigurationTask.TYPE);
}
这里 onAck 是处理客户端发送的 payload 的 handler。
阻塞登录流程(Stalling the login process)
当配置未被确认(acknowledged)时,服务器将会一直等待,客户端也无法进入游戏。因此,除非配置任务失败并且你需要断开客户端,否则务必要始终确认配置任务。