|
| 1 | +Using Configuration Tasks |
| 2 | +==================== |
| 3 | + |
| 4 | +The networking protocol for the client and server has a specific phase where the server can configure the client before the player actually joins the game. |
| 5 | +This phase is called the configuration phase, and is for example used by the vanilla server to send the resource pack information to the client. |
| 6 | + |
| 7 | +This phase can also be used by mods to configure the client before the player joins the game. |
| 8 | + |
| 9 | +## Registering a configuration task |
| 10 | +The first step to using the configuration phase is to register a configuration task. |
| 11 | +This can be done by registering a new configuration task in the `OnGameConfigurationEvent` event. |
| 12 | +```java |
| 13 | +@SubscribeEvent |
| 14 | +public static void register(final OnGameConfigurationEvent event) { |
| 15 | + event.register(new MyConfigurationTask()); |
| 16 | +} |
| 17 | +``` |
| 18 | +The `OnGameConfigurationEvent` event is fired on the mod bus, and exposes the current listener used by the server to configure the relevant client. |
| 19 | +A modder can use the exposed listener to figure out if the client is running the mod, and if so, register a configuration task. |
| 20 | + |
| 21 | +## Implementing a configuration task |
| 22 | +A configuration task is a simple interface: `ICustomConfigurationTask`. |
| 23 | +This interface has two methods: `void run(Consumer<CustomPacketPayload> sender);`, and `ConfigurationTask.Type type();` which returns the type of the configuration task. |
| 24 | +The type is used to identify the configuration task. |
| 25 | +An example of a configuration task is shown below: |
| 26 | +```java |
| 27 | +public record MyConfigurationTask implements ICustomConfigurationTask { |
| 28 | + public static final ConfigurationTask.Type TYPE = new ConfigurationTask.Type(new ResourceLocation("mymod:my_task")); |
| 29 | + |
| 30 | + @Override |
| 31 | + public void run(final Consumer<CustomPacketPayload> sender) { |
| 32 | + final MyData payload = new MyData(); |
| 33 | + sender.accept(payload); |
| 34 | + } |
| 35 | + |
| 36 | + @Override |
| 37 | + public ConfigurationTask.Type type() { |
| 38 | + return TYPE; |
| 39 | + } |
| 40 | +} |
| 41 | +``` |
| 42 | + |
| 43 | +## Acknowledging a configuration task |
| 44 | +Your configuration is executed on the server, and the server needs to know when the next configuration task can be executed. |
| 45 | +This is done by acknowledging the execution of said configuration task. |
| 46 | + |
| 47 | +There are two primary ways of achieving this: |
| 48 | + |
| 49 | +### Capturing the listener |
| 50 | +When the client does not need to acknowledge the configuration task, then the listener can be captured, and the configuration task can be acknowledged directly on the server side. |
| 51 | +```java |
| 52 | +public record MyConfigurationTask(ServerConfigurationListener listener) implements ICustomConfigurationTask { |
| 53 | + public static final ConfigurationTask.Type TYPE = new ConfigurationTask.Type(new ResourceLocation("mymod:my_task")); |
| 54 | + |
| 55 | + @Override |
| 56 | + public void run(final Consumer<CustomPacketPayload> sender) { |
| 57 | + final MyData payload = new MyData(); |
| 58 | + sender.accept(payload); |
| 59 | + listener.finishCurrentTask(type()); |
| 60 | + } |
| 61 | + |
| 62 | + @Override |
| 63 | + public ConfigurationTask.Type type() { |
| 64 | + return TYPE; |
| 65 | + } |
| 66 | +} |
| 67 | +``` |
| 68 | +To use such a configuration task, the listener needs to be captured in the `OnGameConfigurationEvent` event. |
| 69 | +```java |
| 70 | +@SubscribeEvent |
| 71 | +public static void register(final OnGameConfigurationEvent event) { |
| 72 | + event.register(new MyConfigurationTask(event.listener())); |
| 73 | +} |
| 74 | +``` |
| 75 | +Then the next configuration task will be executed immediately after the current configuration task has completed, and the client does not need to acknowledge the configuration task. |
| 76 | +Additionally, the server will not wait for the client to properly process the send payloads. |
| 77 | + |
| 78 | +### Acknowledging the configuration task |
| 79 | +When the client needs to acknowledge the configuration task, then you will need to send your own payload to the client: |
| 80 | +```java |
| 81 | +public record AckPayload() implements CustomPacketPayload { |
| 82 | + public static final ResourceLocation ID = new ResourceLocation("mymod:ack"); |
| 83 | + |
| 84 | + @Override |
| 85 | + public void write(final FriendlyByteBuf buffer) { |
| 86 | + // No data to write |
| 87 | + } |
| 88 | + |
| 89 | + @Override |
| 90 | + public ResourceLocation id() { |
| 91 | + return ID; |
| 92 | + } |
| 93 | +} |
| 94 | +``` |
| 95 | +When a payload from a server side configuration task is properly processed you can send this payload to the server to acknowledge the configuration task. |
| 96 | +```java |
| 97 | +public void onMyData(MyData data, ConfigurationPayloadContext context) { |
| 98 | + context.submitAsync(() -> { |
| 99 | + blah(data.name()); |
| 100 | + }) |
| 101 | + .exceptionally(e -> { |
| 102 | + // Handle exception |
| 103 | + context.packetHandler().disconnect(Component.translatable("my_mod.configuration.failed", e.getMessage())); |
| 104 | + return null; |
| 105 | + }) |
| 106 | + .thenAccept(v -> { |
| 107 | + context.replyHandler().send(new AckPayload()); |
| 108 | + }); |
| 109 | +} |
| 110 | +``` |
| 111 | +Where `onMyData` is the handler for the payload that was sent by the server side configuration task. |
| 112 | + |
| 113 | +When the server receives this payload it will acknowledge the configuration task, and the next configuration task will be executed: |
| 114 | +```java |
| 115 | +public void onAck(AckPayload payload, ConfigurationPayloadContext context) { |
| 116 | + context.taskCompletedHandler().onTaskCompleted(MyConfigurationTask.TYPE); |
| 117 | +} |
| 118 | +``` |
| 119 | +Where `onAck` is the handler for the payload that was sent by the client. |
| 120 | + |
| 121 | +## Stalling the login process |
| 122 | +When the configuration is not acknowledged, then the server will wait forever, and the client will never join the game. |
| 123 | +So it is important to always acknowledge the configuration task, unless the configuration task failed, then you can disconnect the client. |
0 commit comments