diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4a465fe7..f65bf2a7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -41,7 +41,7 @@ jobs: fi - name: Gradle Publish - if: "${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/version/') || github.ref == 'refs/heads/feature/totem-counter-module' }}" + if: "${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/version/') }}" run: ./gradlew publish - name: Gradle Release diff --git a/api/src/main/java/com/lunarclient/apollo/event/modsetting/ApolloUpdateModOptionEvent.java b/api/src/main/java/com/lunarclient/apollo/event/modsetting/ApolloUpdateModOptionEvent.java index d8724a6d..0199fabb 100644 --- a/api/src/main/java/com/lunarclient/apollo/event/modsetting/ApolloUpdateModOptionEvent.java +++ b/api/src/main/java/com/lunarclient/apollo/event/modsetting/ApolloUpdateModOptionEvent.java @@ -24,17 +24,27 @@ package com.lunarclient.apollo.event.modsetting; import com.lunarclient.apollo.event.Event; +import com.lunarclient.apollo.module.RestrictedAccess; +import com.lunarclient.apollo.module.modsetting.ModSettingModule; import com.lunarclient.apollo.option.Option; import com.lunarclient.apollo.player.ApolloPlayer; import lombok.Value; import org.jetbrains.annotations.Nullable; /** - * Represents an event that is fired when an option is updated. + * Represents an event that is fired when a player's Lunar Client mod option is updated. + * + *

This event is fired for live updates only, not for the options listed in a + * {@link ModSettingModule#requestModStatus(ApolloPlayer)} response.

+ * + *

This event is part of the private Mod Status API of {@link ModSettingModule}. Lunar Client + * only reports mod status to servers that have been granted access, so this event is never + * fired on other servers.

* * @since 1.2.1 */ @Value +@RestrictedAccess public class ApolloUpdateModOptionEvent implements Event { /** diff --git a/api/src/main/java/com/lunarclient/apollo/module/RestrictedAccess.java b/api/src/main/java/com/lunarclient/apollo/module/RestrictedAccess.java new file mode 100644 index 00000000..46c68062 --- /dev/null +++ b/api/src/main/java/com/lunarclient/apollo/module/RestrictedAccess.java @@ -0,0 +1,48 @@ +/* + * 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; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks access restricted Apollo API, either a whole module or part of a public module. + * + *

Restricted API only works on servers approved by the Lunar Client team. Lunar Client + * enforces this, so without approval the API does not throw, it just has no effect.

+ * + *

See Private Modules + * to request access.

+ * + * @since 1.3.0 + */ +@Documented +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +@Retention(RetentionPolicy.CLASS) +public @interface RestrictedAccess { + // Marker annotation. +} diff --git a/api/src/main/java/com/lunarclient/apollo/module/cosmetic/CosmeticModule.java b/api/src/main/java/com/lunarclient/apollo/module/cosmetic/CosmeticModule.java index 45783a13..dd20a602 100644 --- a/api/src/main/java/com/lunarclient/apollo/module/cosmetic/CosmeticModule.java +++ b/api/src/main/java/com/lunarclient/apollo/module/cosmetic/CosmeticModule.java @@ -26,6 +26,7 @@ import com.lunarclient.apollo.common.location.ApolloBlockLocation; import com.lunarclient.apollo.module.ApolloModule; import com.lunarclient.apollo.module.ModuleDefinition; +import com.lunarclient.apollo.module.RestrictedAccess; import com.lunarclient.apollo.module.cosmetic.options.BodyOptions; import com.lunarclient.apollo.module.cosmetic.options.CloakOptions; import com.lunarclient.apollo.module.cosmetic.options.CosmeticOptions; @@ -41,8 +42,11 @@ /** * Represents the cosmetic module. * + *

Access to this module is {@link RestrictedAccess restricted}.

+ * * @since 1.2.6 */ +@RestrictedAccess @ApiStatus.NonExtendable @ModuleDefinition(id = "cosmetic", name = "Cosmetic") public abstract class CosmeticModule extends ApolloModule { diff --git a/api/src/main/java/com/lunarclient/apollo/module/modsetting/ModOptionStatus.java b/api/src/main/java/com/lunarclient/apollo/module/modsetting/ModOptionStatus.java new file mode 100644 index 00000000..a294baf5 --- /dev/null +++ b/api/src/main/java/com/lunarclient/apollo/module/modsetting/ModOptionStatus.java @@ -0,0 +1,57 @@ +/* + * 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.modsetting; + +import com.lunarclient.apollo.module.RestrictedAccess; +import com.lunarclient.apollo.option.Option; +import lombok.Builder; +import lombok.Getter; + +/** + * Represents the current value of a Lunar Client mod option. + * + * @since 1.3.0 + */ +@Getter +@Builder +@RestrictedAccess +public final class ModOptionStatus { + + /** + * Returns the mod {@link Option}. + * + * @return the option + * @since 1.3.0 + */ + Option option; + + /** + * Returns the current {@link Object} value of the option. + * + * @return the value + * @since 1.3.0 + */ + Object value; + +} diff --git a/api/src/main/java/com/lunarclient/apollo/module/modsetting/ModSettingModule.java b/api/src/main/java/com/lunarclient/apollo/module/modsetting/ModSettingModule.java index c166aed9..22fff0ce 100644 --- a/api/src/main/java/com/lunarclient/apollo/module/modsetting/ModSettingModule.java +++ b/api/src/main/java/com/lunarclient/apollo/module/modsetting/ModSettingModule.java @@ -27,6 +27,7 @@ import com.lunarclient.apollo.async.Future; import com.lunarclient.apollo.module.ApolloModule; import com.lunarclient.apollo.module.ModuleDefinition; +import com.lunarclient.apollo.module.RestrictedAccess; import com.lunarclient.apollo.option.Option; import com.lunarclient.apollo.player.ApolloPlayer; import com.lunarclient.apollo.util.ConfigTarget; @@ -65,6 +66,7 @@ public boolean isClientNotify() { * @return the value of the option * @since 1.2.1 */ + @RestrictedAccess public abstract > T getStatus(@NotNull ApolloPlayer player, @NonNull C option); /** @@ -76,4 +78,16 @@ public boolean isClientNotify() { */ public abstract Future requestInstalledMods(ApolloPlayer player); + /** + * Sends the {@link ModStatusRequest} to the {@link ApolloPlayer}. + * + *

The response also updates {@link #getStatus(ApolloPlayer, Option)}.

+ * + * @param player the player + * @return future to be listened to for errors/success + * @since 1.3.0 + */ + @RestrictedAccess + public abstract Future requestModStatus(ApolloPlayer player); + } diff --git a/api/src/main/java/com/lunarclient/apollo/module/modsetting/ModStatusRequest.java b/api/src/main/java/com/lunarclient/apollo/module/modsetting/ModStatusRequest.java new file mode 100644 index 00000000..1c5b635b --- /dev/null +++ b/api/src/main/java/com/lunarclient/apollo/module/modsetting/ModStatusRequest.java @@ -0,0 +1,39 @@ +/* + * 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.modsetting; + +import com.lunarclient.apollo.module.RestrictedAccess; +import com.lunarclient.apollo.roundtrip.ApolloRequest; +import lombok.Builder; + +/** + * Represents the mod status request. + * + * @since 1.3.0 + */ +@Builder +@RestrictedAccess +public final class ModStatusRequest extends ApolloRequest { + +} diff --git a/api/src/main/java/com/lunarclient/apollo/module/modsetting/ModStatusResponse.java b/api/src/main/java/com/lunarclient/apollo/module/modsetting/ModStatusResponse.java new file mode 100644 index 00000000..f31373ab --- /dev/null +++ b/api/src/main/java/com/lunarclient/apollo/module/modsetting/ModStatusResponse.java @@ -0,0 +1,54 @@ +/* + * 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.modsetting; + +import com.lunarclient.apollo.module.RestrictedAccess; +import com.lunarclient.apollo.roundtrip.pagination.ApolloPaginatedResponse; +import java.util.List; +import java.util.UUID; +import lombok.Getter; +import lombok.experimental.SuperBuilder; + +/** + * Represents the mod status response. + * + *

The elements are the mod options the player has changed from their default + * value. Any option that is not listed is at its default value.

+ * + * @since 1.3.0 + */ +@Getter +@SuperBuilder +@RestrictedAccess +public final class ModStatusResponse extends ApolloPaginatedResponse { + + @Override + public ApolloPaginatedResponse combine(UUID packetId, List elements) { + return ModStatusResponse.builder() + .packetId(packetId) + .elements(elements) + .build(); + } + +} diff --git a/common/src/main/java/com/lunarclient/apollo/module/modsettings/ModSettingModuleImpl.java b/common/src/main/java/com/lunarclient/apollo/module/modsettings/ModSettingModuleImpl.java index 50e1c963..278b33ca 100644 --- a/common/src/main/java/com/lunarclient/apollo/module/modsettings/ModSettingModuleImpl.java +++ b/common/src/main/java/com/lunarclient/apollo/module/modsettings/ModSettingModuleImpl.java @@ -39,12 +39,16 @@ import com.lunarclient.apollo.modsetting.v1.Mod; import com.lunarclient.apollo.module.modsetting.InstalledModsRequest; import com.lunarclient.apollo.module.modsetting.InstalledModsResponse; +import com.lunarclient.apollo.module.modsetting.ModOptionStatus; import com.lunarclient.apollo.module.modsetting.ModSettingModule; +import com.lunarclient.apollo.module.modsetting.ModStatusRequest; +import com.lunarclient.apollo.module.modsetting.ModStatusResponse; import com.lunarclient.apollo.network.NetworkOptions; import com.lunarclient.apollo.option.Option; import com.lunarclient.apollo.option.StatusOptionsImpl; import com.lunarclient.apollo.player.AbstractApolloPlayer; import com.lunarclient.apollo.player.ApolloPlayer; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -89,6 +93,17 @@ public Future requestInstalledMods(@NotNull ApolloPlayer return ((AbstractApolloPlayer) player).sendRoundTripPacket(request, requestProto); } + @Override + public Future requestModStatus(@NotNull ApolloPlayer player) { + ModStatusRequest request = ModStatusRequest.builder().build(); + + com.lunarclient.apollo.modsetting.v1.ModStatusRequest requestProto = com.lunarclient.apollo.modsetting.v1.ModStatusRequest.newBuilder() + .setRequestId(ByteString.copyFromUtf8(request.getRequestId().toString())) + .build(); + + return ((AbstractApolloPlayer) player).sendRoundTripPacket(request, requestProto); + } + private void onReceivePacket(ApolloReceivePacketEvent event) { ApolloPlayer player = event.getPlayer(); Any any = event.getPacket(); @@ -112,6 +127,17 @@ private void onReceivePacket(ApolloReceivePacketEvent event) { ApolloManager.getRoundtripManager().handleResponse(response); }); + + event.unpack(com.lunarclient.apollo.modsetting.v1.ModStatusResponse.class).ifPresent(packet -> { + ModStatusResponse response = ModStatusResponse.builder() + .packetId(UUID.fromString(packet.getRequestId().toStringUtf8())) + .page(packet.getPage()) + .totalPages(packet.getTotalPages()) + .elements(this.updateModStatus(player, packet)) + .build(); + + ApolloManager.getRoundtripManager().handleResponse(response); + }); } private LunarClientMod fromProtobuf(LunarClientModType type, Mod mod) { @@ -188,4 +214,32 @@ public void updateOptions(ApolloPlayer player, Map properties, bo } } + private List updateModStatus(ApolloPlayer player, com.lunarclient.apollo.modsetting.v1.ModStatusResponse packet) { + StatusOptionsImpl statusOptions = ApolloManager.getModsManager().getPlayerOptions(); + + if (packet.getPage() == 0) { + statusOptions.getPlayerOptions().remove(player.getUniqueId()); + } + + List elements = new ArrayList<>(); + for (com.lunarclient.apollo.modsetting.v1.ModOptionStatus status : packet.getModOptionsList()) { + Option option = statusOptions.getRegistry().get(status.getKey()); + + // Option exists on the client but doesn't exist in the API yet. + if (option == null) { + continue; + } + + Object value = NetworkOptions.unwrapValue(status.getValue(), option.getTypeToken().getType()); + statusOptions.set(player, option, value); + + elements.add(ModOptionStatus.builder() + .option(option) + .value(value) + .build()); + } + + return elements; + } + } diff --git a/common/src/main/java/com/lunarclient/apollo/player/ApolloPlayerManagerImpl.java b/common/src/main/java/com/lunarclient/apollo/player/ApolloPlayerManagerImpl.java index 329c519c..8e2af840 100644 --- a/common/src/main/java/com/lunarclient/apollo/player/ApolloPlayerManagerImpl.java +++ b/common/src/main/java/com/lunarclient/apollo/player/ApolloPlayerManagerImpl.java @@ -23,7 +23,6 @@ */ package com.lunarclient.apollo.player; -import com.google.protobuf.Value; import com.lunarclient.apollo.Apollo; import com.lunarclient.apollo.client.version.LunarClientVersion; import com.lunarclient.apollo.client.version.MinecraftVersion; @@ -31,8 +30,6 @@ import com.lunarclient.apollo.event.player.ApolloPlayerHandshakeEvent; import com.lunarclient.apollo.event.player.ApolloRegisterPlayerEvent; import com.lunarclient.apollo.event.player.ApolloUnregisterPlayerEvent; -import com.lunarclient.apollo.module.modsetting.ModSettingModule; -import com.lunarclient.apollo.module.modsettings.ModSettingModuleImpl; import com.lunarclient.apollo.module.paynow.PayNowEmbeddedCheckoutSupport; import com.lunarclient.apollo.module.tebex.TebexEmbeddedCheckoutSupport; import com.lunarclient.apollo.network.NetworkOptions; @@ -149,15 +146,6 @@ public void handlePlayerHandshake(@NotNull ApolloPlayer player, @NotNull PlayerH apolloPlayer.setTebexEmbeddedCheckoutSupport(tebexEmbeddedCheckoutSupport); apolloPlayer.setPayNowEmbeddedCheckoutSupport(payNowEmbeddedCheckoutSupport); - Map modStatus = message.getModStatusMap(); - if (!modStatus.isEmpty()) { - ModSettingModuleImpl modSettingModule = (ModSettingModuleImpl) Apollo.getModuleManager().getModule(ModSettingModule.class); - - if (modSettingModule.isEnabled()) { - modSettingModule.updateOptions(apolloPlayer, modStatus, false); - } - } - ApolloPlayerHandshakeEvent event = new ApolloPlayerHandshakeEvent( player, minecraftVersion, lunarClientVersion, new ArrayList<>(), tebexEmbeddedCheckoutSupport, payNowEmbeddedCheckoutSupport diff --git a/docs/acceptable-use.mdx b/docs/acceptable-use.mdx index 49226e1c..e640360a 100644 --- a/docs/acceptable-use.mdx +++ b/docs/acceptable-use.mdx @@ -13,7 +13,7 @@ Apollo includes a wide range of publicly available modules that can be used by a The intent of Apollo is to encourage servers to integrate directly with Lunar Client to create better player experiences. ## Private & Future Modules -Apollo includes [private modules](/apollo/developers/private-modules) that require additional approval before use. +Apollo includes [private modules and features](/apollo/developers/private-modules) that require additional approval before use. We typically reserve private modules for features that: * Something that requires backing infrastructure that we're unable to provide for free. diff --git a/docs/developers/events.mdx b/docs/developers/events.mdx index 358b356a..5287787e 100644 --- a/docs/developers/events.mdx +++ b/docs/developers/events.mdx @@ -186,6 +186,8 @@ _Called when a Lunar Client Mod option is updated._ | `Option option` | The option that was updated. | | `Object value` | The new value of the option. | +This event is part of the private [Mod Status API](/apollo/developers/private-modules/modstatus) and is only called on servers that have been granted access. +
diff --git a/docs/developers/lightweight/json/roundtrip-packets.mdx b/docs/developers/lightweight/json/roundtrip-packets.mdx index cd9c9666..a4966284 100644 --- a/docs/developers/lightweight/json/roundtrip-packets.mdx +++ b/docs/developers/lightweight/json/roundtrip-packets.mdx @@ -57,6 +57,9 @@ public class ApolloRoundtripJsonListener implements PluginMessageListener { } else if ("lunarclient.apollo.modsetting.v1.InstalledModsResponse".equals(type)) { UUID requestId = UUID.fromString(payload.get("request_id").getAsString().replace("+", "-")); this.parseModGroups(requestId, payload); + } else if ("lunarclient.apollo.modsetting.v1.ModStatusResponse".equals(type)) { + UUID requestId = UUID.fromString(payload.get("request_id").getAsString().replace("+", "-")); + this.parseModStatus(requestId, payload); } } @@ -124,6 +127,19 @@ public class ApolloRoundtripJsonListener implements PluginMessageListener { this.handlePage(requestId, accumulated, response); } + private void parseModStatus(UUID requestId, JsonObject response) { + List accumulated = this.paginatedAccumulator.computeIfAbsent(requestId, k -> new ArrayList<>()); + + JsonArray modOptions = response.getAsJsonArray("mod_options"); + if (modOptions != null) { + for (JsonElement option : modOptions) { + accumulated.add(option.getAsJsonObject()); + } + } + + this.handlePage(requestId, accumulated, response); + } + private void handlePage(UUID requestId, List accumulated, JsonObject response) { int page = response.has("page") ? response.get("page").getAsInt() : 0; int totalPages = response.has("total_pages") ? response.get("total_pages").getAsInt() : 1; diff --git a/docs/developers/lightweight/protobuf/roundtrip-packets.mdx b/docs/developers/lightweight/protobuf/roundtrip-packets.mdx index eacbddde..f486492d 100644 --- a/docs/developers/lightweight/protobuf/roundtrip-packets.mdx +++ b/docs/developers/lightweight/protobuf/roundtrip-packets.mdx @@ -15,6 +15,8 @@ public class ApolloRoundtripProtoListener implements PluginMessageListener { private final Map>> roundTripPacketFutures = new ConcurrentHashMap<>(); private final Map>> paginatedFutures = new ConcurrentHashMap<>(); private final Map> paginatedAccumulator = new ConcurrentHashMap<>(); + private final Map>> modStatusFutures = new ConcurrentHashMap<>(); + private final Map> modStatusAccumulator = new ConcurrentHashMap<>(); private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); public ApolloRoundtripProtoListener(ApolloExamplePlugin plugin) { @@ -39,6 +41,10 @@ public class ApolloRoundtripProtoListener implements PluginMessageListener { InstalledModsResponse message = any.unpack(InstalledModsResponse.class); UUID requestId = UUID.fromString(message.getRequestId().toStringUtf8()); this.handlePagedResponse(requestId, message); + } else if (any.is(ModStatusResponse.class)) { + ModStatusResponse message = any.unpack(ModStatusResponse.class); + UUID requestId = UUID.fromString(message.getRequestId().toStringUtf8()); + this.handleModStatusPage(requestId, message); } } catch (InvalidProtocolBufferException e) { @@ -85,6 +91,38 @@ public class ApolloRoundtripProtoListener implements PluginMessageListener { return future; } + public CompletableFuture> sendModStatusRequest(Player player, UUID requestId, ModStatusRequest request) { + ProtobufPacketUtil.sendPacket(player, request); + + CompletableFuture> future = new CompletableFuture<>(); + this.modStatusFutures.put(requestId, future); + + ScheduledFuture timeoutTask = this.executorService.schedule(() -> + future.completeExceptionally(new TimeoutException("Response timed out")), + 10, TimeUnit.SECONDS + ); + + future.whenComplete((result, throwable) -> { + timeoutTask.cancel(false); + this.modStatusAccumulator.remove(requestId); + }); + + return future; + } + + private void handleModStatusPage(UUID requestId, ModStatusResponse response) { + List accumulated = this.modStatusAccumulator.computeIfAbsent(requestId, k -> new ArrayList<>()); + accumulated.addAll(response.getModOptionsList()); + + if (response.getPage() == response.getTotalPages() - 1) { + this.modStatusAccumulator.remove(requestId); + CompletableFuture> future = this.modStatusFutures.remove(requestId); + if (future != null) { + future.complete(accumulated); + } + } + } + private void handlePagedResponse(UUID requestId, InstalledModsResponse response) { List accumulated = this.paginatedAccumulator.computeIfAbsent(requestId, k -> new ArrayList<>()); diff --git a/docs/developers/modules.mdx b/docs/developers/modules.mdx index cdf6d7f8..a6d17d16 100644 --- a/docs/developers/modules.mdx +++ b/docs/developers/modules.mdx @@ -4,6 +4,8 @@ Apollo provides a wide-range of public modules that allow you to integrate direc These modules are available to all servers using Apollo and do not require any pre-approval. You are still expected to follow our [Acceptable Use](/apollo/acceptable-use) guidelines. +Some public modules also include [private features](/apollo/developers/private-modules) that require approval. These are noted on each module's page. + ### Available Modules 🔗 [Auto Text Hotkey](/apollo/developers/modules/autotexthotkey)
diff --git a/docs/developers/modules/inventory.mdx b/docs/developers/modules/inventory.mdx index fcd486eb..9d1ef296 100644 --- a/docs/developers/modules/inventory.mdx +++ b/docs/developers/modules/inventory.mdx @@ -1114,7 +1114,7 @@ The same layout built with raw payloads: [apollo-protos implementation](https:// ### Minigame Layout -A minigame overlay: map info, a live kill counter and mod-aware Show Map / Waypoints buttons using client actions, profile, settings and back to lobby buttons. +A minigame overlay: map info, a live kill counter and mod-aware Show Map / Waypoints buttons using client actions, profile, settings and back to lobby buttons. The mod aware buttons use the private [Mod Status API](/apollo/developers/private-modules/modstatus). ![Minigame Layout](/modules/inventory/minigame.png#center) @@ -1227,77 +1227,84 @@ public static void display(InventoryModule inventoryModule, Player viewer) { .onClick(ApolloButtonAction.runCommand("/settings")) .build(); - ModSettingModule modSettingModule = Apollo.getModuleManager().getModule(ModSettingModule.class); - boolean minimapEnabled = modSettingModule.getStatus(apolloPlayer, ModMinimap.ENABLED); - boolean waypointsEnabled = modSettingModule.getStatus(apolloPlayer, ModWaypoints.ENABLED); + List buttons = new ArrayList<>(Arrays.asList(mapInfo, kills, lobby, profile, settings)); + buttons.addAll(modButtons(apolloPlayer)); + inventoryModule.displayInventoryButtons(apolloPlayer, buttons); + }); +} - InventoryButton.InventoryButtonBuilder showMapBuilder = InventoryButton.builder() - .id("show-map") - .inventoryType(InventoryType.PLAYER) - .box(InventoryButtonBox.RIGHT) - .position(HudPosition.of(6, 52)) - .size(InventoryButton.SIZE_WIDE) - .shape(ApolloButtonShape.ROUNDED_SQUARE) - .content(ApolloButtonContent.builder() - .append(ItemStackIcon.builder() - .itemName("FILLED_MAP") - .build()) - .append(Component.text("Show Map")) - .scale(1.0F) - .build()); - - if (minimapEnabled) { - showMapBuilder - .backgroundColor(InventoryButton.DEFAULT_BACKGROUND_COLOR) - .borderColor(InventoryButton.DEFAULT_BORDER_COLOR) - .tooltip(ApolloButtonTooltip.of( - Component.text("Show Map", NamedTextColor.AQUA), - Component.text("Open the fullscreen minimap view", NamedTextColor.GRAY))) - .onClick(ApolloButtonAction.clientAction(ApolloButtonClientAction.OPEN_MINIMAP_VIEW)); - } else { - showMapBuilder - .backgroundColor(new Color(224, 64, 64, 128)) - .borderColor(new Color(255, 200, 200, 140)) - .tooltip(ApolloButtonTooltip.of(Component.text("Minimap mod must be enabled", NamedTextColor.RED))); - } +// Displaying these again with the same ids replaces the previous Show Map / Waypoints buttons +public static List modButtons(ApolloPlayer apolloPlayer) { + // getStatus is part of the private Mod Status API: https://lunarclient.dev/apollo/developers/private-modules/modstatus + ModSettingModule modSettingModule = Apollo.getModuleManager().getModule(ModSettingModule.class); + boolean minimapEnabled = modSettingModule.getStatus(apolloPlayer, ModMinimap.ENABLED); + boolean waypointsEnabled = modSettingModule.getStatus(apolloPlayer, ModWaypoints.ENABLED); + + InventoryButton.InventoryButtonBuilder showMapBuilder = InventoryButton.builder() + .id("show-map") + .inventoryType(InventoryType.PLAYER) + .box(InventoryButtonBox.RIGHT) + .position(HudPosition.of(6, 52)) + .size(InventoryButton.SIZE_WIDE) + .shape(ApolloButtonShape.ROUNDED_SQUARE) + .content(ApolloButtonContent.builder() + .append(ItemStackIcon.builder() + .itemName("FILLED_MAP") + .build()) + .append(Component.text("Show Map")) + .scale(1.0F) + .build()); - InventoryButton showMap = showMapBuilder.build(); + if (minimapEnabled) { + showMapBuilder + .backgroundColor(InventoryButton.DEFAULT_BACKGROUND_COLOR) + .borderColor(InventoryButton.DEFAULT_BORDER_COLOR) + .tooltip(ApolloButtonTooltip.of( + Component.text("Show Map", NamedTextColor.AQUA), + Component.text("Open the fullscreen minimap view", NamedTextColor.GRAY))) + .onClick(ApolloButtonAction.clientAction(ApolloButtonClientAction.OPEN_MINIMAP_VIEW)); + } else { + showMapBuilder + .backgroundColor(new Color(224, 64, 64, 128)) + .borderColor(new Color(255, 200, 200, 140)) + .tooltip(ApolloButtonTooltip.of(Component.text("Minimap mod must be enabled", NamedTextColor.RED))); + } - InventoryButton.InventoryButtonBuilder waypointsBuilder = InventoryButton.builder() - .id("waypoints") - .inventoryType(InventoryType.PLAYER) - .box(InventoryButtonBox.RIGHT) - .position(HudPosition.of(6, 84)) - .size(InventoryButton.SIZE_WIDE) - .shape(ApolloButtonShape.ROUNDED_SQUARE) - .content(ApolloButtonContent.builder() - .append(ItemStackIcon.builder() - .itemName("LODESTONE") - .build()) - .append(Component.text("Waypoints")) - .scale(1.0F) - .build()); - - if (waypointsEnabled) { - waypointsBuilder - .backgroundColor(InventoryButton.DEFAULT_BACKGROUND_COLOR) - .borderColor(InventoryButton.DEFAULT_BORDER_COLOR) - .tooltip(ApolloButtonTooltip.of( - Component.text("Waypoints", NamedTextColor.GOLD), - Component.text("Manage your waypoints", NamedTextColor.GRAY))) - .onClick(ApolloButtonAction.clientAction(ApolloButtonClientAction.OPEN_WAYPOINTS_MENU)); - } else { - waypointsBuilder - .backgroundColor(new Color(224, 64, 64, 128)) - .borderColor(new Color(255, 200, 200, 140)) - .tooltip(ApolloButtonTooltip.of(Component.text("Waypoints mod must be enabled", NamedTextColor.RED))); - } + InventoryButton showMap = showMapBuilder.build(); + + InventoryButton.InventoryButtonBuilder waypointsBuilder = InventoryButton.builder() + .id("waypoints") + .inventoryType(InventoryType.PLAYER) + .box(InventoryButtonBox.RIGHT) + .position(HudPosition.of(6, 84)) + .size(InventoryButton.SIZE_WIDE) + .shape(ApolloButtonShape.ROUNDED_SQUARE) + .content(ApolloButtonContent.builder() + .append(ItemStackIcon.builder() + .itemName("LODESTONE") + .build()) + .append(Component.text("Waypoints")) + .scale(1.0F) + .build()); - InventoryButton waypoints = waypointsBuilder.build(); + if (waypointsEnabled) { + waypointsBuilder + .backgroundColor(InventoryButton.DEFAULT_BACKGROUND_COLOR) + .borderColor(InventoryButton.DEFAULT_BORDER_COLOR) + .tooltip(ApolloButtonTooltip.of( + Component.text("Waypoints", NamedTextColor.GOLD), + Component.text("Manage your waypoints", NamedTextColor.GRAY))) + .onClick(ApolloButtonAction.clientAction(ApolloButtonClientAction.OPEN_WAYPOINTS_MENU)); + } else { + waypointsBuilder + .backgroundColor(new Color(224, 64, 64, 128)) + .borderColor(new Color(255, 200, 200, 140)) + .tooltip(ApolloButtonTooltip.of(Component.text("Waypoints mod must be enabled", NamedTextColor.RED))); + } - inventoryModule.displayInventoryButtons(apolloPlayer, Arrays.asList(mapInfo, kills, lobby, - profile, settings, showMap, waypoints)); - }); + InventoryButton waypoints = waypointsBuilder.build(); + + return Arrays.asList(showMap, waypoints); } private static int getKills(ApolloPlayer apolloViewer) { diff --git a/docs/developers/modules/modsetting.mdx b/docs/developers/modules/modsetting.mdx index 127b46e3..215ff90e 100644 --- a/docs/developers/modules/modsetting.mdx +++ b/docs/developers/modules/modsetting.mdx @@ -12,7 +12,7 @@ You can find all available mods and their options under the mods section. - Adds the ability to interact with Lunar Client mods. - Ability to enable/disable mods. - Ability to change, enable or disable mod settings within a mod. - - Ability to retrieve the current value of any mod and settings within a mod. + - Ability to retrieve the current value of any mod and settings within a mod ([private feature](/apollo/developers/private-modules/modstatus)). ![Mod Settings Module Example](/modules/modsettings/overview.png#center) @@ -22,65 +22,13 @@ You can find all available mods and their options under the mods section. ## Integration -### Setting Status API -The **Setting Status API** provides a simple way to retrieve the **current** value of any mod or any setting within a mod. +### Mod Status API +The **Mod Status API** allows you to **request** the current value of any mod option, and to listen for updates whenever one changes. -**Retrieve the current value for options** - -```java -private final ModSettingModule modSettingModule = Apollo.getModuleManager().getModule(ModSettingModule.class); - -private void printOptionStatusExample(Player player) { - Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(player.getUniqueId()); - apolloPlayerOpt.ifPresent(apolloPlayer -> { - // https://lunarclient.dev/apollo/developers/mods/waypoints#available-options - boolean waypointsEnabled = this.modSettingModule.getStatus(apolloPlayer, ModWaypoints.ENABLED); - - // https://lunarclient.dev/apollo/developers/mods/minimap#available-options - float minimapScale = this.modSettingModule.getStatus(apolloPlayer, ModMinimap.SCALE); - - // https://lunarclient.dev/apollo/developers/mods/fov#available-options - int fovDefaultFov = this.modSettingModule.getStatus(apolloPlayer, ModFov.DEFAULT_FOV); - - apolloPlayer.sendMessage(Component.text("Waypoints Enabled: ") - .append(Component.text(waypointsEnabled))); - - apolloPlayer.sendMessage(Component.text("Minimap Scale: ") - .append(Component.text(minimapScale))); - - apolloPlayer.sendMessage(Component.text("Fov Default Fov: ") - .append(Component.text(fovDefaultFov))); - }); -} -``` - -**Listening for Updates** - -Apollo fires the [ApolloUpdateModOptionEvent](/apollo/developers/events), whenever a Lunar Client Mod Option is updated. - -```java -import com.lunarclient.apollo.event.ApolloListener; -import com.lunarclient.apollo.event.EventBus; -import com.lunarclient.apollo.event.Listen; -import com.lunarclient.apollo.event.modsetting.ApolloUpdateModOptionEvent; -import java.util.Objects; -import net.kyori.adventure.text.Component; - -public class ApolloModStatusExample implements ApolloListener { - - public ApolloModStatusExample() { - EventBus.getBus().register(this); - } - - @Listen - private void onApolloUpdateModOption(ApolloUpdateModOptionEvent event) { - event.getPlayer().sendMessage(Component.text(event.getOption().getKey()) - .append(Component.text(" was updated to ")) - .append(Component.text(Objects.toString(event.getValue())))); - } - -} -``` + + The Mod Status API is a **private feature** of this module and not publicly available. The rest of the module, such as the Setting Interception API, stays public. + See [Mod Status](/apollo/developers/private-modules/modstatus) for usage, and [Private Modules](/apollo/developers/private-modules#how-to-request-access) to request access. + ### Setting Interception API The **Setting Interception API** allows you to **override** the value of any mod option. diff --git a/docs/developers/private-modules.mdx b/docs/developers/private-modules.mdx index bf57d20f..8fb7ca6a 100644 --- a/docs/developers/private-modules.mdx +++ b/docs/developers/private-modules.mdx @@ -1,18 +1,28 @@ # Private Modules -Apollo has private modules, that are access restricted extensions of the API. +Apollo has private modules and private features, which are access restricted parts of the API. -These modules are only available on pre-approved servers and will not work on servers that have not received approval. These private modules are still subject to our [Acceptable Use](/apollo/acceptable-use) guidelines. +- A **private module** is restricted as a whole, such as the Cosmetic module. +- A **private feature** is a restricted part of an otherwise public module, such as the Mod Status API of the Mod Setting module. The rest of that module stays public. + +In the Apollo API, private modules and features are marked with the `@RestrictedAccess` annotation. Access is granted separately for each private module and feature. + +These modules and features are only available on pre-approved servers and will not work on servers that have not received approval. They are still subject to our [Acceptable Use](/apollo/acceptable-use) guidelines. ### How to Request Access -We are not currently granting broad access to private modules. However, if you have a specific use-case in mind, you may request access by contacting **apollo@lunarclient.com**. +We are not currently granting broad access to private modules and features. However, if you have a specific use-case in mind, you may request access by contacting **apollo@lunarclient.com**. Please include: - Your server name & domain +- The private modules and features you need - A description of your use-case - Any relevant code snippets (if applicable) ### Available Modules 🔗 [Cosmetic](/apollo/developers/private-modules/cosmetic)
+ +### Available Features + +🔗 [Mod Status](/apollo/developers/private-modules/modstatus) (part of the [Mod Setting](/apollo/developers/modules/modsetting) module)
diff --git a/docs/developers/private-modules/_meta.json b/docs/developers/private-modules/_meta.json index 482e70cb..4c661c01 100644 --- a/docs/developers/private-modules/_meta.json +++ b/docs/developers/private-modules/_meta.json @@ -1,3 +1,4 @@ { - "cosmetic": "Cosmetic" + "cosmetic": "Cosmetic", + "modstatus": "Mod Status" } diff --git a/docs/developers/private-modules/modstatus.mdx b/docs/developers/private-modules/modstatus.mdx new file mode 100644 index 00000000..48f9fe89 --- /dev/null +++ b/docs/developers/private-modules/modstatus.mdx @@ -0,0 +1,181 @@ +import { Callout, Tab, Tabs } from 'nextra-theme-docs' + +# Mod Status + +## Overview + +The Mod Status API is part of the [Mod Setting](/apollo/developers/modules/modsetting) module and provides a simple way to retrieve the **current** value of any Lunar Client mod or setting within a mod for a player. + +- Request the player's current mod settings + - The response lists every mod option the player has changed from its default value, split into pages + - Read the value of any mod option afterwards, such as whether a mod is enabled or the minimap scale +- Listen for updates + - Get notified whenever the value of a player's mod option changes, including changes made by server overrides + + + The Mod Status API is a **private feature** of the public Mod Setting module and not publicly available. + To use this feature, you must be granted access by the Lunar Client team. [Learn More](/apollo/developers/private-modules) + + +## 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. + + +**Request Mod Status** + +```java +public void requestModStatusExample(Player viewer) { + Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId()); + + if (!apolloPlayerOpt.isPresent()) { + viewer.sendMessage("Join with Lunar Client to test this feature!"); + return; + } + + this.modSettingModule.requestModStatus(apolloPlayerOpt.get()) + .onSuccess(response -> { + List options = response.getElements() + .stream().map(status -> status.getOption().getKey() + "=" + status.getValue()) + .collect(Collectors.toList()); + + viewer.sendMessage("Found " + options.size() + " changed options: " + options); + }) + .onFailure(exception -> viewer.sendMessage("Failed to receive a response in time.")); +} +``` + +**Retrieve the current value for options** + +`getStatus` returns the values from the latest mod status response and any live updates since. + +```java +private final ModSettingModule modSettingModule = Apollo.getModuleManager().getModule(ModSettingModule.class); + +private void printOptionStatusExample(Player player) { + Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(player.getUniqueId()); + apolloPlayerOpt.ifPresent(apolloPlayer -> this.modSettingModule.requestModStatus(apolloPlayer) + .onSuccess(response -> { + // https://lunarclient.dev/apollo/developers/mods/waypoints#available-options + boolean waypointsEnabled = this.modSettingModule.getStatus(apolloPlayer, ModWaypoints.ENABLED); + + // https://lunarclient.dev/apollo/developers/mods/minimap#available-options + float minimapScale = this.modSettingModule.getStatus(apolloPlayer, ModMinimap.SCALE); + + // https://lunarclient.dev/apollo/developers/mods/fov#available-options + int fovDefaultFov = this.modSettingModule.getStatus(apolloPlayer, ModFov.DEFAULT_FOV); + + apolloPlayer.sendMessage(Component.text("Waypoints Enabled: ") + .append(Component.text(waypointsEnabled))); + + apolloPlayer.sendMessage(Component.text("Minimap Scale: ") + .append(Component.text(minimapScale))); + + apolloPlayer.sendMessage(Component.text("Fov Default Fov: ") + .append(Component.text(fovDefaultFov))); + }) + .onFailure(exception -> apolloPlayer.sendMessage(Component.text("Failed to receive a response in time.")))); +} +``` + +**Listening for Updates** + +Apollo fires the [ApolloUpdateModOptionEvent](/apollo/developers/events) whenever a Lunar Client mod option is updated. It is not fired for the options listed in a mod status response. + +```java +import com.lunarclient.apollo.event.ApolloListener; +import com.lunarclient.apollo.event.EventBus; +import com.lunarclient.apollo.event.Listen; +import com.lunarclient.apollo.event.modsetting.ApolloUpdateModOptionEvent; +import java.util.Objects; +import net.kyori.adventure.text.Component; + +public class ApolloModStatusExample implements ApolloListener { + + public ApolloModStatusExample() { + EventBus.getBus().register(this); + } + + @Listen + private void onApolloUpdateModOption(ApolloUpdateModOptionEvent event) { + event.getPlayer().sendMessage(Component.text(event.getOption().getKey()) + .append(Component.text(" was updated to ")) + .append(Component.text(Objects.toString(event.getValue())))); + } + +} +``` + + + + + + +**Lightweight Protobuf examples.** See [Lightweight Protobuf](/apollo/developers/lightweight/protobuf) for setup, and [Roundtrip Packets](/apollo/developers/lightweight/protobuf/roundtrip-packets) for `ApolloRoundtripProtoListener`. + + +**Request Mod Status** + +```java +public void requestModStatusExample(Player viewer) { + UUID requestId = UUID.randomUUID(); + + ModStatusRequest request = ModStatusRequest.newBuilder() + .setRequestId(ByteString.copyFromUtf8(requestId.toString())) + .build(); + + ApolloRoundtripProtoListener.getInstance().sendModStatusRequest(viewer, requestId, request) + .thenAccept(modOptions -> { + List keys = modOptions.stream() + .map(ModOptionStatus::getKey) + .collect(Collectors.toList()); + + viewer.sendMessage("Found " + keys.size() + " changed options: " + keys); + }).exceptionally(throwable -> { + viewer.sendMessage("Failed to receive a response in time."); + return null; + }); +} +``` + + + + + + +**Lightweight JSON examples.** See [Lightweight JSON](/apollo/developers/lightweight/json) for setup, and [Roundtrip Packets](/apollo/developers/lightweight/json/roundtrip-packets) for `ApolloRoundtripJsonListener`. + + +**Request Mod Status** + +```java +public void requestModStatusExample(Player viewer) { + UUID requestId = UUID.randomUUID(); + + ApolloRoundtripJsonListener.getInstance() + .sendPaginatedRequest(viewer, requestId, new JsonObject(), "lunarclient.apollo.modsetting.v1.ModStatusRequest") + .thenAccept(options -> { + List keys = options.stream() + .map(option -> option.get("key").getAsString()) + .collect(Collectors.toList()); + + viewer.sendMessage("Found " + keys.size() + " changed options: " + keys); + }).exceptionally(throwable -> { + viewer.sendMessage("Failed to receive a response in time."); + return null; + }); +} +``` + + + + diff --git a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/mods/ApolloModStatusExample.java b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/mods/ApolloModStatusExample.java index 71ef2a01..1a9a0f05 100644 --- a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/mods/ApolloModStatusExample.java +++ b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/mods/ApolloModStatusExample.java @@ -73,25 +73,27 @@ private void onBlockPlace(BlockPlaceEvent event) { private void printOptionStatusExample(Player player) { Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(player.getUniqueId()); - apolloPlayerOpt.ifPresent(apolloPlayer -> { - // https://lunarclient.dev/apollo/developers/mods/waypoints#available-options - boolean waypointsEnabled = this.modSettingModule.getStatus(apolloPlayer, ModWaypoints.ENABLED); + apolloPlayerOpt.ifPresent(apolloPlayer -> this.modSettingModule.requestModStatus(apolloPlayer) + .onSuccess(response -> { + // https://lunarclient.dev/apollo/developers/mods/waypoints#available-options + boolean waypointsEnabled = this.modSettingModule.getStatus(apolloPlayer, ModWaypoints.ENABLED); - // https://lunarclient.dev/apollo/developers/mods/minimap#available-options - float minimapScale = this.modSettingModule.getStatus(apolloPlayer, ModMinimap.SCALE); + // https://lunarclient.dev/apollo/developers/mods/minimap#available-options + float minimapScale = this.modSettingModule.getStatus(apolloPlayer, ModMinimap.SCALE); - // https://lunarclient.dev/apollo/developers/mods/fov#available-options - int fovDefaultFov = this.modSettingModule.getStatus(apolloPlayer, ModFov.DEFAULT_FOV); + // https://lunarclient.dev/apollo/developers/mods/fov#available-options + int fovDefaultFov = this.modSettingModule.getStatus(apolloPlayer, ModFov.DEFAULT_FOV); - apolloPlayer.sendMessage(Component.text("Waypoints Enabled: ") - .append(Component.text(waypointsEnabled))); + apolloPlayer.sendMessage(Component.text("Waypoints Enabled: ") + .append(Component.text(waypointsEnabled))); - apolloPlayer.sendMessage(Component.text("Minimap Scale: ") - .append(Component.text(minimapScale))); + apolloPlayer.sendMessage(Component.text("Minimap Scale: ") + .append(Component.text(minimapScale))); - apolloPlayer.sendMessage(Component.text("Fov Default Fov: ") - .append(Component.text(fovDefaultFov))); - }); + apolloPlayer.sendMessage(Component.text("Fov Default Fov: ") + .append(Component.text(fovDefaultFov))); + }) + .onFailure(exception -> apolloPlayer.sendMessage(Component.text("Failed to receive a response in time.")))); } } diff --git a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/mods/ModStatusCommand.java b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/mods/ModStatusCommand.java index 39bed666..91d1ea55 100644 --- a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/mods/ModStatusCommand.java +++ b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/mods/ModStatusCommand.java @@ -25,10 +25,10 @@ import com.lunarclient.apollo.Apollo; import com.lunarclient.apollo.ApolloManager; +import com.lunarclient.apollo.module.modsetting.ModOptionStatus; import com.lunarclient.apollo.module.modsetting.ModSettingModule; import com.lunarclient.apollo.option.Option; import com.lunarclient.apollo.player.ApolloPlayer; -import java.util.Map; import java.util.Optional; import org.bukkit.Bukkit; import org.bukkit.command.Command; @@ -86,20 +86,22 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command } private void sendCurrent(CommandSender sender, ApolloPlayer target) { - Map playerOptions = ApolloManager.getModsManager().getPlayerOptions().getPlayerOptions().get(target.getUniqueId()); - - sender.sendMessage("-------------------------------------"); - sender.sendMessage("Target: " + target.getName()); - sender.sendMessage(""); - - if (playerOptions != null && !playerOptions.isEmpty()) { - for (Map.Entry entry : playerOptions.entrySet()) { - sender.sendMessage(" - " + entry.getKey() + "=" + entry.getValue()); - } - } else { - sender.sendMessage("No options found!"); - } - - sender.sendMessage("-------------------------------------"); + this.modSettingModule.requestModStatus(target) + .onSuccess(response -> { + sender.sendMessage("-------------------------------------"); + sender.sendMessage("Target: " + target.getName()); + sender.sendMessage(""); + + if (!response.getElements().isEmpty()) { + for (ModOptionStatus status : response.getElements()) { + sender.sendMessage(" - " + status.getOption().getKey() + "=" + status.getValue()); + } + } else { + sender.sendMessage("No options found!"); + } + + sender.sendMessage("-------------------------------------"); + }) + .onFailure(exception -> sender.sendMessage("Failed to receive a response in time.")); } } diff --git a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/InventoryApiExample.java b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/InventoryApiExample.java index 016dd510..2e11b4d3 100644 --- a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/InventoryApiExample.java +++ b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/InventoryApiExample.java @@ -39,6 +39,7 @@ import com.lunarclient.apollo.mods.impl.ModMinimap; import com.lunarclient.apollo.mods.impl.ModWaypoints; import com.lunarclient.apollo.module.inventory.InventoryModule; +import com.lunarclient.apollo.module.modsetting.ModSettingModule; import com.lunarclient.apollo.player.ApolloPlayer; import java.util.HashSet; import java.util.Optional; @@ -55,6 +56,7 @@ public class InventoryApiExample extends InventoryExample implements ApolloListener, Listener { private final InventoryModule inventoryModule = Apollo.getModuleManager().getModule(InventoryModule.class); + private final ModSettingModule modSettingModule = Apollo.getModuleManager().getModule(ModSettingModule.class); private final Set minigameViewers = new HashSet<>(); @@ -77,6 +79,11 @@ public void displayHubLayoutExample(Player viewer) { public void displayMinigameLayoutExample(Player viewer) { this.minigameViewers.add(viewer.getUniqueId()); MinigameLayout.display(this.inventoryModule, viewer); + + // Show Map / Waypoints follow the player's mod settings (private Mod Status API) + Apollo.getPlayerManager().getPlayer(viewer.getUniqueId()).ifPresent(apolloPlayer -> this.modSettingModule + .requestModStatus(apolloPlayer) + .onSuccess(response -> this.inventoryModule.displayInventoryButtons(apolloPlayer, MinigameLayout.modButtons(apolloPlayer)))); } @Override @@ -120,6 +127,7 @@ private void onPlayerQuit(PlayerQuitEvent event) { } // Update Minigame layout if the player toggles their Minimap or Waypoint mod + // ApolloUpdateModOptionEvent is part of the private Mod Status API: https://lunarclient.dev/apollo/developers/private-modules/modstatus @Listen private void onApolloUpdateModOption(ApolloUpdateModOptionEvent event) { String optionKey = event.getOption().getKey(); @@ -127,15 +135,12 @@ private void onApolloUpdateModOption(ApolloUpdateModOptionEvent event) { return; } - UUID playerIdentifier = event.getPlayer().getUniqueId(); - if (!this.minigameViewers.contains(playerIdentifier)) { + ApolloPlayer apolloPlayer = event.getPlayer(); + if (!this.minigameViewers.contains(apolloPlayer.getUniqueId())) { return; } - Player viewer = Bukkit.getPlayer(playerIdentifier); - if (viewer != null) { - MinigameLayout.display(this.inventoryModule, viewer); - } + this.inventoryModule.displayInventoryButtons(apolloPlayer, MinigameLayout.modButtons(apolloPlayer)); } } diff --git a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/ModSettingsApiExample.java b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/ModSettingsApiExample.java index 76caec20..309e327f 100644 --- a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/ModSettingsApiExample.java +++ b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/ModSettingsApiExample.java @@ -79,4 +79,24 @@ public void requestInstalledModsExample(Player viewer) { }); } + @Override + public void requestModStatusExample(Player viewer) { + Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId()); + + if (!apolloPlayerOpt.isPresent()) { + viewer.sendMessage("Join with Lunar Client to test this feature!"); + return; + } + + this.modSettingModule.requestModStatus(apolloPlayerOpt.get()) + .onSuccess(response -> { + List options = response.getElements() + .stream().map(status -> status.getOption().getKey() + "=" + status.getValue()) + .collect(Collectors.toList()); + + viewer.sendMessage("Found " + options.size() + " changed options: " + options); + }) + .onFailure(exception -> viewer.sendMessage("Failed to receive a response in time.")); + } + } diff --git a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/inventorybuttons/MinigameLayout.java b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/inventorybuttons/MinigameLayout.java index dc1633db..231b6448 100644 --- a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/inventorybuttons/MinigameLayout.java +++ b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/inventorybuttons/MinigameLayout.java @@ -42,7 +42,9 @@ import com.lunarclient.apollo.player.ApolloPlayer; import java.awt.Color; import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.UUID; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; @@ -160,79 +162,86 @@ public static void display(InventoryModule inventoryModule, Player viewer) { .onClick(ApolloButtonAction.runCommand("/settings")) .build(); - ModSettingModule modSettingModule = Apollo.getModuleManager().getModule(ModSettingModule.class); - boolean minimapEnabled = modSettingModule.getStatus(apolloPlayer, ModMinimap.ENABLED); - boolean waypointsEnabled = modSettingModule.getStatus(apolloPlayer, ModWaypoints.ENABLED); - - InventoryButton.InventoryButtonBuilder showMapBuilder = InventoryButton.builder() - .id("show-map") - .inventoryType(InventoryType.PLAYER) - .box(InventoryButtonBox.RIGHT) - .position(HudPosition.of(6, 52)) - .size(InventoryButton.SIZE_WIDE) - .shape(ApolloButtonShape.ROUNDED_SQUARE) - .content(ApolloButtonContent.builder() - .append(ItemStackIcon.builder() - .itemName("FILLED_MAP") - .build()) - .append(Component.text("Show Map")) - .scale(1.0F) - .build()); - - if (minimapEnabled) { - showMapBuilder - .backgroundColor(InventoryButton.DEFAULT_BACKGROUND_COLOR) - .borderColor(InventoryButton.DEFAULT_BORDER_COLOR) - .tooltip(ApolloButtonTooltip.of( - Component.text("Show Map", NamedTextColor.AQUA), - Component.text("Open the fullscreen minimap view", NamedTextColor.GRAY))) - .onClick(ApolloButtonAction.clientAction(ApolloButtonClientAction.OPEN_MINIMAP_VIEW)); - } else { - showMapBuilder - .backgroundColor(new Color(224, 64, 64, 128)) - .borderColor(new Color(255, 200, 200, 140)) - .tooltip(ApolloButtonTooltip.of(Component.text("Minimap mod must be enabled", NamedTextColor.RED))); - } - - InventoryButton showMap = showMapBuilder.build(); - - InventoryButton.InventoryButtonBuilder waypointsBuilder = InventoryButton.builder() - .id("waypoints") - .inventoryType(InventoryType.PLAYER) - .box(InventoryButtonBox.RIGHT) - .position(HudPosition.of(6, 84)) - .size(InventoryButton.SIZE_WIDE) - .shape(ApolloButtonShape.ROUNDED_SQUARE) - .content(ApolloButtonContent.builder() - .append(ItemStackIcon.builder() - .itemName("LODESTONE") - .build()) - .append(Component.text("Waypoints")) - .scale(1.0F) - .build()); - - if (waypointsEnabled) { - waypointsBuilder - .backgroundColor(InventoryButton.DEFAULT_BACKGROUND_COLOR) - .borderColor(InventoryButton.DEFAULT_BORDER_COLOR) - .tooltip(ApolloButtonTooltip.of( - Component.text("Waypoints", NamedTextColor.GOLD), - Component.text("Manage your waypoints", NamedTextColor.GRAY))) - .onClick(ApolloButtonAction.clientAction(ApolloButtonClientAction.OPEN_WAYPOINTS_MENU)); - } else { - waypointsBuilder - .backgroundColor(new Color(224, 64, 64, 128)) - .borderColor(new Color(255, 200, 200, 140)) - .tooltip(ApolloButtonTooltip.of(Component.text("Waypoints mod must be enabled", NamedTextColor.RED))); - } - - InventoryButton waypoints = waypointsBuilder.build(); - - inventoryModule.displayInventoryButtons(apolloPlayer, Arrays.asList(mapInfo, kills, lobby, - profile, settings, showMap, waypoints)); + List buttons = new ArrayList<>(Arrays.asList(mapInfo, kills, lobby, profile, settings)); + buttons.addAll(modButtons(apolloPlayer)); + inventoryModule.displayInventoryButtons(apolloPlayer, buttons); }); } + // Displaying these again with the same ids replaces the previous Show Map / Waypoints buttons + public static List modButtons(ApolloPlayer apolloPlayer) { + // getStatus is part of the private Mod Status API: https://lunarclient.dev/apollo/developers/private-modules/modstatus + ModSettingModule modSettingModule = Apollo.getModuleManager().getModule(ModSettingModule.class); + boolean minimapEnabled = modSettingModule.getStatus(apolloPlayer, ModMinimap.ENABLED); + boolean waypointsEnabled = modSettingModule.getStatus(apolloPlayer, ModWaypoints.ENABLED); + + InventoryButton.InventoryButtonBuilder showMapBuilder = InventoryButton.builder() + .id("show-map") + .inventoryType(InventoryType.PLAYER) + .box(InventoryButtonBox.RIGHT) + .position(HudPosition.of(6, 52)) + .size(InventoryButton.SIZE_WIDE) + .shape(ApolloButtonShape.ROUNDED_SQUARE) + .content(ApolloButtonContent.builder() + .append(ItemStackIcon.builder() + .itemName("FILLED_MAP") + .build()) + .append(Component.text("Show Map")) + .scale(1.0F) + .build()); + + if (minimapEnabled) { + showMapBuilder + .backgroundColor(InventoryButton.DEFAULT_BACKGROUND_COLOR) + .borderColor(InventoryButton.DEFAULT_BORDER_COLOR) + .tooltip(ApolloButtonTooltip.of( + Component.text("Show Map", NamedTextColor.AQUA), + Component.text("Open the fullscreen minimap view", NamedTextColor.GRAY))) + .onClick(ApolloButtonAction.clientAction(ApolloButtonClientAction.OPEN_MINIMAP_VIEW)); + } else { + showMapBuilder + .backgroundColor(new Color(224, 64, 64, 128)) + .borderColor(new Color(255, 200, 200, 140)) + .tooltip(ApolloButtonTooltip.of(Component.text("Minimap mod must be enabled", NamedTextColor.RED))); + } + + InventoryButton showMap = showMapBuilder.build(); + + InventoryButton.InventoryButtonBuilder waypointsBuilder = InventoryButton.builder() + .id("waypoints") + .inventoryType(InventoryType.PLAYER) + .box(InventoryButtonBox.RIGHT) + .position(HudPosition.of(6, 84)) + .size(InventoryButton.SIZE_WIDE) + .shape(ApolloButtonShape.ROUNDED_SQUARE) + .content(ApolloButtonContent.builder() + .append(ItemStackIcon.builder() + .itemName("LODESTONE") + .build()) + .append(Component.text("Waypoints")) + .scale(1.0F) + .build()); + + if (waypointsEnabled) { + waypointsBuilder + .backgroundColor(InventoryButton.DEFAULT_BACKGROUND_COLOR) + .borderColor(InventoryButton.DEFAULT_BORDER_COLOR) + .tooltip(ApolloButtonTooltip.of( + Component.text("Waypoints", NamedTextColor.GOLD), + Component.text("Manage your waypoints", NamedTextColor.GRAY))) + .onClick(ApolloButtonAction.clientAction(ApolloButtonClientAction.OPEN_WAYPOINTS_MENU)); + } else { + waypointsBuilder + .backgroundColor(new Color(224, 64, 64, 128)) + .borderColor(new Color(255, 200, 200, 140)) + .tooltip(ApolloButtonTooltip.of(Component.text("Waypoints mod must be enabled", NamedTextColor.RED))); + } + + InventoryButton waypoints = waypointsBuilder.build(); + + return Arrays.asList(showMap, waypoints); + } + private static int getKills(ApolloPlayer apolloViewer) { Player player = Bukkit.getPlayer(apolloViewer.getUniqueId()); return player != null ? player.getStatistic(Statistic.PLAYER_KILLS) : 0; diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/ModSettingsCommand.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/ModSettingsCommand.java index b62551fa..104ce09e 100644 --- a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/ModSettingsCommand.java +++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/ModSettingsCommand.java @@ -44,7 +44,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command if (args.length != 1) { player.sendMessage("Usage: /modsettings "); - player.sendMessage("Usage: /modsettings "); + player.sendMessage("Usage: /modsettings "); return true; } @@ -75,9 +75,15 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command break; } + case "requestmodstatus": { + modSettingsExample.requestModStatusExample(player); + player.sendMessage("Requesting mod status....."); + break; + } + default: { player.sendMessage("Usage: /modsettings "); - player.sendMessage("Usage: /modsettings "); + player.sendMessage("Usage: /modsettings "); break; } } diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/ModSettingsExample.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/ModSettingsExample.java index 71777e2d..ae5b6d7d 100644 --- a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/ModSettingsExample.java +++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/ModSettingsExample.java @@ -36,4 +36,6 @@ public abstract class ModSettingsExample extends ApolloModuleExample { public abstract void requestInstalledModsExample(Player viewer); + public abstract void requestModStatusExample(Player viewer); + } diff --git a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/listener/ApolloRoundtripJsonListener.java b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/listener/ApolloRoundtripJsonListener.java index 1c2ff26f..518c3a0e 100644 --- a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/listener/ApolloRoundtripJsonListener.java +++ b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/listener/ApolloRoundtripJsonListener.java @@ -88,6 +88,9 @@ public void onPluginMessageReceived(@NonNull String channel, @NonNull Player pla } else if ("lunarclient.apollo.modsetting.v1.InstalledModsResponse".equals(type)) { UUID requestId = UUID.fromString(payload.get("request_id").getAsString().replace("+", "-")); this.parseModGroups(requestId, payload); + } else if ("lunarclient.apollo.modsetting.v1.ModStatusResponse".equals(type)) { + UUID requestId = UUID.fromString(payload.get("request_id").getAsString().replace("+", "-")); + this.parseModStatus(requestId, payload); } } @@ -155,6 +158,19 @@ private void parseModGroups(UUID requestId, JsonObject response) { this.handlePage(requestId, accumulated, response); } + private void parseModStatus(UUID requestId, JsonObject response) { + List accumulated = this.paginatedAccumulator.computeIfAbsent(requestId, k -> new ArrayList<>()); + + JsonArray modOptions = response.getAsJsonArray("mod_options"); + if (modOptions != null) { + for (JsonElement option : modOptions) { + accumulated.add(option.getAsJsonObject()); + } + } + + this.handlePage(requestId, accumulated, response); + } + private void handlePage(UUID requestId, List accumulated, JsonObject response) { int page = response.has("page") ? response.get("page").getAsInt() : 0; int totalPages = response.has("total_pages") ? response.get("total_pages").getAsInt() : 1; diff --git a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/ModSettingsJsonExample.java b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/ModSettingsJsonExample.java index d260febf..fc1fb4a8 100644 --- a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/ModSettingsJsonExample.java +++ b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/ModSettingsJsonExample.java @@ -83,4 +83,22 @@ public void requestInstalledModsExample(Player viewer) { }); } + @Override + public void requestModStatusExample(Player viewer) { + UUID requestId = UUID.randomUUID(); + + ApolloRoundtripJsonListener.getInstance() + .sendPaginatedRequest(viewer, requestId, new JsonObject(), "lunarclient.apollo.modsetting.v1.ModStatusRequest") + .thenAccept(options -> { + List keys = options.stream() + .map(option -> option.get("key").getAsString()) + .collect(Collectors.toList()); + + viewer.sendMessage("Found " + keys.size() + " changed options: " + keys); + }).exceptionally(throwable -> { + viewer.sendMessage("Failed to receive a response in time."); + return null; + }); + } + } diff --git a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/listener/ApolloRoundtripProtoListener.java b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/listener/ApolloRoundtripProtoListener.java index 03d43c72..903aa566 100644 --- a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/listener/ApolloRoundtripProtoListener.java +++ b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/listener/ApolloRoundtripProtoListener.java @@ -30,6 +30,9 @@ import com.lunarclient.apollo.example.proto.util.ProtobufPacketUtil; import com.lunarclient.apollo.modsetting.v1.InstalledModsResponse; import com.lunarclient.apollo.modsetting.v1.Mod; +import com.lunarclient.apollo.modsetting.v1.ModOptionStatus; +import com.lunarclient.apollo.modsetting.v1.ModStatusRequest; +import com.lunarclient.apollo.modsetting.v1.ModStatusResponse; import com.lunarclient.apollo.transfer.v1.PingResponse; import com.lunarclient.apollo.transfer.v1.TransferResponse; import java.util.ArrayList; @@ -56,6 +59,8 @@ public class ApolloRoundtripProtoListener implements PluginMessageListener { private final Map>> roundTripPacketFutures = new ConcurrentHashMap<>(); private final Map>> paginatedFutures = new ConcurrentHashMap<>(); private final Map> paginatedAccumulator = new ConcurrentHashMap<>(); + private final Map>> modStatusFutures = new ConcurrentHashMap<>(); + private final Map> modStatusAccumulator = new ConcurrentHashMap<>(); private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); public ApolloRoundtripProtoListener(ApolloExamplePlugin plugin) { @@ -79,6 +84,10 @@ public void onPluginMessageReceived(@NonNull String channel, @NonNull Player pla InstalledModsResponse message = any.unpack(InstalledModsResponse.class); UUID requestId = UUID.fromString(message.getRequestId().toStringUtf8()); this.handlePagedResponse(requestId, message); + } else if (any.is(ModStatusResponse.class)) { + ModStatusResponse message = any.unpack(ModStatusResponse.class); + UUID requestId = UUID.fromString(message.getRequestId().toStringUtf8()); + this.handleModStatusPage(requestId, message); } } catch (InvalidProtocolBufferException e) { @@ -125,6 +134,38 @@ public CompletableFuture> sendPaginatedRequest(Player player, UUID req return future; } + public CompletableFuture> sendModStatusRequest(Player player, UUID requestId, ModStatusRequest request) { + ProtobufPacketUtil.sendPacket(player, request); + + CompletableFuture> future = new CompletableFuture<>(); + this.modStatusFutures.put(requestId, future); + + ScheduledFuture timeoutTask = this.executorService.schedule(() -> + future.completeExceptionally(new TimeoutException("Response timed out")), + 10, TimeUnit.SECONDS + ); + + future.whenComplete((result, throwable) -> { + timeoutTask.cancel(false); + this.modStatusAccumulator.remove(requestId); + }); + + return future; + } + + private void handleModStatusPage(UUID requestId, ModStatusResponse response) { + List accumulated = this.modStatusAccumulator.computeIfAbsent(requestId, k -> new ArrayList<>()); + accumulated.addAll(response.getModOptionsList()); + + if (response.getPage() == response.getTotalPages() - 1) { + this.modStatusAccumulator.remove(requestId); + CompletableFuture> future = this.modStatusFutures.remove(requestId); + if (future != null) { + future.complete(accumulated); + } + } + } + private void handlePagedResponse(UUID requestId, InstalledModsResponse response) { List accumulated = this.paginatedAccumulator.computeIfAbsent(requestId, k -> new ArrayList<>()); diff --git a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/ModSettingsProtoExample.java b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/ModSettingsProtoExample.java index 04780af3..94dc6798 100644 --- a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/ModSettingsProtoExample.java +++ b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/ModSettingsProtoExample.java @@ -32,6 +32,8 @@ import com.lunarclient.apollo.example.proto.util.ProtobufPacketUtil; import com.lunarclient.apollo.modsetting.v1.InstalledModsRequest; import com.lunarclient.apollo.modsetting.v1.Mod; +import com.lunarclient.apollo.modsetting.v1.ModOptionStatus; +import com.lunarclient.apollo.modsetting.v1.ModStatusRequest; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -90,4 +92,25 @@ public void requestInstalledModsExample(Player viewer) { }); } + @Override + public void requestModStatusExample(Player viewer) { + UUID requestId = UUID.randomUUID(); + + ModStatusRequest request = ModStatusRequest.newBuilder() + .setRequestId(ByteString.copyFromUtf8(requestId.toString())) + .build(); + + ApolloRoundtripProtoListener.getInstance().sendModStatusRequest(viewer, requestId, request) + .thenAccept(modOptions -> { + List keys = modOptions.stream() + .map(ModOptionStatus::getKey) + .collect(Collectors.toList()); + + viewer.sendMessage("Found " + keys.size() + " changed options: " + keys); + }).exceptionally(throwable -> { + viewer.sendMessage("Failed to receive a response in time."); + return null; + }); + } + } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ba4c1777..4ed5c3ef 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.5" +protobuf = "0.2.6" gson = "2.10.1" shadow = "9.4.1" spotless = "8.4.0"