From 08b6bed2fb0b87524be27fc6b7965fcc317e1254 Mon Sep 17 00:00:00 2001 From: ItsNature Date: Thu, 2 Jul 2026 01:58:25 +0200 Subject: [PATCH 1/5] Deploy as `1.2.9-SNAPSHOT` --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 1cecc874..43cc0bbd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=com.lunarclient -version=1.2.8 +version=1.2.9-SNAPSHOT description=The API for interacting with Lunar Client players. org.gradle.parallel=true From 9d25ce46f434d97ebb1704c0ba1f4dbe2140c489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Bu=C4=8Dari=C4=87?= Date: Mon, 10 Aug 2026 23:01:10 +0200 Subject: [PATCH 2/5] Feature - Height Limit Module (#300) * Height Limit Module * Add default height limit config entry, more callouts & update example --- .../module/heightlimit/HeightLimit.java | 70 ++++++ .../module/heightlimit/HeightLimitModule.java | 118 ++++++++++ .../heightlimit/HeightLimitModuleImpl.java | 156 ++++++++++++++ .../PacketEnrichmentImpl.java | 4 + .../module/waypoint/WaypointModuleImpl.java | 4 + docs/developers/lightweight/protobuf.mdx | 6 +- docs/developers/modules.mdx | 2 + docs/developers/modules/_meta.json | 1 + docs/developers/modules/heightlimit.mdx | 202 ++++++++++++++++++ docs/developers/modules/waypoint.mdx | 13 +- .../example/api/ApolloApiExamplePlatform.java | 2 + .../api/module/HeightLimitApiExample.java | 66 ++++++ .../bukkit/api/src/main/resources/plugin.yml | 2 + .../apollo/example/ApolloExamplePlugin.java | 4 + .../example/command/HeightLimitCommand.java | 79 +++++++ .../module/impl/HeightLimitExample.java | 37 ++++ .../json/ApolloJsonExamplePlatform.java | 2 + .../json/module/HeightLimitJsonExample.java | 66 ++++++ .../bukkit/json/src/main/resources/plugin.yml | 2 + .../proto/ApolloProtoExamplePlatform.java | 2 + .../proto/module/HeightLimitProtoExample.java | 66 ++++++ .../proto/src/main/resources/plugin.yml | 2 + .../apollo/common/ApolloComponent.java | 24 +++ gradle/libs.versions.toml | 2 +- .../apollo/ApolloBukkitPlatform.java | 3 + .../apollo/ApolloBungeePlatform.java | 3 + .../apollo/ApolloFoliaPlatform.java | 3 + .../apollo/ApolloMinestomPlatform.java | 3 + .../apollo/ApolloVelocityPlatform.java | 3 + 29 files changed, 942 insertions(+), 5 deletions(-) create mode 100644 api/src/main/java/com/lunarclient/apollo/module/heightlimit/HeightLimit.java create mode 100644 api/src/main/java/com/lunarclient/apollo/module/heightlimit/HeightLimitModule.java create mode 100644 common/src/main/java/com/lunarclient/apollo/module/heightlimit/HeightLimitModuleImpl.java create mode 100644 docs/developers/modules/heightlimit.mdx create mode 100644 example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/HeightLimitApiExample.java create mode 100644 example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/HeightLimitCommand.java create mode 100644 example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/HeightLimitExample.java create mode 100644 example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/HeightLimitJsonExample.java create mode 100644 example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/HeightLimitProtoExample.java diff --git a/api/src/main/java/com/lunarclient/apollo/module/heightlimit/HeightLimit.java b/api/src/main/java/com/lunarclient/apollo/module/heightlimit/HeightLimit.java new file mode 100644 index 00000000..78035f57 --- /dev/null +++ b/api/src/main/java/com/lunarclient/apollo/module/heightlimit/HeightLimit.java @@ -0,0 +1,70 @@ +/* + * This file is part of Apollo, licensed under the MIT License. + * + * Copyright (c) 2026 Moonsworth + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.lunarclient.apollo.module.heightlimit; + +import lombok.Builder; +import lombok.Getter; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Range; + +/** + * Represents a height limit which can be shown on the client. + * + * @since 1.2.9 + */ +@Getter +@Builder +public final class HeightLimit { + + /** + * Returns the height limit {@link String} world name. + * + * @return the height limit world name + * @since 1.2.9 + */ + String world; + + /** + * Returns the height limit {@link Integer} Y level where block placement + * is denied. + * + *

The highest buildable layer is {@code limit - 1}.

+ * + * @return the height limit + * @since 1.2.9 + */ + @Range(from = 1, to = Integer.MAX_VALUE) int limit; + + /** + * Returns the height limit {@link Component} display name. + * + *

Shown on the client's height limit HUD.

+ * + * @return the height limit display name + * @since 1.2.9 + */ + @Nullable Component displayName; + +} diff --git a/api/src/main/java/com/lunarclient/apollo/module/heightlimit/HeightLimitModule.java b/api/src/main/java/com/lunarclient/apollo/module/heightlimit/HeightLimitModule.java new file mode 100644 index 00000000..86431321 --- /dev/null +++ b/api/src/main/java/com/lunarclient/apollo/module/heightlimit/HeightLimitModule.java @@ -0,0 +1,118 @@ +/* + * This file is part of Apollo, licensed under the MIT License. + * + * Copyright (c) 2026 Moonsworth + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.lunarclient.apollo.module.heightlimit; + +import com.lunarclient.apollo.module.ApolloModule; +import com.lunarclient.apollo.module.ModuleDefinition; +import com.lunarclient.apollo.option.ListOption; +import com.lunarclient.apollo.option.Option; +import com.lunarclient.apollo.recipients.Recipients; +import io.leangen.geantyref.TypeToken; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.jetbrains.annotations.ApiStatus; + +/** + * Represents the height limit module. + * + *

Sets the build-height limit shown by the clients Height Limit mod + * (block overlay and HUD display). The client resolves the active limit + * against the world the player is currently in.

+ * + *

This module only provides a visual indicator for the player. The + * server is still responsible for cancelling block placement above the + * height limit.

+ * + * @since 1.2.9 + */ +@ApiStatus.NonExtendable +@ModuleDefinition(id = "height_limit", name = "Height Limit") +public abstract class HeightLimitModule extends ApolloModule { + + private static final HeightLimit OVERWORLD_HEIGHT_LIMIT = HeightLimit.builder() + .world("world") + .limit(200) + .displayName(Component.text("Overworld", NamedTextColor.GOLD)) + .build(); + + /** + * Returns the default list of height limits to send to the player. + * + * @since 1.2.9 + */ + public static final ListOption DEFAULT_HEIGHT_LIMITS = Option.list() + .comment("Sets the default height limits to send to the player.") + .node("default-height-limits").type(new TypeToken>() {}) + .defaultValue(new ArrayList<>(Collections.singletonList(HeightLimitModule.OVERWORLD_HEIGHT_LIMIT))) + .build(); + + protected HeightLimitModule() { + this.registerOptions( + ApolloModule.ENABLE_OPTION_OFF, + HeightLimitModule.DEFAULT_HEIGHT_LIMITS + ); + } + + /** + * Overrides the {@link HeightLimit} for the {@link Recipients}. + * + *

Sending a height limit for an already-known world replaces + * that worlds entry.

+ * + * @param recipients the recipients that are receiving the packet + * @param heightLimit the height limit + * @since 1.2.9 + */ + public abstract void overrideHeightLimit(Recipients recipients, HeightLimit heightLimit); + + /** + * Removes the {@link HeightLimit} from the {@link Recipients}. + * + * @param recipients the recipients that are receiving the packet + * @param world the world name + * @since 1.2.9 + */ + public abstract void removeHeightLimit(Recipients recipients, String world); + + /** + * Removes the {@link HeightLimit} from the {@link Recipients}. + * + * @param recipients the recipients that are receiving the packet + * @param heightLimit the height limit + * @since 1.2.9 + */ + public abstract void removeHeightLimit(Recipients recipients, HeightLimit heightLimit); + + /** + * Resets all {@link HeightLimit}s for the {@link Recipients}. + * + * @param recipients the recipients that are receiving the packet + * @since 1.2.9 + */ + public abstract void resetHeightLimits(Recipients recipients); + +} diff --git a/common/src/main/java/com/lunarclient/apollo/module/heightlimit/HeightLimitModuleImpl.java b/common/src/main/java/com/lunarclient/apollo/module/heightlimit/HeightLimitModuleImpl.java new file mode 100644 index 00000000..e8887aed --- /dev/null +++ b/common/src/main/java/com/lunarclient/apollo/module/heightlimit/HeightLimitModuleImpl.java @@ -0,0 +1,156 @@ +/* + * This file is part of Apollo, licensed under the MIT License. + * + * Copyright (c) 2026 Moonsworth + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.lunarclient.apollo.module.heightlimit; + +import com.lunarclient.apollo.ApolloManager; +import com.lunarclient.apollo.common.ApolloComponent; +import com.lunarclient.apollo.event.player.ApolloRegisterPlayerEvent; +import com.lunarclient.apollo.heightlimit.v1.OverrideHeightLimitMessage; +import com.lunarclient.apollo.heightlimit.v1.RemoveHeightLimitMessage; +import com.lunarclient.apollo.heightlimit.v1.ResetHeightLimitsMessage; +import com.lunarclient.apollo.option.config.Serializer; +import com.lunarclient.apollo.player.ApolloPlayer; +import com.lunarclient.apollo.recipients.Recipients; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.List; +import lombok.NonNull; +import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.serialize.SerializationException; +import org.spongepowered.configurate.serialize.TypeSerializer; + +import static com.lunarclient.apollo.util.Ranges.checkStrictlyPositive; + +/** + * Provides the height limit module. + * + * @since 1.2.9 + */ +public final class HeightLimitModuleImpl extends HeightLimitModule implements Serializer { + + /** + * Creates a new instance of {@link HeightLimitModuleImpl}. + * + * @since 1.2.9 + */ + public HeightLimitModuleImpl() { + super(); + this.serializer(HeightLimit.class, new HeightLimitSerializer()); + this.handle(ApolloRegisterPlayerEvent.class, this::onPlayerRegister); + } + + @Override + public void overrideHeightLimit(@NonNull Recipients recipients, @NonNull HeightLimit heightLimit) { + OverrideHeightLimitMessage.Builder builder = OverrideHeightLimitMessage.newBuilder() + .setWorld(heightLimit.getWorld()) + .setLimit(checkStrictlyPositive(heightLimit.getLimit(), "HeightLimit#limit")); + + Component displayName = heightLimit.getDisplayName(); + if (displayName != null) { + builder.setDisplayNameAdventureJsonLines(ApolloComponent.toJson(displayName)); + } + + OverrideHeightLimitMessage message = builder.build(); + ApolloManager.getNetworkManager().sendPacket(recipients, message); + } + + @Override + public void removeHeightLimit(@NonNull Recipients recipients, @NonNull String world) { + RemoveHeightLimitMessage message = RemoveHeightLimitMessage.newBuilder() + .setWorld(world) + .build(); + + ApolloManager.getNetworkManager().sendPacket(recipients, message); + } + + @Override + public void removeHeightLimit(@NonNull Recipients recipients, @NonNull HeightLimit heightLimit) { + this.removeHeightLimit(recipients, heightLimit.getWorld()); + } + + @Override + public void resetHeightLimits(@NonNull Recipients recipients) { + ResetHeightLimitsMessage message = ResetHeightLimitsMessage.getDefaultInstance(); + ApolloManager.getNetworkManager().sendPacket(recipients, message); + } + + private void onPlayerRegister(ApolloRegisterPlayerEvent event) { + if (!this.isEnabled()) { + return; + } + + ApolloPlayer player = event.getPlayer(); + List heightLimits = this.getOptions().get(player, HeightLimitModule.DEFAULT_HEIGHT_LIMITS); + + if (heightLimits != null) { + for (HeightLimit heightLimit : heightLimits) { + this.overrideHeightLimit(player, heightLimit); + } + } + } + + private static final class HeightLimitSerializer implements TypeSerializer { + + @Override + public HeightLimit deserialize(Type type, ConfigurationNode node) throws SerializationException { + HeightLimit.HeightLimitBuilder builder = HeightLimit.builder() + .world(this.virtualNode(node, "world").getString()) + .limit(this.virtualNode(node, "limit").getInt()); + + String displayName = node.node("display-name").getString(); + if (displayName != null) { + builder.displayName(ApolloComponent.fromLegacyAmpersand(displayName)); + } + + return builder.build(); + } + + @Override + public void serialize(Type type, @Nullable HeightLimit heightLimit, ConfigurationNode node) throws SerializationException { + if (heightLimit == null) { + node.raw(null); + return; + } + + node.node("world").set(heightLimit.getWorld()); + node.node("limit").set(heightLimit.getLimit()); + + Component displayName = heightLimit.getDisplayName(); + if (displayName != null) { + node.node("display-name").set(ApolloComponent.toLegacyAmpersand(displayName)); + } + } + + private ConfigurationNode virtualNode(ConfigurationNode source, Object... path) throws SerializationException { + if (!source.hasChild(path)) { + throw new SerializationException("Required field " + Arrays.toString(path) + " not found!"); + } + + return source.node(path); + } + } + +} diff --git a/common/src/main/java/com/lunarclient/apollo/module/packetenrichment/PacketEnrichmentImpl.java b/common/src/main/java/com/lunarclient/apollo/module/packetenrichment/PacketEnrichmentImpl.java index 1446ef11..cbc97f0d 100644 --- a/common/src/main/java/com/lunarclient/apollo/module/packetenrichment/PacketEnrichmentImpl.java +++ b/common/src/main/java/com/lunarclient/apollo/module/packetenrichment/PacketEnrichmentImpl.java @@ -56,6 +56,10 @@ public PacketEnrichmentImpl() { } private void onReceivePacket(ApolloReceivePacketEvent event) { + if (!this.isEnabled()) { + return; + } + Options options = this.getOptions(); if (options.get(PacketEnrichmentModule.PLAYER_ATTACK_EVENT)) { diff --git a/common/src/main/java/com/lunarclient/apollo/module/waypoint/WaypointModuleImpl.java b/common/src/main/java/com/lunarclient/apollo/module/waypoint/WaypointModuleImpl.java index b9ed8e3b..320eab67 100644 --- a/common/src/main/java/com/lunarclient/apollo/module/waypoint/WaypointModuleImpl.java +++ b/common/src/main/java/com/lunarclient/apollo/module/waypoint/WaypointModuleImpl.java @@ -110,6 +110,10 @@ public void hideWaypoint(@NonNull Recipients recipients, @NonNull String waypoin } private void onPlayerRegister(ApolloRegisterPlayerEvent event) { + if (!this.isEnabled()) { + return; + } + ApolloPlayer player = event.getPlayer(); List waypoints = this.getOptions().get(player, WaypointModule.DEFAULT_WAYPOINTS); diff --git a/docs/developers/lightweight/protobuf.mdx b/docs/developers/lightweight/protobuf.mdx index f39b9e45..2759440a 100644 --- a/docs/developers/lightweight/protobuf.mdx +++ b/docs/developers/lightweight/protobuf.mdx @@ -26,7 +26,7 @@ Available fields for each message, including their types, are available on the B com.lunarclient apollo-protos - 0.2.0 + 0.2.1 ``` @@ -41,7 +41,7 @@ Available fields for each message, including their types, are available on the B } dependencies { - api 'com.lunarclient:apollo-protos:0.2.0' + api 'com.lunarclient:apollo-protos:0.2.1' } ``` @@ -55,7 +55,7 @@ Available fields for each message, including their types, are available on the B } dependencies { - api("com.lunarclient:apollo-protos:0.2.0") + api("com.lunarclient:apollo-protos:0.2.1") } ``` diff --git a/docs/developers/modules.mdx b/docs/developers/modules.mdx index 16803d54..1d8f8fc7 100644 --- a/docs/developers/modules.mdx +++ b/docs/developers/modules.mdx @@ -16,9 +16,11 @@ These modules are available to all servers using Apollo and do not require any p 🔗 [Entity](/apollo/developers/modules/entity)
🔗 [Glint](/apollo/developers/modules/glint)
🔗 [Glow](/apollo/developers/modules/glow)
+🔗 [Height Limit](/apollo/developers/modules/heightlimit)
🔗 [Hologram](/apollo/developers/modules/hologram)
🔗 [Inventory](/apollo/developers/modules/inventory)
🔗 [Limb](/apollo/developers/modules/limb)
+🔗 [Marker](/apollo/developers/modules/marker)
🔗 [Mod Setting](/apollo/developers/modules/modsetting)
🔗 [Nametag](/apollo/developers/modules/nametag)
🔗 [Nick Hider](/apollo/developers/modules/nickhider)
diff --git a/docs/developers/modules/_meta.json b/docs/developers/modules/_meta.json index 182a8a35..11e872d0 100644 --- a/docs/developers/modules/_meta.json +++ b/docs/developers/modules/_meta.json @@ -9,6 +9,7 @@ "entity": "Entity", "glint": "Glint", "glow": "Glow", + "heightlimit": "Height Limit", "hologram": "Hologram", "inventory": "Inventory", "limb": "Limb", diff --git a/docs/developers/modules/heightlimit.mdx b/docs/developers/modules/heightlimit.mdx new file mode 100644 index 00000000..b22148dd --- /dev/null +++ b/docs/developers/modules/heightlimit.mdx @@ -0,0 +1,202 @@ +import { Callout, Tab, Tabs } from 'nextra-theme-docs' + +# Height Limit Module + +## Overview + +The height limit module allows servers to control the build-height limit displayed by the Height Limit mod. + +- Adds the ability to set a height limit per world, rendered as a block overlay and HUD display. + + + This module is disabled by default, if you wish to use this module you will need to enable it in `config.yml`. + + + + This module only provides a visual indicator for the player, the server is still responsible for cancelling block placement above the height limit. + + +## Integration + +### Sample Code +Explore each integration by cycling through each tab, to find the best fit for your requirements and needs. + + + + + + +**Apollo API examples.** See [General](/apollo/developers/general) for common patterns and helpers. + + +### Overriding a Height Limit + +```java +public void overrideHeightLimitExample(Player viewer) { + Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId()); + + apolloPlayerOpt.ifPresent(apolloPlayer -> { + this.heightLimitModule.overrideHeightLimit(apolloPlayer, HeightLimit.builder() + .world("world_the_end") + .limit(150) + .displayName(Component.text("The End", NamedTextColor.DARK_PURPLE)) + .build() + ); + }); +} +``` + +### Removing a Height Limit + +```java +public void removeHeightLimitExample(Player viewer) { + Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId()); + apolloPlayerOpt.ifPresent(apolloPlayer -> this.heightLimitModule.removeHeightLimit(apolloPlayer, "world_the_end")); +} +``` + +### Resetting all Height Limits + +```java +public void resetHeightLimitsExample(Player viewer) { + Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId()); + apolloPlayerOpt.ifPresent(this.heightLimitModule::resetHeightLimits); +} +``` + +### `HeightLimit` Options + +`.world(String)` is the world, by name, that you wish to apply the height limit to. Sending a height limit for an already-known world replaces that worlds entry. + +```java +.world("world_the_end") +``` + +`.limit(Integer)` is the Y level where block placement is denied. The highest buildable layer is `limit - 1`. + +```java +.limit(150) +``` + +`.displayName(Component)` is the optional display name shown on the clients height limit HUD. + +```java +.displayName(Component.text("The End", NamedTextColor.DARK_PURPLE)) +``` + + + + + + +**Lightweight Protobuf examples.** See [Lightweight Protobuf](/apollo/developers/lightweight/protobuf) for setup. + + + + Make sure the server is sending the world name to the client as show in the [Player Detection](/apollo/developers/lightweight/protobuf/player-detection) example. + + +**Overriding a Height Limit** + +```java +public void overrideHeightLimitExample(Player viewer) { + OverrideHeightLimitMessage message = OverrideHeightLimitMessage.newBuilder() + .setWorld("world_the_end") + .setLimit(150) + .setDisplayNameAdventureJsonLines(AdventureUtil.toJson( + Component.text("The End", NamedTextColor.DARK_PURPLE) + )) + .build(); + + ProtobufPacketUtil.sendPacket(viewer, message); +} +``` + +**Removing a Height Limit** + +```java +public void removeHeightLimitExample(Player viewer) { + RemoveHeightLimitMessage message = RemoveHeightLimitMessage.newBuilder() + .setWorld("world_the_end") + .build(); + + ProtobufPacketUtil.sendPacket(viewer, message); +} +``` + +**Resetting all Height Limits** + +```java +public void resetHeightLimitsExample(Player viewer) { + ResetHeightLimitsMessage message = ResetHeightLimitsMessage.getDefaultInstance(); + ProtobufPacketUtil.sendPacket(viewer, message); +} +``` + + + + + + +**Lightweight JSON examples.** See [Lightweight JSON](/apollo/developers/lightweight/json) for setup. + + + + Make sure the server is sending the world name to the client as show in the [Player Detection](/apollo/developers/lightweight/json/player-detection) example. + + +**Overriding a Height Limit** + +```java +public void overrideHeightLimitExample(Player viewer) { + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.heightlimit.v1.OverrideHeightLimitMessage"); + message.addProperty("world", "world_the_end"); + message.addProperty("limit", 150); + message.addProperty("display_name_adventure_json_lines", AdventureUtil.toJson( + Component.text("The End", NamedTextColor.DARK_PURPLE) + )); + + JsonPacketUtil.sendPacket(viewer, message); +} +``` + +**Removing a Height Limit** + +```java +public void removeHeightLimitExample(Player viewer) { + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.heightlimit.v1.RemoveHeightLimitMessage"); + message.addProperty("world", "world_the_end"); + + JsonPacketUtil.sendPacket(viewer, message); +} +``` + +**Resetting all Height Limits** + +```java +public void resetHeightLimitsExample(Player viewer) { + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.heightlimit.v1.ResetHeightLimitsMessage"); + + JsonPacketUtil.sendPacket(viewer, message); +} +``` + + + + + +## Available options + +- __`DEFAULT_HEIGHT_LIMITS`__ + - Sets the default height limits to send to the player. + - Values + - Type: `List` + - Default: + ```yaml + - world: world + limit: 200 + display-name: '&6Overworld' + ``` diff --git a/docs/developers/modules/waypoint.mdx b/docs/developers/modules/waypoint.mdx index d693cd68..78642822 100644 --- a/docs/developers/modules/waypoint.mdx +++ b/docs/developers/modules/waypoint.mdx @@ -433,7 +433,18 @@ public void resetWaypointsExample(Player viewer) { - Sets the default waypoints to send to the player. - Values - Type: `List` - - Default: `Empty List` + - Default: + ```yaml + - name: Spawn + location: + world: world + x: 0 + y: 100 + z: 0 + color: '#FF0000' + prevent-removal: false + hidden: false + ``` ## Automatic Waypoint Creation from Chat diff --git a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/ApolloApiExamplePlatform.java b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/ApolloApiExamplePlatform.java index 2ae26a66..ffdc2396 100644 --- a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/ApolloApiExamplePlatform.java +++ b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/ApolloApiExamplePlatform.java @@ -40,6 +40,7 @@ import com.lunarclient.apollo.example.api.module.CosmeticApiExample; import com.lunarclient.apollo.example.api.module.EntityApiExample; import com.lunarclient.apollo.example.api.module.GlowApiExample; +import com.lunarclient.apollo.example.api.module.HeightLimitApiExample; import com.lunarclient.apollo.example.api.module.HologramApiExample; import com.lunarclient.apollo.example.api.module.LimbApiExample; import com.lunarclient.apollo.example.api.module.MarkerApiExample; @@ -93,6 +94,7 @@ public void registerModuleExamples() { this.setCooldownExample(new CooldownApiExample()); this.setEntityExample(new EntityApiExample()); this.setGlowExample(new GlowApiExample()); + this.setHeightLimitExample(new HeightLimitApiExample()); this.setHologramExample(new HologramApiExample()); this.setLimbExample(new LimbApiExample()); this.setMarkerExample(new MarkerApiExample()); diff --git a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/HeightLimitApiExample.java b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/HeightLimitApiExample.java new file mode 100644 index 00000000..39955281 --- /dev/null +++ b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/HeightLimitApiExample.java @@ -0,0 +1,66 @@ +/* + * This file is part of Apollo, licensed under the MIT License. + * + * Copyright (c) 2026 Moonsworth + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.lunarclient.apollo.example.api.module; + +import com.lunarclient.apollo.Apollo; +import com.lunarclient.apollo.example.module.impl.HeightLimitExample; +import com.lunarclient.apollo.module.heightlimit.HeightLimit; +import com.lunarclient.apollo.module.heightlimit.HeightLimitModule; +import com.lunarclient.apollo.player.ApolloPlayer; +import java.util.Optional; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; + +public class HeightLimitApiExample extends HeightLimitExample { + + private final HeightLimitModule heightLimitModule = Apollo.getModuleManager().getModule(HeightLimitModule.class); + + @Override + public void overrideHeightLimitExample(Player viewer) { + Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId()); + + apolloPlayerOpt.ifPresent(apolloPlayer -> { + this.heightLimitModule.overrideHeightLimit(apolloPlayer, HeightLimit.builder() + .world("world_the_end") + .limit(150) + .displayName(Component.text("The End", NamedTextColor.DARK_PURPLE)) + .build() + ); + }); + } + + @Override + public void removeHeightLimitExample(Player viewer) { + Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId()); + apolloPlayerOpt.ifPresent(apolloPlayer -> this.heightLimitModule.removeHeightLimit(apolloPlayer, "world_the_end")); + } + + @Override + public void resetHeightLimitsExample(Player viewer) { + Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId()); + apolloPlayerOpt.ifPresent(this.heightLimitModule::resetHeightLimits); + } + +} diff --git a/example/bukkit/api/src/main/resources/plugin.yml b/example/bukkit/api/src/main/resources/plugin.yml index 9ba142c0..c16d024b 100644 --- a/example/bukkit/api/src/main/resources/plugin.yml +++ b/example/bukkit/api/src/main/resources/plugin.yml @@ -36,6 +36,8 @@ commands: description: "Glint!" glow: description: "Glow!" + heightlimit: + description: "Height Limit!" hologram: description: "Holograms!" inventory: diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/ApolloExamplePlugin.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/ApolloExamplePlugin.java index c39d80f2..94092492 100644 --- a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/ApolloExamplePlugin.java +++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/ApolloExamplePlugin.java @@ -34,6 +34,7 @@ import com.lunarclient.apollo.example.command.EntityCommand; import com.lunarclient.apollo.example.command.GlintCommand; import com.lunarclient.apollo.example.command.GlowCommand; +import com.lunarclient.apollo.example.command.HeightLimitCommand; import com.lunarclient.apollo.example.command.HologramCommand; import com.lunarclient.apollo.example.command.InventoryCommand; import com.lunarclient.apollo.example.command.LimbCommand; @@ -68,6 +69,7 @@ import com.lunarclient.apollo.example.module.impl.EntityExample; import com.lunarclient.apollo.example.module.impl.GlintExample; import com.lunarclient.apollo.example.module.impl.GlowExample; +import com.lunarclient.apollo.example.module.impl.HeightLimitExample; import com.lunarclient.apollo.example.module.impl.HologramExample; import com.lunarclient.apollo.example.module.impl.InventoryExample; import com.lunarclient.apollo.example.module.impl.LimbExample; @@ -114,6 +116,7 @@ public abstract class ApolloExamplePlugin extends JavaPlugin { private EntityExample entityExample; private GlintExample glintExample; private GlowExample glowExample; + private HeightLimitExample heightLimitExample; private HologramExample hologramExample; private InventoryExample inventoryExample; private LimbExample limbExample; @@ -173,6 +176,7 @@ private void registerCommonCommands() { this.getCommand("entity").setExecutor(new EntityCommand()); this.getCommand("glint").setExecutor(new GlintCommand()); this.getCommand("glow").setExecutor(new GlowCommand()); + this.getCommand("heightlimit").setExecutor(new HeightLimitCommand()); this.getCommand("hologram").setExecutor(new HologramCommand()); this.getCommand("inventory").setExecutor(new InventoryCommand()); this.getCommand("limb").setExecutor(new LimbCommand()); diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/HeightLimitCommand.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/HeightLimitCommand.java new file mode 100644 index 00000000..7a4a3e68 --- /dev/null +++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/HeightLimitCommand.java @@ -0,0 +1,79 @@ +/* + * This file is part of Apollo, licensed under the MIT License. + * + * Copyright (c) 2026 Moonsworth + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.lunarclient.apollo.example.command; + +import com.lunarclient.apollo.example.ApolloExamplePlugin; +import com.lunarclient.apollo.example.module.impl.HeightLimitExample; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public class HeightLimitCommand implements CommandExecutor { + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage("Player only!"); + return true; + } + + Player player = (Player) sender; + + if (args.length != 1) { + player.sendMessage("Usage: /heightlimit "); + return true; + } + + HeightLimitExample heightLimitExample = ApolloExamplePlugin.getInstance().getHeightLimitExample(); + + switch (args[0].toLowerCase()) { + case "override": { + heightLimitExample.overrideHeightLimitExample(player); + player.sendMessage("Overriding height limit...."); + break; + } + + case "remove": { + heightLimitExample.removeHeightLimitExample(player); + player.sendMessage("Removing height limit...."); + break; + } + + case "reset": { + heightLimitExample.resetHeightLimitsExample(player); + player.sendMessage("Resetting height limits..."); + break; + } + + default: { + player.sendMessage("Usage: /heightlimit "); + break; + } + } + + return true; + } +} diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/HeightLimitExample.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/HeightLimitExample.java new file mode 100644 index 00000000..9246625f --- /dev/null +++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/HeightLimitExample.java @@ -0,0 +1,37 @@ +/* + * This file is part of Apollo, licensed under the MIT License. + * + * Copyright (c) 2026 Moonsworth + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.lunarclient.apollo.example.module.impl; + +import com.lunarclient.apollo.example.module.ApolloModuleExample; +import org.bukkit.entity.Player; + +public abstract class HeightLimitExample extends ApolloModuleExample { + + public abstract void overrideHeightLimitExample(Player viewer); + + public abstract void removeHeightLimitExample(Player viewer); + + public abstract void resetHeightLimitsExample(Player viewer); + +} diff --git a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/ApolloJsonExamplePlatform.java b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/ApolloJsonExamplePlatform.java index 391c46d7..4ffba9cd 100644 --- a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/ApolloJsonExamplePlatform.java +++ b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/ApolloJsonExamplePlatform.java @@ -36,6 +36,7 @@ import com.lunarclient.apollo.example.json.module.CosmeticJsonExample; import com.lunarclient.apollo.example.json.module.EntityJsonExample; import com.lunarclient.apollo.example.json.module.GlowJsonExample; +import com.lunarclient.apollo.example.json.module.HeightLimitJsonExample; import com.lunarclient.apollo.example.json.module.HologramJsonExample; import com.lunarclient.apollo.example.json.module.LimbJsonExample; import com.lunarclient.apollo.example.json.module.MarkerJsonExample; @@ -81,6 +82,7 @@ public void registerModuleExamples() { this.setCooldownExample(new CooldownJsonExample()); this.setEntityExample(new EntityJsonExample()); this.setGlowExample(new GlowJsonExample()); + this.setHeightLimitExample(new HeightLimitJsonExample()); this.setHologramExample(new HologramJsonExample()); this.setLimbExample(new LimbJsonExample()); this.setMarkerExample(new MarkerJsonExample()); diff --git a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/HeightLimitJsonExample.java b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/HeightLimitJsonExample.java new file mode 100644 index 00000000..37f8e89d --- /dev/null +++ b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/HeightLimitJsonExample.java @@ -0,0 +1,66 @@ +/* + * This file is part of Apollo, licensed under the MIT License. + * + * Copyright (c) 2026 Moonsworth + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.lunarclient.apollo.example.json.module; + +import com.google.gson.JsonObject; +import com.lunarclient.apollo.example.json.util.AdventureUtil; +import com.lunarclient.apollo.example.json.util.JsonPacketUtil; +import com.lunarclient.apollo.example.module.impl.HeightLimitExample; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; + +public class HeightLimitJsonExample extends HeightLimitExample { + + @Override + public void overrideHeightLimitExample(Player viewer) { + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.heightlimit.v1.OverrideHeightLimitMessage"); + message.addProperty("world", "world_the_end"); + message.addProperty("limit", 150); + message.addProperty("display_name_adventure_json_lines", AdventureUtil.toJson( + Component.text("The End", NamedTextColor.DARK_PURPLE) + )); + + JsonPacketUtil.sendPacket(viewer, message); + } + + @Override + public void removeHeightLimitExample(Player viewer) { + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.heightlimit.v1.RemoveHeightLimitMessage"); + message.addProperty("world", "world_the_end"); + + JsonPacketUtil.sendPacket(viewer, message); + } + + @Override + public void resetHeightLimitsExample(Player viewer) { + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.heightlimit.v1.ResetHeightLimitsMessage"); + + JsonPacketUtil.sendPacket(viewer, message); + } + +} diff --git a/example/bukkit/json/src/main/resources/plugin.yml b/example/bukkit/json/src/main/resources/plugin.yml index 334fe2a4..e92e59a4 100644 --- a/example/bukkit/json/src/main/resources/plugin.yml +++ b/example/bukkit/json/src/main/resources/plugin.yml @@ -32,6 +32,8 @@ commands: description: "Glint!" glow: description: "Glow!" + heightlimit: + description: "Height Limit!" hologram: description: "Holograms!" inventory: diff --git a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/ApolloProtoExamplePlatform.java b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/ApolloProtoExamplePlatform.java index 94578ad4..5c0b2a5c 100644 --- a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/ApolloProtoExamplePlatform.java +++ b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/ApolloProtoExamplePlatform.java @@ -36,6 +36,7 @@ import com.lunarclient.apollo.example.proto.module.CosmeticProtoExample; import com.lunarclient.apollo.example.proto.module.EntityProtoExample; import com.lunarclient.apollo.example.proto.module.GlowProtoExample; +import com.lunarclient.apollo.example.proto.module.HeightLimitProtoExample; import com.lunarclient.apollo.example.proto.module.HologramProtoExample; import com.lunarclient.apollo.example.proto.module.LimbProtoExample; import com.lunarclient.apollo.example.proto.module.MarkerProtoExample; @@ -81,6 +82,7 @@ public void registerModuleExamples() { this.setCooldownExample(new CooldownProtoExample()); this.setEntityExample(new EntityProtoExample()); this.setGlowExample(new GlowProtoExample()); + this.setHeightLimitExample(new HeightLimitProtoExample()); this.setHologramExample(new HologramProtoExample()); this.setLimbExample(new LimbProtoExample()); this.setMarkerExample(new MarkerProtoExample()); diff --git a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/HeightLimitProtoExample.java b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/HeightLimitProtoExample.java new file mode 100644 index 00000000..babbce5b --- /dev/null +++ b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/HeightLimitProtoExample.java @@ -0,0 +1,66 @@ +/* + * This file is part of Apollo, licensed under the MIT License. + * + * Copyright (c) 2026 Moonsworth + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.lunarclient.apollo.example.proto.module; + +import com.lunarclient.apollo.example.module.impl.HeightLimitExample; +import com.lunarclient.apollo.example.proto.util.AdventureUtil; +import com.lunarclient.apollo.example.proto.util.ProtobufPacketUtil; +import com.lunarclient.apollo.heightlimit.v1.OverrideHeightLimitMessage; +import com.lunarclient.apollo.heightlimit.v1.RemoveHeightLimitMessage; +import com.lunarclient.apollo.heightlimit.v1.ResetHeightLimitsMessage; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; + +public class HeightLimitProtoExample extends HeightLimitExample { + + @Override + public void overrideHeightLimitExample(Player viewer) { + OverrideHeightLimitMessage message = OverrideHeightLimitMessage.newBuilder() + .setWorld("world_the_end") + .setLimit(150) + .setDisplayNameAdventureJsonLines(AdventureUtil.toJson( + Component.text("The End", NamedTextColor.DARK_PURPLE) + )) + .build(); + + ProtobufPacketUtil.sendPacket(viewer, message); + } + + @Override + public void removeHeightLimitExample(Player viewer) { + RemoveHeightLimitMessage message = RemoveHeightLimitMessage.newBuilder() + .setWorld("world_the_end") + .build(); + + ProtobufPacketUtil.sendPacket(viewer, message); + } + + @Override + public void resetHeightLimitsExample(Player viewer) { + ResetHeightLimitsMessage message = ResetHeightLimitsMessage.getDefaultInstance(); + ProtobufPacketUtil.sendPacket(viewer, message); + } + +} diff --git a/example/bukkit/proto/src/main/resources/plugin.yml b/example/bukkit/proto/src/main/resources/plugin.yml index f74f917e..468f5d87 100644 --- a/example/bukkit/proto/src/main/resources/plugin.yml +++ b/example/bukkit/proto/src/main/resources/plugin.yml @@ -32,6 +32,8 @@ commands: description: "Glint!" glow: description: "Glow!" + heightlimit: + description: "Height Limit!" hologram: description: "Holograms!" inventory: diff --git a/extra/adventure4/src/main/java/com/lunarclient/apollo/common/ApolloComponent.java b/extra/adventure4/src/main/java/com/lunarclient/apollo/common/ApolloComponent.java index 93c11e7a..04176482 100644 --- a/extra/adventure4/src/main/java/com/lunarclient/apollo/common/ApolloComponent.java +++ b/extra/adventure4/src/main/java/com/lunarclient/apollo/common/ApolloComponent.java @@ -68,6 +68,30 @@ public static String toLegacy(@NonNull Component component) { return LegacyComponentSerializer.legacySection().serialize(component); } + /** + * Returns a new component from the provided legacy {@link String}, + * using {@code &} color codes. + * + * @param legacy the legacy string for this component + * @return the component from the legacy string + * @since 1.2.9 + */ + public static Component fromLegacyAmpersand(@NonNull String legacy) { + return LegacyComponentSerializer.legacyAmpersand().deserialize(legacy); + } + + /** + * Returns this component as a legacy {@link String}, + * using {@code &} color codes. + * + * @param component the component to make into a legacy string + * @return the legacy string for this component + * @since 1.2.9 + */ + public static String toLegacyAmpersand(@NonNull Component component) { + return LegacyComponentSerializer.legacyAmpersand().serialize(component); + } + private ApolloComponent() { } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d83b4d64..8f52700a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ geantyref = "1.3.11" idea = "1.1.7" jetbrains = "24.0.1" lombok = "1.18.38" -protobuf = "0.2.0" +protobuf = "0.2.1" gson = "2.10.1" shadow = "9.4.1" spotless = "8.4.0" diff --git a/platform/bukkit/src/main/java/com/lunarclient/apollo/ApolloBukkitPlatform.java b/platform/bukkit/src/main/java/com/lunarclient/apollo/ApolloBukkitPlatform.java index 6de2e988..c485822d 100644 --- a/platform/bukkit/src/main/java/com/lunarclient/apollo/ApolloBukkitPlatform.java +++ b/platform/bukkit/src/main/java/com/lunarclient/apollo/ApolloBukkitPlatform.java @@ -50,6 +50,8 @@ import com.lunarclient.apollo.module.glint.GlintModule; import com.lunarclient.apollo.module.glow.GlowModule; import com.lunarclient.apollo.module.glow.GlowModuleImpl; +import com.lunarclient.apollo.module.heightlimit.HeightLimitModule; +import com.lunarclient.apollo.module.heightlimit.HeightLimitModuleImpl; import com.lunarclient.apollo.module.hologram.HologramModule; import com.lunarclient.apollo.module.hologram.HologramModuleImpl; import com.lunarclient.apollo.module.inventory.InventoryModule; @@ -149,6 +151,7 @@ public void onEnable() { .addModule(EntityModule.class, new EntityModuleImpl()) .addModule(GlintModule.class) .addModule(GlowModule.class, new GlowModuleImpl()) + .addModule(HeightLimitModule.class, new HeightLimitModuleImpl()) .addModule(HologramModule.class, new HologramModuleImpl()) .addModule(InventoryModule.class) .addModule(LimbModule.class, new LimbModuleImpl()) diff --git a/platform/bungee/src/main/java/com/lunarclient/apollo/ApolloBungeePlatform.java b/platform/bungee/src/main/java/com/lunarclient/apollo/ApolloBungeePlatform.java index a5f50d27..74215459 100644 --- a/platform/bungee/src/main/java/com/lunarclient/apollo/ApolloBungeePlatform.java +++ b/platform/bungee/src/main/java/com/lunarclient/apollo/ApolloBungeePlatform.java @@ -46,6 +46,8 @@ import com.lunarclient.apollo.module.cosmetic.CosmeticModuleImpl; import com.lunarclient.apollo.module.entity.EntityModule; import com.lunarclient.apollo.module.entity.EntityModuleImpl; +import com.lunarclient.apollo.module.heightlimit.HeightLimitModule; +import com.lunarclient.apollo.module.heightlimit.HeightLimitModuleImpl; import com.lunarclient.apollo.module.hologram.HologramModule; import com.lunarclient.apollo.module.hologram.HologramModuleImpl; import com.lunarclient.apollo.module.limb.LimbModule; @@ -131,6 +133,7 @@ public void onEnable() { .addModule(CombatModule.class) .addModule(CooldownModule.class, new CooldownModuleImpl()) .addModule(EntityModule.class, new EntityModuleImpl()) + .addModule(HeightLimitModule.class, new HeightLimitModuleImpl()) .addModule(HologramModule.class, new HologramModuleImpl()) .addModule(LimbModule.class, new LimbModuleImpl()) .addModule(MarkerModule.class, new MarkerModuleImpl()) diff --git a/platform/folia/src/main/java/com/lunarclient/apollo/ApolloFoliaPlatform.java b/platform/folia/src/main/java/com/lunarclient/apollo/ApolloFoliaPlatform.java index 19a4878b..5255e8e3 100644 --- a/platform/folia/src/main/java/com/lunarclient/apollo/ApolloFoliaPlatform.java +++ b/platform/folia/src/main/java/com/lunarclient/apollo/ApolloFoliaPlatform.java @@ -48,6 +48,8 @@ import com.lunarclient.apollo.module.entity.EntityModuleImpl; import com.lunarclient.apollo.module.glow.GlowModule; import com.lunarclient.apollo.module.glow.GlowModuleImpl; +import com.lunarclient.apollo.module.heightlimit.HeightLimitModule; +import com.lunarclient.apollo.module.heightlimit.HeightLimitModuleImpl; import com.lunarclient.apollo.module.hologram.HologramModule; import com.lunarclient.apollo.module.hologram.HologramModuleImpl; import com.lunarclient.apollo.module.limb.LimbModule; @@ -138,6 +140,7 @@ public void onEnable() { .addModule(CooldownModule.class, new CooldownModuleImpl()) .addModule(EntityModule.class, new EntityModuleImpl()) .addModule(GlowModule.class, new GlowModuleImpl()) + .addModule(HeightLimitModule.class, new HeightLimitModuleImpl()) .addModule(HologramModule.class, new HologramModuleImpl()) .addModule(LimbModule.class, new LimbModuleImpl()) .addModule(MarkerModule.class, new MarkerModuleImpl()) diff --git a/platform/minestom/src/main/java/com/lunarclient/apollo/ApolloMinestomPlatform.java b/platform/minestom/src/main/java/com/lunarclient/apollo/ApolloMinestomPlatform.java index 8c2e26ab..b1d97ad7 100644 --- a/platform/minestom/src/main/java/com/lunarclient/apollo/ApolloMinestomPlatform.java +++ b/platform/minestom/src/main/java/com/lunarclient/apollo/ApolloMinestomPlatform.java @@ -49,6 +49,8 @@ import com.lunarclient.apollo.module.glint.GlintModule; import com.lunarclient.apollo.module.glow.GlowModule; import com.lunarclient.apollo.module.glow.GlowModuleImpl; +import com.lunarclient.apollo.module.heightlimit.HeightLimitModule; +import com.lunarclient.apollo.module.heightlimit.HeightLimitModuleImpl; import com.lunarclient.apollo.module.hologram.HologramModule; import com.lunarclient.apollo.module.hologram.HologramModuleImpl; import com.lunarclient.apollo.module.inventory.InventoryModule; @@ -171,6 +173,7 @@ public static void init(ApolloMinestomProperties properties) { .addModule(EntityModule.class, new EntityModuleImpl()) .addModule(GlintModule.class) .addModule(GlowModule.class, new GlowModuleImpl()) + .addModule(HeightLimitModule.class, new HeightLimitModuleImpl()) .addModule(HologramModule.class, new HologramModuleImpl()) .addModule(InventoryModule.class) .addModule(LimbModule.class, new LimbModuleImpl()) diff --git a/platform/velocity/src/main/java/com/lunarclient/apollo/ApolloVelocityPlatform.java b/platform/velocity/src/main/java/com/lunarclient/apollo/ApolloVelocityPlatform.java index 8d3ed35f..3b801511 100644 --- a/platform/velocity/src/main/java/com/lunarclient/apollo/ApolloVelocityPlatform.java +++ b/platform/velocity/src/main/java/com/lunarclient/apollo/ApolloVelocityPlatform.java @@ -46,6 +46,8 @@ import com.lunarclient.apollo.module.cosmetic.CosmeticModuleImpl; import com.lunarclient.apollo.module.entity.EntityModule; import com.lunarclient.apollo.module.entity.EntityModuleImpl; +import com.lunarclient.apollo.module.heightlimit.HeightLimitModule; +import com.lunarclient.apollo.module.heightlimit.HeightLimitModuleImpl; import com.lunarclient.apollo.module.hologram.HologramModule; import com.lunarclient.apollo.module.hologram.HologramModuleImpl; import com.lunarclient.apollo.module.limb.LimbModule; @@ -198,6 +200,7 @@ public void onProxyInitialization(ProxyInitializeEvent event) { .addModule(CombatModule.class) .addModule(CooldownModule.class, new CooldownModuleImpl()) .addModule(EntityModule.class, new EntityModuleImpl()) + .addModule(HeightLimitModule.class, new HeightLimitModuleImpl()) .addModule(HologramModule.class, new HologramModuleImpl()) .addModule(LimbModule.class, new LimbModuleImpl()) .addModule(MarkerModule.class, new MarkerModuleImpl()) From e1592bb8e11a4b477de7fb7635820e0a67cb8272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Bu=C4=8Dari=C4=87?= Date: Mon, 10 Aug 2026 23:04:22 +0200 Subject: [PATCH 3/5] Document snake_case custom data keys (#301) --- docs/developers/modules/inventory.mdx | 12 +++++----- .../example/module/impl/InventoryExample.java | 24 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/developers/modules/inventory.mdx b/docs/developers/modules/inventory.mdx index 35615fe2..62b9a65a 100644 --- a/docs/developers/modules/inventory.mdx +++ b/docs/developers/modules/inventory.mdx @@ -34,27 +34,27 @@ Explore each integration by cycling through each tab, to find the best fit for y **Copy To Clipboard Item** -`/summon item ~ ~1 ~ {Item:{id:"minecraft:paper",Count:1b,components:{"minecraft:custom_name":"COPY TO CLIPBOARD","minecraft:custom_data":{lunar:{unclickable:true,copyToClipboard:"lunarclient.com"}}}}}` +`/summon item ~ ~1 ~ {Item:{id:"minecraft:paper",Count:1b,components:{"minecraft:custom_name":"COPY TO CLIPBOARD","minecraft:custom_data":{lunar:{unclickable:true,copy_to_clipboard:"lunarclient.com"}}}}}` **Open URL Item** -`/summon item ~ ~1 ~ {Item:{id:"minecraft:torch",Count:1b,components:{"minecraft:custom_name":"OPEN URL","minecraft:custom_data":{lunar:{unclickable:true,openUrl:"https://lunarclient.com"}}}}}` +`/summon item ~ ~1 ~ {Item:{id:"minecraft:torch",Count:1b,components:{"minecraft:custom_name":"OPEN URL","minecraft:custom_data":{lunar:{unclickable:true,open_url:"https://lunarclient.com"}}}}}` **Suggest Command Item** -`/summon item ~ ~1 ~ {Item:{id:"minecraft:book",Count:1b,components:{"minecraft:custom_name":"SUGGEST COMMAND","minecraft:custom_data":{lunar:{unclickable:true,suggestCommand:"/apollo"}}}}}` +`/summon item ~ ~1 ~ {Item:{id:"minecraft:book",Count:1b,components:{"minecraft:custom_name":"SUGGEST COMMAND","minecraft:custom_data":{lunar:{unclickable:true,suggest_command:"/apollo"}}}}}` **Run Command Item** -`/summon item ~ ~1 ~ {Item:{id:"minecraft:writable_book",Count:1b,components:{"minecraft:custom_name":"RUN COMMAND","minecraft:custom_data":{lunar:{unclickable:true,runCommand:"/apollo"}}}}}` +`/summon item ~ ~1 ~ {Item:{id:"minecraft:writable_book",Count:1b,components:{"minecraft:custom_name":"RUN COMMAND","minecraft:custom_data":{lunar:{unclickable:true,run_command:"/apollo"}}}}}` **Hide Item Tooltip Item** -`/summon item ~ ~1 ~ {Item:{id:"minecraft:sponge",Count:1b,components:{"minecraft:custom_name":"HIDE ITEM TOOLTIP","minecraft:custom_data":{lunar:{unclickable:true,hideItemTooltip:true}}}}}` +`/summon item ~ ~1 ~ {Item:{id:"minecraft:sponge",Count:1b,components:{"minecraft:custom_name":"HIDE ITEM TOOLTIP","minecraft:custom_data":{lunar:{unclickable:true,hide_item_tooltip:true}}}}}` **Hide Slot Highlight Item** -`/summon item ~ ~1 ~ {Item:{id:"minecraft:dirt",Count:1b,components:{"minecraft:custom_name":"HIDE SLOT HIGHTLIGHT","minecraft:custom_data":{lunar:{unclickable:true,hideSlotHighlight:true}}}}}` +`/summon item ~ ~1 ~ {Item:{id:"minecraft:dirt",Count:1b,components:{"minecraft:custom_name":"HIDE SLOT HIGHTLIGHT","minecraft:custom_data":{lunar:{unclickable:true,hide_slot_highlight:true}}}}}` diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/InventoryExample.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/InventoryExample.java index 37e62f7c..5d5ed71a 100644 --- a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/InventoryExample.java +++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/InventoryExample.java @@ -46,12 +46,12 @@ public boolean inventoryModuleExample(Player player) { public void inventoryModuleCommandExample(Player player) { player.performCommand("summon item ~ ~1 ~ {Item:{id:\"minecraft:stone\",Count:1b,components:{\"minecraft:custom_name\":\"UNCLICKABLE\",\"minecraft:custom_data\":{lunar:{unclickable:true}}}}}"); - player.performCommand("summon item ~ ~1 ~ {Item:{id:\"minecraft:paper\",Count:1b,components:{\"minecraft:custom_name\":\"COPY TO CLIPBOARD\",\"minecraft:custom_data\":{lunar:{unclickable:true,copyToClipboard:\"lunarclient.com\"}}}}}"); - player.performCommand("summon item ~ ~1 ~ {Item:{id:\"minecraft:torch\",Count:1b,components:{\"minecraft:custom_name\":\"OPEN URL\",\"minecraft:custom_data\":{lunar:{unclickable:true,openUrl:\"https://lunarclient.com\"}}}}}"); - player.performCommand("summon item ~ ~1 ~ {Item:{id:\"minecraft:book\",Count:1b,components:{\"minecraft:custom_name\":\"SUGGEST COMMAND\",\"minecraft:custom_data\":{lunar:{unclickable:true,suggestCommand:\"/apollo\"}}}}}"); - player.performCommand("summon item ~ ~1 ~ {Item:{id:\"minecraft:writable_book\",Count:1b,components:{\"minecraft:custom_name\":\"RUN COMMAND\",\"minecraft:custom_data\":{lunar:{unclickable:true,runCommand:\"/apollo\"}}}}}"); - player.performCommand("summon item ~ ~1 ~ {Item:{id:\"minecraft:sponge\",Count:1b,components:{\"minecraft:custom_name\":\"HIDE ITEM TOOLTIP\",\"minecraft:custom_data\":{lunar:{unclickable:true,hideItemTooltip:true}}}}}"); - player.performCommand("summon item ~ ~1 ~ {Item:{id:\"minecraft:dirt\",Count:1b,components:{\"minecraft:custom_name\":\"HIDE SLOT HIGHTLIGHT\",\"minecraft:custom_data\":{lunar:{unclickable:true,hideSlotHighlight:true}}}}}"); + player.performCommand("summon item ~ ~1 ~ {Item:{id:\"minecraft:paper\",Count:1b,components:{\"minecraft:custom_name\":\"COPY TO CLIPBOARD\",\"minecraft:custom_data\":{lunar:{unclickable:true,copy_to_clipboard:\"lunarclient.com\"}}}}}"); + player.performCommand("summon item ~ ~1 ~ {Item:{id:\"minecraft:torch\",Count:1b,components:{\"minecraft:custom_name\":\"OPEN URL\",\"minecraft:custom_data\":{lunar:{unclickable:true,open_url:\"https://lunarclient.com\"}}}}}"); + player.performCommand("summon item ~ ~1 ~ {Item:{id:\"minecraft:book\",Count:1b,components:{\"minecraft:custom_name\":\"SUGGEST COMMAND\",\"minecraft:custom_data\":{lunar:{unclickable:true,suggest_command:\"/apollo\"}}}}}"); + player.performCommand("summon item ~ ~1 ~ {Item:{id:\"minecraft:writable_book\",Count:1b,components:{\"minecraft:custom_name\":\"RUN COMMAND\",\"minecraft:custom_data\":{lunar:{unclickable:true,run_command:\"/apollo\"}}}}}"); + player.performCommand("summon item ~ ~1 ~ {Item:{id:\"minecraft:sponge\",Count:1b,components:{\"minecraft:custom_name\":\"HIDE ITEM TOOLTIP\",\"minecraft:custom_data\":{lunar:{unclickable:true,hide_item_tooltip:true}}}}}"); + player.performCommand("summon item ~ ~1 ~ {Item:{id:\"minecraft:dirt\",Count:1b,components:{\"minecraft:custom_name\":\"HIDE SLOT HIGHTLIGHT\",\"minecraft:custom_data\":{lunar:{unclickable:true,hide_slot_highlight:true}}}}}"); } public void inventoryModuleNMSExample(Player player) { @@ -70,7 +70,7 @@ public void inventoryModuleNMSExample(Player player) { ); copyToClipboardItem = ItemUtil.addTag(copyToClipboardItem, "unclickable", true); - inventory.setItem(12, ItemUtil.addTag(copyToClipboardItem, "copyToClipboard", "lunarclient.com")); + inventory.setItem(12, ItemUtil.addTag(copyToClipboardItem, "copy_to_clipboard", "lunarclient.com")); ItemStack openUrlItem = ItemUtil.itemWithName( Material.TORCH, @@ -78,7 +78,7 @@ public void inventoryModuleNMSExample(Player player) { ); openUrlItem = ItemUtil.addTag(openUrlItem, "unclickable", true); - inventory.setItem(14, ItemUtil.addTag(openUrlItem, "openUrl", "https://lunarclient.com")); + inventory.setItem(14, ItemUtil.addTag(openUrlItem, "open_url", "https://lunarclient.com")); ItemStack suggestCommandItem = ItemUtil.itemWithName( Material.BOOK, @@ -86,7 +86,7 @@ public void inventoryModuleNMSExample(Player player) { ); suggestCommandItem = ItemUtil.addTag(suggestCommandItem, "unclickable", true); - inventory.setItem(16, ItemUtil.addTag(suggestCommandItem, "suggestCommand", "/apollo")); + inventory.setItem(16, ItemUtil.addTag(suggestCommandItem, "suggest_command", "/apollo")); ItemStack runCommandItem = ItemUtil.itemWithName( Material.ENCHANTED_BOOK, @@ -94,7 +94,7 @@ public void inventoryModuleNMSExample(Player player) { ); runCommandItem = ItemUtil.addTag(runCommandItem, "unclickable", true); - inventory.setItem(29, ItemUtil.addTag(runCommandItem, "runCommand", "/apollo")); + inventory.setItem(29, ItemUtil.addTag(runCommandItem, "run_command", "/apollo")); ItemStack hideTooltipItem = ItemUtil.itemWithName( Material.SPONGE, @@ -102,7 +102,7 @@ public void inventoryModuleNMSExample(Player player) { ); hideTooltipItem = ItemUtil.addTag(hideTooltipItem, "unclickable", true); - inventory.setItem(31, ItemUtil.addTag(hideTooltipItem, "hideItemTooltip", true)); + inventory.setItem(31, ItemUtil.addTag(hideTooltipItem, "hide_item_tooltip", true)); ItemStack hideHighlightItem = ItemUtil.itemWithName( Material.DIRT, @@ -110,7 +110,7 @@ public void inventoryModuleNMSExample(Player player) { ); hideHighlightItem = ItemUtil.addTag(hideHighlightItem, "unclickable", true); - inventory.setItem(33, ItemUtil.addTag(hideHighlightItem, "hideSlotHighlight", true)); + inventory.setItem(33, ItemUtil.addTag(hideHighlightItem, "hide_slot_highlight", true)); player.openInventory(inventory); } From e5a2b0bbceba00a365073eccee119228c18068d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Bu=C4=8Dari=C4=87?= Date: Mon, 10 Aug 2026 23:05:15 +0200 Subject: [PATCH 4/5] Add `TransferModule#ping` validation & increase `PingRequest` timeout to 10s (#302) --- .../apollo/module/transfer/PingRequest.java | 11 +++++++++++ .../apollo/module/transfer/TransferModule.java | 12 ++++++++++++ .../lunarclient/apollo/roundtrip/ApolloRequest.java | 10 ++++++++++ .../apollo/roundtrip/ApolloRoundtripManager.java | 5 +++-- .../apollo/module/transfer/TransferModuleImpl.java | 13 ++++++++++++- docs/developers/modules/transfer.mdx | 2 ++ example/bukkit/json/src/main/resources/plugin.yml | 2 ++ example/bukkit/proto/src/main/resources/plugin.yml | 2 ++ 8 files changed, 54 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/com/lunarclient/apollo/module/transfer/PingRequest.java b/api/src/main/java/com/lunarclient/apollo/module/transfer/PingRequest.java index 27640400..3b305bd3 100644 --- a/api/src/main/java/com/lunarclient/apollo/module/transfer/PingRequest.java +++ b/api/src/main/java/com/lunarclient/apollo/module/transfer/PingRequest.java @@ -45,4 +45,15 @@ public final class PingRequest extends ApolloRequest { */ List serverIps; + /** + * Returns the timeout for ping requests, in milliseconds. + * + * @return the request timeout, in milliseconds + * @since 1.2.9 + */ + @Override + public long getTimeoutMillis() { + return 10_000L; + } + } diff --git a/api/src/main/java/com/lunarclient/apollo/module/transfer/TransferModule.java b/api/src/main/java/com/lunarclient/apollo/module/transfer/TransferModule.java index 2d033f4d..39b08ed8 100644 --- a/api/src/main/java/com/lunarclient/apollo/module/transfer/TransferModule.java +++ b/api/src/main/java/com/lunarclient/apollo/module/transfer/TransferModule.java @@ -42,6 +42,14 @@ @ModuleDefinition(id = "transfer", name = "Transfer") public abstract class TransferModule extends ApolloModule { + /** + * The maximum amount of server IPs the client will ping + * for a single {@link PingRequest}. + * + * @since 1.2.9 + */ + public static final int MAX_PINGS_PER_PACKET = 10; + @Override public Collection getSupportedPlatforms() { return Arrays.asList(ApolloPlatform.Kind.SERVER, ApolloPlatform.Kind.PROXY); @@ -55,6 +63,8 @@ public Collection getSupportedPlatforms() { * @param player the player * @param serverIps all server IPs to ping * @return future to be listened to for errors/success + * @throws IllegalArgumentException if no server IPs or more than + * {@value #MAX_PINGS_PER_PACKET} server IPs are provided * @since 1.0.0 */ public Future ping(ApolloPlayer player, List serverIps) { @@ -85,6 +95,8 @@ public Future transfer(ApolloPlayer player, String serverIp) { * @param player the player * @param request the ping request * @return future to be listened to for errors/success + * @throws IllegalArgumentException if no server IPs or more than + * {@value #MAX_PINGS_PER_PACKET} server IPs are provided * @since 1.0.0 */ public abstract Future ping(ApolloPlayer player, PingRequest request); diff --git a/api/src/main/java/com/lunarclient/apollo/roundtrip/ApolloRequest.java b/api/src/main/java/com/lunarclient/apollo/roundtrip/ApolloRequest.java index 372d8ae3..f5bb25b9 100644 --- a/api/src/main/java/com/lunarclient/apollo/roundtrip/ApolloRequest.java +++ b/api/src/main/java/com/lunarclient/apollo/roundtrip/ApolloRequest.java @@ -67,4 +67,14 @@ public ApolloRequest() { this.sentTime = System.currentTimeMillis(); } + /** + * Returns the time to wait for a response, in milliseconds. + * + * @return the request timeout, in milliseconds + * @since 1.2.9 + */ + public long getTimeoutMillis() { + return TIMEOUT; + } + } diff --git a/api/src/main/java/com/lunarclient/apollo/roundtrip/ApolloRoundtripManager.java b/api/src/main/java/com/lunarclient/apollo/roundtrip/ApolloRoundtripManager.java index f0d5bce8..31771c4c 100644 --- a/api/src/main/java/com/lunarclient/apollo/roundtrip/ApolloRoundtripManager.java +++ b/api/src/main/java/com/lunarclient/apollo/roundtrip/ApolloRoundtripManager.java @@ -119,13 +119,14 @@ public void registerListener(ApolloRequest request this.paginationManager.handleTimeout(packetId); if (listener != null) { - Throwable error = new Throwable("Timeout exceeded!"); + Throwable error = new Throwable("Timeout exceeded! No " + request.getClass().getSimpleName() + + " response received within " + request.getTimeoutMillis() + "ms"); future.handleFailure(error); } } catch (Exception e) { e.printStackTrace(); } - }, ApolloRequest.TIMEOUT, TimeUnit.MILLISECONDS); + }, request.getTimeoutMillis(), TimeUnit.MILLISECONDS); this.listeners.put(packetId, (UncertainFuture) future); } diff --git a/common/src/main/java/com/lunarclient/apollo/module/transfer/TransferModuleImpl.java b/common/src/main/java/com/lunarclient/apollo/module/transfer/TransferModuleImpl.java index 6971cf56..febd0109 100644 --- a/common/src/main/java/com/lunarclient/apollo/module/transfer/TransferModuleImpl.java +++ b/common/src/main/java/com/lunarclient/apollo/module/transfer/TransferModuleImpl.java @@ -53,9 +53,20 @@ public TransferModuleImpl() { @Override public Future ping(@NonNull ApolloPlayer player, @NonNull PingRequest request) { + List serverIps = request.getServerIps(); + + if (serverIps == null || serverIps.isEmpty()) { + throw new IllegalArgumentException("PingRequest must contain at least 1 server IP!"); + } + + if (serverIps.size() > MAX_PINGS_PER_PACKET) { + throw new IllegalArgumentException("PingRequest supports up to " + MAX_PINGS_PER_PACKET + + " server IPs, got " + serverIps.size() + "!"); + } + com.lunarclient.apollo.transfer.v1.PingRequest requestProto = com.lunarclient.apollo.transfer.v1.PingRequest.newBuilder() .setRequestId(ByteString.copyFromUtf8(request.getRequestId().toString())) - .addAllServerIps(request.getServerIps()) + .addAllServerIps(serverIps) .build(); return ((AbstractApolloPlayer) player).sendRoundTripPacket(request, requestProto); diff --git a/docs/developers/modules/transfer.mdx b/docs/developers/modules/transfer.mdx index b2ea55b0..b099d973 100644 --- a/docs/developers/modules/transfer.mdx +++ b/docs/developers/modules/transfer.mdx @@ -119,6 +119,7 @@ public void transferExample(Player viewer) { You can provide up to `10` different addresses per ping packet. + Requests with more addresses are rejected with an `IllegalArgumentException`. @@ -209,6 +210,7 @@ public void transferExample(Player player) { You can provide up to `10` different addresses per ping packet. + Addresses beyond the first `10` are reported as `STATUS_TIMED_OUT` instead of being pinged. ```java diff --git a/example/bukkit/json/src/main/resources/plugin.yml b/example/bukkit/json/src/main/resources/plugin.yml index e92e59a4..0b4a5910 100644 --- a/example/bukkit/json/src/main/resources/plugin.yml +++ b/example/bukkit/json/src/main/resources/plugin.yml @@ -54,6 +54,8 @@ commands: description: "Pay Now!" richpresence: description: "Rich Presence!" + serverlink: + description: "Server Links!" saturation: description: "Saturation!" serverrule: diff --git a/example/bukkit/proto/src/main/resources/plugin.yml b/example/bukkit/proto/src/main/resources/plugin.yml index 468f5d87..bab850d2 100644 --- a/example/bukkit/proto/src/main/resources/plugin.yml +++ b/example/bukkit/proto/src/main/resources/plugin.yml @@ -54,6 +54,8 @@ commands: description: "Pay Now!" richpresence: description: "Rich Presence!" + serverlink: + description: "Server Links!" saturation: description: "Saturation!" serverrule: From 464006183ac1798a83d9b0e034e9eb8f48d757d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Bu=C4=8Dari=C4=87?= Date: Mon, 10 Aug 2026 23:06:24 +0200 Subject: [PATCH 5/5] example(internal): npc visibility tracking (#303) --- .../api/listener/ApolloPlayerApiListener.java | 5 + .../api/module/CosmeticApiExample.java | 46 ++++++++- .../apollo/example/ApolloExamplePlugin.java | 8 ++ .../example/command/CosmeticCommand.java | 18 +++- .../example/module/impl/CosmeticExample.java | 21 +++- .../listener/ApolloPlayerJsonListener.java | 23 +++++ .../json/module/CosmeticJsonExample.java | 64 ++++++++++++- .../apollo/example/nms/NpcManager.java | 95 ++++++++++++++++--- .../apollo/example/nms/NpcViewerListener.java | 33 +++++++ .../apollo/example/nms/PlayerNpc.java | 17 ++++ .../listener/ApolloPlayerProtoListener.java | 23 +++++ .../proto/module/CosmeticProtoExample.java | 61 +++++++++++- 12 files changed, 391 insertions(+), 23 deletions(-) create mode 100644 example/bukkit/nms/src/main/java/com/lunarclient/apollo/example/nms/NpcViewerListener.java diff --git a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/listener/ApolloPlayerApiListener.java b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/listener/ApolloPlayerApiListener.java index d0cb1938..068ff039 100644 --- a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/listener/ApolloPlayerApiListener.java +++ b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/listener/ApolloPlayerApiListener.java @@ -82,6 +82,11 @@ private void onApolloRegister(ApolloRegisterPlayerEvent event) { for (CommandCosmetic spec : npc.getCosmetics()) { cosmeticExample.equipNpcCosmeticToViewer(player, npc.getUuid(), spec); } + + PlayerNpc.ActiveEmote emote = npc.getActiveEmote(); + if (emote != null && npc.getViewers().contains(player.getUniqueId())) { + cosmeticExample.startNpcEmoteToViewer(player, npc.getUuid(), emote.getEmoteId(), emote.getMetadata()); + } } } diff --git a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/CosmeticApiExample.java b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/CosmeticApiExample.java index 0c82c2ca..98608354 100644 --- a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/CosmeticApiExample.java +++ b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/CosmeticApiExample.java @@ -175,13 +175,30 @@ public void startNpcEmoteExample(Player viewer, UUID npcUuid) { } @Override - public void startNpcEmoteInternal(Player viewer, UUID npcUuid, int emoteId, int metadata) { + public void startNpcEmoteInternal(UUID npcUuid, int emoteId, int metadata) { + List viewers = this.getApolloViewers(npcUuid); + if (viewers.isEmpty()) { + return; + } + Emote emote = Emote.builder() .id(emoteId) .metadata(metadata) .build(); - this.cosmeticModule.startNpcEmote(Recipients.ofEveryone(), npcUuid, emote); + this.cosmeticModule.startNpcEmote(Recipients.of(viewers), npcUuid, emote); + } + + @Override + public void startNpcEmoteToViewer(Player viewer, UUID npcUuid, int emoteId, int metadata) { + Apollo.getPlayerManager().getPlayer(viewer.getUniqueId()).ifPresent(apolloPlayer -> { + Emote emote = Emote.builder() + .id(emoteId) + .metadata(metadata) + .build(); + + this.cosmeticModule.startNpcEmote(apolloPlayer, npcUuid, emote); + }); } @Override @@ -189,6 +206,31 @@ public void stopNpcEmoteExample(Player viewer, UUID npcUuid) { this.cosmeticModule.stopNpcEmote(Recipients.ofEveryone(), npcUuid); } + @Override + public void stopNpcEmoteInternal(UUID npcUuid) { + List viewers = this.getApolloViewers(npcUuid); + if (viewers.isEmpty()) { + return; + } + + this.cosmeticModule.stopNpcEmote(Recipients.of(viewers), npcUuid); + } + + @Override + public void stopNpcEmoteToViewer(Player viewer, UUID npcUuid) { + Apollo.getPlayerManager().getPlayer(viewer.getUniqueId()).ifPresent(apolloPlayer -> + this.cosmeticModule.stopNpcEmote(apolloPlayer, npcUuid)); + } + + private List getApolloViewers(UUID npcUuid) { + List viewers = new ArrayList<>(); + for (Player player : this.getNpcViewers(npcUuid)) { + Apollo.getPlayerManager().getPlayer(player.getUniqueId()).ifPresent(viewers::add); + } + + return viewers; + } + @Override public void resetNpcEmotesExample() { this.cosmeticModule.resetNpcEmotes(Recipients.ofEveryone()); diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/ApolloExamplePlugin.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/ApolloExamplePlugin.java index 94092492..433af374 100644 --- a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/ApolloExamplePlugin.java +++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/ApolloExamplePlugin.java @@ -93,6 +93,7 @@ import com.lunarclient.apollo.example.module.impl.VignetteExample; import com.lunarclient.apollo.example.module.impl.WaypointExample; import com.lunarclient.apollo.example.nms.NpcManager; +import com.lunarclient.apollo.example.nms.PlayerNpc; import lombok.Getter; import lombok.Setter; import org.bukkit.plugin.java.JavaPlugin; @@ -153,6 +154,13 @@ public void onEnable() { this.registerCommands(); this.registerModuleExamples(); this.registerListeners(); + + this.npcManager.addViewerListener((viewer, npc) -> { + PlayerNpc.ActiveEmote emote = npc.getActiveEmote(); + if (emote != null && this.cosmeticExample != null) { + this.cosmeticExample.startNpcEmoteToViewer(viewer, npc.getUuid(), emote.getEmoteId(), emote.getMetadata()); + } + }); } @Override diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/CosmeticCommand.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/CosmeticCommand.java index ad8057e8..92817e47 100644 --- a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/CosmeticCommand.java +++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/CosmeticCommand.java @@ -222,7 +222,12 @@ private boolean handleEmote(Player player, CosmeticExample example, String[] arg } } - example.startNpcEmoteInternal(player, uuid, emoteId, metadata); + example.startNpcEmoteInternal(uuid, emoteId, metadata); + + int emoteMetadata = metadata; + ApolloExamplePlugin.getInstance().getNpcManager().findByUuid(uuid) + .ifPresent(npc -> npc.setActiveEmote(new PlayerNpc.ActiveEmote(emoteId, emoteMetadata))); + player.sendMessage(ChatColor.GREEN + "Started emote " + emoteId + " on NPC " + args[2]); break; } @@ -238,13 +243,22 @@ private boolean handleEmote(Player player, CosmeticExample example, String[] arg return true; } - example.stopNpcEmoteExample(player, uuid); + example.stopNpcEmoteInternal(uuid); + + ApolloExamplePlugin.getInstance().getNpcManager().findByUuid(uuid) + .ifPresent(npc -> npc.setActiveEmote(null)); + player.sendMessage(ChatColor.GREEN + "Stopped emote on NPC " + args[2]); break; } case "reset": { example.resetNpcEmotesExample(); + + for (PlayerNpc npc : ApolloExamplePlugin.getInstance().getNpcManager().getNpcs()) { + npc.setActiveEmote(null); + } + player.sendMessage(ChatColor.GREEN + "Reset all NPC emotes"); break; } diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/CosmeticExample.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/CosmeticExample.java index 0e56b672..b6e33e20 100644 --- a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/CosmeticExample.java +++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/CosmeticExample.java @@ -23,6 +23,7 @@ */ package com.lunarclient.apollo.example.module.impl; +import com.lunarclient.apollo.example.ApolloExamplePlugin; import com.lunarclient.apollo.example.module.ApolloModuleExample; import com.lunarclient.apollo.example.nms.CommandCosmetic; import java.util.Collections; @@ -54,12 +55,30 @@ public void equipNpcCosmeticToViewer(Player viewer, UUID npcUuid, CommandCosmeti public abstract void startNpcEmoteExample(Player viewer, UUID npcUuid); - public abstract void startNpcEmoteInternal(Player viewer, UUID npcUuid, int emoteId, int metadata); + public void startNpcEmoteInternal(UUID npcUuid, int emoteId, int metadata) { + for (Player viewer : this.getNpcViewers(npcUuid)) { + this.startNpcEmoteToViewer(viewer, npcUuid, emoteId, metadata); + } + } + + public abstract void startNpcEmoteToViewer(Player viewer, UUID npcUuid, int emoteId, int metadata); public abstract void stopNpcEmoteExample(Player viewer, UUID npcUuid); + public void stopNpcEmoteInternal(UUID npcUuid) { + for (Player viewer : this.getNpcViewers(npcUuid)) { + this.stopNpcEmoteToViewer(viewer, npcUuid); + } + } + + public abstract void stopNpcEmoteToViewer(Player viewer, UUID npcUuid); + public abstract void resetNpcEmotesExample(); + protected List getNpcViewers(UUID npcUuid) { + return ApolloExamplePlugin.getInstance().getNpcManager().getViewers(npcUuid); + } + public abstract void displaySprayExample(Player viewer, int sprayId); public abstract void removeSprayExample(int sprayId); diff --git a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/listener/ApolloPlayerJsonListener.java b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/listener/ApolloPlayerJsonListener.java index 6c0984d5..217aab0a 100644 --- a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/listener/ApolloPlayerJsonListener.java +++ b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/listener/ApolloPlayerJsonListener.java @@ -26,6 +26,9 @@ import com.google.gson.JsonObject; import com.lunarclient.apollo.example.ApolloExamplePlugin; import com.lunarclient.apollo.example.json.util.JsonPacketUtil; +import com.lunarclient.apollo.example.module.impl.CosmeticExample; +import com.lunarclient.apollo.example.nms.CommandCosmetic; +import com.lunarclient.apollo.example.nms.PlayerNpc; import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -81,6 +84,26 @@ private void onRegisterChannel(PlayerRegisterChannelEvent event) { PLAYERS_RUNNING_APOLLO.add(player.getUniqueId()); player.sendMessage("You are using LunarClient!"); + + this.applyNpcCosmetics(player); + } + + private void applyNpcCosmetics(Player player) { + CosmeticExample cosmeticExample = this.plugin.getCosmeticExample(); + if (cosmeticExample == null) { + return; + } + + for (PlayerNpc npc : this.plugin.getNpcManager().getNpcs()) { + for (CommandCosmetic spec : npc.getCosmetics()) { + cosmeticExample.equipNpcCosmeticToViewer(player, npc.getUuid(), spec); + } + + PlayerNpc.ActiveEmote emote = npc.getActiveEmote(); + if (emote != null && npc.getViewers().contains(player.getUniqueId())) { + cosmeticExample.startNpcEmoteToViewer(player, npc.getUuid(), emote.getEmoteId(), emote.getMetadata()); + } + } } @EventHandler diff --git a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/CosmeticJsonExample.java b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/CosmeticJsonExample.java index cff28169..b9a43ef9 100644 --- a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/CosmeticJsonExample.java +++ b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/CosmeticJsonExample.java @@ -29,6 +29,7 @@ import com.lunarclient.apollo.example.json.util.JsonPacketUtil; import com.lunarclient.apollo.example.json.util.JsonUtil; import com.lunarclient.apollo.example.module.impl.CosmeticExample; +import com.lunarclient.apollo.example.nms.CommandCosmetic; import java.time.Duration; import java.util.List; import java.util.UUID; @@ -103,6 +104,56 @@ public void equipNpcCosmeticsInternal(Player viewer, UUID npcUuid, List JsonPacketUtil.broadcastPacket(message); } + @Override + public void equipNpcCosmeticInternal(Player viewer, UUID npcUuid, CommandCosmetic cosmetic) { + JsonPacketUtil.broadcastPacket(this.createEquipMessage(npcUuid, cosmetic)); + } + + @Override + public void equipNpcCosmeticToViewer(Player viewer, UUID npcUuid, CommandCosmetic cosmetic) { + JsonPacketUtil.sendPacket(viewer, this.createEquipMessage(npcUuid, cosmetic)); + } + + private JsonObject createEquipMessage(UUID npcUuid, CommandCosmetic cosmetic) { + JsonObject cosmeticObject = new JsonObject(); + cosmeticObject.addProperty("id", cosmetic.getId()); + + CommandCosmetic.Options options = cosmetic.getOptions(); + if (options instanceof CommandCosmetic.Hat) { + CommandCosmetic.Hat hat = (CommandCosmetic.Hat) options; + JsonObject hatOptions = new JsonObject(); + hatOptions.addProperty("show_over_helmet", hat.isShowOverHelmet()); + hatOptions.addProperty("show_over_skin_layer", hat.isShowOverSkinLayer()); + hatOptions.addProperty("height_offset", hat.getHeightOffset()); + cosmeticObject.add("hat_options", hatOptions); + } else if (options instanceof CommandCosmetic.Cloak) { + JsonObject cloakOptions = new JsonObject(); + cloakOptions.addProperty("use_cloth_physics", ((CommandCosmetic.Cloak) options).isUseClothPhysics()); + cosmeticObject.add("cloak_options", cloakOptions); + } else if (options instanceof CommandCosmetic.Pet) { + JsonObject petOptions = new JsonObject(); + petOptions.addProperty("flip_shoulder", ((CommandCosmetic.Pet) options).isFlipShoulder()); + cosmeticObject.add("pet_options", petOptions); + } else if (options instanceof CommandCosmetic.Body) { + CommandCosmetic.Body body = (CommandCosmetic.Body) options; + JsonObject bodyOptions = new JsonObject(); + bodyOptions.addProperty("show_over_chestplate", body.isShowOverChestplate()); + bodyOptions.addProperty("show_over_leggings", body.isShowOverLeggings()); + bodyOptions.addProperty("show_over_boots", body.isShowOverBoots()); + cosmeticObject.add("body_options", bodyOptions); + } + + JsonArray cosmeticsArray = new JsonArray(); + cosmeticsArray.add(cosmeticObject); + + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.cosmetic.v1.EquipNpcCosmeticsMessage"); + message.add("npc_uuid", JsonUtil.createUuidObject(npcUuid)); + message.add("cosmetics", cosmeticsArray); + + return message; + } + @Override public void unequipNpcCosmeticsExample(Player viewer, UUID npcUuid) { List cosmeticIds = Lists.newArrayList(434, 3654, 5095, 3, 3977); @@ -154,7 +205,7 @@ public void startNpcEmoteExample(Player viewer, UUID npcUuid) { } @Override - public void startNpcEmoteInternal(Player viewer, UUID npcUuid, int emoteId, int metadata) { + public void startNpcEmoteToViewer(Player viewer, UUID npcUuid, int emoteId, int metadata) { JsonObject emote = new JsonObject(); emote.addProperty("id", emoteId); emote.addProperty("metadata", metadata); @@ -164,7 +215,7 @@ public void startNpcEmoteInternal(Player viewer, UUID npcUuid, int emoteId, int message.add("npc_uuid", JsonUtil.createUuidObject(npcUuid)); message.add("emote", emote); - JsonPacketUtil.broadcastPacket(message); + JsonPacketUtil.sendPacket(viewer, message); } @Override @@ -176,6 +227,15 @@ public void stopNpcEmoteExample(Player viewer, UUID npcUuid) { JsonPacketUtil.broadcastPacket(message); } + @Override + public void stopNpcEmoteToViewer(Player viewer, UUID npcUuid) { + JsonObject message = new JsonObject(); + message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.cosmetic.v1.StopNpcEmoteMessage"); + message.add("npc_uuid", JsonUtil.createUuidObject(npcUuid)); + + JsonPacketUtil.sendPacket(viewer, message); + } + @Override public void resetNpcEmotesExample() { JsonObject message = new JsonObject(); diff --git a/example/bukkit/nms/src/main/java/com/lunarclient/apollo/example/nms/NpcManager.java b/example/bukkit/nms/src/main/java/com/lunarclient/apollo/example/nms/NpcManager.java index be00b996..37f2e2e5 100644 --- a/example/bukkit/nms/src/main/java/com/lunarclient/apollo/example/nms/NpcManager.java +++ b/example/bukkit/nms/src/main/java/com/lunarclient/apollo/example/nms/NpcManager.java @@ -26,6 +26,7 @@ import com.mojang.authlib.GameProfile; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; @@ -51,10 +52,12 @@ import org.bukkit.World; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.Nullable; @@ -65,7 +68,11 @@ public final class NpcManager implements Listener { HumanoidArm.RIGHT, false, false, ParticleStatus.ALL ); + private static final double TRACKING_RANGE = 48.0; + private static final double TRACKING_RANGE_SQUARED = TRACKING_RANGE * TRACKING_RANGE; + private final Map npcs = new HashMap<>(); + private final List viewerListeners = new ArrayList<>(); private final JavaPlugin plugin; private final NpcStore store; @@ -75,6 +82,11 @@ public NpcManager(JavaPlugin plugin) { Bukkit.getPluginManager().registerEvents(this, plugin); Bukkit.getScheduler().runTask(plugin, this::loadOrSpawnDefaults); + Bukkit.getScheduler().runTaskTimer(plugin, this::updateVisibility, 1L, 10L); + } + + public void addViewerListener(NpcViewerListener listener) { + this.viewerListeners.add(listener); } public void removeNpc(UUID uuid) { @@ -114,21 +126,73 @@ public Collection getNpcs() { } @EventHandler - public void onPlayerJoin(PlayerJoinEvent event) { - ServerPlayer viewer = ((CraftPlayer) event.getPlayer()).getHandle(); - for (PlayerNpc npc : this.npcs.values()) { - this.showNpc(viewer, npc); - } + public void onPlayerQuit(PlayerQuitEvent event) { + this.forgetViewer(event.getPlayer()); } @EventHandler - public void onPlayerQuit(PlayerQuitEvent event) { - ServerPlayer viewer = ((CraftPlayer) event.getPlayer()).getHandle(); + public void onPlayerChangedWorld(PlayerChangedWorldEvent event) { + this.forgetViewer(event.getPlayer()); + } + + @EventHandler + public void onPlayerRespawn(PlayerRespawnEvent event) { + this.forgetViewer(event.getPlayer()); + } + + private void forgetViewer(Player player) { for (PlayerNpc npc : this.npcs.values()) { - this.hideNpc(viewer, npc); + npc.getViewers().remove(player.getUniqueId()); + } + } + + private void updateVisibility() { + for (Player player : Bukkit.getOnlinePlayers()) { + for (PlayerNpc npc : this.npcs.values()) { + boolean inRange = this.isWithinTrackingRange(player, npc); + boolean viewing = npc.getViewers().contains(player.getUniqueId()); + + if (inRange && !viewing) { + npc.getViewers().add(player.getUniqueId()); + this.showNpc(((CraftPlayer) player).getHandle(), npc); + + for (NpcViewerListener listener : this.viewerListeners) { + listener.onNpcShown(player, npc); + } + } else if (!inRange && viewing) { + npc.getViewers().remove(player.getUniqueId()); + this.hideNpc(((CraftPlayer) player).getHandle(), npc); + } + } } } + private boolean isWithinTrackingRange(Player player, PlayerNpc npc) { + Location location = npc.getLocation(); + World world = location.getWorld(); + + return world != null + && world.equals(player.getWorld()) + && player.getLocation().distanceSquared(location) <= NpcManager.TRACKING_RANGE_SQUARED; + } + + public List getViewers(UUID npcUuid) { + PlayerNpc npc = this.npcs.get(npcUuid); + if (npc == null) { + return Collections.emptyList(); + } + + List viewers = new ArrayList<>(); + for (UUID viewerUuid : npc.getViewers()) { + Player player = Bukkit.getPlayer(viewerUuid); + if (player != null) { + viewers.add(player); + } + } + + return viewers; + } + private void loadOrSpawnDefaults() { if (!this.store.exists()) { this.spawnDefaultNpcs(); @@ -184,9 +248,7 @@ private void spawnDefaultNpcs() { PlayerNpc playerNpc = new PlayerNpc(npc.getUUID(), name, location.clone(), npc); this.npcs.put(playerNpc.getUuid(), playerNpc); - for (ServerPlayer viewer : server.getPlayerList().getPlayers()) { - this.showNpc(viewer, playerNpc); - } + this.updateVisibility(); return playerNpc; } @@ -220,9 +282,14 @@ private void hideNpc(ServerPlayer viewer, PlayerNpc npc) { } private void despawnNpcs(PlayerNpc npc) { - for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) { - this.hideNpc(player, npc); + for (UUID viewerUuid : npc.getViewers()) { + Player player = Bukkit.getPlayer(viewerUuid); + if (player != null) { + this.hideNpc(((CraftPlayer) player).getHandle(), npc); + } } + + npc.getViewers().clear(); } } diff --git a/example/bukkit/nms/src/main/java/com/lunarclient/apollo/example/nms/NpcViewerListener.java b/example/bukkit/nms/src/main/java/com/lunarclient/apollo/example/nms/NpcViewerListener.java new file mode 100644 index 00000000..479e3eeb --- /dev/null +++ b/example/bukkit/nms/src/main/java/com/lunarclient/apollo/example/nms/NpcViewerListener.java @@ -0,0 +1,33 @@ +/* + * This file is part of Apollo, licensed under the MIT License. + * + * Copyright (c) 2026 Moonsworth + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.lunarclient.apollo.example.nms; + +import org.bukkit.entity.Player; + +@FunctionalInterface +public interface NpcViewerListener { + + void onNpcShown(Player viewer, PlayerNpc npc); + +} diff --git a/example/bukkit/nms/src/main/java/com/lunarclient/apollo/example/nms/PlayerNpc.java b/example/bukkit/nms/src/main/java/com/lunarclient/apollo/example/nms/PlayerNpc.java index 9bdde6f9..fa464bc3 100644 --- a/example/bukkit/nms/src/main/java/com/lunarclient/apollo/example/nms/PlayerNpc.java +++ b/example/bukkit/nms/src/main/java/com/lunarclient/apollo/example/nms/PlayerNpc.java @@ -24,13 +24,16 @@ package com.lunarclient.apollo.example.nms; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.UUID; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; import net.minecraft.server.level.ServerPlayer; import org.bukkit.Location; +import org.jetbrains.annotations.Nullable; @Getter @RequiredArgsConstructor @@ -40,12 +43,26 @@ public final class PlayerNpc { private final String name; private final Location location; private final ServerPlayer handle; + private final Set viewers = new HashSet<>(); @Setter private List cosmetics = new ArrayList<>(); + @Setter + @Nullable + private ActiveEmote activeEmote; + public int getEntityId() { return this.handle.getId(); } + @Getter + @RequiredArgsConstructor + public static final class ActiveEmote { + + private final int emoteId; + private final int metadata; + + } + } diff --git a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/listener/ApolloPlayerProtoListener.java b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/listener/ApolloPlayerProtoListener.java index a9aee632..6e55ce2a 100644 --- a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/listener/ApolloPlayerProtoListener.java +++ b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/listener/ApolloPlayerProtoListener.java @@ -24,6 +24,9 @@ package com.lunarclient.apollo.example.proto.listener; import com.lunarclient.apollo.example.ApolloExamplePlugin; +import com.lunarclient.apollo.example.module.impl.CosmeticExample; +import com.lunarclient.apollo.example.nms.CommandCosmetic; +import com.lunarclient.apollo.example.nms.PlayerNpc; import com.lunarclient.apollo.example.proto.util.ProtobufPacketUtil; import com.lunarclient.apollo.player.v1.UpdatePlayerWorldMessage; import java.util.HashSet; @@ -79,6 +82,26 @@ private void onRegisterChannel(PlayerRegisterChannelEvent event) { PLAYERS_RUNNING_APOLLO.add(player.getUniqueId()); player.sendMessage("You are using LunarClient!"); + + this.applyNpcCosmetics(player); + } + + private void applyNpcCosmetics(Player player) { + CosmeticExample cosmeticExample = this.plugin.getCosmeticExample(); + if (cosmeticExample == null) { + return; + } + + for (PlayerNpc npc : this.plugin.getNpcManager().getNpcs()) { + for (CommandCosmetic spec : npc.getCosmetics()) { + cosmeticExample.equipNpcCosmeticToViewer(player, npc.getUuid(), spec); + } + + PlayerNpc.ActiveEmote emote = npc.getActiveEmote(); + if (emote != null && npc.getViewers().contains(player.getUniqueId())) { + cosmeticExample.startNpcEmoteToViewer(player, npc.getUuid(), emote.getEmoteId(), emote.getMetadata()); + } + } } @EventHandler diff --git a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/CosmeticProtoExample.java b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/CosmeticProtoExample.java index e83851d7..f66f03aa 100644 --- a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/CosmeticProtoExample.java +++ b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/CosmeticProtoExample.java @@ -24,11 +24,13 @@ package com.lunarclient.apollo.example.proto.module; import com.google.common.collect.Lists; +import com.lunarclient.apollo.cosmetic.v1.BodyOptions; import com.lunarclient.apollo.cosmetic.v1.CloakOptions; import com.lunarclient.apollo.cosmetic.v1.Cosmetic; import com.lunarclient.apollo.cosmetic.v1.DisplaySprayMessage; import com.lunarclient.apollo.cosmetic.v1.Emote; import com.lunarclient.apollo.cosmetic.v1.EquipNpcCosmeticsMessage; +import com.lunarclient.apollo.cosmetic.v1.HatOptions; import com.lunarclient.apollo.cosmetic.v1.PetOptions; import com.lunarclient.apollo.cosmetic.v1.RemoveSprayMessage; import com.lunarclient.apollo.cosmetic.v1.ResetNpcCosmeticsMessage; @@ -38,6 +40,7 @@ import com.lunarclient.apollo.cosmetic.v1.StopNpcEmoteMessage; import com.lunarclient.apollo.cosmetic.v1.UnequipNpcCosmeticsMessage; import com.lunarclient.apollo.example.module.impl.CosmeticExample; +import com.lunarclient.apollo.example.nms.CommandCosmetic; import com.lunarclient.apollo.example.proto.util.ProtobufPacketUtil; import com.lunarclient.apollo.example.proto.util.ProtobufUtil; import com.lunarclient.apollo.packetenrichment.v1.Direction; @@ -109,6 +112,51 @@ public void equipNpcCosmeticsInternal(Player viewer, UUID npcUuid, List ProtobufPacketUtil.broadcastPacket(message); } + @Override + public void equipNpcCosmeticInternal(Player viewer, UUID npcUuid, CommandCosmetic cosmetic) { + ProtobufPacketUtil.broadcastPacket(this.createEquipMessage(npcUuid, cosmetic)); + } + + @Override + public void equipNpcCosmeticToViewer(Player viewer, UUID npcUuid, CommandCosmetic cosmetic) { + ProtobufPacketUtil.sendPacket(viewer, this.createEquipMessage(npcUuid, cosmetic)); + } + + private EquipNpcCosmeticsMessage createEquipMessage(UUID npcUuid, CommandCosmetic cosmetic) { + Cosmetic.Builder cosmeticBuilder = Cosmetic.newBuilder() + .setId(cosmetic.getId()); + + CommandCosmetic.Options options = cosmetic.getOptions(); + if (options instanceof CommandCosmetic.Hat) { + CommandCosmetic.Hat hat = (CommandCosmetic.Hat) options; + cosmeticBuilder.setHatOptions(HatOptions.newBuilder() + .setShowOverHelmet(hat.isShowOverHelmet()) + .setShowOverSkinLayer(hat.isShowOverSkinLayer()) + .setHeightOffset(hat.getHeightOffset()) + .build()); + } else if (options instanceof CommandCosmetic.Cloak) { + cosmeticBuilder.setCloakOptions(CloakOptions.newBuilder() + .setUseClothPhysics(((CommandCosmetic.Cloak) options).isUseClothPhysics()) + .build()); + } else if (options instanceof CommandCosmetic.Pet) { + cosmeticBuilder.setPetOptions(PetOptions.newBuilder() + .setFlipShoulder(((CommandCosmetic.Pet) options).isFlipShoulder()) + .build()); + } else if (options instanceof CommandCosmetic.Body) { + CommandCosmetic.Body body = (CommandCosmetic.Body) options; + cosmeticBuilder.setBodyOptions(BodyOptions.newBuilder() + .setShowOverChestplate(body.isShowOverChestplate()) + .setShowOverLeggings(body.isShowOverLeggings()) + .setShowOverBoots(body.isShowOverBoots()) + .build()); + } + + return EquipNpcCosmeticsMessage.newBuilder() + .setNpcUuid(ProtobufUtil.createUuidProto(npcUuid)) + .addCosmetics(cosmeticBuilder.build()) + .build(); + } + @Override public void unequipNpcCosmeticsExample(Player viewer, UUID npcUuid) { List cosmeticIds = Lists.newArrayList(434, 3654, 5095, 3, 3977); @@ -153,7 +201,7 @@ public void startNpcEmoteExample(Player viewer, UUID npcUuid) { } @Override - public void startNpcEmoteInternal(Player viewer, UUID npcUuid, int emoteId, int metadata) { + public void startNpcEmoteToViewer(Player viewer, UUID npcUuid, int emoteId, int metadata) { StartNpcEmoteMessage message = StartNpcEmoteMessage.newBuilder() .setNpcUuid(ProtobufUtil.createUuidProto(npcUuid)) .setEmote(Emote.newBuilder() @@ -162,7 +210,7 @@ public void startNpcEmoteInternal(Player viewer, UUID npcUuid, int emoteId, int .build()) .build(); - ProtobufPacketUtil.broadcastPacket(message); + ProtobufPacketUtil.sendPacket(viewer, message); } @Override @@ -174,6 +222,15 @@ public void stopNpcEmoteExample(Player viewer, UUID npcUuid) { ProtobufPacketUtil.broadcastPacket(message); } + @Override + public void stopNpcEmoteToViewer(Player viewer, UUID npcUuid) { + StopNpcEmoteMessage message = StopNpcEmoteMessage.newBuilder() + .setNpcUuid(ProtobufUtil.createUuidProto(npcUuid)) + .build(); + + ProtobufPacketUtil.sendPacket(viewer, message); + } + @Override public void resetNpcEmotesExample() { ResetNpcEmotesMessage message = ResetNpcEmotesMessage.getDefaultInstance();