Skip to content

Commit 3707207

Browse files
Lightweight(JSON): Add serverbound & roundtrip packets examples (#264)
* lightweight(json): add serverbound & roundtrip packets examples * change wording * change wording --------- Co-authored-by: Trentin <25537885+TrentinTheKid@users.noreply.github.com>
1 parent 283768c commit 3707207

19 files changed

Lines changed: 807 additions & 40 deletions

docs/developers/lightweight/json/_meta.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
{
22
"getting-started": "Getting Started",
33
"player-detection": "Player Detection",
4+
"serverbound-packets": "Serverbound Packets",
5+
"roundtrip-packets": "Roundtrip Packets",
46
"packet-util": "Packet Util",
57
"object-util": "Object Util",
68
"adventure-util": "Adventure Util"

docs/developers/lightweight/json/getting-started.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,7 @@ While constructing messages, note that the `@type` field isn't required if the m
1818
🔗 [Detecting players using LunarClient](/apollo/developers/lightweight/json/player-detection)<br/>
1919
🔗 [Common Apollo Objects](/apollo/developers/lightweight/json/object-util)<br/>
2020
🔗 [Adventure Util](/apollo/developers/lightweight/json/adventure-util)<br/>
21+
🔗 [Apollo Serverbound packets](/apollo/developers/lightweight/json/serverbound-packets)<br/>
22+
🔗 [Apollo Roundtrip packets](/apollo/developers/lightweight/json/roundtrip-packets)<br/>
2123

2224
For examples of module integration, refer to the specific Module pages. Each page contains code samples under the `Manual JSON Object Construction` tab. For instance, you can find sample code for the `BorderModule` on its dedicated page [BorderModule](/apollo/developers/modules/border#sample-code) in the `Sample Code` section.

docs/developers/lightweight/json/packet-util.mdx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ The methods in this utility leverage the same plugin messaging channel as the Ap
1313
To utilize Apollo Modules, first define a list of the modules you want to use:
1414

1515
```java
16-
private static final List<String> APOLLO_MODULES = Arrays.asList("limb", "beam", "border", "chat", "colored_fire", "combat", "cooldown",
17-
"entity", "glow", "hologram", "mod_setting", "nametag", "nick_hider", "notification", "packet_enrichment", "rich_presence",
18-
"server_rule", "staff_mod", "stopwatch", "team", "title", "tnt_countdown", "transfer", "vignette", "waypoint"
16+
private static final List<String> APOLLO_MODULES = Arrays.asList("auto_text_hotkey", "beam", "border", "chat", "colored_fire", "combat", "cooldown",
17+
"entity", "glint", "glow", "hologram", "inventory", "limb", "mod_setting", "nametag", "nick_hider", "notification", "pay_now", "packet_enrichment",
18+
"rich_presence", "saturation", "server_rule", "staff_mod", "stopwatch", "team", "tebex", "title", "tnt_countdown", "transfer", "vignette", "waypoint"
1919
);
2020
```
2121

@@ -31,9 +31,14 @@ Next, specify the properties for these modules. These properties are included in
3131
private static final Table<String, String, Object> CONFIG_MODULE_PROPERTIES = HashBasedTable.create();
3232

3333
static {
34-
// Module Options that the client needs to notified about, these properties are sent with the enable module packet
34+
// Module Options the client needs to be notified about. These properties are sent with the enable module packet.
3535
// While using the Apollo plugin this would be equivalent to modifying the config.yml
3636
CONFIG_MODULE_PROPERTIES.put("combat", "disable-miss-penalty", false);
37+
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-attack.send-packet", false);
38+
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-chat-open.send-packet", false);
39+
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-chat-close.send-packet", false);
40+
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-use-item.send-packet", false);
41+
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-use-item-bucket.send-packet", false);
3742
CONFIG_MODULE_PROPERTIES.put("server_rule", "competitive-game", false);
3843
CONFIG_MODULE_PROPERTIES.put("server_rule", "competitive-commands", Arrays.asList("/server", "/servers", "/hub"));
3944
CONFIG_MODULE_PROPERTIES.put("server_rule", "disable-shaders", false);
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { Callout } from 'nextra-theme-docs'
2+
3+
# Roundtrip Packets
4+
5+
## Overview
6+
7+
This example demonstrates how to handle roundtrip packets between the server and the Lunar Client using JSON messages. 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.
8+
9+
<Callout type="info">
10+
Note that this method uses a different plugin channel for
11+
sending and receiving packets, which is `apollo:json`.
12+
</Callout>
13+
14+
## Integration
15+
16+
```java
17+
public class ApolloRoundtripJsonListener implements PluginMessageListener {
18+
19+
private static final String TYPE_PREFIX = "type.googleapis.com/";
20+
private static final JsonParser JSON_PARSER = new JsonParser();
21+
22+
@Getter
23+
private static ApolloRoundtripJsonListener instance;
24+
25+
private final Map<UUID, Map<UUID, CompletableFuture<JsonObject>>> roundTripPacketFutures = new ConcurrentHashMap<>();
26+
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
27+
28+
public ApolloRoundtripJsonListener(ApolloExamplePlugin plugin) {
29+
instance = this;
30+
Bukkit.getServer().getMessenger().registerIncomingPluginChannel(plugin, "apollo:json", this);
31+
}
32+
33+
@Override
34+
public void onPluginMessageReceived(@NonNull String channel, @NonNull Player player, byte[] bytes) {
35+
JsonObject payload;
36+
try {
37+
payload = JSON_PARSER.parse(new String(bytes, StandardCharsets.UTF_8)).getAsJsonObject();
38+
} catch (Exception e) {
39+
return;
40+
}
41+
42+
if (!payload.has("@type")) {
43+
return;
44+
}
45+
46+
String type = payload.get("@type").getAsString();
47+
if (type.startsWith(TYPE_PREFIX)) {
48+
type = type.substring(TYPE_PREFIX.length());
49+
}
50+
51+
if ("lunarclient.apollo.transfer.v1.PingResponse".equals(type)
52+
|| "lunarclient.apollo.transfer.v1.TransferResponse".equals(type)) {
53+
UUID requestId = UUID.fromString(payload.get("request_id").getAsString().replace("+", "-"));
54+
this.handleResponse(player, requestId, payload);
55+
}
56+
}
57+
58+
public CompletableFuture<JsonObject> sendRequest(Player player, UUID requestId, JsonObject request, String requestType) {
59+
request.addProperty("@type", TYPE_PREFIX + requestType);
60+
request.addProperty("request_id", requestId.toString());
61+
JsonPacketUtil.sendPacket(player, request);
62+
63+
CompletableFuture<JsonObject> future = new CompletableFuture<>();
64+
65+
this.roundTripPacketFutures
66+
.computeIfAbsent(player.getUniqueId(), k -> new ConcurrentHashMap<>())
67+
.put(requestId, future);
68+
69+
ScheduledFuture<?> timeoutTask = this.executorService.schedule(() ->
70+
future.completeExceptionally(new TimeoutException("Response timed out")),
71+
10, TimeUnit.SECONDS
72+
);
73+
74+
future.whenComplete((result, throwable) -> timeoutTask.cancel(false));
75+
return future;
76+
}
77+
78+
private void handleResponse(Player player, UUID requestId, JsonObject message) {
79+
Map<UUID, CompletableFuture<JsonObject>> futures = this.roundTripPacketFutures.get(player.getUniqueId());
80+
if (futures == null) {
81+
return;
82+
}
83+
84+
CompletableFuture<JsonObject> future = futures.remove(requestId);
85+
if (future != null) {
86+
future.complete(message);
87+
}
88+
}
89+
}
90+
```
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { Callout } from 'nextra-theme-docs'
2+
3+
# Serverbound Packets
4+
5+
## Overview
6+
7+
Players using Lunar Client may send packets to the server for specific Apollo modules, such as the `PacketEnrichment Module` and/or when the player is joining the server. This example demonstrates how to handle packets sent from the client that are related to Apollo while using JSON messages.
8+
9+
Additionally, the `Transfer Module` expects a response packet from the client after the server sends a request. For an example of how to handle roundtrip packets, visit [Packet Roundtrip Example](/apollo/developers/lightweight/json/roundtrip-packets)
10+
11+
<Callout type="info">
12+
Note that this method uses a different plugin channel for
13+
sending and receiving packets, which is `apollo:json`.
14+
</Callout>
15+
16+
## Integration
17+
18+
```java
19+
public class ApolloPacketReceiveJsonListener implements PluginMessageListener {
20+
21+
private static final String TYPE_PREFIX = "type.googleapis.com/";
22+
private static final JsonParser JSON_PARSER = new JsonParser();
23+
24+
public ApolloPacketReceiveJsonListener(ApolloExamplePlugin plugin) {
25+
Bukkit.getServer().getMessenger().registerIncomingPluginChannel(plugin, "apollo:json", this);
26+
}
27+
28+
@Override
29+
public void onPluginMessageReceived(@NonNull String channel, @NonNull Player player, byte[] bytes) {
30+
JsonObject payload;
31+
try {
32+
payload = JSON_PARSER.parse(new String(bytes, StandardCharsets.UTF_8)).getAsJsonObject();
33+
} catch (Exception e) {
34+
return;
35+
}
36+
37+
if (!payload.has("@type")) {
38+
return;
39+
}
40+
41+
String type = payload.get("@type").getAsString();
42+
if (type.startsWith(TYPE_PREFIX)) {
43+
type = type.substring(TYPE_PREFIX.length());
44+
}
45+
46+
if ("lunarclient.apollo.player.v1.PlayerHandshakeMessage".equals(type)) {
47+
this.onPlayerHandshake(payload);
48+
}
49+
50+
// Packet Enrichment Module
51+
if ("lunarclient.apollo.packetenrichment.v1.PlayerAttackMessage".equals(type)) {
52+
this.onPlayerAttack(payload);
53+
} else if ("lunarclient.apollo.packetenrichment.v1.PlayerChatOpenMessage".equals(type)) {
54+
this.onPlayerChatOpen(payload);
55+
} else if ("lunarclient.apollo.packetenrichment.v1.PlayerChatCloseMessage".equals(type)) {
56+
this.onPlayerChatClose(payload);
57+
} else if ("lunarclient.apollo.packetenrichment.v1.PlayerUseItemMessage".equals(type)) {
58+
this.onPlayerUseItem(payload);
59+
} else if ("lunarclient.apollo.packetenrichment.v1.PlayerUseItemBucketMessage".equals(type)) {
60+
this.onPlayerUseItemBucket(payload);
61+
}
62+
}
63+
64+
private void onPlayerHandshake(JsonObject message) {
65+
String checkoutSupport = message.get("embedded_checkout_support").getAsString();
66+
67+
JsonObject minecraftVersion = message.getAsJsonObject("minecraft_version");
68+
String version = minecraftVersion.get("enum").getAsString();
69+
70+
JsonObject lunarClientVersion = message.getAsJsonObject("lunar_client_version");
71+
String gitBranch = lunarClientVersion.get("git_branch").getAsString();
72+
String gitCommit = lunarClientVersion.get("git_commit").getAsString();
73+
String semVer = lunarClientVersion.get("semver").getAsString();
74+
75+
for (JsonElement element : message.getAsJsonArray("installed_mods")) {
76+
JsonObject mod = element.getAsJsonObject();
77+
78+
String modId = mod.get("id").getAsString();
79+
String displayName = mod.get("name").getAsString();
80+
String modVersion = mod.get("version").getAsString();
81+
String modType = mod.get("type").getAsString();
82+
}
83+
}
84+
85+
private void onPlayerAttack(JsonObject message) {
86+
long instantiationTimeMs = JsonUtil.toJavaTimestamp(message);
87+
88+
JsonObject targetInfo = message.getAsJsonObject("target_info");
89+
JsonObject attackerInfo = message.getAsJsonObject("attacker_info");
90+
91+
this.onPlayerInfo(targetInfo);
92+
this.onPlayerInfo(attackerInfo);
93+
}
94+
95+
private void onPlayerChatOpen(JsonObject message) {
96+
long instantiationTimeMs = JsonUtil.toJavaTimestamp(message);
97+
this.onPlayerInfo(message.getAsJsonObject("player_info"));
98+
}
99+
100+
private void onPlayerChatClose(JsonObject message) {
101+
long instantiationTimeMs = JsonUtil.toJavaTimestamp(message);
102+
this.onPlayerInfo(message.getAsJsonObject("player_info"));
103+
}
104+
105+
private void onPlayerUseItem(JsonObject message) {
106+
long instantiationTimeMs = JsonUtil.toJavaTimestamp(message);
107+
this.onPlayerInfo(message.getAsJsonObject("player_info"));
108+
109+
boolean mainHand = message.get("main_hand").getAsBoolean();
110+
}
111+
112+
private void onPlayerUseItemBucket(JsonObject message) {
113+
long instantiationTimeMs = JsonUtil.toJavaTimestamp(message);
114+
this.onPlayerInfo(message.getAsJsonObject("player_info"));
115+
116+
JsonObject rayTraceResult = message.getAsJsonObject("ray_trace_result");
117+
118+
if (rayTraceResult.has("block")) {
119+
JsonObject blockHit = rayTraceResult.getAsJsonObject("block");
120+
121+
JsonObject hitLocation = blockHit.getAsJsonObject("hit_location");
122+
JsonObject blockLocation = blockHit.getAsJsonObject("block_location");
123+
String directionStr = blockHit.get("direction").getAsString();
124+
} else if (rayTraceResult.has("entity")) {
125+
JsonObject entityHit = rayTraceResult.getAsJsonObject("entity");
126+
127+
JsonObject hitLocation = entityHit.getAsJsonObject("hit_location");
128+
String entityId = entityHit.get("entity_id").getAsString();
129+
} else {
130+
// MISS
131+
}
132+
}
133+
134+
private void onPlayerInfo(JsonObject info) {
135+
UUID uuid = JsonUtil.toJavaUuid(info.getAsJsonObject("player_uuid"));
136+
Location location = JsonUtil.toBukkitPlayerLocation(info.getAsJsonObject("location"));
137+
boolean sneaking = info.get("sneaking").getAsBoolean();
138+
boolean sprinting = info.get("sprinting").getAsBoolean();
139+
boolean jumping = info.get("jumping").getAsBoolean();
140+
float forwardSpeed = info.get("forward_speed").getAsFloat();
141+
float strafeSpeed = info.get("strafe_speed").getAsFloat();
142+
}
143+
}
144+
```

docs/developers/lightweight/protobuf/object-util.mdx

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ These utilities facilitate the creation of Apollo objects, commonly used across
77
## Integration
88

99
```java
10-
public static UUID toJavaUuid(Uuid message) {
11-
return new UUID(message.getHigh64(), message.getLow64());
10+
public static UUID toJavaUuid(JsonObject obj) {
11+
long high = Long.parseUnsignedLong(obj.get("high64").getAsString());
12+
long low = Long.parseUnsignedLong(obj.get("low64").getAsString());
13+
return new UUID(high, low);
1214
}
1315

14-
public static long toJavaTimestamp(Timestamp message) {
15-
return message.getSeconds() * 1000 + message.getNanos() / 1000000;
16+
public static long toJavaTimestamp(JsonObject timestampObject) {
17+
JsonObject packetInfo = timestampObject.getAsJsonObject("packet_info");
18+
String iso = packetInfo.get("instantiation_time").getAsString();
19+
return Instant.parse(iso).toEpochMilli();
1620
}
1721

1822
public static Uuid createUuidProto(UUID object) {
@@ -73,14 +77,19 @@ public static BlockLocation createBlockLocationProto(Location location) {
7377
.build();
7478
}
7579

76-
public static Location toBukkitLocation(com.lunarclient.apollo.common.v1.Location message) {
77-
return new Location(Bukkit.getWorld(message.getWorld()), message.getX(), message.getY(), message.getZ());
80+
public static Location toBukkitLocation(JsonObject message) {
81+
return new Location(
82+
Bukkit.getWorld(message.get("world").getAsString()),
83+
message.get("x").getAsDouble(),
84+
message.get("y").getAsDouble(),
85+
message.get("z").getAsDouble()
86+
);
7887
}
7988

80-
public static Location toBukkitLocation(com.lunarclient.apollo.common.v1.PlayerLocation message) {
81-
Location location = ProtobufUtil.toBukkitLocation(message.getLocation());
82-
location.setYaw(message.getYaw());
83-
location.setPitch(message.getPitch());
89+
public static Location toBukkitPlayerLocation(JsonObject message) {
90+
Location location = JsonUtil.toBukkitLocation(message.getAsJsonObject("location"));
91+
location.setYaw(message.get("yaw").getAsFloat());
92+
location.setPitch(message.get("pitch").getAsFloat());
8493
return location;
8594
}
8695
```

docs/developers/lightweight/protobuf/packet-util.mdx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ The methods in this utility leverage the same plugin messaging channel as the Ap
1313
To utilize Apollo Modules, first define a list of the modules you want to use:
1414

1515
```java
16-
private static final List<String> APOLLO_MODULES = Arrays.asList("limb", "beam", "border", "chat", "colored_fire", "combat", "cooldown",
17-
"entity", "glow", "hologram", "mod_setting", "nametag", "nick_hider", "notification", "packet_enrichment", "rich_presence",
18-
"server_rule", "staff_mod", "stopwatch", "team", "title", "tnt_countdown", "transfer", "vignette", "waypoint"
16+
private static final List<String> APOLLO_MODULES = Arrays.asList("auto_text_hotkey", "beam", "border", "chat", "colored_fire", "combat", "cooldown",
17+
"entity", "glint", "glow", "hologram", "inventory", "limb", "mod_setting", "nametag", "nick_hider", "notification", "pay_now", "packet_enrichment",
18+
"rich_presence", "saturation", "server_rule", "staff_mod", "stopwatch", "team", "tebex", "title", "tnt_countdown", "transfer", "vignette", "waypoint"
1919
);
2020
```
2121

@@ -31,7 +31,14 @@ Next, specify the properties for these modules. These properties are included in
3131
private static final Table<String, String, Value> CONFIG_MODULE_PROPERTIES = HashBasedTable.create();
3232

3333
static {
34+
// Module Options the client needs to be notified about. These properties are sent with the enable module packet.
35+
// While using the Apollo plugin this would be equivalent to modifying the config.yml
3436
CONFIG_MODULE_PROPERTIES.put("combat", "disable-miss-penalty", Value.newBuilder().setBoolValue(false).build());
37+
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-attack.send-packet", Value.newBuilder().setBoolValue(false).build());
38+
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-chat-open.send-packet", Value.newBuilder().setBoolValue(false).build());
39+
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-chat-close.send-packet", Value.newBuilder().setBoolValue(false).build());
40+
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-use-item.send-packet", Value.newBuilder().setBoolValue(false).build());
41+
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-use-item-bucket.send-packet", Value.newBuilder().setBoolValue(false).build());
3542
CONFIG_MODULE_PROPERTIES.put("server_rule", "competitive-game", Value.newBuilder().setBoolValue(false).build());
3643
CONFIG_MODULE_PROPERTIES.put("server_rule", "competitive-commands", Value.newBuilder().setListValue(
3744
ListValue.newBuilder().addAllValues(Arrays.asList(

docs/developers/lightweight/protobuf/roundtrip-packets.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class ApolloRoundtripProtoListener implements PluginMessageListener {
2121
}
2222

2323
@Override
24-
public void onPluginMessageReceived(String s, Player player, byte[] bytes) {
24+
public void onPluginMessageReceived(@NonNull String channel, @NonNull Player player, byte[] bytes) {
2525
try {
2626
Any any = Any.parseFrom(bytes);
2727

0 commit comments

Comments
 (0)