From a42f0d3e2b70d1f872c1c24aaaf82fe48a64da1e Mon Sep 17 00:00:00 2001 From: ItsNature Date: Tue, 1 Oct 2024 17:29:20 +0200 Subject: [PATCH] Finish JSON lightweight docs --- .../apollo/example/json/JsonPacketUtil.java | 54 ++++++-- .../apollo/example/json/JsonUtil.java | 62 ++++----- .../listeners/ApolloPlayerJsonListener.java | 71 ++++------ .../apollo/example/proto/ProtobufUtil.java | 26 ++-- docs/developers/lightweight/_meta.json | 3 +- docs/developers/lightweight/json/_meta.json | 7 + .../lightweight/json/adventure-util.mdx | 13 ++ .../lightweight/json/getting-started.mdx | 22 +++ .../lightweight/json/object-util.mdx | 129 ++++++++++++++++++ .../lightweight/json/packet-util.mdx | 124 +++++++++++++++++ .../lightweight/json/player-detection.mdx | 62 +++++++++ 11 files changed, 473 insertions(+), 100 deletions(-) create mode 100644 docs/developers/lightweight/json/_meta.json create mode 100644 docs/developers/lightweight/json/adventure-util.mdx create mode 100644 docs/developers/lightweight/json/getting-started.mdx create mode 100644 docs/developers/lightweight/json/object-util.mdx create mode 100644 docs/developers/lightweight/json/packet-util.mdx create mode 100644 docs/developers/lightweight/json/player-detection.mdx diff --git a/bukkit-example/src/main/java/com/lunarclient/apollo/example/json/JsonPacketUtil.java b/bukkit-example/src/main/java/com/lunarclient/apollo/example/json/JsonPacketUtil.java index 456eaf44..419fbc8d 100644 --- a/bukkit-example/src/main/java/com/lunarclient/apollo/example/json/JsonPacketUtil.java +++ b/bukkit-example/src/main/java/com/lunarclient/apollo/example/json/JsonPacketUtil.java @@ -23,6 +23,7 @@ */ package com.lunarclient.apollo.example.json; +import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -30,6 +31,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.lunarclient.apollo.example.ApolloExamplePlugin; +import java.util.Arrays; import java.util.List; import java.util.Map; import org.bukkit.Bukkit; @@ -38,6 +40,34 @@ public final class JsonPacketUtil { + private static final List 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" + ); + + // Module Id -> Option key -> Object + private static final Table 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", 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); + CONFIG_MODULE_PROPERTIES.put("server_rule", "disable-chunk-reloading", false); + CONFIG_MODULE_PROPERTIES.put("server_rule", "disable-broadcasting", false); + CONFIG_MODULE_PROPERTIES.put("server_rule", "anti-portal-traps", true); + CONFIG_MODULE_PROPERTIES.put("server_rule", "override-brightness", false); + CONFIG_MODULE_PROPERTIES.put("server_rule", "brightness", 50); + CONFIG_MODULE_PROPERTIES.put("server_rule", "override-nametag-render-distance", false); + CONFIG_MODULE_PROPERTIES.put("server_rule", "nametag-render-distance", 64); + CONFIG_MODULE_PROPERTIES.put("server_rule", "override-max-chat-length", false); + CONFIG_MODULE_PROPERTIES.put("server_rule", "max-chat-length", 256); + CONFIG_MODULE_PROPERTIES.put("tnt_countdown", "tnt-ticks", 80); + CONFIG_MODULE_PROPERTIES.put("waypoint", "server-handles-waypoints", false); + } + public static void sendPacket(Player player, JsonObject message) { player.sendPluginMessage(ApolloExamplePlugin.getPlugin(), "apollo:json", message.toString().getBytes()); } @@ -49,18 +79,6 @@ public static void broadcastPacket(JsonObject message) { player.sendPluginMessage(ApolloExamplePlugin.getPlugin(), "apollo:json", data)); } - public static void enableModules(Player player, List modules, Table properties) { - JsonArray settings = modules.stream() - .map(module -> JsonPacketUtil.createEnableModuleObject(module, properties.row(module))) - .collect(JsonArray::new, JsonArray::add, JsonArray::addAll); - - JsonObject message = new JsonObject(); - message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.configurable.v1.OverrideConfigurableSettingsMessage"); - message.add("configurable_settings", settings); - - JsonPacketUtil.sendPacket(player, message); - } - public static JsonObject createEnableModuleObject(@NotNull String module, Map properties) { JsonObject enableModuleObject = new JsonObject(); enableModuleObject.addProperty("apollo_module", module); @@ -98,6 +116,18 @@ private static JsonElement convertToJsonElement(Object value) { throw new RuntimeException("Unable to wrap value of type '" + value.getClass().getSimpleName() + "'!"); } + public static void enableModules(Player player) { + JsonArray settings = APOLLO_MODULES.stream() + .map(module -> JsonPacketUtil.createEnableModuleObject(module, CONFIG_MODULE_PROPERTIES.row(module))) + .collect(JsonArray::new, JsonArray::add, JsonArray::addAll); + + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.configurable.v1.OverrideConfigurableSettingsMessage"); + message.add("configurable_settings", settings); + + JsonPacketUtil.sendPacket(player, message); + } + private JsonPacketUtil() { } diff --git a/bukkit-example/src/main/java/com/lunarclient/apollo/example/json/JsonUtil.java b/bukkit-example/src/main/java/com/lunarclient/apollo/example/json/JsonUtil.java index b34b42df..7df9cd62 100644 --- a/bukkit-example/src/main/java/com/lunarclient/apollo/example/json/JsonUtil.java +++ b/bukkit-example/src/main/java/com/lunarclient/apollo/example/json/JsonUtil.java @@ -35,6 +35,25 @@ public final class JsonUtil { + public static JsonObject createEnableModuleObjectWithType(@NotNull String module, Map properties) { + JsonObject enableModuleObject = JsonPacketUtil.createEnableModuleObject(module, properties); + enableModuleObject.addProperty("@type", "type.googleapis.com/lunarclient.apollo.configurable.v1.ConfigurableSettings"); + return enableModuleObject; + } + + public static JsonObject createUuidObject(@NotNull UUID uuid) { + JsonObject uuidObject = new JsonObject(); + uuidObject.addProperty("high64", Long.toUnsignedString(uuid.getMostSignificantBits())); + uuidObject.addProperty("low64", Long.toUnsignedString(uuid.getLeastSignificantBits())); + return uuidObject; + } + + public static JsonObject createColorObject(@NotNull Color color) { + JsonObject colorObject = new JsonObject(); + colorObject.addProperty("color", color.getRGB()); + return colorObject; + } + public static String createDurationObject(@NotNull Duration duration) { long seconds = duration.getSeconds(); int nanos = duration.getNano(); @@ -52,23 +71,20 @@ public static String createDurationObject(@NotNull Duration duration) { return durationString; } - public static JsonObject createColorObject(@NotNull Color color) { - JsonObject colorObject = new JsonObject(); - colorObject.addProperty("color", color.getRGB()); - return colorObject; - } - - public static JsonObject createUuidObject(@NotNull UUID uuid) { - JsonObject uuidObject = new JsonObject(); - uuidObject.addProperty("high64", Long.toUnsignedString(uuid.getMostSignificantBits())); - uuidObject.addProperty("low64", Long.toUnsignedString(uuid.getLeastSignificantBits())); - return uuidObject; + public static JsonObject createCuboid2DObject(double minX, double minZ, double maxX, double maxZ) { + JsonObject cuboid2DObject = new JsonObject(); + cuboid2DObject.addProperty("min_x", minX); + cuboid2DObject.addProperty("min_z", minZ); + cuboid2DObject.addProperty("max_x", maxX); + cuboid2DObject.addProperty("max_z", maxZ); + return cuboid2DObject; } - public static JsonObject createEnableModuleObjectWithType(@NotNull String module, Map properties) { - JsonObject enableModuleObject = JsonPacketUtil.createEnableModuleObject(module, properties); - enableModuleObject.addProperty("@type", "type.googleapis.com/lunarclient.apollo.configurable.v1.ConfigurableSettings"); - return enableModuleObject; + public static JsonObject createEntityIdObject(@NotNull Entity entity) { + JsonObject entityIdObject = new JsonObject(); + entityIdObject.addProperty("entity_id", entity.getEntityId()); + entityIdObject.add("entity_uuid", JsonUtil.createUuidObject(entity.getUniqueId())); + return entityIdObject; } public static JsonObject createLocationObject(@NotNull Location location) { @@ -89,22 +105,6 @@ public static JsonObject createBlockLocationObject(@NotNull Location location) { return locationObject; } - public static JsonObject createEntityIdObject(@NotNull Entity entity) { - JsonObject entityIdObject = new JsonObject(); - entityIdObject.addProperty("entity_id", entity.getEntityId()); - entityIdObject.add("entity_uuid", JsonUtil.createUuidObject(entity.getUniqueId())); - return entityIdObject; - } - - public static JsonObject createCuboid2DObject(double minX, double minZ, double maxX, double maxZ) { - JsonObject cuboid2DObject = new JsonObject(); - cuboid2DObject.addProperty("min_x", minX); - cuboid2DObject.addProperty("min_z", minZ); - cuboid2DObject.addProperty("max_x", maxX); - cuboid2DObject.addProperty("max_z", maxZ); - return cuboid2DObject; - } - public static JsonObject createItemStackIconObject(@Nullable String itemName, int itemId, int customModelData) { JsonObject itemIconObject = new JsonObject(); if (itemName != null) { diff --git a/bukkit-example/src/main/java/com/lunarclient/apollo/example/json/listeners/ApolloPlayerJsonListener.java b/bukkit-example/src/main/java/com/lunarclient/apollo/example/json/listeners/ApolloPlayerJsonListener.java index 1e5db523..1df631db 100644 --- a/bukkit-example/src/main/java/com/lunarclient/apollo/example/json/listeners/ApolloPlayerJsonListener.java +++ b/bukkit-example/src/main/java/com/lunarclient/apollo/example/json/listeners/ApolloPlayerJsonListener.java @@ -23,13 +23,10 @@ */ package com.lunarclient.apollo.example.json.listeners; -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.Table; +import com.google.gson.JsonObject; import com.lunarclient.apollo.example.ApolloExamplePlugin; import com.lunarclient.apollo.example.json.JsonPacketUtil; -import java.util.Arrays; import java.util.HashSet; -import java.util.List; import java.util.Set; import java.util.UUID; import org.bukkit.Bukkit; @@ -37,42 +34,12 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerRegisterChannelEvent; import org.bukkit.plugin.messaging.Messenger; public class ApolloPlayerJsonListener implements Listener { - private static final List 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" - ); - - // Module Id -> Option key -> Object - private static final Table 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", 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); - CONFIG_MODULE_PROPERTIES.put("server_rule", "disable-chunk-reloading", false); - CONFIG_MODULE_PROPERTIES.put("server_rule", "disable-broadcasting", false); - CONFIG_MODULE_PROPERTIES.put("server_rule", "anti-portal-traps", true); - CONFIG_MODULE_PROPERTIES.put("server_rule", "override-brightness", false); - CONFIG_MODULE_PROPERTIES.put("server_rule", "brightness", 50); - CONFIG_MODULE_PROPERTIES.put("server_rule", "override-nametag-render-distance", false); - CONFIG_MODULE_PROPERTIES.put("server_rule", "nametag-render-distance", 64); - CONFIG_MODULE_PROPERTIES.put("server_rule", "override-max-chat-length", false); - CONFIG_MODULE_PROPERTIES.put("server_rule", "max-chat-length", 256); - CONFIG_MODULE_PROPERTIES.put("tnt_countdown", "tnt-ticks", 80); - CONFIG_MODULE_PROPERTIES.put("waypoint", "server-handles-waypoints", false); - } - - private static final String REGISTER_CHANNEL = "lunar:apollo"; // Used for detecting whether the player supports Apollo - private static final String LIGHTWEIGHT_CHANNEL = "apollo:json"; // Used for sending and receiving feature packets - private final ApolloExamplePlugin plugin; private final Set playersRunningApollo = new HashSet<>(); @@ -81,9 +48,9 @@ public ApolloPlayerJsonListener(ApolloExamplePlugin plugin) { this.plugin = plugin; Messenger messenger = Bukkit.getServer().getMessenger(); - messenger.registerIncomingPluginChannel(plugin, REGISTER_CHANNEL, (s, player, bytes) -> { }); - messenger.registerIncomingPluginChannel(plugin, LIGHTWEIGHT_CHANNEL, (s, player, bytes) -> { }); - messenger.registerOutgoingPluginChannel(plugin, LIGHTWEIGHT_CHANNEL); + messenger.registerIncomingPluginChannel(plugin, "lunar:apollo", (s, player, bytes) -> { }); + messenger.registerIncomingPluginChannel(plugin, "apollo:json", (s, player, bytes) -> { }); + messenger.registerOutgoingPluginChannel(plugin, "apollo:json"); Bukkit.getPluginManager().registerEvents(this, plugin); } @@ -92,28 +59,46 @@ public void disable() { this.playersRunningApollo.clear(); Messenger messenger = Bukkit.getServer().getMessenger(); - messenger.unregisterIncomingPluginChannel(this.plugin, REGISTER_CHANNEL); - messenger.unregisterIncomingPluginChannel(this.plugin, LIGHTWEIGHT_CHANNEL); - messenger.unregisterOutgoingPluginChannel(this.plugin, LIGHTWEIGHT_CHANNEL); + messenger.unregisterIncomingPluginChannel(this.plugin, "lunar:apollo"); + messenger.unregisterIncomingPluginChannel(this.plugin, "apollo:json"); + messenger.unregisterOutgoingPluginChannel(this.plugin, "apollo:json"); HandlerList.unregisterAll(this); } @EventHandler private void onRegisterChannel(PlayerRegisterChannelEvent event) { - if (!event.getChannel().equalsIgnoreCase(REGISTER_CHANNEL)) { + if (!event.getChannel().equalsIgnoreCase("lunar:apollo")) { return; } this.onApolloRegister(event.getPlayer()); } + @EventHandler + private void onPlayerChangedWorld(PlayerChangedWorldEvent event) { + Player player = event.getPlayer(); + + // Sending the player's world name to the client is required for some modules + JsonPacketUtil.sendPacket(player, this.createUpdatePlayerWorldMessage(player)); + } + + private JsonObject createUpdatePlayerWorldMessage(Player player) { + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.player.v1.UpdatePlayerWorldMessage"); + message.addProperty("world", player.getWorld().getName()); + return message; + } + private boolean isPlayerRunningApollo(Player player) { return this.playersRunningApollo.contains(player.getUniqueId()); } private void onApolloRegister(Player player) { - JsonPacketUtil.enableModules(player, APOLLO_MODULES, CONFIG_MODULE_PROPERTIES); + JsonPacketUtil.enableModules(player); + + // Sending the player's world name to the client is required for some modules + JsonPacketUtil.sendPacket(player, this.createUpdatePlayerWorldMessage(player)); this.playersRunningApollo.add(player.getUniqueId()); player.sendMessage("You are using LunarClient!"); diff --git a/bukkit-example/src/main/java/com/lunarclient/apollo/example/proto/ProtobufUtil.java b/bukkit-example/src/main/java/com/lunarclient/apollo/example/proto/ProtobufUtil.java index a6cbce06..a8ea6dfe 100644 --- a/bukkit-example/src/main/java/com/lunarclient/apollo/example/proto/ProtobufUtil.java +++ b/bukkit-example/src/main/java/com/lunarclient/apollo/example/proto/ProtobufUtil.java @@ -56,6 +56,19 @@ public static Uuid createUuidProto(UUID object) { .build(); } + public static com.lunarclient.apollo.common.v1.Color createColorProto(Color object) { + return com.lunarclient.apollo.common.v1.Color.newBuilder() + .setColor(object.getRGB()) + .build(); + } + + public static com.google.protobuf.Duration createDurationProto(Duration object) { + return com.google.protobuf.Duration.newBuilder() + .setSeconds(object.getSeconds()) + .setNanos(object.getNano()) + .build(); + } + public static Cuboid2D createCuboid2DProto(double minX, double minZ, double maxX, double maxZ) { return Cuboid2D.newBuilder() .setMinX(minX) @@ -72,19 +85,6 @@ public static EntityId createEntityIdProto(int id, UUID uuid) { .build(); } - public static com.lunarclient.apollo.common.v1.Color createColorProto(Color object) { - return com.lunarclient.apollo.common.v1.Color.newBuilder() - .setColor(object.getRGB()) - .build(); - } - - public static com.google.protobuf.Duration createDurationProto(Duration object) { - return com.google.protobuf.Duration.newBuilder() - .setSeconds(object.getSeconds()) - .setNanos(object.getNano()) - .build(); - } - public static com.lunarclient.apollo.common.v1.Location createLocationProto(Location location) { return com.lunarclient.apollo.common.v1.Location.newBuilder() .setWorld(location.getWorld().getName()) diff --git a/docs/developers/lightweight/_meta.json b/docs/developers/lightweight/_meta.json index fac1b38d..88c256cf 100644 --- a/docs/developers/lightweight/_meta.json +++ b/docs/developers/lightweight/_meta.json @@ -1,4 +1,5 @@ { "introduction": "Introduction", - "protobuf": "Protobuf" + "protobuf": "Protobuf", + "json": "JSON" } diff --git a/docs/developers/lightweight/json/_meta.json b/docs/developers/lightweight/json/_meta.json new file mode 100644 index 00000000..38a14154 --- /dev/null +++ b/docs/developers/lightweight/json/_meta.json @@ -0,0 +1,7 @@ +{ + "getting-started": "Getting Started", + "player-detection": "Player Detection", + "packet-util": "Packet Util", + "object-util": "Object Util", + "adventure-util": "Adventure Util" +} diff --git a/docs/developers/lightweight/json/adventure-util.mdx b/docs/developers/lightweight/json/adventure-util.mdx new file mode 100644 index 00000000..2490a34b --- /dev/null +++ b/docs/developers/lightweight/json/adventure-util.mdx @@ -0,0 +1,13 @@ +# Adventure Util + +## Overview + +Provides a single method responsible for serializing the Adventure Component to a String + +## Integration + +```java +public static String toJson(@NonNull Component component) { + return GsonComponentSerializer.gson().serialize(component); +} +``` diff --git a/docs/developers/lightweight/json/getting-started.mdx b/docs/developers/lightweight/json/getting-started.mdx new file mode 100644 index 00000000..b531dfcc --- /dev/null +++ b/docs/developers/lightweight/json/getting-started.mdx @@ -0,0 +1,22 @@ +import { Tab, Tabs } from 'nextra-theme-docs' + +# Getting Started + +## Overview + +This method involves manually constructing JSON objects using a JSON library (e.g., Gson) and then converting these objects into byte arrays for transmission. It allows for dynamic and programmatic construction of JSON objects, offering flexibility in modifying the JSON structure. Available fields for each message, including their types, are available on the Buf Schema Registry at https://buf.build/lunarclient/apollo. +While constructing messages, note that the `@type` field isn't required if the message is being sent within a message. + + + Note that this method uses a different plugin channel for sending packets, + which is `apollo:json`, while still using the `lunar:apollo` for player detection. + + +## Integration Examples + +🔗 [Sending Apollo packets & Enabling Apollo modules](/apollo/developers/lightweight/json/packet-util)
+🔗 [Detecting players using LunarClient](/apollo/developers/lightweight/json/player-detection)
+🔗 [Common Apollo Objects](/apollo/developers/lightweight/json/object-util)
+🔗 [Adventure Util](/apollo/developers/lightweight/json/adventure-util)
+ +TODO mention module examples diff --git a/docs/developers/lightweight/json/object-util.mdx b/docs/developers/lightweight/json/object-util.mdx new file mode 100644 index 00000000..44bca352 --- /dev/null +++ b/docs/developers/lightweight/json/object-util.mdx @@ -0,0 +1,129 @@ +# JSON Object Util + +## Overview + +These utilities facilitate the creation of Apollo objects, commonly used across various Apollo Modules. The utility methods are used for converting objects to and from their corresponding Protocol Buffers represented as a JSON Object. + +## Integration + +```java +public static JsonObject createEnableModuleObjectWithType(@NotNull String module, Map properties) { + JsonObject enableModuleObject = JsonPacketUtil.createEnableModuleObject(module, properties); + enableModuleObject.addProperty("@type", "type.googleapis.com/lunarclient.apollo.configurable.v1.ConfigurableSettings"); + return enableModuleObject; +} + +public static JsonObject createUuidObject(@NotNull UUID uuid) { + JsonObject uuidObject = new JsonObject(); + uuidObject.addProperty("high64", Long.toUnsignedString(uuid.getMostSignificantBits())); + uuidObject.addProperty("low64", Long.toUnsignedString(uuid.getLeastSignificantBits())); + return uuidObject; +} + +public static JsonObject createColorObject(@NotNull Color color) { + JsonObject colorObject = new JsonObject(); + colorObject.addProperty("color", color.getRGB()); + return colorObject; +} + +public static String createDurationObject(@NotNull Duration duration) { + long seconds = duration.getSeconds(); + int nanos = duration.getNano(); + + String durationString; + if (nanos == 0) { + durationString = seconds + "s"; + } else { + durationString = String.format("%d.%09ds", seconds, nanos) + .replaceAll("0+$", "") + .replaceAll("\\.$", ""); + } + + return durationString; +} + +public static JsonObject createCuboid2DObject(double minX, double minZ, double maxX, double maxZ) { + JsonObject cuboid2DObject = new JsonObject(); + cuboid2DObject.addProperty("min_x", minX); + cuboid2DObject.addProperty("min_z", minZ); + cuboid2DObject.addProperty("max_x", maxX); + cuboid2DObject.addProperty("max_z", maxZ); + return cuboid2DObject; +} + +public static JsonObject createEntityIdObject(@NotNull Entity entity) { + JsonObject entityIdObject = new JsonObject(); + entityIdObject.addProperty("entity_id", entity.getEntityId()); + entityIdObject.add("entity_uuid", JsonUtil.createUuidObject(entity.getUniqueId())); + return entityIdObject; +} +``` + +Location-related methods + +```java +public static JsonObject createLocationObject(@NotNull Location location) { + JsonObject locationObject = new JsonObject(); + locationObject.addProperty("world", location.getWorld().getName()); + locationObject.addProperty("x", location.getX()); + locationObject.addProperty("y", location.getY()); + locationObject.addProperty("z", location.getZ()); + return locationObject; +} + +public static JsonObject createBlockLocationObject(@NotNull Location location) { + JsonObject locationObject = new JsonObject(); + locationObject.addProperty("world", location.getWorld().getName()); + locationObject.addProperty("x", location.getBlockX()); + locationObject.addProperty("y", location.getBlockY()); + locationObject.addProperty("z", location.getBlockZ()); + return locationObject; +} +``` + +Icon-related methods + +```java +public static JsonObject createItemStackIconObject(@Nullable String itemName, int itemId, int customModelData) { + JsonObject itemIconObject = new JsonObject(); + if (itemName != null) { + itemIconObject.addProperty("item_name", itemName); + } else { + itemIconObject.addProperty("item_id", itemId); + } + + itemIconObject.addProperty("custom_model_data", customModelData); + + JsonObject iconObject = new JsonObject(); + iconObject.add("item_stack", itemIconObject); + return iconObject; +} + +public static JsonObject createSimpleResourceLocationIconObject(String resourceLocation, int size) { + JsonObject simpleIconObject = new JsonObject(); + simpleIconObject.addProperty("resource_location", resourceLocation); + simpleIconObject.addProperty("size", size); + + JsonObject iconObject = new JsonObject(); + iconObject.add("simple_resource_location", simpleIconObject); + + return iconObject; +} + +public static JsonObject createAdvancedResourceLocationIconObject(String resourceLocation, float width, float height, + float minU, float maxU, float minV, float maxV) { + JsonObject advancedIcon = new JsonObject(); + advancedIcon.addProperty("resource_location", resourceLocation); + advancedIcon.addProperty("width", width); + advancedIcon.addProperty("height", height); + advancedIcon.addProperty("min_u", minU); + advancedIcon.addProperty("max_u", maxU); + advancedIcon.addProperty("min_v", minV); + advancedIcon.addProperty("max_v", maxV); + + JsonObject iconObject = new JsonObject(); + iconObject.add("advanced_resource_location", advancedIcon); + + return iconObject; +} +``` diff --git a/docs/developers/lightweight/json/packet-util.mdx b/docs/developers/lightweight/json/packet-util.mdx new file mode 100644 index 00000000..24ff79ae --- /dev/null +++ b/docs/developers/lightweight/json/packet-util.mdx @@ -0,0 +1,124 @@ +import { Callout } from 'nextra-theme-docs' + +# JSON Packet Util + +## Overview + +These utilities are designed to handle packet transmission and creation for integrating and configuring Apollo modules with their respective properties. The utility class provides a list of most Apollo modules and their configuration options. + +The methods in this utility leverage the same plugin messaging channel as the Apollo API `lunar:apollo`. + +## Integration + +To utilize Apollo Modules, first define a list of the modules you want to use: + +```java +private static final List 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" +); +``` + +Next, specify the properties for these modules. These properties are included in the enable module packet. When using the Apollo plugin, this corresponds to modifying the `config.yml` + + + The example below does not encompass all available options and may not be up-to-date. + For the latest options, refer to the module documentation ([example](/apollo/developers/modules/serverrule#available-options)). + + +```java +// Module Id -> Option key -> Object +private static final Table 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", 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); + CONFIG_MODULE_PROPERTIES.put("server_rule", "disable-chunk-reloading", false); + CONFIG_MODULE_PROPERTIES.put("server_rule", "disable-broadcasting", false); + CONFIG_MODULE_PROPERTIES.put("server_rule", "anti-portal-traps", true); + CONFIG_MODULE_PROPERTIES.put("server_rule", "override-brightness", false); + CONFIG_MODULE_PROPERTIES.put("server_rule", "brightness", 50); + CONFIG_MODULE_PROPERTIES.put("server_rule", "override-nametag-render-distance", false); + CONFIG_MODULE_PROPERTIES.put("server_rule", "nametag-render-distance", 64); + CONFIG_MODULE_PROPERTIES.put("server_rule", "override-max-chat-length", false); + CONFIG_MODULE_PROPERTIES.put("server_rule", "max-chat-length", 256); + CONFIG_MODULE_PROPERTIES.put("tnt_countdown", "tnt-ticks", 80); + CONFIG_MODULE_PROPERTIES.put("waypoint", "server-handles-waypoints", false); +} +``` + +Use the following methods to send packets either to a specific player or to all online players: + +```java +public static void sendPacket(Player player, JsonObject message) { + player.sendPluginMessage(ApolloExamplePlugin.getPlugin(), "apollo:json", message.toString().getBytes()); +} + +public static void broadcastPacket(JsonObject message) { + byte[] data = message.toString().getBytes(); + + Bukkit.getOnlinePlayers().forEach(player -> + player.sendPluginMessage(ApolloExamplePlugin.getPlugin(), "apollo:json", data)); +} +``` + +To create a Module packet, which is used for enabling modules (with or without properties) and for dynamically updating Apollo Options for modules such as the `Mod Settings Module` and `Server Rule Module`, use the following method: + +```java +public static JsonObject createEnableModuleObject(@NotNull String module, Map properties) { + JsonObject enableModuleObject = new JsonObject(); + enableModuleObject.addProperty("apollo_module", module); + enableModuleObject.addProperty("enable", true); + + if (properties != null) { + JsonObject propertiesObject = new JsonObject(); + for (Map.Entry entry : properties.entrySet()) { + propertiesObject.add(entry.getKey(), JsonPacketUtil.convertToJsonElement(entry.getValue())); + } + + enableModuleObject.add("properties", propertiesObject); + } + + return enableModuleObject; +} + +private static JsonElement convertToJsonElement(Object value) { + if (value == null) { + return JsonNull.INSTANCE; + } else if (value instanceof String) { + return new JsonPrimitive((String) value); + } else if (value instanceof Number) { + return new JsonPrimitive((Number) value); + } else if (value instanceof Boolean) { + return new JsonPrimitive((Boolean) value); + } else if (value instanceof List) { + JsonArray jsonArray = new JsonArray(); + for (Object item : (List) value) { + jsonArray.add(convertToJsonElement(item)); + } + return jsonArray; + } + + throw new RuntimeException("Unable to wrap value of type '" + value.getClass().getSimpleName() + "'!"); +} +``` + +Enable modules using the methods and fields defined above: + +```java +public static void enableModules(Player player) { + JsonArray settings = APOLLO_MODULES.stream() + .map(module -> JsonPacketUtil.createEnableModuleObject(module, CONFIG_MODULE_PROPERTIES.row(module))) + .collect(JsonArray::new, JsonArray::add, JsonArray::addAll); + + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.configurable.v1.OverrideConfigurableSettingsMessage"); + message.add("configurable_settings", settings); + + JsonPacketUtil.sendPacket(player, message); +} +``` diff --git a/docs/developers/lightweight/json/player-detection.mdx b/docs/developers/lightweight/json/player-detection.mdx new file mode 100644 index 00000000..04e9a80c --- /dev/null +++ b/docs/developers/lightweight/json/player-detection.mdx @@ -0,0 +1,62 @@ +# Player Detection + +## Overview + +This example demonstrates how to detect whether a player is using Lunar Client by listening for the `PlayerRegisterChannelEvent` on the `lunar:apollo` channel. Additionally, it showcases how to enable Apollo Modules using utility methods from [JsonPacketUtil](/apollo/developers/lightweight/json/packet-util) + + + Note that this method uses a different plugin channel for sending packets, + which is `apollo:json`, while still using the `lunar:apollo` for player detection. + + +## Integration + +```java +public class ApolloPlayerProtoListener implements Listener { + + private final Set playersRunningApollo = new HashSet<>(); + + public ApolloPlayerProtoListener(ApolloExamplePlugin plugin) { + Messenger messenger = Bukkit.getServer().getMessenger(); + messenger.registerIncomingPluginChannel(plugin, "lunar:apollo", (s, player, bytes) -> { }); + messenger.registerIncomingPluginChannel(plugin, "apollo:json", (s, player, bytes) -> { }); + messenger.registerOutgoingPluginChannel(plugin, "apollo:json"); + + Bukkit.getPluginManager().registerEvents(this, plugin); + } + + private boolean isPlayerRunningApollo(Player player) { + return this.playersRunningApollo.contains(player.getUniqueId()); + } + + @EventHandler + private void onRegisterChannel(PlayerRegisterChannelEvent event) { + if (!event.getChannel().equalsIgnoreCase("lunar:apollo")) { + return; + } + + JsonPacketUtil.enableModules(player); + + // Sending the player's world name to the client is required for some modules + JsonPacketUtil.sendPacket(player, this.createUpdatePlayerWorldMessage(player)); + + this.playersRunningApollo.add(player.getUniqueId()); + player.sendMessage("You are using LunarClient!"); + } + + @EventHandler + private void onPlayerChangedWorld(PlayerChangedWorldEvent event) { + Player player = event.getPlayer(); + + // Sending the player's world name to the client is required for some modules + JsonPacketUtil.sendPacket(player, this.createUpdatePlayerWorldMessage(player)); + } + + private JsonObject createUpdatePlayerWorldMessage(Player player) { + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.player.v1.UpdatePlayerWorldMessage"); + message.addProperty("world", player.getWorld().getName()); + return message; + } +} +```