使用配置任务
客户端和服务器的网络协议有一个专门阶段,允许服务器在玩家真正进入游戏前配置客户端。这个阶段称为配置阶段,例如原版服务器会在此阶段向客户端发送资源包信息。
Mod 也可以利用该阶段,在玩家进入游戏前配置客户端。
注册配置任务
使用配置阶段的第一步是注册配置任务。可以在 RegisterConfigurationTasksEvent 事件中注册新的配置任务。
@SubscribeEvent // on the mod event bus
public static void register(final RegisterConfigurationTasksEvent event) {
event.register(new MyConfigurationTask());
}
RegisterConfigurationTasksEvent 在 Mod 事件总线上触发,并提供服务器当前用于配置相应客户端的监听器。Mod 开发者可以用该监听器判断客户端是否安装了自己的 Mod,再决定是否注册配置任务。
实现配置任务
配置任务使用简单的 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;
}
}
确认配置任务
配置任务在服务器上执行,服务器需要知道何时可以执行下一个任务。因此,必须确认当前任务已执行。
主要有以下两种方式。
捕获监听器
如果不需要客户端确认,可以捕获监听器,直接在服务器端确认配置任务。
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;
}
}
要使用此类任务,需要在 RegisterConfigurationTasksEvent 中捕获监听器。
@SubscribeEvent // on the mod event bus
public static void register(final RegisterConfigurationTasksEvent event) {
event.register(new MyConfigurationTask(event.getListener()));
}
这样,当前配置任务完成后会立即执行下一个任务,客户端无需确认。服务器也不会等待客户端正确处理已发送的载荷。
由客户端确认配置任务
如果需要客户端确认配置任务,就要定义并向客户端发送自己的载荷:
public record AckPayload() implements CustomPacketPayload {
public static final CustomPacketPayload.Type<AckPayload> TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath("mymod", "ack"));
// Unit codec with no data to write
public static final StreamCodec<ByteBuf, AckPayload> STREAM_CODEC = StreamCodec.unit(new AckPayload());
@Override
public CustomPacketPayload.Type<? extends CustomPacketPayload> type() {
return TYPE;
}
}
客户端正确处理来自服务器配置任务的载荷后,可以向服务器发送该确认载荷。
public void onMyData(MyData data, IPayloadContext context) {
context.enqueueWork(() -> {
blah(data.name());
})
.exceptionally(e -> {
// Handle exception
context.disconnect(Component.translatable("my_mod.configuration.failed", e.getMessage()));
return null;
})
.thenAccept(v -> {
context.reply(new AckPayload());
});
}
其中 onMyData 是服务器配置任务所发送载荷的处理器。
服务器收到确认载荷后,会确认当前配置任务,并执行下一个任务:
public void onAck(AckPayload payload, IPayloadContext context) {
context.finishCurrentTask(MyConfigurationTask.TYPE);
}
其中 onAck 是客户端所发送载荷的处理器。
登录流程停滞
如果配置任务一直没有得到确认,服务器就会永远等待,客户端也无法进入游戏。因此,必须始终确认配置任务;如果任务失败,则可以断开客户端连接。
来源: NeoForged/Documentation(1.21.1)。中文翻译依据提交
4a680cdd4d15。