diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index f65bf2a7..4a465fe7 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/') }}"
+ if: "${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/version/') || github.ref == 'refs/heads/feature/totem-counter-module' }}"
run: ./gradlew publish
- name: Gradle Release
diff --git a/api/src/main/java/com/lunarclient/apollo/module/totemcounter/TotemCounter.java b/api/src/main/java/com/lunarclient/apollo/module/totemcounter/TotemCounter.java
new file mode 100644
index 00000000..3848c14f
--- /dev/null
+++ b/api/src/main/java/com/lunarclient/apollo/module/totemcounter/TotemCounter.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.totemcounter;
+
+import java.util.UUID;
+import lombok.Builder;
+import lombok.Getter;
+import org.jetbrains.annotations.Range;
+
+/**
+ * Represents a totem counter which can be shown on the client.
+ *
+ * @since 1.3.0
+ */
+@Getter
+@Builder
+public final class TotemCounter {
+
+ /**
+ * Returns the totem counter player's {@link UUID}.
+ *
+ * @return the player uuid
+ * @since 1.3.0
+ */
+ UUID playerUuid;
+
+ /**
+ * Returns the totem counter {@link Integer} amount of totems the player
+ * has popped.
+ *
+ * @return the totem counter pops
+ * @since 1.3.0
+ */
+ @Range(from = 0, to = Integer.MAX_VALUE) int pops;
+
+}
diff --git a/api/src/main/java/com/lunarclient/apollo/module/totemcounter/TotemCounterModule.java b/api/src/main/java/com/lunarclient/apollo/module/totemcounter/TotemCounterModule.java
new file mode 100644
index 00000000..6599551b
--- /dev/null
+++ b/api/src/main/java/com/lunarclient/apollo/module/totemcounter/TotemCounterModule.java
@@ -0,0 +1,123 @@
+/*
+ * 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.totemcounter;
+
+import com.lunarclient.apollo.module.ApolloModule;
+import com.lunarclient.apollo.module.ModuleDefinition;
+import com.lunarclient.apollo.option.Option;
+import com.lunarclient.apollo.option.SimpleOption;
+import com.lunarclient.apollo.recipients.Recipients;
+import io.leangen.geantyref.TypeToken;
+import java.util.Collection;
+import java.util.UUID;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Represents the totem counter module.
+ *
+ *
Overrides the totem pops shown by the client's Totem Counter mod.
+ * The client keeps counting totem pops on its own on top of the overridden
+ * values, unless {@link #DISABLE_LOCAL_TRACKING} is enabled.
+ *
+ * @since 1.3.0
+ */
+@ApiStatus.NonExtendable
+@ModuleDefinition(id = "totem_counter", name = "Totem Counter")
+public abstract class TotemCounterModule extends ApolloModule {
+
+ /**
+ * Whether the client stops tracking and resetting totem pops on its own.
+ *
+ * @since 1.3.0
+ */
+ public static final SimpleOption DISABLE_LOCAL_TRACKING = Option.builder()
+ .comment("Set to 'true' to stop the client from tracking and resetting totem pops on its own, otherwise 'false'.")
+ .node("disable-local-tracking").type(TypeToken.get(Boolean.class))
+ .defaultValue(false).notifyClient().build();
+
+ protected TotemCounterModule() {
+ this.registerOptions(
+ TotemCounterModule.DISABLE_LOCAL_TRACKING
+ );
+ }
+
+ @Override
+ public boolean isClientNotify() {
+ return true;
+ }
+
+ /**
+ * Overrides the {@link TotemCounter}s for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @param totemCounters the totem counters
+ * @since 1.3.0
+ */
+ public abstract void overrideTotemCounters(Recipients recipients, Collection totemCounters);
+
+ /**
+ * Overrides the {@link TotemCounter} for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @param totemCounter the totem counter
+ * @since 1.3.0
+ */
+ public abstract void overrideTotemCounter(Recipients recipients, TotemCounter totemCounter);
+
+ /**
+ * Resets the {@link TotemCounter} for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @param playerUuid the player whose totem counter we are manipulating
+ * @since 1.3.0
+ */
+ public abstract void resetTotemCounter(Recipients recipients, UUID playerUuid);
+
+ /**
+ * Resets the {@link TotemCounter} for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @param totemCounter the totem counter
+ * @since 1.3.0
+ */
+ public abstract void resetTotemCounter(Recipients recipients, TotemCounter totemCounter);
+
+ /**
+ * Resets the {@link TotemCounter}s for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @param playerUuids the players whose totem counters we are manipulating
+ * @since 1.3.0
+ */
+ public abstract void resetTotemCounter(Recipients recipients, Collection playerUuids);
+
+ /**
+ * Resets all {@link TotemCounter}s for the {@link Recipients}.
+ *
+ * @param recipients the recipients that are receiving the packet
+ * @since 1.3.0
+ */
+ public abstract void resetTotemCounters(Recipients recipients);
+
+}
diff --git a/common/src/main/java/com/lunarclient/apollo/module/totemcounter/TotemCounterModuleImpl.java b/common/src/main/java/com/lunarclient/apollo/module/totemcounter/TotemCounterModuleImpl.java
new file mode 100644
index 00000000..94d1a20d
--- /dev/null
+++ b/common/src/main/java/com/lunarclient/apollo/module/totemcounter/TotemCounterModuleImpl.java
@@ -0,0 +1,96 @@
+/*
+ * 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.totemcounter;
+
+import com.lunarclient.apollo.ApolloManager;
+import com.lunarclient.apollo.network.NetworkTypes;
+import com.lunarclient.apollo.recipients.Recipients;
+import com.lunarclient.apollo.totemcounter.v1.OverrideTotemCountersMessage;
+import com.lunarclient.apollo.totemcounter.v1.ResetTotemCounterMessage;
+import com.lunarclient.apollo.totemcounter.v1.ResetTotemCountersMessage;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.UUID;
+import lombok.NonNull;
+
+import static com.lunarclient.apollo.util.Ranges.checkPositive;
+
+/**
+ * Provides the totem counter module.
+ *
+ * @since 1.3.0
+ */
+public final class TotemCounterModuleImpl extends TotemCounterModule {
+
+ @Override
+ public void overrideTotemCounters(@NonNull Recipients recipients, @NonNull Collection totemCounters) {
+ OverrideTotemCountersMessage.Builder builder = OverrideTotemCountersMessage.newBuilder();
+
+ for (TotemCounter totemCounter : totemCounters) {
+ builder.addTotemCounters(this.toProtobuf(totemCounter));
+ }
+
+ ApolloManager.getNetworkManager().sendPacket(recipients, builder.build());
+ }
+
+ @Override
+ public void overrideTotemCounter(@NonNull Recipients recipients, @NonNull TotemCounter totemCounter) {
+ this.overrideTotemCounters(recipients, Collections.singleton(totemCounter));
+ }
+
+ @Override
+ public void resetTotemCounter(@NonNull Recipients recipients, @NonNull UUID playerUuid) {
+ this.resetTotemCounter(recipients, Collections.singleton(playerUuid));
+ }
+
+ @Override
+ public void resetTotemCounter(@NonNull Recipients recipients, @NonNull TotemCounter totemCounter) {
+ this.resetTotemCounter(recipients, Collections.singleton(totemCounter.getPlayerUuid()));
+ }
+
+ @Override
+ public void resetTotemCounter(@NonNull Recipients recipients, @NonNull Collection playerUuids) {
+ ResetTotemCounterMessage.Builder builder = ResetTotemCounterMessage.newBuilder();
+
+ for (UUID playerUuid : playerUuids) {
+ builder.addPlayerUuids(NetworkTypes.toProtobuf(playerUuid));
+ }
+
+ ApolloManager.getNetworkManager().sendPacket(recipients, builder.build());
+ }
+
+ @Override
+ public void resetTotemCounters(@NonNull Recipients recipients) {
+ ResetTotemCountersMessage message = ResetTotemCountersMessage.getDefaultInstance();
+ ApolloManager.getNetworkManager().sendPacket(recipients, message);
+ }
+
+ private com.lunarclient.apollo.totemcounter.v1.TotemCounter toProtobuf(TotemCounter totemCounter) {
+ return com.lunarclient.apollo.totemcounter.v1.TotemCounter.newBuilder()
+ .setPlayerUuid(NetworkTypes.toProtobuf(totemCounter.getPlayerUuid()))
+ .setPops(checkPositive(totemCounter.getPops(), "TotemCounter#pops"))
+ .build();
+ }
+
+}
diff --git a/docs/developers/lightweight/json/packet-util.mdx b/docs/developers/lightweight/json/packet-util.mdx
index ee5ec7d0..c1517c7c 100644
--- a/docs/developers/lightweight/json/packet-util.mdx
+++ b/docs/developers/lightweight/json/packet-util.mdx
@@ -15,7 +15,7 @@ To utilize Apollo Modules, first define a list of the modules you want to use:
```java
private static final List APOLLO_MODULES = Arrays.asList("auto_text_hotkey", "beam", "border", "chat", "colored_fire", "combat", "cooldown",
"entity", "glint", "glow", "hologram", "inventory", "limb", "mod_setting", "nametag", "nick_hider", "notification", "pay_now", "packet_enrichment",
- "rich_presence", "saturation", "server_link", "server_rule", "staff_mod", "stopwatch", "team", "tebex", "title", "tnt_countdown", "transfer", "vignette", "waypoint"
+ "rich_presence", "saturation", "server_link", "server_rule", "staff_mod", "stopwatch", "team", "tebex", "title", "tnt_countdown", "totem_counter", "transfer", "vignette", "waypoint"
);
```
@@ -59,6 +59,7 @@ static {
CONFIG_MODULE_PROPERTIES.put("server_rule", "crystal-optimizer", false);
CONFIG_MODULE_PROPERTIES.put("tnt_countdown", "tnt-ticks", 80);
CONFIG_MODULE_PROPERTIES.put("title", "clear-title-on-server-switch", false);
+ CONFIG_MODULE_PROPERTIES.put("totem_counter", "disable-local-tracking", false);
CONFIG_MODULE_PROPERTIES.put("waypoint", "server-handles-waypoints", false);
}
```
diff --git a/docs/developers/lightweight/protobuf.mdx b/docs/developers/lightweight/protobuf.mdx
index 0bb373d6..5845b980 100644
--- a/docs/developers/lightweight/protobuf.mdx
+++ b/docs/developers/lightweight/protobuf.mdx
@@ -26,7 +26,7 @@ Available fields for each message, including their types, are available on the B
com.lunarclient
apollo-protos
- 0.2.4
+ 0.2.5
```
@@ -41,7 +41,7 @@ Available fields for each message, including their types, are available on the B
}
dependencies {
- api 'com.lunarclient:apollo-protos:0.2.4'
+ api 'com.lunarclient:apollo-protos:0.2.5'
}
```
@@ -55,7 +55,7 @@ Available fields for each message, including their types, are available on the B
}
dependencies {
- api("com.lunarclient:apollo-protos:0.2.4")
+ api("com.lunarclient:apollo-protos:0.2.5")
}
```
diff --git a/docs/developers/lightweight/protobuf/packet-util.mdx b/docs/developers/lightweight/protobuf/packet-util.mdx
index 67e7b9d5..cd7bee5b 100644
--- a/docs/developers/lightweight/protobuf/packet-util.mdx
+++ b/docs/developers/lightweight/protobuf/packet-util.mdx
@@ -15,7 +15,7 @@ To utilize Apollo Modules, first define a list of the modules you want to use:
```java
private static final List APOLLO_MODULES = Arrays.asList("auto_text_hotkey", "beam", "border", "chat", "colored_fire", "combat", "cooldown",
"entity", "glint", "glow", "hologram", "inventory", "limb", "mod_setting", "nametag", "nick_hider", "notification", "pay_now", "packet_enrichment",
- "rich_presence", "saturation", "server_link", "server_rule", "staff_mod", "stopwatch", "team", "tebex", "title", "tnt_countdown", "transfer", "vignette", "waypoint"
+ "rich_presence", "saturation", "server_link", "server_rule", "staff_mod", "stopwatch", "team", "tebex", "title", "tnt_countdown", "totem_counter", "transfer", "vignette", "waypoint"
);
```
@@ -65,6 +65,7 @@ static {
CONFIG_MODULE_PROPERTIES.put("server_rule", "crystal-optimizer", Value.newBuilder().setBoolValue(false).build());
CONFIG_MODULE_PROPERTIES.put("tnt_countdown", "tnt-ticks", Value.newBuilder().setNumberValue(80).build());
CONFIG_MODULE_PROPERTIES.put("title", "clear-title-on-server-switch", Value.newBuilder().setBoolValue(false).build());
+ CONFIG_MODULE_PROPERTIES.put("totem_counter", "disable-local-tracking", Value.newBuilder().setBoolValue(false).build());
CONFIG_MODULE_PROPERTIES.put("waypoint", "server-handles-waypoints", Value.newBuilder().setBoolValue(false).build());
}
```
diff --git a/docs/developers/modules.mdx b/docs/developers/modules.mdx
index 1d8f8fc7..cdf6d7f8 100644
--- a/docs/developers/modules.mdx
+++ b/docs/developers/modules.mdx
@@ -37,6 +37,7 @@ These modules are available to all servers using Apollo and do not require any p
🔗 [Tebex](/apollo/developers/modules/tebex)
🔗 [Title](/apollo/developers/modules/title)
🔗 [TNT Countdown](/apollo/developers/modules/tntcountdown)
+🔗 [Totem Counter](/apollo/developers/modules/totemcounter)
🔗 [Transfer](/apollo/developers/modules/transfer)
🔗 [Vignette](/apollo/developers/modules/vignette)
🔗 [Waypoint](/apollo/developers/modules/waypoint)
diff --git a/docs/developers/modules/_meta.json b/docs/developers/modules/_meta.json
index 11e872d0..4b5b8831 100644
--- a/docs/developers/modules/_meta.json
+++ b/docs/developers/modules/_meta.json
@@ -30,6 +30,7 @@
"tebex": "Tebex",
"title": "Title",
"tntcountdown": "TNT Countdown",
+ "totemcounter": "Totem Counter",
"transfer": "Transfer",
"vignette": "Vignette",
"waypoint": "Waypoint"
diff --git a/docs/developers/modules/totemcounter.mdx b/docs/developers/modules/totemcounter.mdx
new file mode 100644
index 00000000..ce112998
--- /dev/null
+++ b/docs/developers/modules/totemcounter.mdx
@@ -0,0 +1,447 @@
+import { Callout, Tab, Tabs } from 'nextra-theme-docs'
+
+# Totem Counter Module
+
+## Overview
+
+The totem counter module allows servers to control the totem pops displayed by the Totem Counter mod.
+
+- Adds the ability to override the totem pops of any player, shown on their nametag, tab list entry and the Totem Counter HUD.
+ - Display totem pops the client can't track on its own, such as pops outside of tracking range or before the player joined.
+ - Ability to stop the client from tracking and resetting totem pops on its own.
+
+An overridden totem counter replaces the client's own count for that player, and the client keeps counting on top of it unless `DISABLE_LOCAL_TRACKING` is enabled.
+
+
+ This module is only supported for 1.12 and above versions of Lunar Client.
+
+
+
+ The Totem Counter mod is disabled by default, totem counters sent while it is disabled are ignored. See the [Totem Counter mod](/apollo/developers/mods/totemcounter) page to enable it for your players.
+
+
+## Integration
+
+
+ Enable `DISABLE_LOCAL_TRACKING` when sending every totem pop, otherwise the client also counts each pop and shows one more.
+
+
+### Sample Code
+Explore each integration by cycling through each tab, to find the best fit for your requirements and needs.
+
+
+
+
+
+
+**Apollo API examples.** See [General](/apollo/developers/general) for common patterns and helpers.
+
+
+### Overriding a Totem Counter
+
+```java
+public void overrideTotemCounterExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+
+ apolloPlayerOpt.ifPresent(apolloPlayer -> {
+ this.totemCounterModule.overrideTotemCounter(apolloPlayer, TotemCounter.builder()
+ .playerUuid(viewer.getUniqueId())
+ .pops(3)
+ .build()
+ );
+ });
+}
+```
+
+### Overriding multiple Totem Counters
+
+```java
+public void overrideTotemCountersExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+
+ apolloPlayerOpt.ifPresent(apolloPlayer -> {
+ List totemCounters = Bukkit.getOnlinePlayers()
+ .stream().map(player -> TotemCounter.builder()
+ .playerUuid(player.getUniqueId())
+ .pops(2)
+ .build())
+ .collect(Collectors.toList());
+
+ this.totemCounterModule.overrideTotemCounters(apolloPlayer, totemCounters);
+ });
+}
+```
+
+### Resetting a Totem Counter
+
+The client forgets the player's totem pops, including the ones it tracked on its own.
+
+```java
+public void resetTotemCounterExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.totemCounterModule.resetTotemCounter(apolloPlayer, viewer.getUniqueId()));
+}
+```
+
+### Resetting all Totem Counters
+
+The client forgets every player's totem pops, including the ones it tracked on its own.
+
+```java
+public void resetTotemCountersExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(this.totemCounterModule::resetTotemCounters);
+}
+```
+
+### Tracking Totem Pops
+
+```java
+private final Map totemPops = new ConcurrentHashMap<>();
+
+@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+private void onEntityResurrect(EntityResurrectEvent event) {
+ if (!(event.getEntity() instanceof Player)) {
+ return;
+ }
+
+ Player player = (Player) event.getEntity();
+ int pops = this.totemPops.merge(player.getUniqueId(), 1, Integer::sum);
+
+ // Pops are absolute, send the new total instead of an increment
+ this.totemCounterModule.overrideTotemCounter(Recipients.ofEveryone(), TotemCounter.builder()
+ .playerUuid(player.getUniqueId())
+ .pops(pops)
+ .build()
+ );
+}
+
+@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+private void onPlayerDeath(PlayerDeathEvent event) {
+ this.resetTotemPops(event.getEntity());
+}
+
+@EventHandler
+private void onPlayerQuit(PlayerQuitEvent event) {
+ this.resetTotemPops(event.getPlayer());
+}
+
+private void resetTotemPops(Player player) {
+ if (this.totemPops.remove(player.getUniqueId()) != null) {
+ this.totemCounterModule.resetTotemCounter(Recipients.ofEveryone(), player.getUniqueId());
+ }
+}
+```
+
+### Toggle Local Tracking
+
+```java
+public void setDisableLocalTracking(boolean value) {
+ this.totemCounterModule.getOptions().set(TotemCounterModule.DISABLE_LOCAL_TRACKING, value);
+}
+```
+
+### `overrideTotemCounter` & `overrideTotemCounters` Parameters
+
+1. `Recipients recipients`
+ - A list of all the player(s) you want to be able to see the updated totem counters.
+2. `TotemCounter totemCounter` or `Collection totemCounters`
+ - The totem counter(s) you want to display. See the [`TotemCounter` Options](#totemcounter-options) section for more.
+
+### `resetTotemCounter` Parameters
+
+1. `Recipients recipients`
+ - A list of all the player(s) you want to be able to see the updated totem counter.
+2. `UUID playerUuid`, `TotemCounter totemCounter` or `Collection playerUuids`
+ - The player(s) you want to reset the totem counter of.
+
+### `TotemCounter` Options
+
+`.playerUuid(UUID)` is the player, by UUID, that you wish to apply the totem counter to. Sending a totem counter for an already known player replaces that player's pops.
+
+```java
+.playerUuid(viewer.getUniqueId())
+```
+
+`.pops(Integer)` is the amount of totems the player has popped. Values are absolute, not increments, so sending the same value twice is harmless. The client keeps counting from this value, unless `DISABLE_LOCAL_TRACKING` is enabled.
+
+```java
+.pops(3)
+```
+
+
+
+
+
+
+**Lightweight Protobuf examples.** See [Lightweight Protobuf](/apollo/developers/lightweight/protobuf) for setup.
+
+
+**Overriding a Totem Counter**
+
+```java
+public void overrideTotemCounterExample(Player viewer) {
+ TotemCounter totemCounter = TotemCounter.newBuilder()
+ .setPlayerUuid(ProtobufUtil.createUuidProto(viewer.getUniqueId()))
+ .setPops(3)
+ .build();
+
+ OverrideTotemCountersMessage message = OverrideTotemCountersMessage.newBuilder()
+ .addTotemCounters(totemCounter)
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+}
+```
+
+**Overriding multiple Totem Counters**
+
+```java
+public void overrideTotemCountersExample(Player viewer) {
+ List totemCounters = Bukkit.getOnlinePlayers()
+ .stream().map(player -> TotemCounter.newBuilder()
+ .setPlayerUuid(ProtobufUtil.createUuidProto(player.getUniqueId()))
+ .setPops(2)
+ .build())
+ .collect(Collectors.toList());
+
+ OverrideTotemCountersMessage message = OverrideTotemCountersMessage.newBuilder()
+ .addAllTotemCounters(totemCounters)
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+}
+```
+
+**Resetting a Totem Counter**
+
+```java
+public void resetTotemCounterExample(Player viewer) {
+ ResetTotemCounterMessage message = ResetTotemCounterMessage.newBuilder()
+ .addPlayerUuids(ProtobufUtil.createUuidProto(viewer.getUniqueId()))
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+}
+```
+
+**Resetting all Totem Counters**
+
+```java
+public void resetTotemCountersExample(Player viewer) {
+ ResetTotemCountersMessage message = ResetTotemCountersMessage.getDefaultInstance();
+ ProtobufPacketUtil.sendPacket(viewer, message);
+}
+```
+
+**Tracking Totem Pops**
+
+```java
+private final Map totemPops = new ConcurrentHashMap<>();
+
+@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+private void onEntityResurrect(EntityResurrectEvent event) {
+ if (!(event.getEntity() instanceof Player)) {
+ return;
+ }
+
+ Player player = (Player) event.getEntity();
+ int pops = this.totemPops.merge(player.getUniqueId(), 1, Integer::sum);
+
+ // Pops are absolute, send the new total instead of an increment
+ TotemCounter totemCounter = TotemCounter.newBuilder()
+ .setPlayerUuid(ProtobufUtil.createUuidProto(player.getUniqueId()))
+ .setPops(pops)
+ .build();
+
+ OverrideTotemCountersMessage message = OverrideTotemCountersMessage.newBuilder()
+ .addTotemCounters(totemCounter)
+ .build();
+
+ ProtobufPacketUtil.broadcastPacket(message);
+}
+
+@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+private void onPlayerDeath(PlayerDeathEvent event) {
+ this.resetTotemPops(event.getEntity());
+}
+
+@EventHandler
+private void onPlayerQuit(PlayerQuitEvent event) {
+ this.resetTotemPops(event.getPlayer());
+}
+
+private void resetTotemPops(Player player) {
+ if (this.totemPops.remove(player.getUniqueId()) != null) {
+ ResetTotemCounterMessage message = ResetTotemCounterMessage.newBuilder()
+ .addPlayerUuids(ProtobufUtil.createUuidProto(player.getUniqueId()))
+ .build();
+
+ ProtobufPacketUtil.broadcastPacket(message);
+ }
+}
+```
+
+**Toggle Local Tracking**
+
+```java
+public void setDisableLocalTracking(boolean value) {
+ Map properties = new HashMap<>();
+ properties.put("disable-local-tracking", Value.newBuilder().setBoolValue(value).build());
+
+ ConfigurableSettings settings = ProtobufPacketUtil.createModuleMessage("totem_counter", properties);
+ ProtobufPacketUtil.broadcastPacket(settings);
+}
+```
+
+
+
+
+
+
+**Lightweight JSON examples.** See [Lightweight JSON](/apollo/developers/lightweight/json) for setup.
+
+
+**Overriding a Totem Counter**
+
+```java
+public void overrideTotemCounterExample(Player viewer) {
+ JsonObject totemCounter = new JsonObject();
+ totemCounter.add("player_uuid", JsonUtil.createUuidObject(viewer.getUniqueId()));
+ totemCounter.addProperty("pops", 3);
+
+ JsonArray totemCounters = new JsonArray();
+ totemCounters.add(totemCounter);
+
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.totemcounter.v1.OverrideTotemCountersMessage");
+ message.add("totem_counters", totemCounters);
+
+ JsonPacketUtil.sendPacket(viewer, message);
+}
+```
+
+**Overriding multiple Totem Counters**
+
+```java
+public void overrideTotemCountersExample(Player viewer) {
+ JsonArray totemCounters = new JsonArray();
+
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ JsonObject totemCounter = new JsonObject();
+ totemCounter.add("player_uuid", JsonUtil.createUuidObject(player.getUniqueId()));
+ totemCounter.addProperty("pops", 2);
+
+ totemCounters.add(totemCounter);
+ }
+
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.totemcounter.v1.OverrideTotemCountersMessage");
+ message.add("totem_counters", totemCounters);
+
+ JsonPacketUtil.sendPacket(viewer, message);
+}
+```
+
+**Resetting a Totem Counter**
+
+```java
+public void resetTotemCounterExample(Player viewer) {
+ JsonArray playerUuids = new JsonArray();
+ playerUuids.add(JsonUtil.createUuidObject(viewer.getUniqueId()));
+
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.totemcounter.v1.ResetTotemCounterMessage");
+ message.add("player_uuids", playerUuids);
+
+ JsonPacketUtil.sendPacket(viewer, message);
+}
+```
+
+**Resetting all Totem Counters**
+
+```java
+public void resetTotemCountersExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.totemcounter.v1.ResetTotemCountersMessage");
+
+ JsonPacketUtil.sendPacket(viewer, message);
+}
+```
+
+**Tracking Totem Pops**
+
+```java
+private final Map totemPops = new ConcurrentHashMap<>();
+
+@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+private void onEntityResurrect(EntityResurrectEvent event) {
+ if (!(event.getEntity() instanceof Player)) {
+ return;
+ }
+
+ Player player = (Player) event.getEntity();
+ int pops = this.totemPops.merge(player.getUniqueId(), 1, Integer::sum);
+
+ // Pops are absolute, send the new total instead of an increment
+ JsonObject totemCounter = new JsonObject();
+ totemCounter.add("player_uuid", JsonUtil.createUuidObject(player.getUniqueId()));
+ totemCounter.addProperty("pops", pops);
+
+ JsonArray totemCounters = new JsonArray();
+ totemCounters.add(totemCounter);
+
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.totemcounter.v1.OverrideTotemCountersMessage");
+ message.add("totem_counters", totemCounters);
+
+ JsonPacketUtil.broadcastPacket(message);
+}
+
+@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+private void onPlayerDeath(PlayerDeathEvent event) {
+ this.resetTotemPops(event.getEntity());
+}
+
+@EventHandler
+private void onPlayerQuit(PlayerQuitEvent event) {
+ this.resetTotemPops(event.getPlayer());
+}
+
+private void resetTotemPops(Player player) {
+ if (this.totemPops.remove(player.getUniqueId()) != null) {
+ JsonArray playerUuids = new JsonArray();
+ playerUuids.add(JsonUtil.createUuidObject(player.getUniqueId()));
+
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.totemcounter.v1.ResetTotemCounterMessage");
+ message.add("player_uuids", playerUuids);
+
+ JsonPacketUtil.broadcastPacket(message);
+ }
+}
+```
+
+**Toggle Local Tracking**
+
+```java
+public void setDisableLocalTracking(boolean value) {
+ Map properties = new HashMap<>();
+ properties.put("disable-local-tracking", value);
+
+ JsonObject message = JsonUtil.createEnableModuleObjectWithType("totem_counter", properties);
+ JsonPacketUtil.broadcastPacket(message);
+}
+```
+
+
+
+
+
+## Available options
+
+- __`DISABLE_LOCAL_TRACKING`__
+ - Whether the client stops tracking and resetting totem pops on its own.
+ - Values
+ - Type: `Boolean`
+ - Default: `false`
diff --git a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/ApolloApiExamplePlatform.java b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/ApolloApiExamplePlatform.java
index d9124449..d43b8bb6 100644
--- a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/ApolloApiExamplePlatform.java
+++ b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/ApolloApiExamplePlatform.java
@@ -59,6 +59,7 @@
import com.lunarclient.apollo.example.api.module.TebexApiExample;
import com.lunarclient.apollo.example.api.module.TitleApiExample;
import com.lunarclient.apollo.example.api.module.TntCountdownApiExample;
+import com.lunarclient.apollo.example.api.module.TotemCounterApiExample;
import com.lunarclient.apollo.example.api.module.TransferApiExample;
import com.lunarclient.apollo.example.api.module.VignetteApiExample;
import com.lunarclient.apollo.example.api.module.WaypointApiExample;
@@ -114,6 +115,7 @@ public void registerModuleExamples() {
this.setTebexExample(new TebexApiExample());
this.setTitleExample(new TitleApiExample());
this.setTntCountdownExample(new TntCountdownApiExample());
+ this.setTotemCounterExample(new TotemCounterApiExample());
this.setTransferExample(new TransferApiExample());
this.setVignetteExample(new VignetteApiExample());
this.setWaypointExample(new WaypointApiExample());
diff --git a/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/TotemCounterApiExample.java b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/TotemCounterApiExample.java
new file mode 100644
index 00000000..f916833c
--- /dev/null
+++ b/example/bukkit/api/src/main/java/com/lunarclient/apollo/example/api/module/TotemCounterApiExample.java
@@ -0,0 +1,140 @@
+/*
+ * This file is part of Apollo, licensed under the MIT License.
+ *
+ * Copyright (c) 2026 Moonsworth
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.lunarclient.apollo.example.api.module;
+
+import com.lunarclient.apollo.Apollo;
+import com.lunarclient.apollo.example.ApolloExamplePlugin;
+import com.lunarclient.apollo.example.module.impl.TotemCounterExample;
+import com.lunarclient.apollo.example.util.ServerUtil;
+import com.lunarclient.apollo.module.totemcounter.TotemCounter;
+import com.lunarclient.apollo.module.totemcounter.TotemCounterModule;
+import com.lunarclient.apollo.player.ApolloPlayer;
+import com.lunarclient.apollo.recipients.Recipients;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityResurrectEvent;
+import org.bukkit.event.entity.PlayerDeathEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+
+public class TotemCounterApiExample extends TotemCounterExample implements Listener {
+
+ private final TotemCounterModule totemCounterModule = Apollo.getModuleManager().getModule(TotemCounterModule.class);
+
+ private final Map totemPops = new ConcurrentHashMap<>();
+
+ public TotemCounterApiExample() {
+ if (ServerUtil.hasEntityResurrectEvent()) {
+ Bukkit.getPluginManager().registerEvents(this, ApolloExamplePlugin.getInstance());
+ }
+ }
+
+ @Override
+ public void overrideTotemCounterExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+
+ apolloPlayerOpt.ifPresent(apolloPlayer -> {
+ this.totemCounterModule.overrideTotemCounter(apolloPlayer, TotemCounter.builder()
+ .playerUuid(viewer.getUniqueId())
+ .pops(3)
+ .build()
+ );
+ });
+ }
+
+ @Override
+ public void overrideTotemCountersExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+
+ apolloPlayerOpt.ifPresent(apolloPlayer -> {
+ List totemCounters = Bukkit.getOnlinePlayers()
+ .stream().map(player -> TotemCounter.builder()
+ .playerUuid(player.getUniqueId())
+ .pops(2)
+ .build())
+ .collect(Collectors.toList());
+
+ this.totemCounterModule.overrideTotemCounters(apolloPlayer, totemCounters);
+ });
+ }
+
+ @Override
+ public void resetTotemCounterExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(apolloPlayer -> this.totemCounterModule.resetTotemCounter(apolloPlayer, viewer.getUniqueId()));
+ }
+
+ @Override
+ public void resetTotemCountersExample(Player viewer) {
+ Optional apolloPlayerOpt = Apollo.getPlayerManager().getPlayer(viewer.getUniqueId());
+ apolloPlayerOpt.ifPresent(this.totemCounterModule::resetTotemCounters);
+ }
+
+ @Override
+ public void setDisableLocalTracking(boolean value) {
+ this.totemCounterModule.getOptions().set(TotemCounterModule.DISABLE_LOCAL_TRACKING, value);
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ private void onEntityResurrect(EntityResurrectEvent event) {
+ if (!(event.getEntity() instanceof Player)) {
+ return;
+ }
+
+ Player player = (Player) event.getEntity();
+ int pops = this.totemPops.merge(player.getUniqueId(), 1, Integer::sum);
+
+ // Pops are absolute, send the new total instead of an increment
+ this.totemCounterModule.overrideTotemCounter(Recipients.ofEveryone(), TotemCounter.builder()
+ .playerUuid(player.getUniqueId())
+ .pops(pops)
+ .build()
+ );
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ private void onPlayerDeath(PlayerDeathEvent event) {
+ this.resetTotemPops(event.getEntity());
+ }
+
+ @EventHandler
+ private void onPlayerQuit(PlayerQuitEvent event) {
+ this.resetTotemPops(event.getPlayer());
+ }
+
+ private void resetTotemPops(Player player) {
+ if (this.totemPops.remove(player.getUniqueId()) != null) {
+ this.totemCounterModule.resetTotemCounter(Recipients.ofEveryone(), player.getUniqueId());
+ }
+ }
+
+}
diff --git a/example/bukkit/api/src/main/resources/plugin.yml b/example/bukkit/api/src/main/resources/plugin.yml
index 9ac047c9..db41169d 100644
--- a/example/bukkit/api/src/main/resources/plugin.yml
+++ b/example/bukkit/api/src/main/resources/plugin.yml
@@ -76,6 +76,8 @@ commands:
description: "Titles!"
tntcountdown:
description: "TNT Countdown!"
+ totemcounter:
+ description: "Totem Counter!"
transfer:
description: "Transfer!"
vignette:
diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/ApolloExamplePlugin.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/ApolloExamplePlugin.java
index a116a259..a341047e 100644
--- a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/ApolloExamplePlugin.java
+++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/ApolloExamplePlugin.java
@@ -55,6 +55,7 @@
import com.lunarclient.apollo.example.command.TebexCommand;
import com.lunarclient.apollo.example.command.TitleCommand;
import com.lunarclient.apollo.example.command.TntCountdownCommand;
+import com.lunarclient.apollo.example.command.TotemCounterCommand;
import com.lunarclient.apollo.example.command.TransferCommand;
import com.lunarclient.apollo.example.command.VignetteCommand;
import com.lunarclient.apollo.example.command.WaypointCommand;
@@ -89,6 +90,7 @@
import com.lunarclient.apollo.example.module.impl.TebexExample;
import com.lunarclient.apollo.example.module.impl.TitleExample;
import com.lunarclient.apollo.example.module.impl.TntCountdownExample;
+import com.lunarclient.apollo.example.module.impl.TotemCounterExample;
import com.lunarclient.apollo.example.module.impl.TransferExample;
import com.lunarclient.apollo.example.module.impl.VignetteExample;
import com.lunarclient.apollo.example.module.impl.WaypointExample;
@@ -137,6 +139,7 @@ public abstract class ApolloExamplePlugin extends JavaPlugin {
private TebexExample tebexExample;
private TitleExample titleExample;
private TntCountdownExample tntCountdownExample;
+ private TotemCounterExample totemCounterExample;
private TransferExample transferExample;
private VignetteExample vignetteExample;
private WaypointExample waypointExample;
@@ -204,6 +207,7 @@ private void registerCommonCommands() {
this.getCommand("tebex").setExecutor(new TebexCommand());
this.getCommand("title").setExecutor(new TitleCommand());
this.getCommand("tntcountdown").setExecutor(new TntCountdownCommand());
+ this.getCommand("totemcounter").setExecutor(new TotemCounterCommand());
this.getCommand("transfer").setExecutor(new TransferCommand());
this.getCommand("vignette").setExecutor(new VignetteCommand());
this.getCommand("waypoint").setExecutor(new WaypointCommand());
diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/TotemCounterCommand.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/TotemCounterCommand.java
new file mode 100644
index 00000000..667cf84e
--- /dev/null
+++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/command/TotemCounterCommand.java
@@ -0,0 +1,94 @@
+/*
+ * This file is part of Apollo, licensed under the MIT License.
+ *
+ * Copyright (c) 2026 Moonsworth
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.lunarclient.apollo.example.command;
+
+import com.lunarclient.apollo.example.ApolloExamplePlugin;
+import com.lunarclient.apollo.example.module.impl.TotemCounterExample;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+public class TotemCounterCommand implements CommandExecutor {
+
+ @Override
+ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
+ if (!(sender instanceof Player)) {
+ sender.sendMessage("Player only!");
+ return true;
+ }
+
+ Player player = (Player) sender;
+ TotemCounterExample totemCounterExample = ApolloExamplePlugin.getInstance().getTotemCounterExample();
+
+ if (args.length == 2 && args[0].equalsIgnoreCase("disableLocalTracking")) {
+ boolean value = Boolean.parseBoolean(args[1]);
+ totemCounterExample.setDisableLocalTracking(value);
+
+ player.sendMessage("Disable local tracking has been set to " + value);
+ return true;
+ }
+
+ if (args.length != 1) {
+ player.sendMessage("Usage: /totemcounter ");
+ player.sendMessage("Usage: /totemcounter ");
+ return true;
+ }
+
+ switch (args[0].toLowerCase()) {
+ case "override": {
+ totemCounterExample.overrideTotemCounterExample(player);
+ player.sendMessage("Overriding totem counter....");
+ break;
+ }
+
+ case "overrideall": {
+ totemCounterExample.overrideTotemCountersExample(player);
+ player.sendMessage("Overriding totem counters....");
+ break;
+ }
+
+ case "reset": {
+ totemCounterExample.resetTotemCounterExample(player);
+ player.sendMessage("Resetting totem counter....");
+ break;
+ }
+
+ case "clear": {
+ totemCounterExample.resetTotemCountersExample(player);
+ player.sendMessage("Clearing totem counters...");
+ break;
+ }
+
+ default: {
+ player.sendMessage("Usage: /totemcounter ");
+ player.sendMessage("Usage: /totemcounter ");
+ break;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/TotemCounterExample.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/TotemCounterExample.java
new file mode 100644
index 00000000..766f13f5
--- /dev/null
+++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/module/impl/TotemCounterExample.java
@@ -0,0 +1,41 @@
+/*
+ * This file is part of Apollo, licensed under the MIT License.
+ *
+ * Copyright (c) 2026 Moonsworth
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.lunarclient.apollo.example.module.impl;
+
+import com.lunarclient.apollo.example.module.ApolloModuleExample;
+import org.bukkit.entity.Player;
+
+public abstract class TotemCounterExample extends ApolloModuleExample {
+
+ public abstract void overrideTotemCounterExample(Player viewer);
+
+ public abstract void overrideTotemCountersExample(Player viewer);
+
+ public abstract void resetTotemCounterExample(Player viewer);
+
+ public abstract void resetTotemCountersExample(Player viewer);
+
+ public abstract void setDisableLocalTracking(boolean value);
+
+}
diff --git a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/util/ServerUtil.java b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/util/ServerUtil.java
index f96c8568..6280e66e 100644
--- a/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/util/ServerUtil.java
+++ b/example/bukkit/common/src/main/java/com/lunarclient/apollo/example/util/ServerUtil.java
@@ -34,6 +34,15 @@ public static boolean isFolia() {
}
}
+ public static boolean hasEntityResurrectEvent() {
+ try {
+ Class.forName("org.bukkit.event.entity.EntityResurrectEvent");
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
private ServerUtil() {
}
diff --git a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/ApolloJsonExamplePlatform.java b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/ApolloJsonExamplePlatform.java
index 22a99601..0c84c89f 100644
--- a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/ApolloJsonExamplePlatform.java
+++ b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/ApolloJsonExamplePlatform.java
@@ -55,6 +55,7 @@
import com.lunarclient.apollo.example.json.module.TebexJsonExample;
import com.lunarclient.apollo.example.json.module.TitleJsonExample;
import com.lunarclient.apollo.example.json.module.TntCountdownJsonExample;
+import com.lunarclient.apollo.example.json.module.TotemCounterJsonExample;
import com.lunarclient.apollo.example.json.module.TransferJsonExample;
import com.lunarclient.apollo.example.json.module.VignetteJsonExample;
import com.lunarclient.apollo.example.json.module.WaypointJsonExample;
@@ -102,6 +103,7 @@ public void registerModuleExamples() {
this.setTebexExample(new TebexJsonExample());
this.setTitleExample(new TitleJsonExample());
this.setTntCountdownExample(new TntCountdownJsonExample());
+ this.setTotemCounterExample(new TotemCounterJsonExample());
this.setTransferExample(new TransferJsonExample());
this.setVignetteExample(new VignetteJsonExample());
this.setWaypointExample(new WaypointJsonExample());
diff --git a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/TotemCounterJsonExample.java b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/TotemCounterJsonExample.java
new file mode 100644
index 00000000..c5caf24c
--- /dev/null
+++ b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/module/TotemCounterJsonExample.java
@@ -0,0 +1,167 @@
+/*
+ * This file is part of Apollo, licensed under the MIT License.
+ *
+ * Copyright (c) 2026 Moonsworth
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.lunarclient.apollo.example.json.module;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.lunarclient.apollo.example.ApolloExamplePlugin;
+import com.lunarclient.apollo.example.json.util.JsonPacketUtil;
+import com.lunarclient.apollo.example.json.util.JsonUtil;
+import com.lunarclient.apollo.example.module.impl.TotemCounterExample;
+import com.lunarclient.apollo.example.util.ServerUtil;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityResurrectEvent;
+import org.bukkit.event.entity.PlayerDeathEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+
+public class TotemCounterJsonExample extends TotemCounterExample implements Listener {
+
+ private final Map totemPops = new ConcurrentHashMap<>();
+
+ public TotemCounterJsonExample() {
+ if (ServerUtil.hasEntityResurrectEvent()) {
+ Bukkit.getPluginManager().registerEvents(this, ApolloExamplePlugin.getInstance());
+ }
+ }
+
+ @Override
+ public void overrideTotemCounterExample(Player viewer) {
+ JsonObject totemCounter = new JsonObject();
+ totemCounter.add("player_uuid", JsonUtil.createUuidObject(viewer.getUniqueId()));
+ totemCounter.addProperty("pops", 3);
+
+ JsonArray totemCounters = new JsonArray();
+ totemCounters.add(totemCounter);
+
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.totemcounter.v1.OverrideTotemCountersMessage");
+ message.add("totem_counters", totemCounters);
+
+ JsonPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void overrideTotemCountersExample(Player viewer) {
+ JsonArray totemCounters = new JsonArray();
+
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ JsonObject totemCounter = new JsonObject();
+ totemCounter.add("player_uuid", JsonUtil.createUuidObject(player.getUniqueId()));
+ totemCounter.addProperty("pops", 2);
+
+ totemCounters.add(totemCounter);
+ }
+
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.totemcounter.v1.OverrideTotemCountersMessage");
+ message.add("totem_counters", totemCounters);
+
+ JsonPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void resetTotemCounterExample(Player viewer) {
+ JsonArray playerUuids = new JsonArray();
+ playerUuids.add(JsonUtil.createUuidObject(viewer.getUniqueId()));
+
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.totemcounter.v1.ResetTotemCounterMessage");
+ message.add("player_uuids", playerUuids);
+
+ JsonPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void resetTotemCountersExample(Player viewer) {
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.totemcounter.v1.ResetTotemCountersMessage");
+
+ JsonPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void setDisableLocalTracking(boolean value) {
+ Map properties = new HashMap<>();
+ properties.put("disable-local-tracking", value);
+
+ JsonObject message = JsonUtil.createEnableModuleObjectWithType("totem_counter", properties);
+ JsonPacketUtil.broadcastPacket(message);
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ private void onEntityResurrect(EntityResurrectEvent event) {
+ if (!(event.getEntity() instanceof Player)) {
+ return;
+ }
+
+ Player player = (Player) event.getEntity();
+ int pops = this.totemPops.merge(player.getUniqueId(), 1, Integer::sum);
+
+ // Pops are absolute, send the new total instead of an increment
+ JsonObject totemCounter = new JsonObject();
+ totemCounter.add("player_uuid", JsonUtil.createUuidObject(player.getUniqueId()));
+ totemCounter.addProperty("pops", pops);
+
+ JsonArray totemCounters = new JsonArray();
+ totemCounters.add(totemCounter);
+
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.totemcounter.v1.OverrideTotemCountersMessage");
+ message.add("totem_counters", totemCounters);
+
+ JsonPacketUtil.broadcastPacket(message);
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ private void onPlayerDeath(PlayerDeathEvent event) {
+ this.resetTotemPops(event.getEntity());
+ }
+
+ @EventHandler
+ private void onPlayerQuit(PlayerQuitEvent event) {
+ this.resetTotemPops(event.getPlayer());
+ }
+
+ private void resetTotemPops(Player player) {
+ if (this.totemPops.remove(player.getUniqueId()) != null) {
+ JsonArray playerUuids = new JsonArray();
+ playerUuids.add(JsonUtil.createUuidObject(player.getUniqueId()));
+
+ JsonObject message = new JsonObject();
+ message.addProperty("@type", "type.googleapis.com/lunarclient.apollo.totemcounter.v1.ResetTotemCounterMessage");
+ message.add("player_uuids", playerUuids);
+
+ JsonPacketUtil.broadcastPacket(message);
+ }
+ }
+
+}
diff --git a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/util/JsonPacketUtil.java b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/util/JsonPacketUtil.java
index 59733a24..38c4b260 100644
--- a/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/util/JsonPacketUtil.java
+++ b/example/bukkit/json/src/main/java/com/lunarclient/apollo/example/json/util/JsonPacketUtil.java
@@ -42,7 +42,7 @@ public final class JsonPacketUtil {
private static final List APOLLO_MODULES = Arrays.asList("auto_text_hotkey", "beam", "border", "chat", "colored_fire", "combat", "cooldown",
"cosmetic", "entity", "glint", "glow", "hologram", "inventory", "limb", "marker", "mod_setting", "nametag", "nick_hider", "notification", "pay_now", "packet_enrichment",
- "rich_presence", "saturation", "server_link", "server_rule", "staff_mod", "stopwatch", "team", "tebex", "title", "tnt_countdown", "transfer", "vignette", "waypoint"
+ "rich_presence", "saturation", "server_link", "server_rule", "staff_mod", "stopwatch", "team", "tebex", "title", "tnt_countdown", "totem_counter", "transfer", "vignette", "waypoint"
);
// Module Id -> Option key -> Object
@@ -79,6 +79,7 @@ public final class JsonPacketUtil {
CONFIG_MODULE_PROPERTIES.put("server_rule", "crystal-optimizer", false);
CONFIG_MODULE_PROPERTIES.put("tnt_countdown", "tnt-ticks", 80);
CONFIG_MODULE_PROPERTIES.put("title", "clear-title-on-server-switch", false);
+ CONFIG_MODULE_PROPERTIES.put("totem_counter", "disable-local-tracking", false);
CONFIG_MODULE_PROPERTIES.put("waypoint", "server-handles-waypoints", false);
}
diff --git a/example/bukkit/json/src/main/resources/plugin.yml b/example/bukkit/json/src/main/resources/plugin.yml
index 64cd0c62..f6a3c0c8 100644
--- a/example/bukkit/json/src/main/resources/plugin.yml
+++ b/example/bukkit/json/src/main/resources/plugin.yml
@@ -72,6 +72,8 @@ commands:
description: "Titles!"
tntcountdown:
description: "TNT Countdown!"
+ totemcounter:
+ description: "Totem Counter!"
transfer:
description: "Transfer!"
vignette:
diff --git a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/ApolloProtoExamplePlatform.java b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/ApolloProtoExamplePlatform.java
index 680a71e2..09d3a28e 100644
--- a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/ApolloProtoExamplePlatform.java
+++ b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/ApolloProtoExamplePlatform.java
@@ -55,6 +55,7 @@
import com.lunarclient.apollo.example.proto.module.TebexProtoExample;
import com.lunarclient.apollo.example.proto.module.TitleProtoExample;
import com.lunarclient.apollo.example.proto.module.TntCountdownProtoExample;
+import com.lunarclient.apollo.example.proto.module.TotemCounterProtoExample;
import com.lunarclient.apollo.example.proto.module.TransferProtoExample;
import com.lunarclient.apollo.example.proto.module.VignetteProtoExample;
import com.lunarclient.apollo.example.proto.module.WaypointProtoExample;
@@ -102,6 +103,7 @@ public void registerModuleExamples() {
this.setTebexExample(new TebexProtoExample());
this.setTitleExample(new TitleProtoExample());
this.setTntCountdownExample(new TntCountdownProtoExample());
+ this.setTotemCounterExample(new TotemCounterProtoExample());
this.setTransferExample(new TransferProtoExample());
this.setVignetteExample(new VignetteProtoExample());
this.setWaypointExample(new WaypointProtoExample());
diff --git a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/TotemCounterProtoExample.java b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/TotemCounterProtoExample.java
new file mode 100644
index 00000000..255b36bc
--- /dev/null
+++ b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/module/TotemCounterProtoExample.java
@@ -0,0 +1,158 @@
+/*
+ * This file is part of Apollo, licensed under the MIT License.
+ *
+ * Copyright (c) 2026 Moonsworth
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.lunarclient.apollo.example.proto.module;
+
+import com.google.protobuf.Value;
+import com.lunarclient.apollo.configurable.v1.ConfigurableSettings;
+import com.lunarclient.apollo.example.ApolloExamplePlugin;
+import com.lunarclient.apollo.example.module.impl.TotemCounterExample;
+import com.lunarclient.apollo.example.proto.util.ProtobufPacketUtil;
+import com.lunarclient.apollo.example.proto.util.ProtobufUtil;
+import com.lunarclient.apollo.example.util.ServerUtil;
+import com.lunarclient.apollo.totemcounter.v1.OverrideTotemCountersMessage;
+import com.lunarclient.apollo.totemcounter.v1.ResetTotemCounterMessage;
+import com.lunarclient.apollo.totemcounter.v1.ResetTotemCountersMessage;
+import com.lunarclient.apollo.totemcounter.v1.TotemCounter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityResurrectEvent;
+import org.bukkit.event.entity.PlayerDeathEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+
+public class TotemCounterProtoExample extends TotemCounterExample implements Listener {
+
+ private final Map totemPops = new ConcurrentHashMap<>();
+
+ public TotemCounterProtoExample() {
+ if (ServerUtil.hasEntityResurrectEvent()) {
+ Bukkit.getPluginManager().registerEvents(this, ApolloExamplePlugin.getInstance());
+ }
+ }
+
+ @Override
+ public void overrideTotemCounterExample(Player viewer) {
+ TotemCounter totemCounter = TotemCounter.newBuilder()
+ .setPlayerUuid(ProtobufUtil.createUuidProto(viewer.getUniqueId()))
+ .setPops(3)
+ .build();
+
+ OverrideTotemCountersMessage message = OverrideTotemCountersMessage.newBuilder()
+ .addTotemCounters(totemCounter)
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void overrideTotemCountersExample(Player viewer) {
+ List totemCounters = Bukkit.getOnlinePlayers()
+ .stream().map(player -> TotemCounter.newBuilder()
+ .setPlayerUuid(ProtobufUtil.createUuidProto(player.getUniqueId()))
+ .setPops(2)
+ .build())
+ .collect(Collectors.toList());
+
+ OverrideTotemCountersMessage message = OverrideTotemCountersMessage.newBuilder()
+ .addAllTotemCounters(totemCounters)
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void resetTotemCounterExample(Player viewer) {
+ ResetTotemCounterMessage message = ResetTotemCounterMessage.newBuilder()
+ .addPlayerUuids(ProtobufUtil.createUuidProto(viewer.getUniqueId()))
+ .build();
+
+ ProtobufPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void resetTotemCountersExample(Player viewer) {
+ ResetTotemCountersMessage message = ResetTotemCountersMessage.getDefaultInstance();
+ ProtobufPacketUtil.sendPacket(viewer, message);
+ }
+
+ @Override
+ public void setDisableLocalTracking(boolean value) {
+ Map properties = new HashMap<>();
+ properties.put("disable-local-tracking", Value.newBuilder().setBoolValue(value).build());
+
+ ConfigurableSettings settings = ProtobufPacketUtil.createModuleMessage("totem_counter", properties);
+ ProtobufPacketUtil.broadcastPacket(settings);
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ private void onEntityResurrect(EntityResurrectEvent event) {
+ if (!(event.getEntity() instanceof Player)) {
+ return;
+ }
+
+ Player player = (Player) event.getEntity();
+ int pops = this.totemPops.merge(player.getUniqueId(), 1, Integer::sum);
+
+ // Pops are absolute, send the new total instead of an increment
+ TotemCounter totemCounter = TotemCounter.newBuilder()
+ .setPlayerUuid(ProtobufUtil.createUuidProto(player.getUniqueId()))
+ .setPops(pops)
+ .build();
+
+ OverrideTotemCountersMessage message = OverrideTotemCountersMessage.newBuilder()
+ .addTotemCounters(totemCounter)
+ .build();
+
+ ProtobufPacketUtil.broadcastPacket(message);
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ private void onPlayerDeath(PlayerDeathEvent event) {
+ this.resetTotemPops(event.getEntity());
+ }
+
+ @EventHandler
+ private void onPlayerQuit(PlayerQuitEvent event) {
+ this.resetTotemPops(event.getPlayer());
+ }
+
+ private void resetTotemPops(Player player) {
+ if (this.totemPops.remove(player.getUniqueId()) != null) {
+ ResetTotemCounterMessage message = ResetTotemCounterMessage.newBuilder()
+ .addPlayerUuids(ProtobufUtil.createUuidProto(player.getUniqueId()))
+ .build();
+
+ ProtobufPacketUtil.broadcastPacket(message);
+ }
+ }
+
+}
diff --git a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/util/ProtobufPacketUtil.java b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/util/ProtobufPacketUtil.java
index b5171f58..7b74c74b 100644
--- a/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/util/ProtobufPacketUtil.java
+++ b/example/bukkit/proto/src/main/java/com/lunarclient/apollo/example/proto/util/ProtobufPacketUtil.java
@@ -43,7 +43,7 @@ public final class ProtobufPacketUtil {
private static final List APOLLO_MODULES = Arrays.asList("auto_text_hotkey", "beam", "border", "chat", "colored_fire", "combat", "cooldown",
"cosmetic", "entity", "glint", "glow", "hologram", "inventory", "limb", "marker", "mod_setting", "nametag", "nick_hider", "notification", "pay_now", "packet_enrichment",
- "rich_presence", "saturation", "server_link", "server_rule", "staff_mod", "stopwatch", "team", "tebex", "title", "tnt_countdown", "transfer", "vignette", "waypoint"
+ "rich_presence", "saturation", "server_link", "server_rule", "staff_mod", "stopwatch", "team", "tebex", "title", "tnt_countdown", "totem_counter", "transfer", "vignette", "waypoint"
);
// Module Id -> Option key -> Value
@@ -86,6 +86,7 @@ public final class ProtobufPacketUtil {
CONFIG_MODULE_PROPERTIES.put("server_rule", "crystal-optimizer", Value.newBuilder().setBoolValue(false).build());
CONFIG_MODULE_PROPERTIES.put("tnt_countdown", "tnt-ticks", Value.newBuilder().setNumberValue(80).build());
CONFIG_MODULE_PROPERTIES.put("title", "clear-title-on-server-switch", Value.newBuilder().setBoolValue(false).build());
+ CONFIG_MODULE_PROPERTIES.put("totem_counter", "disable-local-tracking", Value.newBuilder().setBoolValue(false).build());
CONFIG_MODULE_PROPERTIES.put("waypoint", "server-handles-waypoints", Value.newBuilder().setBoolValue(false).build());
}
diff --git a/example/bukkit/proto/src/main/resources/plugin.yml b/example/bukkit/proto/src/main/resources/plugin.yml
index da8661d0..8cb0cbeb 100644
--- a/example/bukkit/proto/src/main/resources/plugin.yml
+++ b/example/bukkit/proto/src/main/resources/plugin.yml
@@ -72,6 +72,8 @@ commands:
description: "Titles!"
tntcountdown:
description: "TNT Countdown!"
+ totemcounter:
+ description: "Totem Counter!"
transfer:
description: "Transfer!"
vignette:
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 5099e64b..ba4c1777 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.4"
+protobuf = "0.2.5"
gson = "2.10.1"
shadow = "9.4.1"
spotless = "8.4.0"
diff --git a/platform/bukkit/src/main/java/com/lunarclient/apollo/ApolloBukkitPlatform.java b/platform/bukkit/src/main/java/com/lunarclient/apollo/ApolloBukkitPlatform.java
index dd49d311..f3d83d56 100644
--- a/platform/bukkit/src/main/java/com/lunarclient/apollo/ApolloBukkitPlatform.java
+++ b/platform/bukkit/src/main/java/com/lunarclient/apollo/ApolloBukkitPlatform.java
@@ -90,6 +90,8 @@
import com.lunarclient.apollo.module.title.TitleModuleImpl;
import com.lunarclient.apollo.module.tntcountdown.TntCountdownModule;
import com.lunarclient.apollo.module.tntcountdown.TntCountdownModuleImpl;
+import com.lunarclient.apollo.module.totemcounter.TotemCounterModule;
+import com.lunarclient.apollo.module.totemcounter.TotemCounterModuleImpl;
import com.lunarclient.apollo.module.transfer.TransferModule;
import com.lunarclient.apollo.module.transfer.TransferModuleImpl;
import com.lunarclient.apollo.module.vignette.VignetteModule;
@@ -174,6 +176,7 @@ public void onEnable() {
.addModule(TebexModule.class, new TebexModuleImpl())
.addModule(TitleModule.class, new TitleModuleImpl())
.addModule(TntCountdownModule.class, new TntCountdownModuleImpl())
+ .addModule(TotemCounterModule.class, new TotemCounterModuleImpl())
.addModule(TransferModule.class, new TransferModuleImpl())
.addModule(VignetteModule.class, new VignetteModuleImpl())
.addModule(WaypointModule.class, new WaypointModuleImpl());
diff --git a/platform/bungee/src/main/java/com/lunarclient/apollo/ApolloBungeePlatform.java b/platform/bungee/src/main/java/com/lunarclient/apollo/ApolloBungeePlatform.java
index 2c35eb66..9e349427 100644
--- a/platform/bungee/src/main/java/com/lunarclient/apollo/ApolloBungeePlatform.java
+++ b/platform/bungee/src/main/java/com/lunarclient/apollo/ApolloBungeePlatform.java
@@ -79,6 +79,8 @@
import com.lunarclient.apollo.module.tebex.TebexModuleImpl;
import com.lunarclient.apollo.module.title.TitleModule;
import com.lunarclient.apollo.module.title.TitleModuleImpl;
+import com.lunarclient.apollo.module.totemcounter.TotemCounterModule;
+import com.lunarclient.apollo.module.totemcounter.TotemCounterModuleImpl;
import com.lunarclient.apollo.module.transfer.TransferModule;
import com.lunarclient.apollo.module.transfer.TransferModuleImpl;
import com.lunarclient.apollo.module.vignette.VignetteModule;
@@ -153,6 +155,7 @@ public void onEnable() {
.addModule(TeamModule.class, new TeamModuleImpl())
.addModule(TebexModule.class, new TebexModuleImpl())
.addModule(TitleModule.class, new TitleModuleImpl())
+ .addModule(TotemCounterModule.class, new TotemCounterModuleImpl())
.addModule(TransferModule.class, new TransferModuleImpl())
.addModule(VignetteModule.class, new VignetteModuleImpl())
.addModule(WaypointModule.class, new WaypointModuleImpl());
diff --git a/platform/folia/src/main/java/com/lunarclient/apollo/ApolloFoliaPlatform.java b/platform/folia/src/main/java/com/lunarclient/apollo/ApolloFoliaPlatform.java
index 1bf3caea..4ee2734b 100644
--- a/platform/folia/src/main/java/com/lunarclient/apollo/ApolloFoliaPlatform.java
+++ b/platform/folia/src/main/java/com/lunarclient/apollo/ApolloFoliaPlatform.java
@@ -87,6 +87,8 @@
import com.lunarclient.apollo.module.title.TitleModuleImpl;
import com.lunarclient.apollo.module.tntcountdown.TntCountdownModule;
import com.lunarclient.apollo.module.tntcountdown.TntCountdownModuleImpl;
+import com.lunarclient.apollo.module.totemcounter.TotemCounterModule;
+import com.lunarclient.apollo.module.totemcounter.TotemCounterModuleImpl;
import com.lunarclient.apollo.module.transfer.TransferModule;
import com.lunarclient.apollo.module.transfer.TransferModuleImpl;
import com.lunarclient.apollo.module.vignette.VignetteModule;
@@ -163,6 +165,7 @@ public void onEnable() {
.addModule(TebexModule.class, new TebexModuleImpl())
.addModule(TitleModule.class, new TitleModuleImpl())
.addModule(TntCountdownModule.class, new TntCountdownModuleImpl())
+ .addModule(TotemCounterModule.class, new TotemCounterModuleImpl())
.addModule(TransferModule.class, new TransferModuleImpl())
.addModule(VignetteModule.class, new VignetteModuleImpl())
.addModule(WaypointModule.class, new WaypointModuleImpl());
diff --git a/platform/minestom/src/main/java/com/lunarclient/apollo/ApolloMinestomPlatform.java b/platform/minestom/src/main/java/com/lunarclient/apollo/ApolloMinestomPlatform.java
index 60ac7559..1c3a32e2 100644
--- a/platform/minestom/src/main/java/com/lunarclient/apollo/ApolloMinestomPlatform.java
+++ b/platform/minestom/src/main/java/com/lunarclient/apollo/ApolloMinestomPlatform.java
@@ -89,6 +89,8 @@
import com.lunarclient.apollo.module.title.TitleModuleImpl;
import com.lunarclient.apollo.module.tntcountdown.TntCountdownModule;
import com.lunarclient.apollo.module.tntcountdown.TntCountdownModuleImpl;
+import com.lunarclient.apollo.module.totemcounter.TotemCounterModule;
+import com.lunarclient.apollo.module.totemcounter.TotemCounterModuleImpl;
import com.lunarclient.apollo.module.transfer.TransferModule;
import com.lunarclient.apollo.module.transfer.TransferModuleImpl;
import com.lunarclient.apollo.module.vignette.VignetteModule;
@@ -198,6 +200,7 @@ public static void init(ApolloMinestomProperties properties) {
.addModule(TebexModule.class, new TebexModuleImpl())
.addModule(TitleModule.class, new TitleModuleImpl())
.addModule(TntCountdownModule.class, new TntCountdownModuleImpl())
+ .addModule(TotemCounterModule.class, new TotemCounterModuleImpl())
.addModule(TransferModule.class, new TransferModuleImpl())
.addModule(VignetteModule.class, new VignetteModuleImpl())
.addModule(WaypointModule.class, new WaypointModuleImpl());
diff --git a/platform/velocity/src/main/java/com/lunarclient/apollo/ApolloVelocityPlatform.java b/platform/velocity/src/main/java/com/lunarclient/apollo/ApolloVelocityPlatform.java
index cacb9393..69bd2e43 100644
--- a/platform/velocity/src/main/java/com/lunarclient/apollo/ApolloVelocityPlatform.java
+++ b/platform/velocity/src/main/java/com/lunarclient/apollo/ApolloVelocityPlatform.java
@@ -79,6 +79,8 @@
import com.lunarclient.apollo.module.tebex.TebexModuleImpl;
import com.lunarclient.apollo.module.title.TitleModule;
import com.lunarclient.apollo.module.title.TitleModuleImpl;
+import com.lunarclient.apollo.module.totemcounter.TotemCounterModule;
+import com.lunarclient.apollo.module.totemcounter.TotemCounterModuleImpl;
import com.lunarclient.apollo.module.transfer.TransferModule;
import com.lunarclient.apollo.module.transfer.TransferModuleImpl;
import com.lunarclient.apollo.module.vignette.VignetteModule;
@@ -236,6 +238,7 @@ public void onProxyInitialization(ProxyInitializeEvent event) {
.addModule(TeamModule.class, new TeamModuleImpl())
.addModule(TebexModule.class, new TebexModuleImpl())
.addModule(TitleModule.class, new TitleModuleImpl())
+ .addModule(TotemCounterModule.class, new TotemCounterModuleImpl())
.addModule(TransferModule.class, new TransferModuleImpl())
.addModule(VignetteModule.class, new VignetteModuleImpl())
.addModule(WaypointModule.class, new WaypointModuleImpl());