使用配置任务

使用配置任务

客户端与服务器的网络协议中,有一个专门阶段,允许服务器在玩家真正加入游戏前配置客户端。该阶段称为配置阶段,例如原版服务器会在此阶段向客户端发送资源包信息。

Mod 也可以利用这个阶段,在玩家加入游戏前配置客户端。

注册配置任务

使用配置阶段的第一步是注册配置任务。这可以通过在 RegisterConfigurationTasksEvent 事件中注册新的配置任务来完成。

@SubscribeEvent // on the mod event bus
public static void register(final RegisterConfigurationTasksEvent event) {
    event.register(new MyConfigurationTask());
}

RegisterConfigurationTasksEvent 在 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(Identifier.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(Identifier.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<>(Identifier.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(26.1)。中文翻译依据提交 4a680cdd4d15。