Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/developers/lightweight/json/_meta.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"getting-started": "Getting Started",
"player-detection": "Player Detection",
"serverbound-packets": "Serverbound Packets",
"roundtrip-packets": "Roundtrip Packets",
"packet-util": "Packet Util",
"object-util": "Object Util",
"adventure-util": "Adventure Util"
Expand Down
2 changes: 2 additions & 0 deletions docs/developers/lightweight/json/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ While constructing messages, note that the `@type` field isn't required if the m
🔗 [Detecting players using LunarClient](/apollo/developers/lightweight/json/player-detection)<br/>
🔗 [Common Apollo Objects](/apollo/developers/lightweight/json/object-util)<br/>
🔗 [Adventure Util](/apollo/developers/lightweight/json/adventure-util)<br/>
🔗 [Apollo Serverbound packets](/apollo/developers/lightweight/json/serverbound-packets)<br/>
🔗 [Apollo Roundtrip packets](/apollo/developers/lightweight/json/roundtrip-packets)<br/>

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.
13 changes: 9 additions & 4 deletions docs/developers/lightweight/json/packet-util.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ The methods in this utility leverage the same plugin messaging channel as the Ap
To utilize Apollo Modules, first define a list of the modules you want to use:

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

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

static {
// Module Options that the client needs to notified about, these properties are sent with the enable module packet
// Module Options the client needs to be notified about. These properties are sent with the enable module packet.
// While using the Apollo plugin this would be equivalent to modifying the config.yml
CONFIG_MODULE_PROPERTIES.put("combat", "disable-miss-penalty", false);
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-attack.send-packet", false);
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-chat-open.send-packet", false);
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-chat-close.send-packet", false);
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-use-item.send-packet", false);
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-use-item-bucket.send-packet", false);
CONFIG_MODULE_PROPERTIES.put("server_rule", "competitive-game", false);
CONFIG_MODULE_PROPERTIES.put("server_rule", "competitive-commands", Arrays.asList("/server", "/servers", "/hub"));
CONFIG_MODULE_PROPERTIES.put("server_rule", "disable-shaders", false);
Expand Down
90 changes: 90 additions & 0 deletions docs/developers/lightweight/json/roundtrip-packets.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Callout } from 'nextra-theme-docs'

# Roundtrip Packets

## Overview

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.

<Callout type="info">
Note that this method uses a different plugin channel for
sending and receiving packets, which is `apollo:json`.
</Callout>

## Integration

```java
public class ApolloRoundtripJsonListener implements PluginMessageListener {

private static final String TYPE_PREFIX = "type.googleapis.com/";
private static final JsonParser JSON_PARSER = new JsonParser();

@Getter
private static ApolloRoundtripJsonListener instance;

private final Map<UUID, Map<UUID, CompletableFuture<JsonObject>>> roundTripPacketFutures = new ConcurrentHashMap<>();
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);

public ApolloRoundtripJsonListener(ApolloExamplePlugin plugin) {
instance = this;
Bukkit.getServer().getMessenger().registerIncomingPluginChannel(plugin, "apollo:json", this);
}

@Override
public void onPluginMessageReceived(@NonNull String channel, @NonNull Player player, byte[] bytes) {
JsonObject payload;
try {
payload = JSON_PARSER.parse(new String(bytes, StandardCharsets.UTF_8)).getAsJsonObject();
} catch (Exception e) {
return;
}

if (!payload.has("@type")) {
return;
}

String type = payload.get("@type").getAsString();
if (type.startsWith(TYPE_PREFIX)) {
type = type.substring(TYPE_PREFIX.length());
}

if ("lunarclient.apollo.transfer.v1.PingResponse".equals(type)
|| "lunarclient.apollo.transfer.v1.TransferResponse".equals(type)) {
UUID requestId = UUID.fromString(payload.get("request_id").getAsString().replace("+", "-"));
this.handleResponse(player, requestId, payload);
}
}

public CompletableFuture<JsonObject> sendRequest(Player player, UUID requestId, JsonObject request, String requestType) {
request.addProperty("@type", TYPE_PREFIX + requestType);
request.addProperty("request_id", requestId.toString());
JsonPacketUtil.sendPacket(player, request);

CompletableFuture<JsonObject> future = new CompletableFuture<>();

this.roundTripPacketFutures
.computeIfAbsent(player.getUniqueId(), k -> new ConcurrentHashMap<>())
.put(requestId, future);

ScheduledFuture<?> timeoutTask = this.executorService.schedule(() ->
future.completeExceptionally(new TimeoutException("Response timed out")),
10, TimeUnit.SECONDS
);

future.whenComplete((result, throwable) -> timeoutTask.cancel(false));
return future;
}

private void handleResponse(Player player, UUID requestId, JsonObject message) {
Map<UUID, CompletableFuture<JsonObject>> futures = this.roundTripPacketFutures.get(player.getUniqueId());
if (futures == null) {
return;
}

CompletableFuture<JsonObject> future = futures.remove(requestId);
if (future != null) {
future.complete(message);
}
}
}
```
144 changes: 144 additions & 0 deletions docs/developers/lightweight/json/serverbound-packets.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { Callout } from 'nextra-theme-docs'

# Serverbound Packets

## Overview

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.

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)

<Callout type="info">
Note that this method uses a different plugin channel for
sending and receiving packets, which is `apollo:json`.
</Callout>

## Integration

```java
public class ApolloPacketReceiveJsonListener implements PluginMessageListener {

private static final String TYPE_PREFIX = "type.googleapis.com/";
private static final JsonParser JSON_PARSER = new JsonParser();

public ApolloPacketReceiveJsonListener(ApolloExamplePlugin plugin) {
Bukkit.getServer().getMessenger().registerIncomingPluginChannel(plugin, "apollo:json", this);
}

@Override
public void onPluginMessageReceived(@NonNull String channel, @NonNull Player player, byte[] bytes) {
JsonObject payload;
try {
payload = JSON_PARSER.parse(new String(bytes, StandardCharsets.UTF_8)).getAsJsonObject();
} catch (Exception e) {
return;
}

if (!payload.has("@type")) {
return;
}

String type = payload.get("@type").getAsString();
if (type.startsWith(TYPE_PREFIX)) {
type = type.substring(TYPE_PREFIX.length());
}

if ("lunarclient.apollo.player.v1.PlayerHandshakeMessage".equals(type)) {
this.onPlayerHandshake(payload);
}

// Packet Enrichment Module
if ("lunarclient.apollo.packetenrichment.v1.PlayerAttackMessage".equals(type)) {
this.onPlayerAttack(payload);
} else if ("lunarclient.apollo.packetenrichment.v1.PlayerChatOpenMessage".equals(type)) {
this.onPlayerChatOpen(payload);
} else if ("lunarclient.apollo.packetenrichment.v1.PlayerChatCloseMessage".equals(type)) {
this.onPlayerChatClose(payload);
} else if ("lunarclient.apollo.packetenrichment.v1.PlayerUseItemMessage".equals(type)) {
this.onPlayerUseItem(payload);
} else if ("lunarclient.apollo.packetenrichment.v1.PlayerUseItemBucketMessage".equals(type)) {
this.onPlayerUseItemBucket(payload);
}
}

private void onPlayerHandshake(JsonObject message) {
String checkoutSupport = message.get("embedded_checkout_support").getAsString();

JsonObject minecraftVersion = message.getAsJsonObject("minecraft_version");
String version = minecraftVersion.get("enum").getAsString();

JsonObject lunarClientVersion = message.getAsJsonObject("lunar_client_version");
String gitBranch = lunarClientVersion.get("git_branch").getAsString();
String gitCommit = lunarClientVersion.get("git_commit").getAsString();
String semVer = lunarClientVersion.get("semver").getAsString();

for (JsonElement element : message.getAsJsonArray("installed_mods")) {
JsonObject mod = element.getAsJsonObject();

String modId = mod.get("id").getAsString();
String displayName = mod.get("name").getAsString();
String modVersion = mod.get("version").getAsString();
String modType = mod.get("type").getAsString();
}
}

private void onPlayerAttack(JsonObject message) {
long instantiationTimeMs = JsonUtil.toJavaTimestamp(message);

JsonObject targetInfo = message.getAsJsonObject("target_info");
JsonObject attackerInfo = message.getAsJsonObject("attacker_info");

this.onPlayerInfo(targetInfo);
this.onPlayerInfo(attackerInfo);
}

private void onPlayerChatOpen(JsonObject message) {
long instantiationTimeMs = JsonUtil.toJavaTimestamp(message);
this.onPlayerInfo(message.getAsJsonObject("player_info"));
}

private void onPlayerChatClose(JsonObject message) {
long instantiationTimeMs = JsonUtil.toJavaTimestamp(message);
this.onPlayerInfo(message.getAsJsonObject("player_info"));
}

private void onPlayerUseItem(JsonObject message) {
long instantiationTimeMs = JsonUtil.toJavaTimestamp(message);
this.onPlayerInfo(message.getAsJsonObject("player_info"));

boolean mainHand = message.get("main_hand").getAsBoolean();
}

private void onPlayerUseItemBucket(JsonObject message) {
long instantiationTimeMs = JsonUtil.toJavaTimestamp(message);
this.onPlayerInfo(message.getAsJsonObject("player_info"));

JsonObject rayTraceResult = message.getAsJsonObject("ray_trace_result");

if (rayTraceResult.has("block")) {
JsonObject blockHit = rayTraceResult.getAsJsonObject("block");

JsonObject hitLocation = blockHit.getAsJsonObject("hit_location");
JsonObject blockLocation = blockHit.getAsJsonObject("block_location");
String directionStr = blockHit.get("direction").getAsString();
} else if (rayTraceResult.has("entity")) {
JsonObject entityHit = rayTraceResult.getAsJsonObject("entity");

JsonObject hitLocation = entityHit.getAsJsonObject("hit_location");
String entityId = entityHit.get("entity_id").getAsString();
} else {
// MISS
}
}

private void onPlayerInfo(JsonObject info) {
UUID uuid = JsonUtil.toJavaUuid(info.getAsJsonObject("player_uuid"));
Location location = JsonUtil.toBukkitPlayerLocation(info.getAsJsonObject("location"));
boolean sneaking = info.get("sneaking").getAsBoolean();
boolean sprinting = info.get("sprinting").getAsBoolean();
boolean jumping = info.get("jumping").getAsBoolean();
float forwardSpeed = info.get("forward_speed").getAsFloat();
float strafeSpeed = info.get("strafe_speed").getAsFloat();
}
}
```
29 changes: 19 additions & 10 deletions docs/developers/lightweight/protobuf/object-util.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ These utilities facilitate the creation of Apollo objects, commonly used across
## Integration

```java
public static UUID toJavaUuid(Uuid message) {
return new UUID(message.getHigh64(), message.getLow64());
public static UUID toJavaUuid(JsonObject obj) {
long high = Long.parseUnsignedLong(obj.get("high64").getAsString());
long low = Long.parseUnsignedLong(obj.get("low64").getAsString());
return new UUID(high, low);
}

public static long toJavaTimestamp(Timestamp message) {
return message.getSeconds() * 1000 + message.getNanos() / 1000000;
public static long toJavaTimestamp(JsonObject timestampObject) {
JsonObject packetInfo = timestampObject.getAsJsonObject("packet_info");
String iso = packetInfo.get("instantiation_time").getAsString();
return Instant.parse(iso).toEpochMilli();
}

public static Uuid createUuidProto(UUID object) {
Expand Down Expand Up @@ -73,14 +77,19 @@ public static BlockLocation createBlockLocationProto(Location location) {
.build();
}

public static Location toBukkitLocation(com.lunarclient.apollo.common.v1.Location message) {
return new Location(Bukkit.getWorld(message.getWorld()), message.getX(), message.getY(), message.getZ());
public static Location toBukkitLocation(JsonObject message) {
return new Location(
Bukkit.getWorld(message.get("world").getAsString()),
message.get("x").getAsDouble(),
message.get("y").getAsDouble(),
message.get("z").getAsDouble()
);
}

public static Location toBukkitLocation(com.lunarclient.apollo.common.v1.PlayerLocation message) {
Location location = ProtobufUtil.toBukkitLocation(message.getLocation());
location.setYaw(message.getYaw());
location.setPitch(message.getPitch());
public static Location toBukkitPlayerLocation(JsonObject message) {
Location location = JsonUtil.toBukkitLocation(message.getAsJsonObject("location"));
location.setYaw(message.get("yaw").getAsFloat());
location.setPitch(message.get("pitch").getAsFloat());
return location;
}
```
Expand Down
13 changes: 10 additions & 3 deletions docs/developers/lightweight/protobuf/packet-util.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ The methods in this utility leverage the same plugin messaging channel as the Ap
To utilize Apollo Modules, first define a list of the modules you want to use:

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

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

static {
// Module Options that the client needs to notified about, these properties are sent with the enable module packet
// While using the Apollo plugin this would be equivalent to modifying the config.yml
CONFIG_MODULE_PROPERTIES.put("combat", "disable-miss-penalty", Value.newBuilder().setBoolValue(false).build());
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-attack.send-packet", Value.newBuilder().setBoolValue(false).build());
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-chat-open.send-packet", Value.newBuilder().setBoolValue(false).build());
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-chat-close.send-packet", Value.newBuilder().setBoolValue(false).build());
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-use-item.send-packet", Value.newBuilder().setBoolValue(false).build());
CONFIG_MODULE_PROPERTIES.put("packet_enrichment", "player-use-item-bucket.send-packet", Value.newBuilder().setBoolValue(false).build());
CONFIG_MODULE_PROPERTIES.put("server_rule", "competitive-game", Value.newBuilder().setBoolValue(false).build());
CONFIG_MODULE_PROPERTIES.put("server_rule", "competitive-commands", Value.newBuilder().setListValue(
ListValue.newBuilder().addAllValues(Arrays.asList(
Expand Down
2 changes: 1 addition & 1 deletion docs/developers/lightweight/protobuf/roundtrip-packets.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class ApolloRoundtripProtoListener implements PluginMessageListener {
}

@Override
public void onPluginMessageReceived(String s, Player player, byte[] bytes) {
public void onPluginMessageReceived(@NonNull String channel, @NonNull Player player, byte[] bytes) {
try {
Any any = Any.parseFrom(bytes);

Expand Down
Loading
Loading