Roundtrip Packets
Overview
This example demonstrates how to handle roundtrip packets between the server and the Lunar Client. These packets are sent from the server, expecting a corresponding response from the client. The example utilizes a map to track the requests and their corresponding responses. For instance, this pattern is common in modules like TransferModule where a request packet is sent and a response is awaited.
Integration
public class ApolloRoundtripProtoListener implements PluginMessageListener {
@Getter
private static ApolloRoundtripProtoListener instance;
// Player UUID -> Packet ID -> Response Packet
// Consider having a timeout for packets that expect a Response
private final Map<UUID, Map<UUID, Consumer<GeneratedMessageV3>>> roundTripPacketConsumers = new HashMap<>();
public ApolloRoundtripProtoListener() {
instance = this;
Bukkit.getServer().getMessenger().registerIncomingPluginChannel(plugin, "lunar:apollo", this);
}
@Override
public void onPluginMessageReceived(String s, Player player, byte[] bytes) {
try {
Any any = Any.parseFrom(bytes);
if (any.is(PingResponse.class)) {
PingResponse message = any.unpack(PingResponse.class);
UUID requestId = UUID.fromString(message.getRequestId().toStringUtf8());
this.handleResponse(player, requestId, message);
} else if (any.is(TransferResponse.class)) {
TransferResponse message = any.unpack(TransferResponse.class);
UUID requestId = UUID.fromString(message.getRequestId().toStringUtf8());
this.handleResponse(player, requestId, message);
}
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
}
public <T extends GeneratedMessageV3> void sendRequest(Player player, UUID requestId, GeneratedMessageV3 request,
Class<T> response, Consumer<T> action) {
ProtobufPacketUtil.sendPacket(player, request);
this.roundTripPacketConsumers.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>())
.put(requestId, (Consumer<GeneratedMessageV3>) action);
}
private <T extends GeneratedMessageV3> void handleResponse(Player player, UUID requestId, T message) {
Map<UUID, Consumer<GeneratedMessageV3>> consumers = this.roundTripPacketConsumers.get(player.getUniqueId());
if (consumers == null) {
return;
}
Consumer<GeneratedMessageV3> action = consumers.remove(requestId);
if (action != null) {
action.accept(message);
}
}
}Here's an example demonstrating how to use the code to handle a server-to-client TransferModule transfer, where the client responds with a status (accepted or rejected).
public void transferExample(Player player) {
UUID requestId = UUID.randomUUID();
TransferRequest transferRequestMessage = TransferRequest.newBuilder()
.setRequestId(ByteString.copyFromUtf8(requestId.toString()))
.setServerIp("mc.hypixel.net")
.build();
ApolloRoundtripProtoListener.getInstance().sendRequest(player, requestId, transferRequestMessage, TransferResponse.class, response -> {
String message = "";
switch (response.getStatus()) {
case STATUS_ACCEPTED: {
message = "Transfer accepted! Goodbye!";
break;
}
case STATUS_REJECTED: {
message = "Transfer rejected by client!";
break;
}
}
player.sendMessage(message);
});
}