diff --git a/api/src/main/java/com/lunarclient/apollo/common/location/HudPosition.java b/api/src/main/java/com/lunarclient/apollo/common/location/HudPosition.java
new file mode 100644
index 00000000..c6f7f4fa
--- /dev/null
+++ b/api/src/main/java/com/lunarclient/apollo/common/location/HudPosition.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.common.location;
+
+import lombok.Builder;
+import lombok.Getter;
+
+/**
+ * Represents a HUD element position on the client screen.
+ *
+ * @since 1.2.6
+ */
+@Getter
+@Builder
+public final class HudPosition {
+
+ /**
+ * Returns the {@code float} X coordinate for this HUD position.
+ *
+ * @return the x coordinate
+ * @since 1.2.6
+ */
+ float x;
+
+ /**
+ * Returns the {@code float} Y coordinate for this HUD position.
+ *
+ * @return the y coordinate
+ * @since 1.2.6
+ */
+ float y;
+
+}
diff --git a/api/src/main/java/com/lunarclient/apollo/module/cooldown/CooldownStyle.java b/api/src/main/java/com/lunarclient/apollo/module/cooldown/CooldownStyle.java
index 2641e5a3..9886e19f 100644
--- a/api/src/main/java/com/lunarclient/apollo/module/cooldown/CooldownStyle.java
+++ b/api/src/main/java/com/lunarclient/apollo/module/cooldown/CooldownStyle.java
@@ -29,7 +29,7 @@
import org.jetbrains.annotations.Nullable;
/**
- * Represents the {@link Cooldown} style, allowing customization of the circle start, end, edge & text color.
+ * Represents the {@link Cooldown} style, allowing customization of the circle start, end, edge and text color.
*
* @since 1.2.5
*/
diff --git a/api/src/main/java/com/lunarclient/apollo/module/stopwatch/Stopwatch.java b/api/src/main/java/com/lunarclient/apollo/module/stopwatch/Stopwatch.java
new file mode 100644
index 00000000..3afd1df2
--- /dev/null
+++ b/api/src/main/java/com/lunarclient/apollo/module/stopwatch/Stopwatch.java
@@ -0,0 +1,119 @@
+/*
+ * 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.stopwatch;
+
+import com.lunarclient.apollo.common.location.HudPosition;
+import java.awt.Color;
+import lombok.Builder;
+import lombok.Getter;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Represents a stopwatch which can be displayed on the client HUD.
+ *
+ * @since 1.2.6
+ */
+@Getter
+@Builder
+public final class Stopwatch {
+
+ /**
+ * Returns the stopwatch {@link String} id.
+ *
+ * @return the stopwatch id
+ * @since 1.2.6
+ */
+ String id;
+
+ /**
+ * Returns the stopwatch {@link String} name.
+ *
+ * @return the stopwatch name
+ * @since 1.2.6
+ */
+ String name;
+
+ /**
+ * Returns the stopwatch {@code boolean} reset on start.
+ *
+ *
If {@code true}, elapsed time is reset on each start.
+ *
+ * @return whether to reset on start
+ * @since 1.2.6
+ */
+ boolean resetOnStart;
+
+ /**
+ * Returns the stopwatch {@code boolean} prevent modification.
+ *
+ * If {@code true}, the user cannot modify the options for this
+ * stopwatch on the client side.
+ *
+ * @return whether modification is prevented
+ * @since 1.2.6
+ */
+ boolean preventModification;
+
+ /**
+ * Returns the stopwatch {@code boolean} hide when stopped.
+ *
+ * If {@code true}, the stopwatch is hidden from the HUD when stopped.
+ *
+ * @return whether to hide when stopped
+ * @since 1.2.6
+ */
+ boolean hideWhenStopped;
+
+ /**
+ * Returns the stopwatch {@link String} display format.
+ *
+ * A format string (e.g. {@code "mm:ss"}), or {@code null}
+ * for the default display format.
+ *
+ * @return the display format string
+ * @since 1.2.6
+ */
+ @Nullable String displayFormat;
+
+ /**
+ * Returns the stopwatch {@link Color} text color.
+ *
+ * If {@code null}, the default color (white) is used.
+ *
+ * @return the text color
+ * @since 1.2.6
+ */
+ @Nullable Color textColor;
+
+ /**
+ * Returns the stopwatch {@link HudPosition} HUD position.
+ *
+ * If {@code null}, the stopwatch is auto-stacked at the default position.
+ *
+ * @return the HUD position
+ * @since 1.2.6
+ */
+ @Nullable HudPosition hudPosition;
+
+}
diff --git a/api/src/main/java/com/lunarclient/apollo/module/stopwatch/StopwatchModule.java b/api/src/main/java/com/lunarclient/apollo/module/stopwatch/StopwatchModule.java
index bec98316..f39af579 100644
--- a/api/src/main/java/com/lunarclient/apollo/module/stopwatch/StopwatchModule.java
+++ b/api/src/main/java/com/lunarclient/apollo/module/stopwatch/StopwatchModule.java
@@ -45,28 +45,141 @@ public Collection getSupportedPlatforms() {
return Arrays.asList(ApolloPlatform.Kind.SERVER, ApolloPlatform.Kind.PROXY);
}
+ /**
+ * Adds a {@link Stopwatch} to the HUD for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @param stopwatch the stopwatch to add
+ * @since 1.2.6
+ */
+ public abstract void addStopwatch(Recipients recipients, Stopwatch stopwatch);
+
+ /**
+ * Removes the {@link Stopwatch} with the given id for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @param id the stopwatch id
+ * @since 1.2.6
+ */
+ public abstract void removeStopwatch(Recipients recipients, String id);
+
+ /**
+ * Starts the {@link Stopwatch} with the given id for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @param id the stopwatch id
+ * @since 1.2.6
+ */
+ public abstract void startStopwatch(Recipients recipients, String id);
+
+ /**
+ * Stops the {@link Stopwatch} with the given id for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @param id the stopwatch id
+ * @since 1.2.6
+ */
+ public abstract void stopStopwatch(Recipients recipients, String id);
+
+ /**
+ * Resets the {@link Stopwatch} with the given id for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @param id the stopwatch id
+ * @since 1.2.6
+ */
+ public abstract void resetStopwatch(Recipients recipients, String id);
+
+ /**
+ * Resets all {@link Stopwatch}es for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @since 1.2.6
+ */
+ public abstract void resetStopwatches(Recipients recipients);
+
+ /**
+ * Adds a {@link Timer} to the HUD for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @param timer the timer to add
+ * @since 1.2.6
+ */
+ public abstract void addTimer(Recipients recipients, Timer timer);
+
+ /**
+ * Removes the {@link Timer} with the given id for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @param id the timer id
+ * @since 1.2.6
+ */
+ public abstract void removeTimer(Recipients recipients, String id);
+
+ /**
+ * Starts the {@link Timer} with the given id for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @param id the timer id
+ * @since 1.2.6
+ */
+ public abstract void startTimer(Recipients recipients, String id);
+
+ /**
+ * Stops the {@link Timer} with the given id for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @param id the timer id
+ * @since 1.2.6
+ */
+ public abstract void stopTimer(Recipients recipients, String id);
+
+ /**
+ * Resets the {@link Timer} with the given id for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @param id the timer id
+ * @since 1.2.6
+ */
+ public abstract void resetTimer(Recipients recipients, String id);
+
+ /**
+ * Resets all {@link Timer}s for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @since 1.2.6
+ */
+ public abstract void resetTimers(Recipients recipients);
+
/**
* Starts the stopwatch for the {@link Recipients}.
*
* @param recipients the recipients that are receiving the packet
+ * @deprecated for removal since 1.2.6, use {@link #addStopwatch(Recipients, Stopwatch)}
+ * and {@link #startStopwatch(Recipients, String)} instead.
* @since 1.0.0
*/
+ @Deprecated
public abstract void startStopwatch(Recipients recipients);
/**
* Stops the stopwatch for the {@link Recipients}.
*
* @param recipients the recipients that are receiving the packet
+ * @deprecated for removal since 1.2.6, use {@link #stopStopwatch(Recipients, String)} instead.
* @since 1.0.0
*/
+ @Deprecated
public abstract void stopStopwatch(Recipients recipients);
/**
* Resets the stopwatch for the {@link Recipients}.
*
* @param recipients the recipients that are receiving the packet
+ * @deprecated for removal since 1.2.6, use {@link #resetStopwatch(Recipients, String)} instead.
* @since 1.0.0
*/
+ @Deprecated
public abstract void resetStopwatch(Recipients recipients);
}
diff --git a/api/src/main/java/com/lunarclient/apollo/module/stopwatch/Timer.java b/api/src/main/java/com/lunarclient/apollo/module/stopwatch/Timer.java
new file mode 100644
index 00000000..99d71861
--- /dev/null
+++ b/api/src/main/java/com/lunarclient/apollo/module/stopwatch/Timer.java
@@ -0,0 +1,150 @@
+/*
+ * 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.stopwatch;
+
+import com.lunarclient.apollo.common.location.HudPosition;
+import java.awt.Color;
+import java.time.Duration;
+import lombok.Builder;
+import lombok.Getter;
+import net.kyori.adventure.text.Component;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Represents a timer which can be displayed on the client HUD.
+ *
+ * @since 1.2.6
+ */
+@Getter
+@Builder
+public final class Timer {
+
+ /**
+ * Returns the timer {@link String} id.
+ *
+ * @return the timer id
+ * @since 1.2.6
+ */
+ String id;
+
+ /**
+ * Returns the timer {@link String} name.
+ *
+ * @return the timer name
+ * @since 1.2.6
+ */
+ String name;
+
+ /**
+ * Returns the timer {@link Duration}.
+ *
+ * @return the timer duration
+ * @since 1.2.6
+ */
+ Duration duration;
+
+ /**
+ * Returns the timer {@code boolean} loop.
+ *
+ * If {@code true}, the timer restarts automatically when finished.
+ *
+ * @return whether the timer loops
+ * @since 1.2.6
+ */
+ boolean loop;
+
+ /**
+ * Returns the timer {@code boolean} prevent modification.
+ *
+ * If {@code true}, the user cannot modify the options for this
+ * timer on the client side.
+ *
+ * @return whether modification is prevented
+ * @since 1.2.6
+ */
+ boolean preventModification;
+
+ /**
+ * Returns the timer {@code boolean} hide when stopped.
+ *
+ * If {@code true}, the timer is hidden from the HUD when stopped.
+ *
+ * @return whether to hide when stopped
+ * @since 1.2.6
+ */
+ boolean hideWhenStopped;
+
+ /**
+ * Returns the timer {@link String} display format.
+ *
+ * A format string (e.g. {@code "mm:ss"}), or {@code null}
+ * for the default display format.
+ *
+ * @return the display format string
+ * @since 1.2.6
+ */
+ @Nullable String displayFormat;
+
+ /**
+ * Returns the timer {@link Component} title text.
+ *
+ * The on-screen title shown when the timer finishes,
+ * or {@code null} to skip.
+ *
+ * @return the title text component
+ * @since 1.2.6
+ */
+ @Nullable Component titleText;
+
+ /**
+ * Returns the timer {@code boolean} in-game notification.
+ *
+ * If {@code true}, an in-game popup is shown when the timer finishes.
+ *
+ * @return whether an in-game notification is shown
+ * @since 1.2.6
+ */
+ boolean inGameNotification;
+
+ /**
+ * Returns the timer {@link Color} text color.
+ *
+ * If {@code null}, the default color (white) is used.
+ *
+ * @return the text color
+ * @since 1.2.6
+ */
+ @Nullable Color textColor;
+
+ /**
+ * Returns the timer {@link HudPosition} HUD position.
+ *
+ * If {@code null}, the timer is auto-stacked at the default position.
+ *
+ * @return the HUD position
+ * @since 1.2.6
+ */
+ @Nullable HudPosition hudPosition;
+
+}
diff --git a/common/src/main/java/com/lunarclient/apollo/module/stopwatch/StopwatchModuleImpl.java b/common/src/main/java/com/lunarclient/apollo/module/stopwatch/StopwatchModuleImpl.java
index efba37df..ded30e4b 100644
--- a/common/src/main/java/com/lunarclient/apollo/module/stopwatch/StopwatchModuleImpl.java
+++ b/common/src/main/java/com/lunarclient/apollo/module/stopwatch/StopwatchModuleImpl.java
@@ -23,12 +23,26 @@
*/
package com.lunarclient.apollo.module.stopwatch;
+import com.lunarclient.apollo.common.ApolloComponent;
+import com.lunarclient.apollo.common.location.HudPosition;
+import com.lunarclient.apollo.network.NetworkTypes;
import com.lunarclient.apollo.player.AbstractApolloPlayer;
import com.lunarclient.apollo.recipients.Recipients;
+import com.lunarclient.apollo.stopwatch.v1.AddStopwatchMessage;
+import com.lunarclient.apollo.stopwatch.v1.AddTimerMessage;
+import com.lunarclient.apollo.stopwatch.v1.RemoveStopwatchMessage;
+import com.lunarclient.apollo.stopwatch.v1.RemoveTimerMessage;
import com.lunarclient.apollo.stopwatch.v1.ResetStopwatchMessage;
+import com.lunarclient.apollo.stopwatch.v1.ResetStopwatchesMessage;
+import com.lunarclient.apollo.stopwatch.v1.ResetTimerMessage;
+import com.lunarclient.apollo.stopwatch.v1.ResetTimersMessage;
import com.lunarclient.apollo.stopwatch.v1.StartStopwatchMessage;
+import com.lunarclient.apollo.stopwatch.v1.StartTimerMessage;
import com.lunarclient.apollo.stopwatch.v1.StopStopwatchMessage;
+import com.lunarclient.apollo.stopwatch.v1.StopTimerMessage;
+import java.awt.Color;
import lombok.NonNull;
+import net.kyori.adventure.text.Component;
/**
* Provides the stopwatch module.
@@ -55,4 +69,151 @@ public void resetStopwatch(@NonNull Recipients recipients) {
recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message));
}
+ @Override
+ public void addStopwatch(@NonNull Recipients recipients, @NonNull Stopwatch stopwatch) {
+ AddStopwatchMessage.Builder builder = AddStopwatchMessage.newBuilder()
+ .setId(stopwatch.getId())
+ .setName(stopwatch.getName())
+ .setResetOnStart(stopwatch.isResetOnStart())
+ .setPreventModification(stopwatch.isPreventModification())
+ .setHideWhenStopped(stopwatch.isHideWhenStopped());
+
+ String displayFormat = stopwatch.getDisplayFormat();
+ if (displayFormat != null) {
+ builder.setDisplayFormat(displayFormat);
+ }
+
+ Color textColor = stopwatch.getTextColor();
+ if (textColor != null) {
+ builder.setTextColor(NetworkTypes.toProtobuf(textColor));
+ }
+
+ HudPosition hudPosition = stopwatch.getHudPosition();
+ if (hudPosition != null) {
+ builder.setHudPosition(NetworkTypes.toProtobuf(hudPosition));
+ }
+
+ AddStopwatchMessage message = builder.build();
+ recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message));
+ }
+
+ @Override
+ public void removeStopwatch(@NonNull Recipients recipients, @NonNull String id) {
+ RemoveStopwatchMessage message = RemoveStopwatchMessage.newBuilder()
+ .setId(id)
+ .build();
+
+ recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message));
+ }
+
+ @Override
+ public void startStopwatch(@NonNull Recipients recipients, @NonNull String id) {
+ StartStopwatchMessage message = StartStopwatchMessage.newBuilder()
+ .setId(id)
+ .build();
+
+ recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message));
+ }
+
+ @Override
+ public void stopStopwatch(@NonNull Recipients recipients, @NonNull String id) {
+ StopStopwatchMessage message = StopStopwatchMessage.newBuilder()
+ .setId(id)
+ .build();
+
+ recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message));
+ }
+
+ @Override
+ public void resetStopwatch(@NonNull Recipients recipients, @NonNull String id) {
+ ResetStopwatchMessage message = ResetStopwatchMessage.newBuilder()
+ .setId(id)
+ .build();
+
+ recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message));
+ }
+
+ @Override
+ public void resetStopwatches(@NonNull Recipients recipients) {
+ ResetStopwatchesMessage message = ResetStopwatchesMessage.getDefaultInstance();
+ recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message));
+ }
+
+ @Override
+ public void addTimer(@NonNull Recipients recipients, @NonNull Timer timer) {
+ AddTimerMessage.Builder builder = AddTimerMessage.newBuilder()
+ .setId(timer.getId())
+ .setName(timer.getName())
+ .setDuration(NetworkTypes.toProtobuf(timer.getDuration()))
+ .setLoop(timer.isLoop())
+ .setPreventModification(timer.isPreventModification())
+ .setHideWhenStopped(timer.isHideWhenStopped())
+ .setInGameNotification(timer.isInGameNotification());
+
+ String displayFormat = timer.getDisplayFormat();
+ if (displayFormat != null) {
+ builder.setDisplayFormat(displayFormat);
+ }
+
+ Component titleText = timer.getTitleText();
+ if (titleText != null) {
+ builder.setTitleTextAdventureJsonLines(ApolloComponent.toJson(titleText));
+ }
+
+ Color textColor = timer.getTextColor();
+ if (textColor != null) {
+ builder.setTextColor(NetworkTypes.toProtobuf(textColor));
+ }
+
+ HudPosition hudPosition = timer.getHudPosition();
+ if (hudPosition != null) {
+ builder.setHudPosition(NetworkTypes.toProtobuf(hudPosition));
+ }
+
+ AddTimerMessage message = builder.build();
+ recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message));
+ }
+
+ @Override
+ public void removeTimer(@NonNull Recipients recipients, @NonNull String id) {
+ RemoveTimerMessage message = RemoveTimerMessage.newBuilder()
+ .setId(id)
+ .build();
+
+ recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message));
+ }
+
+ @Override
+ public void startTimer(@NonNull Recipients recipients, @NonNull String id) {
+ StartTimerMessage message = StartTimerMessage.newBuilder()
+ .setId(id)
+ .build();
+
+ recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message));
+ }
+
+ @Override
+ public void stopTimer(@NonNull Recipients recipients, @NonNull String id) {
+ StopTimerMessage message = StopTimerMessage.newBuilder()
+ .setId(id)
+ .build();
+
+ recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message));
+ }
+
+ @Override
+ public void resetTimer(@NonNull Recipients recipients, @NonNull String id) {
+ ResetTimerMessage message = ResetTimerMessage.newBuilder()
+ .setId(id)
+ .build();
+
+ recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message));
+ }
+
+ @Override
+ public void resetTimers(@NonNull Recipients recipients) {
+ ResetTimersMessage message = ResetTimersMessage.getDefaultInstance();
+ recipients.forEach(player -> ((AbstractApolloPlayer) player).sendPacket(message));
+ }
+
}
diff --git a/common/src/main/java/com/lunarclient/apollo/network/NetworkTypes.java b/common/src/main/java/com/lunarclient/apollo/network/NetworkTypes.java
index df89e825..2a0a40ff 100644
--- a/common/src/main/java/com/lunarclient/apollo/network/NetworkTypes.java
+++ b/common/src/main/java/com/lunarclient/apollo/network/NetworkTypes.java
@@ -35,6 +35,7 @@
import com.lunarclient.apollo.common.location.ApolloBlockLocation;
import com.lunarclient.apollo.common.location.ApolloLocation;
import com.lunarclient.apollo.common.location.ApolloPlayerLocation;
+import com.lunarclient.apollo.common.location.HudPosition;
import com.lunarclient.apollo.common.profile.Profile;
import com.lunarclient.apollo.common.v1.EntityId;
import com.lunarclient.apollo.common.v1.Uuid;
@@ -142,6 +143,36 @@ public static Color fromProtobuf(com.lunarclient.apollo.common.v1.Color message)
return new Color(message.getColor());
}
+ /**
+ * Converts a {@link HudPosition} object to a
+ * {@link com.lunarclient.apollo.hud.v1.HudPosition} proto message.
+ *
+ * @param object the hud position
+ * @return the proto hud position message
+ * @since 1.2.6
+ */
+ public static com.lunarclient.apollo.hud.v1.HudPosition toProtobuf(HudPosition object) {
+ return com.lunarclient.apollo.hud.v1.HudPosition.newBuilder()
+ .setX(object.getX())
+ .setY(object.getY())
+ .build();
+ }
+
+ /**
+ * Converts a {@link com.lunarclient.apollo.hud.v1.HudPosition}
+ * proto message to a {@link HudPosition} object.
+ *
+ * @param message the hud position message
+ * @return the hud position object
+ * @since 1.2.6
+ */
+ public static HudPosition fromProtobuf(com.lunarclient.apollo.hud.v1.HudPosition message) {
+ return HudPosition.builder()
+ .x(message.getX())
+ .y(message.getY())
+ .build();
+ }
+
/**
* Converts an {@link Duration} object to an
* {@link com.google.protobuf.Duration} proto message.
diff --git a/docs/developers/modules/stopwatch.mdx b/docs/developers/modules/stopwatch.mdx
index ade32cf4..7d48472b 100644
--- a/docs/developers/modules/stopwatch.mdx
+++ b/docs/developers/modules/stopwatch.mdx
@@ -1,15 +1,20 @@
-import { Callout, Tab, Tabs } from 'nextra-theme-docs'
+import { Tab, Tabs } from 'nextra-theme-docs'
+import { Callout } from 'nextra-theme-docs'
# Stopwatch Module
## Overview
-The stopwatch module allows you to control the stopwatch mod inside Lunar Client.
+The stopwatch module allows you to add custom Stopwatch and Timers directly to player's screens in any HUD position you want.
-- Ability to fully control a player's stopwatch mod.
- - Start a player's stopwatch
- - Stop a player's stopwatch
- - Reset a player's stopwatch
+- Ability to create unique Stopwatch counters for a player.
+ - Control when it starts, stops, and when it resets.
+ - Customize the name, display color, and display format.
+ - Display the stopwatch anywhere on the player's screen.
+- Ability to create and control unique Timers for a player.
+ - Control when the timer starts, stops, and when it resets.
+ - Customize the duration, name, looping, display format, and display color of the timer.
+ - Display the timer HUD in the position you'd like on the player's screen.

@@ -30,64 +35,436 @@ Explore each integration by cycling through each tab, to find the best fit for y
**Apollo API examples.** See [General](/apollo/developers/general) for common patterns and helpers.
-### Start Stopwatch
+### Adding a Stopwatch
+
+```java
+public void addStopwatchExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+
+ apolloPlayerOpt.ifPresent(apolloPlayer -> {
+ this.stopwatchModule.addStopwatch(apolloPlayer, Stopwatch.builder()
+ .id("parkour-stopwatch")
+ .name("Parkour")
+ .resetOnStart(true)
+ .preventModification(true)
+ .hideWhenStopped(false)
+ .displayFormat("mm:ss")
+ .textColor(ApolloColors.DARK_AQUA)
+ .hudPosition(HudPosition.builder()
+ .x(-30)
+ .y(30)
+ .build()
+ )
+ .build());
+ });
+}
+```
+
+### Removing a Stopwatch
+
+```java
+public void removeStopwatchExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.stopwatchModule.removeStopwatch(apolloPlayer, "parkour-stopwatch"));
+}
+```
+
+### Starting a Stopwatch
```java
public void startStopwatchExample(Player viewer) {
Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
- apolloPlayerOpt.ifPresent(this.stopwatchModule::startStopwatch);
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.stopwatchModule.startStopwatch(apolloPlayer, "parkour-stopwatch"));
}
```
-### Stop Stopwatch
+### Stopping a Stopwatch
```java
public void stopStopwatchExample(Player viewer) {
Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
- apolloPlayerOpt.ifPresent(this.stopwatchModule::stopStopwatch);
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.stopwatchModule.stopStopwatch(apolloPlayer, "parkour-stopwatch"));
}
```
-### Reset Stopwatch
+### Resetting a Stopwatch
```java
public void resetStopwatchExample(Player viewer) {
Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
- apolloPlayerOpt.ifPresent(this.stopwatchModule::resetStopwatch);
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.stopwatchModule.resetStopwatch(apolloPlayer, "parkour-stopwatch"));
+}
+```
+
+### Resetting all Stopwatches
+
+```java
+public void resetStopwatchesExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(this.stopwatchModule::resetStopwatches);
+}
+```
+
+### Adding a Timer
+
+```java
+public void addTimerExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+
+ apolloPlayerOpt.ifPresent(apolloPlayer -> {
+ this.stopwatchModule.addTimer(apolloPlayer, Timer.builder()
+ .id("game-timer")
+ .name("Countdown")
+ .duration(Duration.ofSeconds(45))
+ .loop(false)
+ .preventModification(true)
+ .hideWhenStopped(false)
+ .displayFormat("mm:ss")
+ .titleText(Component.text("Time's up!", NamedTextColor.RED))
+ .inGameNotification(true)
+ .textColor(ApolloColors.LIGHT_PURPLE)
+ .hudPosition(HudPosition.builder()
+ .x(-10)
+ .y(30)
+ .build()
+ )
+ .build());
+ });
+}
+```
+
+### Removing a Timer
+
+```java
+public void removeTimerExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.stopwatchModule.removeTimer(apolloPlayer, "game-timer"));
+}
+```
+
+### Starting a Timer
+
+```java
+public void startTimerExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.stopwatchModule.startTimer(apolloPlayer, "game-timer"));
+}
+```
+
+### Stopping a Timer
+
+```java
+public void stopTimerExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.stopwatchModule.stopTimer(apolloPlayer, "game-timer"));
+}
+```
+
+### Resetting a Timer
+
+```java
+public void resetTimerExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.stopwatchModule.resetTimer(apolloPlayer, "game-timer"));
+}
+```
+
+### Resetting all Timers
+
+```java
+public void resetTimersExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(this.stopwatchModule::resetTimers);
}
```
+#### `Stopwatch` Options
+
+`.id(String)` is the unique server-assigned id for the stopwatch.
+
+```java
+.id("parkour-stopwatch")
+```
+
+`.name(String)` is the HUD display name.
+
+```java
+.name("Parkour")
+```
+
+`.resetOnStart(boolean)` determines whether to reset elapsed time on each start.
+
+```java
+.resetOnStart(true)
+```
+
+`.preventModification(boolean)` prevents the user from modifying the options for this stopwatch on the client side.
+
+```java
+.preventModification(true)
+```
+
+`.hideWhenStopped(boolean)` determines whether the stopwatch is hidden from the HUD when stopped.
+
+```java
+.hideWhenStopped(false)
+```
+
+`.displayFormat(String)` is a format string (e.g. `"mm:ss"`). Uses the default display format if not provided.
+
+```java
+.displayFormat("mm:ss")
+```
+
+`.textColor(java.awt.Color)` is the text color. Defaults to white if not provided. See the [colors page](/apollo/developers/utilities/colors) for more.
+
+```java
+.textColor(ApolloColors.DARK_AQUA)
+```
+
+`.hudPosition(HudPosition)` is the HUD position on the client screen. If not provided, the stopwatch is auto-stacked.
+
+```java
+.hudPosition(HudPosition.builder().x(-30).y(30).build())
+```
+
+#### `Timer` Options
+
+`.id(String)` is the unique server-assigned id for the timer.
+
+```java
+.id("game-timer")
+```
+
+`.name(String)` is the HUD display name.
+
+```java
+.name("Countdown")
+```
+
+`.duration(java.time.Duration)` is the countdown duration.
+
+```java
+.duration(Duration.ofSeconds(45))
+```
+
+`.loop(boolean)` determines whether the timer restarts automatically when finished.
+
+```java
+.loop(false)
+```
+
+`.preventModification(boolean)` prevents the user from modifying the options for this timer on the client side.
+
+```java
+.preventModification(true)
+```
+
+`.hideWhenStopped(boolean)` determines whether the timer is hidden from the HUD when stopped.
+
+```java
+.hideWhenStopped(false)
+```
+
+`.displayFormat(String)` is a format string. Uses the default display format if not provided.
+
+```java
+.displayFormat("mm:ss")
+```
+
+`.titleText(Component)` is the on-screen title shown when the timer finishes. Set to `null` or omit to skip. See the [chat components](https://docs.advntr.dev/text.html) page for more.
+
+```java
+.titleText(Component.text("Time's up!", NamedTextColor.RED))
+```
+
+`.inGameNotification(boolean)` determines whether to show an in-game popup when the timer finishes.
+
+```java
+.inGameNotification(true)
+```
+
+`.textColor(java.awt.Color)` is the text color. Defaults to white if not provided. See the [colors page](/apollo/developers/utilities/colors) for more.
+
+```java
+.textColor(ApolloColors.LIGHT_PURPLE)
+```
+
+`.hudPosition(HudPosition)` is the HUD position on the client screen. If not provided, the timer is auto-stacked.
+
+```java
+.hudPosition(HudPosition.builder().x(-10).y(30).build())
+```
+
+### Adding a Stopwatch
+
**Lightweight Protobuf examples.** See [Lightweight Protobuf](/apollo/developers/lightweight/protobuf) for setup.
-**Start Stopwatch**
+```java
+public void addStopwatchExample(Player viewer) {
+ AddStopwatchMessage message = AddStopwatchMessage.newBuilder()
+ .setId("parkour-stopwatch")
+ .setName("Parkour")
+ .setResetOnStart(true)
+ .setPreventModification(true)
+ .setHideWhenStopped(false)
+ .setDisplayFormat("mm:ss")
+ .setTextColor(ProtobufUtil.createColorProto(new Color(0, 170, 170)))
+ .setHudPosition(HudPosition.newBuilder()
+ .setX(-30)
+ .setY(30)
+ .build()
+ )
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Removing a Stopwatch
+
+```java
+public void removeStopwatchExample(Player viewer) {
+ RemoveStopwatchMessage message = RemoveStopwatchMessage.newBuilder()
+ .setId("parkour-stopwatch")
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Starting a Stopwatch
```java
public void startStopwatchExample(Player viewer) {
- StartStopwatchMessage message = StartStopwatchMessage.getDefaultInstance();
+ StartStopwatchMessage message = StartStopwatchMessage.newBuilder()
+ .setId("parkour-stopwatch")
+ .build();
+
ProtobufPacketUtil.sendPacket(viewer, message);
}
```
-**Stop Stopwatch**
+### Stopping a Stopwatch
```java
public void stopStopwatchExample(Player viewer) {
- StopStopwatchMessage message = StopStopwatchMessage.getDefaultInstance();
+ StopStopwatchMessage message = StopStopwatchMessage.newBuilder()
+ .setId("parkour-stopwatch")
+ .build();
+
ProtobufPacketUtil.sendPacket(viewer, message);
}
```
-**Reset Stopwatch**
+### Resetting a Stopwatch
```java
public void resetStopwatchExample(Player viewer) {
- ResetStopwatchMessage message = ResetStopwatchMessage.getDefaultInstance();
+ ResetStopwatchMessage message = ResetStopwatchMessage.newBuilder()
+ .setId("parkour-stopwatch")
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Resetting all Stopwatches
+
+```java
+public void resetStopwatchesExample(Player viewer) {
+ ResetStopwatchesMessage message = ResetStopwatchesMessage.getDefaultInstance();
+ ProtobufPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Adding a Timer
+
+```java
+public void addTimerExample(Player viewer) {
+ AddTimerMessage message = AddTimerMessage.newBuilder()
+ .setId("game-timer")
+ .setName("Countdown")
+ .setDuration(ProtobufUtil.createDurationProto(Duration.ofSeconds(45)))
+ .setLoop(false)
+ .setPreventModification(true)
+ .setHideWhenStopped(false)
+ .setDisplayFormat("mm:ss")
+ .setTitleTextAdventureJsonLines(AdventureUtil.toJson(
+ Component.text("Time's up!", NamedTextColor.RED)
+ ))
+ .setInGameNotification(true)
+ .setTextColor(ProtobufUtil.createColorProto(new Color(255, 85, 255)))
+ .setHudPosition(HudPosition.newBuilder()
+ .setX(-10)
+ .setY(30)
+ .build()
+ )
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Removing a Timer
+
+```java
+public void removeTimerExample(Player viewer) {
+ RemoveTimerMessage message = RemoveTimerMessage.newBuilder()
+ .setId("game-timer")
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Starting a Timer
+
+```java
+public void startTimerExample(Player viewer) {
+ StartTimerMessage message = StartTimerMessage.newBuilder()
+ .setId("game-timer")
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Stopping a Timer
+
+```java
+public void stopTimerExample(Player viewer) {
+ StopTimerMessage message = StopTimerMessage.newBuilder()
+ .setId("game-timer")
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Resetting a Timer
+
+```java
+public void resetTimerExample(Player viewer) {
+ ResetTimerMessage message = ResetTimerMessage.newBuilder()
+ .setId("game-timer")
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Resetting all Timers
+
+```java
+public void resetTimersExample(Player viewer) {
+ ResetTimersMessage message = ResetTimersMessage.getDefaultInstance();
ProtobufPacketUtil.sendPacket(viewer, message);
}
```
@@ -100,34 +477,170 @@ public void resetStopwatchExample(Player viewer) {
**Lightweight JSON examples.** See [Lightweight JSON](/apollo/developers/lightweight/json) for setup.
-**Start Stopwatch**
+### Adding a Stopwatch
+
+```java
+public void addStopwatchExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.AddStopwatchMessage");
+ message.addProperty("id", "parkour-stopwatch");
+ message.addProperty("name", "Parkour");
+ message.addProperty("reset_on_start", true);
+ message.addProperty("prevent_modification", true);
+ message.addProperty("hide_when_stopped", false);
+ message.addProperty("display_format", "mm:ss");
+ message.add("text_color", JsonUtil.createColorObject(new Color(0, 170, 170)));
+
+ JsonObject stopwatchPosition = new JsonObject();
+ stopwatchPosition.addProperty("x", -30);
+ stopwatchPosition.addProperty("y", 30);
+ message.add("hud_position", stopwatchPosition);
+
+ JsonPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Removing a Stopwatch
+
+```java
+public void removeStopwatchExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.RemoveStopwatchMessage");
+ message.addProperty("id", "parkour-stopwatch");
+
+ JsonPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Starting a Stopwatch
```java
public void startStopwatchExample(Player viewer) {
JsonObject message = new JsonObject();
message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.StartStopwatchMessage");
+ message.addProperty("id", "parkour-stopwatch");
JsonPacketUtil.sendPacket(viewer, message);
}
```
-**Stop Stopwatch**
+### Stopping a Stopwatch
```java
public void stopStopwatchExample(Player viewer) {
JsonObject message = new JsonObject();
message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.StopStopwatchMessage");
+ message.addProperty("id", "parkour-stopwatch");
JsonPacketUtil.sendPacket(viewer, message);
}
```
-**Reset Stopwatch**
+### Resetting a Stopwatch
```java
public void resetStopwatchExample(Player viewer) {
JsonObject message = new JsonObject();
message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.ResetStopwatchMessage");
+ message.addProperty("id", "parkour-stopwatch");
+
+ JsonPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Resetting all Stopwatches
+
+```java
+public void resetStopwatchesExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.ResetStopwatchesMessage");
+
+ JsonPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Adding a Timer
+
+```java
+public void addTimerExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.AddTimerMessage");
+ message.addProperty("id", "game-timer");
+ message.addProperty("name", "Countdown");
+ message.addProperty("duration", JsonUtil.createDurationObject(Duration.ofSeconds(45)));
+ message.addProperty("loop", false);
+ message.addProperty("prevent_modification", true);
+ message.addProperty("hide_when_stopped", false);
+ message.addProperty("display_format", "mm:ss");
+ message.addProperty("title_text_adventure_json_lines", AdventureUtil.toJson(
+ Component.text("Time's up!", NamedTextColor.RED)
+ ));
+ message.addProperty("in_game_notification", true);
+ message.add("text_color", JsonUtil.createColorObject(new Color(255, 85, 255)));
+
+ JsonObject timerPosition = new JsonObject();
+ timerPosition.addProperty("x", -10);
+ timerPosition.addProperty("y", 30);
+ message.add("hud_position", timerPosition);
+
+ JsonPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Removing a Timer
+
+```java
+public void removeTimerExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.RemoveTimerMessage");
+ message.addProperty("id", "game-timer");
+
+ JsonPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Starting a Timer
+
+```java
+public void startTimerExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.StartTimerMessage");
+ message.addProperty("id", "game-timer");
+
+ JsonPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Stopping a Timer
+
+```java
+public void stopTimerExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.StopTimerMessage");
+ message.addProperty("id", "game-timer");
+
+ JsonPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Resetting a Timer
+
+```java
+public void resetTimerExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.ResetTimerMessage");
+ message.addProperty("id", "game-timer");
+
+ JsonPacketUtil.sendPacket(viewer, message);
+}
+```
+
+### Resetting all Timers
+
+```java
+public void resetTimersExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.ResetTimersMessage");
JsonPacketUtil.sendPacket(viewer, message);
}
diff --git a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/StopwatchApiExample.java b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/StopwatchApiExample.java
index 620bb4fb..2ff0d6c4 100644
--- a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/StopwatchApiExample.java
+++ b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/StopwatchApiExample.java
@@ -24,32 +24,128 @@
package com.lunarclient.apollo.example.api.module;
import com.lunarclient.apollo.Apollo;
+import com.lunarclient.apollo.common.ApolloColors;
+import com.lunarclient.apollo.common.location.HudPosition;
import com.lunarclient.apollo.example.module.impl.StopwatchExample;
+import com.lunarclient.apollo.module.stopwatch.Stopwatch;
import com.lunarclient.apollo.module.stopwatch.StopwatchModule;
+import com.lunarclient.apollo.module.stopwatch.Timer;
import com.lunarclient.apollo.player.ApolloPlayer;
+import java.time.Duration;
import java.util.Optional;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
public class StopwatchApiExample extends StopwatchExample {
private final StopwatchModule stopwatchModule = Apollo.getModuleManager().getModule(StopwatchModule.class);
+ @Override
+ public void addStopwatchExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+
+ apolloPlayerOpt.ifPresent(apolloPlayer -> {
+ this.stopwatchModule.addStopwatch(apolloPlayer, Stopwatch.builder()
+ .id("parkour-stopwatch")
+ .name("Parkour")
+ .resetOnStart(true)
+ .preventModification(true)
+ .hideWhenStopped(false)
+ .displayFormat("mm:ss")
+ .textColor(ApolloColors.DARK_AQUA)
+ .hudPosition(HudPosition.builder()
+ .x(-30)
+ .y(30)
+ .build()
+ )
+ .build());
+ });
+ }
+
+ @Override
+ public void removeStopwatchExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.stopwatchModule.removeStopwatch(apolloPlayer, "parkour-stopwatch"));
+ }
+
@Override
public void startStopwatchExample(Player viewer) {
Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
- apolloPlayerOpt.ifPresent(this.stopwatchModule::startStopwatch);
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.stopwatchModule.startStopwatch(apolloPlayer, "parkour-stopwatch"));
}
@Override
public void stopStopwatchExample(Player viewer) {
Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
- apolloPlayerOpt.ifPresent(this.stopwatchModule::stopStopwatch);
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.stopwatchModule.stopStopwatch(apolloPlayer, "parkour-stopwatch"));
}
@Override
public void resetStopwatchExample(Player viewer) {
Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
- apolloPlayerOpt.ifPresent(this.stopwatchModule::resetStopwatch);
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.stopwatchModule.resetStopwatch(apolloPlayer, "parkour-stopwatch"));
+ }
+
+ @Override
+ public void resetStopwatchesExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(this.stopwatchModule::resetStopwatches);
+ }
+
+ @Override
+ public void addTimerExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+
+ apolloPlayerOpt.ifPresent(apolloPlayer -> {
+ this.stopwatchModule.addTimer(apolloPlayer, Timer.builder()
+ .id("game-timer")
+ .name("Countdown")
+ .duration(Duration.ofSeconds(45))
+ .loop(false)
+ .preventModification(true)
+ .hideWhenStopped(false)
+ .displayFormat("mm:ss")
+ .titleText(Component.text("Time's up!", NamedTextColor.RED))
+ .inGameNotification(true)
+ .textColor(ApolloColors.LIGHT_PURPLE)
+ .hudPosition(HudPosition.builder()
+ .x(-10)
+ .y(30)
+ .build()
+ )
+ .build());
+ });
+ }
+
+ @Override
+ public void removeTimerExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.stopwatchModule.removeTimer(apolloPlayer, "game-timer"));
+ }
+
+ @Override
+ public void startTimerExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.stopwatchModule.startTimer(apolloPlayer, "game-timer"));
+ }
+
+ @Override
+ public void stopTimerExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.stopwatchModule.stopTimer(apolloPlayer, "game-timer"));
+ }
+
+ @Override
+ public void resetTimerExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.stopwatchModule.resetTimer(apolloPlayer, "game-timer"));
+ }
+
+ @Override
+ public void resetTimersExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(this.stopwatchModule::resetTimers);
}
}
diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/StopwatchCommand.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/StopwatchCommand.java
index a4f081a1..a4bbbc7e 100644
--- a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/StopwatchCommand.java
+++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/StopwatchCommand.java
@@ -42,38 +42,124 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command
Player player = (Player) sender;
- if (args.length != 1) {
- player.sendMessage("Usage: /stopwatch ");
+ if (args.length < 2) {
+ player.sendMessage("Usage: /stopwatch stopwatch ");
+ player.sendMessage("Usage: /stopwatch timer ");
return true;
}
StopwatchExample stopwatchExample = ApolloExamplePlugin.getInstance().getStopwatchExample();
+ String type = args[0].toLowerCase();
+ String action = args[1].toLowerCase();
+
+ switch (type) {
+ case "stopwatch": {
+ this.handleStopwatch(player, stopwatchExample, action);
+ break;
+ }
+
+ case "timer": {
+ this.handleTimer(player, stopwatchExample, action);
+ break;
+ }
+
+ default: {
+ player.sendMessage("Usage: /stopwatch stopwatch ");
+ player.sendMessage("Usage: /stopwatch timer ");
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ private void handleStopwatch(Player player, StopwatchExample example, String action) {
+ switch (action) {
+ case "add": {
+ example.addStopwatchExample(player);
+ player.sendMessage("Adding stopwatch...");
+ break;
+ }
+
+ case "remove": {
+ example.removeStopwatchExample(player);
+ player.sendMessage("Removing stopwatch...");
+ break;
+ }
- switch (args[0].toLowerCase()) {
case "start": {
- stopwatchExample.startStopwatchExample(player);
- player.sendMessage("Starting stopwatch....");
+ example.startStopwatchExample(player);
+ player.sendMessage("Starting stopwatch...");
break;
}
case "stop": {
- stopwatchExample.stopStopwatchExample(player);
- player.sendMessage("Stopping stopwatch....");
+ example.stopStopwatchExample(player);
+ player.sendMessage("Stopping stopwatch...");
break;
}
case "reset": {
- stopwatchExample.resetStopwatchExample(player);
- player.sendMessage("Resetting stopwatch....");
+ example.resetStopwatchExample(player);
+ player.sendMessage("Resetting stopwatch...");
+ break;
+ }
+
+ case "resetall": {
+ example.resetStopwatchesExample(player);
+ player.sendMessage("Resetting all stopwatches...");
break;
}
default: {
- player.sendMessage("Usage: /stopwatch ");
+ player.sendMessage("Usage: /stopwatch stopwatch ");
break;
}
}
+ }
- return true;
+ private void handleTimer(Player player, StopwatchExample example, String action) {
+ switch (action) {
+ case "add": {
+ example.addTimerExample(player);
+ player.sendMessage("Adding timer...");
+ break;
+ }
+
+ case "remove": {
+ example.removeTimerExample(player);
+ player.sendMessage("Removing timer...");
+ break;
+ }
+
+ case "start": {
+ example.startTimerExample(player);
+ player.sendMessage("Starting timer...");
+ break;
+ }
+
+ case "stop": {
+ example.stopTimerExample(player);
+ player.sendMessage("Stopping timer...");
+ break;
+ }
+
+ case "reset": {
+ example.resetTimerExample(player);
+ player.sendMessage("Resetting timer...");
+ break;
+ }
+
+ case "resetall": {
+ example.resetTimersExample(player);
+ player.sendMessage("Resetting all timers...");
+ break;
+ }
+
+ default: {
+ player.sendMessage("Usage: /stopwatch timer ");
+ break;
+ }
+ }
}
}
diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/StopwatchExample.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/StopwatchExample.java
index 3994bbd4..57d70710 100644
--- a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/StopwatchExample.java
+++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/StopwatchExample.java
@@ -28,10 +28,28 @@
public abstract class StopwatchExample extends ApolloModuleExample {
+ public abstract void addStopwatchExample(Player viewer);
+
+ public abstract void removeStopwatchExample(Player viewer);
+
public abstract void startStopwatchExample(Player viewer);
public abstract void stopStopwatchExample(Player viewer);
public abstract void resetStopwatchExample(Player viewer);
+ public abstract void resetStopwatchesExample(Player viewer);
+
+ public abstract void addTimerExample(Player viewer);
+
+ public abstract void removeTimerExample(Player viewer);
+
+ public abstract void startTimerExample(Player viewer);
+
+ public abstract void stopTimerExample(Player viewer);
+
+ public abstract void resetTimerExample(Player viewer);
+
+ public abstract void resetTimersExample(Player viewer);
+
}
diff --git a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/StopwatchJsonExample.java b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/StopwatchJsonExample.java
index 96a972ca..33031f2e 100644
--- a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/StopwatchJsonExample.java
+++ b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/StopwatchJsonExample.java
@@ -24,16 +24,52 @@
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.json.util.JsonUtil;
import com.lunarclient.apollo.example.module.impl.StopwatchExample;
+import java.awt.Color;
+import java.time.Duration;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
public class StopwatchJsonExample extends StopwatchExample {
+ @Override
+ public void addStopwatchExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.AddStopwatchMessage");
+ message.addProperty("id", "parkour-stopwatch");
+ message.addProperty("name", "Parkour");
+ message.addProperty("reset_on_start", true);
+ message.addProperty("prevent_modification", true);
+ message.addProperty("hide_when_stopped", false);
+ message.addProperty("display_format", "mm:ss");
+ message.add("text_color", JsonUtil.createColorObject(new Color(0, 170, 170)));
+
+ JsonObject stopwatchPosition = new JsonObject();
+ stopwatchPosition.addProperty("x", -30);
+ stopwatchPosition.addProperty("y", 30);
+ message.add("hud_position", stopwatchPosition);
+
+ JsonPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void removeStopwatchExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.RemoveStopwatchMessage");
+ message.addProperty("id", "parkour-stopwatch");
+
+ JsonPacketUtil.sendPacket(viewer, message);
+ }
+
@Override
public void startStopwatchExample(Player viewer) {
JsonObject message = new JsonObject();
message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.StartStopwatchMessage");
+ message.addProperty("id", "parkour-stopwatch");
JsonPacketUtil.sendPacket(viewer, message);
}
@@ -42,6 +78,7 @@ public void startStopwatchExample(Player viewer) {
public void stopStopwatchExample(Player viewer) {
JsonObject message = new JsonObject();
message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.StopStopwatchMessage");
+ message.addProperty("id", "parkour-stopwatch");
JsonPacketUtil.sendPacket(viewer, message);
}
@@ -50,6 +87,84 @@ public void stopStopwatchExample(Player viewer) {
public void resetStopwatchExample(Player viewer) {
JsonObject message = new JsonObject();
message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.ResetStopwatchMessage");
+ message.addProperty("id", "parkour-stopwatch");
+
+ JsonPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void resetStopwatchesExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.ResetStopwatchesMessage");
+
+ JsonPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void addTimerExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.AddTimerMessage");
+ message.addProperty("id", "game-timer");
+ message.addProperty("name", "Countdown");
+ message.addProperty("duration", JsonUtil.createDurationObject(Duration.ofSeconds(45)));
+ message.addProperty("loop", false);
+ message.addProperty("prevent_modification", true);
+ message.addProperty("hide_when_stopped", false);
+ message.addProperty("display_format", "mm:ss");
+ message.addProperty("title_text_adventure_json_lines", AdventureUtil.toJson(
+ Component.text("Time's up!", NamedTextColor.RED)
+ ));
+ message.addProperty("in_game_notification", true);
+ message.add("text_color", JsonUtil.createColorObject(new Color(255, 85, 255)));
+
+ JsonObject timerPosition = new JsonObject();
+ timerPosition.addProperty("x", -10);
+ timerPosition.addProperty("y", 30);
+ message.add("hud_position", timerPosition);
+
+ JsonPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void removeTimerExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.RemoveTimerMessage");
+ message.addProperty("id", "game-timer");
+
+ JsonPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void startTimerExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.StartTimerMessage");
+ message.addProperty("id", "game-timer");
+
+ JsonPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void stopTimerExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.StopTimerMessage");
+ message.addProperty("id", "game-timer");
+
+ JsonPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void resetTimerExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.ResetTimerMessage");
+ message.addProperty("id", "game-timer");
+
+ JsonPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void resetTimersExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.stopwatch.v1.ResetTimersMessage");
JsonPacketUtil.sendPacket(viewer, message);
}
diff --git a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/StopwatchProtoExample.java b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/StopwatchProtoExample.java
index 5f8960b5..9f001529 100644
--- a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/StopwatchProtoExample.java
+++ b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/StopwatchProtoExample.java
@@ -24,29 +24,156 @@
package com.lunarclient.apollo.example.proto.module;
import com.lunarclient.apollo.example.module.impl.StopwatchExample;
+import com.lunarclient.apollo.example.proto.util.AdventureUtil;
import com.lunarclient.apollo.example.proto.util.ProtobufPacketUtil;
+import com.lunarclient.apollo.example.proto.util.ProtobufUtil;
+import com.lunarclient.apollo.hud.v1.HudPosition;
+import com.lunarclient.apollo.stopwatch.v1.AddStopwatchMessage;
+import com.lunarclient.apollo.stopwatch.v1.AddTimerMessage;
+import com.lunarclient.apollo.stopwatch.v1.RemoveStopwatchMessage;
+import com.lunarclient.apollo.stopwatch.v1.RemoveTimerMessage;
import com.lunarclient.apollo.stopwatch.v1.ResetStopwatchMessage;
+import com.lunarclient.apollo.stopwatch.v1.ResetStopwatchesMessage;
+import com.lunarclient.apollo.stopwatch.v1.ResetTimerMessage;
+import com.lunarclient.apollo.stopwatch.v1.ResetTimersMessage;
import com.lunarclient.apollo.stopwatch.v1.StartStopwatchMessage;
+import com.lunarclient.apollo.stopwatch.v1.StartTimerMessage;
import com.lunarclient.apollo.stopwatch.v1.StopStopwatchMessage;
+import com.lunarclient.apollo.stopwatch.v1.StopTimerMessage;
+import java.awt.Color;
+import java.time.Duration;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
public class StopwatchProtoExample extends StopwatchExample {
+ @Override
+ public void addStopwatchExample(Player viewer) {
+ AddStopwatchMessage message = AddStopwatchMessage.newBuilder()
+ .setId("parkour-stopwatch")
+ .setName("Parkour")
+ .setResetOnStart(true)
+ .setPreventModification(true)
+ .setHideWhenStopped(false)
+ .setDisplayFormat("mm:ss")
+ .setTextColor(ProtobufUtil.createColorProto(new Color(0, 170, 170)))
+ .setHudPosition(HudPosition.newBuilder()
+ .setX(-30)
+ .setY(30)
+ .build()
+ )
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void removeStopwatchExample(Player viewer) {
+ RemoveStopwatchMessage message = RemoveStopwatchMessage.newBuilder()
+ .setId("parkour-stopwatch")
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+ }
+
@Override
public void startStopwatchExample(Player viewer) {
- StartStopwatchMessage message = StartStopwatchMessage.getDefaultInstance();
+ StartStopwatchMessage message = StartStopwatchMessage.newBuilder()
+ .setId("parkour-stopwatch")
+ .build();
+
ProtobufPacketUtil.sendPacket(viewer, message);
}
@Override
public void stopStopwatchExample(Player viewer) {
- StopStopwatchMessage message = StopStopwatchMessage.getDefaultInstance();
+ StopStopwatchMessage message = StopStopwatchMessage.newBuilder()
+ .setId("parkour-stopwatch")
+ .build();
+
ProtobufPacketUtil.sendPacket(viewer, message);
}
@Override
public void resetStopwatchExample(Player viewer) {
- ResetStopwatchMessage message = ResetStopwatchMessage.getDefaultInstance();
+ ResetStopwatchMessage message = ResetStopwatchMessage.newBuilder()
+ .setId("parkour-stopwatch")
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void resetStopwatchesExample(Player viewer) {
+ ResetStopwatchesMessage message = ResetStopwatchesMessage.getDefaultInstance();
+ ProtobufPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void addTimerExample(Player viewer) {
+ AddTimerMessage message = AddTimerMessage.newBuilder()
+ .setId("game-timer")
+ .setName("Countdown")
+ .setDuration(ProtobufUtil.createDurationProto(Duration.ofSeconds(45)))
+ .setLoop(false)
+ .setPreventModification(true)
+ .setHideWhenStopped(false)
+ .setDisplayFormat("mm:ss")
+ .setTitleTextAdventureJsonLines(AdventureUtil.toJson(
+ Component.text("Time's up!", NamedTextColor.RED)
+ ))
+ .setInGameNotification(true)
+ .setTextColor(ProtobufUtil.createColorProto(new Color(255, 85, 255)))
+ .setHudPosition(HudPosition.newBuilder()
+ .setX(-10)
+ .setY(30)
+ .build()
+ )
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void removeTimerExample(Player viewer) {
+ RemoveTimerMessage message = RemoveTimerMessage.newBuilder()
+ .setId("game-timer")
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void startTimerExample(Player viewer) {
+ StartTimerMessage message = StartTimerMessage.newBuilder()
+ .setId("game-timer")
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void stopTimerExample(Player viewer) {
+ StopTimerMessage message = StopTimerMessage.newBuilder()
+ .setId("game-timer")
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void resetTimerExample(Player viewer) {
+ ResetTimerMessage message = ResetTimerMessage.newBuilder()
+ .setId("game-timer")
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void resetTimersExample(Player viewer) {
+ ResetTimersMessage message = ResetTimersMessage.getDefaultInstance();
ProtobufPacketUtil.sendPacket(viewer, message);
}